14

Embeddings: el significado como geometría

¿Cómo convierte una máquina el significado en distancias?

11 minRuta ejecutiva

Embeddings: el significado como geometría

Con voz. Puedes pausar y saltar a cualquier paso.

Lección completa

Subtítulos

Un cliente escribe «quiero mi dinero de vuelta», y el artículo correcto habla de «reembolso». No comparten ni una palabra. ¿Cómo puede una máquina ver que significan lo mismo?

Voz

Sin voz: la lección avanza sola con subtítulos.

La voz de esta lección está en preparación: por ahora avanza sola con subtítulos.

0:00 / 0:00

Qué lograrás

  • Explicar por qué un vector denso y aprendido representa el significado mejor que una casilla por palabra (one-hot).
  • Calcular la similitud coseno entre dos vectores e interpretar vecinos, mapas 2D y aritmética de significados, con sus límites.
  • Reconocer cómo los sesgos del texto viajan al espacio de vectores y cómo evaluar una búsqueda semántica con casos propios.

Ideas clave

  1. Un embedding es un vector denso y aprendido: una fila de una tabla que se ajusta con los datos, en lugar de una casilla fija por palabra.
  2. Lo que aparece en contextos parecidos acaba con vectores parecidos: el significado se vuelve ángulo y distancia.
  3. La similitud coseno compara direcciones, no longitudes. Un mapa 2D es una proyección y pierde información: sirve para explorar, no para medir.
  4. La aritmética de significados es real pero frágil: hay que excluir las palabras de entrada y funciona mejor con relaciones limpias, como país y capital.
  5. Los vectores heredan los sesgos del texto. La búsqueda semántica rescata paráfrasis, y su calidad se mide con tus propios casos.

Lo que mucha gente cree

Creencia común «Rey − hombre + mujer = reina» demuestra que el modelo entiende qué es la realeza y qué es el género.

Lo correcto Es una regularidad geométrica real, pero frágil: solo sale excluyendo las palabras de entrada, falla con muchas relaciones, y los mismos vectores arrastran los sesgos del texto. Medir no es entender.

Compruébalo

Cuatro preguntas. Sin nota ni cuenta: sirven para ver si la idea quedó.

Concepto¿Por qué un vector denso y aprendido representa mejor el significado que un vector one-hot?
CálculoDos vectores son a = (3, 4) y b = (4, 3). ¿Cuánto vale su similitud coseno?
ConceptoUn modelo entrenado con texto de la web ubica «enfermera» más cerca de las palabras femeninas que «ingeniero». ¿Cuál es la mejor explicación?
AplicaciónTu equipo quiere un buscador semántico para el soporte y tres proveedores prometen «entender el significado». ¿Qué haces primero?

Por dentroPara quien quiere las fórmulas y el código. No hace falta para seguir el curso.

Un embedding es una fila de una matriz

Sea VV el tamaño del vocabulario y dd la dimensión de los vectores. La tabla es una matriz E∈RV×dE\in\mathbb{R}^{V\times d}. La palabra de índice ii es el vector one-hot xi∈{0,1}Vx_i\in\{0,1\}^V (un uno en la posición ii, ceros en el resto) y su embedding es

ei=xi⊤E=Ei,:e_i = x_i^{\top}E = E_{i,:}

Multiplicar por un one-hot equivale a elegir una fila: es una capa lineal sin activación (lección 7) cuyos V⋅dV\cdot d pesos se aprenden. Con V=400 000V=400\,000 y d=100d=100 son 40 000 00040\,000\,000 de números. En la práctica no se multiplica: se consulta la fila. El mismo mecanismo abre los modelos de lenguaje: en GPT-2 pequeño la tabla es de 50 257×768≈38.650\,257\times 768\approx 38.6 millones de parámetros (lecciones 20 y 22).

Similitud coseno

cos⁡(a,b)=a⋅b∥a∥ ∥b∥=cos⁡θ∈[−1,1]\cos(a,b)=\frac{a\cdot b}{\lVert a\rVert\,\lVert b\rVert}=\cos\theta\in[-1,1]

