1 From frequency-based to embedding-based representations

Bag-of-Words y TF-IDF representan el texto mediante frecuencias de palabras en vectores dispersos. En cambio, los embeddings utilizan vectores densos en un espacio compartido, lo que permite capturar similitud semántica basada en el contexto, donde palabras cercanas en el espacio vectorial tienden a tener significados similares, y no solo por si aparecen o no en el mismo documento.

2 Embedding model description

Se utiliza el modelo preentrenado glove-wiki-gigaword-50, disponible en la librería gensim, este presenta embeddings tipo GloVe entrenados con los corpus Wikipedia 2014 y Gigaword 5, en este modelo cada palabra se representa mediante un vector numérico de 50 dimensiones, es decir, una lista de 50 valores que codifican información semántica.

import sys
import numpy as np
import pandas as pd
import gensim
import gensim.downloader as api
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import TfidfVectorizer

np.random.seed(42)

model_name = "glove-wiki-gigaword-50"
model = api.load(model_name)

print("Python version:", sys.version)
## Python version: 3.10.20 | packaged by Anaconda, Inc. | (main, Mar 11 2026, 17:42:35) [MSC v.1942 64 bit (AMD64)]
print("gensim version:", gensim.__version__)
## gensim version: 4.4.0
print("numpy version:", np.__version__)
## numpy version: 2.2.6
print("pandas version:", pd.__version__)
## pandas version: 2.3.3
print("Model:", model_name)
## Model: glove-wiki-gigaword-50
print("Vector dimensionality:", model.vector_size)
## Vector dimensionality: 50

3 Vocabulary exploration

El modelo cuenta con un vocabulario de aproximadamente 400000 palabras, cualquier palabra que no se encuentre dentro de este vocabulario será considerada como fuera de vocabulario (OOV), esto puede afectar el análisis si el corpus incluye términos muy específicos o poco frecuentes.

print("Vocabulary size:", len(model))
## Vocabulary size: 400000
words = [
    "music", "song", "guitar", "data", "statistics",
    "model", "truck", "road", "market", "price",
    "goodbye", "trust", "heartbreak", "algorithm", "python"
]

vocab_check = pd.DataFrame({
    "word": words,
    "in_vocabulary": [word in model for word in words]
})

vocab_check
##           word  in_vocabulary
## 0        music           True
## 1         song           True
## 2       guitar           True
## 3         data           True
## 4   statistics           True
## 5        model           True
## 6        truck           True
## 7         road           True
## 8       market           True
## 9        price           True
## 10     goodbye           True
## 11       trust           True
## 12  heartbreak           True
## 13   algorithm           True
## 14      python           True
missing_words = vocab_check[vocab_check["in_vocabulary"] == False]["word"].tolist()
print("Missing words:", missing_words)
## Missing words: []

En este caso, todas las palabras seleccionadas se encuentran dentro del vocabulario del modelo (in_vocabulary = True), esto significa que el modelo presenta representaciones vectoriales para cada término, lo cual permite realizar el análisis de similitud semántica sin pérdida de información.

Por otro lado, en caso de que se hubiesen presentado OOV, esto indicaría que algunos términos no forman parte del conjunto de palabras con el que fue entrenado el modelo. Esto puede deberse a que son términos muy específicos de un dominio, poco frecuentes, recientes o con una forma distinta a la utilizada en el corpus original.

4 Nearest neighbors

Los vecinos más cercanos muestran que el modelo agrupa palabras según su contexto semántico. A continuación, se eligen las palabras: ‘music’, ‘data’ y y ‘truck’ y se determinan las 5 palabras más similares según similitud del coseno.

target_words = ["music", "data", "truck"]

neighbors_results = []

for word in target_words:
    similar_words = model.most_similar(word, topn=5)
    for neighbor, similarity in similar_words:
        neighbors_results.append({
            "target_word": word,
            "neighbor": neighbor,
            "cosine_similarity": round(similarity, 4)
        })

