Embeddings

En este post exploramos el mundo de los embeddings en la inteligencia artificial, una técnica esencial que transforma datos complejos, como texto, en formatos numéricos comprensibles para los modelos de aprendizaje automático. Descubre cómo los embeddings capturan significados y relaciones en los datos, facilitando el procesamiento del lenguaje, y comprende su impacto en el avance de la tecnología de los LLM

Open In Colab

En un anterior post sobre tokens, ya vimos la representación mínima de cada palabra. Que corresponde a darle un número a la mínima división de cada palabra.

Sin embargo los transformers y por tanto los LLMs, no representan así la información de las palabras, sino que lo hacen mediante embeddings.

Vamos a ver primero dos formas de representar las palabras, el ordinal encoding y el one hot encoding. Y viendo los problemas de estos dos tipos de representaciones podremos llegar hasta los word embeddings y los sentence embeddings.

Además vamos a ver un ejemplo de cómo entrenar un modelo de word embeddings con la librería gensim.

Y por último veremos cómo usar modelos preentrenados de embeddings con la librería transformers de HuggingFace.

Esta es la manera más básica de representar las palabras dentro de los transformers. Consiste en darle un número a cada palabra, o quedarnos con los números que ya tienen asignados los tokens.

Sin embargo este tipo de representación tiene dos problemas

  • Imaginemos que mesa corresponde al token 3, gato al token 1 y perro al token 2. Se podría llegar a suponer que mesa = gato + perro, pero no es así. No existe esa relación entre esas palabras. Incluso podríamos pensar que adjudicando los tokens correctos sí podría llegar a darse este tipo de relaciones. Sin embargo este pensamiento se viene abajo con las palabras que tienen más de un significado, como por ejemplo la palabra banco

  • El segundo problema es que las redes neuronales internamente hacen muchos cálculos numéricos, por lo que podría darse el caso en el que si mesa tiene el token 3, tenga internamente más importancia que la palabra gato que tiene el token 1.

De modo que este tipo de representación de las palabras se puede descartar muy rápidamente

Aquí lo que se hace es usar vectores de N dimensiones. Por ejemplo vimos que OpenAI tiene un vocabulario de 100277 tokens distintos. Por lo que si usamos one hot encoding, cada palabra se representaría con un vector de 100277 dimensiones.

Sin embargo el one hot encodding tiene otro dos grandes problemas

  • No tiene en cuenta la relación entre las palabras. Por lo que si tenemos dos palabras que son sinónimos, como por ejemplo gato y felino, tendríamos dos vectores distintos para representarlas.

En el lenguaje la relación entre las palabras es muy importante, y no tener en cuenta esta relación es un gran problema.

  • El segundo problema es que los vectores son muy grandes. Si tenemos un vocabulario de 100277 tokens, cada palabra se representaría con un vector de 100277 dimensiones. Esto hace que los vectores sean muy grandes y que los cálculos sean muy costosos. Además estos vectores van a ser todo ceros, excepto en la posición que corresponda al token de la palabra. Por lo que la mayoría de los cálculos van a ser multiplicaciones por cero, que son cálculos que no aportan nada. Así que vamos a tener un montón de memoria asignada a vectores en los que solo se tiene un 1 en una posición determinada.

Con los word embeddings se intenta solucionar los problemas de los dos tipos de representaciones anteriores. Para ello se usan vectores de N dimensiones, pero en este caso no se usan vectores de 100277 dimensiones, sino que se usan vectores de muchas menos dimensiones. Por ejemplo veremos que OpenAI usa 1536 dimensiones.

Cada una de las dimensiones de estos vectores representan una característica de la palabra. Por ejemplo una de las dimensiones podría representar si la palabra es un verbo o un sustantivo. Otra dimensión podría representar si la palabra es un animal o no. Otra dimensión podría representar si la palabra es un nombre propio o no. Y así sucesivamente.

Sin embargo estas características no se definen a mano, sino que se aprenden de forma automática. Durante el entrenamiento de los transformers, se van ajustando los valores de cada una de las dimensiones de los vectores, de modo que se aprenden las características de cada una de las palabras.

Al hacer que cada una de las dimensiones de las palabras represente una característica de la palabra, se consigue que las palabras que tengan características similares, tengan vectores similares. Por ejemplo las palabras gato y felino tendrán vectores muy similares, ya que ambas son animales. Y las palabras mesa y silla tendrán vectores similares, ya que ambas son muebles.

