눈에 띄는 이미지 캡션

TensorFlow.org에서 보기 Google Colab에서 실행 GitHub에서 소스 보기 노트북 다운로드

아래 예와 같은 이미지가 주어졌을 때의 목표는 "파도를 타는 서퍼"와 같은 캡션을 생성하는 것입니다.

Man Surfing

이미지 출처; 라이선스: Public Domain

이를 위해 attention 기반 모델을 사용하여 캡션을 생성할 때 이미지의 어떤 부분에 초점을 맞출 수 있는지 확인할 수 있습니다.

Prediction

모델 아키텍처는 Show, Attend and Tell: Neural Image Caption Generation with Visual Attention과 유사합니다.

이 노트북은 엔드 투 엔드 예제입니다. 노트북을 실행할 때 MS-COCO 데이터세트를 다운로드하고 Inception V3를 사용하여 이미지의 서브 세트를 사전 처리 및 캐시하고 encoder-decoder 모델을 훈련하고 훈련된 모델을 사용하여 새 이미지에 캡션을 생성합니다.

이 예제에서는 데이터세트에서 이미지당 여러 개의 캡션이 있기 때문에 약 20,000개의 이미지에 대한 첫 번째 30,000개의 캡션과 같이 비교적 적은 양의 데이터에 대해 모델을 훈련합니다.

import tensorflow as tf

# You'll generate plots of attention in order to see which parts of an image
# your model focuses on during captioning
import matplotlib.pyplot as plt

import collections
import random
import numpy as np
import os
import time
import json
from PIL import Image

MS-COCO 데이터세트 다운로드 및 준비

MS-COCO 데이터세트를 사용하여 모델을 훈련합니다. 데이터세트에는 82,000개가 넘는 이미지가 포함되어 있으며, 각 이미지에는 최소 5개의 다른 캡션 주석이 있습니다. 아래 코드는 자동으로 데이터세트를 다운로드하고 추출합니다.

주의: 대용량 다운로드가 진행됩니다. 13GB 파일의 훈련 세트를 사용합니다.

# Download caption annotation files
annotation_folder = '/annotations/'
if not os.path.exists(os.path.abspath('.') + annotation_folder):
  annotation_zip = tf.keras.utils.get_file('captions.zip',
                                           cache_subdir=os.path.abspath('.'),
                                           origin='http://images.cocodataset.org/annotations/annotations_trainval2014.zip',
                                           extract=True)
  annotation_file = os.path.dirname(annotation_zip)+'/annotations/captions_train2014.json'
  os.remove(annotation_zip)

# Download image files
image_folder = '/train2014/'
if not os.path.exists(os.path.abspath('.') + image_folder):
  image_zip = tf.keras.utils.get_file('train2014.zip',
                                      cache_subdir=os.path.abspath('.'),
                                      origin='http://images.cocodataset.org/zips/train2014.zip',
                                      extract=True)
  PATH = os.path.dirname(image_zip) + image_folder
  os.remove(image_zip)
else:
  PATH = os.path.abspath('.') + image_folder
Downloading data from http://images.cocodataset.org/annotations/annotations_trainval2014.zip
252878848/252872794 [==============================] - 8s 0us/step
252887040/252872794 [==============================] - 8s 0us/step
Downloading data from http://images.cocodataset.org/zips/train2014.zip
13510574080/13510573713 [==============================] - 458s 0us/step
13510582272/13510573713 [==============================] - 458s 0us/step

선택 사항: 훈련 세트의 크기를 제한합니다.

이 튜토리얼의 학습 속도를 높이기 위해 30,000개의 캡션과 해당 이미지를 사용하여 모델을 훈련합니다. 더 많은 데이터를 사용하도록 선택하면 캡션 품질이 향상됩니다.

with open(annotation_file, 'r') as f:
    annotations = json.load(f)
