Classifique o texto com BERT

Ver no TensorFlow.org Executar no Google Colab Ver no GitHub Baixar caderno Veja o modelo TF Hub

Este tutorial contém o código completo para ajustar o BERT para realizar uma análise de sentimento em um conjunto de dados de resenhas de filmes IMDB em texto simples. Além de treinar um modelo, você aprenderá como pré-processar o texto em um formato apropriado.

Neste bloco de notas, você irá:

  • Carregar o conjunto de dados IMDB
  • Carregue um modelo BERT do TensorFlow Hub
  • Construa seu próprio modelo combinando BERT com um classificador
  • Treine seu próprio modelo, ajustando o BERT como parte desse
  • Salve seu modelo e use-o para classificar frases

Se você é novo para trabalhar com o conjunto de dados IMDB, consulte a classificação de texto básico para mais detalhes.

Sobre BERT

BERT e outras arquiteturas de codificador Transformer têm sido de grande sucesso em uma variedade de tarefas em PNL (processamento de linguagem natural). Eles calculam representações de espaço vetorial de linguagem natural que são adequadas para uso em modelos de aprendizado profundo. A família de modelos BERT usa a arquitetura do codificador Transformer para processar cada token de texto de entrada no contexto completo de todos os tokens antes e depois, daí o nome: Bidirectional Encoder Representations from Transformers.

Os modelos BERT são geralmente pré-treinados em um grande corpo de texto e, em seguida, ajustados para tarefas específicas.

Configurar

# A dependency of the preprocessing for BERT inputs
pip install -q -U tensorflow-text

Você vai usar o otimizador AdamW de tensorflow / modelos .

pip install -q tf-models-official
import os
import shutil

import tensorflow as tf
import tensorflow_hub as hub
import tensorflow_text as text
from official.nlp import optimization  # to create AdamW optimizer

import matplotlib.pyplot as plt

tf.get_logger().setLevel('ERROR')
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/pkg_resources/__init__.py:119: PkgResourcesDeprecationWarning: 0.18ubuntu0.18.04.1 is an invalid version and will not be supported in a future release
  PkgResourcesDeprecationWarning,

Análise de sentimentos

Este notebook treina um modelo de análise de sentimento de revisões de filme classificar como positivo ou negativo, com base no texto da revisão.

Você usará o Grande Movie Review Dataset que contém o texto de 50.000 revisões de filme da Internet Movie Database .

Baixe o conjunto de dados IMDB

Vamos fazer o download e extrair o conjunto de dados e, em seguida, explorar a estrutura do diretório.

url = 'https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz'

dataset = tf.keras.utils.get_file('aclImdb_v1.tar.gz', url,
                                  untar=True, cache_dir='.',
                                  cache_subdir='')

dataset_dir = os.path.join(os.path.dirname(dataset), 'aclImdb')

train_dir = os.path.join(dataset_dir, 'train')

# remove unused folders to make it easier to load the data
remove_dir = os.path.join(train_dir, 'unsup')
shutil.rmtree(remove_dir)
Downloading data from https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz
84131840/84125825 [==============================] - 7s 0us/step
84140032/84125825 [==============================] - 7s 0us/step

Em seguida, você irá utilizar o text_dataset_from_directory utilitário para criar um rotulado tf.data.Dataset .

O conjunto de dados IMDB já foi dividido em treinamento e teste, mas carece de um conjunto de validação. Vamos criar um conjunto de validação usando uma 80:20 divisão dos dados de treinamento usando a validation_split argumento abaixo.

AUTOTUNE = tf.data.AUTOTUNE
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)

class_names = raw_train_ds.class_names
train_ds = raw_train_ds.cache().prefetch(buffer_size=AUTOTUNE)

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

val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)

test_ds = tf.keras.utils.text_dataset_from_directory(
    'aclImdb/test',
    batch_size=batch_size)

