שלב 4: בניית, אימון והערכה של המודל

בסעיף הזה, נפעל כדי לבנות, לאמן ולהעריך את המודל שלנו. בשלב 3, אנחנו בוחרים להשתמש במודל n גרם או במודל רצף עם היחס של S/W. זה הזמן לכתוב את אלגוריתם הסיווג שלנו ולתרגל אותו. נשתמש ב-TensorFlow עם ממשק ה-API tf.keras.

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

ערימת שכבות לינארית

איור 9: ערימת שכבות לינארית

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

בניית השכבה האחרונה

אם יש לנו רק שני מדדים (סיווג בינארי), המודל אמור להניב ציון הסתברות בודד. לדוגמה, אם יוצרים פלט של 0.2 עבור דגימה נתונה, פירוש הדבר הוא "20% ביטחון שדוגמה זו נמצאת ברמה הראשונה (כיתה 1), 80% שהיא בכיתה השנייה (כיתה 0)." כדי לקבל ציון הסתברות כזה, פונקציית ההפעלה של השכבה האחרונה צריכה להיות פונקציה sigmoid, ופונקציה מוחשית 1 {1111}

כאשר יש יותר משני כיתות (סיווג מרובה כיתות), המודל אמור להניב ציון הסתברות אחד לכל כיתה. סך הציון הזה צריך להיות 1. לדוגמה, אם מתקבל פלט של {0: 0.2, 1: 0.7, 2: 0.1}, פירוש הדבר הוא ש-20% בטוחים שהדגימה הזו היא ברמה 0, 70% שהיא נמצאת ברמה 1, ו-10% שהיא נמצאת ברמה 2. כדי להוציא את הציונים האלה, פונקציית ההפעלה של השכבה האחרונה צריכה להיות softmax, ופונקציית ההפסדים משמשת לאימון המודל צריכה להיות קטגורית קטגורית. (ראו איור 10, מימין).

שכבה אחרונה

איור 10: השכבה האחרונה

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

def _get_last_layer_units_and_activation(num_classes):
    """Gets the # units and activation function for the last network layer.

    # Arguments
        num_classes: int, number of classes.

    # Returns
        units, activation values.
    """
    if num_classes == 2:
        activation = 'sigmoid'
        units = 1
    else:
        activation = 'softmax'
        units = num_classes
    return units, activation

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

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

Build n gram model [Option A]

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

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

הקוד הבא מגדיר מודל MLP דו-שכבתי ב-tf.keras, שמוסיף כמה שכבות נטישה לסידור (כדי למנוע התאמה הולמת לדגימות אימון).

from tensorflow.python.keras import models
from tensorflow.python.keras.layers import Dense
from tensorflow.python.keras.layers import Dropout

def mlp_model(layers, units, dropout_rate, input_shape, num_classes):
    """Creates an instance of a multi-layer perceptron model.

    # Arguments
        layers: int, number of `Dense` layers in the model.
        units: int, output dimension of the layers.
        dropout_rate: float, percentage of input to drop at Dropout layers.
        input_shape: tuple, shape of input to the model.
        num_classes: int, number of output classes.

    # Returns
        An MLP model instance.
    """
    op_units, op_activation = _get_last_layer_units_and_activation(num_classes)
    model = models.Sequential()
    model.add(Dropout(rate=dropout_rate, input_shape=input_shape))

    for _ in range(layers-1):
        model.add(Dense(units=units, activation='relu'))
        model.add(Dropout(rate=dropout_rate))

    model.add(Dense(units=op_units, activation=op_activation))
    return model

