Join us at TensorFlow World, Oct 28-31. Use code TF20 for 20% off select passes. Register now

Classificação de texto com avaliações de filmes

Veja em TensorFlow.org Execute em Google Colab Veja a fonte em GitHub

Este notebook classifica avaliações de filmes como positiva ou negativa usando o texto da avaliação. Isto é um exemplo de classificação binária —ou duas-classes—, um importante e bastante aplicado tipo de problema de aprendizado de máquina.

Usaremos a base de dados IMDB que contém avaliaçòes de mais de 50000 filmes do bando de dados Internet Movie Database. A base é dividida em 25000 avaliações para treinamento e 25000 para teste. Os conjuntos de treinamentos e testes são balanceados, ou seja, eles possuem a mesma quantidade de avaliações positivas e negativas.

O notebook utiliza tf.keras, uma API alto-nível para construir e treinar modelos com TensorFlow. Para mais tutoriais avançados de classificação de textos usando tf.keras, veja em MLCC Text Classification Guide.

# keras.datasets.imdb está quebrado em 1.13 e 1.14, pelo np 1.16.3
!pip install -q tf_nightly
ERROR: tensorflow-gpu 2.0.0b1 has requirement tb-nightly<1.14.0a20190604,>=1.14.0a20190603, but you'll have tb-nightly 1.15.0a20190719 which is incompatible.
from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow as tf
from tensorflow import keras

import numpy as np

print(tf.__version__)
1.15.0-dev20190718

Baixe a base de dados IMDB

A base de dados vem empacotada com TensorFlow. Ele já vem pré-processado de forma que as avaliações (sequências de palavras) foi convertida em sequências de inteiros, onde cada inteiro representa uma palavra específica no dicionário.

O código abaixo baixa a base de dados IMDB para a sua máquina (ou usa a cópia em cache, caso já tenha baixado):

imdb = keras.datasets.imdb

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz
17465344/17464789 [==============================] - 0s 0us/step

O argumento num_words=10000 mantém as 10000 palavras mais frequentes no conjunto de treinamento. As palavras mais raras são descartadas para preservar o tamanho dos dados de forma maleável.

Explore os dados

Vamos parar um momento para entender o formato dos dados. O conjunto de dados vem pré-processado: cada exemplo é um array de inteiros representando as palavras da avaliação do filme. Cada label é um inteiro com valor ou de 0 ou 1, onde 0 é uma avaliação negativa e 1 é uma avaliação positiva.

print("Training entries: {}, labels: {}".format(len(train_data), len(train_labels)))
Training entries: 25000, labels: 25000

O texto das avaliações foi convertido para inteiros, onde cada inteiro representa uma palavra específica no dicionário. Isso é como se parece a primeira revisão:

print(train_data[0])
[1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 4536, 1111, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2025, 19, 14, 22, 4, 1920, 4613, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 1247, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2223, 5244, 16, 480, 66, 3785, 33, 4, 130, 12, 16, 38, 619, 5, 25, 124, 51, 36, 135, 48, 25, 1415, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 5952, 15, 256, 4, 2, 7, 3766, 5, 723, 36, 71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 1029, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2071, 56, 26, 141, 6, 194, 7486, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30, 5535, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 1334, 88, 12, 16, 283, 5, 16, 4472, 113, 103, 32, 15, 16, 5345, 19, 178, 32]

As avaliações dos filmes têm diferentes tamanhos. O código abaixo mostra o número de palavras da primeira e segunda avaliação. Sabendo que o número de entradas da rede neural tem que ser de mesmo também, temos que resolver isto mais tarde.

len(train_data[0]), len(train_data[1])
(218, 189)

Converta os inteiros de volta a palavras

É util saber como converter inteiros de volta a texto. Aqui, criaremos uma função de ajuda para consultar um objeto dictionary que contenha inteiros mapeados em strings:

# Um dicionário mapeando palavras em índices inteiros
word_index = imdb.get_word_index()

# Os primeiros índices são reservados
word_index = {k:(v+3) for k,v in word_index.items()}
word_index["<PAD>"] = 0
word_index["<START>"] = 1
word_index["<UNK>"] = 2  # unknown
word_index["<UNUSED>"] = 3

reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])

def decode_review(text):
    return ' '.join([reverse_word_index.get(i, '?') for i in text])
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb_word_index.json
1646592/1641221 [==============================] - 0s 0us/step

