Participe do Simpósio Women in ML em 7 de dezembro Inscreva-se agora

Legendagem de imagem com atenção visual

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Veja no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Dada uma imagem como a do exemplo abaixo, seu objetivo é gerar uma legenda como "um surfista surfando em uma onda".

Homem surfando

Fonte da imagem ; Licença: Domínio Público

Para fazer isso, você usará um modelo baseado em atenção, que nos permite ver em quais partes da imagem o modelo se concentra ao gerar uma legenda.

Predição

A arquitetura do modelo é semelhante ao Show, Attend and Tell: Neural Image Caption Generation with Visual Attention .

Este notebook é um exemplo de ponta a ponta. Quando você executa o notebook, ele baixa o conjunto de dados MS-COCO , pré-processa e armazena em cache um subconjunto de imagens usando o Inception V3, treina um modelo de codificador-decodificador e gera legendas em novas imagens usando o modelo treinado.

Neste exemplo, você treinará um modelo em uma quantidade relativamente pequena de dados — as primeiras 30.000 legendas para cerca de 20.000 imagens (porque há várias legendas por imagem no conjunto de dados).

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

Baixe e prepare o conjunto de dados MS-COCO

Você usará o conjunto de dados MS-COCO para treinar seu modelo. O conjunto de dados contém mais de 82.000 imagens, cada uma com pelo menos 5 anotações de legenda diferentes. O código abaixo baixa e extrai o conjunto de dados automaticamente.

# 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 [==============================] - 16s 0us/step
252887040/252872794 [==============================] - 16s 0us/step
Downloading data from http://images.cocodataset.org/zips/train2014.zip
13510574080/13510573713 [==============================] - 784s 0us/step
13510582272/13510573713 [==============================] - 784s 0us/step

Opcional: limite o tamanho do conjunto de treinamento

Para acelerar o treinamento para este tutorial, você usará um subconjunto de 30.000 legendas e suas imagens correspondentes para treinar seu modelo. Escolher usar mais dados resultaria em melhor qualidade de legendagem.

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 person trying to get a cat out of a suitcase <end>

png

Pré-processe as imagens usando o InceptionV3

Em seguida, você usará o InceptionV3 (que é pré-treinado no Imagenet) para classificar cada imagem. Você extrairá recursos da última camada convolucional.

Primeiro, você converterá as imagens no formato esperado do InceptionV3:

  • Redimensionando a imagem para 299px por 299px
  • Pré-processe as imagens usando o método preprocess_input para normalizar a imagem para que ela contenha pixels no intervalo de -1 a 1, que corresponde ao formato das imagens usadas para treinar o InceptionV3.
def load_image(image_path):
    img = tf.io.read_file(image_path)
    img = tf.io.decode_jpeg(img, channels=3)
    img = tf.keras.layers.Resizing(299, 299)(img)
    img = tf.keras.applications.inception_v3.preprocess_input(img)
    return img, image_path

Inicialize o InceptionV3 e carregue os pesos Imagenet pré-treinados

Agora você criará um modelo tf.keras onde a camada de saída é a última camada convolucional na arquitetura InceptionV3. A forma da saída desta camada é 8x8x2048 . Você usa a última camada convolucional porque está usando atenção neste exemplo. Você não executa essa inicialização durante o treinamento porque pode se tornar um gargalo.

  • Você encaminha cada imagem pela rede e armazena o vetor resultante em um dicionário (image_name --> feature_vector).
  • Após todas as imagens passarem pela rede, você salva o dicionário no disco.
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)

Como armazenar em cache os recursos extraídos do InceptionV3

Você pré-processará cada imagem com o InceptionV3 e armazenará em cache a saída no disco. O armazenamento em cache da saída na RAM seria mais rápido, mas também consumiria muita memória, exigindo 8 * 8 * 2048 floats por imagem. No momento da escrita, isso excede as limitações de memória do Colab (atualmente 12 GB de memória).

