Шаг 4: Создайте, обучите и оцените свою модель

В этом разделе мы будем работать над созданием, обучением и оценкой нашей модели. На шаге 3 мы решили использовать либо модель n-грамм, либо модель последовательности, используя наше соотношение S/W . Теперь пришло время написать наш алгоритм классификации и обучить его. Для этого мы будем использовать TensorFlow с API tf.keras .

Создание моделей машинного обучения с помощью Keras — это сборка слоев, строительных блоков для обработки данных, так же, как мы собираем кубики Lego. Эти слои позволяют нам указать последовательность преобразований, которые мы хотим выполнить с нашим вводом. Поскольку наш алгоритм обучения принимает один входной текст и выводит одну классификацию, мы можем создать линейный стек слоев с помощью API последовательной модели .

Линейный стек слоев

Рисунок 9: Линейный набор слоев

Входной слой и промежуточные слои будут построены по-разному, в зависимости от того, строим ли мы n-грамму или модель последовательности. Но независимо от типа модели последний слой будет одинаковым для данной задачи.

Создание последнего слоя

Когда у нас есть только 2 класса (бинарная классификация), наша модель должна выводить одну оценку вероятности. Например, вывод 0.2 для данной входной выборки означает «20% уверенность, что эта выборка относится к первому классу (класс 1), 80%, что она относится ко второму классу (класс 0)». Чтобы вывести такой показатель вероятности, функция активации последнего слоя должна быть сигмовидной , а функция потерь, используемая для обучения модели, должна быть бинарной кросс-энтропийной (см. рис. 10 слева).

Когда имеется более 2 классов (многоклассовая классификация), наша модель должна выводить одну оценку вероятности для каждого класса. Сумма этих оценок должна быть равна 1. Например, вывод {0: 0.2, 1: 0.7, 2: 0.1} означает «20% уверенность в том, что этот образец относится к классу 0, 70% — что он относится к классу 1 и 10% — к классу 0». %, что он относится к классу 2». Чтобы вывести эти оценки, функция активации последнего слоя должна быть softmax, а функция потерь, используемая для обучения модели, должна быть категориальной кросс-энтропией. (См. рис. 10 , справа).

Последний слой

Рисунок 10: Последний слой

Следующий код определяет функцию, которая принимает количество классов в качестве входных данных и выводит соответствующее количество единиц уровня (1 единица для двоичной классификации; в противном случае 1 единица для каждого класса) и соответствующую функцию активации:

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-грамм и моделей последовательностей.

Мы обнаружили, что когда отношение S/W невелико, модели n-грамм работают лучше, чем модели последовательностей. Модели последовательности лучше, когда есть большое количество маленьких, плотных векторов. Это связано с тем, что отношения встраивания изучаются в плотном пространстве, и лучше всего это происходит на множестве выборок.

Построить n-граммовую модель [Вариант A]

Мы называем модели, обрабатывающие токены независимо (без учета порядка слов), моделями n-грамм. Под эту категорию попадают простые многослойные персептроны (включая логистическую регрессию ), машины повышения градиента и модели машин опорных векторов ; они не могут использовать какую-либо информацию о порядке текста.

Мы сравнили производительность некоторых моделей n-грамм, упомянутых выше, и обнаружили, что многослойные персептроны (MLP) обычно работают лучше , чем другие варианты. MLP просты для определения и понимания, обеспечивают хорошую точность и требуют относительно небольших вычислений.

Следующий код определяет двухуровневую модель MLP в tf.keras, добавляя пару слоев Dropout для регуляризации (чтобы предотвратить переоснащение обучающих выборок).

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 .

  • Метрика : Как измерить производительность нашей модели с помощью метрики . Мы использовали точность в качестве метрики в наших экспериментах.
  • Функция потерь : функция, используемая для вычисления значения потерь, которое затем пытается минимизировать процесс обучения путем настройки весов сети. Для задач классификации хорошо работает кросс-энтропийная потеря.
  • Оптимизатор : функция, которая решает, как веса сети будут обновляться на основе выходных данных функции потерь. В своих экспериментах мы использовали популярный оптимизатор 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]

Примеры кода для обучения модели последовательности можно найти здесь .