En la siguiente imagen podemos ver una representación de 3 dimensiones depalabras, y podemos ver que todas las palabras relacionadas con school están cerca, todas las palabras relacionadas con food están cerca y todas las palabras relacionadas con ball están cerca.

word_embedding_3_dimmension

Tener que cada una de las dimensiones de los vectores represente una característica de la palabra, consigue que podamos hacer operaciones con palabras. Por ejemplo si a la palabra rey se le resta la palabra hombre y se le suma la palabra mujer, obtenemos una palabra muy parecida a la palabra reina. Más adelante lo comprobaremos con un ejemplo

Como cada una de las palabras se representa mediante un vector de N dimensiones, podemos calcular la similitud entre dos palabras. Para ello se usa la función de similitud del coseno o cosine similarity.

Si dos palabras están cercanas en el espacio vectorial, quiere decir que el álgulo que hay entre sus vectores es pequeño, por lo que su coseno es cercano a 1. Si hay un ángulo de 90 grados entre los vectores, el coseno es 0, es decir que no hay similitud entre las palabras. Y si hay un ángulo de 180 grados entre los vectores, el coseno es -1, es decir que las palabras son opuestas.

cosine similarity

Ahora que sabemos lo que son los embeddings, veamos unos ejemplos con los embeddings que nos proporciona la API de OpenAI.

Para ello primero tenemos que tener instalado el paquete de OpenAI

pip install openai

Importamos las librerías necesarias

Code:

from openai import OpenAI

import torch

from torch.nn.functional import cosine_similarity

Usamos una API key de OpenAI. Para ello, nos dirigimos a la página de OpenAI, y nos registramos. Una vez registrados, nos dirigimos a la sección de API Keys, y creamos una nueva API Key.

open ai api key

Code:

api_key = "Pon aquí tu API key"

Seleccionamos que modelo de embeddings queremos usar. En este caso vamos a usar text-embedding-ada-002 que es el que recomienda OpenAI en su documentación de embeddings.

Code:

model_openai = "text-embedding-ada-002"

Creamos un cliente de la API

Code:

client_openai = OpenAI(api_key=api_key, organization=None)

Vamos a ver cómo son los embeddings de la palabra Rey

Code:

word = "Rey"

embedding_openai = torch.Tensor(client_openai.embeddings.create(input=word, model=model_openai).data[0].embedding)

embedding_openai.shape, embedding_openai

Output:

(torch.Size([1536]),

tensor([-0.0103, -0.0005, -0.0189, ..., -0.0009, -0.0226, 0.0045]))

Como vemos obtenemos un vector de 1536 dimensiones

Vamos a obtener los embeddings de las palabras rey, hombre, mujer y reina

Code:

embedding_openai_rey = torch.Tensor(client_openai.embeddings.create(input="rey", model=model_openai).data[0].embedding)

embedding_openai_hombre = torch.Tensor(client_openai.embeddings.create(input="hombre", model=model_openai).data[0].embedding)

embedding_openai_mujer = torch.Tensor(client_openai.embeddings.create(input="mujer", model=model_openai).data[0].embedding)

embedding_openai_reina = torch.Tensor(client_openai.embeddings.create(input="reina", model=model_openai).data[0].embedding)

Code:

embedding_openai_reina.shape, embedding_openai_reina

Output:

(torch.Size([1536]),

tensor([-0.0110, -0.0084, -0.0115, ..., 0.0082, -0.0096, -0.0024]))

Vamos a obtener el embedding resultante de restarle a rey el embedding de hombre y sumarle el embedding de mujer

Code:

embedding_openai = embedding_openai_rey - embedding_openai_hombre + embedding_openai_mujer

Code:

embedding_openai.shape, embedding_openai

Output:

(torch.Size([1536]),

tensor([-0.0226, -0.0323, 0.0017, ..., 0.0014, -0.0290, -0.0188]))

Por último comparamos el resultado obtenido con el embedding de reina. Para ello usamos la función de cosine_similarity que nos proporciona la librería pytorch

Code:

similarity_openai = cosine_similarity(embedding_openai.unsqueeze(0), embedding_openai_reina.unsqueeze(0)).item()

print(f"similarity_openai: {similarity_openai}")

Output:

similarity_openai: 0.7564167976379395

