CNN-LSTM

CNN-LSTM

November 18, 2023

CNN-LSTM

CNN과 LSTM으로 만든 Neural Network로 image caption 생성하기

다음 내용들을 포함하고 있다.

  • Build Vocab
  • Preprocessing Image data
  • Define image captions data loader
  • Define CNN-LSTM model
  • Train the models

visit : https://github.com/All4Nothing/pytorch-DL-project/blob/main/Ch02_CNN_LSTM.ipynb

CNN-LSTM

CNN과 LSTM을 인코더-디코더 프레임워크로 연결해, 이미지나 동영상 데이터를 통해 텍스트를 추출하는 하이브리드 모델을 만들 수 있다. 이러한 모델은 image captioning에 사용할 수 있다.

CNN을 incoder로 LSTM을 decoder로 구성하여 다음과 같이 학습에 사용할 수 있다.

Model Define

class CNNModel(nn.Module):
  def __init__(self, embedding_size):
    """ Load the pretrained ResNet-152 and replace top fc layer. """
    super(CNNModel, self).__init__()
    resnet = models.resnet152(pretrained=True)
    module_list = list(resnet.children())[:-1] # delete the last fc layer
    self.resnet_module = nn.Sequential(*module_list)
    self.linear_layer = nn.Linear(resnet.fc.in_features, embedding_size)
    self.batch_norm = nn.BatchNorm1d(embedding_size, momentum=0.01)

  def forward(self, input_images):
    """ Extract feature vectors from input images. """
    with torch.no_grad():
      resnet_features = self.resnet_module(input_images)
    resnet_features = resnet_features.reshape(resnet_features.size(0), -1)
    final_features = self.batch_norm(self.linear_layer(resnet_features))
    return final_features

class LSTMModel(nn.Module):
  def __init__(self, embedding_size, hidden_layer_size, vocabulary_size, num_layers, max_seq_len=20):
    """ Set the hyper-parameters and build the layers. """
    super(LSTMModel, self).__init__()
    self.embedding_layer = nn.Embedding(vocabulary_size, embedding_size)
    self.lstm_layer = nn.LSTM(embedding_size, hidden_layer_size, num_layers, batch_first = True)
    self.linear_layer = nn.Linear(hidden_layer_size, vocabulary_size)
    self.max_seq_len = max_seq_len

  def forward(self, input_features, capts, lens):
    """ Decode image feature vectors and generates captions. """
    embeddings = self.embedding_layer(capts)
    embeddings = torch.cat((input_features.unsqueeze(1), embeddings), 1)
    lstm_input = pack_padded_sequence(embeddings, lens, batch_first = True)
    hidden_variables, _ = self.lstm_layer(lstm_input)
    model_outputs = self.linear_layer(hidden_variables[0])
    return model_outputs

  def sample(self, input_features, lstm_states = None):
    """ Generate captions for given image features using greedy search. """
    """ 확률이 가장 높은 문장을 선택한다 """
    sampled_indices = []
    lstm_inputs = input_features.unsqueeze(1)
    for i in range(self.max_seq_len):
      hidden_variables, lstm_states = self.lstm_layer(lstm_inputs, lstm_states)          # hiddens: (batch_size, 1, hidden_size)
      model_outputs = self.linear_layer(hidden_variables.squeeze(1))            # outputs:  (batch_size, vocab_size)
      _, predicted_outputs = model_outputs.max(1)                        # predicted: (batch_size)
      sampled_indices.append(predicted_outputs)
      lstm_inputs = self.embedding_layer(predicted_outputs)                       # inputs: (batch_size, embed_size)
      lstm_inputs = lstm_inputs.unsqueeze(1)                         # inputs: (batch_size, 1, embed_size)
    sampled_indices = torch.stack(sampled_indices, 1)                # sampled_ids: (batch_size, max_seq_length)
    return sampled_indices

LSTM layer는 순환 계층으로 LSTM 셀이 시간 차원을 따라 unfold 되어 LSTM 셀의 시간 배열을 구성한다. 여기서 이 셀은 각 시간 단계마다 단어의 예측 확률을 출력하고 가장 확률이 높은 단어가 출력 문장 뒤에 추가된다.

각 시간 단계에서 LSTM 셀은 내부 셀 상태를 생성하고 이 상태는 다음 시간 단계의 LSTM 셀의 입력으로 전달된다. LSTM 셀이 토큰/단어를 출력할 때 까지 이 과정을 반복한다.

nn.Embedding()

PyTorch에서 임베딩 층(embedding layer)을 만들어 훈련 데이터로부터 처음부터 임베딩 벡터를 학습한다.

nn.Embedding()을 사용하여 학습 가능한 임베딩 테이블(룩업 테이블)을 만든다.

nn.Embedding은 크게 두 가지 인자를 받는다.

  • num_embeddings : 임베딩을 할 단어들의 개수. 다시 말해 단어 집합의 크기이다.
  • embedding_dim : 임베딩 할 벡터의 차원이다. 사용자가 정해주는 hyperparameter이다.
  • padding_idx : 선택적으로 사용하는 para,eter이다. 패딩을 위한 토큰의 인덱스를 알려준다.

embedding_layer **=** nn**.**Embedding(num_embeddings**=**len(vocab), embedding_dim**=**3, padding_idx**=**1) print(embedding_layer**.**weight)

Parameter containing:
tensor([[-0.1778, -1.9974, -1.2478],
        [ 0.0000,  0.0000,  0.0000],
        [ 1.0921,  0.0416, -0.7896],
        [ 0.0960, -0.6029,  0.3721],
        [ 0.2780, -0.4300, -1.9770],
        [ 0.0727,  0.5782, -3.2617],
        [-0.0173, -0.7092,  0.9121],
        [-0.4817, -1.1222,  2.2774]], requires_grad=True)

Train loop

# Build the models
encoder_model = CNNModel(256).to(device)
decoder_model = LSTMModel(256, 512, len(vocabulary), 1).to(device)

# Loss and optimizer
loss_criterion = nn.CrossEntropyLoss()
parameters = list(decoder_model.parameters()) + list(encoder_model.linear_layer.parameters()) + list(encoder_model.batch_norm.parameters())
optimizer = torch.optim.Adam(parameters, lr=0.001)

# Train the models
total_num_steps = len(custom_data_loader)
for epoch in range(5):
    for i, (imgs, caps, lens) in enumerate(custom_data_loader):

        # Set mini-batch dataset
        imgs = imgs.to(device)
        caps = caps.to(device)
        tgts = pack_padded_sequence(caps, lens, batch_first=True)[0]

        # Forward, backward and optimize
        feats = encoder_model(imgs)
        outputs = decoder_model(feats, caps, lens)
        loss = loss_criterion(outputs, tgts)
        decoder_model.zero_grad()
        encoder_model.zero_grad()
        loss.backward()
        optimizer.step()

        # Print log info
        if i % 10 == 0:
            print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}, Perplexity: {:5.4f}'
                  .format(epoch, 5, i, total_num_steps, loss.item(),
                          np.exp(loss.item())))

        # Save the model checkpoints
        if (i+1) % 1000 == 0:
            torch.save(decoder_model.state_dict(), os.path.join(
                'models_dir/', 'decoder-{}-{}.ckpt'.format(epoch+1, i+1)))
            torch.save(encoder_model.state_dict(), os.path.join(
                'models_dir/', 'encoder-{}-{}.ckpt'.format(epoch+1, i+1)))

해당 포스팅은 ‘실전! 파이토치 딥러닝 프로젝트’의 Ch02을 참고하여 작성되었습니다.

# concepts