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

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

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

الحزمة الخطية للطبقات

الشكل 9: حزمة الطبقات الخطية

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

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

عندما يكون لدينا فئتين (تصنيف ثنائي)، يجب أن يُنشئ نموذجنا نتيجة احتمال واحدة. على سبيل المثال، ناتج 0.2 لعينة إدخال معينة يعني "ثقة بنسبة 20%" في أن هذا النموذج في الفئة الأولى (الفئة 1) و80% أنه في الفئة الثانية (الفئة 0). ولإخراج نتيجة الاحتمال هذه، يجب أن تكون وظيفة التفعيل للطبقة الأخيرة عبارة عن دالة {1{/1 1 الضمير، 1{ 1 * ص 1} ص 1 ) دق 1{ 1 : ص هـ 1)

وعندما يكون هناك أكثر من فئتين (تصنيف متعدد الفئات)، يجب أن يعرِض نموذجنا نتيجة احتمال واحدة لكل صف. يجب أن يكون مجموع هذه النقاط 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-gram تحقّق أداءً أفضل من نماذج التسلسل. تكون نماذج التسلسلات أفضل عندما يكون هناك عدد كبير من المتجهات الكثيفة. ويرجع ذلك إلى أن تضمين العلاقات يتم تعلّمه في المساحة الكثيفة، وهذا يحدث بشكل أفضل على العديد من النماذج.

تصميم نموذج n-gram [الخيار (أ)]

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

قارننا أداء بعض نماذج n-gram التي ذكرناها أعلاه ولاحظنا أنّ الجهات متعددة الطبقات (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 &amp؛ GRU) وCNN-RNN وRNN المكدّس، و الفيديو يتفاوت من بين بُنى النماذج. وقد تبيّن لنا أنّ بروتوكول sepCNNs هو نوع من الشبكة التكيفية، وغالبًا ما يكون أكثر كفاءة من حيث استهلاك البيانات والحوسبة، ويحقّق أداءً أفضل من النماذج الأخرى.

ينشئ الرمز التالي نموذج 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.

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

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

مَعلمة التعلُّم القيمة
المقياس الدقة
دالة الفقدان - التصنيف الثنائي برنامج_ثنائي
دالة الفقدان - التصنيف المتعدد الفئات متناثرة_بإنجاز_متقاطع
المحسِّن آدم

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

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

معلمة التدريب الزائد القيمة
معدّل التعلُّم من 1 إلى 3
الحقبات 1,000
حجم الدفعة 512
إيقاف مبكر المعلمة: val_lum، الصبر: 1

الجدول 3: مَعلمات التدريب الزائد

ينفِّذ رمز Keras التالي عملية التدريب باستخدام المعلَمات التي تم اختيارها في الجدولَين 2 وamp؛ 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]

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