neighbors_table = pd.DataFrame(neighbors_results)
neighbors_table
##    target_word      neighbor  cosine_similarity
## 0        music       musical             0.8854
## 1        music           pop             0.8682
## 2        music         dance             0.8531
## 3        music         songs             0.8526
## 4        music     recording             0.8392
## 5         data   information             0.8330
## 6         data      tracking             0.8125
## 7         data      database             0.8122
## 8         data      analysis             0.7967
## 9         data  applications             0.7924
## 10       truck           car             0.9209
## 11       truck       vehicle             0.8651
## 12       truck        trucks             0.8635
## 13       truck       tractor             0.8452
## 14       truck        parked             0.8431

Como se observa en la tabla, las palabras cercanas a ‘music’ suelen estar relacionadas con canciones, artistas o interpretación. Las palabras cercanas a ‘data’ suelen asociarse con información, sistemas o análisis. Las palabras cercanas a ‘truck’ están relacionadas con transporte, vehículos y carreteras.

5 Analogy reasoning

El razonamiento por analogías evalúa si el modelo de embeddings captura relaciones semánticas entre palabras. A continuación se hace uso de dos analogías, la primera analogía explora una relación de género, mientras que la segunda explora una categoría religiosa.

analogies = [
    {
        "analogy": "king - man + woman",
        "positive": ["king", "woman"],
        "negative": ["man"]
    },
    {
        "analogy": "religion - catholic + jewish",
        "positive": ["religion", "jewish"],
        "negative": ["catholic"]
    }
]

analogy_results = []

for item in analogies:
    result = model.most_similar(
        positive=item["positive"],
        negative=item["negative"],
        topn=5
    )
    
    for predicted_word, similarity in result:
        analogy_results.append({
            "analogy": item["analogy"],
            "predicted_word": predicted_word,
            "cosine_similarity": round(similarity, 4)
        })

analogy_table = pd.DataFrame(analogy_results)
analogy_table
##                         analogy predicted_word  cosine_similarity
## 0            king - man + woman          queen             0.8524
## 1            king - man + woman         throne             0.7664
## 2            king - man + woman         prince             0.7592
## 3            king - man + woman       daughter             0.7474
## 4            king - man + woman      elizabeth             0.7460
## 5  religion - catholic + jewish        judaism             0.7307
## 6  religion - catholic + jewish        culture             0.7001
## 7  religion - catholic + jewish           jews             0.6929
## 8  religion - catholic + jewish      religious             0.6782
## 9  religion - catholic + jewish          arabs             0.6661

Los resultados de las analogías muestran que el modelo es capaz de capturar relaciones semánticas mediante operaciones vectoriales. En el caso de la analogía king - man + woman, el modelo predice correctamente la palabra ‘queen’ como el resultado más cercano, lo que indica que ha aprendido una relación de género entre los términos. Adicionalmente, aparecen palabras relacionadas con la realeza como ‘throne’, ‘prince’ y ‘daughter’, lo que refuerza la coherencia semántica del resultado.

De manera similar, en la analogía religion - catholic + jewish, al restar los componentes específicos del catolicismo y sumar la carga semántica de lo judío, el modelo identifica correctamente el sistema de creencias resultante (“judaism”)

analogies = [
    {
        "analogy": "king - woman + man",
        "positive": ["king", "man"],
        "negative": ["woman"]
    },
    {
        "analogy": "religion - jewish + catholic",
        "positive": ["religion", "catholic"],
        "negative": ["jewish"]
    }
]

analogy_results = []

for item in analogies:
    result = model.most_similar(
        positive=item["positive"],
        negative=item["negative"],
        topn=5
    )
    
    for predicted_word, similarity in result:
        analogy_results.append({
            "analogy": item["analogy"],
            "predicted_word": predicted_word,
            "cosine_similarity": round(similarity, 4)
        })

analogy_table = pd.DataFrame(analogy_results)
analogy_table
##                         analogy predicted_word  cosine_similarity
## 0            king - woman + man          ruler             0.7396
## 1            king - woman + man         prince             0.7367
## 2            king - woman + man             ii             0.7365
## 3            king - woman + man           lord             0.7310
## 4            king - woman + man            iii             0.7169
## 5  religion - jewish + catholic       theology             0.7264
## 6  religion - jewish + catholic        beliefs             0.7218
## 7  religion - jewish + catholic      teachings             0.7172
## 8  religion - jewish + catholic    catholicism             0.7160
## 9  religion - jewish + catholic          faith             0.7140