# Group all captions together having the same image ID.
image_path_to_caption = collections.defaultdict(list)
for val in annotations['annotations']:
  caption = f"<start> {val['caption']} <end>"
  image_path = PATH + 'COCO_train2014_' + '%012d.jpg' % (val['image_id'])
  image_path_to_caption[image_path].append(caption)
image_paths = list(image_path_to_caption.keys())
random.shuffle(image_paths)

# Select the first 6000 image_paths from the shuffled set.
# Approximately each image id has 5 captions associated with it, so that will
# lead to 30,000 examples.
train_image_paths = image_paths[:6000]
print(len(train_image_paths))
6000
train_captions = []
img_name_vector = []

for image_path in train_image_paths:
  caption_list = image_path_to_caption[image_path]
  train_captions.extend(caption_list)
  img_name_vector.extend([image_path] * len(caption_list))
print(train_captions[0])
Image.open(img_name_vector[0])
<start> A woman wearing a robe and a scarf is snarling. <end>

png

InceptionV3를 사용하여 이미지 전처리하기

다음으로, (Imagenet에 대해 사전 훈련된) InceptionV3을 사용하여 각 이미지를 분류합니다. 마지막 컨볼루셔널 레이어에서 특성을 추출합니다.

먼저 다음을 수행하여 이미지를 InceptionV3의 예상 형식으로 변환합니다.

  • 이미지를 299px x 299px로 크기 조정합니다.
  • preprocess_input 메서드로 이미지를 사전 처리하여 -1에서 1 범위의 픽셀을 포함하도록 이미지를 정규화합니다. 이는 InceptionV3를 훈련하는 데 사용되는 이미지의 형식과 일치합니다.
def load_image(image_path):
    img = tf.io.read_file(image_path)
    img = tf.image.decode_jpeg(img, channels=3)
    img = tf.image.resize(img, (299, 299))
    img = tf.keras.applications.inception_v3.preprocess_input(img)
    return img, image_path

InceptionV3 초기화 및 사전 훈련된 Imagenet 가중치 로드하기

이제 출력 레이어가 InceptionV3 아키텍처의 마지막 컨볼루셔널 레이어인 tf.keras 모델을 만듭니다. 이 레이어의 출력 형상은 8x8x2048입니다. 이 예제에서는 attention을 사용하므로 마지막 컨볼루셔널 레이어를 사용합니다. 병목 현상이 발생할 수 있으므로 훈련 중에는 이 초기화를 수행하지 마세요.

  • 네트워크를 통해 각 이미지를 전달하고 결과 벡터를 사전 (image_name --> feature_vector)에 저장합니다.
  • 모든 이미지가 네트워크를 통해 전달된 후, 사전을 디스크에 저장합니다.
image_model = tf.keras.applications.InceptionV3(include_top=False,
                                                weights='imagenet')
new_input = image_model.input
hidden_layer = image_model.layers[-1].output

image_features_extract_model = tf.keras.Model(new_input, hidden_layer)
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/inception_v3/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5
87916544/87910968 [==============================] - 0s 0us/step
87924736/87910968 [==============================] - 0s 0us/step

InceptionV3에서 추출된 특성 캐시하기

InceptionV3로 각 이미지를 사전 처리하고 출력을 디스크에 캐시합니다. RAM에 출력을 캐시하는 것이 더 빠르지만, 메모리를 많이 사용하므로 이미지당 8 * 8 * 2048 부동 소수점이 필요합니다. 본 튜토리얼을 작성할 시점에서 Colab의 메모리 제한(현재 12GB의 메모리)을 초과합니다.

보다 정교한 캐싱 전략(예를 들어, 임의 액세스 디스크 I/O를 줄이기 위해 이미지를 분할)으로 성능을 향상할 수 있지만, 더 많은 코드가 필요합니다.