Como vemos es un valor muy cercano a 1, por lo que podemos decir que el resultado obtenido es muy parecido al embedding de reina

Si usamos palabras en inglés, obtenemos un resultado más cercano a 1

Code:

embedding_openai_rey = torch.Tensor(client_openai.embeddings.create(input="king", model=model_openai).data[0].embedding)

embedding_openai_hombre = torch.Tensor(client_openai.embeddings.create(input="man", model=model_openai).data[0].embedding)

embedding_openai_mujer = torch.Tensor(client_openai.embeddings.create(input="woman", model=model_openai).data[0].embedding)

embedding_openai_reina = torch.Tensor(client_openai.embeddings.create(input="queen", model=model_openai).data[0].embedding)

Code:

embedding_openai = embedding_openai_rey - embedding_openai_hombre + embedding_openai_mujer

Code:

similarity_openai = cosine_similarity(embedding_openai.unsqueeze(0), embedding_openai_reina.unsqueeze(0))

print(f"similarity_openai: {similarity_openai}")

Output:

similarity_openai: tensor([0.8849])

Esto es normal, ya que el modelo de OpenAi ha sido entrenado con más textos en inglés que en español

Existen varios tipos de word embeddings, y cada uno de ellos tiene sus ventajas e inconvenientes. Vamos a ver los más importantes

  • Word2Vec
  • GloVe
  • FastText
  • BERT
  • GPT-2

Word2Vec es un algoritmo que se usa para crear word embeddings. Este algoritmo fue creado por Google en 2013, y es uno de los algoritmos más usados para crear word embeddings.

Tiene dos variantes, CBOW y Skip-gram. CBOW es más rápido de entrenar, mientras que Skip-gram es más preciso. Vamos a ver cómo funciona cada uno de ellos

CBOW o Continuous Bag of Words es un algoritmo que se usa para predecir una palabra a partir de las palabras que la rodean. Por ejemplo si tenemos la frase El gato es un animal, el algoritmo intentará predecir la palabra gato a partir de las palabras que la rodean, en este caso El, es, un y animal.

CBOW

En esta arquitectura, el modelo predice cuál es la palabra más probable en el contexto dado. Por lo tanto, las palabras que tienen la misma probabilidad de aparecer se consideran similares y, por lo tanto, se acercan más en el espacio dimensional.

Supongamos que en una oración reemplazamos barco con bote, entonces el modelo predice la probabilidad para ambos y si resulta ser similar entonces podemos considerar que las palabras son similares.

Skip-gram o Skip-gram with Negative Sampling es un algoritmo que se usa para predecir las palabras que rodean a una palabra. Por ejemplo si tenemos la frase El gato es un animal, el algoritmo intentará predecir las palabras El, es, un y animal a partir de la palabra gato.

Skip-gram

Esta arquitectura es similar a la de CBOW, pero en cambio el modelo funciona al revés. El modelo predice el contexto usando la palabra dada. Por lo tanto, las palabras que tienen el mismo contexto se consideran similares y, por lo tanto, se acercan más en el espacio dimensional.

GloVe o Global Vectors for Word Representation es un algoritmo que se usa para crear word embeddings. Este algoritmo fue creado por la Universidad de Stanford en 2014.

Word2Vec ignora el hecho de que algunas palabras de contexto se producen con más frecuencia que otras y también solo tienen en cuenta el contexto local y por lo tanto, no capturar el contexto global.

Este algoritmo usa una matriz de co-ocurrencia para crear los word embeddings. Esta matriz de co-ocurrencia es una matriz que contiene el número de veces que aparece cada palabra junto a cada una de las otras palabras del vocabulario.

FastText es un algoritmo que se usa para crear word embeddings. Este algoritmo fue creado por Facebook en 2016.

Una de las principales desventajas de Word2Vec y GloVe es que no pueden codificar palabras desconocidas o fuera del vocabulario.

Entonces, para lidiar con este problema, Facebook propuso un modelo FastText. Es una extensión de Word2Vec y sigue el mismo modelo Skip-gram y CBOW. pero a diferencia de Word2Vec que alimenta palabras enteras en la red neuronal, FastText primero divide las palabras en varias subpalabras (o n-grams) y luego las alimenta a la red neuronal.

