724 lines
29 KiB
Plaintext
724 lines
29 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Embeddings\n",
|
|
"\n",
|
|
"En nuestro ejemplo anterior, trabajamos con vectores de bolsa de palabras de alta dimensión con una longitud de `vocab_size`, y estábamos convirtiendo explícitamente desde vectores de representación posicional de baja dimensión a una representación dispersa de una sola posición activa (one-hot). Esta representación de una sola posición activa no es eficiente en términos de memoria, además, cada palabra se trata de manera independiente, es decir, los vectores codificados en una sola posición activa no expresan ninguna similitud semántica entre las palabras.\n",
|
|
"\n",
|
|
"En esta unidad, continuaremos explorando el dataset **News AG**. Para comenzar, carguemos los datos y obtengamos algunas definiciones del cuaderno anterior.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Loading dataset...\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\train.csv: 29.5MB [00:01, 18.8MB/s] \n",
|
|
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\test.csv: 1.86MB [00:00, 11.2MB/s] \n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Building vocab...\n",
|
|
"Vocab size = 95812\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"import torch\n",
|
|
"import torchtext\n",
|
|
"import numpy as np\n",
|
|
"from torchnlp import *\n",
|
|
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
|
|
"vocab_size = len(vocab)\n",
|
|
"print(\"Vocab size = \",vocab_size)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## ¿Qué es el embedding?\n",
|
|
"\n",
|
|
"La idea del **embedding** es representar palabras mediante vectores densos de menor dimensión, que de alguna manera reflejen el significado semántico de una palabra. Más adelante discutiremos cómo construir embeddings de palabras significativos, pero por ahora pensemos en los embeddings simplemente como una forma de reducir la dimensionalidad de un vector de palabras.\n",
|
|
"\n",
|
|
"Entonces, una capa de embedding tomaría una palabra como entrada y produciría un vector de salida con un `embedding_size` especificado. En cierto sentido, es muy similar a una capa `Linear`, pero en lugar de tomar un vector codificado en one-hot, podrá tomar un número de palabra como entrada.\n",
|
|
"\n",
|
|
"Al usar una capa de embedding como la primera capa en nuestra red, podemos cambiar del modelo de bolsa de palabras (**bag-of-words**) al modelo de **bolsa de embeddings** (**embedding bag**), donde primero convertimos cada palabra de nuestro texto en su embedding correspondiente, y luego calculamos alguna función de agregación sobre todos esos embeddings, como `sum`, `average` o `max`.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Nuestra red neuronal clasificador comenzará con una capa de embedding, luego una capa de agregación, y finalmente un clasificador lineal en la parte superior:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class EmbedClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
|
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, x):\n",
|
|
" x = self.embedding(x)\n",
|
|
" x = torch.mean(x,dim=1)\n",
|
|
" return self.fc(x)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Cómo manejar el tamaño variable de las secuencias\n",
|
|
"\n",
|
|
"Como resultado de esta arquitectura, los minibatches para nuestra red necesitarán ser creados de una manera específica. En la unidad anterior, al usar bag-of-words, todos los tensores BoW en un minibatch tenían el mismo tamaño `vocab_size`, independientemente de la longitud real de nuestra secuencia de texto. Una vez que pasamos a las incrustaciones de palabras, terminaremos con un número variable de palabras en cada muestra de texto, y al combinar esas muestras en minibatches tendremos que aplicar algún tipo de relleno.\n",
|
|
"\n",
|
|
"Esto se puede hacer utilizando la misma técnica de proporcionar la función `collate_fn` a la fuente de datos:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def padify(b):\n",
|
|
" # b is the list of tuples of length batch_size\n",
|
|
" # - first element of a tuple = label, \n",
|
|
" # - second = feature (text sequence)\n",
|
|
" # build vectorized sequence\n",
|
|
" v = [encode(x[1]) for x in b]\n",
|
|
" # first, compute max length of a sequence in this minibatch\n",
|
|
" l = max(map(len,v))\n",
|
|
" return ( # tuple of two tensors - labels and features\n",
|
|
" torch.LongTensor([t[0]-1 for t in b]),\n",
|
|
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v])\n",
|
|
" )\n",
|
|
"\n",
|
|
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Entrenando el clasificador de incrustaciones\n",
|
|
"\n",
|
|
"Ahora que hemos definido un dataloader adecuado, podemos entrenar el modelo utilizando la función de entrenamiento que definimos en la unidad anterior:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.6415625\n",
|
|
"6400: acc=0.6865625\n",
|
|
"9600: acc=0.7103125\n",
|
|
"12800: acc=0.726953125\n",
|
|
"16000: acc=0.739375\n",
|
|
"19200: acc=0.75046875\n",
|
|
"22400: acc=0.7572321428571429\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(0.889799795315499, 0.7623160588611644)"
|
|
]
|
|
},
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
|
"train_epoch(net,train_loader, lr=1, epoch_size=25000)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **Nota**: Aquí solo estamos entrenando con 25k registros (menos de una época completa) por cuestiones de tiempo, pero puedes continuar entrenando, escribir una función para entrenar durante varias épocas y experimentar con el parámetro de la tasa de aprendizaje para lograr una mayor precisión. Deberías poder alcanzar una precisión de alrededor del 90%.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Capa EmbeddingBag y Representación de Secuencias de Longitud Variable\n",
|
|
"\n",
|
|
"En la arquitectura anterior, necesitábamos rellenar todas las secuencias para que tuvieran la misma longitud y así poder ajustarlas en un minibatch. Esta no es la forma más eficiente de representar secuencias de longitud variable; otra opción sería usar un vector de **desplazamientos** (offset), que contendría los desplazamientos de todas las secuencias almacenadas en un único vector grande.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"> **Nota**: En la imagen de arriba, mostramos una secuencia de caracteres, pero en nuestro ejemplo estamos trabajando con secuencias de palabras. Sin embargo, el principio general de representar secuencias con un vector de desplazamientos sigue siendo el mismo.\n",
|
|
"\n",
|
|
"Para trabajar con la representación basada en desplazamientos, usamos la capa [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html). Es similar a `Embedding`, pero toma como entrada un vector de contenido y un vector de desplazamientos, e incluye además una capa de promediado, que puede ser `mean`, `sum` o `max`.\n",
|
|
"\n",
|
|
"Aquí está la red modificada que utiliza `EmbeddingBag`:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class EmbedClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.embedding = torch.nn.EmbeddingBag(vocab_size, embed_dim)\n",
|
|
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, text, off):\n",
|
|
" x = self.embedding(text, off)\n",
|
|
" return self.fc(x)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Para preparar el conjunto de datos para el entrenamiento, necesitamos proporcionar una función de conversión que prepare el vector de desplazamiento:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def offsetify(b):\n",
|
|
" # first, compute data tensor from all sequences\n",
|
|
" x = [torch.tensor(encode(t[1])) for t in b]\n",
|
|
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
|
" o = [0] + [len(t) for t in x]\n",
|
|
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
|
" return ( \n",
|
|
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
|
" torch.cat(x), # text \n",
|
|
" o\n",
|
|
" )\n",
|
|
"\n",
|
|
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Nota que, a diferencia de todos los ejemplos anteriores, nuestra red ahora acepta dos parámetros: vector de datos y vector de desplazamiento, que son de diferentes tamaños. De manera similar, nuestro cargador de datos también nos proporciona 3 valores en lugar de 2: tanto los vectores de texto como los vectores de desplazamiento se proporcionan como características. Por lo tanto, necesitamos ajustar ligeramente nuestra función de entrenamiento para encargarnos de eso:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.6153125\n",
|
|
"6400: acc=0.6615625\n",
|
|
"9600: acc=0.6932291666666667\n",
|
|
"12800: acc=0.715078125\n",
|
|
"16000: acc=0.7270625\n",
|
|
"19200: acc=0.7382291666666667\n",
|
|
"22400: acc=0.7486160714285715\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(22.771553103007037, 0.7551983365323096)"
|
|
]
|
|
},
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
|
"\n",
|
|
"def train_epoch_emb(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.CrossEntropyLoss(),epoch_size=None, report_freq=200):\n",
|
|
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
|
|
" loss_fn = loss_fn.to(device)\n",
|
|
" net.train()\n",
|
|
" total_loss,acc,count,i = 0,0,0,0\n",
|
|
" for labels,text,off in dataloader:\n",
|
|
" optimizer.zero_grad()\n",
|
|
" labels,text,off = labels.to(device), text.to(device), off.to(device)\n",
|
|
" out = net(text, off)\n",
|
|
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
|
|
" loss.backward()\n",
|
|
" optimizer.step()\n",
|
|
" total_loss+=loss\n",
|
|
" _,predicted = torch.max(out,1)\n",
|
|
" acc+=(predicted==labels).sum()\n",
|
|
" count+=len(labels)\n",
|
|
" i+=1\n",
|
|
" if i%report_freq==0:\n",
|
|
" print(f\"{count}: acc={acc.item()/count}\")\n",
|
|
" if epoch_size and count>epoch_size:\n",
|
|
" break\n",
|
|
" return total_loss.item()/count, acc.item()/count\n",
|
|
"\n",
|
|
"\n",
|
|
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Embeddings Semánticos: Word2Vec\n",
|
|
"\n",
|
|
"En nuestro ejemplo anterior, la capa de embeddings del modelo aprendió a mapear palabras a representaciones vectoriales, sin embargo, esta representación no tenía mucho significado semántico. Sería ideal aprender una representación vectorial en la que palabras similares o sinónimos correspondan a vectores cercanos entre sí en términos de alguna distancia vectorial (por ejemplo, distancia euclidiana).\n",
|
|
"\n",
|
|
"Para lograr esto, necesitamos preentrenar nuestro modelo de embeddings en una gran colección de texto de una manera específica. Una de las primeras formas de entrenar embeddings semánticos se llama [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Se basa en dos arquitecturas principales que se utilizan para producir una representación distribuida de palabras:\n",
|
|
"\n",
|
|
" - **Continuous bag-of-words** (CBoW) — en esta arquitectura, entrenamos el modelo para predecir una palabra a partir del contexto circundante. Dado el ngrama $(W_{-2},W_{-1},W_0,W_1,W_2)$, el objetivo del modelo es predecir $W_0$ a partir de $(W_{-2},W_{-1},W_1,W_2)$.\n",
|
|
" - **Continuous skip-gram** es lo opuesto a CBoW. El modelo utiliza una ventana de palabras de contexto circundantes para predecir la palabra actual.\n",
|
|
"\n",
|
|
"CBoW es más rápido, mientras que skip-gram es más lento, pero hace un mejor trabajo representando palabras poco frecuentes.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Para experimentar con embeddings Word2Vec preentrenados en el conjunto de datos de Google News, podemos usar la biblioteca **gensim**. A continuación, encontramos las palabras más similares a 'neural'.\n",
|
|
"\n",
|
|
"> **Nota:** ¡Cuando creas vectores de palabras por primera vez, descargarlos puede tomar algo de tiempo!\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import gensim.downloader as api\n",
|
|
"w2v = api.load('word2vec-google-news-300')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"neuronal -> 0.7804799675941467\n",
|
|
"neurons -> 0.7326500415802002\n",
|
|
"neural_circuits -> 0.7252851724624634\n",
|
|
"neuron -> 0.7174385190010071\n",
|
|
"cortical -> 0.6941086649894714\n",
|
|
"brain_circuitry -> 0.6923246383666992\n",
|
|
"synaptic -> 0.6699118614196777\n",
|
|
"neural_circuitry -> 0.6638563275337219\n",
|
|
"neurochemical -> 0.6555314064025879\n",
|
|
"neuronal_activity -> 0.6531826257705688\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"for w,p in w2v.most_similar('neural'):\n",
|
|
" print(f\"{w} -> {p}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Podemos también calcular incrustaciones vectoriales a partir de la palabra, para ser utilizadas en el entrenamiento del modelo de clasificación (solo mostramos los primeros 20 componentes del vector para mayor claridad):\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"array([ 0.01226807, 0.06225586, 0.10693359, 0.05810547, 0.23828125,\n",
|
|
" 0.03686523, 0.05151367, -0.20703125, 0.01989746, 0.10058594,\n",
|
|
" -0.03759766, -0.1015625 , -0.15820312, -0.08105469, -0.0390625 ,\n",
|
|
" -0.05053711, 0.16015625, 0.2578125 , 0.10058594, -0.25976562],\n",
|
|
" dtype=float32)"
|
|
]
|
|
},
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"w2v.word_vec('play')[:20]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Lo grandioso de las incrustaciones semánticas es que puedes manipular la codificación vectorial para cambiar la semántica. Por ejemplo, podemos pedir encontrar una palabra cuya representación vectorial sea lo más cercana posible a las palabras *rey* y *mujer*, y lo más alejada posible de la palabra *hombre*:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"('queen', 0.7118192911148071)"
|
|
]
|
|
},
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"w2v.most_similar(positive=['king','woman'],negative=['man'])[0]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Tanto CBoW como Skip-Grams son incrustaciones \"predictivas\", ya que solo toman en cuenta los contextos locales. Word2Vec no aprovecha el contexto global.\n",
|
|
"\n",
|
|
"**FastText** se basa en Word2Vec al aprender representaciones vectoriales para cada palabra y los n-gramas de caracteres que se encuentran dentro de cada palabra. Los valores de las representaciones se promedian en un vector en cada paso de entrenamiento. Aunque esto agrega mucha computación adicional al pre-entrenamiento, permite que las incrustaciones de palabras codifiquen información de sub-palabras.\n",
|
|
"\n",
|
|
"Otro método, **GloVe**, aprovecha la idea de la matriz de co-ocurrencia y utiliza métodos neuronales para descomponer la matriz de co-ocurrencia en vectores de palabras más expresivos y no lineales.\n",
|
|
"\n",
|
|
"Puedes experimentar con el ejemplo cambiando las incrustaciones a FastText y GloVe, ya que gensim admite varios modelos diferentes de incrustaciones de palabras.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Uso de Embeddings Preentrenados en PyTorch\n",
|
|
"\n",
|
|
"Podemos modificar el ejemplo anterior para prellenar la matriz en nuestra capa de embedding con embeddings semánticos, como Word2Vec. Debemos tener en cuenta que los vocabularios del embedding preentrenado y nuestro corpus de texto probablemente no coincidan, por lo que inicializaremos los pesos de las palabras faltantes con valores aleatorios:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Embedding size: 300\n",
|
|
"Populating matrix, this will take some time...Done, found 41080 words, 54732 words missing\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"embed_size = len(w2v.get_vector('hello'))\n",
|
|
"print(f'Embedding size: {embed_size}')\n",
|
|
"\n",
|
|
"net = EmbedClassifier(vocab_size,embed_size,len(classes))\n",
|
|
"\n",
|
|
"print('Populating matrix, this will take some time...',end='')\n",
|
|
"found, not_found = 0,0\n",
|
|
"for i,w in enumerate(vocab.get_itos()):\n",
|
|
" try:\n",
|
|
" net.embedding.weight[i].data = torch.tensor(w2v.get_vector(w))\n",
|
|
" found+=1\n",
|
|
" except:\n",
|
|
" net.embedding.weight[i].data = torch.normal(0.0,1.0,(embed_size,))\n",
|
|
" not_found+=1\n",
|
|
"\n",
|
|
"print(f\"Done, found {found} words, {not_found} words missing\")\n",
|
|
"net = net.to(device)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Ahora entrenemos nuestro modelo. Ten en cuenta que el tiempo que lleva entrenar el modelo es significativamente mayor que en el ejemplo anterior, debido al tamaño más grande de la capa de incrustación y, por lo tanto, a un número mucho mayor de parámetros. Además, debido a esto, es posible que necesitemos entrenar nuestro modelo con más ejemplos si queremos evitar el sobreajuste.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.6359375\n",
|
|
"6400: acc=0.68109375\n",
|
|
"9600: acc=0.7067708333333333\n",
|
|
"12800: acc=0.723671875\n",
|
|
"16000: acc=0.73625\n",
|
|
"19200: acc=0.7463541666666667\n",
|
|
"22400: acc=0.7560714285714286\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(214.1013875559821, 0.7626759436980166)"
|
|
]
|
|
},
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"En nuestro caso, no observamos un gran aumento en la precisión, lo cual probablemente se deba a vocabularios bastante diferentes. \n",
|
|
"Para superar el problema de los vocabularios distintos, podemos usar una de las siguientes soluciones: \n",
|
|
"* Reentrenar el modelo word2vec con nuestro vocabulario \n",
|
|
"* Cargar nuestro conjunto de datos utilizando el vocabulario del modelo word2vec preentrenado. El vocabulario usado para cargar el conjunto de datos puede especificarse durante la carga. \n",
|
|
"\n",
|
|
"El último enfoque parece más sencillo, especialmente porque el marco `torchtext` de PyTorch contiene soporte integrado para embeddings. Por ejemplo, podemos instanciar un vocabulario basado en GloVe de la siguiente manera: \n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"100%|█████████▉| 399999/400000 [00:15<00:00, 25411.14it/s]\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"vocab = torchtext.vocab.GloVe(name='6B', dim=50)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"El vocabulario cargado tiene las siguientes operaciones básicas: \n",
|
|
"* El diccionario `vocab.stoi` nos permite convertir una palabra en su índice dentro del diccionario. \n",
|
|
"* `vocab.itos` hace lo contrario: convierte un número en una palabra. \n",
|
|
"* `vocab.vectors` es el array de vectores de incrustación, por lo que, para obtener la incrustación de una palabra `s`, necesitamos usar `vocab.vectors[vocab.stoi[s]]`. \n",
|
|
"\n",
|
|
"Aquí tienes un ejemplo de cómo manipular las incrustaciones para demostrar la ecuación **amable-hombre+mujer = reina** (tuve que ajustar un poco el coeficiente para que funcionara): \n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'queen'"
|
|
]
|
|
},
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# get the vector corresponding to kind-man+woman\n",
|
|
"qvec = vocab.vectors[vocab.stoi['king']]-vocab.vectors[vocab.stoi['man']]+1.3*vocab.vectors[vocab.stoi['woman']]\n",
|
|
"# find the index of the closest embedding vector \n",
|
|
"d = torch.sum((vocab.vectors-qvec)**2,dim=1)\n",
|
|
"min_idx = torch.argmin(d)\n",
|
|
"# find the corresponding word\n",
|
|
"vocab.itos[min_idx]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Para entrenar el clasificador utilizando esos embeddings, primero necesitamos codificar nuestro conjunto de datos utilizando el vocabulario de GloVe:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def offsetify(b):\n",
|
|
" # first, compute data tensor from all sequences\n",
|
|
" x = [torch.tensor(encode(t[1],voc=vocab)) for t in b] # pass the instance of vocab to encode function!\n",
|
|
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
|
" o = [0] + [len(t) for t in x]\n",
|
|
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
|
" return ( \n",
|
|
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
|
" torch.cat(x), # text \n",
|
|
" o\n",
|
|
" )"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Como hemos visto anteriormente, todas las incrustaciones de vectores se almacenan en la matriz `vocab.vectors`. Esto hace que sea muy fácil cargar esos pesos en los pesos de la capa de incrustación mediante una simple copia:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"net = EmbedClassifier(len(vocab),len(vocab.vectors[0]),len(classes))\n",
|
|
"net.embedding.weight.data = vocab.vectors\n",
|
|
"net = net.to(device)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.6271875\n",
|
|
"6400: acc=0.68078125\n",
|
|
"9600: acc=0.7030208333333333\n",
|
|
"12800: acc=0.71984375\n",
|
|
"16000: acc=0.7346875\n",
|
|
"19200: acc=0.7455729166666667\n",
|
|
"22400: acc=0.7529464285714286\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(35.53972978646833, 0.7575175943698017)"
|
|
]
|
|
},
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)\n",
|
|
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Una de las razones por las que no estamos viendo un aumento significativo en la precisión se debe al hecho de que algunas palabras de nuestro conjunto de datos faltan en el vocabulario preentrenado de GloVe, y por lo tanto, esencialmente se ignoran. Para superar este hecho, podemos entrenar nuestras propias incrustaciones en nuestro conjunto de datos.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Embeddings Contextuales\n",
|
|
"\n",
|
|
"Una limitación clave de las representaciones tradicionales de embeddings preentrenados, como Word2Vec, es el problema de la desambiguación del sentido de las palabras. Aunque los embeddings preentrenados pueden capturar parte del significado de las palabras en contexto, todos los posibles significados de una palabra se codifican en el mismo embedding. Esto puede generar problemas en los modelos posteriores, ya que muchas palabras, como la palabra 'play', tienen diferentes significados dependiendo del contexto en el que se utilicen.\n",
|
|
"\n",
|
|
"Por ejemplo, la palabra 'play' en estas dos frases tiene significados bastante diferentes:\n",
|
|
"- Fui a una **obra** en el teatro.\n",
|
|
"- John quiere **jugar** con sus amigos.\n",
|
|
"\n",
|
|
"Los embeddings preentrenados mencionados anteriormente representan ambos significados de la palabra 'play' en el mismo embedding. Para superar esta limitación, necesitamos construir embeddings basados en el **modelo de lenguaje**, que se entrena con un gran corpus de texto y *sabe* cómo las palabras pueden combinarse en diferentes contextos. Hablar de embeddings contextuales está fuera del alcance de este tutorial, pero volveremos a ellos cuando hablemos de modelos de lenguaje en la próxima unidad.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Descargo de responsabilidad**: \nEste documento ha sido traducido utilizando el servicio de traducción automática [Co-op Translator](https://github.com/Azure/co-op-translator). Aunque nos esforzamos por garantizar la precisión, tenga en cuenta que las traducciones automatizadas pueden contener errores o imprecisiones. El documento original en su idioma nativo debe considerarse como la fuente autorizada. Para información crítica, se recomienda una traducción profesional realizada por humanos. No nos hacemos responsables de malentendidos o interpretaciones erróneas que puedan surgir del uso de esta traducción.\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"interpreter": {
|
|
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "py37_pytorch",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.8.12"
|
|
},
|
|
"coopTranslator": {
|
|
"original_hash": "f50b026abce5cf36783a560ea72cb9b1",
|
|
"translation_date": "2025-08-31T17:14:26+00:00",
|
|
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
|
"language_code": "es"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |