Embeddingy

Význam jako souřadnice. Základ hledání i RAG.

Co se naučíš: Budeš umět převést text na vektor, spočítat podobnost a poznat, kdy je podobnost zavádějící.

9 min čteníNavazuje na:🔤 Tokeny

Embedding je převod textu na seznam čísel (vektor) tak, že podobné významy leží blízko sebe. Je to most mezi jazykem a matematikou: jakmile je text bod v prostoru, můžeš počítat vzdálenosti, hledat nejbližší sousedy a shlukovat. Bez embeddingů by neexistovalo sémantické vyhledávání ani RAG, a to jsou dvě věci, které v praxi postavíš nejdřív.


Pár pojmů na úvod

  • Vektor = uspořádaný seznam čísel, třeba 768 nebo 1536 hodnot. Nic víc.
  • Dimenze = kolik čísel vektor má. Víc dimenzí = jemnější rozlišení, ale víc místa a času.
  • Embedding model = model, který text na vektor převádí. Je to jiný model než ten, co generuje odpovědi (a bývá mnohonásobně levnější).
  • Kosinová podobnost = číslo od −1 do 1, které říká, jak podobné dva vektory jsou. 1 = totéž téma, 0 = nesouvisí.

Význam jako souřadnice

Představ si mapu, kde má každý text svoje místo. Věty o stejné věci skončí ve stejné čtvrti, i když nemají společné ani jedno slovo:

"Jak zruším objednávku?"        ─┐
"Chci vrátit zboží"              ├─ tahle část mapy = reklamace a vratky
"Storno nákupu je možné do..."  ─┘

"Jaké máte otevírací hodiny?"   ── úplně jiná čtvrť

Tohle je zásadní rozdíl proti hledání podle slov. Klasické fulltextové hledání by u dotazu „zruším objednávku“ nenašlo dokument, který mluví o „stornu nákupu“, nemají společné slovo. Embeddingy ho najdou, protože jsou si blízké významem.


Všimni si, že A a B nesdílejí ani jedno stejné slovo. Klasické vyhledávání podle klíčových slov by je nespojilo, embeddingy ano.


Jak se s tím prakticky pracuje

1. text  ──embedding model──▶  [0.021, -0.184, 0.77, ...]   (vektor)
2. vektor + text  ──────────▶  uložíš do vektorové databáze
3. dotaz ──embedding model──▶  [0.019, -0.201, 0.75, ...]
4. databáze vrátí nejbližší vektory  ─▶  odpovídající texty

Klíčové pravidlo: dokumenty i dotaz musí projít stejným embedding modelem. Vektory z různých modelů spolu nejde porovnávat, je to jako měřit vzdálenost mezi bodem v Praze a bodem na Marsu.

⚠️ Když vyměníš embedding model, musíš přegenerovat celou databázi. Počítej s tím dopředu: ulož si u každého záznamu, jakým modelem a kdy byl vytvořen.


Vyrob si embeddingy z vlastního textu

Nepotřebuješ k tomu ani neuronovou síť. Stačí spočítat, která slova se vyskytují v podobném okolí, a udělat z toho souřadnice. Do text.txt dej co nejvíc souvislého textu, ideálně aspoň kolem megabajtu, jinak budou výsledky jen šum:

▶ Spustitelné. Ulož jako embeddingy.py a pusť python3 embeddingy.py. Potřebuješ jen NumPy a soubor text.txt.

"""Embeddingy z ničeho: co se vyskytuje v podobném okolí, to skončí blízko."""
import numpy as np, re, collections

text = open('text.txt', encoding='utf8').read().lower()
words = re.findall(r'[a-záčďéěíňóřšťúůýž]{3,}', text)
top = [w for w, _ in collections.Counter(words).most_common(3000)]
idx = {w: i for i, w in enumerate(top)}
N = len(top)

C = np.zeros((N, N))                                  # kolikrát se slova potkají v okně
W = 4
ids = [idx.get(w, -1) for w in words]
for i, a in enumerate(ids):
    if a < 0: continue
    for b in ids[i+1:i+1+W]:
        if b >= 0: C[a, b] += 1; C[b, a] += 1

# PPMI: kolikrát častěji se potkávají, než kdyby byla slova nezávislá
tot = C.sum(); row = C.sum(1, keepdims=True)
with np.errstate(divide='ignore', invalid='ignore'):
    P = np.log((C * tot) / (row * row.T))
P[~np.isfinite(P)] = 0
P = np.maximum(P, 0)