Agora, podemos usar a função decode_review para mostrar o texto da primeira avaliação:

decode_review(train_data[0])
"<START> this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there robert <UNK> is an amazing actor and now the same being director <UNK> father came from the same scottish island as myself so i loved the fact there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for <UNK> and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was so sad and you know what they say if you cry at a film it must have been good and this definitely was also <UNK> to the two little boy's that played the <UNK> of norman and paul they were just brilliant children are often left out of the <UNK> list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don't you think the whole story was so lovely because it was true and was someone's life after all that was shared with us all"

Prepare os dados

As avaliações—o arrays de inteiros— deve ser convertida em tensores (tensors) antes de alimentar a rede neural. Essa conversão pode ser feita de duas formas:

  • Converter os arrays em vetores de 0s e 1s indicando a ocorrência da palavra, similar com one-hot encoding. Por exemplo, a sequência [3, 5] se tornaria um vetor de 10000 dimensões, onde todos seriam 0s, tirando 3 would become a 10,000-dimensional vector that is all zeros except for indices 3 and 5, which are ones. Then, make this the first layer in our network—a Dense layer—that can handle floating point vector data. This approach is memory intensive, though, requiring a num_words * num_reviews size matrix.

  • Alternatively, we can pad the arrays so they all have the same length, then create an integer tensor of shape max_length * num_reviews. We can use an embedding layer capable of handling this shape as the first layer in our network.

In this tutorial, we will use the second approach.

Since the movie reviews must be the same length, we will use the pad_sequences function to standardize the lengths:

train_data = keras.preprocessing.sequence.pad_sequences(train_data,
                                                        value=word_index["<PAD>"],
                                                        padding='post',
                                                        maxlen=256)

test_data = keras.preprocessing.sequence.pad_sequences(test_data,
                                                       value=word_index["<PAD>"],
                                                       padding='post',
                                                       maxlen=256)

Let's look at the length of the examples now:

len(train_data[0]), len(train_data[1])
(256, 256)

And inspect the (now padded) first review:

print(train_data[0])
[   1   14   22   16   43  530  973 1622 1385   65  458 4468   66 3941
    4  173   36  256    5   25  100   43  838  112   50  670    2    9
   35  480  284    5  150    4  172  112  167    2  336  385   39    4
  172 4536 1111   17  546   38   13  447    4  192   50   16    6  147
 2025   19   14   22    4 1920 4613  469    4   22   71   87   12   16
   43  530   38   76   15   13 1247    4   22   17  515   17   12   16
  626   18    2    5   62  386   12    8  316    8  106    5    4 2223
 5244   16  480   66 3785   33    4  130   12   16   38  619    5   25
  124   51   36  135   48   25 1415   33    6   22   12  215   28   77
   52    5   14  407   16   82    2    8    4  107  117 5952   15  256
    4    2    7 3766    5  723   36   71   43  530  476   26  400  317
   46    7    4    2 1029   13  104   88    4  381   15  297   98   32
 2071   56   26  141    6  194 7486   18    4  226   22   21  134  476
   26  480    5  144   30 5535   18   51   36   28  224   92   25  104
    4  226   65   16   38 1334   88   12   16  283    5   16 4472  113
  103   32   15   16 5345   19  178   32    0    0    0    0    0    0
    0    0    0    0    0    0    0    0    0    0    0    0    0    0
    0    0    0    0    0    0    0    0    0    0    0    0    0    0
    0    0    0    0]

Construindo o modelo

A rede neural é criada por camadas empilhadas —isso necessita duas decisões arquiteturais principais:

  • Quantas camadas serão usadas no modelo?
  • Quantas hidden units são usadas em cada camada?

Neste exemplo, os dados de entrada são um array de palavras-índices. As labels para predizer são ou 0 ou 1. Vamos construir um modelo para este problema:

# O formato de entrada é a contagem vocabulário usados pelas avaliações dos filmes (10000 palavras)
vocab_size = 10000

model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, 16))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(16, activation=tf.nn.relu))
model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid))