캐싱은 GPU로 Colab에서 실행하는 데 약 10분이 걸립니다. 진행률 표시줄을 보려면 다음을 수행합니다.

  1. tqdm을 설치합니다.

    !pip install tqdm

  2. tqdm를 가져옵니다.

    from tqdm import tqdm

  3. 다음 줄을

    for img, path in image_dataset:

    다음으로 변경합니다.

    for img, path in tqdm(image_dataset):

# Get unique images
encode_train = sorted(set(img_name_vector))

# Feel free to change batch_size according to your system configuration
image_dataset = tf.data.Dataset.from_tensor_slices(encode_train)
image_dataset = image_dataset.map(
  load_image, num_parallel_calls=tf.data.AUTOTUNE).batch(16)

for img, path in image_dataset:
  batch_features = image_features_extract_model(img)
  batch_features = tf.reshape(batch_features,
                              (batch_features.shape[0], -1, batch_features.shape[3]))

  for bf, p in zip(batch_features, path):
    path_of_feature = p.numpy().decode("utf-8")
    np.save(path_of_feature, bf.numpy())

캡션 전처리 및 토큰화하기

  • 먼저 캡션을 토큰화합니다(예: 공백으로 분할). 이를 통해 데이터의 모든 고유 단어(예: "서핑", "축구" 등)에 대한 어휘를 제공합니다.
  • 다음으로, 단어 크기를 상위 5,000개 단어로 제한하여 메모리를 절약합니다. 다른 모든 단어는 토큰 "UNK"(알 수 없음)로 대체됩니다.
  • 그런 다음, 단어 대 인덱스 및 인덱스 대 단어 매핑을 만듭니다.
  • 마지막으로, 모든 시퀀스를 가장 긴 시퀀스와 같은 길이로 채웁니다.
# Find the maximum length of any caption in the dataset
def calc_max_length(tensor):
    return max(len(t) for t in tensor)
# Choose the top 5000 words from the vocabulary
top_k = 5000
tokenizer = tf.keras.preprocessing.text.Tokenizer(num_words=top_k,
                                                  oov_token="<unk>",
                                                  filters='!"#$%&()*+.,-/:;=?@[\]^_`{|}~')
tokenizer.fit_on_texts(train_captions)
tokenizer.word_index['<pad>'] = 0
tokenizer.index_word[0] = '<pad>'
# Create the tokenized vectors
train_seqs = tokenizer.texts_to_sequences(train_captions)
# Pad each vector to the max_length of the captions
# If you do not provide a max_length value, pad_sequences calculates it automatically
cap_vector = tf.keras.preprocessing.sequence.pad_sequences(train_seqs, padding='post')
# Calculates the max_length, which is used to store the attention weights
max_length = calc_max_length(train_seqs)

데이터를 훈련 및 테스트로 분할하기

img_to_cap_vector = collections.defaultdict(list)
for img, cap in zip(img_name_vector, cap_vector):
  img_to_cap_vector[img].append(cap)

# Create training and validation sets using an 80-20 split randomly.
img_keys = list(img_to_cap_vector.keys())
random.shuffle(img_keys)

slice_index = int(len(img_keys)*0.8)
img_name_train_keys, img_name_val_keys = img_keys[:slice_index], img_keys[slice_index:]

img_name_train = []
cap_train = []
for imgt in img_name_train_keys:
  capt_len = len(img_to_cap_vector[imgt])
  img_name_train.extend([imgt] * capt_len)
  cap_train.extend(img_to_cap_vector[imgt])

img_name_val = []
cap_val = []
for imgv in img_name_val_keys:
  capv_len = len(img_to_cap_vector[imgv])
  img_name_val.extend([imgv] * capv_len)
  cap_val.extend(img_to_cap_vector[imgv])
len(img_name_train), len(cap_train), len(img_name_val), len(cap_val)
(24007, 24007, 6001, 6001)

훈련을 위한 tf.data 데이터세트 생성하기

이미지와 캡션이 준비되었습니다! 다음으로 모델 훈련에 사용할 tf.data 데이터세트를 만들어 봅시다.

# Feel free to change these parameters according to your system's configuration

BATCH_SIZE = 64
BUFFER_SIZE = 1000
embedding_dim = 256
units = 512
vocab_size = top_k + 1
num_steps = len(img_name_train) // BATCH_SIZE
# Shape of the vector extracted from InceptionV3 is (64, 2048)
# These two variables represent that vector shape
features_shape = 2048
attention_features_shape = 64
# Load the numpy files
def map_func(img_name, cap):
  img_tensor = np.load(img_name.decode('utf-8')+'.npy')
  return img_tensor, cap
dataset = tf.data.Dataset.from_tensor_slices((img_name_train, cap_train))

# Use map to load the numpy files in parallel
dataset = dataset.map(lambda item1, item2: tf.numpy_function(
          map_func, [item1, item2], [tf.float32, tf.int32]),
          num_parallel_calls=tf.data.AUTOTUNE)

# Shuffle and batch
dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE)
dataset = dataset.prefetch(buffer_size=tf.data.AUTOTUNE)

모델

재미있는 사실: 아래의 디코더는 Attention을 사용한 신경망 기계 번역의 디코더와 동일합니다.

모델 아키텍처는 Show, Attend and Tell 논문에서 영감을 얻었습니다.

  • 이 예제에서는 InceptionV3의 하위 컨볼루셔널 레이어에서 특성을 추출하여 형상 (8, 8, 2048)의 백터를 제공합니다.
  • (64, 2048)의 형상으로 스쿼시합니다.
  • 그런 다음, 이 벡터는 CNN Encoder(단일 완전 연결 레이어로 구성)를 통해 전달됩니다.
  • RNN(여기서는 GRU)이 다음 단어를 예측하기 위해 이미지를 살펴봅니다.
class BahdanauAttention(tf.keras.Model):
  def __init__(self, units):
    super(BahdanauAttention, self).__init__()
    self.W1 = tf.keras.layers.Dense(units)
    self.W2 = tf.keras.layers.Dense(units)
    self.V = tf.keras.layers.Dense(1)

  def call(self, features, hidden):
    # features(CNN_encoder output) shape == (batch_size, 64, embedding_dim)

    # hidden shape == (batch_size, hidden_size)
    # hidden_with_time_axis shape == (batch_size, 1, hidden_size)
    hidden_with_time_axis = tf.expand_dims(hidden, 1)

    # attention_hidden_layer shape == (batch_size, 64, units)
    attention_hidden_layer = (tf.nn.tanh(self.W1(features) +
                                         self.W2(hidden_with_time_axis)))

    # score shape == (batch_size, 64, 1)
    # This gives you an unnormalized score for each image feature.
    score = self.V(attention_hidden_layer)

    # attention_weights shape == (batch_size, 64, 1)
    attention_weights = tf.nn.softmax(score, axis=1)

    # context_vector shape after sum == (batch_size, hidden_size)
    context_vector = attention_weights * features
    context_vector = tf.reduce_sum(context_vector, axis=1)

    return context_vector, attention_weights
class CNN_Encoder(tf.keras.Model):
    # Since you have already extracted the features and dumped it
    # This encoder passes those features through a Fully connected layer
    def __init__(self, embedding_dim):
        super(CNN_Encoder, self).__init__()
        # shape after fc == (batch_size, 64, embedding_dim)
        self.fc = tf.keras.layers.Dense(embedding_dim)

    def call(self, x):
        x = self.fc(x)
        x = tf.nn.relu(x)
        return x
class RNN_Decoder(tf.keras.Model):
  def __init__(self, embedding_dim, units, vocab_size):
    super(RNN_Decoder, self).__init__()
    self.units = units

    self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
    self.gru = tf.keras.layers.GRU(self.units,
                                   return_sequences=True,
                                   return_state=True,
                                   recurrent_initializer='glorot_uniform')
    self.fc1 = tf.keras.layers.Dense(self.units)
    self.fc2 = tf.keras.layers.Dense(vocab_size)

    self.attention = BahdanauAttention(self.units)

  def call(self, x, features, hidden):
    # defining attention as a separate model
    context_vector, attention_weights = self.attention(features, hidden)

    # x shape after passing through embedding == (batch_size, 1, embedding_dim)
    x = self.embedding(x)

    # x shape after concatenation == (batch_size, 1, embedding_dim + hidden_size)
    x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1)

    # passing the concatenated vector to the GRU
    output, state = self.gru(x)

    # shape == (batch_size, max_length, hidden_size)
    x = self.fc1(output)

    # x shape == (batch_size * max_length, hidden_size)
    x = tf.reshape(x, (-1, x.shape[2]))

    # output shape == (batch_size * max_length, vocab)
    x = self.fc2(x)

    return x, state, attention_weights

  def reset_state(self, batch_size):
    return tf.zeros((batch_size, self.units))
encoder = CNN_Encoder(embedding_dim)
decoder = RNN_Decoder(embedding_dim, units, vocab_size)
optimizer = tf.keras.optimizers.Adam()
loss_object = tf.keras.losses.SparseCategoricalCrossentropy(
    from_logits=True, reduction='none')


def loss_function(real, pred):
  mask = tf.math.logical_not(tf.math.equal(real, 0))
  loss_ = loss_object(real, pred)

  mask = tf.cast(mask, dtype=loss_.dtype)
  loss_ *= mask

  return tf.reduce_mean(loss_)

체크포인트

checkpoint_path = "./checkpoints/train"
ckpt = tf.train.Checkpoint(encoder=encoder,
                           decoder=decoder,
                           optimizer=optimizer)
ckpt_manager = tf.train.CheckpointManager(ckpt, checkpoint_path, max_to_keep=5)
start_epoch = 0
if ckpt_manager.latest_checkpoint:
  start_epoch = int(ckpt_manager.latest_checkpoint.split('-')[-1])
  # restoring the latest checkpoint in checkpoint_path
  ckpt.restore(ckpt_manager.latest_checkpoint)

훈련하기

  • .npy 파일에 저장된 특성을 추출한 다음, 해당 특성을 endcoder를 통해 전달합니다.
  • encoder 출력, hidden state(0으로 초기화됨) 및 decoder 입력(start token)이 decoder로 전달됩니다.
  • decoder는 예측값 및 decoder hidden state를 반환합니다.
  • 그런 다음 decoder hidden state가 모델로 다시 전달되고 예측값은 손실을 계산하는 데 사용됩니다.
  • teacher forcing를 사용하여 decoder에 대한 다음 입력을 결정합니다.
  • teacher forcing은 대상 단어가 다음 입력으로 decoder에 전달되는 기법입니다.
  • 마지막 스텝은 그래디언트를 계산하고 이를 옵티마이저에 적용하여 역전파하는 것입니다.
# adding this in a separate cell because if you run the training cell
# many times, the loss_plot array will be reset
loss_plot = []
@tf.function
def train_step(img_tensor, target):
  loss = 0

  # initializing the hidden state for each batch
  # because the captions are not related from image to image
  hidden = decoder.reset_state(batch_size=target.shape[0])

  dec_input = tf.expand_dims([tokenizer.word_index['<start>']] * target.shape[0], 1)

  with tf.GradientTape() as tape:
      features = encoder(img_tensor)

      for i in range(1, target.shape[1]):
          # passing the features through the decoder
          predictions, hidden, _ = decoder(dec_input, features, hidden)

          loss += loss_function(target[:, i], predictions)

          # using teacher forcing
          dec_input = tf.expand_dims(target[:, i], 1)

  total_loss = (loss / int(target.shape[1]))

  trainable_variables = encoder.trainable_variables + decoder.trainable_variables

  gradients = tape.gradient(loss, trainable_variables)

  optimizer.apply_gradients(zip(gradients, trainable_variables))

  return loss, total_loss