Por ejemplo, si el valor de n es 3 y la palabra es manzana entonces su tri-gram será [, man, anz, nza, zan, ana, na>] y su embedding de palabras será la suma de la representación vectorial de estos tri-grams. Aquí, los hiperparámetros min_n y max_n se consideran como 3 y los caracteres < y > representan el comienzo y el final de la palabra.

Por lo tanto, utilizando esta metodología, las palabras desconocidas se pueden representar en forma vectorial, ya que tiene una alta probabilidad de que sus n-grams también estén presentes en otras palabras.

Este algoritmo es una mejora de Word2Vec, ya que además de tener en cuenta las palabras que rodean a una palabra, también tiene en cuenta los n-grams de la palabra. Por ejemplo si tenemos la palabra gato, también tiene en cuenta los n-gramas de la palabra, en este caso ga, at y to, para n = 2.

Las técnicas de word embedding han dado un resultado decente, pero el problema es que el enfoque no es lo suficientemente preciso. No tienen en cuenta el orden de las palabras en las que aparecen, lo que conduce a la pérdida de la comprensión sintáctica y semántica de la oración.

Por ejemplo, Vas allí para enseñar, no a jugar Y Vas allí a jugar, no a enseñar Ambas oraciones tendrán la misma representación en el espacio vectorial, pero no significan lo mismo.

Además, el modelo de word embedding no puede dar resultados satisfactorios en una gran cantidad de datos de texto, ya que la misma palabra puede tener un significado diferente en una oración diferente según el contexto de la oración.

Por ejemplo, Voy a sentarme en el banco Y Voy a hacer gestiones en el banco En ambas oraciones, la palabra banco tiene diferentes significados.

Por lo tanto, requerimos un tipo de representación que pueda retener el significado contextual de la palabra presente en una oración.

El sentence embedding es similar al word embedding, pero en lugar de palabras, codifican toda la oración en la representación vectorial.

Una forma simple de obtener sentence embedding es promediando los word embedding de todas las palabras presentes en la oración. Pero no son lo suficientemente precisos.

Algunos de los modelos más avanzados para sentence embedding son ELMo, InferSent y Sentence-BERT

ELMo o Embeddings from Language Models es un modelo de sentence embedding que fue creado por la Universidad de Allen en 2018. Utiliza una red LSTM profunda bidireccional para producir representación vectorial. ELMo puede representar las palabras desconocidas o fuera del vocabulario en forma vectorial ya que está basado en caracteres.

InferSent es un modelo de sentence embedding que fue creado por Facebook en 2017. Utiliza una red LSTM profunda bidireccional para producir representación vectorial. InferSent puede representar las palabras desconocidas o fuera del vocabulario en forma vectorial ya que está basado en caracteres. Las oraciones están codificadas en una representación vectorial de 4096 dimensiones.

La capacitación del modelo se realiza en el conjunto de datos de Stanford Natural Language Inference (SNLI). Este conjunto de datos está etiquetado y escrito por humanos para alrededor de 500K pares de oraciones.

Sentence-BERT es un modelo de sentence embedding que fue creado por la Universidad de Londres en 2019. Utiliza una red LSTM profunda bidireccional para producir representación vectorial. Sentence-BERT puede representar las palabras desconocidas o fuera del vocabulario en forma vectorial ya que está basado en caracteres. Las oraciones están codificadas en una representación vectorial de 768 dimensiones.

El modelo de NLP de última generación BERT es excelente en las tareas de Similitud Textual Semántica, pero el problema es que tomaría mucho tiempo para un corpus enorme (65 horas para 10,000 oraciones), ya que requiere que ambas oraciones se introduzcan en la red y esto aumenta el cálculo por un factor enorme.

Por lo tanto, Sentence-BERT es una modificación del modelo BERT.

Para descargar el dataset que vamos a usar hay que instalar la librería dataset de huggingface:

pip install datasets

Para entrenar el modelo de embeddings vamos a usar la librería gensim. Para instalarla con conda usamos

conda install -c conda-forge gensim

Y para instalarla con pip usamos

pip install gensim

Para limpiar el dataset que nos hemos descargado vamos a usar expresiones regulares, que normalmente ya está instalada en python, y nltk que es una librería de procesamiento de lenguaje natural. Para instalarla con conda usamos

conda install -c anaconda nltk

Y para instalarla con pip usamos

pip install nltk

Ahora que tenemos todo instalado podemos importar las librerías que vamos a usar:

Code:

from gensim.models import Word2Vec

from gensim.parsing.preprocessing import strip_punctuation, strip_numeric, strip_short

import re

from nltk.corpus import stopwords

from nltk.tokenize import word_tokenize

Vamos a descargar un dataset de textos procedentes de la wikipedia en español, para ello ejecutamos lo siguiente:

Code:

from datasets import load_dataset

dataset_corpus = load_dataset('large_spanish_corpus', name='all_wikis')

Vamos a ver cómo es

Code:

dataset_corpus

Output:

DatasetDict({

train: Dataset({

features: ['text'],

num_rows: 28109484

})

})

Como podemos ver, el dataset tiene más de 28 millones de textos. Vamos a ver alguno de ellos:

Code:

dataset_corpus['train']['text'][0:10]

Output:

['¡Bienvenidos!',

'Ir a los contenidos»',

'= Contenidos =',

'',

'Portada',

'Tercera Lengua más hablada en el mundo.',

'La segunda en número de habitantes en el mundo occidental.',

'La de mayor proyección y crecimiento día a día.',

'El español es, hoy en día, nombrado en cada vez más contextos, tomando realce internacional como lengua de cultura y civilización siempre de mayor envergadura.',

'Ejemplo de ello es que la comunidad minoritaria más hablada en los Estados Unidos es precisamente la que habla idioma español.']

Como hay muchos ejemplos vamos a crear un subset de 10 millónes de ejemplos para poder trabajar más rápido:

Code:

subset = dataset_corpus['train'].select(range(10000000))

Ahora nos descargamos las stopwords de nltk, que son palabras que no aportan información y que vamos a eliminar de los textos

Code:

import nltk

nltk.download('stopwords')

Output:

True

Ahora vamos a descargar los punkt de nltk, que es un tokenizer que nos va a permitir separar los textos en frases

Code:

nltk.download('punkt')

Output:

True

Creamos una función para limpiar los datos, esta función va a:

  • Pasar el texto a minúsculas
  • Eliminar las urls
  • Eliminar las menciones a redes sociales como @twitter p #hashtag
  • Eliminar los signos de puntuación
  • Eliminar los números
  • Eliminar las palabras cortas
  • Eliminar las stopwords

Como estamos usando un dataset de huggeface, los textos están en formato dict, así que devolvemos un diccionario.

Code:

def clean_text(sentence_batch):

# extrae el texto de la entrada

text_list = sentence_batch['text']

cleaned_text_list = []

for text in text_list:

# Convierte el texto a minúsculas

text = text.lower()

# Elimina URLs

text = re.sub(r'http\S+|www\S+|https\S+', '', text, flags=re.MULTILINE)

# Elimina las menciones @ y '#' de las redes sociales

text = re.sub(r'\@\w+|\#\w+', '', text)

# Elimina los caracteres de puntuación

text = strip_punctuation(text)

# Elimina los números

text = strip_numeric(text)

# Elimina las palabras cortas

text = strip_short(text,minsize=2)

# Elimina las palabras comunes (stop words)

stop_words = set(stopwords.words('spanish'))

word_tokens = word_tokenize(text)

filtered_text = [word for word in word_tokens if word not in stop_words]

cleaned_text_list.append(filtered_text)

# Devuelve el texto limpio

return {'text': cleaned_text_list}

Aplicamos la función a los datos

Code:

sentences_corpus = subset.map(clean_text, batched=True)

Output:

Map: 0%| | 0/10000000 [00:00<?, ? examples/s]

Vamos a guardar el dataset filtrado en un fichero para no tener que volver a ejecutar el proceso de limpieza

Code:

sentences_corpus.save_to_disk("sentences_corpus")

Output:

Saving the dataset (0/4 shards): 0%| | 0/15000000 [00:00<?, ? examples/s]

Para cargarlo podemos hacer

Code:

from datasets import load_from_disk

sentences_corpus = load_from_disk('sentences_corpus')

Ahora lo que vamos a tener es una lista de listas, donde cada lista es una frase tokenizada y sin stopwords. Es decir, tenemos una lista de frases, y cada frase es una lista de palabras. Vamos a ver cómo es:

Code:

for i in range(10):

print(f'La frase "{subset["text"][i]}" se convierte en la lista de palabras "{sentences_corpus["text"][i]}"')

Output:

La frase "¡Bienvenidos!" se convierte en la lista de palabras "['¡bienvenidos']"