Al invertir las operaciones, la analogía king - woman + man refuerza la dimensión de poder masculino al predecir títulos como ‘ruler’ y ‘prince’, junto con sufijos de linaje histórico (‘ii’, ‘iii’). En el segundo ejemplo, los resultados de religion - jewish + catholic revelan una transición semántica hacia lo institucional y dogmático, prediciendo términos como ‘theology’, ‘beliefs’ y ‘teachings’, a diferencia de la operación inversa, que asociaba lo judío a la identidad cultural, el modelo vincula el catolicismo con la estructura formal de la fe.

En conjunto, el razonamiento de analogías permite observar la capacidad del modelo para desplazar conceptos entre dimensiones de género e institucionalidad con una similitud de coseno superior a 0.71. lo cual indica que los embeddings han codificado con éxito relaciones complejas que van más allá de la simple coincidencia de palabras.

6 Word similarity matrix

matrix_words = ["music", "song", "guitar", "data", "truck", "road"]

vectors = np.array([model[word] for word in matrix_words])
similarity_matrix = cosine_similarity(vectors)

similarity_df = pd.DataFrame(
    similarity_matrix,
    index=matrix_words,
    columns=matrix_words
)

similarity_df.round(4)
##          music    song  guitar    data   truck    road
## music   1.0000  0.7985  0.7736  0.3565  0.1565  0.2536
## song    0.7985  1.0000  0.7063  0.2311  0.1944  0.3224
## guitar  0.7736  0.7063  1.0000  0.1895  0.2238  0.2147
## data    0.3565  0.2311  0.1895  1.0000  0.3049  0.2500
## truck   0.1565  0.1944  0.2238  0.3049  1.0000  0.5338
## road    0.2536  0.3224  0.2147  0.2500  0.5338  1.0000

Palabras como ‘music’, ‘song’ y ‘guitar’ presentan una mayor similitud porque pertenecen al mismo campo semántico.
En contraste, palabras como ‘data’ y ‘guitar’ muestran menor similitud debido a que aparecen en contextos distintos.
La matriz confirma que la similitud en embeddings se basa en el significado y en el uso contextual de las palabras.

7 From words to text similarity

Los resultados de similitud basados en embeddings muestran cómo las oraciones pueden compararse a partir de su significado global, y no únicamente por las palabras exactas que contienen. A continuación, se usan los primeros tres documentos trabajados en la actividad de Text to Vector-base similarity:

sentences = [
    "Where's the good in goodbye?, Where'sthe nice in nice try?",
    "where's the us in trust gone?, Where's the soul in soldier on?",
    "Now I'm the low in lonely,'Cause I don't own you only"
]

sentences_df = pd.DataFrame({
    "sentence_id": ["S1", "S2", "S3"],
    "sentence": sentences
})

sentences_df
##   sentence_id                                           sentence
## 0          S1  Where's the good in goodbye?, Where'sthe nice ...
## 1          S2  where's the us in trust gone?, Where's the sou...
## 2          S3  Now I'm the low in lonely,'Cause I don't own y...
def sentence_embedding(sentence, model):
    tokens = sentence.lower().split()
    valid_tokens = [token for token in tokens if token in model]
    
    if len(valid_tokens) == 0:
        return np.zeros(model.vector_size)
    
    return np.mean([model[token] for token in valid_tokens], axis=0)

sentence_vectors = np.array([
    sentence_embedding(sentence, model) for sentence in sentences
])

text_similarity_matrix = cosine_similarity(sentence_vectors)

text_similarity_df = pd.DataFrame(
    text_similarity_matrix,
    index=["S1", "S2", "S3"],
    columns=["S1", "S2", "S3"]
)

text_similarity_df.round(4)
##         S1      S2      S3
## S1  1.0000  0.8736  0.9397
## S2  0.8736  1.0000  0.9110
## S3  0.9397  0.9110  1.0000

El par con mayor similitud (S1-S3) corresponde a las oraciones que comparten un contexto emocional similar, relacionado con temas de ruptura, soledad y sentimientos negativos, lo que indica que el modelo logra capturar este campo semántico común.

pairs = []