EPOCHS = 20

for epoch in range(start_epoch, EPOCHS):
    start = time.time()
    total_loss = 0

    for (batch, (img_tensor, target)) in enumerate(dataset):
        batch_loss, t_loss = train_step(img_tensor, target)
        total_loss += t_loss

        if batch % 100 == 0:
            average_batch_loss = batch_loss.numpy()/int(target.shape[1])
            print(f'Epoch {epoch+1} Batch {batch} Loss {average_batch_loss:.4f}')
    # storing the epoch end loss value to plot later
    loss_plot.append(total_loss / num_steps)

    if epoch % 5 == 0:
      ckpt_manager.save()

    print(f'Epoch {epoch+1} Loss {total_loss/num_steps:.6f}')
    print(f'Time taken for 1 epoch {time.time()-start:.2f} sec\n')
Epoch 1 Batch 0 Loss 1.9205
Epoch 1 Batch 100 Loss 1.0760
Epoch 1 Batch 200 Loss 0.9832
Epoch 1 Batch 300 Loss 0.8996
Epoch 1 Loss 1.042245
Time taken for 1 epoch 125.09 sec

Epoch 2 Batch 0 Loss 0.8650
Epoch 2 Batch 100 Loss 0.7292
Epoch 2 Batch 200 Loss 0.8493
Epoch 2 Batch 300 Loss 0.7142
Epoch 2 Loss 0.792074
Time taken for 1 epoch 45.16 sec

Epoch 3 Batch 0 Loss 0.7278
Epoch 3 Batch 100 Loss 0.6871
Epoch 3 Batch 200 Loss 0.7765
Epoch 3 Batch 300 Loss 0.6726
Epoch 3 Loss 0.713975
Time taken for 1 epoch 45.35 sec

Epoch 4 Batch 0 Loss 0.6662
Epoch 4 Batch 100 Loss 0.6742
Epoch 4 Batch 200 Loss 0.7237
Epoch 4 Batch 300 Loss 0.6170
Epoch 4 Loss 0.662263
Time taken for 1 epoch 45.01 sec

Epoch 5 Batch 0 Loss 0.6547
Epoch 5 Batch 100 Loss 0.6001
Epoch 5 Batch 200 Loss 0.6061
Epoch 5 Batch 300 Loss 0.5889
Epoch 5 Loss 0.621185
Time taken for 1 epoch 45.33 sec

Epoch 6 Batch 0 Loss 0.6185
Epoch 6 Batch 100 Loss 0.6071
Epoch 6 Batch 200 Loss 0.5910
Epoch 6 Batch 300 Loss 0.5870
Epoch 6 Loss 0.586602
Time taken for 1 epoch 45.42 sec

Epoch 7 Batch 0 Loss 0.5588
Epoch 7 Batch 100 Loss 0.5009
Epoch 7 Batch 200 Loss 0.5731
Epoch 7 Batch 300 Loss 0.5276
Epoch 7 Loss 0.553746
Time taken for 1 epoch 45.41 sec

Epoch 8 Batch 0 Loss 0.5604
Epoch 8 Batch 100 Loss 0.5067
Epoch 8 Batch 200 Loss 0.5587
Epoch 8 Batch 300 Loss 0.5047
Epoch 8 Loss 0.524486
Time taken for 1 epoch 45.35 sec

Epoch 9 Batch 0 Loss 0.5003
Epoch 9 Batch 100 Loss 0.4388
Epoch 9 Batch 200 Loss 0.5272
Epoch 9 Batch 300 Loss 0.5026
Epoch 9 Loss 0.497023
Time taken for 1 epoch 45.33 sec

Epoch 10 Batch 0 Loss 0.5231
Epoch 10 Batch 100 Loss 0.4568
Epoch 10 Batch 200 Loss 0.4656
Epoch 10 Batch 300 Loss 0.4989
Epoch 10 Loss 0.470513
Time taken for 1 epoch 45.68 sec