O desempenho poderia ser melhorado com uma estratégia de cache mais sofisticada (por exemplo, fragmentando as imagens para reduzir a E/S de disco de acesso aleatório), mas isso exigiria mais código.

O cache levará cerca de 10 minutos para ser executado no Colab com uma GPU. Se quiser ver uma barra de progresso, você pode:

  1. Instale o tqdm :

    !pip install tqdm

  2. Importar tqdm:

    from tqdm import tqdm

  3. Altere a seguinte linha:

    for img, path in image_dataset:

    para:

    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())

Pré-processar e tokenizar as legendas

Você transformará as legendas de texto em sequências inteiras usando a camada TextVectorization , com as seguintes etapas:

  • Use adaptar para iterar sobre todas as legendas, dividir as legendas em palavras e calcular um vocabulário das 5.000 palavras principais (para economizar memória).
  • Tokenize todas as legendas mapeando cada palavra para seu índice no vocabulário. Todas as sequências de saída serão preenchidas até o comprimento 50.
  • Crie mapeamentos palavra-a-índice e índice-palavra para exibir os resultados.
caption_dataset = tf.data.Dataset.from_tensor_slices(train_captions)

# We will override the default standardization of TextVectorization to preserve
# "<>" characters, so we preserve the tokens for the <start> and <end>.
def standardize(inputs):
  inputs = tf.strings.lower(inputs)
  return tf.strings.regex_replace(inputs,
                                  r"!\"#$%&\(\)\*\+.,-/:;=?@\[\\\]^_`{|}~", "")

# Max word count for a caption.
max_length = 50
# Use the top 5000 words for a vocabulary.
vocabulary_size = 5000
tokenizer = tf.keras.layers.TextVectorization(
    max_tokens=vocabulary_size,
    standardize=standardize,
    output_sequence_length=max_length)
# Learn the vocabulary from the caption data.
tokenizer.adapt(caption_dataset)
# Create the tokenized vectors
cap_vector = caption_dataset.map(lambda x: tokenizer(x))
# Create mappings for words to indices and indicies to words.
word_to_index = tf.keras.layers.StringLookup(
    mask_token="",
    vocabulary=tokenizer.get_vocabulary())
index_to_word = tf.keras.layers.StringLookup(
    mask_token="",
    vocabulary=tokenizer.get_vocabulary(),
    invert=True)

Divida os dados em treinamento e teste

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)
(24012, 24012, 6004, 6004)

Crie um conjunto de dados tf.data para treinamento

Suas imagens e legendas estão prontas! Em seguida, vamos criar um conjunto de dados tf.data para usar no treinamento do seu modelo.

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

BATCH_SIZE = 64
BUFFER_SIZE = 1000
embedding_dim = 256
units = 512
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.int64]),
          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)

Modelo

Curiosidade: o decodificador abaixo é idêntico ao do exemplo de Tradução Automática Neural com Atenção .

A arquitetura do modelo é inspirada no papel Show, Attend and Tell .

  • Neste exemplo, você extrai os recursos da camada convolucional inferior do InceptionV3, fornecendo um vetor de forma (8, 8, 2048).
  • Você esmaga isso para uma forma de (64, 2048).
  • Esse vetor é então passado pelo codificador CNN (que consiste em uma única camada totalmente conectada).
  • O RNN (aqui GRU) atende sobre a imagem para prever a próxima palavra.
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, tokenizer.vocabulary_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_)

Ponto de verificação

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)

Treinamento

  • Você extrai os recursos armazenados nos respectivos arquivos .npy e, em seguida, passa esses recursos pelo codificador.
  • A saída do codificador, o estado oculto (inicializado em 0) e a entrada do decodificador (que é o token inicial) são passados ​​para o decodificador.
  • O decodificador retorna as previsões e o estado oculto do decodificador.
  • O estado oculto do decodificador é então passado de volta para o modelo e as previsões são usadas para calcular a perda.
  • Use a força do professor para decidir a próxima entrada para o decodificador.
  • O forçamento do professor é a técnica em que a palavra alvo é passada como a próxima entrada para o decodificador.
  • A etapa final é calcular os gradientes e aplicá-los ao otimizador e retropropagação.
# 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([word_to_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.9157
Epoch 1 Batch 100 Loss 1.1384
Epoch 1 Batch 200 Loss 0.9826
Epoch 1 Batch 300 Loss 0.8792
Epoch 1 Loss 1.025084
Time taken for 1 epoch 153.68 sec

Epoch 2 Batch 0 Loss 0.8554
Epoch 2 Batch 100 Loss 0.8062
Epoch 2 Batch 200 Loss 0.7998
Epoch 2 Batch 300 Loss 0.6949
Epoch 2 Loss 0.775522
Time taken for 1 epoch 47.44 sec

Epoch 3 Batch 0 Loss 0.7251
Epoch 3 Batch 100 Loss 0.6746
Epoch 3 Batch 200 Loss 0.7269
Epoch 3 Batch 300 Loss 0.7025
Epoch 3 Loss 0.699518
Time taken for 1 epoch 47.78 sec

Epoch 4 Batch 0 Loss 0.6970
Epoch 4 Batch 100 Loss 0.6150
Epoch 4 Batch 200 Loss 0.6196
Epoch 4 Batch 300 Loss 0.6131
Epoch 4 Loss 0.650994
Time taken for 1 epoch 46.87 sec

Epoch 5 Batch 0 Loss 0.6139
Epoch 5 Batch 100 Loss 0.6305
Epoch 5 Batch 200 Loss 0.6493
Epoch 5 Batch 300 Loss 0.5535
Epoch 5 Loss 0.611642
Time taken for 1 epoch 45.06 sec

Epoch 6 Batch 0 Loss 0.6755
Epoch 6 Batch 100 Loss 0.5603
Epoch 6 Batch 200 Loss 0.5161
Epoch 6 Batch 300 Loss 0.5671
Epoch 6 Loss 0.578854
Time taken for 1 epoch 45.25 sec

Epoch 7 Batch 0 Loss 0.5575
Epoch 7 Batch 100 Loss 0.4937
Epoch 7 Batch 200 Loss 0.5625
Epoch 7 Batch 300 Loss 0.5456
Epoch 7 Loss 0.549154
Time taken for 1 epoch 44.85 sec

Epoch 8 Batch 0 Loss 0.5555
Epoch 8 Batch 100 Loss 0.5142
Epoch 8 Batch 200 Loss 0.4842
Epoch 8 Batch 300 Loss 0.5119
Epoch 8 Loss 0.519941
Time taken for 1 epoch 44.78 sec

Epoch 9 Batch 0 Loss 0.4790
Epoch 9 Batch 100 Loss 0.4654
Epoch 9 Batch 200 Loss 0.4568
Epoch 9 Batch 300 Loss 0.4468
Epoch 9 Loss 0.494242
Time taken for 1 epoch 44.99 sec

Epoch 10 Batch 0 Loss 0.4740
Epoch 10 Batch 100 Loss 0.4592
Epoch 10 Batch 200 Loss 0.4380
Epoch 10 Batch 300 Loss 0.4556
Epoch 10 Loss 0.468823
Time taken for 1 epoch 44.89 sec

Epoch 11 Batch 0 Loss 0.4488
Epoch 11 Batch 100 Loss 0.4423
Epoch 11 Batch 200 Loss 0.4317
Epoch 11 Batch 300 Loss 0.4371
Epoch 11 Loss 0.444164
Time taken for 1 epoch 45.02 sec

Epoch 12 Batch 0 Loss 0.4335
Epoch 12 Batch 100 Loss 0.4473
Epoch 12 Batch 200 Loss 0.3770
Epoch 12 Batch 300 Loss 0.4506
Epoch 12 Loss 0.421234
Time taken for 1 epoch 44.95 sec

