שלב 3: הכנת הנתונים

כדי שאפשר יהיה להזין את הנתונים שלנו למודל, צריך לשנות אותם לפורמט שהמודל יכול להבין.

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

כדי לוודא שהמודל לא מושפע מסדר הנתונים, מומלץ תמיד לערבב את הנתונים לפני שמבצעים פעולות אחרות. אם הנתונים כבר פוצלו לקבוצות אימון ואימות, הקפידו לשנות את נתוני האימות באותו אופן שבו אתם משנים את נתוני האימון. אם עדיין אין לכם קבוצות נפרדות של אימון ואימות, אפשר לפצל את המדגמים אחרי שאקראית, בדרך כלל עדיף להשתמש ב-80% מהדגימות לאימון וב-20% לאימות.

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

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

  2. וקטורים: הגדרת מדד מספרי טוב לאפיון הטקסטים האלה.

בואו נראה איך לבצע את שני השלבים האלו בשביל וקטורים של n-gram וגם בשביל וקטורים של רצף, ונראה איך לבצע אופטימיזציה של ייצוגים וקטוריים באמצעות טכניקות לבחירת תכונות ונירמול.

וקטורים של N-gram [אפשרות א]

בפסקאות הבאות נראה איך לבצע המרה לאסימונים ווקטורים למודלים של n-gram. נסביר גם איך אפשר לבצע אופטימיזציה של ייצוג n-gram באמצעות טכניקות של בחירת תכונות ונירמול.

בווקטור n-gram, הטקסט מיוצג כאוסף של n-grams ייחודיים: קבוצות של n אסימונים סמוכים (בדרך כלל מילים). חשוב לזכור את הטקסט The mouse ran up the clock. כאן:

  • המילה יוניגרמים (n = 1) היא ['the', 'mouse', 'ran', 'up', 'clock'].
  • המילה Bigrams (n = 2) היא ['the mouse', 'mouse ran', 'ran up', 'up the', 'the clock']
  • וכן הלאה.

יצירת אסימונים

גילינו שיצירת אסימונים באותיות אחידות + Bigrams מאפשרת דיוק טוב ופחות זמן מחשוב.

וקטורים

אחרי שמפצלים את דוגמאות הטקסט ל-n-gram, אנחנו צריכים להפוך את ה-n-gram לווקטורים של מספריים שהמודלים של למידת המכונה יכולים לעבד. בדוגמה הבאה מוצגים האינדקסים שהוקצו ליוניגרמים ולביגרמות שנוצרו לשני טקסטים.