for i in range(len(sentences)):
    for j in range(i + 1, len(sentences)):
        pairs.append({
            "pair": f"S{i+1} - S{j+1}",
            "embedding_similarity": round(text_similarity_matrix[i, j], 4)
        })

pairs_df = pd.DataFrame(pairs)

most_similar = pairs_df.loc[pairs_df["embedding_similarity"].idxmax()]
least_similar = pairs_df.loc[pairs_df["embedding_similarity"].idxmin()]

pairs_df
##       pair  embedding_similarity
## 0  S1 - S2                0.8736
## 1  S1 - S3                0.9397
## 2  S2 - S3                0.9110

Por otro lado, el par con menor similitud corresponde a aquellas oraciones que, aunque pertenecen al mismo corpus, presentan menor cercanía en términos de las palabras que las componen o de su construcción semántica. Esto refleja que, incluso dentro de un mismo tema general, existen variaciones en el uso del lenguaje que afectan la representación vectorial.

print("Most similar pair:")
## Most similar pair:
print(most_similar)
## pair                    S1 - S3
## embedding_similarity     0.9397
## Name: 1, dtype: object
print("\nLeast similar pair:")
## 
## Least similar pair:
print(least_similar)
## pair                    S1 - S2
## embedding_similarity     0.8736
## Name: 0, dtype: object

También se aplica TF-IDF:

tfidf = TfidfVectorizer()
tfidf_matrix = tfidf.fit_transform(sentences)

tfidf_similarity = cosine_similarity(tfidf_matrix)

tfidf_similarity_df = pd.DataFrame(
    tfidf_similarity,
    index=["S1", "S2", "S3"],
    columns=["S1", "S2", "S3"]
)

tfidf_similarity_df.round(4)
##         S1      S2      S3
## S1  1.0000  0.3808  0.1022
## S2  0.3808  1.0000  0.1420
## S3  0.1022  0.1420  1.0000

En comparación con TF-IDF, se observa que este último depende principalmente del solapamiento exacto de palabras entre oraciones. Como resultado, las similitudes calculadas con TF-IDF resultan bajas incluso cuando las oraciones comparten un significado similar. En cambio, los embeddings permiten identificar relaciones semánticas más profundas, ya que consideran el contexto en el que las palabras suelen aparecer, en este caso el par más similar resulta ser S1-S2.

En conjunto, estos resultados evidencian que los embeddings ofrecen una representación más flexible y semánticamente rica del texto, permitiendo comparar oraciones incluso cuando no comparten exactamente el mismo vocabulario.

8 Conceptual reflection

Los word embeddings permiten capturar relaciones semánticas que TF-IDF no puede representar directamente. Mientras TF-IDF se enfoca en la frecuencia y relevancia de las palabras dentro de un documento, los embeddings representan las palabras según su uso en contexto, lo que permite identificar similitudes incluso cuando los textos utilizan vocabulario diferente.

Esto hace que los embeddings sean especialmente útiles cuando se desea comparar el significado de los textos más allá de la coincidencia exacta de términos. Sin embargo, una limitación importante de modelos como Word2Vec y GloVe es que asignan una única representación vectorial a cada palabra, independientemente del contexto en el que aparezca. Esto implica que el modelo no puede diferenciar completamente entre distintos significados de una misma palabra, como en el caso de ‘bank’, que puede referirse a una entidad financiera o a la orilla de un río.

En este sentido, aunque los embeddings ofrecen una representación más rica y flexible del lenguaje, también presentan limitaciones en términos de sensibilidad al contexto. A pesar de ello, resultan especialmente útiles en tareas como análisis de similitud semántica, sistemas de recomendación, clustering y clasificación de texto.

9 Reproducibilidad y versiones de librerías

Todos los resultados se generan automáticamente cuando se compila el documento

import sys
import pandas as pd
import numpy as np
import gensim
import sklearn

pd.DataFrame({
    "Library": ["Python", "pandas", "numpy", "gensim", "scikit-learn"],
    "Version": [
        sys.version.split()[0],
        pd.__version__,
        np.__version__,
        gensim.__version__,
        sklearn.__version__
    ]
})
##         Library  Version
## 0        Python  3.10.20
## 1        pandas    2.3.3
## 2         numpy    2.2.6
## 3        gensim    4.4.0
## 4  scikit-learn    1.7.2