ステップ 4: モデルの構築、トレーニング、評価

このセクションでは、モデルの構築、トレーニング、評価を行います。ステップ 3 では、S/W 比率を使用して、N グラム モデルまたはシーケンス モデルを選択しました。今度は、分類アルゴリズムを記述してトレーニングします。このために、TensorFlowtf.keras API を使用します。

Keras での ML モデルの構築は、レゴブロックを組み立てるのと同じように、レイヤとデータ処理構成要素を組み立てることです。これらのレイヤを使用すると、入力に対して実行する変換の順序を指定できます。この学習アルゴリズムは単一のテキスト入力を受け取り、1 つの分類を出力するため、Sequential model API を使用してレイヤの線形スタックを作成できます。

レイヤの線形スタック

図 9: レイヤの線形スタック

入力層と中間層の構成は、N グラムとシーケンス モデルのどちらを作成するかによって異なります。ただし、モデルタイプに関係なく、特定の問題の最後のレイヤは同じです。

最後のレイヤを作成する

クラスが 2 つ(バイナリ分類)しかない場合、モデルは 1 つの確率スコアを出力します。たとえば、特定の入力サンプルに対して 0.2 を出力すると、「このサンプルが第 1 クラス(クラス 1)にあるという信頼度が 20%、第 2 クラス(クラス 0)にあるという信頼度が 80% になります。このような確率スコアを出力するには、最後のレイヤの活性化関数シグモイド関数で、モデルのバイナリ トレーニングに使用される損失関数が(左の図 10 を参照)。

3 つ以上のクラス(マルチクラス分類)がある場合、モデルはクラスごとに 1 つの確率スコアを出力する必要があります。これらのスコアの合計は 1 になります。たとえば、{0: 0.2, 1: 0.7, 2: 0.1} の出力は、「このサンプルがクラス 0 であることの信頼度 20%、クラス 1 にあることの 70%、クラス 2 にあることの 10% の信頼度」を意味します。これらのスコアを出力するには、最後のレイヤの活性化関数をソフトマックスとし、モデルのトレーニングに使用する損失関数をカテゴリ交差エントロピーにする必要があります。(右の図 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

次の 2 つのセクションでは、N グラム モデルとシーケンス モデルの残りのモデルレイヤの作成について説明します。

S/W 比が小さい場合、N グラム モデルはシーケンス モデルよりも優れたパフォーマンスを発揮します。シーケンス モデルは、小規模で密なベクトルが大量にある場合に適しています。これは、エンベディング関係が密な空間で学習され、多くのサンプルで最も効果を発揮するためです。

N グラム モデルの構築 [オプション A]

トークンを個別に処理する(単語の順序は考慮しない)モデルを N グラムモデルと呼びます。単純な多層パーセプトロン(ロジスティック回帰勾配ブースティング マシンサポート ベクター マシン モデルなど)はすべてこのカテゴリに分類され、テキストの順序に関する情報は使用できません。

前述の N グラム モデルのパフォーマンスを比較したところ、多層パーセプトロン(MLP)が一般的に他のオプションよりも優れたパフォーマンスを発揮することがわかりました。MLP は定義と理解が簡単で、高い精度を実現し、計算量を比較的少なくできます。

次のコードは、tf.keras で 2 層の MLP モデルを定義し、トレーニング サンプルの過学習を防ぐために、正則化用の 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 は複数のコーパス(主に Wikipedia)でトレーニングされています。GloVe エンベディングのバージョンを使用してシーケンス モデルのトレーニングをテストし、事前トレーニング済みのエンベディングの重みを固定して、ネットワークの残りの部分だけをトレーニングすると、モデルのパフォーマンスが低下することがわかりました。これは、エンベディング レイヤがトレーニングされたコンテキストが、使用したコンテキストと異なる可能性があるためです。

Wikipedia のデータでトレーニングされた GloVe のエンベディングは、IMDb データセットの言語パターンと一致しない場合があります。推定される関係は更新が必要になる場合があります。つまり、エンベディングの重みにコンテキスト チューニングが必要になる場合があります。このプロセスは、次の 2 段階で行われます。

  1. 最初の実行では、エンベディング レイヤの重みを固定して、ネットワークの残りの部分が学習できるようにします。この実行が終了すると、モデルの重みは、初期化されていない値よりもはるかに良い状態に到達します。2 回目の実行では、エンベディング レイヤも学習して、ネットワーク内のすべての重みを微調整できます。このプロセスをファインチューニングされたエンベディングの使用と呼びます。

  2. エンベディングをファインチューニングすると精度が向上します。ただし、これにはネットワークのトレーニングに必要なコンピューティング能力が増加します。十分な数のサンプルがあれば、エンベディングをゼロから学習する場合とまったく同じことができます。S/W > 15K については、ゼロから始めると、ファインチューニングされたエンベディングを使用した場合とほぼ同じ精度が得られることがわかりました。

CNN、sepCNN、RNN(LSTM & GRU)、CNN-RNN、スタック RNN などのさまざまなモデル アーキテクチャを比較しました。sepCNN は、多くの場合、データ効率とコンピューティング効率が高い畳み込みネットワークのバリアントであり、他のモデルよりもパフォーマンスが高いことがわかりました。

次のコードは、4 レイヤの 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

モデルのトレーニング

モデル アーキテクチャを構築したので、モデルをトレーニングする必要があります。トレーニングでは、モデルの現在の状態に基づいて予測を行い、予測の誤りの程度を計算し、ネットワークの重みまたはパラメータを更新して、このエラーを最小限に抑え、モデルの予測精度を高めます。モデルが収束して学習できなくなるまで、このプロセスを繰り返します。このプロセスでは、3 つの主要なパラメータを選択します(表 2 を参照)。

  • 指標: 指標を使用してモデルのパフォーマンスを測定する方法。テストでは指標として精度を使用しました。
  • 損失関数: 損失値の計算に使用される関数。損失値は、トレーニング プロセスでネットワークの重みを調整して最小化しようとします。分類問題には、交差エントロピー損失が適しています。
  • オプティマイザー: 損失関数の出力に基づいてネットワークの重みを更新する方法を決定する関数。テストでは一般的な Adam オプティマイザーを使用しました。

Keras では、compile メソッドを使用してこれらの学習パラメータをモデルに渡すことができます。

表 2: 学習パラメータ

学習パラメータ 価値
指標 accuracy
損失関数 - バイナリ分類 binary_crossentropy
損失関数 - マルチクラス分類 sparse_categorical_crossentropy
オプティマイザー adam

実際のトレーニングは fit メソッドを使用して行われます。データセットのサイズによっては、この方法でほとんどのコンピューティング サイクルが消費されます。トレーニングの反復処理ごとに、トレーニング データから batch_size 個のサンプルを使用して損失が計算され、この値に基づいて重みが 1 回更新されます。モデルがトレーニング データセット全体を確認すると、トレーニング プロセスは epoch を完了します。各エポックの終了時に、検証データセットを使用してモデルの学習状況を評価します。このデータセットを使用して 事前に決められたエポック数でトレーニングを連続するエポック間で検証精度が安定したら、早期に停止することでこれを最適化できます。これは、モデルがトレーニングされなくなったことを示します。

ハイパーパラメータのトレーニング 価値
学習率 1 ~ 3
エポック 1000
バッチサイズ 512
早期停止 パラメータ: Val_loss, latency: 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]

シーケンス モデルをトレーニングするためのコード例については、こちらをご覧ください。