Texts: 'The mouse ran up the clock' and 'The mouse ran down'
Index assigned for every token: {'the': 7, 'mouse': 2, 'ran': 4, 'up': 10,
  'clock': 0, 'the mouse': 9, 'mouse ran': 3, 'ran up': 6, 'up the': 11, 'the
clock': 8, 'down': 1, 'ran down': 5}

אחרי שמקצים אינדקסים ל-n-gram, אנחנו בדרך כלל יוצרים וקטורים באמצעות אחת מהאפשרויות הבאות.

קידוד מסוג One-hot: כל טקסט לדוגמה מיוצג בווקטור שמציין את הנוכחות או היעד של אסימון בטקסט.

'The mouse ran up the clock' = [1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1]

קידוד הספירה: כל טקסט לדוגמה מיוצג בווקטור שמציין את מספר האסימון בטקסט. שימו לב שהרכיב שתואם ל-unigram 'the' מיוצג עכשיו כ-2 כי המילה 'the' מופיעה פעמיים בטקסט.

'The mouse ran up the clock' = [1, 0, 1, 1, 1, 0, 1, 2, 1, 1, 1, 1]

קידוד Tf-idf: הבעיה בשתי הגישות שלמעלה היא שאין סנקציות לגבי מילים נפוצות שמופיעות בתדירויות דומות בכל המסמכים (כלומר מילים שאינן ייחודיות במיוחד לדגימות הטקסט במערך הנתונים). לדוגמה, מילים כמו "a" יופיעו לעיתים קרובות מאוד בכל הטקסט. לכן, מספר אסימונים גבוה יותר ל-'the' מאשר למילים אחרות משמעותיות יותר לא מועיל.

'The mouse ran up the clock' = [0.33, 0, 0.23, 0.23, 0.23, 0, 0.33, 0.47, 0.33, 0.23, 0.33, 0.33]

(ניתן לעיין במאמר Scikit-learn TfidfTransformer)

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

שמנו לב שהקידוד של tf-idf יותר שולי מהשניים האחרים מבחינת הדיוק (בממוצע: 0.25-15% יותר), ומומלץ להשתמש בשיטה הזו כדי ליצור וקטורים של n-גרם. עם זאת, חשוב לזכור שהוא תופס יותר זיכרון (כי נעשה בו שימוש בייצוג של נקודה צפה) ונדרש יותר זמן כדי לחשב אותו, במיוחד במערכי נתונים גדולים (יכול להיות שהעיבוד שלו יימשך פי שניים במקרים מסוימים).

בחירת תכונה

כשממירים את כל הטקסטים במערך נתונים לאסימוני מילה uni+bigram, ייתכן שיתקבלו עשרות אלפי אסימונים. לא כל האסימונים או התכונות האלה תורמים לחיזוי התוויות. לכן אנחנו יכולים להשמיט אסימונים מסוימים, למשל כאלה שמתרחשים לעיתים רחוקות מאוד במערך הנתונים. אנחנו גם יכולים למדוד את חשיבות התכונות (כמה כל אסימון תורם לחיזויים של תוויות) ולכלול רק את האסימונים עם המידע הכי אינפורמטיבי.

יש הרבה פונקציות סטטיסטיות שמשתמשות בתכונות ובתוויות המתאימות ומפיקות את פלט ציון החשיבות של התכונה. שתי הפונקציות הנפוצות הן f_classif ו-chi2. הניסויים שלנו מראים ששתי הפונקציות האלה מניבות ביצועים טובים באותה מידה.

חשוב מכך, ראינו שרמת הדיוק מגיעה לשיא בכ-20,000 תכונות במערכי נתונים רבים (ראו איור 6). הוספת עוד פיצ'רים מעבר לסף הזה תורמת מעט מאוד, ולפעמים אפילו מובילה להתאמה יתר ולפגיעה בביצועים.

ערך המילים המובילות (K) לעומת דיוק

איור 6: התכונות המובילות (K) לעומת דיוק. במערכי נתונים שונים, רמות דיוק כוללות כ-20,000 תכונות מובילות.

נירמול

נירמול ממיר את כל הערכים של התכונות/הדגימה לערכים קטנים ודומים. זה מפשט את ההתכנסות של ירידות הדרגתיות באלגוריתמים של הלמידה. נראה שלנירמול במהלך עיבוד הנתונים מראש אין ערך רב בבעיות של סיווג טקסט. מומלץ לדלג על השלב הזה.

הקוד הבא מסכם את כל השלבים שלמעלה:

  • יצירת אסימונים עם דוגמאות טקסט לאסימונים
  • וקטורים באמצעות קידוד tf-idf,
  • בחרו רק את 20,000 המאפיינים המובילים מהווקטור של אסימונים על ידי מחיקת אסימונים שמופיעים פחות מפעמיים, ושימוש ב-f_classif כדי לחשב את החשיבות של המאפיין.
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_classif

# Vectorization parameters
# Range (inclusive) of n-gram sizes for tokenizing text.
NGRAM_RANGE = (1, 2)

# Limit on the number of features. We use the top 20K features.
TOP_K = 20000

# Whether text should be split into word or character n-grams.
# One of 'word', 'char'.
TOKEN_MODE = 'word'

# Minimum document/corpus frequency below which a token will be discarded.
MIN_DOCUMENT_FREQUENCY = 2

def ngram_vectorize(train_texts, train_labels, val_texts):
    """Vectorizes texts as n-gram vectors.

    1 text = 1 tf-idf vector the length of vocabulary of unigrams + bigrams.

    # Arguments
        train_texts: list, training text strings.
        train_labels: np.ndarray, training labels.
        val_texts: list, validation text strings.

    # Returns
        x_train, x_val: vectorized training and validation texts
    """
    # Create keyword arguments to pass to the 'tf-idf' vectorizer.
    kwargs = {
            'ngram_range': NGRAM_RANGE,  # Use 1-grams + 2-grams.
            'dtype': 'int32',
            'strip_accents': 'unicode',
            'decode_error': 'replace',
            'analyzer': TOKEN_MODE,  # Split text into word tokens.
            'min_df': MIN_DOCUMENT_FREQUENCY,
    }
    vectorizer = TfidfVectorizer(**kwargs)

    # Learn vocabulary from training texts and vectorize training texts.
    x_train = vectorizer.fit_transform(train_texts)

    # Vectorize validation texts.
    x_val = vectorizer.transform(val_texts)

    # Select top 'k' of the vectorized features.
    selector = SelectKBest(f_classif, k=min(TOP_K, x_train.shape[1]))
    selector.fit(x_train, train_labels)
    x_train = selector.transform(x_train).astype('float32')
    x_val = selector.transform(x_val).astype('float32')
    return x_train, x_val

באמצעות ייצוג בווקטור n-gram, אנחנו מוחקים מידע רב על סדר ודקדוק של מילים (במקרה טוב, אנחנו יכולים לשמור על חלק מהמידע על הסדר אם הוא n > 1). השיטה הזו נקראת 'שקית מילים'. הייצוג הזה משמש בשילוב עם מודלים שלא מביאים בחשבון את הסדר, כמו רגרסיה לוגיסטית, פרספקטרום רב-שכבתי, מכונות להגדלת הדרגתיות ומכונות וקטורים של תמיכה.

וקטורים של רצף [אפשרות ב']

בפסקאות הבאות נראה איך לבצע המרה לאסימונים ווקטורים למודלים של רצף. נסביר גם איך אפשר לשפר את הייצוג של הרצפים באמצעות בחירת תכונות ונירמול.

בחלק מהדוגמאות, סדר המילים הוא גורם קריטי למשמעות הטקסט. למשל, המשפטים: "פעם שנאתי את הנסיעה היומית שלי. האופניים החדשים שלי השתנו לחלוטין", כך שניתן להבין אותם רק אם קוראים אותם לפי הסדר. מודלים כמו רשתות CNN/RNN יכולים להסיק את המשמעות לפי סדר המילים בדוגמה. במודלים האלה, אנחנו מייצגים את הטקסט כרצף של אסימונים ושומרים על הסדר.

יצירת אסימונים

אפשר לייצג טקסט כרצף של תווים או כרצף של מילים. גילינו ששימוש בייצוג ברמת המילה מניב ביצועים טובים יותר מאסימוני תווים. זו גם הנורמה הכללית שעל פיה התעשייה גוברת. הגיוני להשתמש באסימוני תווים רק אם בטקסט יש הרבה שגיאות הקלדה, שבדרך כלל לא המצב.

וקטורים

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

Texts: 'The mouse ran up the clock' and 'The mouse ran down'

האינדקס שהוקצה לכל אסימון:

{'clock': 5, 'ran': 3, 'up': 4, 'down': 6, 'the': 1, 'mouse': 2}

הערה: המילה "the" מופיעה בתדירות הגבוהה ביותר, לכן ערך האינדקס של 1 מוקצה לה. ספריות מסוימות שומרות את האינדקס 0 לאסימונים לא ידועים, כמו במקרה כאן.

רצף של אינדקסים של אסימונים:

'The mouse ran up the clock' = [1, 2, 3, 4, 1, 5]

יש שתי אפשרויות ליצור וקטורים של רצפי אסימונים:

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

'The mouse ran up the clock' = [
  [0, 1, 0, 0, 0, 0, 0],
  [0, 0, 1, 0, 0, 0, 0],
  [0, 0, 0, 1, 0, 0, 0],
  [0, 0, 0, 0, 1, 0, 0],
  [0, 1, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 1, 0]
]

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

הטמעת מילים

איור 7: הטמעות מילים

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

שכבת הטמעה

איור 8: שכבת ההטמעה

בחירת תכונה

לא כל המילים בנתונים שלנו תורמות לחיזויים של תוויות. נוכל לייעל את תהליך הלמידה על ידי מחיקת מילים נדירות או לא רלוונטיות מאוצר המילים שלנו. למעשה, שמנו לב ששימוש ב-20,000 התכונות הכי נפוצות בדרך כלל מספיק. הדבר נכון גם לגבי מודלים של n-gram (ראו איור 6).

נשלב את כל השלבים שלמעלה בווקטורים של רצף. הקוד הבא מבצע את המשימות האלה:

  • המרת הטקסטים לאסימונים למילים
  • יצירת אוצר מילים באמצעות 20,000 האסימונים המובילים
  • ממירה את האסימונים לווקטורים של רצף
  • התאמת הרצפים לאורך רצף קבוע
from tensorflow.python.keras.preprocessing import sequence
from tensorflow.python.keras.preprocessing import text

# Vectorization parameters
# Limit on the number of features. We use the top 20K features.
TOP_K = 20000

# Limit on the length of text sequences. Sequences longer than this
# will be truncated.
MAX_SEQUENCE_LENGTH = 500

def sequence_vectorize(train_texts, val_texts):
    """Vectorizes texts as sequence vectors.

    1 text = 1 sequence vector with fixed length.

    # Arguments
        train_texts: list, training text strings.
        val_texts: list, validation text strings.

    # Returns
        x_train, x_val, word_index: vectorized training and validation
            texts and word index dictionary.
    """
    # Create vocabulary with training texts.
    tokenizer = text.Tokenizer(num_words=TOP_K)
    tokenizer.fit_on_texts(train_texts)

    # Vectorize training and validation texts.
    x_train = tokenizer.texts_to_sequences(train_texts)
    x_val = tokenizer.texts_to_sequences(val_texts)

    # Get max sequence length.
    max_length = len(max(x_train, key=len))
    if max_length > MAX_SEQUENCE_LENGTH:
        max_length = MAX_SEQUENCE_LENGTH

    # Fix sequence length to max value. Sequences shorter than the length are
    # padded in the beginning and sequences longer are truncated
    # at the beginning.
    x_train = sequence.pad_sequences(x_train, maxlen=max_length)
    x_val = sequence.pad_sequences(x_val, maxlen=max_length)
    return x_train, x_val, tokenizer.word_index

יצירת וקטורים של תוויות

ראינו איך להמיר נתוני טקסט לדוגמה לווקטורים מספריים. חובה לבצע תהליך דומה גם על התוויות. אנחנו יכולים פשוט להמיר תוויות לערכים בטווח [0, num_classes - 1]. לדוגמה, אם יש 3 סיווגים, אנחנו יכולים להשתמש בערכים 0, 1 ו-2 כדי לייצג אותם. באופן פנימי, הרשת תשתמש בווקטורים חד-פעמיים חד-פעמיים כדי לייצג את הערכים האלה (כדי לא להסיק קשר שגוי בין תוויות). הייצוג הזה תלוי בפונקציית אובדן הנתונים ובפונקציית ההפעלה בשכבה האחרונה שבה אנחנו משתמשים ברשת הנוירונים שלנו. בקטע הבא נלמד על הכלים האלה.