La frase "Ir a los contenidos»" se convierte en la lista de palabras "['ir', 'contenidos', '»']"

La frase "= Contenidos =" se convierte en la lista de palabras "['contenidos']"

La frase "" se convierte en la lista de palabras "[]"

La frase "Portada" se convierte en la lista de palabras "['portada']"

La frase "Tercera Lengua más hablada en el mundo." se convierte en la lista de palabras "['tercera', 'lengua', 'hablada', 'mundo']"

La frase "La segunda en número de habitantes en el mundo occidental." se convierte en la lista de palabras "['segunda', 'número', 'habitantes', 'mundo', 'occidental']"

La frase "La de mayor proyección y crecimiento día a día." se convierte en la lista de palabras "['mayor', 'proyección', 'crecimiento', 'día', 'día']"

La frase "El español es, hoy en día, nombrado en cada vez más contextos, tomando realce internacional como lengua de cultura y civilización siempre de mayor envergadura." se convierte en la lista de palabras "['español', 'hoy', 'día', 'nombrado', 'cada', 'vez', 'contextos', 'tomando', 'realce', 'internacional', 'lengua', 'cultura', 'civilización', 'siempre', 'mayor', 'envergadura']"

La frase "Ejemplo de ello es que la comunidad minoritaria más hablada en los Estados Unidos es precisamente la que habla idioma español." se convierte en la lista de palabras "['ejemplo', 'ello', 'comunidad', 'minoritaria', 'hablada', 'unidos', 'precisamente', 'habla', 'idioma', 'español']"

Vamos a entrenar un modelo de embeddings que convertirá palabras en vectores. Para ello vamos a usar la librería gensim y su modelo Word2Vec.

Code:

dataset = sentences_corpus['text']

dim_embedding = 100

window_size = 5 # 5 palabras a la izquierda y 5 palabras a la derecha

min_count = 5 # Ignora las palabras con frecuencia menor a 5

workers = 4 # Número de hilos de ejecución

sg = 1 # 0 para CBOW, 1 para Skip-gram

model = Word2Vec(dataset, vector_size=dim_embedding, window=window_size, min_count=min_count, workers=workers, sg=sg)

Este modelo se ha entrenado en la CPU, ya qye gensim no tiene opción de realizar el entrenamiento en la GPU y aun así en mi ordenador ha tardado X minutos en entrenar el modelo. Aunque la dimensión del embedding que hemos elegido es de solo 100 (a diferencia del tamaño de los embeddings de openai que es de 1536), no es un tiempo demasiado grande, ya que el dataset tiene 10 millones de frases.

Los grandes modelos de lenguaje son entrenados con datasets de miles de millones de frases, por lo que es normal que el entrenamiento de un modelo de embeddings con un dataset de 10 millones de frases tarde unos minutos.

Una vez entrenado el modelo lo guardamos en un archivo para poder usarlo en el futuro

Code:

model.save('word2vec.model')

Si lo quisiéramos cargar en el futuro, podemos hacerlo con

Code:

model = Word2Vec.load('word2vec.model')

Vamos a ver las palabras más similares de algunas palabras

Code:

model.wv.most_similar('perro', topn=10)

Output:

[('gato', 0.7948548197746277),

('perros', 0.77247554063797),

('cachorro', 0.7638891339302063),

('hámster', 0.7540281414985657),

('caniche', 0.7514827251434326),

('bobtail', 0.7492328882217407),

('mastín', 0.7491254210472107),

('lobo', 0.7312178611755371),

('semental', 0.7292628288269043),

('sabueso', 0.7290207147598267)]

Code:

model.wv.most_similar('gato', topn=10)

Output:

[('conejo', 0.8148329854011536),

('zorro', 0.8109457492828369),

('perro', 0.7948548793792725),

('lobo', 0.7878773808479309),

('ardilla', 0.7860757112503052),

('mapache', 0.7817519307136536),

('huiña', 0.766639232635498),

('oso', 0.7656188011169434),

('mono', 0.7633568644523621),

('camaleón', 0.7623056769371033)]

Ahora vamos a ver el ejemplo en el que comprobamos la similitud de la palabra reina con el resultado de a la palabra rey le restamos la palabra hombre y le sumamos la palabra mujer

Code:

embedding_hombre = model.wv['hombre']