Epoch 11 Batch 0 Loss 0.4900
Epoch 11 Batch 100 Loss 0.4773
Epoch 11 Batch 200 Loss 0.4477
Epoch 11 Batch 300 Loss 0.4214
Epoch 11 Loss 0.446156
Time taken for 1 epoch 45.64 sec

Epoch 12 Batch 0 Loss 0.4202
Epoch 12 Batch 100 Loss 0.4277
Epoch 12 Batch 200 Loss 0.4360
Epoch 12 Batch 300 Loss 0.4667
Epoch 12 Loss 0.423507
Time taken for 1 epoch 45.48 sec

Epoch 13 Batch 0 Loss 0.3983
Epoch 13 Batch 100 Loss 0.4424
Epoch 13 Batch 200 Loss 0.4149
Epoch 13 Batch 300 Loss 0.3811
Epoch 13 Loss 0.401418
Time taken for 1 epoch 45.45 sec

Epoch 14 Batch 0 Loss 0.3867
Epoch 14 Batch 100 Loss 0.4021
Epoch 14 Batch 200 Loss 0.3839
Epoch 14 Batch 300 Loss 0.3747
Epoch 14 Loss 0.381274
Time taken for 1 epoch 45.40 sec

Epoch 15 Batch 0 Loss 0.3587
Epoch 15 Batch 100 Loss 0.3694
Epoch 15 Batch 200 Loss 0.3519
Epoch 15 Batch 300 Loss 0.3794
Epoch 15 Loss 0.361780
Time taken for 1 epoch 45.29 sec

Epoch 16 Batch 0 Loss 0.3545
Epoch 16 Batch 100 Loss 0.3596
Epoch 16 Batch 200 Loss 0.3444
Epoch 16 Batch 300 Loss 0.3965
Epoch 16 Loss 0.344560
Time taken for 1 epoch 45.54 sec

Epoch 17 Batch 0 Loss 0.3500
Epoch 17 Batch 100 Loss 0.3154
Epoch 17 Batch 200 Loss 0.3274
Epoch 17 Batch 300 Loss 0.3418
Epoch 17 Loss 0.329001
Time taken for 1 epoch 45.79 sec

Epoch 18 Batch 0 Loss 0.3520
Epoch 18 Batch 100 Loss 0.2929
Epoch 18 Batch 200 Loss 0.2871
Epoch 18 Batch 300 Loss 0.3256
Epoch 18 Loss 0.314030
Time taken for 1 epoch 45.37 sec

Epoch 19 Batch 0 Loss 0.3294
Epoch 19 Batch 100 Loss 0.2961
Epoch 19 Batch 200 Loss 0.2917
Epoch 19 Batch 300 Loss 0.2932
Epoch 19 Loss 0.299212
Time taken for 1 epoch 45.29 sec

Epoch 20 Batch 0 Loss 0.3437
Epoch 20 Batch 100 Loss 0.2732
Epoch 20 Batch 200 Loss 0.2868
Epoch 20 Batch 300 Loss 0.3187
Epoch 20 Loss 0.286528
Time taken for 1 epoch 45.30 sec
plt.plot(loss_plot)
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.title('Loss Plot')
plt.show()

png

캡션!

  • evaluate 함수는 여기에서 teacher forcing을 사용하지 않는다는 점을 제외하고 훈련 루프와 유사합니다. 각 타임스텝에서 decoder로의 입력은 hidden state 및 encoder 출력과 함께 이전 예측값입니다.
  • 모델이 end 토큰을 예측할 때 예측을 중지합니다.
  • 그리고 타임스텝마다 attention 가중치를 저장합니다.