Si los dos vectores tienen longitud 1, cos⁡(a,b)=a⋅b\cos(a,b)=a\cdot b y ∥a−b∥2=2−2cos⁡(a,b)\lVert a-b\rVert^2=2-2\cos(a,b): ordenar por coseno y ordenar por distancia euclídea da el mismo orden. Ejemplo: a=(3,4)a=(3,4) y b=(4,3)b=(4,3) dan cos⁡=12+125⋅5=0.96\cos=\frac{12+12}{5\cdot 5}=0.96, un ángulo de unos 16∘16^\circ. Con cos⁡=0.88\cos=0.88 ("perro" y "gato") el ángulo es arccos⁡0.88≈28∘\arccos 0.88\approx 28^\circ.

¿De dónde salen los números?

Skip-gram (Mikolov et al., 2013) maximiza la probabilidad promedio de las palabras vecinas de cada palabra wtw_t, con una ventana de tamaño cc:

1T∑t=1T  ∑−c≤j≤c,  j≠0log⁡p(wt+j∣wt),p(o∣i)=exp⁡ ⁣(vo′⊤ vi)∑w=1Vexp⁡ ⁣(vw′⊤ vi)\frac1T\sum_{t=1}^{T}\;\sum_{-c\le j\le c,\;j\ne 0}\log p(w_{t+j}\mid w_t),\qquad p(o\mid i)=\frac{\exp\!\big(v'^{\top}_{o}\,v_i\big)}{\sum_{w=1}^{V}\exp\!\big(v'^{\top}_{w}\,v_i\big)}

Cada palabra tiene un vector de entrada vv (su embedding) y uno de salida v′v'. El denominador recorre todo el vocabulario, y por eso el artículo propone aproximaciones, como el muestreo negativo. GloVe (Pennington et al., 2014) ajusta en cambio