test_ds = test_ds.cache().prefetch(buffer_size=AUTOTUNE)
Found 25000 files belonging to 2 classes.
Using 20000 files for training.
Found 25000 files belonging to 2 classes.
Using 5000 files for validation.
Found 25000 files belonging to 2 classes.

Vamos dar uma olhada em alguns comentários.

for text_batch, label_batch in train_ds.take(1):
  for i in range(3):
    print(f'Review: {text_batch.numpy()[i]}')
    label = label_batch.numpy()[i]
    print(f'Label : {label} ({class_names[label]})')
Review: b'"Pandemonium" is a horror movie spoof that comes off more stupid than funny. Believe me when I tell you, I love comedies. Especially comedy spoofs. "Airplane", "The Naked Gun" trilogy, "Blazing Saddles", "High Anxiety", and "Spaceballs" are some of my favorite comedies that spoof a particular genre. "Pandemonium" is not up there with those films. Most of the scenes in this movie had me sitting there in stunned silence because the movie wasn\'t all that funny. There are a few laughs in the film, but when you watch a comedy, you expect to laugh a lot more than a few times and that\'s all this film has going for it. Geez, "Scream" had more laughs than this film and that was more of a horror film. How bizarre is that?<br /><br />*1/2 (out of four)'
Label : 0 (neg)
Review: b"David Mamet is a very interesting and a very un-equal director. His first movie 'House of Games' was the one I liked best, and it set a series of films with characters whose perspective of life changes as they get into complicated situations, and so does the perspective of the viewer.<br /><br />So is 'Homicide' which from the title tries to set the mind of the viewer to the usual crime drama. The principal characters are two cops, one Jewish and one Irish who deal with a racially charged area. The murder of an old Jewish shop owner who proves to be an ancient veteran of the Israeli Independence war triggers the Jewish identity in the mind and heart of the Jewish detective.<br /><br />This is were the flaws of the film are the more obvious. The process of awakening is theatrical and hard to believe, the group of Jewish militants is operatic, and the way the detective eventually walks to the final violent confrontation is pathetic. The end of the film itself is Mamet-like smart, but disappoints from a human emotional perspective.<br /><br />Joe Mantegna and William Macy give strong performances, but the flaws of the story are too evident to be easily compensated."
Label : 0 (neg)
Review: b'Great documentary about the lives of NY firefighters during the worst terrorist attack of all time.. That reason alone is why this should be a must see collectors item.. What shocked me was not only the attacks, but the"High Fat Diet" and physical appearance of some of these firefighters. I think a lot of Doctors would agree with me that,in the physical shape they were in, some of these firefighters would NOT of made it to the 79th floor carrying over 60 lbs of gear. Having said that i now have a greater respect for firefighters and i realize becoming a firefighter is a life altering job. The French have a history of making great documentary\'s and that is what this is, a Great Documentary.....'
Label : 1 (pos)
2021-12-01 12:17:32.795514: W tensorflow/core/kernels/data/cache_dataset_ops.cc:768] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.

Carregando modelos do TensorFlow Hub

Aqui você pode escolher qual modelo BERT irá carregar do TensorFlow Hub e fazer o ajuste fino. Existem vários modelos BERT disponíveis.

  • Bert-base , uncased e mais sete modelos com pesos treinados liberados pelos autores BERT originais.
  • Pequenas BERTs ter a mesma arquitetura geral, mas em menor número e / ou blocos de transformadores menores, o que permite explorar melhor combinação entre velocidade, tamanho e qualidade.
  • ALBERT : quatro tamanhos diferentes de "A Lite BERT" que reduz o tamanho do modelo (mas não o tempo de computação) através da partilha de parâmetros entre as camadas.
  • BERT Especialistas : oito modelos que todos têm a arquitetura Bert-base, mas oferecem uma escolha entre domínios pré-formação diferentes, para alinhar mais estreitamente com a tarefa alvo.
  • Electra tem a mesma arquitetura como Bert (em três tamanhos diferentes), mas fica pré-treinado como um discriminador em um set-up que se assemelha a um Adversarial de rede Generative (GAN).
  • BERT com a falar-Cabeças atenção e fechado Gelu [ de base , grande ] tem duas melhorias para o núcleo da arquitetura Transformer.

