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

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

כאשר פיתחנו מערכות Web, היה חשוב להבין את ה HTTP protocol בכדי לעשות את העבודה שלנו בצורה מקצועית.

כאשר אנחנו מפתחים מערכות LLM, פרוטוקול שאנחנו עומדים לעסוק בו הרבה הוא ״טקסט של שפה אנושית״, אפילו בתסריטים של Machine to Machine. בפוסט הזה אני רוצה לדבר על כמה כלי NLP בסיסיים שעוזרים לעבוד עם טקסט, ועל השימושים שלהם במערכות LLM. בואו נתחיל להבין איפה הכלים האלו יכולים לשמש אותנו:

  1. custom) RAG) – כדי להגיע לתוצאות מצוינות הרבה פעמים נצטרך לנהל את ה context retrieval בעצמנו ולתפור אותו ל use-case, לא סתם להשתמש ב RAG Framework מהמדף. לשלב טכניקות כגון vector similarity, hybrid search ו BM25.
  2. Safety / Validation – כאשר אנחנו רוצים לבדוק את ה output של ה LLM שהוא סביר / לגיטימי / בגבולות שאנו מצפים ולא פוגע / לא-חוקי / מעבר לגבולות האחריות שלנו – אנחנו צריכים לאתר תבניות מסוימות בטקסט. איך עושים את זה? בעזרת כלי NLP כמובן. NER (קרי Named Entity Recognition – זיהוי אלמנטים כמו כתובת, שם עסק, עיסוק, וכו׳) למשל לאיתור PII או, Text Classification (סיווג טקסט לקטגוריה, למשל ״עצה ביטוחית״ או ״תוכן רעיל״).
  3. Evaluation – כאשר אנחנו רוצים לאמוד כמה מוצלחות התשובות של ה LLM שהפעלנו. לזהות מגמות של רגרסיה כדי לטפל בהן. יש מדדי הערכה קלאסיים כגון F1 או ROUGE, טכניקות חדשות יותר כגון LLM-as-a-Judge ו Pairwise Comparison.
  4. ניקוי נתונים בתהליך ה ingest ל Knowledge Base – נרצה לצמצם כפילויות סמנטיות, ולצמצם טקסט מיותר שלא שווה לאנדקס. כאן יש שימוש ב Canonicalization, Stemming, Lemmatization, Deduplication ועוד.
  5. Intent extraction / routing – אנחנו רוצים לאבחן את סוג הבקשה (intent) של המשתמש (אנושי או מכונה) ולנתב אותו ל branch הנכון של ה LLM. נשתמש בכלים כגון SVM (Support Vector Machine) או LLM Chains.

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

את רוב הפונקציות הנ״ל תוכלו למצוא בספריות כגון SpaCy או NLTK (קרי Natural Language Toolkit) לעיבוד טקסט + scikit-learn ל intent/safety ו evaluation בפייטון או Apache OpenNLP בעולמות הג׳אווה.
חשוב לציין שה ecosystem בעולם הפייטון מוביל על פני כל stack טכנולוגי אחר בעולמות הללו.

ניתן למצוא גם יכולות NLP חלק ממוצרים מוכרים המטפלים בטקסט, בעיקר Apache Lucene / Elasticsearch/OpenSearch אבל גם בסיסי נתונים כמו Postgres או אורקל (שממנו הושפע רבות) – בעזרת plugins כמו pg_bm25 או pgvector אבל גם SQLite (בסיס נתונים embedded – קטן ופשוט) עם כמה יכולות. במהלך הדרך התוודעתי לבסיס נתונים קטן ו embedded נוסף בשם DuckDB (הוא Columnar, vectorized, multi-threaded DB) המצוין לאבטיפוס בעולמות ה retrieval ומאפשר להריץ דירוג לשוני (BM25) ודירוג סמנטי (וקטורי) במנוע אחד.

Use case פשוט להתחלה

אנחנו רוצים לזהות במערכת שלנו לקוחות B2B שמנסים להרשם פעמיים. הם מספקים כתובת אימייל ושם העסק (Business Name). אם המייל + שם העסק זהים – נודיע להם שיש משתמש במערכת ונציע להם retrieval email (קרי ״forgot my password״).

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

לשמחתנו, אנחנו מכירים גוגל/פרפלקסיטי ויכולנו למצוא בקלות ספרייה כגון RapidFuzz (פייטון) שעושה Fuzzy matching לטקסט. יש שם מגוון פונקציות, אבל הטובה ביותר (לפי GPT) היא WRatio. נשתמש בה!

> fuzz.WRatio("fuzzy wuzzy was a bear", "wuzzy fuzzy was a bear")
95.0
> fuzz.WRatio("ACME international", "ACME INTERNATIONAL")
34.54
>>> fuzz.WRatio("AIG", "AIF") # keyboard-adjastant typo
66.66
>>> fuzz.WRatio("AIG", "AI±") # keyboard-distant typo
66.66

כלומר, אני מבין ש Fuzzy Match מחזיר מספר בין 0 ל 100, ואני צריך לקבוע מעל איזו ציון אני מקבל תוצאה כחיובית – זה מובן, אבל הספריה המובילה הזו, שגם GPT וגם Claude המליצו לי עליה – לפעמים דיי מפגרת!

אולי שווה להשתמש בפונקציות אחרות ולא דווקא WRatio?
weighted ratio מריץ כמה ״אלגוריתמים״ שונים ונותן את התוצאה הגבוהה ביותר. לפעמים יש אלגוריתם ״אופטימי״ שיקפיץ תוצאות וייתן false positive, אז כן – כדאי להכיר מה הספרייה עושה.

אולי צריך להבין טיפה NLP גם כאשר משתמשים בספרייה ״מובילה״?
בהחלט. Fuzzy match הוא אלגוריתם שמכסה תחומים מסוימים – ולא מכסה אחרים. הוא יידע לזהות חילופי מילים, מילים בודדות שנוספו לטקסט, או שגיאות כתיב מסוימות (אות חסרה) – אבל יש אלמנטים שלמים של טקסט (אפילו בסיסיים כמו UPPERCASE vs. lowercase) שהוא לא בנוי להתמודד איתם.

בדומיין כמו שמות עסקים (לא אביא דוגמאות – אבל האמנו לי, בבקשה) – יש הרבה תצורות של שוני שמחוץ ל״רדאר״ של fuzzy match. אז מה עושים? Canonicalization.

כמפתחים, אנו רגילים לעשות normalization בטקסט, קרי יצירת ייצוג עקבי לצורת הטקסט כגון ״ CAFÉ״ ו "cafe״.
כעת נרצה קצת להתקדם ולבצע canonicalization שמשמעו ייצוג עקבי למשמעות הטקסט (בדומיין) כגון ״Acme LLC״ ו ״acme״ (למי שלא מכיר LLC ≈ ה ״בע׳מ״ של ארה״ב).

יישום canonicalization לצורך השוואת טקסט

נתחיל ב normalization:

import spacy
import unicodedata 

tokenizer = spacy.blank("en") # english-only