embedding_mujer = model.wv['mujer']

embedding_rey = model.wv['rey']

embedding_reina = model.wv['reina']

Code:

embedding = embedding_rey - embedding_hombre + embedding_mujer

Code:

from torch.nn.functional import cosine_similarity

embedding = torch.tensor(embedding).unsqueeze(0)

embedding_reina = torch.tensor(embedding_reina).unsqueeze(0)

similarity = cosine_similarity(embedding, embedding_reina, dim=1)

similarity

Output:

tensor([0.8156])

Como vemos hay bastante similitud

Vamos a visualizar los embeddings, para ello primero obtenemos los vectores y las palabras del modelo

Code:

embeddings = model.wv.vectors

words = list(model.wv.index_to_key)

Como la dimensión de los embeddings es 100, para poder visualizarlos en 2 o 3 dimensiones tenemos que reducir la dimensión. Para ello vamos a usar PCA (más rápido) o TSNE (más preciso) de sklearn

Code:

from sklearn.decomposition import PCA

dimmesions = 2

pca = PCA(n_components=dimmesions)

reduced_embeddings_PCA = pca.fit_transform(embeddings)

Code:

from sklearn.manifold import TSNE

dimmesions = 2

tsne = TSNE(n_components=dimmesions, verbose=1, perplexity=40, n_iter=300)

reduced_embeddings_tsne = tsne.fit_transform(embeddings)

Output:

[t-SNE] Computing 121 nearest neighbors...

[t-SNE] Indexed 493923 samples in 0.013s...

[t-SNE] Computed neighbors for 493923 samples in 377.143s...

[t-SNE] Computed conditional probabilities for sample 1000 / 493923

[t-SNE] Computed conditional probabilities for sample 2000 / 493923

[t-SNE] Computed conditional probabilities for sample 3000 / 493923

[t-SNE] Computed conditional probabilities for sample 4000 / 493923

[t-SNE] Computed conditional probabilities for sample 5000 / 493923

[t-SNE] Computed conditional probabilities for sample 6000 / 493923

[t-SNE] Computed conditional probabilities for sample 7000 / 493923

[t-SNE] Computed conditional probabilities for sample 8000 / 493923

[t-SNE] Computed conditional probabilities for sample 9000 / 493923

[t-SNE] Computed conditional probabilities for sample 10000 / 493923

[t-SNE] Computed conditional probabilities for sample 11000 / 493923

[t-SNE] Computed conditional probabilities for sample 12000 / 493923

[t-SNE] Computed conditional probabilities for sample 13000 / 493923

[t-SNE] Computed conditional probabilities for sample 14000 / 493923

[t-SNE] Computed conditional probabilities for sample 15000 / 493923

[t-SNE] Computed conditional probabilities for sample 16000 / 493923

[t-SNE] Computed conditional probabilities for sample 17000 / 493923

[t-SNE] Computed conditional probabilities for sample 18000 / 493923

[t-SNE] Computed conditional probabilities for sample 19000 / 493923

[t-SNE] Computed conditional probabilities for sample 20000 / 493923

[t-SNE] Computed conditional probabilities for sample 21000 / 493923

[t-SNE] Computed conditional probabilities for sample 22000 / 493923

...

[t-SNE] Computed conditional probabilities for sample 493923 / 493923

[t-SNE] Mean sigma: 0.275311

[t-SNE] KL divergence after 250 iterations with early exaggeration: 117.413788

[t-SNE] KL divergence after 300 iterations: 5.774648

Ahora los visualizamos en 2 dimensiones con matplotlib. Vamos a visualizar la reducción de dimensionalidad que hemos hecho con PCA y con TSNE

Code:

import matplotlib.pyplot as plt

plt.figure(figsize=(10, 10))

for i, word in enumerate(words[:200]): # Limitar a las primeras 200 palabras

plt.scatter(reduced_embeddings_PCA[i, 0], reduced_embeddings_PCA[i, 1])

plt.annotate(word, xy=(reduced_embeddings_PCA[i, 0], reduced_embeddings_PCA[i, 1]), xytext=(5, 2), textcoords='offset points', ha='right', va='bottom')

plt.title('Embeddings (PCA)')

plt.show()

Output:
Code:

plt.figure(figsize=(10, 10))

for i, word in enumerate(words[:200]): # Limitar a las primeras 200 palabras

