Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/tutorials/keras/text_classification.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.
#@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.

Classificação de texto

Este tutorial demonstra a classificação de texto, começando pela classificação de arquivos de texto sem formatação armazenados no disco. Você treinará um classificador binário para fazer análise de sentimento para um dataset do IMDB. No final do notebook, você poderá fazer um exercício, em que treinará um classificador multiclasse para prever a tag de uma pergunta de programação no Stack Overflow.

import matplotlib.pyplot as plt import os import re import shutil import string import tensorflow as tf from tensorflow.keras import layers from tensorflow.keras import losses
print(tf.__version__)

Análise de sentimento

Este notebook treina um modelo de análise de sentimento para classificar avaliações de filmes como positivas ou negativas, com base no texto da avaliação. Este é um exemplo de classificação binária, ou de duas classes, um tipo de problema de aprendizado de máquina importante, com diversas aplicações.

Você usará o Large Movie Review Dataset, que contém o texto de 50 mil avaliações de filmes do Internet Movie Database. Elas são divididas em 25 mil avaliações para treinamento e 25 mil para teste. Os conjuntos de treinamento e teste são equilibrados, ou seja, contêm a mesma quantidade de avaliações positivas e negativas.

Baixe e explore o dataset do IMDB

Vamos baixar e extrair o dataset, depois vamos explorar a estrutura de diretórios.

url = "https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz" dataset = tf.keras.utils.get_file("aclImdb_v1", url, untar=True, cache_dir='.', cache_subdir='') dataset_dir = os.path.join(os.path.dirname(dataset), 'aclImdb')
os.listdir(dataset_dir)
train_dir = os.path.join(dataset_dir, 'train') os.listdir(train_dir)

Os diretórios aclImdb/train/pos e aclImdb/train/neg contêm diversos arquivos de texto, sendo que cada um é uma única avaliação de filme. Vamos dar uma olhada em um desses arquivos.

sample_file = os.path.join(train_dir, 'pos/1181_9.txt') with open(sample_file) as f: print(f.read())

Carregue o dataset

Agora, você vai carregar os dados para fora do disco e colocá-los em um formato adequado para o treinamento. Para isso, você usará um utilitário muito útil, o text_dataset_from_directory, que espera uma estrutura de diretórios, como mostrado abaixo.

main_directory/ ...class_a/ ......a_text_1.txt ......a_text_2.txt ...class_b/ ......b_text_1.txt ......b_text_2.txt

Para preparar um dataset para fazer classificação binária, você precisa de duas pastas no disco, correspondentes a class_a e class_b. Elas conterão avaliações positivas e negativas de filmes, que podem ser encontradas em aclImdb/train/pos e aclImdb/train/neg. Como o dataset do IMDB contém pastas adicionais, você vai removê-las antes de usar o utilitário.

remove_dir = os.path.join(train_dir, 'unsup') shutil.rmtree(remove_dir)

Agora, você usará o utilitário text_dataset_from_directory para criar um tf.data.Dataset com rótulos. tf.data é uma coleção de ferramentas avançadas para trabalhar com dados.

Ao realizar um experimento de aprendizado de máquina, é uma prática recomendada dividir o dataset em três: treinamento, validação e teste.

O dataset do IMDB já foi dividido em conjuntos de treinamento e teste, mas ainda falta um de validação. Vamos criar um conjunto de validação utilizando uma divisão 80/20 para os dados do treinamento por meio do argumento validation_split abaixo.

batch_size = 32 seed = 42 raw_train_ds = tf.keras.utils.text_dataset_from_directory( 'aclImdb/train', batch_size=batch_size, validation_split=0.2, subset='training', seed=seed)

Como podemos ver acima, há 25 mil exemplos na pasta de treinamento, das quais serão usadas 80%, ou 20 mil, para treinamento. Como veremos em breve, você pode treinar um modelo passando um dataset diretamente para model.fit. Se você ainda estiver aprendendo sobre tf.data, também pode fazer a iteração do dataset e exibir alguns exemplos, conforme mostrado abaixo.

for text_batch, label_batch in raw_train_ds.take(1): for i in range(3): print("Review", text_batch.numpy()[i]) print("Label", label_batch.numpy()[i])

Observe que a avaliação contém texto bruto (com pontuações e tags HTML, como <br/>). Você verá como lidar com isso na próxima seção.

Os rótulos são 0 e 1. Para ver qual deles corresponde a avaliações positivas ou negativas de filmes, confira a propriedade class_names do dataset.

print("Label 0 corresponds to", raw_train_ds.class_names[0]) print("Label 1 corresponds to", raw_train_ds.class_names[1])

Em seguida, você criará um dataset de validação e de teste. Você usará as 5 mil avaliações restantes do conjunto de treinamento para a validação.

Observação: ao usar os argumentos validation_split e subset, especifique uma semente aleatória ou passe shuffle=False para que as divisões de validação e treinamento não se sobreponham.

raw_val_ds = tf.keras.utils.text_dataset_from_directory( 'aclImdb/train', batch_size=batch_size, validation_split=0.2, subset='validation', seed=seed)
raw_test_ds = tf.keras.utils.text_dataset_from_directory( 'aclImdb/test', batch_size=batch_size)

Prepare o dataset para treinamento

Em seguida, você vai padronizar, tokenizar e vetorizar os dados usando a camada tf.keras.layers.TextVectorization.

Padronização refere-se ao pré-processamento do texto, tipicamente para remover pontuações ou elementos HTML a fim de simplificar o dataset. Tokenização refere-se à divisão das strings em tokens (por exemplo, dividir uma frase em palavras individuais, fazendo a divisão a cada espaço). Vetorização refere-se à conversão de tokens em números para que eles possam ser alimentados em uma rede neural. Todas essas tarefas podem ser feitas com essa camada.

Como visto acima, as avaliações contêm diversas tags HTML, como <br />. Elas não serão removidas pelo padronizador padrão na camada TextVectorization (que converte texto em letras minúsculas e remove as pontuações por padrão, mas não retira código HTML). Você escreverá uma função de padronização personalizada para remover código HTML.

Observação: para evitar o desvio de treinamento/teste (também conhecido como desvio de treinamento/serviço), é importante pré-processar os dados de forma idêntica no momento de treinamento e teste. Para isso, a camada TextVectorization pode ser incluída diretamente dentro do modelo, conforme exibido posteriormente neste tutorial.

def custom_standardization(input_data): lowercase = tf.strings.lower(input_data) stripped_html = tf.strings.regex_replace(lowercase, '<br />', ' ') return tf.strings.regex_replace(stripped_html, '[%s]' % re.escape(string.punctuation), '')

Em seguida, você criará uma camada TextVectorization, que será usada para padronizar, tokenizar e vetorizar os dados. Você deve definir output_mode como int para criar índices de inteiros únicos para cada token.

Observe que você está utilizando a função de divisão padrão e a função de padronização personalizada definida acima. Você também definirá algumas constantes para o modelo, como um mínimo explícito sequence_length, que fará a camada preencher ou truncar sequências para valores exatamente iguais a sequence_length.

max_features = 10000 sequence_length = 250 vectorize_layer = layers.TextVectorization( standardize=custom_standardization, max_tokens=max_features, output_mode='int', output_sequence_length=sequence_length)

Em seguida, chame adapt para adequar o estado da camada de pré-processamento ao dataset. Isso fará com que o modelo crie um índice de strings para os números inteiros.

Observação: é importante usar somente os dados de treinamento ao chamar adapt, já que o uso do dataset de teste vazaria informações.

# Make a text-only dataset (without labels), then call adapt train_text = raw_train_ds.map(lambda x, y: x) vectorize_layer.adapt(train_text)

Vamos criar uma função para ver o resultado ao usar esta camada para pré-processar alguns dados.

def vectorize_text(text, label): text = tf.expand_dims(text, -1) return vectorize_layer(text), label
# retrieve a batch (of 32 reviews and labels) from the dataset text_batch, label_batch = next(iter(raw_train_ds)) first_review, first_label = text_batch[0], label_batch[0] print("Review", first_review) print("Label", raw_train_ds.class_names[first_label]) print("Vectorized review", vectorize_text(first_review, first_label))

Conforme visto acima, cada token foi substituído por um inteiro. Para visualizar o token (string) ao qual cada inteiro corresponde, você pode chamar .get_vocabulary() na camada.

print("1287 ---> ",vectorize_layer.get_vocabulary()[1287]) print(" 313 ---> ",vectorize_layer.get_vocabulary()[313]) print('Vocabulary size: {}'.format(len(vectorize_layer.get_vocabulary())))

Está quase tudo pronto para treinar o modelo. Como etapa final de pré-processamento, você aplicará a camada TextVectorization criada anteriormente aos datasets de treinamento, validação e teste.

train_ds = raw_train_ds.map(vectorize_text) val_ds = raw_val_ds.map(vectorize_text) test_ds = raw_test_ds.map(vectorize_text)

Configure o dataset para melhor desempenho

