Incorporações de palavras

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

Este tutorial contém uma introdução à incorporação de palavras. Você treinará suas próprias incorporações de palavras usando um modelo Keras simples para uma tarefa de classificação de sentimentos e, em seguida, as visualizará no projetor de incorporação (mostrado na imagem abaixo).

Captura de tela do projetor de incorporação

Representando texto como números

Os modelos de aprendizado de máquina usam vetores (matrizes de números) como entrada. Ao trabalhar com texto, a primeira coisa que você deve fazer é criar uma estratégia para converter strings em números (ou "vetorizar" o texto) antes de alimentá-lo ao modelo. Nesta seção, você verá três estratégias para fazer isso.

Codificações One-Hot

Como primeira ideia, você pode codificar "one-hot" cada palavra em seu vocabulário. Considere a frase "O gato sentou-se no tapete". O vocabulário (ou palavras únicas) nesta frase é (cat, mat, on, sat, the). Para representar cada palavra, você criará um vetor zero com comprimento igual ao vocabulário e, em seguida, colocará um no índice que corresponde à palavra. Essa abordagem é mostrada no diagrama a seguir.

Diagrama de codificações one-hot

Para criar um vetor que contém a codificação da frase, você pode concatenar os vetores one-hot para cada palavra.

Codifique cada palavra com um número único

Uma segunda abordagem que você pode tentar é codificar cada palavra usando um número único. Continuando o exemplo acima, você pode atribuir 1 a "cat", 2 a "mat" e assim por diante. Você poderia então codificar a frase "O gato sentou-se no tapete" como um vetor denso como [5, 1, 4, 3, 5, 2]. Essa abordagem é eficiente. Em vez de um vetor esparso, agora você tem um vetor denso (onde todos os elementos estão cheios).

No entanto, existem duas desvantagens nessa abordagem:

  • A codificação inteira é arbitrária (não captura nenhuma relação entre palavras).

  • Uma codificação inteira pode ser um desafio para um modelo interpretar. Um classificador linear, por exemplo, aprende um único peso para cada recurso. Como não há relação entre a semelhança de duas palavras e a semelhança de suas codificações, essa combinação de peso de recurso não é significativa.

Incorporações de palavras

A incorporação de palavras nos dá uma maneira de usar uma representação eficiente e densa na qual palavras semelhantes têm uma codificação semelhante. É importante ressaltar que você não precisa especificar essa codificação manualmente. Uma incorporação é um vetor denso de valores de ponto flutuante (o comprimento do vetor é um parâmetro que você especifica). Em vez de especificar os valores para a incorporação manualmente, eles são parâmetros treináveis ​​(pesos aprendidos pelo modelo durante o treinamento, da mesma forma que um modelo aprende pesos para uma camada densa). É comum ver incorporações de palavras em 8 dimensões (para conjuntos de dados pequenos), até 1024 dimensões ao trabalhar com conjuntos de dados grandes. Uma incorporação dimensional mais alta pode capturar relacionamentos refinados entre palavras, mas precisa de mais dados para aprender.

Diagrama de uma incorporação

Acima está um diagrama para uma incorporação de palavras. Cada palavra é representada como um vetor de 4 dimensões de valores de ponto flutuante. Outra maneira de pensar em uma incorporação é como "tabela de consulta". Depois que esses pesos forem aprendidos, você pode codificar cada palavra procurando o vetor denso ao qual ela corresponde na tabela.

Configurar

import io
import os
import re
import shutil
import string
import tensorflow as tf

from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Embedding, GlobalAveragePooling1D
from tensorflow.keras.layers import TextVectorization

Baixe o conjunto de dados do IMDb

Você usará o conjunto de dados de revisão de filme grande por meio do tutorial. Você treinará um modelo de classificador de sentimento nesse conjunto de dados e, no processo, aprenderá as incorporações do zero. Para ler mais sobre como carregar um conjunto de dados do zero, consulte o tutorial Carregando texto .

