# Convertir palabras en números que signifiquen algo

URL canónica: https://missyera.com/guias/deep-learning-desde-cero/texto-y-embeddings/
Última actualización: 2026-11-14
Autora: Gera Flores (Miss Yera)

> **Respuesta corta**
>
> La primera vez que vi este número me pareció mentira. Con one-hot, bodega y minimarket se parecen 0,000, exactamente igual que bodega y arroz: para la máquina, dos palabras del mismo mundo son tan distintas como dos que no tienen nada que ver. Un embedding arregla eso construyendo cada palabra a partir de con quién aparece 🔤

Capítulo 13 de 22 del libro [Deep learning desde cero](https://missyera.com/guias/deep-learning-desde-cero.md).

En el capítulo 11 el dato tenía forma de rejilla. Aquí tiene forma de **secuencia**, y antes de meterla en cualquier red hay un problema que resolver: una red multiplica números y "bodega" no se multiplica 🔤

Antes de seguir, piénsalo tú: **¿cómo le explicarías a una máquina que bodega y minimarket se parecen, y que arroz no tiene nada que ver con ninguna de las dos?** 🔤

## Lo que veníamos haciendo, y por qué no basta

En todo el libro de machine learning convertimos las palabras con `OneHotEncoder`:

- Una columna por valor
- Un 1 en la que toca
- Ceros en las demás

```
import numpy as np

palabras = ['bodega', 'minimarket', 'mayorista', 'arroz', 'aceite']
posicion = {p: i for i, p in enumerate(palabras)}

def one_hot(p):
    v = np.zeros(len(palabras))
    v[posicion[p]] = 1
    return v

print('bodega    :', one_hot('bodega'))
print('minimarket:', one_hot('minimarket'))
print()
print('parecido bodega y minimarket:', float(one_hot('bodega') @ one_hot('minimarket')))
print('parecido bodega y arroz     :', float(one_hot('bodega') @ one_hot('arroz')))
```

```text
bodega    : [1. 0. 0. 0. 0.]
minimarket: [0. 1. 0. 0. 0.]

parecido bodega y minimarket: 0.0
parecido bodega y arroz     : 0.0
```

Los dos parecidos dan **0,0**. Exactamente el mismo número.

O sea que para el modelo, una bodega se parece a un minimarket lo mismo que se parece a un saco de arroz: nada. Cada palabra es una isla 🏝️

Con cuatro segmentos eso se aguanta, porque el modelo aprende cada uno por separado y hay datos de sobra. Con un vocabulario de 50.000 palabras de texto es imposible: no vas a tener ejemplos suficientes de cada una, y lo que aprendas sobre "retraso" no le servirá de nada a "tardanza".

## La idea: mirar con quién anda

Un **embedding** es una lista corta de números que representa una palabra. Y la idea para construirlo es de 1957, mucho antes que las redes: una palabra se define por las palabras que la rodean.

Vamos con un puñado de comentarios de clientes. Los escribí yo para este capítulo, con la forma de los que llegan a una distribuidora:

```
COMENTARIOS = [
    'el pedido llego completo y a tiempo',
    'el pedido llego incompleto y con retraso',
    'la bodega pidio arroz y azucar',
    'el minimarket pidio aceite y azucar',
    'el mayorista pidio arroz en cantidad',
    'el pedido de la bodega llego tarde',
    'el pedido del minimarket llego tarde',
    'la bodega reclamo por el retraso del pedido',
    'el minimarket reclamo por el aceite roto',
    'el mayorista reclamo por la factura',
    'llego todo completo sin reclamo',
    'el arroz llego en buen estado',
    'el aceite llego en buen estado',
    'el azucar llego roto y con retraso',
    'la bodega pago la factura a tiempo',
    'el minimarket pago la factura con retraso',
    'el mayorista pago la factura a tiempo',
    'el vendedor visito la bodega el lunes',
    'el vendedor visito el minimarket el martes',
    'el vendedor visito al mayorista el lunes',
    'la bodega compro arroz el lunes',
    'el minimarket compro aceite el martes',
    'el mayorista compro arroz y aceite',
    'nadie visito la bodega esta semana',
    'el pedido de arroz llego completo',
    'el pedido de aceite llego incompleto',
]

vocabulario = sorted({p for f in COMENTARIOS for p in f.split()})
lugar = {p: i for i, p in enumerate(vocabulario)}
print('frases:', len(COMENTARIOS), ' palabras distintas:', len(vocabulario))
```

```text
frases: 26  palabras distintas: 41
```

## Contar quién aparece al lado de quién

```
V = len(vocabulario)
juntas = np.zeros((V, V))
VENTANA = 2                      # dos palabras a cada lado

for f in COMENTARIOS:
    ps = f.split()
    for i, p in enumerate(ps):
        for j in range(max(0, i - VENTANA), min(len(ps), i + VENTANA + 1)):
            if i != j:
                juntas[lugar[p], lugar[ps[j]]] += 1

for p in ['bodega', 'arroz']:
    vecinas = np.argsort(-juntas[lugar[p]])[:4]
    print(f'{p:10} aparece más al lado de:',
          ', '.join(f'{vocabulario[i]} ({int(juntas[lugar[p], i])})' for i in vecinas))
```

```text
bodega     aparece más al lado de: la (8), arroz (2), visito (2), de (1)
arroz      aparece más al lado de: bodega (2), en (2), el (2), compro (2)
```

Esa matriz es todo lo que sabemos del idioma: quién anda con quién. No hay gramática, ni diccionario, ni nadie que haya explicado qué es una bodega 📋

![Palabras colocadas como puntos en dos dimensiones: los animales juntos en una zona, las ciudades en otra, y las cuatro palabras rey, reina, hombre y mujer formando un paralelogramo, de modo que rey menos hombre más mujer cae en reina.](https://missyera.com/static/diagramas/webp/g-dl-embeddings.webp)

*Un embedding es esto: cada palabra convertida en un punto, colocado de forma que lo parecido queda cerca. Y como es un espacio, se puede restar: la flecha que va de hombre a mujer es la misma que va de rey a reina. Sobre esa geometría se busca por significado en vez de por palabra exacta.*

## De la matriz a los vectores cortos

La matriz tiene 41 por 41. Lo que queremos son listas de cinco números, y la forma clásica de comprimir una matriz conservando lo que importa es la **descomposición en valores singulares**, que numpy trae hecha:

```
U, S, _ = np.linalg.svd(np.log1p(juntas))     # log1p suaviza las cuentas grandes
E = U[:, :5] * S[:5]                          # cinco números por palabra

def parecido(a, b):
    x, y = E[lugar[a]], E[lugar[b]]
    return float(x @ y / (np.linalg.norm(x) * np.linalg.norm(y) + 1e-12))

print('bodega    :', np.round(E[lugar['bodega']], 3))
print('arroz     :', np.round(E[lugar['arroz']], 3))
print()
for a, b in [('bodega', 'minimarket'), ('arroz', 'aceite'), ('bodega', 'arroz'),
             ('lunes', 'martes'), ('completo', 'incompleto')]:
    print(f'{a:12} y {b:12}: {parecido(a, b):+.3f}')
```

```text
bodega    : [-2.354 -1.028  0.214 -0.414 -1.977]
arroz     : [-2.576  1.07   0.257  0.034  1.047]

bodega       y minimarket  : +0.849
arroz        y aceite      : +0.945
bodega       y arroz       : +0.300
lunes        y martes      : +0.968
completo     y incompleto  : +0.975
```

Ahí está lo que queríamos 🎉

**bodega y minimarket dan 0,849**, cuando con one-hot daban 0,0. Y arroz y aceite 0,945, y lunes y martes 0,968.

Y lo importante: **bodega y arroz dan 0,300**, o sea bajo. El método distinguió solo que hay palabras de "tipo de cliente" y palabras de "producto", sin que nadie se lo dijera. Solo mirando con quién andan.

Ese número, el `parecido`, es la **similitud coseno**: mide si dos vectores apuntan en la misma dirección, sin importar su tamaño. Es la medida estándar para embeddings y va de -1 a 1.

![Una red recurrente desenrollada en cuatro pasos de tiempo: la misma celda repetida, cada una recibe su palabra de entrada y el estado oculto que le pasa la anterior, y produce una salida y el estado para la siguiente.](https://missyera.com/static/diagramas/webp/g-dl-rnn.webp)

*Las cuatro celdas son la misma celda con los mismos pesos, dibujada cuatro veces. Y ahí se ve el problema de un vistazo: para llegar a la cuarta hay que haber pasado por las tres anteriores, así que no hay forma de repartir el trabajo entre varias tarjetas.*

## Y ahora el problema, que es famoso

Vuelve a mirar la última línea: **completo e incompleto dan 0,975**. Casi idénticos.

Y no es un fallo de mi corpus ni de mi código. Es que *completo* e *incompleto* aparecen exactamente en los mismos sitios: después de "llego", antes de "y". Su compañía es la misma, así que su vector es el mismo.

Este es el agujero conocido de esta idea: **los embeddings de coocurrencia no distinguen antónimos**. Bueno y malo, subir y bajar, con y sin. Todos salen parecidísimos, y si construyes un detector de quejas encima, va a confundir el elogio con el reclamo 😬

La solución no llegó hasta el capítulo 16: en vez de un vector fijo por palabra, un vector **que cambia según la frase**. Eso es la atención, y es la idea que hizo posible todo lo que usas hoy.

## Buscar lo más parecido

```
for p in ['bodega', 'arroz', 'retraso']:
    puntajes = sorted(((parecido(p, w), w) for w in vocabulario if w != p),
                      reverse=True)[:4]
    print(f'{p:9} se parece a: ' +
          ', '.join(f'{w} ({s:+.2f})' for s, w in puntajes))
```

```text
bodega    se parece a: mayorista (+0.91), nadie (+0.90), minimarket (+0.85), vendedor (+0.74)
arroz     se parece a: aceite (+0.94), del (+0.92), tarde (+0.92), roto (+0.88)
retraso   se parece a: con (+0.84), roto (+0.82), pidio (+0.81), sin (+0.81)
```

Para `bodega` saca mayorista, nadie, minimarket y vendedor. Tres de las cuatro son exactamente lo que esperarías 👏

Y la cuarta explica el método: `nadie` aparece porque escribí "nadie visito la bodega esta semana", y en esa frase *nadie* ocupa el mismo hueco que ocuparía un vendedor. El método no sabe qué significa nadie: sabe en qué hueco cae.

Con `arroz` pasa lo mismo: sale aceite (perfecto) y también `del` y `tarde`, que son ruido de tener solo 26 frases. Los embeddings de verdad se entrenan con miles de millones de palabras, y ahí el ruido se diluye 📚

## Y si en vez de palabras fueran fotos

Acá está el salto que explica la palabra **multimodal**, que vas a oír mucho y que suena a más de lo que es.

Un embedding no sabe que está hecho de palabras. Es una lista de números y un parecido que se mide con un ángulo. Nada de eso pide que el origen sea texto. **Si consigues meter una foto en el mismo espacio, la foto y la palabra se pueden comparar entre ellas.**

Vamos con tres productos y unas fotos de mentira, que acá son rejillas de 6 por 6. Lo que hace falta es aprender a pasar de los 36 números de la foto a los 5 números de la palabra, y para eso basta un ajuste por mínimos cuadrados:

```
rng = np.random.default_rng(0)

def foto(fila, columna):
    im = np.zeros((6, 6))
    im[fila] = 1.0
    im[:, columna] = 1.0
    return im

FOTOS = {'arroz': foto(1, 1), 'aceite': foto(3, 3), 'azucar': foto(4, 0)}
Xi = np.array([FOTOS[p].ravel() for p in FOTOS])
Yt = np.array([E[lugar[p]] for p in FOTOS])
W, *_ = np.linalg.lstsq(Xi, Yt, rcond=None)
print('cada foto son', Xi.shape[1], 'numeros y cada palabra', Yt.shape[1])

def coseno(x, y):
    return float(x @ y / (np.linalg.norm(x) * np.linalg.norm(y) + 1e-12))

for p in FOTOS:
    sucia = (FOTOS[p] + rng.normal(0, .15, (6, 6))).ravel()
    v = sucia @ W
    print(f'foto de {p:8}',
          sorted(((round(coseno(v, E[lugar[q]]), 3), q) for q in FOTOS), reverse=True))
```

```text
cada foto son 36 numeros y cada palabra 5
foto de arroz    [(1.0, 'arroz'), (0.943, 'aceite'), (0.809, 'azucar')]
foto de aceite   [(0.999, 'aceite'), (0.955, 'arroz'), (0.916, 'azucar')]
foto de azucar   [(1.0, 'azucar'), (0.918, 'aceite'), (0.813, 'arroz')]
```

Cada foto, y encima ensuciada con ruido, cae más cerca de su propia palabra que de las otras dos. Nadie le enseñó a la foto qué es el arroz: se le enseñó a **aterrizar donde ya estaba la palabra** 🔤

Mira los márgenes antes de emocionarte: 1,0 contra 0,943 es ganar por poco. Con tres ejemplos y una transformación lineal no da para más, y eso también es el mecanismo de verdad. Los modelos que hacen esto en serio usan una red por cada lado y millones de pares foto-texto, pero la idea que los sostiene es esta y cabe en veinte líneas.

De ahí sale todo lo que llaman multimodal: buscar fotos escribiendo, describir una imagen, encontrar el producto del catálogo con una foto del cliente. No son tres inventos, es el mismo espacio compartido tres veces 📋

## Ejercicios

**1. Cuántas dimensiones conviene**

Prueba con 2, 5 y 10 números por palabra.

```
for dims in [2, 5, 10]:
    Ed = U[:, :dims] * S[:dims]

    def par(a, b):
        x, y = Ed[lugar[a]], Ed[lugar[b]]
        return float(x @ y / (np.linalg.norm(x) * np.linalg.norm(y) + 1e-12))

    print(f'{dims:2d} dimensiones: bodega/minimarket {par("bodega", "minimarket"):+.3f}  '
          f'bodega/arroz {par("bodega", "arroz"):+.3f}')
```

```text
 2 dimensiones: bodega/minimarket +0.955  bodega/arroz +0.693
 5 dimensiones: bodega/minimarket +0.849  bodega/arroz +0.300
10 dimensiones: bodega/minimarket +0.686  bodega/arroz +0.346
```

Con 2 dimensiones todo se parece a todo (bodega y arroz dan 0,693). Con 10, la distinción entre tipos es más nítida.

Es el mismo compromiso de siempre: pocas dimensiones aplastan las diferencias y muchas se aprenden el ruido. Los embeddings de verdad usan entre 100 y 1.000, y salen de probar 📏

**2. La ventana también decide**

Cuenta la coocurrencia con una ventana de 1 y de 5.

```
def con_ventana(v):
    M = np.zeros((V, V))
    for f in COMENTARIOS:
        ps = f.split()
        for i, p in enumerate(ps):
            for j in range(max(0, i - v), min(len(ps), i + v + 1)):
                if i != j:
                    M[lugar[p], lugar[ps[j]]] += 1
    Uu, Ss, _ = np.linalg.svd(np.log1p(M))
    Ev = Uu[:, :5] * Ss[:5]

    def par(a, b):
        x, y = Ev[lugar[a]], Ev[lugar[b]]
        return float(x @ y / (np.linalg.norm(x) * np.linalg.norm(y) + 1e-12))
    return par('bodega', 'minimarket'), par('bodega', 'arroz')

for v in [1, 2, 5]:
    a, b = con_ventana(v)
    print(f'ventana {v}: bodega/minimarket {a:+.3f}  bodega/arroz {b:+.3f}')
```

```text
ventana 1: bodega/minimarket +0.609  bodega/arroz +0.433
ventana 2: bodega/minimarket +0.849  bodega/arroz +0.300
ventana 5: bodega/minimarket +0.823  bodega/arroz +0.781
```

Lo que hay que mirar es la *distancia* entre las dos columnas, o sea cuánto distingue.

Con ventana 5, bodega y arroz suben a 0,781: mirando casi la frase entera, todo aparece con todo y la distinción se pierde. Con ventana 1 tampoco va bien, porque con solo la palabra pegada hay tan pocas cuentas que el resultado es ruidoso (0,609 contra 0,433, casi sin separación).

La ventana 2 es la que más separa: 0,849 contra 0,300. **Ventana grande agrupa por tema y ventana chica se queda sin datos**, y el punto bueno está en medio, que es donde suele estar todo 🔍

**3. Sumar vectores para representar una frase**

La forma más simple de convertir una frase en números: sumar los de sus palabras.

```
def vector_de(frase):
    return np.mean([E[lugar[p]] for p in frase.split() if p in lugar], axis=0)

def parecido_frases(f1, f2):
    a, b = vector_de(f1), vector_de(f2)
    return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-12))

print('dos quejas parecidas:', round(parecido_frases(
    'la bodega reclamo por el retraso', 'el minimarket reclamo por el retraso'), 3))
print('queja contra elogio :', round(parecido_frases(
    'la bodega reclamo por el retraso', 'llego todo completo sin reclamo'), 3))
```

```text
dos quejas parecidas: 0.969
queja contra elogio : 0.731
```

Las dos quejas dan 0,969 y la queja contra el elogio 0,731. Distingue, y mejor de lo que yo esperaba con 26 frases 👏

Pero mira el límite que tiene sumar: **se pierde el orden**. "llego completo" y "completo llego" dan exactamente el mismo vector, porque la media no sabe en qué orden venían. Y "no llego el pedido" se parece muchísimo a "llego el pedido", que es justo lo contrario.

El capítulo 16 arregla también esto 🔀

**4. La aritmética famosa, a ver si sale**

Lo de "rey menos hombre más mujer da reina", con nuestras palabras.

```
objetivo = E[lugar['bodega']] - E[lugar['arroz']] + E[lugar['aceite']]

puntajes = []
for w in vocabulario:
    x = E[lugar[w]]
    puntajes.append((float(objetivo @ x / (np.linalg.norm(objetivo) * np.linalg.norm(x) + 1e-12)), w))

for s, w in sorted(puntajes, reverse=True)[:4]:
    print(f'  {w:12} {s:+.3f}')
```

```text
  bodega       +0.971
  nadie        +0.897
  mayorista    +0.877
  minimarket   +0.786
```

Sale bodega, que es hacer trampa: como arroz y aceite son casi idénticos, se cancelan y queda bodega.

La aritmética de vectores es de las cosas más citadas de este campo y también de las más exageradas. Con 26 frases no sale nada, y con corpus enormes sale a veces y con muchos ejemplos elegidos a mano 🎩

**5. Por qué esto no sirve para la tabla de ventas**

Arma el mismo procedimiento tratando cada fila del CSV como una frase y mira qué sale.

```
import pandas as pd

URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv'
ventas = pd.read_csv(URL).drop_duplicates()
ventas['ciudad'] = (ventas['ciudad'].str.strip().str.lower()
                    .str.normalize('NFKD')
                    .str.encode('ascii', 'ignore').str.decode('utf-8'))

filas = [f'{r.segmento} {r.ciudad} {r.canal} {r.categoria}'.lower()
         for r in ventas.itertuples()]
voc2 = sorted({p for f in filas for p in f.split()})
lug2 = {p: i for i, p in enumerate(voc2)}

M = np.zeros((len(voc2), len(voc2)))
for f in filas:
    ps = f.split()
    for i, p in enumerate(ps):
        for j, q in enumerate(ps):
            if i != j:
                M[lug2[p], lug2[q]] += 1

U2, S2, _ = np.linalg.svd(np.log1p(M))
E2 = U2[:, :5] * S2[:5]

def par2(a, b):
    x, y = E2[lug2[a]], E2[lug2[b]]
    return float(x @ y / (np.linalg.norm(x) * np.linalg.norm(y) + 1e-12))

for a, b in [('bodega', 'minimarket'), ('bodega', 'lima'), ('lima', 'arequipa')]:
    print(f'{a:12} y {b:12}: {par2(a, b):+.3f}')
```

```text
bodega       y minimarket  : +1.000
bodega       y lima        : +0.676
lima         y arequipa    : +1.000
```

Bodega y minimarket dan **1,000** clavado, y lima y arequipa también. O sea que dentro de una misma columna, el método no distingue nada.

Y bodega con lima da 0,676, que sigue siendo alto. Lo único que aprendió es "esta palabra es de la misma columna o de otra", que es exactamente lo que el `OneHotEncoder` ya te da gratis.

La razón es que en esta tabla **cada fila tiene una palabra de cada tipo y todas se cruzan con todas**. Bodega aparece con las seis ciudades, con los cuatro canales y con las cinco categorías, igual que mayorista. No hay ningún patrón de compañía que separar.

Por eso el libro de machine learning usó `OneHotEncoder` y no embeddings: **los embeddings necesitan un contexto que varíe**, y en una tabla con columnas independientes no lo hay. Es una herramienta de texto y conviene no forzarla 🧰

**6. Cuántos números ocupa cada cosa**

Compara la memoria de one-hot contra embeddings para vocabularios reales.

```
for palabras_n in [41, 1_000, 50_000]:
    print(f'{palabras_n:7,} palabras: one-hot {palabras_n * palabras_n:14,} números   '
          f'embeddings de 100 {palabras_n * 100:12,}')
```

```text
     41 palabras: one-hot          1,681 números   embeddings de 100        4,100
  1,000 palabras: one-hot      1,000,000 números   embeddings de 100      100,000
 50,000 palabras: one-hot  2,500,000,000 números   embeddings de 100    5,000,000
```

Con 50.000 palabras, one-hot necesitaría 2.500 millones de números y los embeddings 5 millones. Quinientas veces menos.

Y lo que ahorra no es solo memoria: con one-hot, cada palabra hay que aprenderla por separado. Con embeddings, lo que el modelo aprende sobre "retraso" aprovecha para "tardanza", porque están cerca 🤝

**7. El error de la palabra que no está**

Pídele el vector de una palabra que no salió en ninguna frase.

```
E[lugar['huancayo']]
```

```
KeyError: 'huancayo'
```

Y este error es el problema real de los embeddings clásicos, no una anécdota: **solo saben las palabras que vieron**. Una ciudad nueva, un nombre de producto nuevo, un error de tipeo, y no hay vector.

Es la misma avería silenciosa de la ciudad nueva del capítulo 21 del libro de machine learning, y aquí al menos revienta en vez de callarse.

Se resuelve partiendo las palabras en trozos más chicos, y eso es lo que hacen los modelos de hoy: "huancayo" se parte en pedacitos que sí conocen. Lo vemos en el capítulo 17 🧩

Comprueba que lo tienes

Los embeddings de coocurrencia dan a completo e incompleto un parecido de 0,975. ¿Qué está pasando?

- Que aparecen en los mismos contextos, y eso es todo lo que el método mira
- Que el cálculo está mal
- Que hacen falta más dimensiones
- Que faltan datos de entrenamiento

## Y los vectores que vieron demasiado

La trampa

Entrenas los embeddings con todo el texto que tienes, que es lo lógico porque cuanto más texto mejor, y después repartes para evaluar.

```
vectores = entrena_embeddings(todo_el_texto)

X = [vectores[p] for p in frases]
X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=0.25)
```

**Qué está mal**

Los vectores se construyeron mirando **con qué palabras aparece cada palabra**, y en ese conteo entraron las frases de la prueba 🔤 No copiaste la respuesta, pero sí metiste en el modelo información que el día de mañana no vas a tener.

Y hay un caso donde esto deja de ser sutil y se vuelve grave: si tus frases de prueba se parecen mucho a las de entrenamiento, los vectores aprenden esa coincidencia y el modelo la aprovecha. El número sube y nadie sabe explicar por qué.

La regla es la de siempre y no cambia porque el dato sea texto: **todo lo que aprenda algo de los datos se aprende solo con el trozo de entrenamiento**. Vale para la media, para las categorías y también para los embeddings.

## Lo que te llevas

- 🏝️ Con one-hot, bodega y minimarket se parecen 0,0, igual que bodega y arroz. Cada palabra es una isla.
- 👀 Un embedding se construye contando con qué palabras aparece cada una. No hay gramática ni diccionario.
- 🎉 Con cinco dimensiones, bodega y minimarket dan 0,849 y bodega y arroz 0,300: distinguió tipos de palabra solo.
- 😬 Y completo con incompleto dan 0,975. Los antónimos aparecen en los mismos sitios, así que salen iguales.
- 🔍 La ventana decide qué agrupa: chica junta palabras que se sustituyen, grande junta palabras del mismo tema.
- 🔀 Sumar los vectores de una frase pierde el orden: "no llego el pedido" se parece muchísimo a "llego el pedido".
- 🧰 En la tabla de ventas, bodega y minimarket dan 1,000 y no se distinguen: cada columna se cruza con todas. Los embeddings necesitan contexto que varíe.
- 🧩 Y solo saben las palabras que vieron: pedir una nueva revienta.

Y si de todo el capítulo te llevas una sola frase, que sea esta:

Para una máquina, dos palabras del mismo mundo son tan distintas como dos que no tienen nada que ver. Salvo que se lo enseñes.

Los embeddings son también lo que hay debajo de un buscador de documentos. Si eso es lo que te toca montar en tu empresa, en [soluciones de IA](https://missyera.com/consultoria-ia/) cuento cómo se aterriza 🏭

En el capítulo [[cap:atención]] llega la atención, que arregla las tres cosas que quedaron mal aquí: los antónimos, el orden y el vector fijo.

Que tengas lindo día! 🌸

## Seguir leyendo

- Anterior: [Dónde está y qué forma tiene, no solo qué es](https://missyera.com/guias/deep-learning-desde-cero/detectar-y-segmentar.md)
- Siguiente: [Buscar por significado entre un millón de trozos](https://missyera.com/guias/deep-learning-desde-cero/buscar-en-vectores.md)

## Contacto

- Diagnóstico de 30 minutos con Miss Yera: https://calendly.com/soymissyera/reunion-con-yera
- Correo: hola@missyera.com
- Esta página en HTML: https://missyera.com/guias/deep-learning-desde-cero/texto-y-embeddings/
- Índice de todas las páginas en texto: https://missyera.com/llms-index.txt