model.summary()
WARNING: Logging before flag parsing goes to stderr.
W0719 13:53:38.642651 140701208585984 deprecation.py:506] From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/keras/initializers.py:119: calling RandomUniform.__init__ (from tensorflow.python.ops.init_ops) with dtype is deprecated and will be removed in a future version.
Instructions for updating:
Call initializer instance with the dtype argument instead of passing it to the constructor
W0719 13:53:38.658064 140701208585984 deprecation.py:506] From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/ops/resource_variable_ops.py:1633: calling BaseResourceVariable.__init__ (from tensorflow.python.ops.resource_variable_ops) with constraint is deprecated and will be removed in a future version.
Instructions for updating:
If using Keras pass *_constraint arguments to layers.

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, None, 16)          160000    
_________________________________________________________________
global_average_pooling1d (Gl (None, 16)                0         
_________________________________________________________________
dense (Dense)                (None, 16)                272       
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 17        
=================================================================
Total params: 160,289
Trainable params: 160,289
Non-trainable params: 0
_________________________________________________________________

As camadas são empilhadas sequencialmente para construir o classificador:

  1. A primeira camada é uma camada Embedding layer (Embedding layer). Essa camada pega o vocabulário em inteiros e olha o vetor embedding em cada palavra-index. Esses vetores são aprendidos pelo modelo, ao longo do treinamento. Os vetores adicionam a dimensão ao array de saída. As dimensões resultantes são: (batch, sequence, embedding).
  2. Depois, uma camada GlobalAveragePooling1D retorna um vetor de saída com comprimento fixo para cada exemplo fazendo a média da sequência da dimensão. Isso permite o modelo de lidar com entradas de tamanhos diferentes da maneira mais simples possível.
  3. Esse vetor de saída com tamanho fixo passa por uma camada fully-connected (Dense) layer com 16 hidden units.
  4. A última camada é uma densely connected com um único nó de saída. Usando uma função de ativação sigmoid, esse valor é um float que varia entre 0 e 1, representando a probabilidade, ou nível de confiança.

Hidden units

O modelo abaixo tem duas camadas intermediárias ou "hidden" (hidden layers), entre a entrada e saída. O número de saídas (unidades— units—, nós ou neurônios) é a dimensão do espaço representacional para a camada. Em outras palavras, a quantidade de liberdade que a rede é permitida enquanto aprende uma representação interna.

Se o modelo tem mais hidden units (um espaço representacional de maior dimensão), e/ou mais camadas, então a rede pode aprender representações mais complexas. Entretanto, isso faz com que a rede seja computacionamente mais custosa e pode levar o aprendizado de padrões não desejados— padrões que melhoram a performance com os dados de treinamento, mas não com os de teste. Isso se chama overfitting, e exploraremos mais tarde.

Função Loss e otimizadores (optimizer)

O modelo precisa de uma função loss e um otimizador (optimizer) para treinamento. Já que é um problema de classificação binário e o modelo tem com saída uma probabilidade (uma única camada com ativação sigmoide), usaremos a função loss binary_crossentropy.

Essa não é a única escolha de função loss, você poderia escolher, no lugar, a mean_squared_error. Mas, geralmente, binary_crossentropy é melhor para tratar probabilidades— ela mede a "distância" entre as distribuições de probabilidade, ou, no nosso caso, sobre a distribuição real e as previsões.

Mais tarde, quando explorarmos problemas de regressão (como, predizer preço de uma casa), veremos como usar outra função loss chamada mean squared error.

Agora, configure o modelo para usar o optimizer a função loss:

model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['acc'])
W0719 13:53:38.740554 140701208585984 deprecation.py:323] From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/ops/nn_impl.py:183: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where

Crie um conjunto de validação

Quando treinando. queremos checar a acurácia do modelo com os dados que ele nunca viu. Crie uma conjunto de validação tirando 10000 exemplos do conjunto de treinamento original. (Por que não usar o de teste agora? Nosso objetivo é desenvolver e melhorar (tunar) nosso modelo usando somente os dados de treinamento, depois usar o de teste uma única vez para avaliar a acurácia).

x_val = train_data[:10000]
partial_x_train = train_data[10000:]

y_val = train_labels[:10000]
partial_y_train = train_labels[10000:]

Treine o modelo

Treine o modelo em 40 epochs com mini-batches de 512 exemplos. Essas 40 iterações sobre todos os exemplos nos tensores x_train e y_train. Enquanto treina, monitore os valores do loss e da acurácia do modelo nos 10000 exemplos do conjunto de validação:

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=40,
                    batch_size=512,
                    validation_data=(x_val, y_val),
                    verbose=1)