Baixe o conjunto de dados usando o utilitário de arquivo Keras e dê uma olhada nos diretórios.

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')
os.listdir(dataset_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
['test', 'imdb.vocab', 'imdbEr.txt', 'train', 'README']

Dê uma olhada no diretório train/ . Tem pastas pos e neg com resenhas de filmes rotuladas como positivas e negativas, respectivamente. Você usará revisões das pastas pos e neg para treinar um modelo de classificação binária.

train_dir = os.path.join(dataset_dir, 'train')
os.listdir(train_dir)
['urls_pos.txt',
 'urls_unsup.txt',
 'urls_neg.txt',
 'pos',
 'unsup',
 'unsupBow.feat',
 'neg',
 'labeledBow.feat']

O diretório train também possui pastas adicionais que devem ser removidas antes de criar o conjunto de dados de treinamento.

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

Em seguida, crie um tf.data.Dataset usando tf.keras.utils.text_dataset_from_directory . Você pode ler mais sobre como usar este utilitário neste tutorial de classificação de texto .

Use o diretório train para criar conjuntos de dados de trem e validação com uma divisão de 20% para validação.

batch_size = 1024
seed = 123
train_ds = tf.keras.utils.text_dataset_from_directory(
    'aclImdb/train', batch_size=batch_size, validation_split=0.2,
    subset='training', seed=seed)
val_ds = tf.keras.utils.text_dataset_from_directory(
    'aclImdb/train', batch_size=batch_size, validation_split=0.2,
    subset='validation', seed=seed)
Found 25000 files belonging to 2 classes.
Using 20000 files for training.
Found 25000 files belonging to 2 classes.
Using 5000 files for validation.

Dê uma olhada em algumas críticas de filmes e seus rótulos (1: positive, 0: negative) do conjunto de dados do trem.

for text_batch, label_batch in train_ds.take(1):
  for i in range(5):
    print(label_batch[i].numpy(), text_batch.numpy()[i])
0 b"Oh My God! Please, for the love of all that is holy, Do Not Watch This Movie! It it 82 minutes of my life I will never get back. Sure, I could have stopped watching half way through. But I thought it might get better. It Didn't. Anyone who actually enjoyed this movie is one seriously sick and twisted individual. No wonder us Australians/New Zealanders have a terrible reputation when it comes to making movies. Everything about this movie is horrible, from the acting to the editing. I don't even normally write reviews on here, but in this case I'll make an exception. I only wish someone had of warned me before I hired this catastrophe"
1 b'This movie is SOOOO funny!!! The acting is WONDERFUL, the Ramones are sexy, the jokes are subtle, and the plot is just what every high schooler dreams of doing to his/her school. I absolutely loved the soundtrack as well as the carefully placed cynicism. If you like monty python, You will love this film. This movie is a tad bit "grease"esk (without all the annoying songs). The songs that are sung are likable; you might even find yourself singing these songs once the movie is through. This musical ranks number two in musicals to me (second next to the blues brothers). But please, do not think of it as a musical per say; seeing as how the songs are so likable, it is hard to tell a carefully choreographed scene is taking place. I think of this movie as more of a comedy with undertones of romance. You will be reminded of what it was like to be a rebellious teenager; needless to say, you will be reminiscing of your old high school days after seeing this film. Highly recommended for both the family (since it is a very youthful but also for adults since there are many jokes that are funnier with age and experience.'
0 b"Alex D. Linz replaces Macaulay Culkin as the central figure in the third movie in the Home Alone empire. Four industrial spies acquire a missile guidance system computer chip and smuggle it through an airport inside a remote controlled toy car. Because of baggage confusion, grouchy Mrs. Hess (Marian Seldes) gets the car. She gives it to her neighbor, Alex (Linz), just before the spies turn up. The spies rent a house in order to burglarize each house in the neighborhood until they locate the car. Home alone with the chicken pox, Alex calls 911 each time he spots a theft in progress, but the spies always manage to elude the police while Alex is accused of making prank calls. The spies finally turn their attentions toward Alex, unaware that he has rigged devices to cleverly booby-trap his entire house. Home Alone 3 wasn't horrible, but probably shouldn't have been made, you can't just replace Macauley Culkin, Joe Pesci, or Daniel Stern. Home Alone 3 had some funny parts, but I don't like when characters are changed in a movie series, view at own risk."
0 b"There's a good movie lurking here, but this isn't it. The basic idea is good: to explore the moral issues that would face a group of young survivors of the apocalypse. But the logic is so muddled that it's impossible to get involved.<br /><br />For example, our four heroes are (understandably) paranoid about catching the mysterious airborne contagion that's wiped out virtually all of mankind. Yet they wear surgical masks some times, not others. Some times they're fanatical about wiping down with bleach any area touched by an infected person. Other times, they seem completely unconcerned.<br /><br />Worse, after apparently surviving some weeks or months in this new kill-or-be-killed world, these people constantly behave like total newbs. They don't bother accumulating proper equipment, or food. They're forever running out of fuel in the middle of nowhere. They don't take elementary precautions when meeting strangers. And after wading through the rotting corpses of the entire human race, they're as squeamish as sheltered debutantes. You have to constantly wonder how they could have survived this long... and even if they did, why anyone would want to make a movie about them.<br /><br />So when these dweebs stop to agonize over the moral dimensions of their actions, it's impossible to take their soul-searching seriously. Their actions would first have to make some kind of minimal sense.<br /><br />On top of all this, we must contend with the dubious acting abilities of Chris Pine. His portrayal of an arrogant young James T Kirk might have seemed shrewd, when viewed in isolation. But in Carriers he plays on exactly that same note: arrogant and boneheaded. It's impossible not to suspect that this constitutes his entire dramatic range.<br /><br />On the positive side, the film *looks* excellent. It's got an over-sharp, saturated look that really suits the southwestern US locale. But that can't save the truly feeble writing nor the paper-thin (and annoying) characters. Even if you're a fan of the end-of-the-world genre, you should save yourself the agony of watching Carriers."
0 b'I saw this movie at an actual movie theater (probably the \\(2.00 one) with my cousin and uncle. We were around 11 and 12, I guess, and really into scary movies. I remember being so excited to see it because my cool uncle let us pick the movie (and we probably never got to do that again!) and sooo disappointed afterwards!! Just boring and not scary. The only redeeming thing I can remember was Corky Pigeon from Silver Spoons, and that wasn\'t all that great, just someone I recognized. I\'ve seen bad movies before and this one has always stuck out in my mind as the worst. This was from what I can recall, one of the most boring, non-scary, waste of our collective \\)6, and a waste of film. I have read some of the reviews that say it is worth a watch and I say, "Too each his own", but I wouldn\'t even bother. Not even so bad it\'s good.'

Configurar o conjunto de dados para desempenho

Esses são dois métodos importantes que você deve usar ao carregar dados para garantir que a E/S não fique bloqueada.

.cache() mantém os dados na memória depois de carregados no disco. Isso garantirá que o conjunto de dados não se torne um gargalo ao treinar seu modelo. Se seu conjunto de dados for muito grande para caber na memória, você também poderá usar esse método para criar um cache em disco de alto desempenho, que é mais eficiente para ler do que muitos arquivos pequenos.

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

Você pode aprender mais sobre os dois métodos, bem como sobre como armazenar dados em cache no disco no guia de desempenho de dados .

AUTOTUNE = tf.data.AUTOTUNE

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

Usando a camada de incorporação

Keras facilita o uso de incorporações de palavras. Dê uma olhada na camada Embedding .

A camada Embedding pode ser entendida como uma tabela de pesquisa que mapeia desde índices inteiros (que representam palavras específicas) até vetores densos (seus embeddings). A dimensionalidade (ou largura) da incorporação é um parâmetro que você pode experimentar para ver o que funciona bem para o seu problema, da mesma forma que você experimentaria com o número de neurônios em uma camada Densa.

# Embed a 1,000 word vocabulary into 5 dimensions.
embedding_layer = tf.keras.layers.Embedding(1000, 5)

Quando você cria uma camada de incorporação, os pesos para a incorporação são inicializados aleatoriamente (assim como qualquer outra camada). Durante o treinamento, eles são ajustados gradualmente via retropropagação. Uma vez treinadas, as incorporações de palavras aprendidas codificarão aproximadamente as semelhanças entre as palavras (como elas foram aprendidas para o problema específico em que seu modelo é treinado).

Se você passar um inteiro para uma camada de incorporação, o resultado substituirá cada inteiro pelo vetor da tabela de incorporação:

result = embedding_layer(tf.constant([1, 2, 3]))
result.numpy()
array([[ 0.01318491, -0.02219239,  0.024673  , -0.03208025,  0.02297195],
       [-0.00726584,  0.03731754, -0.01209557, -0.03887399, -0.02407478],
       [ 0.04477594,  0.04504738, -0.02220147, -0.03642888, -0.04688282]],
      dtype=float32)

Para problemas de texto ou sequência, a camada Embedding usa um tensor 2D de inteiros, de forma (samples, sequence_length) , onde cada entrada é uma sequência de inteiros. Ele pode incorporar sequências de comprimentos variáveis. Você pode alimentar a camada de incorporação acima de lotes com formas (32, 10) (lote de 32 sequências de comprimento 10) ou (64, 15) (lote de 64 sequências de comprimento 15).

O tensor retornado tem um eixo a mais que a entrada, os vetores de incorporação são alinhados ao longo do novo último eixo. Passe um lote de entrada (2, 3) e a saída é (2, 3, N)

result = embedding_layer(tf.constant([[0, 1, 2], [3, 4, 5]]))
result.shape
TensorShape([2, 3, 5])

Quando recebe um lote de sequências como entrada, uma camada de incorporação retorna um tensor de ponto flutuante 3D, de forma (samples, sequence_length, embedding_dimensionality) . Para converter esta sequência de comprimento variável para uma representação fixa, há uma variedade de abordagens padrão. Você pode usar uma camada RNN, Attention ou pooling antes de passá-la para uma camada Dense. Este tutorial usa pooling porque é o mais simples. A classificação de texto com um tutorial RNN é um bom próximo passo.

Pré-processamento de texto

Em seguida, defina as etapas de pré-processamento do conjunto de dados necessárias para seu modelo de classificação de sentimento. Inicialize uma camada TextVectorization com os parâmetros desejados para vetorizar resenhas de filmes. Você pode aprender mais sobre como usar essa camada no tutorial Classificação de texto .

# Create a custom standardization function to strip HTML break tags '<br />'.
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), '')