A documentação do modelo no TensorFlow Hub tem mais detalhes e referências à literatura de pesquisa. Siga os links acima, ou clique no tfhub.dev URL impresso após a próxima execução celular.

A sugestão é começar com um Small BERT (com menos parâmetros), pois eles são mais rápidos de fazer o ajuste fino. Se você gosta de um modelo pequeno, mas com maior precisão, ALBERT pode ser sua próxima opção. Se você deseja uma precisão ainda melhor, escolha um dos tamanhos clássicos de BERT ou seus refinamentos recentes como Electra, Talking Heads ou um BERT Expert.

Além dos modelos disponíveis abaixo, existem várias versões dos modelos que são maiores e podem render ainda melhor precisão, mas eles são grandes demais para estar em uma única GPU afinado. Você será capaz de fazer isso nas tarefas GLUE resolver usando BERT em um colab TPU .

Você verá no código abaixo que trocar a URL tfhub.dev é suficiente para tentar qualquer um desses modelos, porque todas as diferenças entre eles estão encapsuladas nos SavedModels do TF Hub.

Escolha um modelo de BERT para ajustar

BERT model selected           : https://tfhub.dev/tensorflow/small_bert/bert_en_uncased_L-4_H-512_A-8/1
Preprocess model auto-selected: https://tfhub.dev/tensorflow/bert_en_uncased_preprocess/3

O modelo de pré-processamento

As entradas de texto precisam ser transformadas em ids de token numéricos e organizadas em vários tensores antes de serem inseridas no BERT. O TensorFlow Hub fornece um modelo de pré-processamento correspondente para cada um dos modelos BERT discutidos acima, que implementa essa transformação usando operações TF da biblioteca TF.text. Não é necessário executar o código Python puro fora do seu modelo do TensorFlow para pré-processar o texto.

O modelo de pré-processamento deve ser aquele referenciado na documentação do modelo BERT, que pode ser lida na URL impressa acima. Para modelos BERT no menu suspenso acima, o modelo de pré-processamento é selecionado automaticamente.

bert_preprocess_model = hub.KerasLayer(tfhub_handle_preprocess)

Vamos experimentar o modelo de pré-processamento em algum texto e ver o resultado:

text_test = ['this is such an amazing movie!']
text_preprocessed = bert_preprocess_model(text_test)

print(f'Keys       : {list(text_preprocessed.keys())}')
print(f'Shape      : {text_preprocessed["input_word_ids"].shape}')
print(f'Word Ids   : {text_preprocessed["input_word_ids"][0, :12]}')
print(f'Input Mask : {text_preprocessed["input_mask"][0, :12]}')
print(f'Type Ids   : {text_preprocessed["input_type_ids"][0, :12]}')
Keys       : ['input_word_ids', 'input_mask', 'input_type_ids']
Shape      : (1, 128)
Word Ids   : [ 101 2023 2003 2107 2019 6429 3185  999  102    0    0    0]
Input Mask : [1 1 1 1 1 1 1 1 1 0 0 0]
Type Ids   : [0 0 0 0 0 0 0 0 0 0 0 0]

Como você pode ver, agora você tem as 3 saídas do pré-processamento que um modelo BERT usaria ( input_words_id , input_mask e input_type_ids ).

Alguns outros pontos importantes:

  • A entrada é truncada para 128 tokens. O número de fichas pode ser personalizado, e você pode ver mais detalhes sobre as tarefas Resolva adesiva, utilizando BERT em um colab TPU .
  • Os input_type_ids só tem um valor (0) porque esta é uma entrada única frase. Para uma entrada de várias frases, teria um número para cada entrada.

