בפוסט הראשון (לינק) הצגנו תהליך Canonicalization והכרנו כמה כלי NLP בסיסיים וחשובים – אבל היו כמה שלא הזכרנו – שחשוב להכיר. לכך מוקדש הפוסט הזה.
Lemmatization (או Morphological Normalization)
בבלשנות, מורפולוגיה היא תחום העוסק באופן שבו מילים נוצרות ומוטות (משתנות לפי זמן, מספר, מין וכו’). למשל:
• run → running, runs, ran — פּוֹעַל בצורות שונות.
• service → services, servicing — שם עצם ופּוֹעַל הנגזרים מאותו שורש.
• good → better → best — שינוי מדרגת תואר.
lemmatization עוזר לנו לנרמל את המשמעות בכך שהוא הופך מילים להטיה קאנונית שקל להשוות. למשל:
| Explanation | Part of Speech (POS) | Lemma | Token |
| Irregular plural normalized to singular | Noun | child | children |
| Past tense → base verb “be” | Auxiliary Verb | be | were |
| Present participle → base “run” | Verb | run | running |
| No change; already base form | ADV | fast | fast |
| No change for prepositions | ADP | in | in |
| Determiner, unchanged | DET | the | the |
| Noun in singular form, stays same | Noun | park | park |
שווה לציין מונח דומה בשם Stemming תהליך דומה ל Lemmatization אבל מבוסס-חוקים / יוריסטיקות – לא ניתוח תחבירי או מורפולוגי. הוא מהיר הרבה יותר, ומשמעותית פחות מדויק. מתאים בעיקר לכמויות גדולות של טקסט כאשר הדיוק חשוב פחות. לדוגמה Stemmer הוא חלק מתהליך ניתוח הטקסט של ה Indexing של Elasticsearch.
בואו נראה קצת קוד:
from rapidfuzz import fuzz
import spacy
nlp = spacy.load("en_core_web_sm") # small vector english model. Included.
def lemmatize(text):
return " ".join([t.lemma_ for t in nlp(text.lower())])
text1 = "children were running fast in the park"
text2 = "a child in the park ran fast"
fuzz.WRatio(text1, text2) # score: ~70
fuzz.WRatio(lemmatize(text1), lemmatize(text2)) # score: ~92
כמובן ש RapidFuzz הוא לא שימוש יחיד. במקרה, בפוסט, השתמשנו בו. הרבה פעמים נשתמש בטקסט לחיפוש, פענוח intent, סיווג הטקסט – ושימושים אחרים. בכל אלו – lemmatization הוא כלי מרכזי.
חשוב לציין שלמטיזציה (lemmatization) מטפלת רק בצורה של המילה, לא במשמעות שלה. במקרה של Good vs. Best – צפויה לנו אכזבה.
print(fuzz.WRatio("good", "better")) # score: 0.0
print(fuzz.WRatio("good", "best")) # score: 0.0
print(fuzz.WRatio(lemmatize("good"), lemmatize("better"))) # score: 0.0
במקרה שאנחנו רוצים להשוות משמעות, אנחנו משתמשים ב semantic similarity, למשל:
import spacy
nlp = spacy.load("en_core_web_lg") # LARGE vector english model
print(nlp("good").similarity(nlp("better"))) # score: ~81
print(nlp("good").similarity(nlp("bad"))) # score: ~73; note we didn't get a negative number since the algorith directional closeness, not semantic opposition.
print(nlp("good").similarity(nlp("best"))) # score: 70
שימו לב שכדי שזה יצליח, עברנו לעבוד במודל הוקטורי הגדול של Spacy (צריך להוריד כ 400MB).
יש מגוון כלים/אלגוריתמים ל Semantic Similarity, ספציפית Spacy משתמשת ב cosine similarity – עליו ארצה להרחיב ולהסביר בפוסט המשך.
הפונקציה nlp() מריצה pipeline של עיבוד שפה:

- Tokenizer – חלוקת טקסט למלים / יחידות בעלות משמעות. במקרה שלנו, token יחיד.
- כל אלגוריתם שמנסה “להבין” טקסט, בין אם זה מנוע חיפוש, מודל Transformer, או סתם regex — צריך לדעת מהן יחידות המשמעות שעליהן לעבוד. ללא Tokenization, כל הטקסט הוא רק רצף תווים חסר גבולות.
- הערה: מודלי שפה גדולים (LLM) token הוא לא בהכרח מילה. לפעמים מחלקים מילה לכמה tokens, למשל: unbelievable ל
["un", "believ", "able"].
- Token to Vector (קרי embedding) – הופך את ה tokens לייצוג וקטורי (מערך של מספרים, למשל בין 1.0- ל 1.0). נרחיב עליו בהמשך.
- רכיבים שונים שמוסיפים metadata על ה tokens.
- Tagger -סיווג Part of Speech (POS).
- Parser – מנתח תלויות תחביריות במשפט. מי קשור למי.
- NER – סיווג יישויות בטקסט: ״חברה״, ״מדינה״ וכו׳.
האובייקט שחוזר מפונקציית ה nlp() הוא doc המכיל את ה tokens ו metadata (סיווגים). ניתן לקסטם את ה pipeline ולהשבית / להוסיף שלבים. במקרה שלנו אנחנו מסתמכים על רק על ה embedding.
הפונקציה similarity() היא פונקציה הפועלת על מסמכים ומחזירה את המרחק הממוצע (ליתר דיוק: דמיון קוסיני) בין המסמכים. במקרה שלנו, כל מסמך מכיל רק token אחד, אז מדובר בדמיון סמנטי בין המושגים הבודדים ששלחנו.
Sentence Segmentation (או Sentence Boundary Detection בקיצור SBD)
פונקציית ה nlp() של SpaCy כוללת רכיב בשם dependency parser המפרק טקסט למשפטים. בסוג של tokenization, אנו יכולים לגשת ל doc.sents (בפייטון ובשפת C אוהבים לקצר שמות משתנים, לדעת) – רשימת המשפטים בטקסט. פירוק למשפטים הוא לא מלאכה קלה, ולא כל המודלים תומכים ביכולת הזו. בתוך אובייקט ה doc ה source of truth של החלוקה למשפטים הוא ברמת ה token, בתכונה token.is_sent_start ועריכת הערך שלו, קובעת את חלוקת המשפטים במסמך.
- שימוש בסיסי הוא להבין אם הטקסט שלנו כולל משפט אחד או יותר. למשל: אולי מישהו שרשר שני משפטים במקום שציפינו לאחד?
- כאשר מבצעים חיפוש / אינדוקס לפי הטקסט – יש משמעות לחלוקה למשפטים.
- צריך להגביל אורך של טקסט. אולי נעדיף לחתוך לפי משפטים, ולא חיתוך אקראי של הטקסט…
- וכו׳.
Embedding
תהליך ה embedding הוא תהליך שהופך tokens לוקטור (מערך מספרים עשרוניים). לכל וקטור יש מימדים, ו token נתון ממופה במרחב בכל מימד בנפרד. המרחקים במרחב מבטאים דמיון סמנטי, למשל:
- ״חתול״ ממופה ל
[0.7, 0.5, 0.1] - "כלב״ ממופה ל
[0.8, 0.3, 0.1] - ״אבטיח״ ממופה ל
[0.1, 0.2, 0.8]
אל תנסו למצוא קשר בין מספר או סוג האותיות – למספרים בוקטור. המספרים מבטאים משמעות בלבד. למשל: מימד ראשון יכול להיות ״חיתיות״ שכלב קצת יותר מחתול, בעוד אבטיח הוא צומח -ממש לא חייתי. המימד השלישי עשוי להיות ״אכילות״ כאשר אבטיח הוא הרבה יותר אכיל מכלב וחתול – אבל גם להם יש ניקוד מסוים.
המימדים נקבעים ע״י ניתוח סטטיסטי ולא תמיד ניתן לתאר או להסביר אותם בצורה אינטואטיבית. כאשר עושים embedding לטקסט, אז כל token הופך לוקטור. תלוי במספר המימדים, אך הייצוג הוקטורי יכול להיות גדול או קטן מהייצוג הטקסטואלי (כאשר מודדים ב KB).
מדוע עושים embedding? רשתות ניורונים עובדות רק על מספרים, לא על טקסט. הייצוג הוקטורי מאפשר לנו לעבד את הטקסט ברשתות ניורוניות. אלגוריתמים כמו זיהוי סנטימנט או כוונה, סיכום, תרגום, זיהוי יישויות ועוד משיגים תוצאות טובות בהרבה על גבי רשתות ניורוניות מאשר בכלי NLP קלאסיים. ה Embedding הוא התרגום הנדרש כדי להפעיל טקסט על רשתות ניורונים לצורך הפעלת האלגוריתמים האלו. בעצם embedding הוא הייצוג הטבעי (״שפת המכונה״) של רשתות הניורונים.