# Vocabulary size and number of words in a sequence.
vocab_size = 10000
sequence_length = 100

# Use the text vectorization layer to normalize, split, and map strings to
# integers. Note that the layer uses the custom standardization defined above.
# Set maximum_sequence length as all samples are not of the same length.
vectorize_layer = TextVectorization(
    standardize=custom_standardization,
    max_tokens=vocab_size,
    output_mode='int',
    output_sequence_length=sequence_length)

# Make a text-only dataset (no labels) and call adapt to build the vocabulary.
text_ds = train_ds.map(lambda x, y: x)
vectorize_layer.adapt(text_ds)

Criar um modelo de classificação

Use a API Keras Sequential para definir o modelo de classificação de sentimento. Neste caso é um modelo de estilo "saco contínuo de palavras".

  • A camada TextVectorization transforma strings em índices de vocabulário. Você já inicializou vectorize_layer como uma camada TextVectorization e construiu seu vocabulário chamando adapt em text_ds . Agora vectorize_layer pode ser usado como a primeira camada do seu modelo de classificação de ponta a ponta, alimentando strings transformadas na camada Embedding.
  • A camada Embedding pega o vocabulário codificado por inteiro e procura o vetor de embedding para cada índice de palavras. Esses vetores são aprendidos à medida que o modelo é treinado. Os vetores adicionam uma dimensão à matriz de saída. As dimensões resultantes são: (batch, sequence, embedding) .

  • A camada GlobalAveragePooling1D retorna um vetor de saída de comprimento fixo para cada exemplo calculando a média sobre a dimensão da sequência. Isso permite que o modelo manipule entradas de comprimento variável, da maneira mais simples possível.

  • O vetor de saída de comprimento fixo é canalizado através de uma camada totalmente conectada ( Dense ) com 16 unidades ocultas.

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