def evaluate(image):
    attention_plot = np.zeros((max_length, attention_features_shape))

    hidden = decoder.reset_state(batch_size=1)

    temp_input = tf.expand_dims(load_image(image)[0], 0)
    img_tensor_val = image_features_extract_model(temp_input)
    img_tensor_val = tf.reshape(img_tensor_val, (img_tensor_val.shape[0],
                                                 -1,
                                                 img_tensor_val.shape[3]))

    features = encoder(img_tensor_val)

    dec_input = tf.expand_dims([tokenizer.word_index['<start>']], 0)
    result = []

    for i in range(max_length):
        predictions, hidden, attention_weights = decoder(dec_input,
                                                         features,
                                                         hidden)

        attention_plot[i] = tf.reshape(attention_weights, (-1, )).numpy()

        predicted_id = tf.random.categorical(predictions, 1)[0][0].numpy()
        result.append(tokenizer.index_word[predicted_id])

        if tokenizer.index_word[predicted_id] == '<end>':
            return result, attention_plot

        dec_input = tf.expand_dims([predicted_id], 0)

    attention_plot = attention_plot[:len(result), :]
    return result, attention_plot
def plot_attention(image, result, attention_plot):
    temp_image = np.array(Image.open(image))

    fig = plt.figure(figsize=(10, 10))

    len_result = len(result)
    for i in range(len_result):
        temp_att = np.resize(attention_plot[i], (8, 8))
        grid_size = max(np.ceil(len_result/2), 2)
        ax = fig.add_subplot(grid_size, grid_size, i+1)
        ax.set_title(result[i])
        img = ax.imshow(temp_image)
        ax.imshow(temp_att, cmap='gray', alpha=0.6, extent=img.get_extent())

    plt.tight_layout()
    plt.show()
# captions on the validation set
rid = np.random.randint(0, len(img_name_val))
image = img_name_val[rid]
real_caption = ' '.join([tokenizer.index_word[i]
                        for i in cap_val[rid] if i not in [0]])
result, attention_plot = evaluate(image)

print('Real Caption:', real_caption)
print('Prediction Caption:', ' '.join(result))
plot_attention(image, result, attention_plot)
Real Caption: <start> a close up of the batter about to smack the ball <end>
Prediction Caption: a person catching the bat in the field <end>
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:10: MatplotlibDeprecationWarning: Passing non-integers as three-element position specification is deprecated since 3.3 and will be removed two minor releases later.
  # Remove the CWD from sys.path while we load stuff.

png

자체 이미지로 시도해보기

재미를 위해 방금 훈련한 모델로 자체 이미지를 캡션하는 데 사용할 수 있는 방법을 제공했습니다. 상대적으로 적은 양의 데이터로 훈련되었으므로 이미지가 훈련 데이터와 다를 수 있습니다(결과가 이상할 수 있습니다!).

image_url = 'https://tensorflow.org/images/surf.jpg'
image_extension = image_url[-4:]
image_path = tf.keras.utils.get_file('image'+image_extension, origin=image_url)

result, attention_plot = evaluate(image_path)
print('Prediction Caption:', ' '.join(result))
plot_attention(image_path, result, attention_plot)
# opening the image
Image.open(image_path)
Downloading data from https://tensorflow.org/images/surf.jpg
65536/64400 [==============================] - 0s 3us/step
73728/64400 [==================================] - 0s 3us/step
Prediction Caption: a person surfing in the water <end>
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:10: MatplotlibDeprecationWarning: Passing non-integers as three-element position specification is deprecated since 3.3 and will be removed two minor releases later.
  # Remove the CWD from sys.path while we load stuff.

png

png

다음 단계

축하합니다! 방금 이미지 캡션 모델을 주의 깊게 학습했습니다. 다음으로, 이 예제 Attention을 사용한 신경망 기계 번역을 살펴보세요. 비슷한 구조를 사용하여 스페인어와 영어 문장을 번역합니다. 이 노트북의 코드를 다른 데이터세트에서 학습하는 방법도 실험해 볼 수도 있습니다.