בניית מודל רצף [אפשרות ב']

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

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

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

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

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

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

  2. הטמעות מדויקות יותר מניבות דיוק רב יותר. עם זאת, זה נובע מהצריכה המוגברת של מחשוב הנדרש כדי לאמן את הרשת. לאור כמות מספקת של דגימות, נוכל לשפר את הטמעת ההטמעה. הבחנו שעבור S/W > 15K, התחלה מאפס מאפשרת בדיוק את אותה רמת דיוק כמו הטמעה מדויקת.

השווינו מודלים שונים של רצף, כמו CNN, sepCNN, RNN (LSTM & GRU), CNN-RNN ו-RNN בערימה, עם שינויים שונים בארכיטקטורה של המודלים. גילינו ש-sepCNN, וריאציה של רשת מפותלת, שלעיתים קרובות יעילה יותר בנתונים ויעילה למחשוב, מניבה ביצועים טובים יותר ממודלים אחרים.

הקוד הבא יוצר מודל sepCNN בן ארבע שכבות:

from tensorflow.python.keras import models
from tensorflow.python.keras import initializers
from tensorflow.python.keras import regularizers

from tensorflow.python.keras.layers import Dense
from tensorflow.python.keras.layers import Dropout
from tensorflow.python.keras.layers import Embedding
from tensorflow.python.keras.layers import SeparableConv1D
from tensorflow.python.keras.layers import MaxPooling1D
from tensorflow.python.keras.layers import GlobalAveragePooling1D

def sepcnn_model(blocks,
                 filters,
                 kernel_size,
                 embedding_dim,
                 dropout_rate,
                 pool_size,
                 input_shape,
                 num_classes,
                 num_features,
                 use_pretrained_embedding=False,
                 is_embedding_trainable=False,
                 embedding_matrix=None):
    """Creates an instance of a separable CNN model.

    # Arguments
        blocks: int, number of pairs of sepCNN and pooling blocks in the model.
        filters: int, output dimension of the layers.
        kernel_size: int, length of the convolution window.
        embedding_dim: int, dimension of the embedding vectors.
        dropout_rate: float, percentage of input to drop at Dropout layers.
        pool_size: int, factor by which to downscale input at MaxPooling layer.
        input_shape: tuple, shape of input to the model.
        num_classes: int, number of output classes.
        num_features: int, number of words (embedding input dimension).
        use_pretrained_embedding: bool, true if pre-trained embedding is on.
        is_embedding_trainable: bool, true if embedding layer is trainable.
        embedding_matrix: dict, dictionary with embedding coefficients.

    # Returns
        A sepCNN model instance.
    """
    op_units, op_activation = _get_last_layer_units_and_activation(num_classes)
    model = models.Sequential()

    # Add embedding layer. If pre-trained embedding is used add weights to the
    # embeddings layer and set trainable to input is_embedding_trainable flag.
    if use_pretrained_embedding:
        model.add(Embedding(input_dim=num_features,
                            output_dim=embedding_dim,
                            input_length=input_shape[0],
                            weights=[embedding_matrix],
                            trainable=is_embedding_trainable))
    else:
        model.add(Embedding(input_dim=num_features,
                            output_dim=embedding_dim,
                            input_length=input_shape[0]))

    for _ in range(blocks-1):
        model.add(Dropout(rate=dropout_rate))
        model.add(SeparableConv1D(filters=filters,
                                  kernel_size=kernel_size,
                                  activation='relu',
                                  bias_initializer='random_uniform',
                                  depthwise_initializer='random_uniform',
                                  padding='same'))
        model.add(SeparableConv1D(filters=filters,
                                  kernel_size=kernel_size,
                                  activation='relu',
                                  bias_initializer='random_uniform',
                                  depthwise_initializer='random_uniform',
                                  padding='same'))
        model.add(MaxPooling1D(pool_size=pool_size))

    model.add(SeparableConv1D(filters=filters * 2,
                              kernel_size=kernel_size,
                              activation='relu',
                              bias_initializer='random_uniform',
                              depthwise_initializer='random_uniform',
                              padding='same'))
    model.add(SeparableConv1D(filters=filters * 2,
                              kernel_size=kernel_size,
                              activation='relu',
                              bias_initializer='random_uniform',
                              depthwise_initializer='random_uniform',
                              padding='same'))
    model.add(GlobalAveragePooling1D())
    model.add(Dropout(rate=dropout_rate))
    model.add(Dense(op_units, activation=op_activation))
    return model

אימון המודל שלך

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

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

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

פרמטר למידה ערך
מדד דיוק
פונקציית אובדן נתונים – סיווג בינארי בינארי-צלב
פונקציית אובדן נתונים – סיווג מרובה כיתות sparse_categorical_crossentropy
כלי אופטימיזציה אדם

טבלה 2: פרמטרים ללמידה

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

היפר-פרמטר של אימון ערך
קצב למידה 1-3
תקופות 1000
גודל אצווה 512
עצירה מוקדמת פרמטר: val_loss, סבלנות: 1

טבלה 3: היפר-פרמטרים לאימון

קוד Keras הבא מטמיע את תהליך האימון באמצעות הפרמטרים שנבחרו בטבלאות 2 & 3 שלמעלה:

def train_ngram_model(data,
                      learning_rate=1e-3,
                      epochs=1000,
                      batch_size=128,
                      layers=2,
                      units=64,
                      dropout_rate=0.2):
    """Trains n-gram model on the given dataset.

    # Arguments
        data: tuples of training and test texts and labels.
        learning_rate: float, learning rate for training model.
        epochs: int, number of epochs.
        batch_size: int, number of samples per batch.
        layers: int, number of `Dense` layers in the model.
        units: int, output dimension of Dense layers in the model.
        dropout_rate: float: percentage of input to drop at Dropout layers.

    # Raises
        ValueError: If validation data has label values which were not seen
            in the training data.
    """
    # Get the data.
    (train_texts, train_labels), (val_texts, val_labels) = data

    # Verify that validation labels are in the same range as training labels.
    num_classes = explore_data.get_num_classes(train_labels)
    unexpected_labels = [v for v in val_labels if v not in range(num_classes)]
    if len(unexpected_labels):
        raise ValueError('Unexpected label values found in the validation set:'
                         ' {unexpected_labels}. Please make sure that the '
                         'labels in the validation set are in the same range '
                         'as training labels.'.format(
                             unexpected_labels=unexpected_labels))

    # Vectorize texts.
    x_train, x_val = vectorize_data.ngram_vectorize(
        train_texts, train_labels, val_texts)

    # Create model instance.
    model = build_model.mlp_model(layers=layers,
                                  units=units,
                                  dropout_rate=dropout_rate,
                                  input_shape=x_train.shape[1:],
                                  num_classes=num_classes)

    # Compile model with learning parameters.
    if num_classes == 2:
        loss = 'binary_crossentropy'
    else:
        loss = 'sparse_categorical_crossentropy'
    optimizer = tf.keras.optimizers.Adam(lr=learning_rate)
    model.compile(optimizer=optimizer, loss=loss, metrics=['acc'])

    # Create callback for early stopping on validation loss. If the loss does
    # not decrease in two consecutive tries, stop training.
    callbacks = [tf.keras.callbacks.EarlyStopping(
        monitor='val_loss', patience=2)]

    # Train and validate model.
    history = model.fit(
            x_train,
            train_labels,
            epochs=epochs,
            callbacks=callbacks,
            validation_data=(x_val, val_labels),
            verbose=2,  # Logs once per epoch.
            batch_size=batch_size)

    # Print results.
    history = history.history
    print('Validation accuracy: {acc}, loss: {loss}'.format(
            acc=history['val_acc'][-1], loss=history['val_loss'][-1]))

    # Save model.
    model.save('IMDb_mlp_model.h5')
    return history['val_acc'][-1], history['val_loss'][-1]

דוגמאות לקוד לאימון מודל הרצף: כאן.