embedding_dim=16

model = Sequential([
  vectorize_layer,
  Embedding(vocab_size, embedding_dim, name="embedding"),
  GlobalAveragePooling1D(),
  Dense(16, activation='relu'),
  Dense(1)
])

Compilar e treinar o modelo

Você usará o TensorBoard para visualizar métricas, incluindo perda e precisão. Crie um tf.keras.callbacks.TensorBoard .

tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir="logs")

Compile e treine o modelo usando o otimizador Adam e a perda BinaryCrossentropy .

model.compile(optimizer='adam',
              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=['accuracy'])
model.fit(
    train_ds,
    validation_data=val_ds,
    epochs=15,
    callbacks=[tensorboard_callback])
Epoch 1/15
20/20 [==============================] - 2s 71ms/step - loss: 0.6910 - accuracy: 0.5028 - val_loss: 0.6878 - val_accuracy: 0.4886
Epoch 2/15
20/20 [==============================] - 1s 57ms/step - loss: 0.6838 - accuracy: 0.5028 - val_loss: 0.6791 - val_accuracy: 0.4886
Epoch 3/15
20/20 [==============================] - 1s 58ms/step - loss: 0.6726 - accuracy: 0.5028 - val_loss: 0.6661 - val_accuracy: 0.4886
Epoch 4/15
20/20 [==============================] - 1s 58ms/step - loss: 0.6563 - accuracy: 0.5028 - val_loss: 0.6481 - val_accuracy: 0.4886
Epoch 5/15
20/20 [==============================] - 1s 58ms/step - loss: 0.6343 - accuracy: 0.5061 - val_loss: 0.6251 - val_accuracy: 0.5066
Epoch 6/15
20/20 [==============================] - 1s 58ms/step - loss: 0.6068 - accuracy: 0.5634 - val_loss: 0.5982 - val_accuracy: 0.5762
Epoch 7/15
20/20 [==============================] - 1s 58ms/step - loss: 0.5752 - accuracy: 0.6405 - val_loss: 0.5690 - val_accuracy: 0.6386
Epoch 8/15
20/20 [==============================] - 1s 58ms/step - loss: 0.5412 - accuracy: 0.7036 - val_loss: 0.5390 - val_accuracy: 0.6850
Epoch 9/15
20/20 [==============================] - 1s 59ms/step - loss: 0.5064 - accuracy: 0.7479 - val_loss: 0.5106 - val_accuracy: 0.7222
Epoch 10/15
20/20 [==============================] - 1s 59ms/step - loss: 0.4734 - accuracy: 0.7774 - val_loss: 0.4855 - val_accuracy: 0.7430
Epoch 11/15
20/20 [==============================] - 1s 59ms/step - loss: 0.4432 - accuracy: 0.7971 - val_loss: 0.4636 - val_accuracy: 0.7570
Epoch 12/15
20/20 [==============================] - 1s 58ms/step - loss: 0.4161 - accuracy: 0.8155 - val_loss: 0.4453 - val_accuracy: 0.7674
Epoch 13/15
20/20 [==============================] - 1s 59ms/step - loss: 0.3921 - accuracy: 0.8304 - val_loss: 0.4303 - val_accuracy: 0.7780
Epoch 14/15
20/20 [==============================] - 1s 61ms/step - loss: 0.3711 - accuracy: 0.8398 - val_loss: 0.4181 - val_accuracy: 0.7884
Epoch 15/15
20/20 [==============================] - 1s 58ms/step - loss: 0.3524 - accuracy: 0.8493 - val_loss: 0.4082 - val_accuracy: 0.7948
<keras.callbacks.History at 0x7fca579745d0>