plt.scatter(reduced_embeddings_tsne[i, 0], reduced_embeddings_tsne[i, 1])

plt.annotate(word, xy=(reduced_embeddings_tsne[i, 0], reduced_embeddings_tsne[i, 1]), xytext=(5, 2),

textcoords='offset points', ha='right', va='bottom')

plt.show()

Output:

Para usar modelos preentrenados de embeddings vamos a usar la librería transformers de huggingface. Para instalarla con conda usamos

conda install -c conda-forge transformers

Y para instalarla con pip usamos

pip install transformers

Con la tarea feature-extraction de huggingface podemos usar modelos preentrenados para obtener los embeddings de las palabras. Para ello primero impoertamos la librería necesaria

Code:

from transformers import pipeline

Vamos a obtener los embeddings de BERT

Code:

checkpoint = "bert-base-uncased"

feature_extractor = pipeline("feature-extraction",framework="pt",model=checkpoint)

Vamos a ver los embeddings de la palabra rey

Code:

embedding = feature_extractor("rey", return_tensors="pt").squeeze(0)

embedding.shape

Output:

torch.Size([3, 768])

Como vemos obtenemos un vector de 768 dimensiones, es decir, los embeddings de BERT tienen 768 dimensiones. Por otro lado vemos que tiene 3 vectores de embeddings, esto es porque BERT añade un token al principio y otro al final de la frase, por lo que a nosotros solo nos interesa el vector del medio

Vamos a volver a hacer el ejemplo en el que comprobamos la similitud de la palabra reina con el resultado de a la palabra rey le restamos la palabra hombre y le sumamos la palabra mujer

Code:

embedding_hombre = feature_extractor("man", return_tensors="pt").squeeze(0)[1]

embedding_mujer = feature_extractor("woman", return_tensors="pt").squeeze(0)[1]

embedding_rey = feature_extractor("king", return_tensors="pt").squeeze(0)[1]

embedding_reina = feature_extractor("queen", return_tensors="pt").squeeze(0)[1]

Code:

embedding = embedding_rey - embedding_hombre + embedding_mujer

Vamos a ver la similitud

Code:

import torch

from torch.nn.functional import cosine_similarity

embedding = torch.tensor(embedding).unsqueeze(0)

embedding_reina = torch.tensor(embedding_reina).unsqueeze(0)

similarity = cosine_similarity(embedding, embedding_reina, dim=1)

similarity.item()

Output:

0.742547333240509

Usando los embeddings de BERT también obtenemos un resultado muy cercano a 1

Continuar leyendo
DoLa-thumbnail

DoLa – Decoding by Contrasting Layers Improves Factuality in Large Language Models

¿Alguna vez has hablado con un LLM y te ha respondido algo que suena como si hubiera estado bebiendo café …
QLoRA_thumbnail_ES

QLoRA: Efficient Finetuning of Quantized LLMs

¡Hola a todos! 🤗 Hoy vamos a hablar de QLoRA, la técnica que te permitirá hacer que tus modelos de …
GPTQ-thumbnail-shot

GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers

¡Atención, desarrolladores! 🚨 ¿Tienes un modelo de lenguaje que es demasiado grande y pesado para tu aplicación? 🤯 ¡No te …
llm.int8()-thumbnail

llm.int8() – 8-bit Matrix Multiplication for Transformers at Scale

¡Prepárate para ahorrar espacio y acelerar tus modelos! 💥 En este post, voy a explorar el método llm.int8(), una técnica …
quantization-thumbnail

LLMs quantization

¡Imagina que tienes un modelo de lenguaje gigante que puede responder a cualquier pregunta, desde la capital de Francia hasta …
LoRA_thumbnail_ES

LoRA – low rank adaptation of large language models

¡Prepárate para llevar la adaptación de tus modelos al siguiente nivel con LoRA! 🚀 Esta técnica de adaptación de baja …
Resumen
Embeddings
Nombre del artículo
Embeddings
Descripción
En este post exploramos el mundo de los embeddings en la inteligencia artificial, una técnica esencial que transforma datos complejos, como texto, en formatos numéricos comprensibles para los modelos de aprendizaje automático. Descubre cómo los embeddings capturan significados y relaciones en los datos, facilitando el procesamiento del lenguaje, y comprende su impacto en el avance de la tecnología de los LLM
MaximoFN
MaximoFN
MaximoFN
Publisher Logo