Usaremos la implementación de HuggingFace in en Pytorch.
BERT es un modelo con incrustaciones de posición absoluta, por lo que generalmente se recomienda rellenar (padding) las entradas a la derecha en lugar de a la izquierda.
BERT fue entrenado con el modelado de lenguaje enmascarado (MLM) y los objetivos de predicción de la siguiente oración (NSP). Es eficiente para predecir tokens enmascarados y en NLU en general, pero no es óptimo para la generación de texto.
La tarea de PLN es análisis de sentimiento. El primer experimento lo hacemos en Español. Para esta tarea esta bién usar el modelo uncase (eliminando mayúsculas).
from transformers import BertModel, BertTokenizer
import torch
model = BertModel.from_pretrained('dccuchile/bert-base-spanish-wwm-uncased')
tokenizer = BertTokenizer.from_pretrained('dccuchile/bert-base-spanish-wwm-uncased')
Downloading: 0%| | 0.00/650 [00:00<?, ?B/s]
Downloading: 0%| | 0.00/440M [00:00<?, ?B/s]
Some weights of the model checkpoint at dccuchile/bert-base-spanish-wwm-uncased were not used when initializing BertModel: ['cls.predictions.transform.LayerNorm.bias', 'cls.predictions.transform.dense.weight', 'cls.predictions.transform.LayerNorm.weight', 'cls.predictions.bias', 'cls.predictions.decoder.weight', 'cls.predictions.transform.dense.bias', 'cls.predictions.decoder.bias'] - This IS expected if you are initializing BertModel from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model). - This IS NOT expected if you are initializing BertModel from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model). Some weights of BertModel were not initialized from the model checkpoint at dccuchile/bert-base-spanish-wwm-uncased and are newly initialized: ['bert.pooler.dense.weight', 'bert.pooler.dense.bias'] You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
Downloading: 0%| | 0.00/248k [00:00<?, ?B/s]
Downloading: 0%| | 0.00/134 [00:00<?, ?B/s]
Downloading: 0%| | 0.00/310 [00:00<?, ?B/s]
Downloading: 0%| | 0.00/486k [00:00<?, ?B/s]
# sentencia
sentence = 'Yo amo a Bogotá'
# tokenización
tokens = tokenizer.tokenize(sentence)
# print
print(tokens)
['yo', 'amo', 'a', 'bogotá']
tokens = ['[CLS]'] + tokens + ['[SEP]']
print(tokens)
['[CLS]', 'yo', 'amo', 'a', 'bogotá', '[SEP]']
El tamaño de la lista de tokens es 5. Supongamos que hemos decidido que el tamaño máximo se las sentencias será 7. BERT está constuido para aceptar sentencias hasta de tamaño 512. Todas las sentencias deben tener el mismo tamaño.
## Relleno
max_sentence_size = 7
pad_size = max_sentence_size - len(tokens)
for i in range(pad_size):
tokens = tokens + ['[PAD]']
print(tokens)
['[CLS]', 'yo', 'amo', 'a', 'bogotá', '[SEP]', '[PAD]']
## máscara de atención
attention_mask = [1 if i!= '[PAD]' else 0 for i in tokens]
print(attention_mask)
[1, 1, 1, 1, 1, 1, 0]
token_ids = tokenizer.convert_tokens_to_ids(tokens)
print(token_ids)
[4, 1252, 4017, 1012, 14548, 5, 1]
token_ids = torch.tensor(token_ids).unsqueeze(0) # unsuezze es para agregar una dimensión al comienzo (varias sentencias)
attention_mask = torch.tensor(attention_mask).unsqueeze(0)
print(token_ids) # tensor([[ 101, 1045, 2293, 3000, 102, 0, 0]])
tensor([[ 4, 1252, 4017, 1012, 14548, 5, 1]])
print(token_ids)
print(attention_mask)
tensor([[ 4, 1252, 4017, 1012, 14548, 5, 1]]) tensor([[1, 1, 1, 1, 1, 1, 0]])
¿Cómo hace esto con tensorflow?
model regresa una lista con dos objetos:
out = model(token_ids, attention_mask = attention_mask)
last_hidden_state, pooler_output = out.last_hidden_state, out.pooler_output # out[0], out[1]
print(last_hidden_state.shape)
torch.Size([1, 7, 768])
# out es un diccionario. Podemos obtener las claves así:
out.keys()
odict_keys(['last_hidden_state', 'pooler_output'])
En esta sección revisamos como extraer las incrustaciones (embeddings) que salen de cada una de las capas codificadoras (12 por ejemplo en el modelo base). Algunos veces estop se hace para extraer diferentes features de las sentencias.
Por ejemplo en la tarea NER (name entity recognition) los investigadores han usado las incrustaciones de las diferentes capas, para hacr promedios pesados de algunas de ellas y con esto han podido mejorar la exactitud en la precisión.
Para hacer esto, es necesario instanciar el modelo preentrenado con la opción output_hidden_states=True:
model = BertModel.from_pretrained('dccuchile/bert-base-spanish-wwm-uncased', output_hidden_states=True)
tokenizer = BertTokenizer.from_pretrained('dccuchile/bert-base-spanish-wwm-uncased')
out = model(token_ids, attention_mask=attention_mask)
last_hidden_state, pooler_output, hidden_states = \
out.last_hidden_state, out.pooler_output, out.hidden_states
print(last_hidden_state.shape)
print(pooler_output.shape)
print(len(hidden_states)) # esta es una lista conteniendo las
# incrutaciones de todas las capas codificadoras
torch.Size([1, 7, 768]) torch.Size([1, 768]) 13
Observe que hidden_states tiene 13 elementos. La capa 0 corresponde a la incrustación de la capa de entrada, luego los elementos 1 a 12 corresponden a las incrustaciones de de salida de cada una de las 12 capas codificadoras.
La representación de los token de la última capa oculta (codificadora) pueden ser obtenidos así:
Esta es la representación contextual final de los token.
Las incrustaciones de cada capa i, se obtienen mediante *hidden_states[i]:
# Incrutaciones de la capa de entrada
input_embedding = hidden_states[0]
print(input_embedding.shape)
# incrustaciones de la capa codificadora 11
embedding_11 = hidden_states[11]
print(embedding_11.shape)
torch.Size([1, 7, 768]) torch.Size([1, 7, 768])
help(out)
Los pesos de atención después de la atención softmax, se utilizan para calcular el promedio ponderado en las cabezas de autoatención. Son obtenidos pasando al modelo output_attentions=True
model = BertModel.from_pretrained('dccuchile/bert-base-spanish-wwm-uncased',\
output_hidden_states=True, output_attentions=True)
tokenizer = BertTokenizer.from_pretrained('dccuchile/bert-base-spanish-wwm-uncased')
out = model(token_ids, attention_mask=attention_mask)
last_hidden_state, pooler_output, hidden_states, attentions = \
out.last_hidden_state, out.pooler_output, out.hidden_states, \
out.attentions
print(len(attentions))
12
print(attentions[11].shape)
torch.Size([1, 12, 7, 7])
La salida se explica así:
Por lo tanto tenemos la salida de las 12 cabezas de atención para la sentencia.
Vamos a darle una mirada a los pesos de atención de la última capa codificadora
attention11 = attentions[11].squeeze()#elimina la dimensión de batch.
attention11.shape
torch.Size([12, 7, 7])
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
# versión con decode utf-8
def plot_attention_head_cp(in_tokens, translated_tokens, attention):
# The plot is of the attention when a token was generated.
# The model didn't generate `<START>` in the output. Skip it.
translated_tokens = translated_tokens[1:]
ax = plt.gca()
ax.matshow(attention)
ax.set_xticks(range(len(in_tokens)))
ax.set_yticks(range(len(translated_tokens)))
labels = [label.decode('utf-8') for label in in_tokens.numpy()]
ax.set_xticklabels(
labels, rotation=90)
labels = [label.decode('utf-8') for label in translated_tokens.numpy()]
ax.set_yticklabels(labels)
def plot_attention_head(in_tokens, translated_tokens, attention):
# The plot is of the attention when a token was generated.
# The model didn't generate `<START>` in the output. Skip it.
#translated_tokens = translated_tokens[1:]
ax = plt.gca()
pcm = ax.matshow(attention)
ax.set_xticks(range(len(in_tokens)))
ax.set_yticks(range(len(translated_tokens)))
labels = [label for label in in_tokens]
ax.set_xticklabels(
labels, rotation=90)
labels = [label for label in translated_tokens]
ax.set_yticklabels(labels)
head = attention11[0]
head.shape
torch.Size([7, 7])
head = head.detach().numpy()
plot_attention_head(in_tokens=tokens, translated_tokens=tokens, attention=head)
def plot_attention_weights(sentence, translated_tokens, attention_heads):
in_tokens = sentence
#in_tokens = tokenizers.pt.tokenize(in_tokens).to_tensor()
#in_tokens = tokenizers.pt.lookup(in_tokens)[0]
#in_tokens
fig = plt.figure(figsize=(16, 8))
for h, head in enumerate(attention_heads):
ax = fig.add_subplot(3, 4, h+1)
plot_attention_head(in_tokens, translated_tokens, head)
ax.set_xlabel(f'Head {h+1}')
plt.tight_layout()
plt.show()
heads = attention11.detach().numpy()
plot_attention_weights(sentence=tokens, translated_tokens=tokens,
attention_heads=heads)