Com esta abordagem, o modelo atinge uma precisão de validação em torno de 78% (observe que o modelo está sobreajustado, pois a precisão do treinamento é maior).

Você pode examinar o resumo do modelo para saber mais sobre cada camada do modelo.

model.summary()
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 text_vectorization (TextVec  (None, 100)              0         
 torization)                                                     
                                                                 
 embedding (Embedding)       (None, 100, 16)           160000    
                                                                 
 global_average_pooling1d (G  (None, 16)               0         
 lobalAveragePooling1D)                                          
                                                                 
 dense (Dense)               (None, 16)                272       
                                                                 
 dense_1 (Dense)             (None, 1)                 17        
                                                                 
=================================================================
Total params: 160,289
Trainable params: 160,289
Non-trainable params: 0
_________________________________________________________________

Visualize as métricas do modelo no TensorBoard.

#docs_infra: no_execute
%load_ext tensorboard
%tensorboard --logdir logs

embeddings_classifier_accuracy.png

Recupere as incorporações de palavras treinadas e salve-as em disco

Em seguida, recupere as incorporações de palavras aprendidas durante o treinamento. Os embeddings são pesos da camada Embedding no modelo. A matriz de pesos tem formato (vocab_size, embedding_dimension) .

Obtenha os pesos do modelo usando get_layer() e get_weights() . A função get_vocabulary() fornece o vocabulário para construir um arquivo de metadados com um token por linha.