W0719 13:53:38.951165 140701208585984 deprecation.py:323] From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/keras/optimizer_v2/optimizer_v2.py:457: BaseResourceVariable.constraint (from tensorflow.python.ops.resource_variable_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Apply a constraint manually following the optimizer update step.

Train on 15000 samples, validate on 10000 samples
Epoch 1/40
15000/15000 [==============================] - 1s 43us/sample - loss: 0.6923 - acc: 0.6038 - val_loss: 0.6905 - val_acc: 0.7151
Epoch 2/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.6874 - acc: 0.7367 - val_loss: 0.6836 - val_acc: 0.7495
Epoch 3/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.6751 - acc: 0.7518 - val_loss: 0.6665 - val_acc: 0.7541
Epoch 4/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.6511 - acc: 0.7651 - val_loss: 0.6388 - val_acc: 0.7497
Epoch 5/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.6154 - acc: 0.7914 - val_loss: 0.6013 - val_acc: 0.7849
Epoch 6/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.5701 - acc: 0.8141 - val_loss: 0.5575 - val_acc: 0.8066
Epoch 7/40
15000/15000 [==============================] - 1s 36us/sample - loss: 0.5193 - acc: 0.8322 - val_loss: 0.5100 - val_acc: 0.8219
Epoch 8/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.4688 - acc: 0.8501 - val_loss: 0.4667 - val_acc: 0.8358
Epoch 9/40
15000/15000 [==============================] - 1s 34us/sample - loss: 0.4226 - acc: 0.8653 - val_loss: 0.4288 - val_acc: 0.8482
Epoch 10/40
15000/15000 [==============================] - 1s 34us/sample - loss: 0.3828 - acc: 0.8761 - val_loss: 0.3983 - val_acc: 0.8542
Epoch 11/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.3498 - acc: 0.8847 - val_loss: 0.3740 - val_acc: 0.8619
Epoch 12/40
15000/15000 [==============================] - 1s 34us/sample - loss: 0.3226 - acc: 0.8918 - val_loss: 0.3554 - val_acc: 0.8649
Epoch 13/40
15000/15000 [==============================] - 1s 34us/sample - loss: 0.3004 - acc: 0.8984 - val_loss: 0.3396 - val_acc: 0.8708
Epoch 14/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.2807 - acc: 0.9039 - val_loss: 0.3280 - val_acc: 0.8733
Epoch 15/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.2641 - acc: 0.9080 - val_loss: 0.3189 - val_acc: 0.8755
Epoch 16/40
15000/15000 [==============================] - 1s 37us/sample - loss: 0.2493 - acc: 0.9140 - val_loss: 0.3111 - val_acc: 0.8758
Epoch 17/40
15000/15000 [==============================] - 1s 37us/sample - loss: 0.2355 - acc: 0.9181 - val_loss: 0.3047 - val_acc: 0.8783
Epoch 18/40
15000/15000 [==============================] - 1s 36us/sample - loss: 0.2235 - acc: 0.9227 - val_loss: 0.2996 - val_acc: 0.8808
Epoch 19/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.2125 - acc: 0.9258 - val_loss: 0.2951 - val_acc: 0.8826
Epoch 20/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.2026 - acc: 0.9293 - val_loss: 0.2921 - val_acc: 0.8828
Epoch 21/40
15000/15000 [==============================] - 1s 37us/sample - loss: 0.1925 - acc: 0.9349 - val_loss: 0.2897 - val_acc: 0.8832
Epoch 22/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.1841 - acc: 0.9387 - val_loss: 0.2879 - val_acc: 0.8842
Epoch 23/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.1756 - acc: 0.9429 - val_loss: 0.2875 - val_acc: 0.8841
Epoch 24/40
15000/15000 [==============================] - 1s 36us/sample - loss: 0.1682 - acc: 0.9461 - val_loss: 0.2863 - val_acc: 0.8843
Epoch 25/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.1607 - acc: 0.9489 - val_loss: 0.2855 - val_acc: 0.8853
Epoch 26/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.1539 - acc: 0.9517 - val_loss: 0.2866 - val_acc: 0.8827
Epoch 27/40
15000/15000 [==============================] - 1s 36us/sample - loss: 0.1476 - acc: 0.9545 - val_loss: 0.2866 - val_acc: 0.8839
Epoch 28/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.1415 - acc: 0.9565 - val_loss: 0.2875 - val_acc: 0.8852
Epoch 29/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.1362 - acc: 0.9595 - val_loss: 0.2898 - val_acc: 0.8838
Epoch 30/40
15000/15000 [==============================] - 1s 36us/sample - loss: 0.1306 - acc: 0.9607 - val_loss: 0.2896 - val_acc: 0.8853
Epoch 31/40
15000/15000 [==============================] - 1s 36us/sample - loss: 0.1250 - acc: 0.9635 - val_loss: 0.2912 - val_acc: 0.8849
Epoch 32/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.1199 - acc: 0.9667 - val_loss: 0.2932 - val_acc: 0.8852
Epoch 33/40
15000/15000 [==============================] - 1s 36us/sample - loss: 0.1151 - acc: 0.9678 - val_loss: 0.2959 - val_acc: 0.8843
Epoch 34/40
15000/15000 [==============================] - 1s 36us/sample - loss: 0.1107 - acc: 0.9689 - val_loss: 0.2984 - val_acc: 0.8832
Epoch 35/40
15000/15000 [==============================] - 1s 37us/sample - loss: 0.1065 - acc: 0.9699 - val_loss: 0.3019 - val_acc: 0.8836
Epoch 36/40
15000/15000 [==============================] - 1s 36us/sample - loss: 0.1023 - acc: 0.9722 - val_loss: 0.3039 - val_acc: 0.8835
Epoch 37/40
15000/15000 [==============================] - 1s 35us/sample - loss: 0.0981 - acc: 0.9735 - val_loss: 0.3069 - val_acc: 0.8822
Epoch 38/40
15000/15000 [==============================] - 1s 34us/sample - loss: 0.0943 - acc: 0.9756 - val_loss: 0.3110 - val_acc: 0.8828
Epoch 39/40
15000/15000 [==============================] - 1s 34us/sample - loss: 0.0912 - acc: 0.9765 - val_loss: 0.3152 - val_acc: 0.8807
Epoch 40/40
15000/15000 [==============================] - 1s 34us/sample - loss: 0.0872 - acc: 0.9781 - val_loss: 0.3177 - val_acc: 0.8806

Avalie o modelo

E vamos ver como o modelo se saiu. Dois valores serão retornados. Loss (um número que representa o nosso erro, valores mais baixos são melhores), e acurácia.

results = model.evaluate(test_data, test_labels)

print(results)
25000/25000 [==============================] - 1s 30us/sample - loss: 0.3393 - acc: 0.8705
[0.3392547805643082, 0.87048]

Está é uma aproximação ingênua que conseguiu uma acurácia de 87%. Com mais abordagens avançadas, o modelo deve chegar em 95%.

Crie um gráfico de acurácia e loss por tempo

model.fit() retorna um objeto History que contém um dicionário de tudo o que aconteceu durante o treinamento:

history_dict = history.history
history_dict.keys()
dict_keys(['loss', 'val_loss', 'acc', 'val_acc'])

Tem 4 entradas: uma para cada métrica monitorada durante a validação e treinamento. Podemos usá-las para plotar a comparação do loss de treinamento e validação, assim como a acurácia de treinamento e validação:

import matplotlib.pyplot as plt

acc = history_dict['acc']
val_acc = history_dict['val_acc']
loss = history_dict['loss']
val_loss = history_dict['val_loss']

epochs = range(1, len(acc) + 1)

# "bo" de "blue dot" ou "ponto azul"
plt.plot(epochs, loss, 'bo', label='Training loss')
# b de "solid blue line" "linha azul"
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()
<Figure size 640x480 with 1 Axes>
plt.clf()   # limpa a figura

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()

plt.show()

png

No gráfico, os pontos representam o loss e acurácia de treinamento, e as linhas são o loss e a acurácia de validação.

Note que o loss de treinamento diminui a cada epoch e a acurácia aumenta. Isso é esperado quando usado um gradient descent optimization—ele deve minimizar a quantidade desejada a cada iteração.

Esse não é o caso do loss e da acurácia de validação— eles parecem ter um pico depois de 20 epochs. Isso é um exemplo de overfitting: o modelo desempenha melhor nos dados de treinamento do que quando usado com dados nunca vistos. Depois desse ponto, o modelo otimiza além da conta e aprende uma representação especifica para os dados de treinamento e não generaliza para os dados de teste.

Para esse caso particular, podemos prevenir o overfitting simplesmente parando o treinamento após mais ou menos 20 epochs. Depois, você verá como fazer isso automaticamente com um callback.

#@title MIT License
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.