눈에 띄는 이미지 캡션

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

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을 사용한 신경망 기계 번역을 살펴보세요. 비슷한 구조를 사용하여 스페인어와 영어 문장을 번역합니다. 이 노트북의 코드를 다른 데이터세트에서 학습하는 방법도 실험해 볼 수도 있습니다.