Como esse pré-processador de texto é um modelo do TensorFlow, ele pode ser incluído diretamente no seu modelo.

Usando o modelo BERT

Antes de colocar o BERT em seu próprio modelo, vamos dar uma olhada em suas saídas. Você o carregará do TF Hub e verá os valores retornados.

bert_model = hub.KerasLayer(tfhub_handle_encoder)
bert_results = bert_model(text_preprocessed)

print(f'Loaded BERT: {tfhub_handle_encoder}')
print(f'Pooled Outputs Shape:{bert_results["pooled_output"].shape}')
print(f'Pooled Outputs Values:{bert_results["pooled_output"][0, :12]}')
print(f'Sequence Outputs Shape:{bert_results["sequence_output"].shape}')
print(f'Sequence Outputs Values:{bert_results["sequence_output"][0, :12]}')
Loaded BERT: https://tfhub.dev/tensorflow/small_bert/bert_en_uncased_L-4_H-512_A-8/1
Pooled Outputs Shape:(1, 512)
Pooled Outputs Values:[ 0.76262873  0.99280983 -0.1861186   0.36673835  0.15233682  0.65504444
  0.9681154  -0.9486272   0.00216158 -0.9877732   0.0684272  -0.9763061 ]
Sequence Outputs Shape:(1, 128, 512)
Sequence Outputs Values:[[-0.28946388  0.3432126   0.33231565 ...  0.21300787  0.7102078
  -0.05771166]
 [-0.28742015  0.31981024 -0.2301858  ...  0.58455074 -0.21329722
   0.7269209 ]
 [-0.66157013  0.6887685  -0.87432927 ...  0.10877253 -0.26173282
   0.47855264]
 ...
 [-0.2256118  -0.28925604 -0.07064401 ...  0.4756601   0.8327715
   0.40025353]
 [-0.29824278 -0.27473143 -0.05450511 ...  0.48849759  1.0955356
   0.18163344]
 [-0.44378197  0.00930723  0.07223766 ...  0.1729009   1.1833246
   0.07897988]]

Os modelos BERT retornar um mapa com 3 chaves importantes: pooled_output , sequence_output , encoder_outputs :

  • pooled_output representa cada sequência de entrada como um todo. A forma é [batch_size, H] . Você pode pensar nisso como uma incorporação de toda a crítica do filme.
  • sequence_output representa cada símbolo de input no contexto. A forma é [batch_size, seq_length, H] . Você pode pensar nisso como uma incorporação contextual para cada token na crítica do filme.
  • encoder_outputs são as activações intermediários dos L blocos transformador. outputs["encoder_outputs"][i] é um tensor de forma [batch_size, seq_length, 1024] com as saídas do bloco de ordem i do transformador, para 0 <= i < L . O último valor da lista é igual a sequence_output .

Para o ajuste fino você estiver indo para usar o pooled_output matriz.

Defina o seu modelo

Você criará um modelo bem ajustado muito simples, com o modelo de pré-processamento, o modelo BERT selecionado, uma camada Densa e uma camada de Dropout.

def build_classifier_model():
  text_input = tf.keras.layers.Input(shape=(), dtype=tf.string, name='text')
  preprocessing_layer = hub.KerasLayer(tfhub_handle_preprocess, name='preprocessing')
  encoder_inputs = preprocessing_layer(text_input)
  encoder = hub.KerasLayer(tfhub_handle_encoder, trainable=True, name='BERT_encoder')
  outputs = encoder(encoder_inputs)
  net = outputs['pooled_output']
  net = tf.keras.layers.Dropout(0.1)(net)
  net = tf.keras.layers.Dense(1, activation=None, name='classifier')(net)
  return tf.keras.Model(text_input, net)

Vamos verificar se o modelo é executado com a saída do modelo de pré-processamento.

classifier_model = build_classifier_model()
bert_raw_result = classifier_model(tf.constant(text_test))
print(tf.sigmoid(bert_raw_result))
tf.Tensor([[0.6749899]], shape=(1, 1), dtype=float32)

A saída não tem sentido, é claro, porque o modelo ainda não foi treinado.

Vamos dar uma olhada na estrutura do modelo.

tf.keras.utils.plot_model(classifier_model)

png

Treinamento de modelo

Agora você tem todas as peças para treinar um modelo, incluindo o módulo de pré-processamento, codificador BERT, dados e classificador.

Função de perda

Uma vez que este é um problema de classificação binária e o modelo gera uma probabilidade (uma camada única unidade), você vai usar losses.BinaryCrossentropy função de perda.

loss = tf.keras.losses.BinaryCrossentropy(from_logits=True)
metrics = tf.metrics.BinaryAccuracy()

Otimizador

Para o ajuste fino, vamos usar o mesmo otimizador com o qual o BERT foi originalmente treinado: o "Adaptive Moments" (Adam). Este optimizador minimiza a perda de predição e faz regularização por decaimento peso (sem uso de momentos), que também é conhecido como AdamW .

Para a taxa de aprendizagem ( init_lr ), você vai usar o mesmo esquema Bert-formação pré: Deterioração linear de uma taxa inicial de aprendizado teórico, prefixado com uma fase de aquecimento linear durante os primeiros 10% de treinar os passos ( num_warmup_steps ). De acordo com o artigo de BERT, a taxa de aprendizado inicial é menor para o ajuste fino (melhor de 5e-5, 3e-5, 2e-5).

epochs = 5
steps_per_epoch = tf.data.experimental.cardinality(train_ds).numpy()
num_train_steps = steps_per_epoch * epochs
num_warmup_steps = int(0.1*num_train_steps)

init_lr = 3e-5
optimizer = optimization.create_optimizer(init_lr=init_lr,
                                          num_train_steps=num_train_steps,
                                          num_warmup_steps=num_warmup_steps,
                                          optimizer_type='adamw')

Carregando o modelo de BERT e treinamento

Usando o classifier_model criado anteriormente, você pode compilar o modelo com a perda, métrica e otimizador.

classifier_model.compile(optimizer=optimizer,
                         loss=loss,
                         metrics=metrics)
print(f'Training model with {tfhub_handle_encoder}')
history = classifier_model.fit(x=train_ds,
                               validation_data=val_ds,
                               epochs=epochs)
Training model with https://tfhub.dev/tensorflow/small_bert/bert_en_uncased_L-4_H-512_A-8/1
Epoch 1/5
625/625 [==============================] - 91s 138ms/step - loss: 0.4776 - binary_accuracy: 0.7513 - val_loss: 0.3791 - val_binary_accuracy: 0.8380
Epoch 2/5
625/625 [==============================] - 85s 136ms/step - loss: 0.3266 - binary_accuracy: 0.8547 - val_loss: 0.3659 - val_binary_accuracy: 0.8486
Epoch 3/5
625/625 [==============================] - 86s 138ms/step - loss: 0.2521 - binary_accuracy: 0.8928 - val_loss: 0.3975 - val_binary_accuracy: 0.8518
Epoch 4/5
625/625 [==============================] - 86s 137ms/step - loss: 0.1910 - binary_accuracy: 0.9269 - val_loss: 0.4180 - val_binary_accuracy: 0.8522
Epoch 5/5
625/625 [==============================] - 86s 137ms/step - loss: 0.1509 - binary_accuracy: 0.9433 - val_loss: 0.4641 - val_binary_accuracy: 0.8522

Avalie o modelo

Vamos ver o desempenho do modelo. Dois valores serão retornados. Perda (um número que representa o erro, valores mais baixos são melhores) e precisão.

loss, accuracy = classifier_model.evaluate(test_ds)

print(f'Loss: {loss}')
print(f'Accuracy: {accuracy}')
782/782 [==============================] - 61s 78ms/step - loss: 0.4495 - binary_accuracy: 0.8554
Loss: 0.4494614601135254
Accuracy: 0.8553599715232849