Há dois métodos importantes que você deve usar ao carregar os dados para garantir que a I/O não seja bloqueada.

.cache mantém os dados na memória após o carregamento fora do disco. Isso garante que o dataset não se torne um gargalo ao treinar seu modelo. Se o dataset for muito grande para a memória, você também pode usar esse método para criar um cache no disco eficaz, que tem uma leitura mais eficiente do que vários arquivos pequenos.

/prefetch sobrepõe o pré-processamento de dados e a execução do modelo durante o treinamento.

Saiba mais sobre ambos os métodos, além de como armazenar os dados em cache no disco, no guia sobre desempenho dos dados.

AUTOTUNE = tf.data.AUTOTUNE train_ds = train_ds.cache().prefetch(buffer_size=AUTOTUNE) val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE) test_ds = test_ds.cache().prefetch(buffer_size=AUTOTUNE)

Crie o modelo

Chegou a hora de criar sua rede neural:

embedding_dim = 16
model = tf.keras.Sequential([ layers.Embedding(max_features + 1, embedding_dim), layers.Dropout(0.2), layers.GlobalAveragePooling1D(), layers.Dropout(0.2), layers.Dense(1)]) model.summary()

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

  1. A primeira é uma camada Embedding, que recebe avaliações codificadas em inteiros e avalia um vetor de embedding para cada palavra-índice. Esses vetores são aprendidos à medida que o modelo é treinado. Os vetores acrescentam uma dimensão à matriz de saída. As dimensões resultantes são: (batch, sequence, embedding) (lote, sequência, embedding). Para saber mais sobre embeddings, confira o tutorial Embeddings de palavras.

  2. A segunda camada é GlobalAveragePooling1D, que retorna um vetor de saída de tamanho fixo para cada exemplo, calculando a média da dimensão de sequência. Dessa forma, o modelo consegue lidar com entradas de tamanho variável da forma mais simples possível.

  3. A última camada é densamente conectada com um único nó de saída.

Função de perda e otimizador

Todo modelo precisa de uma função de perda e um otimizador para o treinamento. Como este é um problema de classificação binária e o modelo gera como saída uma probabilidade (uma camada de unidade única com uma ativação sigmóide), você usará a função de perda losses.BinaryCrossentropy.

Agora, configure o modelo para usar um otimizador e uma função de perda:

model.compile(loss=losses.BinaryCrossentropy(from_logits=True), optimizer='adam', metrics=tf.metrics.BinaryAccuracy(threshold=0.0))

Treine o modelo

Você passará o objeto dataset ao método fit para treinar o modelo.

epochs = 10 history = model.fit( train_ds, validation_data=val_ds, epochs=epochs)

Avalie o modelo

Vamos conferir o desempenho do modelo. Serão retornados dois valores: perda (um número que representa o erro; quanto menor, melhor) e exatidão.

loss, accuracy = model.evaluate(test_ds) print("Loss: ", loss) print("Accuracy: ", accuracy)

Essa estratégia bem simples atinge uma exatidão de cerca de 86%.

Crie um gráfico de exatidão e perda ao longo do tempo

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

history_dict = history.history history_dict.keys()

Há quatro entradas: uma para cada métrica monitorada durante o treinamento e a validação. Você usará esses valores para plotar a perda do treinamento e da validação para fins comparativos, além da exatidão do treinamento e da validação:

acc = history_dict['binary_accuracy'] val_acc = history_dict['val_binary_accuracy'] loss = history_dict['loss'] val_loss = history_dict['val_loss'] epochs = range(1, len(acc) + 1) # "bo" is for "blue dot" plt.plot(epochs, loss, 'bo', label='Training loss') # b is for "solid blue line" 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()
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(loc='lower right') plt.show()

Neste gráfico, os pontos representam a perda e exatidão do treinamento, enquanto as linhas sólidas representam a perda e exatidão da validação.

Observe que a perda do treinamento diminui a cada época, e a exatidão do treinamento aumenta a cada época. Isso é o esperado ao usar uma otimização do método do gradiente descendente, que deve minimizar a quantidade desejada em cada iteração.

Esse não é o caso para a perda e exatidão de validação, que parecem atingir o pico antes da exatidão do treinamento. Este é um exemplo de overfitting: o modelo tem desempenho melhor com os dados de treinamento em comparação a dados nunca vistos antes. Após esse ponto, o modelo sofre uma sobreotimização e aprende representações específicas dos dados de treinamento que não oferecem boas generalizações para os dados de teste.

Para este caso específico, é possível evitar o overfitting simplesmente parando o treinamento quando a exatidão da validação deixa de aumentar. Uma forma de fazer isso é usando o callback tf.keras.callbacks.EarlyStopping.

Exporte o modelo

No código acima, você aplicou a camada TextVectorization ao dataset antes de alimentar o modelo com texto. Se quiser tornar o modelo capaz de processar strings brutas (por exemplo, para simplificar a implantação), é possível incluir a camada TextVectorization dentro do modelo. Para isso, você pode criar um novo modelo usando os pesos que acabou de treinar.

export_model = tf.keras.Sequential([ vectorize_layer, model, layers.Activation('sigmoid') ]) export_model.compile( loss=losses.BinaryCrossentropy(from_logits=False), optimizer="adam", metrics=['accuracy'] ) # Test it with `raw_test_ds`, which yields raw strings loss, accuracy = export_model.evaluate(raw_test_ds) print(accuracy)

Inferência de dados novos

Para fazer previsões para novos exemplos, basta chamar model.predict().

examples = [ "The movie was great!", "The movie was okay.", "The movie was terrible..." ] export_model.predict(examples)

Ao incluir a lógica de pré-processamento de texto dentro do modelo, você pode exportar um modelo para produção que simplifica a implantação e reduz o potencial de desvio de treinamento/teste.

Há uma diferença de desempenho que você deve considerar ao escolher onde aplicar a camada TextVectorization. Ao usá-la fora do modelo, você pode fazer o processamento assíncrono na CPU e armazenar os dados em buffer ao treinar na GPU. Portanto, se você estiver treinando seu modelo na GPU, deve escolher essa opção para obter o melhor desempenho ao desenvolver o modelo. Depois, quando você estiver pronto para preparar a implantação, inclua a camada TextVectorization dentro do modelo.

Confira este tutorial para saber mais sobre como salvar modelos.

Exercício: classificação multiclasse para perguntas do Stack Overflow

Este tutorial mostrou como treinar um classificador binário do zero usando o dataset do IMDB. Você pode fazer um exercício: modifique este notebook para treinar um classificador multiclasse que preveja a tag de uma pergunta de programação feita no Stack Overflow.

Um dataset foi preparado para uso, contendo o texto de milhares de perguntas de programação (por exemplo, "Como posso ordenar um dicionário por valor no Python?") publicadas no Stack Overflow. Cada pergunta é rotulada com exatamente uma tag (Python, CSharp, JavaScript ou Java). Sua tarefa é receber uma pergunta como entrada e prever a tag apropriada, que, neste caso, é Python.

Você usará um dataset que contém milhares de perguntas extraídas do dataset público do Stack Overflow, que é bem maior, no BigQuery, contendo mais de 17 milhões de publicações.

Após baixar o dataset, você verá que ele tem uma estrutura de diretórios similar ao dataset do IMDB utilizado anteriormente:

train/ ...python/ ......0.txt ......1.txt ...javascript/ ......0.txt ......1.txt ...csharp/ ......0.txt ......1.txt ...java/ ......0.txt ......1.txt

Observação: para aumentar a dificuldade do problema de classificação, as ocorrências das palavras Python, CSharp, JavaScript e Java nas perguntas de programação foram substituídas pela palavra blank (em branco), já que diversas perguntas contêm a linguagem de programação em questão.

Para fazer este exercício, você deve modificar este notebook para que funcione com o dataset do Stack Overflow das seguintes maneiras:

  1. Na parte superior do notebook, atualize o código que baixa o dataset do IMDB com o código que baixa o dataset do Stack Overflow, que já foi preparado. Como o dataset do Stack Overflow tem uma estrutura de diretórios parecida, você não precisará fazer muitas modificações.

  2. Modifique a última camada do modelo para Dense(4), pois agora há quatro classes de saída.

  3. Ao compilar o modelo, altere a perda para tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True). Esta é a função de perda correta a ser usada para problemas de classificação muilticlasse, quando os rótulos de cada classe são inteiros (neste caso, podem ser 0, 1, 2 ou 3). Além disso, altere as métricas para metrics=['accuracy'], já que este é um problema de classificação multicasse (tf.metrics.BinaryAccuracy é usado somente para classificadores binários).

  4. Ao plotar a precisão ao longo do tempo, altere binary_accuracy e val_binary_accuracy para accuracy e val_accuracy, respectivamente.

  5. Após fazer essas alterações, você poderá treinar um classificador multiclasse.

Saiba mais

Este tutorial mostrou como fazer a classificação de texto do zero. Para saber mais sobre o workflow de classificação de texto de forma geral, confira o guia Classificação de texto no Google Developers.