Epoch 13 Batch 0 Loss 0.4289
Epoch 13 Batch 100 Loss 0.4215
Epoch 13 Batch 200 Loss 0.3689
Epoch 13 Batch 300 Loss 0.3864
Epoch 13 Loss 0.399234
Time taken for 1 epoch 45.16 sec

Epoch 14 Batch 0 Loss 0.4013
Epoch 14 Batch 100 Loss 0.3571
Epoch 14 Batch 200 Loss 0.3847
Epoch 14 Batch 300 Loss 0.3722
Epoch 14 Loss 0.379495
Time taken for 1 epoch 44.99 sec

Epoch 15 Batch 0 Loss 0.3879
Epoch 15 Batch 100 Loss 0.3652
Epoch 15 Batch 200 Loss 0.3025
Epoch 15 Batch 300 Loss 0.3522
Epoch 15 Loss 0.360756
Time taken for 1 epoch 44.96 sec

Epoch 16 Batch 0 Loss 0.3542
Epoch 16 Batch 100 Loss 0.3199
Epoch 16 Batch 200 Loss 0.3565
Epoch 16 Batch 300 Loss 0.3352
Epoch 16 Loss 0.344851
Time taken for 1 epoch 44.96 sec

Epoch 17 Batch 0 Loss 0.3681
Epoch 17 Batch 100 Loss 0.3477
Epoch 17 Batch 200 Loss 0.3025
Epoch 17 Batch 300 Loss 0.3349
Epoch 17 Loss 0.326141
Time taken for 1 epoch 44.89 sec

Epoch 18 Batch 0 Loss 0.3286
Epoch 18 Batch 100 Loss 0.3203
Epoch 18 Batch 200 Loss 0.3029
Epoch 18 Batch 300 Loss 0.2952
Epoch 18 Loss 0.309969
Time taken for 1 epoch 44.89 sec

Epoch 19 Batch 0 Loss 0.2942
Epoch 19 Batch 100 Loss 0.2920
Epoch 19 Batch 200 Loss 0.2899
Epoch 19 Batch 300 Loss 0.2875
Epoch 19 Loss 0.295664
Time taken for 1 epoch 46.18 sec

Epoch 20 Batch 0 Loss 0.2843
Epoch 20 Batch 100 Loss 0.2907
Epoch 20 Batch 200 Loss 0.2813
Epoch 20 Batch 300 Loss 0.2554
Epoch 20 Loss 0.283829
Time taken for 1 epoch 45.51 sec
plt.plot(loss_plot)
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.title('Loss Plot')
plt.show()

png

Rubrica!

  • A função de avaliação é semelhante ao loop de treinamento, exceto que você não usa forçar o professor aqui. A entrada para o decodificador em cada etapa de tempo são suas previsões anteriores, juntamente com o estado oculto e a saída do codificador.
  • Pare de prever quando o modelo prever o token final.
  • E armazene os pesos de atenção para cada passo de tempo.
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([word_to_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()
        predicted_word = tf.compat.as_text(index_to_word(predicted_id).numpy())
        result.append(predicted_word)

        if predicted_word == '<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(int(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([tf.compat.as_text(index_to_word(i).numpy())
                         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> the bus is driving down the busy street. <end>
Prediction Caption: a bus is on the street <end>

png

Experimente em suas próprias imagens

Para se divertir, abaixo você encontra um método que você pode usar para legendar suas próprias imagens com o modelo que você acabou de treinar. Lembre-se de que ele foi treinado com uma quantidade relativamente pequena de dados e suas imagens podem ser diferentes dos dados de treinamento (por isso, esteja preparado para resultados estranhos!)

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)
Prediction Caption: an image of a man with man standing wearing a [UNK] into the [UNK] <end>

png

png

Próximos passos

Parabéns! Você acabou de treinar um modelo de legendagem de imagem com atenção. Em seguida, dê uma olhada neste exemplo de tradução automática neural com atenção . Ele usa uma arquitetura semelhante para traduzir entre frases em espanhol e inglês. Você também pode experimentar treinar o código neste notebook em um conjunto de dados diferente.