LIKE намира думи, не смисъл
Ученик пита „къде мога да се науча да пиша код“. В базата има запис „Клуб по програмиране — всяка сряда пишем игри на C#“. WHERE opisanie LIKE '%код%' не намира нищо: нито една дума не съвпада. За човек двете са очевидно едно и също. Нужно е представяне на текста, при което близкото по смисъл е близко и като числа.
Векторно представяне (embedding)
Редица от няколкостотин числа, която представя дума, изречение или абзац така, че близките по смисъл текстове имат близки вектори.
Откъде идва смисълът
Думата се познава по компанията, в която се среща. Ако „волейбол“ и „баскетбол“ се появяват в едни и същи контексти („тренировка“, „отбор“, „салон“), значи имат близък смисъл. Моделът за вектори чете милиарди изречения и настройва числата така, че текстове със сходно обкръжение да получат сходни вектори. Никой не е задавал категориите „спорт“ и „програмиране“ — те се появяват сами.
Броят числа във вектора е неговата размерност: 384 при малък локален модел, 768 или 3072 при моделите на Google. Повече измерения — по-фини разлики, но повече място и по-бавно търсене. Никое отделно число няма човешко значение; смисълът е в целия вектор.
Косинусова близост
Два вектора са „близки“, когато сочат в една посока. Мярката е косинусът на ъгъла между тях:
cos(a, b) = (a · b) / (|a| · |b|)
a · b — скаларно произведение: a₁b₁ + a₂b₂ + … + aₙbₙ
|a| — дължина на вектора: √(a₁² + a₂² + … + aₙ²)- 1 — една посока: почти един и същ смисъл.
- 0 — перпендикулярни: несвързани неща.
- −1 — противоположни посоки (при текстове почти не се среща).
Защо ъгъл, а не разстояние: дълъг и кратък текст на една тема имат вектори с различна дължина, но с почти еднаква посока. Косинусът не се влияе от дължината. На Python са два реда с numpy:
import numpy as np
def kosinus(a, b):
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
print(kosinus([1, 2, 3], [2, 4, 6])) # 1.0 — същата посока, различна дължина
print(kosinus([1, 0], [0, 1])) # 0.0 — перпендикулярниКлючови думи срещу смисъл
| LIKE (ключови думи) | Вектори (смисъл) | |
|---|---|---|
| намира | текстове със същите букви | текстове със същия смисъл, дори без общи думи |
| силна страна | точни съвпадения: име, номер, код на грешка | въпрос, зададен с други думи; синоними; друг език |
| слаба страна | „кодя“ не намира „програмиране“ | не различава добре отрицание; иска модел за векторите |
| цена | нула — вградено в SQL | вектор за всеки запис и за всеки въпрос |
Векторите в базата
Откъде идват
Същият клиент от урок 19, друг метод. Моделът gemini-embedding-001 приема списък от текстове и връща по един вектор за всеки:
def vektori(tekstove):
otg = client.models.embed_content(
model="gemini-embedding-001", contents=tekstove)
return [e.values for e in otg.embeddings] # списък от списъци с числа
V = vektori(["Клуб по програмиране", "Волейболен отбор"])
print(len(V), "вектора по", len(V[0]), "числа")Без ключ или при изчерпан лимит: безплатен многоезичен модел, който се сваля в Colab и работи локално — paraphrase-multilingual-MiniLM-L12-v2 от библиотеката sentence-transformers. Разбира български и дава вектори от 384 числа. Останалият код е същият.
Къде се пазят
SQLite няма тип „вектор“. При няколко десетки или стотици записа стига колона TEXT, в която векторът е записан като JSON списък; при търсене всички се четат и се сравняват с numpy. При милиони записи това е бавно и се минава на векторна база — но това вече е извън училищния проект.
import json
cur.execute("UPDATE tekstove SET vektor = ? WHERE id = ?", (json.dumps(v), id_))
# … и обратно:
v = np.array(json.loads(red[2]))Какво не могат векторите
- Не различават отрицание добре: „обичам котки“ и „не обичам котки“ имат близки вектори.
- Наследяват пристрастията от текстовете, върху които са обучени.
- Не смятат: „клуб с над 20 участници“ е работа на WHERE, не на близостта (урок 24).
- Винаги връщат „най-близкото“ — дори когато нищо не е близко. Прагът на близостта е ваша работа (урок 26).
Какво трябва да запомниш
- Векторът представя текст като редица от числа; близък смисъл — близки вектори.
- Близостта се мери с косинус на ъгъла:
a·b / (|a||b|), от 1 (еднакви) до 0 (несвързани). LIKEнамира букви, векторите — смисъл; добрите системи ползват и двете.- Вектор през
embed_contentили локален модел; пази се в SQLite като JSON текст; търси се с numpy. - Векторите от различни модели не са съвместими — записвайте модела.