weights = model.get_layer('embedding').get_weights()[0]
vocab = vectorize_layer.get_vocabulary()

Escreva os pesos no disco. Para usar o Embedding Projector , você fará o upload de dois arquivos em formato separado por tabulação: um arquivo de vetores (contendo a incorporação) e um arquivo de metadados (contendo as palavras).

out_v = io.open('vectors.tsv', 'w', encoding='utf-8')
out_m = io.open('metadata.tsv', 'w', encoding='utf-8')

for index, word in enumerate(vocab):
  if index == 0:
    continue  # skip 0, it's padding.
  vec = weights[index]
  out_v.write('\t'.join([str(x) for x in vec]) + "\n")
  out_m.write(word + "\n")
out_v.close()
out_m.close()

Se você estiver executando este tutorial no Colaboratory , poderá usar o trecho a seguir para baixar esses arquivos para sua máquina local (ou usar o navegador de arquivos View -> Table of contents -> File browser ).

try:
  from google.colab import files
  files.download('vectors.tsv')
  files.download('metadata.tsv')
except Exception:
  pass

Visualize as incorporações

Para visualizar as incorporações, carregue-as no projetor de incorporação.

Abra o projetor de incorporação (isso também pode ser executado em uma instância local do TensorBoard).

  • Clique em "Carregar dados".

  • Carregue os dois arquivos que você criou acima: vecs.tsv e meta.tsv .

Os embeddings que você treinou agora serão exibidos. Você pode procurar palavras para encontrar seus vizinhos mais próximos. Por exemplo, tente pesquisar por "bonito". Você pode ver os vizinhos como "maravilhoso".

Próximos passos

Este tutorial mostrou como treinar e visualizar incorporações de palavras do zero em um pequeno conjunto de dados.