Site icon בלוג ארכיטקטורת תוכנה

טכניקות NLP בסיסיות – חלק ב׳

בפוסט הראשון (לינק) הצגנו תהליך Canonicalization והכרנו כמה כלי NLP בסיסיים וחשובים – אבל היו כמה שלא הזכרנו – שחשוב להכיר. לכך מוקדש הפוסט הזה.

Lemmatization (או Morphological Normalization)

בבלשנות, מורפולוגיה היא תחום העוסק באופן שבו מילים נוצרות ומוטות (משתנות לפי זמן, מספר, מין וכו’). למשל:
• run → running, runs, ran — פּוֹעַל בצורות שונות.
• service → services, servicing — שם עצם ופּוֹעַל הנגזרים מאותו שורש.
• good → better → best — שינוי מדרגת תואר.

lemmatization עוזר לנו לנרמל את המשמעות בכך שהוא הופך מילים להטיה קאנונית שקל להשוות. למשל:

ExplanationPart of Speech (POS)LemmaToken
Irregular plural normalized to singularNounchildchildren
Past tense → base verb “be”Auxiliary Verbbewere
Present participle → base “run”Verbrunrunning
No change; already base formADVfastfast
No change for prepositionsADPinin
Determiner, unchangedDETthethe
Noun in singular form, stays sameNounparkpark

שווה לציין מונח דומה בשם 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 של עיבוד שפה:

האובייקט שחוזר מפונקציית ה 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 נתון ממופה במרחב בכל מימד בנפרד. המרחקים במרחב מבטאים דמיון סמנטי, למשל:

אל תנסו למצוא קשר בין מספר או סוג האותיות – למספרים בוקטור. המספרים מבטאים משמעות בלבד. למשל: מימד ראשון יכול להיות ״חיתיות״ שכלב קצת יותר מחתול, בעוד אבטיח הוא צומח -ממש לא חייתי. המימד השלישי עשוי להיות ״אכילות״ כאשר אבטיח הוא הרבה יותר אכיל מכלב וחתול – אבל גם להם יש ניקוד מסוים.

המימדים נקבעים ע״י ניתוח סטטיסטי ולא תמיד ניתן לתאר או להסביר אותם בצורה אינטואטיבית. כאשר עושים embedding לטקסט, אז כל token הופך לוקטור. תלוי במספר המימדים, אך הייצוג הוקטורי יכול להיות גדול או קטן מהייצוג הטקסטואלי (כאשר מודדים ב KB).

מדוע עושים embedding? רשתות ניורונים עובדות רק על מספרים, לא על טקסט. הייצוג הוקטורי מאפשר לנו לעבד את הטקסט ברשתות ניורוניות. אלגוריתמים כמו זיהוי סנטימנט או כוונה, סיכום, תרגום, זיהוי יישויות ועוד משיגים תוצאות טובות בהרבה על גבי רשתות ניורוניות מאשר בכלי NLP קלאסיים. ה Embedding הוא התרגום הנדרש כדי להפעיל טקסט על רשתות ניורונים לצורך הפעלת האלגוריתמים האלו. בעצם embedding הוא הייצוג הטבעי (״שפת המכונה״) של רשתות הניורונים.

יש מעט embeddings סטנדרטיים (למשל Word2Vec או GloVe) אבל רוב הפעמים שנעשה embedding נעשה אותם ל״שפה״ של מודל ספציפי, למשל BERT, GPT-4, GPT-5. כל embedding מגדיר:

נסווג את המודלים הזמינים לנו לשלוש קבוצות:

המודלים של SpaCy אינם יודעים לעשות זאת – ועוד ב 24 שניות..

סיכום

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

ייתכן ומומחי NLP יכעסו על הפוסט – שלא פתחתי בהגדרות כמו תחביר, פונולוגיה, סמנטיקה, ופרגמטיקה. שאני בכלל לא מומחה NLP בעצמי. נו… שיכעסו 🤷.

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

שיהיה בהצלחה!

Exit mobile version