الخطوة 4: إنشاء نموذجك وتدريبه وتقييمه

في هذا القسم، سنعمل على إنشاء نموذجنا وتدريبه وتقييمه. في الخطوة الثالثة، اخترنا استخدام نموذج ن غرام أو نموذج تسلسل، باستخدام نسبة S/W. والآن، حان الوقت لكتابة خوارزمية التصنيف وتدريبها. سنستخدم TensorFlow مع واجهة برمجة التطبيقات tf.keras.

إن إنشاء نماذج التعلم الآلي باستخدام Keras يدور حول تجميع الطبقات والكتل البرمجية الإنشائية لمعالجة البيانات معًا كما هو الحال عند تجميع مكعبات الليغو. تسمح لنا هذه الطبقات بتحديد تسلسل التحولات التي نريد إجرائها على المدخلات. بينما تأخذ خوارزمية التعلم لدينا إدخالاً نصيًا واحدًا وتُخرج تصنيفًا واحدًا، يمكننا إنشاء حزمة خطية من الطبقات باستخدام واجهة برمجة التطبيقات للنموذج التسلسلي.

مكدس خطي من الطبقات

الشكل 9: مكدّس خطي للطبقات

سيتم بناء طبقة الإدخال والطبقات الوسطى بشكل مختلف، اعتمادًا على ما إذا كنا نبني نموذج ن جرام أو نموذج تسلسل. ولكن بغض النظر عن نوع النموذج، فإن الطبقة الأخيرة ستكون هي نفسها لمشكلة معينة.

إنشاء الطبقة الأخيرة

عندما يكون لدينا فئتان فقط (تصنيف ثنائي)، يجب أن ينتج عن نموذجنا درجة احتمالية واحدة. على سبيل المثال، يعني إخراج 0.2 لعيّنة إدخال معيّنة "ثقة بنسبة 20% في أن هذه العينة في الفئة الأولى (الفئة 1)، و80% منها في الفئة الثانية (الفئة 0)." للحصول على درجة الاحتمالية هذه، يجب أن تكون دالة التفعيل للطبقة الأخيرة دالة سينية، ودالة الخسارة والثنائية لتدريب النموذج. (اطّلِع على الشكل 10، على اليمين).

عندما يكون هناك أكثر من فئتين (التصنيف متعدد الفئات)، ينبغي أن ينتج عن نموذجنا درجة واحدة للاحتمالية لكل فئة. يجب أن يكون مجموع هذه الدرجات 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

يستعرض القسمان التاليان عملية إنشاء طبقات النموذج المتبقية لنماذج الغرام ونماذج التسلسل.

عندما تكون نسبة S/W صغيرة، يكون أداء نماذج الن جرام أفضل من نماذج التسلسل. تكون نماذج التسلسل أفضل عندما يكون هناك عدد كبير من المتجهات الصغيرة والكثيفة. وذلك لأن علاقات التضمين يتم تعلمها في مساحات كثيفة، وهذا يحدث بشكل أفضل على العديد من العينات.

إنشاء نموذج n-gram [الخيار A]

نشير إلى النماذج التي تعالج الرموز المميزة بشكل مستقل (مع مراعاة ترتيب الكلمات في الحساب) كنماذج ن غرام. تندرج كل المحاليل المرئية البسيطة متعددة الطبقات (بما في ذلك الانحدار اللوجستي آلات تعزيز التدرج ونماذج متجهات الدعم) ضمن هذه الفئة، ولا يمكنها استخدام أي معلومات حول ترتيب النصوص.

لقد قارنّا أداء بعض نماذج الن جرام المذكورة أعلاه ولاحظنا أن المؤشرات متعددة الطبقات (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

إنشاء نموذج تسلسلي [الخيار B]

نشير إلى النماذج التي يمكن أن تتعلم من بجوار الرموز المميزة كنماذج تسلسلية. ويتضمن ذلك فئتي نماذج 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).

  • المقياس: كيفية قياس أداء النموذج باستخدام مقياس استخدمنا الدقة كمقياس في تجاربنا.
  • دالة الخسارة: دالة تُستخدم لحساب قيمة الخسارة التي تحاول عملية التدريب بعد ذلك تقليلها عن طريق ضبط أوزان الشبكة. بالنسبة لمشكلات التصنيف، تكون نسبة فقدان الإنتروبيا الأفضل أداءً.
  • أداة التحسين: دالة تحدّد الطريقة التي سيتم بها تعديل القيم المرجحة للشبكة استنادًا إلى ناتج دالة الخسارة. استخدمنا مُحسِّن آدم الشهير في تجاربنا.

في Keras، يمكننا تمرير معلَمات التعلُّم هذه إلى نموذج باستخدام طريقة compile.

الجدول 2: مَعلمات التعلُّم

مَعلمة التعلُّم القيمة
المقياس الدقة
دالة الخسارة - التصنيف الثنائي binary_crossentropy
دالة الخسارة - التصنيف متعدد الفئات sparse_categorical_crossentropy
مُشاهد عملي adam

يحدث التدريب الفعلي باستخدام طريقة fit. اعتمادًا على حجم مجموعة البيانات الخاصة بك، هذه هي الطريقة التي سيتم بها قضاء معظم دورات الحوسبة. في كل تكرار للتدريب، يتم استخدام عدد batch_size من العينات من بيانات التدريب لحساب الخسارة، ويتم تعديل القيم التقديرية مرة واحدة، بناءً على هذه القيمة. وتكمل عملية التطبيق epoch بعد أن يطّلع النموذج على مجموعة بيانات التدريب بالكامل. وفي نهاية كل حقبة، نستخدم مجموعة بيانات التحقق من الصحة لتقييم مدى جودة تعلم النموذج. نكرر التدريب باستخدام مجموعة البيانات لعدد محدد مسبقًا من الحقب. ويمكن تحسين ذلك من خلال التوقف مبكرًا عندما تستقر دقة التحقّق بين الفترات المتتالية، ما يشير إلى أنّ النموذج لم يعُد قيد التدريب.

المَعلمة الفائقة للتدريب القيمة
معدّل التعلّم 1e-3
الحقبات 1,000
حجم الدفعة 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]

يُرجى الاطّلاع على أمثلة الرموز لتدريب نموذج التسلسل هنا.