<jupyter_start><jupyter_text>Character RNN(CharRNN) 모델 설명 및 학습과 생성<jupyter_code>import os
from easydict import EasyDict
from typing import List, Tuple, Dict, Union

from laiddmg import (
  CharRNNConfig,
  Tokenizer,
  CharRNNModel,
  get_rawdataset,
  get_dataset,
)

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.nn.utils.rnn as rnn_utils
import torch.optim as optim<jupyter_output><empty_output><jupyter_text>configuration, tokenizer, model 생성* `CharRNNConfig` class:  * 모델을 구성하기 위해 필요한 정보(`hidden_dim`, `num_layers` 등)들이 담긴 class입니다.  * 자세한 코드는 [`laiddmg/models/char_rnn/configuration.py`](https://github.com/ilguyi/LAIDD-molecular-generation/blob/main/laiddmg/models/char_rnn/configuration.py)에 나와 있습니다.* `Tokenizer` class:  * `str`으로 된 SMILES 데이터를 미리 정의해둔 `vocab_dict`에 맞춰 token data(`int`)로 바꿔주는 역할을 합니다.  * 자세한 코드는 [`laiddmg/tokenization_utils.py`](https://github.com/ilguyi/LAIDD-molecular-generation/blob/main/laiddmg/tokenization_utils.py)에 나와 있습니다.* `CharRNNModel` class:  * 실제 모델을 만들어주는 클래스입니다.  * `PyTorch`에서 제공하는 표준적인 방법으로 클래스를 구성하였습니다. tutorial은 [https://pytorch.org/tutorials/beginner/basics/buildmodel_tutorial.html](https://pytorch.org/tutorials/beginner/basics/buildmodel_tutorial.html) 여기서 확인할 수 있습니다.  * 이 모델은 <NAME>, et. al., [Generating Focused Molecule Libraries for Drug Discovery with Recurrent Neural Networks](https://pubs.acs.org/doi/10.1021/acscentsci.7b00512)을 바탕으로 작성하였습니다.  * 자세한 코드는 [`laiddmg/models/char_rnn/modeling.py`](https://github.com/ilguyi/LAIDD-molecular-generation/blob/main/laiddmg/models/char_rnn/modeling.py)에 나와 있습니다.<jupyter_code>model_type = 'char_rnn'
config = CharRNNConfig()
tokenizer = Tokenizer()
model = CharRNNModel(config)<jupyter_output><empty_output><jupyter_text>Print model configuration<jupyter_code>for k, v in config.__dict__.items():
  print(f'{k}: {v}')<jupyter_output><empty_output><jupyter_text>How to use tokenizer<jupyter_code>tokenizer.vocab
smiles = 'c1ccccc1'  # 벤젠
tokenizer(smiles)<jupyter_output><empty_output><jupyter_text>Print model's informations<jupyter_code>model
print(f'model type: {config.model_type}')
print(f'model device: {model.device}')
print(f'model dtype: {model.dtype}')
print(f'number of training parameters: {model.num_parameters()}')<jupyter_output><empty_output><jupyter_text>RNN Model class* 자세한 코드는 [`laiddmg/models/char_rnn/modeling.py`](https://github.com/ilguyi/LAIDD-molecular-generation/blob/main/laiddmg/models/char_rnn/modeling.py)에 나와 있습니다.<jupyter_code>class _CharRNNModel(nn.Module):

  def __init__(self, config: CharRNNConfig):
    super(CharRNNModel, self).__init__()
    self.config = config
    self.vocab_size = config.vocab_size
    self.embedding_dim = config.embedding_dim
    self.hidden_dim = config.hidden_dim
    self.num_layers = config.num_layers
    self.dropout = config.dropout
    self.padding_value = config.padding_value
    self.output_dim = self.vocab_size

    self.embeddings = nn.Embedding(self.vocab_size, self.embedding_dim,
                                   padding_idx=self.padding_value)
    self.lstm = nn.LSTM(self.embedding_dim, self.hidden_dim,
                        self.num_layers,
                        batch_first=True,
                        dropout=self.dropout)
    self.fc = nn.Linear(self.hidden_dim, self.output_dim)

  def forward(
    self,
    input_ids: torch.Tensor,  # (batch_size, seq_len)
    lengths: torch.Tensor,  # (batch_size,)
    hiddens: Tuple[torch.Tensor] = None,  # (num_layers, batch_size, hidden_dim)
    **kwargs,
  ) -> Tuple[torch.Tensor, Tuple[torch.Tensor]]:
    x = self.embeddings(input_ids)  # x: (batch_size, seq_len, embedding_dim)
    x = rnn_utils.pack_padded_sequence(
      x,
      lengths.cpu(),
      batch_first=True,
      enforce_sorted=False,
    )
    x, hiddens = self.lstm(x, hiddens)
    # hiddens: (h, c); (num_layers, batch_size, hidden_dim), respectively
    x, _ = rnn_utils.pad_packed_sequence(
      x,
      batch_first=True,
    )  # x: (batch_size, seq_len, hidden_dim)
    outputs = self.fc(x)  # outputs: (batch_size, seq_len, vocab_size)

    return outputs, hiddens<jupyter_output><empty_output><jupyter_text>`pack_padded_sequence` 설명을 위한 token data다음과 같은 token data (출처: https://medium.com/huggingface/understanding-emotions-from-keras-to-pytorch-3ccb61d5a983) 가 있다고 생각해봅시다.총 5개의 데이터가 있고 각 문장(1개의 데이터)의 길이(한 문장의 token 갯수)는 다음과 같습니다.`lenghts = [6, 5, 2, 4, 1]`.시퀀스 길이가 서로 다르기 때문에 가장 긴 길이에 맞춰 `padding`을 해줍니다.![token_data](https://user-images.githubusercontent.com/11681225/129828808-e1e35cf2-1730-4e9d-b616-4426c11be1aa.png)다음과 같은 `vocab_dict`에 따라 `input_ids` tensor를 만들어줍니다.```pythonvocab_dict = {  'I': 1,  'Mom': 2,  'No': 3,  'This': 4,  'Yes': 5,  'cooking': 6,  'is': 7,  'love': 8,  's': 9,  'shit': 10,  'the': 11,  'too': 12,  'way': 13,  'you': 14,  '!': 15,  '`': 16,}```우리가 사용할 `input_ids` tensor는 아래와 같습니다.<jupyter_code>input_ids = torch.LongTensor([
  [  1,   8,   2,  16,   9,   6],
  [  1,   8,  14,  12,  15,   0],
  [  3,  13,   0,   0,   0,   0],
  [  4,   7,  11,  10,   0,   0],
  [  5,   0,   0,   0,   0,   0]
])  # input_ids: (batch_size, seq_len)
lengths = [6, 5, 2, 4, 1]<jupyter_output><empty_output><jupyter_text>우리가 사용할 embedding matrix를 눈으로 확인하기 쉽게 만들어줍니다.<jupyter_code>vocab_size = 16 + 1  # `+ 1`: 1을 더해주는 이유는 padding index(0)를 추가하기 때문입니다.
embedding_dim = 1
embeddings = nn.Embedding.from_pretrained(torch.arange(vocab_size, dtype=torch.float).unsqueeze(1))
embeddings.weight<jupyter_output><empty_output><jupyter_text>`pack_padded_sequence` 적용* `pack_padded_sequence`의 `PyTorch` 예제는 다음 링크에 있습니다. [https://pytorch.org/tutorials/beginner/chatbot_tutorial.htmlencoder](https://pytorch.org/tutorials/beginner/chatbot_tutorial.htmlencoder)* 함수 설명은 다음 링크에 있습니다. [https://pytorch.org/docs/stable/generated/torch.nn.utils.rnn.pack_padded_sequence.html](https://pytorch.org/docs/stable/generated/torch.nn.utils.rnn.pack_padded_sequence.html)<jupyter_code>x = embeddings(input_ids)  # x: (batch_size, seq_len, embedding_dim)
packed_x = rnn_utils.pack_padded_sequence(
  x,
  lengths,
  batch_first=True,
  enforce_sorted=False,
)
x<jupyter_output><empty_output><jupyter_text>참고```input_ids = torch.LongTensor([  [  1,   8,   2,  16,   9,   6],  [  1,   8,  14,  12,  15,   0],  [  3,  13,   0,   0,   0,   0],  [  4,   7,  11,  10,   0,   0],  [  5,   0,   0,   0,   0,   0]])```<jupyter_code>packed_x<jupyter_output><empty_output><jupyter_text>참고: `pack_padded_sequence`를 수행하면 시퀀스 길이에 따라 정렬을 하고 정렬된 데이터를 `pack`을 해준다.```pythoninput_ids = torch.LongTensor([  [  1,   8,   2,  16,   9,   6],  [  1,   8,  14,  12,  15,   0],  [  4,   7,  11,  10,   0,   0],   시퀀스 길이 순서에 따라 4번째 행이 3번째 행으로 올라감  [  3,  13,   0,   0,   0,   0],   시퀀스 길이 순서에 따라 3번째 행이 4번째 행으로 내려감  [  5,   0,   0,   0,   0,   0]])ᅟbatch_sizes = [5, 4, 3, 3, 2, 1]```  `pack_padded_sequence` 이후의 모습![packed_sequence](https://user-images.githubusercontent.com/11681225/129835933-852b6add-2acc-493c-bfdd-7693d6cfe737.png)(출처: https://medium.com/huggingface/understanding-emotions-from-keras-to-pytorch-3ccb61d5a983)  간단한 RNN 모델 생성<jupyter_code>rnn = nn.RNN(embedding_dim, 2,
             batch_first=True)<jupyter_output><empty_output><jupyter_text>`packed_x`와 `x` (pack 하지 않은 데이터)의 rnn 결과물 비교<jupyter_code>rnn_x, hiddens = rnn(packed_x)
rnn_x1, hiddens1 = rnn(x)
# hiddens: (h, c); (num_layers, batch_size, hidden_dim), respectively
hiddens1
rnn_x1<jupyter_output><empty_output><jupyter_text>`packed`된 데이터를 다시 원래 모습으로 바꿔주기 위해 `pad_packed_sequence`를 사용한다* `pad_packed_sequence`의 자세한 함수 설명은 다음 링크에서 확인할 수 있습니다. [https://pytorch.org/docs/stable/generated/torch.nn.utils.rnn.pad_packed_sequence.html](https://pytorch.org/docs/stable/generated/torch.nn.utils.rnn.pad_packed_sequence.html)<jupyter_code>rnn_x
output_x, _ = rnn_utils.pad_packed_sequence(
  rnn_x,
  batch_first=True,
)  # x: (batch_size, seq_len, hidden_dim)
output_x
hiddens<jupyter_output><empty_output><jupyter_text>Model 체크* model의 input으로는 `input_ids`와 `lengths`가 필요합니다. * `input_ids`는 `tokenizer`를 통해 SMILES character를 각각 token number로 바꾼 결과입니다.* `lengths`는 각 문장(각 SMILES 데이터)의 sequence length 정보입니다.<jupyter_code>smiles = 'c1ccccc1'
inputs = tokenizer(smiles)
inputs
# outputs, hiddens = model(**inputs)
outputs, hiddens = model(input_ids=inputs['input_ids'],
                         lengths=inputs['lengths'])
print(f'outputs shape: {outputs.shape}')
print(f'hidden state shape: {hiddens[0].shape}')
print(f'memory state shape: {hiddens[1].shape}')<jupyter_output><empty_output><jupyter_text>Data 얻기* [Molecular Sets (MOSES): A benchmarking platform for molecular generation models](https://github.com/molecularsets/moses)에서 사용한 ZINC데이터를 random sampling을 통해 `train : test = 250000 : 30000`으로 나누었습니다.* 실제 데이터 파일 경로는 아래와 같습니다.  * [`datasets/moses`](https://github.com/ilguyi/LAIDD-molecular-generation/blob/main/datasets/moses)* `get_rawdataset`함수를 이용하여 얻은 데이터는 각 항목이 SMILES `str`데이터로 이루어진 `np.ndarray`입니다.* 이 rawdataset을 사용하기 편하게 `custom Dataset` class를 만들었습니다.  * `custom Dataset`을 만드는 간단한 예제는 [PyTorch tutorial](https://pytorch.org/tutorials/beginner/basics/data_tutorial.htmlcreating-a-custom-dataset-for-your-files)에 있습니다.  * 자세한 코드는 [`laiddmg/ᅟdatasets.py`](https://github.com/ilguyi/LAIDD-molecular-generation/blob/main/laiddmg/datasets.py)에 나와 있습니다.<jupyter_code>train = get_rawdataset('train')
train
print(f'number of training dataset: {len(train)}')
print(f'raw data type: {type(train[0])}')<jupyter_output><empty_output><jupyter_text>`model`에 sample data 적용해보기<jupyter_code>sampled_data = train[:4]
inputs = tokenizer(sampled_data)
inputs<jupyter_output><empty_output><jupyter_text>`tokenizer`를 이용해 token data를 character로 바꾸기<jupyter_code>tokenizer.decode(inputs['input_ids'][0], skip_special_tokens=True)
sampled_data<jupyter_output><empty_output><jupyter_text>`inputs`를 `model`의 입력값으로 넣기<jupyter_code>outputs, hiddens = model(**inputs)
print(f'outputs shape: {outputs.shape}')
print(f'hidden state shape: {hiddens[0].shape}')
print(f'memory state shape: {hiddens[1].shape}')<jupyter_output><empty_output><jupyter_text>PyTorch `Dataset`, `DataLoader` 얻기<jupyter_code>from torch.utils.data import Dataset, DataLoader
train_dataset = get_dataset(train, tokenizer)
train_dataset[1000]<jupyter_output><empty_output><jupyter_text>`input_id`와 `target`의 관계RNN을 이용한 생성모델(generative model)은 language model의 학습방법을 이용한다.Language model은 간단하게 이야기하면 다음 단어를 예측하는 모델이다.다음 단어를 예측한다는 뜻은 RNN 그림을 보면 쉽게 이해할 수 있다.![RNN-input-target](https://user-images.githubusercontent.com/11681225/129859647-af31934a-0eea-4ad8-9a85-2d3c2a75f517.jpeg)위와 같이 input data의 token이 하나씩 이동한 것이 target이 되는 것이다.<jupyter_code>def _pad_sequence(data: List[torch.Tensor],
                  padding_value: int = 0) -> torch.Tensor:
  return rnn_utils.pad_sequence(data,
                                batch_first=True,
                                padding_value=padding_value)
def _collate_fn(batch: List[Dict[str, Union[torch.Tensor, str, int]]],
                **kwargs) -> Dict[str, Union[torch.Tensor, List[str], List[int]]]:

  indexes = [item['index'] for item in batch]
  smiles = [item['smiles'] for item in batch]
  input_ids = [item['input_id'] for item in batch]
  targets = [item['target'] for item in batch]
  lengths = [item['length'] for item in batch]

  padding_value = tokenizer.padding_value
  input_ids = _pad_sequence(input_ids, padding_value)
  targets = _pad_sequence(targets, padding_value)
  lengths = torch.LongTensor(lengths)

  return {'input_ids': input_ids,
          'targets': targets,
          'lengths': lengths,
          'smiles': smiles,
          'indexes': indexes}
train_dataloader = DataLoader(train_dataset,
                              batch_size=4,
                              shuffle=True,
                              collate_fn=_collate_fn,
                             )<jupyter_output><empty_output><jupyter_text>`pad_sequence` 작동 방식* 한 batch내의 sequence length가 다른 데이터들의 sequence length를 가장 긴 데이터를 기준으로 `padding_value`(일반적으로 0)를 채워넣어 길이를 맞춰줍니다.(`padding 한다`라고 부릅니다.)* 자세한 함수 설명은 [여기](https://pytorch.org/docs/stable/generated/torch.nn.utils.rnn.pad_sequence.html)에 있습니다.<jupyter_code>input_ids = [train_dataset[i]['input_id'] for i in range(4)]
input_ids
rnn_utils.pad_sequence(input_ids, batch_first=True, padding_value=0)<jupyter_output><empty_output><jupyter_text>`train_dataloader` 확인<jupyter_code>batch_data = next(iter(train_dataloader))
batch_data.keys()
batch_data['input_ids']
batch_data['targets']
batch_data['lengths']
batch_data['smiles']
batch_data['indexes']<jupyter_output><empty_output><jupyter_text>Train without `Trainer` class* 실제 사용할 수 있게 패키징한 코드에서는 `Trainer` class를 만들어 사용하기 편리하게 모듈화 시켰습니다.* 하지만 해당 Jupyter notebook은 이해를 돕기위해 모듈화 되어 있는 코드를 풀어서 블록 단위로 나타내었습니다.* `Trainer`에 관련된 자세한 코드는 아래 링크에 있습니다.  * [`laiddmg/trainer.py`](https://github.com/ilguyi/LAIDD-molecular-generation/blob/main/laiddmg/trainer.py)  * [`laiddmg/models/char_rnn/char_rnn_trainer.py`](https://github.com/ilguyi/LAIDD-molecular-generation/blob/main/laiddmg/models/char_rnn/char_rnn_trainer.py)  loss function and optimizer 생성<jupyter_code>training_args = EasyDict({
  'output_dir': 'outputs/char_rnn/jupyter1',
  'num_train_epochs': 10,
  'batch_size': 256,
  'lr': 1e-3,
  'step_size': 10,
  'gamma': 0.5,  
})
train_dataloader = DataLoader(train_dataset,
                              batch_size=training_args.batch_size,
                              shuffle=True,
                              collate_fn=_collate_fn,
                             )
loss_fn = nn.CrossEntropyLoss(ignore_index=tokenizer.padding_value)
optimizer = optim.Adam(model.parameters(), lr=training_args.lr)
lr_scheduler = optim.lr_scheduler.StepLR(optimizer, training_args.step_size, training_args.gamma)<jupyter_output><empty_output><jupyter_text>Plot for `lr_scheduler`* 학습할 때 더 빠른 수렴속도와 더 나은 정확도를 위해 learning rate를 조절하면서 학습하는 방식을 `learning rate scheduling`이라고 부릅니다.* PyTorch에는 다양한 scheduler들이 잘 정리되어 있습니다.  * [여기](https://pytorch.org/docs/stable/optim.htmlhow-to-adjust-learning-rate)를 참조하면 다양한 scheduler들을 볼 수 있습니다.  * 이 튜토리얼에서 사용하는 `StepLR scheduler`는 다음 [링크](https://pytorch.org/docs/stable/generated/torch.optim.lr_scheduler.StepLR.html)에서 확인할 수 있습니다.<jupyter_code>import matplotlib.pyplot as plt
%matplotlib inline
lr_history = []
for _ in range(50):
  lr_history.append(lr_scheduler.get_last_lr()[0])
  lr_scheduler.step()
plt.plot(lr_history)<jupyter_output><empty_output><jupyter_text>Training<jupyter_code># 위에서 그림을 그리기 위해 `lr_scheduler`를 업데이트 했기때문에 다시 생성해줍니다.x
lr_scheduler = optim.lr_scheduler.StepLR(optimizer, training_args.step_size, training_args.gamma)
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print(device)
model = model.to(device)
print(model.device)
def save_model(epoch: int, global_step: int, model: nn.Module):
  checkpoint_dir = os.path.join(training_args.output_dir)
  os.makedirs(checkpoint_dir, exist_ok=True)
  ckpt_name = f'ckpt_{epoch:03d}.pt'
  ckpt_path = os.path.join(checkpoint_dir, ckpt_name)
  
  torch.save({'epoch': epoch,
              'global_step': global_step,
              'model_state_dict': model.state_dict()},
             ckpt_path)
  print(f'saved {model.config.model_type} model at epoch {epoch}.')
model.train()
global_step = 0

for epoch in range(1, training_args.num_train_epochs + 1):
  print(f'\nStart training: {epoch} Epoch\n')
  
  for i, data in enumerate(train_dataloader, 1):
    optimizer.zero_grad()
    
    data['input_ids'] = data['input_ids'].to(device)
    data['targets'] = data['targets'].to(device)
    outputs, _ = model(data['input_ids'], data['lengths'])
    
    loss = loss_fn(outputs.view(-1, outputs.shape[-1]),
                   data['targets'].view(-1))
    
    loss.backward()
    optimizer.step()
    global_step += 1
    
    if global_step % 100 == 0:
      print(f'{epoch} Epochs | {i}/{len(train_dataloader)} | loss: {loss.item():.4g} | '
            f'lr: {lr_scheduler.get_last_lr()[0]:.4g}')
    
  lr_scheduler.step()
  
  save_model(epoch, global_step, model)
  
print('Training done!!')<jupyter_output><empty_output><jupyter_text>Generate new SMILES* model을 학습한 후에는 학습된 모델을 `load`하여 SMILES를 생성할 준비를 합니다.* `model.generate`함수를 이용하면 새로운 SMILES sequence를 만들수 있습니다.* 여기서는 generation의 각 과정을 하나씩 설명합니다.* 자세한 코드는 [`laiddmg/generate.py`](https://github.com/ilguyi/LAIDD-molecular-generation/blob/main/laiddmg/generate.py)에 나와 있습니다.<jupyter_code># checkpoint_dir = training_args.output_dir
# model = CharRNNModel.from_pretrained(config,
#                                      os.path.join(f'{checkpoint_dir}',
#                                                   f'ckpt_{training_args.num_train_epochs:03d}.pt'))
# model = model.to(device)
# model.eval()<jupyter_output><empty_output><jupyter_text>* 본 수업에서는 시간관계상 미리 학습한 `best_model`을 다운 받아서 씁니다.<jupyter_code>!wget 'https://www.dropbox.com/s/fqwpvx6nfh2ba1p/char_rnn_best.tar.gz?dl=0'
!tar xvzf char_rnn_best.tar.gz?dl=0
!rm -f char_rnn_best.tar.gz?dl=0
!mv best_model/ outputs/char_rnn/
model = CharRNNModel.from_pretrained(config,
                                     os.path.join('./outputs/char_rnn/best_model/best_model.pt'))
model = model.to(device)
model.eval()
batch_size_for_generate = 4
outputs = model.generate(tokenizer=tokenizer,
                         max_length=128,
                         num_return_sequences=batch_size_for_generate,
                         skip_special_tokens=True)
outputs<jupyter_output><empty_output><jupyter_text>generation 과정 step by step으로 알아보기1. `input_ids`변수에 첫 번째 token 데이터인 ` token` 넣기2. `input_ids`데이터를 (embedding 후에) lstm 모듈에 넣어 `outputs`과 `hidden state`를 얻기3. `outputs`을 `Linear`레이어를 통과시켜서 `next_token_logits`을 얻기4. `next_token_logits`을 `softmax`를 통해 확률분포를 얻음5. 이 확률분포를 기반한 sampling 작업을 함 (`torch.multinomial`을 이용)6. 실제로 sampling된 값이 `next_tokens`이 되고 이게 다음 스텝의 rnn 인풋으로 쓰임 (`input_ids = next_tokens`)7. 2 ~ 6과정을 반복  step 1. `input_ids`변수에 ` token` 넣기<jupyter_code>initial_inputs = torch.full((batch_size_for_generate, 1),
                            tokenizer.convert_token_to_id(tokenizer.start_token),
                            dtype=torch.long,
                            device=model.device)
generated_sequences = initial_inputs
input_ids = initial_inputs
hiddens = model.reset_states(batch_size_for_generate)
input_ids
hiddens<jupyter_output><empty_output><jupyter_text>step 2. `input_ids`데이터를 (embedding 후에) lstm 모듈에 넣어 `outputs`과 `hidden state`를 얻기<jupyter_code>x = model.embeddings(input_ids)
x, hiddens = model.lstm(x, hiddens)
x.shape<jupyter_output><empty_output><jupyter_text>step 3. `outputs`을 `Linear`레이어를 통과시켜서 `next_token_logits`을 얻기<jupyter_code>logits = model.fc(x)
next_token_logits = logits.squeeze(1)
logits.shape
next_token_logits.shape<jupyter_output><empty_output><jupyter_text>step 4. `next_token_logits`을 `softmax`를 통해 확률분포를 얻음<jupyter_code>probabilities = F.softmax(next_token_logits, dim=-1)
probabilities[0]<jupyter_output><empty_output><jupyter_text>step 5. 이 확률분포를 기반한 sampling 작업을 함 ([`torch.multinomial`](https://pytorch.org/docs/stable/generated/torch.multinomial.html?highlight=multinomialtorch.multinomial)을 이용)<jupyter_code>next_tokens = torch.multinomial(probabilities, num_samples=1)
next_tokens<jupyter_output><empty_output><jupyter_text>참고 `tokenizer.vocab````python{('', 4), ('(', 5), (')', 6), ('-', 7), ('1', 8), ('2', 9), ('3', 10), ('4', 11), ('5', 12), ('6', 13), ('=', 14), ('B', 15), ('C', 16), ('F', 17), ('H', 18), ('N', 19), ('O', 20), ('S', 21), ('[', 22), (']', 23), ('c', 24), ('l', 25), ('n', 26), ('o', 27), ('r', 28), ('s', 29)}```  step 6. 실제로 sampling된 값이 `next_tokens`이 되고 이게 다음 스텝의 rnn 인풋으로 쓰임 (`input_ids = next_tokens`)<jupyter_code>inputs_ids = next_tokens
generated_sequences = torch.cat((generated_sequences, next_tokens), dim=1)
generated_sequences<jupyter_output><empty_output><jupyter_text>위의 과정을 모듈화해서 `generate`함수를 만들었습니다.<jupyter_code>outputs = model.generate(tokenizer=tokenizer,
                         max_length=128,
                         # num_return_sequences=batch_size_for_generate,
                         num_return_sequences=128,
                         skip_special_tokens=True)
import rdkit
from rdkit import Chem
from rdkit.Chem.Draw import IPythonConsole
mols = []
for s in outputs:
  try:
    mol = Chem.MolFromSmiles(s)
  except:
    pass
  if mol is not None:
    mols.append(mol)
len(mols)
mols[0]
mols[1]
mols[2]<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1348949125908466, 'fraction_numerical': 0.022539776075427224, 'mean_word_length': 4.362032648762507, 'pattern_counts': {'":': 0, '<': 138, '<?xml version=': 0, '>': 141, 'https://': 30, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 5, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '34', 'dir': 'jupyter-structured-clean-dedup', 'id': '620673', 'license': 'MIT', 'n_tokens_mistral': 9221, 'n_tokens_neox': 9541, 'n_words': 1437, 'path': 'laiddmg/jupyter_char_rnn.ipynb', 'repo_name': 'ilguyi/LAIDD-molecular-generation', 'size': '20377'}