יש מעט embeddings סטנדרטיים (למשל Word2Vec או GloVe) אבל רוב הפעמים שנעשה embedding נעשה אותם ל״שפה״ של מודל ספציפי, למשל BERT, GPT-4, GPT-5. כל embedding מגדיר:
- מבנה פנימי שונה (מספר שכבות, גודל וקטור, normalization),
- אוצר מילים שונה (tokenization אחר),
- מערכת קואורדינטות סמנטית שונה לגמרי.
נסווג את המודלים הזמינים לנו לשלוש קבוצות:
- מודלים קלאסיים – SpaCy (או ספריות דומות) מכילה מגוון מודלים, עם תכונות שונות, לשפות שונות (אנגלית, גרמנית, וכו׳). הם מיועדים בעיקר לניתוח ותיוג טקסט בשיטות NLP קלאסיות. יש מודלים לשפה כללית (web), משפטית, רפואית, וכו׳. המודלים משתמשים בעיקר ב embedding סטנדרטיים כגון GloVe שהם סטטים – כלומר לכל מילה יש וקטור קבוע מראש, ב 300 מימדים (במקרה של GloVe).
- מודלים סטטיים לא ידע לסווג אחרת את המילה Apple במשפטים ״Apple release a new iPhone״ ו "He ate an apple״. למילה מסוימת, למשל "Apple״, יש משמעות קבועה.
- מודל סטנדרטי אומר שניתן לעשות לטקסט embedding פעם אחת – ולהשתמש בו בכמה מודלים.
- מודלים קונטקסטואליים – במודלים האלו ה embedding הוא חלק מובנה במודל (ולכן לא יכול להיות סטנדרטי). המודל בוחן את הטקסט ולומד בצורה דינאמית את משמעות המילים ובונה להם וקטורים במרחב. פלטפורמה פופולארית למודלים קונטקסטואליים היא Hugging Face Transformers המספקת מודלים של Deep Learning (מסוג Transformers). יש מודלים למשימות שונות כגון ניתוח סנטימנט, תרגום, סיכום, OCR, וכו׳. המודלים הללו איטיים יותר, אבל מספקים תוצאות מדויקות יותר ממודלים קלאסיים. לרוב הם עובדים בייצוג של 500 עד 1000 מימדים.
- במודלים קונטקסטואליים, ה־embedding הוא חלק אינטגרלי מהמודל עצמו, ולכן אינו סטנדרטי או ניתן להפרדה. המודל מנתח את הטקסט ולומד באופן דינמי את משמעות המילים בהתאם להקשר שלהן, ובונה עבורן ייצוגים וקטוריים במרחב רב־ממדי.
- למשל, כאשר נעשה embedding לשני מסמכים, א׳ וב׳:
- במסמך א': "Apple announced a new iPhone" המודל רואה את המילים "announced" ו-"iPhone". הוא מבין שההקשר הוא טכנולוגיה, ולכן הוא ממקם את "Apple" בנקודה על המפה שהיא:
- גבוהה בציר "טכנולוגי" (ציר 17).
- נמוכה בציר "אכיל" (ציר 250).
- במסמך ב': "She ate an apple" המודל רואה את המילים "ate" ו-"an". הוא מבין שההקשר הוא אוכל, ולכן הוא ממקם את "apple" בנקודה אחרת לגמרי על אותה מפה:
- נמוכה בציר "טכנולוגי" (ציר 17).
- גבוהה בציר "אכיל" (ציר 250).
- במסמך א': "Apple announced a new iPhone" המודל רואה את המילים "announced" ו-"iPhone". הוא מבין שההקשר הוא טכנולוגיה, ולכן הוא ממקם את "Apple" בנקודה על המפה שהיא:
- כלומר, שומרים על ״חוקי הפיסיקה״ של כל embedding: יחסי הקירבה עובדים לפי אותם כללים, מרחקים נמדדים בצורה זהה, ויש אותו מספר מימדים – אבל המיקום של אותן מילים במימדים – שונה.
- עבור משימות מסוימות, כמו השוואת משעות של הטקסט – חיבור וקטורים יכול לעבוד (צירוף של רשימת הוקטורים של מסמך א׳ לוקטורים של מסמך ב׳). המילים המקוריות לא משנות – רק המשמעות שלהן.
- עבור משימות אחרות, למשל סיכום טקסט או זיהוי סנטימנט – פתאום המילים מטשטשות משמעות. למילה ״Apple״ אין כבר משמעות יחידה. מכאן שחיבור embedding יגרום לפגיעה קשה בתוצאות.
- הפתרון הפשוט הוא לחבר את המסמכים א׳+ב׳ – ולבצע embedding מחדש לאיחוד שלהם.
- יש אלגוריתמים שמאחדים embedding שונים שנוצרו בהקשרים שונים – אך יש בהם פשרות.
- מודלים ענקיים (LLM) אלו מודלים מסוג Transformers אבל עצומים בגודלם (ולכן איטיים הרבה יותר – לרוב מריצים אותם על שרת מרוחק). הם קונטקסטואליים וכוללים גם ידע עולמי, ולא רק הקשר מקומי מתוך הטקסט. המודלים הללו הם general-purpose ולרוב יכולים להתמודד עם כל הבעיות (ברמת הצלחה כזו או אחרת), או לפחות לנסות.
- למשל (דוגמה רגישה), ״ישראל״ במודל קונטסטואלי רגיל ≈ מדינה, מופיעה יחד עם מילים כמו “מזרח”, “תל־אביב”, “ממשלה”. “ישראל” תשב קרוב ל־“ירדן”, “לבנון”, “סוריה”, “ארה״ב” ו״אירופה״ – פשוט כי אלו מופיעים יחד הרבה בטקסטים שעליהם אומן.
- מודל LLM יידע ש״ישראל״ יושבת במזרח התיכון (עובדה) ולא באמריקה. שיש בה קונפליקטים ביטחוניים מתמשכים, שהיא מזוהה עם טכנולוגיה, דמוקרטיה, דתות, צבא, ועוד. כלומר הוא ימפה את הידע הזה בתוך מימדים ב embedding (בשונה למשל מ״מונטנגרו״ – בעלות מאפיינים אחרים).
- מודלי LLM לרוב משתמשים ביותר מימדים, לרוב 1500-4000 מימדים (בעת כתיבת הפוסט).

סיכום
אם לפני הפוסט הזה (חלק א׳+ב׳) לא היה לכם מושג מהו NLP (מלבד מושג כללי) ואיך משתמשים בו – הרי בסוף הפוסט אמור להיות לכם את ה״א״, וחצי ה״ב״.
ייתכן ומומחי NLP יכעסו על הפוסט – שלא פתחתי בהגדרות כמו תחביר, פונולוגיה, סמנטיקה, ופרגמטיקה. שאני בכלל לא מומחה NLP בעצמי. נו… שיכעסו 🤷.
בפוסט הכרנו הרבה מונחים חשובים ושימושיים (lemma, embedding ועוד) שיכולים ממש לעזור ולהתמצא במרחב הדיון כאשר מדברים על ניתוח טקסט. זה בסיס חיוני להמשך הדיון – ואני מתכוון לכתוב עוד, ולצלול יותר לדוגמאות מעשיות, המובילות לפיתוח יעיל של מערכות LLM.
שיהיה בהצלחה!

תודה רבה!
מחכה לחלקים הבאים