U, S, _ = np.linalg.svd(P, full_matrices=False)
E = U[:, :100] * np.sqrt(S[:100])                     # embedding: 100 čísel na slovo
E /= np.linalg.norm(E, axis=1, keepdims=True) + 1e-9

def nejblizsi(w, n=5):
    sim = E @ E[idx[w]]                               # kosinová podobnost
    return ", ".join(f'{top[i]} {sim[i]:.2f}' for i in np.argsort(-sim)[1:n+1])

for w in ['token', 'paměť', 'server', 'chyba', 'soubor', 'model']:
    if w in idx: print(f'{w:8s} -> {nejblizsi(w)}')

Na milionu znaků textu téhle učebnice vypadne tohle:

token    -> pravděpodobnosti 0.63, tokenu 0.57, pravděpodobnost 0.54, další 0.54, softmax 0.53
paměť    -> paměti 0.65, experti 0.60, vram 0.57, karty 0.55, zero 0.53
server   -> klient 0.62, serveru 0.53, tvůj 0.48, prohlížeč 0.48, přiloží 0.47
soubor   -> souboru 0.66, soubory 0.62, složky 0.57, txt 0.56, složce 0.53
model    -> modelu 0.57, natrénovaný 0.53, tréninku 0.52, textu 0.51, text 0.49

Nikde v tom kódu není ani slovo o významu, gramatice nebo češtině. Přesto se soubor, souboru a soubory sesypaly k sobě, protože se objevují ve stejných větách, a u paměť vyskočilo vram a zero, tedy věci, které s pamětí souvisí, ale nejsou to synonyma.

To je přesně to, co embeddingy dělají a co je na nich užitečné i nebezpečné zároveň: měří kontextovou příbuznost, ne totožnost a už vůbec ne pravdivost. server a klient jsou si blízko právě proto, že jsou to protiklady vyskytující se v jedné větě.


Podobnost není totéž co pravda

Embedding hledá téma, ne správnost. Věty „Server běží“ a „Server neběží“ jsou si vektorově velmi blízké, protože jsou o tomtéž, přestože si odporují. Z toho plynou dvě pasti:

  • Negace vyhledávání mate. Dotaz „produkty bez lepku“ může vytáhnout i texty o lepku.
  • Čísla a přesné identifikátory embedding nezachytí. Objednávku #48213 hledej klasicky podle čísla, ne významem.

Proto se v praxi používá hybridní hledání: embeddingy najdou tematicky blízké kandidáty a klasický fulltext (klíčová slova, čísla, jména) je doplní. Detaily ve vektorové databázi.


Na co se embeddingy hodí kromě hledání

  • Deduplikace: najdi téměř stejné texty (duplicitní tikety, opakované dotazy).
  • Klasifikace bez trénování: porovnej dotaz s popisy kategorií a vyber nejbližší.
  • Doporučování: „podobné články“ bez ručních štítků.
  • Shlukování: o čem si zákazníci nejčastěji píšou, aniž bys to musel číst.
  • Detekce odchylek: dotaz, který je daleko od všeho známého, si zaslouží pozornost člověka.

Na všechny tyhle úlohy platí totéž: embeddingy jsou levné a rychlé. Generativní model je řádově dražší, tak ho nezaměstnávej něčím, co zvládne vzdálenost dvou vektorů.


Shrnutí

  • Embedding = text převedený na vektor tak, že podobný význam leží blízko.
  • Embedding model je jiný (a levnější) než generativní; dotaz i dokumenty musí projít stejným.
  • Změna modelu znamená přegenerovat celý index.
  • Podobnost říká „je to o tomtéž“, ne „je to pravda“, negace a čísla řeš fulltextem.
Proč fulltextové hledání nenajde odpověď na dotaz „zruším objednávku“ v textu o „stornu nákupu“, ale embeddingy ano?

Fulltext porovnává slova a tyhle dva texty nemají žádné společné. Embeddingy porovnávají význam: oba texty se převedou na vektory, které leží blízko sebe, protože pojednávají o tomtéž.

Vyměnil jsi embedding model za novější. Co musíš udělat s daty?

Přegenerovat všechny uložené vektory. Vektory z různých modelů nejsou porovnatelné, takže míchání starých a nových by dalo nesmyslné výsledky. Proto se u záznamů eviduje, jakým modelem vznikly.

Zákazník hledá „produkty bez lepku“ a vyhledávání mu vrátí i pečivo s lepkem. Čím to je?

Embedding zachytí téma (lepek), ale ne negaci, vektor „bez lepku“ je velmi blízko vektoru „s lepkem“. Řeší se to hybridním hledáním s filtry: příznak bezlepkovosti patří do strukturovaných metadat, ne do sémantického hledání.