J=∑i,j=1Vf(Xij) (wi⊤w~j+bi+b~j−log⁡Xij)2,f(x)={(x/100)3/4x<1001si noJ=\sum_{i,j=1}^{V} f(X_{ij})\,\big(w_i^{\top}\tilde w_j+b_i+\tilde b_j-\log X_{ij}\big)^2,\qquad f(x)=\begin{cases}(x/100)^{3/4}&x<100\\ 1&\text{si no}\end{cases}

donde XijX_{ij} cuenta cuántas veces aparece jj cerca de ii en el corpus, y se entrena con AdaGrad. Dos caminos, una misma idea: las palabras que comparten contextos acaban con vectores parecidos.

La aritmética, paso a paso

La pregunta «aa es a a∗a^* como bb es a ...» se responde con

x∗=arg⁡max⁡x∉{a, a∗, b} cos⁡ ⁣(x,  a∗−a+b)x^{*}=\arg\max_{x\notin\{a,\,a^*,\,b\}}\ \cos\!\big(x,\;a^*-a+b\big)

Con vectores de longitud 1, ∥a∗−a+b∥\lVert a^*-a+b\rVert no depende de xx, así que esto equivale a maximizar cos⁡(x,a∗)−cos⁡(x,a)+cos⁡(x,b)\cos(x,a^*)-\cos(x,a)+\cos(x,b). La exclusión no es un detalle: Linzen (2016) midió que, sin ella, la más cercana era bb en el 93 % de los casos y a∗a^* en el 5 %. En esta lección, con los 100 números de GloVe y eligiendo entre sus 400 000 palabras:

Preguntas Con exclusión Sin exclusión La más cercana era una de las 3 de entrada
Capital–país (342) 80.1 % (274) 68.1 % 19.3 %
Género (90) 85.6 % (77) 55.6 % 41.1 %

Los pares los elegí yo y son relaciones limpias; el conjunto de Mikolov et al. (19 544 preguntas, 14 tipos, y solo cuenta la palabra exacta, no sinónimos) es más difícil: su mejor modelo llegó a 65.6 %. Con solo las 880 palabras de la lección el examen es mucho más fácil (94.4 % y 85.6 %): hay menos candidatos entre los que equivocarse. Linzen añade que el método mezcla la consistencia del desplazamiento con la estructura de vecindad: no mide solo «relaciones».

Cómo se mide el sesgo en la lección

Con FF = {mujer, niña, madre, esposa, hermana, hija} y MM = {hombre, niño, padre, esposo, hermano, hijo} (en inglés en los datos), el puntaje de una palabra ww es

s(w)=1∣F∣∑f∈Fcos⁡(w,f)  −  1∣M∣∑m∈Mcos⁡(w,m)s(w)=\frac{1}{|F|}\sum_{f\in F}\cos(w,f)\;-\;\frac{1}{|M|}\sum_{m\in M}\cos(w,m)

Positivo es más cerca de lo femenino. Se usan conjuntos y no un par porque «man» tiene varios sentidos. Es una versión simplificada de las pruebas de asociación de Caliskan et al. (2017). Resultado con las 52 profesiones del vocabulario: de −0.16-0.16 («boss») a +0.10+0.10 («receptionist»); 26 objetos sin género (mesa, árbol, río…) quedan entre −0.05-0.05 y +0.03+0.03. La correlación de Spearman con el puntaje «she» − «he» en GloVe completo es 0.91. El puntaje mezcla género con tema (en el texto, lo femenino aparece más junto a «vestido» o «jabón»): sirve para ver la pendiente, no para dar un veredicto sobre una persona. Si a cada vector se le resta su componente en g=Fˉ−Mˉg=\bar F-\bar M (normalizado), el puntaje en ese eje vale 0 por construcción; aun así, adivinar si cada profesión estaba por encima o por debajo de la mediana del puntaje original, con el centroide más cercano por coseno y dejando una fuera cada vez, acierta 41 de 52 antes y 35 de 52 después (azar: 26). Es un experimento mínimo, con pocas palabras, en el espíritu del de Gonen y Goldberg (2019), quienes mostraron que los métodos para «desagregar» el sesgo en gran medida lo esconden, no lo quitan.

Búsqueda semántica

El promedio de un texto con palabras w1,…,wnw_1,\dots,w_n es q=1n∑kewkq=\frac1n\sum_k e_{w_k}; se ordenan los documentos por cos⁡(q,Dj)\cos(q,D_j). Comparar contra NN documentos cuesta O(N⋅d)O(N\cdot d) por consulta, y con millones se usan índices aproximados. Los vectores de oraciones (Reimers y Gurevych, 2019) se calculan una sola vez por documento: encontrar el par más parecido entre 10 000 oraciones con BERT exige unos 50 millones de inferencias (unas 65 horas); con vectores de oraciones, unos 5 segundos. La tercera columna de la escena usa paraphrase-multilingual-MiniLM-L12-v2 (Apache-2.0, 384 dimensiones, más de 50 idiomas; Reimers y Gurevych, 2020), con promedio de los vectores de salida y coseno. Sus puntajes se calcularon fuera de línea para las 36 preguntas de prueba.

Con 36 preguntas, 19 aciertos son 52.8 % con un intervalo de Wilson al 95 % de 37 % a 68 %: un margen de unos ±15 puntos. Por eso la lección 34 insiste en el tamaño de la muestra.

En código

Con vectores de juguete (números inventados) se ve el mecanismo completo:

import numpy as np

# Tabla de embeddings de juguete: una fila por palabra, 3 números cada una (inventados; un modelo real usa cientos).
palabras = ["rey", "reina", "hombre", "mujer", "pan"]
E = np.array([
    [4.0, 3.0,  1.0],   # rey
    [4.0, 0.5, -2.0],   # reina
    [4.0, 0.0,  1.0],   # hombre
    [4.0, 0.0, -1.0],   # mujer
    [0.0, 0.5,  3.0],   # pan
])
E = E / np.linalg.norm(E, axis=1, keepdims=True)   # longitud 1: el producto punto es el coseno
fila = {w: i for i, w in enumerate(palabras)}

def coseno(a, b):
    return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b)))

def cercanas(v, excluir=()):
    pares = [(w, round(coseno(v, E[i]), 2)) for w, i in fila.items() if w not in excluir]
    return sorted(pares, key=lambda p: -p[1])[:3]

print(round(coseno(E[fila["rey"]], E[fila["reina"]]), 2))          # rey y reina
v = E[fila["rey"]] - E[fila["hombre"]] + E[fila["mujer"]]           # rey - hombre + mujer
print(cercanas(v))                                                   # sin excluir
print(cercanas(v, excluir=["rey", "hombre", "mujer"]))               # excluyendo las tres de entrada
# 0.68
# [('rey', 0.89), ('reina', 0.87), ('mujer', 0.81)]
# [('reina', 0.87), ('pan', -0.18)]