def normalize_text(text: str) -> str:
  
    text = text.lower() #lowercase

    # Unicode normalization
    text = unicodedata.normalize("NFKD", text) # **1** Unicode normalize (NFKD)
    text = "".join(character for character in text if unicodedata.combining(character) == 0) # **2** remove combining characters

    # Tokenize with spaCy **3**
    doc = tokenizer(text)

    # Strip punctuation/whitespace **4**
    tokens = []
    for token in doc:
      if token.is_punct or token.is_space:
        continue
      tokens.append(token.text)

    # Join back
    return " ".join(tokens)
  1. זו נורמליזציה של Unicode כפי שהסברתי בפוסט על תוכנה רב-לשונית (תחת חיפוש ו Normalization). ייתכן והטקסט שמגיע אלינו, גם ממשתמשי קצה לא אחיד בצורה שלו. למשל:  מרכאות ניטראליות ( " ) מול מרכאות בעלות כיוון (  , ‟ ). או בגרמנית Fuβ שזו צורת כתיבה שקולה ל Fuss. נרמול NFKD הוא לרוב הנרמול המומלץ.
  2. זה שלב נוסף של הסרת סימנים מסוימים, כמו accent (נפוץ בשפות אירופאיות).
    למשל café ל cafe.
  3. כאן אנחנו משתמשים בספרייה בשם SpaCy (מקור השם: space – מרחב בין מילים/לשוני + Cy כי מומשה ב Cython גרסה מהירה של פייטון) לביצוע tokenization – חלוקת טקסט ליחידות בסיסיות הנקראות tokens — לרוב מילים, סימני פיסוק, מספרים וכו’. לדוגמא:
    "I love NLP." → [ "I", "love", "NLP", "." ]
  4. אנחנו משתמשים ב tokenization על מנת לנקות whitespace (זה קל, אפשר גם ב Regex), וסימני פיסוק (נקודה, פסיק וכו׳).
    ״אבל ליאור, אפשר בקלות לעשות את זה ב Regex״ – זה דיי נכון, אבל לא מדויק. למשל, אנחנו רוצים להוריד גרשיים ׳ מסביב למילה, אבל לא בתוך המילה "don't״. או למשל לא לשבור את "U.S.A״ ל ["U","S","A"]. בעוד Regex ייפול במקרי הקצה אלו, SpaCy לא.
    כמובן שזה קצה הקרחון של היכולות של SpaCy – אבל זה הרגל טוב להשתמש בו – ואז ניתן להרחיב ולהשתמש ביכולות נוספות.

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

למשל, בשמות חברות בארה״ב נהוג (אבל לא חובה) להוסיף LLC או INC (המקביל ל״בע׳מ״ בישראל). נרצה להסיר את הביטויים האלו על מנת שאם נרשם השם פעם עם, ופעם בלי – עדיין נדע להשוות אותם. למשל:
["llc", "inc", "corp", "co", "pc", "lp", "llp"]

שימו לב ש llc מופיע בטקסט לרוב כ L.L.C – אבל מכיוון שכבר הסרנו סימני פסוק, אין צורך לבדוק את המקרים הללו.

השלב הבא הוא לנקות Stop Words. אלו מילים מאוד נפוצות, אך שלא מוסיפות משמעות לתוכן הטקסט (ולכן מקשות על השוואה יעילה). למשל: the, a, an, and, or, is, was, are, to, from, for, of

אנחנו יכולים להשתמש ברשימה מוכנה / גנרית כמו spacy.lang.en.stop_words.STOP_WORDS (המכילה 326 מילים) אבל בד״כ התוצאות טובות / נשלטות יותר אם ננהל את הרשימה בעצמנו. במקרה שלנו, בחנתי את הטקסט* ומצאתי ש and ו the הן המילים הנפוצות שארצה להגדיר ב STOP WORDS.

בנוסף, ארצה להחליף את הטקסט "&" ב " and ״ – כחלק מהתהליך. כמובן שחשוב לעשות זאת, לפני הסרת סימני הפיסוק. הכל מצפייה בטקסט*

* האמת, מאז שיש LLM – הוא עושה הרבה מהעבודה הזו. אני שולף מבסיס הנתונים כ 10K רשומות של שמות עסקים, לוקח מודל חזק (כיום: GPT5-thinking-high) ומבקש ממנו לעזור לי לנתח את הטקסט. מה ה stop words? מה ה designations הנפוצים? מה ה synonyms (נדבר עליהם מיד)? מה עוד פספסתי? כמובן שאני חושב ומבקר את מה שהוא אומר וגם בודק בעצמי – אבל הוא בהחלט מאוד עוזר. (למשל את קיצורי החברות lp ו pc הוא זיהה – ואני לא הייתי מודע אליהם).

השלב האחרון שנשתמש בו הוא Synonym expansion.
synonym הן מילים נרדפות, ושוב – הרשימה הרלוונטית היא לרוב מבוססת דומיין. הנה מונחים לדוגמא שזיהיתי:

synonyms:
  service:
    - services
    - solutions
  construction:
    - contruction # very common spelling mistake
    - constrcution # a common spelling mistake
    - builders
    - contractors
  consulting:
    - advisory
  cleaning:
    - janitorial
 ...

אני אעבור על הטקסט ואחליף כל ערך ברשימת המילים הנרדפות, למילה המובילה (למשל: services ל service). שימו לב ש:

  1. יש מקרים בודדים של יחיד <=> רבים. לא טיפלתי בזה בצורה גורפת כי לא ראיתי שזה נפוץ בדוגמאות הטקסט. כמו כן, בסוף אשתמש ב RapidFuzz שהאלגוריתמים שלו מתמודדים טוב עם יחיד-רבים בשפה האנגלית.
  2. הכנסתי שגיאות כתיב נפוצות במקרה של construction. הכנסה של שגיאות כתיב כ synonym הוא לא לגמרי סטנדרטי ונתון להחלטה ספציפית. במקרה שלי, הטקסט להשוואה מוקלד ע״י משתמש במובייל – קרקע פורייה לשגיאות כתיב נפוצות. הוספתי את האפשרות יותר כדוגמא, כי היעד הסופי של ה canonicalization במקרה הזה, RapidFuzz, ״אוכל״ היטב גם שגיאות כתיב מהסוג הזה.

בשלב הזה, סיימנו canonicalization איכותי לדומיין של שמות עסקים – ועכשיו אנחנו יכולים להפעיל את RapidFuzz בבטחה, ולקבל תוצאות איכותיות.

סיכום

גם בעולם בו יש את ה״קסם״ של LLM – עדיין יש מקום ל NLP ״קלאסי״.

אפשר לומר שדווקא ה־LLM החזיר (חלק מ) ה־NLP למרכז. בכל מקרה שבו המודל לא מצליח “במכה אחת” וללא עזרה (מה שנקרא ״zero-shot״), אנו נדרשים לעבד טקסט: לנקות, להבין, למיין, וכו׳ — בדיוק שם נכנס ה־NLP הקלאסי לתמונה.

בדוגמה, ראינו ש Fuzzy Matching לא תמיד עובד Out of the Box, ללא הכנת הטקסט. עשינו Canonicalization והכרנו כמה מושגים חשובים ושימושיים בעיבוד טקסט כמו Stop Words ו Synonyms Expansion. לא הזכרנו Lemmatization ו Embedding – מה שיחייב אותי לכתוב פוסט המשך 😅.

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

שווה גם לציין שחלק מהשימושים בכלים שראינו הם ספציפיים Use Case. למשל Synonyms expansion הוא לא תמיד החלפה של רשימת מונחים בערך קאנוני. זה תלוי במקרה. למשל, עבור חיפוש טקסט, ייתכן ונרצה להרחיב ערך מהרשימה לכל ה synonyms המוגדרים. דוגמה: אם הופיעה ב query המילה ״service״ נרחיב את החיפוש לכל הערכים [״service״, ״services״, ״solutions״] כי כל אחד מהם רלוונטי פוטנציאלית לחיפוש שלנו.

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

Exit mobile version