Trace a precisão e a perda ao longo do tempo

Com base na History objeto retornado por model.fit() . Você pode representar graficamente a perda de treinamento e validação para comparação, bem como a precisão do treinamento e validação:

history_dict = history.history
print(history_dict.keys())

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)
fig = plt.figure(figsize=(10, 6))
fig.tight_layout()

plt.subplot(2, 1, 1)
# r is for "solid red line"
plt.plot(epochs, loss, 'r', 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.subplot(2, 1, 2)
plt.plot(epochs, acc, 'r', 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')
dict_keys(['loss', 'binary_accuracy', 'val_loss', 'val_binary_accuracy'])
<matplotlib.legend.Legend at 0x7fee7cdb4450>

png

Neste gráfico, as linhas vermelhas representam a perda e precisão do treinamento, e as linhas azuis são a perda e precisão da validação.

Exportar para inferência

Agora, basta salvar seu modelo ajustado para uso posterior.

dataset_name = 'imdb'
saved_model_path = './{}_bert'.format(dataset_name.replace('/', '_'))

classifier_model.save(saved_model_path, include_optimizer=False)
2021-12-01 12:26:06.207608: W tensorflow/python/util/util.cc:368] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
WARNING:absl:Found untraced functions such as restored_function_body, restored_function_body, restored_function_body, restored_function_body, restored_function_body while saving (showing 5 of 310). These functions will not be directly callable after loading.

Vamos recarregar o modelo, para que você possa experimentá-lo lado a lado com o modelo que ainda está na memória.

reloaded_model = tf.saved_model.load(saved_model_path)

Aqui você pode testar seu modelo em qualquer frase que desejar, basta adicionar à variável de exemplos abaixo.

def print_my_examples(inputs, results):
  result_for_printing = \
    [f'input: {inputs[i]:<30} : score: {results[i][0]:.6f}'
                         for i in range(len(inputs))]
  print(*result_for_printing, sep='\n')
  print()


examples = [
    'this is such an amazing movie!',  # this is the same sentence tried earlier
    'The movie was great!',
    'The movie was meh.',
    'The movie was okish.',
    'The movie was terrible...'
]

reloaded_results = tf.sigmoid(reloaded_model(tf.constant(examples)))
original_results = tf.sigmoid(classifier_model(tf.constant(examples)))

print('Results from the saved model:')
print_my_examples(examples, reloaded_results)
print('Results from the model in memory:')
print_my_examples(examples, original_results)
Results from the saved model:
input: this is such an amazing movie! : score: 0.999521
input: The movie was great!           : score: 0.997015
input: The movie was meh.             : score: 0.988535
input: The movie was okish.           : score: 0.079138
input: The movie was terrible...      : score: 0.001622

Results from the model in memory:
input: this is such an amazing movie! : score: 0.999521
input: The movie was great!           : score: 0.997015
input: The movie was meh.             : score: 0.988535
input: The movie was okish.           : score: 0.079138
input: The movie was terrible...      : score: 0.001622

Se você quiser usar o seu modelo na TF Dose , lembre-se que ele irá chamar o seu SavedModel através de uma das suas assinaturas nomeados. Em Python, você pode testá-los da seguinte maneira:

serving_results = reloaded_model \
            .signatures['serving_default'](tf.constant(examples))

serving_results = tf.sigmoid(serving_results['classifier'])

print_my_examples(examples, serving_results)
input: this is such an amazing movie! : score: 0.999521
input: The movie was great!           : score: 0.997015
input: The movie was meh.             : score: 0.988535
input: The movie was okish.           : score: 0.079138
input: The movie was terrible...      : score: 0.001622

Próximos passos

Como próximo passo, você pode tentar resolver tarefas adesiva, utilizando BERT em um tutorial TPU , que é executado em um TPU e mostra-lhe como trabalhar com múltiplas entradas.