Embeddings — וקטורים סמנטיים
הטכנולוגיה שמאחורי חיפוש סמנטי ו-RAG. איך הופכים טקסט למספרים שמייצגים משמעות, ואיך משתמשים בזה בפועל.
מה זה embedding
Embedding הוא ייצוג של טקסט (מילה, משפט או מסמך) כוקטור של מספרים — רשימה של מאות או אלפי מספרים שמקודדים את המשמעות. הרעיון המרכזי: טקסטים עם משמעות דומה יקבלו וקטורים קרובים במרחב, וטקסטים שונים — וקטורים רחוקים.
לדוגמה, "כלב" ו"גור" יהיו קרובים; "כלב" ו"מכונית" יהיו רחוקים. החוכמה: הקִרבה נמדדת לפי משמעות, לא לפי מילים זהות. לכן חיפוש מבוסס-embeddings מוצא תוצאות רלוונטיות גם כשלא השתמשת באותן מילים בדיוק — זה מה שנקרא חיפוש סמנטי.
Embedding = "טביעת אצבע מספרית" של משמעות. קרוב במרחב = דומה במשמעות. זה הבסיס ל-RAG, חיפוש סמנטי, המלצות וסיווג.
איך מודדים דמיון — Cosine Similarity
כדי לדעת כמה שני וקטורים קרובים, משתמשים לרוב ב-cosine similarity — מדד שבוחן את הזווית בין הווקטורים (לא המרחק). התוצאה נעה בין 1- ל-1:
- 1.0 — זהים לחלוטין במשמעות
- ~0.8 — דומים מאוד
- ~0 — לא קשורים
בפועל, מנוע חיפוש סמנטי מחשב את ה-embedding של השאילתה, ומחזיר את הקטעים עם ה-cosine similarity הגבוה ביותר. חישוב זה נעשה במהירות על ידי Vector Database גם על מיליוני וקטורים.
מודלים ומימדים
לא מייצרים embeddings לבד — משתמשים במודל embedding ייעודי. הבחירה משפיעה על איכות, מהירות ועלות:
- OpenAI —
text-embedding-3-small(1536 מימדים, זול ומהיר) ו-text-embedding-3-large(3072 מימדים, מדויק יותר). ברירת מחדל טובה לרוב. - קוד פתוח / מקומי — משפחות כמו BGE, E5 ו-nomic. חינם ופרטי, רצים מקומית או דרך Hugging Face.
- רב-לשוני — לעברית, ודא שהמודל תומך רב-לשונית טובה. מודלים מודרניים לרוב מטפלים בעברית סבירה.
מספר המימדים הוא אורך הווקטור. יותר מימדים = יותר "רזולוציה" אבל יותר אחסון וחישוב. חשוב: אסור לערבב embeddings ממודלים שונים באותו אינדקס — הם לא באותו מרחב.
שימושים עיקריים
- RAG — הבסיס לאחזור מידע לפני שהמודל עונה. השימוש הנפוץ ביותר.
- חיפוש סמנטי — חיפוש באתר/מוצר לפי משמעות, לא רק מילות מפתח.
- סיווג (classification) — לקבץ טקסטים לקטגוריות לפי דמיון.
- Clustering — לגלות נושאים/קבוצות בתוך אוסף טקסטים.
- המלצות — "מאמרים דומים", "מוצרים קשורים".
- Deduplication — לזהות תוכן כפול או דומה מאוד.
Chunking — חלוקה נכונה של מסמכים
לא מטמיעים מסמך שלם כווקטור אחד — מחלקים אותו ל-chunks (קטעים) ומטמיעים כל אחד. זה קריטי לאיכות RAG: chunk גדול מדי מדלל את המשמעות; קטן מדי מאבד הקשר.
- גודל טיפוסי: 300-800 טוקנים לכל chunk, עם חפיפה (overlap) של ~10-15% כדי לא לחתוך משפטים באמצע.
- חלוקה חכמה: עדיף לפי גבולות טבעיים (פסקאות, כותרות) מאשר חיתוך שרירותי.
- מטא-דאטה: שמור לכל chunk מקור, כותרת ותאריך — שימושי לסינון ולציטוט.
דוגמת קוד — חיפוש סמנטי בסיסי
from openai import OpenAI
import numpy as np
client = OpenAI()
def embed(texts):
r = client.embeddings.create(model="text-embedding-3-small", input=texts)
return [d.embedding for d in r.data]
def cosine(a, b):
a, b = np.array(a), np.array(b)
return a @ b / (np.linalg.norm(a) * np.linalg.norm(b))
docs = ["איך מאפסים סיסמה", "שעות פעילות התמיכה", "מדיניות החזרים"]
doc_vecs = embed(docs)
query = "שכחתי את הסיסמה שלי"
q_vec = embed([query])[0]
scores = [(cosine(q_vec, dv), d) for dv, d in zip(doc_vecs, docs)]
scores.sort(reverse=True)
print(scores[0]) # ('...0.86...', 'איך מאפסים סיסמה')
שים לב: השאילתה לא הכילה את המילה "מאפסים", אבל החיפוש מצא את הקטע הנכון — כי הוא הבין את המשמעות. בייצור, את החישוב הזה מבצע Vector DB ביעילות על נפח גדול.
טעויות נפוצות
- ערבוב מודלים. אסור לחפש עם מודל embedding אחד באינדקס שנבנה במודל אחר.
- Chunks גרועים. רוב בעיות ה-RAG הן בעצם בעיות chunking, לא בעיות מודל.
- התעלמות מנרמול. אם ה-DB לא מנרמל, השווה עם cosine ולא dot product.
- לא שומרים מטא-דאטה. בלי מקור לכל chunk אי אפשר לצטט או לסנן.
- רק חיפוש סמנטי. לרוב שילוב עם חיפוש מילולי (hybrid) נותן תוצאות טובות יותר — ראו RAG מתקדם.
הצעד הבא
הבנת embeddings? עכשיו חבר אותם לאחסון ולאחזור עם Vector DB ו-RAG.