Y con los vectores reales de GloVe (el archivo glove.6B.100d.txt sale de la página de Stanford, ver las fuentes; leerlo tarda unos segundos):

import numpy as np

palabras, filas = [], []
with open("glove.6B.100d.txt", encoding="utf8") as f:
    for linea in f:
        w, _, resto = linea.partition(" ")
        palabras.append(w)
        filas.append(np.asarray(resto.split(), dtype=np.float32))
E = np.stack(filas)
E /= np.linalg.norm(E, axis=1, keepdims=True)   # longitud 1: el producto punto es el coseno
idx = {w: i for i, w in enumerate(palabras)}

def cercanas(v, k=5, excluir=()):
    s = E @ (v / np.linalg.norm(v))
    for w in excluir:
        s[idx[w]] = -1
    top = np.argsort(-s)[:k]
    return [(palabras[i], round(float(s[i]), 2)) for i in top]

print(cercanas(E[idx["dog"]], 5, excluir=["dog"]))
v = E[idx["king"]] - E[idx["man"]] + E[idx["woman"]]
print(cercanas(v, 3))
print(cercanas(v, 3, excluir=["king", "man", "woman"]))
# [('cat', 0.88), ('dogs', 0.83), ('pet', 0.74), ('puppy', 0.72), ('horse', 0.71)]
# [('king', 0.84), ('queen', 0.77), ('monarch', 0.68)]
# [('queen', 0.77), ('monarch', 0.68), ('throne', 0.68)]

De dónde salen los datos de esta lección

Vectores: GloVe 6B de 100 dimensiones (Wikipedia 2014 y Gigaword 5, 6 mil millones de palabras, 400 000 de vocabulario, solo inglés), publicado por Stanford bajo la licencia Public Domain Dedication and License v1.0, que permite redistribuir. Se normalizan a longitud 1 y se guardan en 8 bits (error de coseno máximo 0.0095). El mapa es t-SNE (van der Maaten y Hinton, 2008) con perplejidad 20, métrica coseno y semilla 0: Wattenberg et al. (2016) advierten que en un t-SNE el tamaño de los grupos y la distancia entre ellos pueden no significar nada. Las 10 frases de soporte y las 36 preguntas de prueba las escribió el autor de la lección: son ilustrativas. Todo se regenera con los scripts de la carpeta de la lección (build-vectors.py, eval-search.py, eval-analogies.py, measure-data.py).

Lo que viene

Los vectores de esta lección son estáticos: una palabra, un vector. La atención (lección 21) y el transformer (lección 22) producen un vector distinto para cada palabra según la frase. La lección 15 pregunta si, además de representar lo que existe, se puede generar algo nuevo; la 18 une vectores de imágenes y de texto en un mismo espacio, y la 31 usa la búsqueda semántica para dar conocimiento propio a un modelo.

Lente de decisión

Qué significa esta idea si tu trabajo es decidir, no programar.

  • Búsqueda semántica y bases vectoriales: cada documento se convierte en vector una vez y cada pregunta, al momento. Pregunta qué modelo de embeddings usan, cuántas dimensiones, en qué idiomas y con qué licencia: si cambia el modelo, hay que recalcular todo el índice.
  • Mide la calidad con tus propios casos: unas decenas de preguntas reales, cada una con su documento correcto, y cuenta cuántas veces sale primero o entre los cinco primeros. Con treinta y seis casos el margen es de unos quince puntos; con más, la comparación se afina.
  • Los embeddings heredan los sesgos del texto. Si ordenan personas (currículos, candidatos, clientes), pide cómo se midió el sesgo con datos de ese uso; «lo corregimos» no basta, porque quitar una dirección suele esconderlo, no eliminarlo.
  • Señales de alerta: un mapa 2D presentado como prueba de calidad, una demo con preguntas elegidas a mano, la frase «entiende el significado» sin resultados medidos, o un modelo en inglés aplicado a textos en español sin comprobarlo.

Términos de la lección

Transcripción completa
ComentariosConversación pública de esta lección: dudas, datos por revisar, ideas.

Los comentarios viven en GitHub Discussions, en el repositorio público del curso; para escribir se necesita una cuenta de GitHub. Se cargan desde giscus.app al abrir esta sección. Las personas que revisan el curso los leen, y si un cambio se acepta, se aplica en los dos idiomas. Abrir el hilo en GitHub

Fuentes