TensorFlow 機械学習ガイド : テキスト分類 (4) モデルの構築、訓練そして評価 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 08/23/2018
* 本ページは、developers.google.com サイトの Machine Learning Guides : Text classification の以下のページを
翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
このセクションでは、モデルの構築、訓練そして評価に向けて作業します。Step 3 では、S/W 比率を使用して、n-gram モデルかシークエンス・モデルを使用するかを選択しています。今、分類器アルゴリズムを書いてそれを訓練するときです。私達はこのために tf.keras API を伴う TensorFlow を使用します。
Keras で機械学習モデルを構築することは層、データ処理ビルディングブロックを一緒に組み立てることが総てで、Lego ブロックを組み立てるようなものです。これらの層は入力上で遂行することを望む変換のシークエンスを指定することを可能にします。私達の学習アルゴリズムは単一のテキスト入力を取り単一の分類を出力しますので、Sequential モデル API を使用して層の線形スタックを作成することができます。
Figure 9: 層の線形スタック
入力層と中間層は、n-gram からシークエンス・モデルを構築するかに依拠して、異なる構成が成されます。しかしモデルのタイプにかかわりなく、最後の層は与えられた問題に対して同じです。
最後の層を構築する
2 クラスだけを持つ (二値分類) とき、モデルは単一の確率スコアを出力すべきです。例えば、与えられたサンプルに対して 0.2 の出力は「このサンプルがクラス 0 である 20% の信頼度とクラス 1 である 80 % の信頼度」を意味します。そのような確率スコアを出力するためには、最後の層の活性化関数は sigmoid 関数であるべきで、モデルを訓練するために使用される損失関数は binary cross-entropy (二値交差エントロピー) であるべきです (Figure10, 左側参照)。
2 クラス以上ある (多クラス分類) ときは、モデルはクラス毎に一つの確率スコアを出力するべきです。スコアの総計は 1 であるはずです。例えば、{0: 0.2, 1: 0.7, 2: 0.1} の出力は、「このサンプルがクラス 0 である 20% の信頼度、クラス 1 である 70% の信頼度、そしてクラス 2 である 10 % の信頼度」を意味します。これらのスコアを出力するためには、最後の層の活性化関数は softmax であるべきで、モデルを訓練するために使用される損失関数は categorical cross-entropy (カテゴリカル交差エントロピー) であるべきです (Figure 10, 右側参照)。
Figure 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-gram モデルとシークエンス・モデルに対する残りのモデル層の作成をウォークスルーします。
S/W 比率が小さいとき、n-gram モデルはシークエンス・モデルよりもより良く遂行することを私達は見出しました。シークエンス・モデルは巨大な数の小さい、密ベクトルがあるときにより良いです。これは埋め込み関係が密空間で学習されて、これは多くのサンプルに渡りベストに発生するからです。
n-gram モデルを構築する [オプション A]
トークンを独立的に (単語順序を考慮しないで) 処理するモデルを n-gram モデルとして参照します。(ロジスティック回帰を含む) 単純な多層パーセプトロン、勾配ブースティングマシンとサポートベクターマシンは総てこのカテゴリに入ります ; それらはテキスト順序についてのどのような情報も活用しません。
上で言及した n-gram モデルの幾つかのパフォーマンスを比較して他の選択肢よりも 多層パーセプトロン (MLP) が良く遂行する ことを観察しました。MLP は定義して理解するために単純で、良い精度を提供し、そして比較的小さい計算を必要とします。
次のコードは 2-層 MLP モデルを tf.keras で定義し、正則化のために 2 つの Dropout 層を追加しています (訓練サンプルへの overfitting を防ぐため)。
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 クラスを含みます。データはこれらのモデルのためにシークエンス・ベクトルとして前処理されます。
シークエンス・モデルは学習するためのより巨大なパラメータ数を一般的に持ちます。これらのモデルの最初の層は埋め込み層で、これは密ベクトル空間で単語間の関係を学習します。単語関係の学習は多くのサンプルに渡りベストに動作します。
与えられたデータセットの単語はおそらくそのデータセットに固有ではないでしょう。そのため他のデータセットを使用して私達のデータセットの単語間の関係を学習できます。それをするために、他のデータセットから学習した埋め込みを私達の埋め込み層に転移する (= transfer) ことができます。これらの埋め込みは事前訓練された埋め込みとして参照されます。事前訓練された埋め込みの使用は学習過程においてモデルに有利なスタートを与えます。
GloVe のように、巨大なコーパスを使用して訓練された利用可能な事前訓練された埋め込みがあります。GloVe は複数のコーパス (主として Wikipedia) 上で訓練されました。GloVe 埋め込みのバージョンを使用して私達のシークエンス・モデルを訓練することをテストし、事前訓練された埋め込みの重みを凍結してネットワークの残りだけを訓練した場合、モデルは上手く遂行しないことを私達は観察しました。これは埋め込み層が訓練されたコンテキストが、それを使用しているコンテキストとは異なるためでしょう。
Wikipedia データ上で訓練された GloVe 埋め込みは IMDb データセットの言語パターンと合致しません。推定される関係は何某かの更新が必要かもしれません — i.e., 埋め込み重みはコンテキスト的な調整が必要かもしれません。これを 2 ステージで行ないます :
- 最初の実行では、埋め込み層の重みは凍結して、ネットワークの残りに学習することを許容します。この実行の最後には、モデル重みは非初期化値よりも遥かに良い状態に達します。2 番目の実行では、埋め込み層にもまた学習することを許容し、ネットワークの総ての重みに微調整 (= fine adjustments) を行ないます。この過程を再調整された埋め込みの使用として参照します。
- 再調整された埋め込みはより良い精度を生成します。けれども、これはネットワークを訓練するために必要な増大する計算パワーと引き換えに生じています。十分な数のサンプルが与えられたとき、スクラッチからでも上手く埋め込みを学習できるでしょう。S/W > 15K については、スクラッチから開始しても再調整された埋め込みとおよそ同じ精度を効果的に生成します。
私達はモデル・アーキテクチャを多様にして、CNN, sepCNN (Depthwise Separable Convolutional Network), RNN (LSTM & GRU), CNN-RNN, そして stacked RNN のような異なるシークエンス・モデルを比較しました。 しばしばよりデータ効率的で計算効率的な畳み込みネットワークの変種 – sepCNN が他のモデルよりもより良く遂行することを見出しました。
Note: RNN はユースケースの小さいサブセットだけに関連します。QRNN や RNNs with Attention のようなモデルは試していません、何故ならばそれらの精度改善はより高い計算コストによる補正だからです。
次のコードは 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 つの主要なパラメータがあります (Table 2 参照)。
- メトリック: メトリックを使用してモデルのパフォーマンスをどのように測定するか。私達の実験では accracy (精度) をメトリックとして使用しました。
- 損失関数: 訓練プロセスがネットワーク重みを調整することにより最小化しようと試みる損失値を計算するために使用される関数。分類問題に対しては、cross-entropy 損失が上手く動作します。
- Optimizer: 損失関数の出力に基づいてどのようにネットワーク重みが更新されるかを決定する関数です。私達の実験では一般的な Adam optimizer を使用しました。
Keras では、これらの学習パラメータを compile メソッドを使用してモデルに渡します。
学習パラメータ | 値 |
Metric | accuracy |
損失関数 – binary classification | binary_crossentropy |
損失関数 – multi class classification | sparse_categorical_crossentropy |
Optimizer | adam |
Table 2: 学習パラメータ
実際の訓練は fit メソッドの使用で発生します。データセットのサイズに依拠して、これは殆どの計算サイクルがそこで費やされるメソッドです。各訓練反復で、訓練データから batch_size 数のサンプルが損失を計算するために使用され、この値を基に重みが一度だけ更新されます。モデルが訓練データセット全体を一度見れば訓練プロセスは (1 つの) エポックを完了します。各エポックの最後に、モデルがどのように学習しているかを評価するために検証データセットを使用します。規定のエポック数の間データセットを使用して訓練を繰り返します。これを stopping early によって最適化しても良いです、検証精度が連続するエポック間で安定するとき、モデルはもはや訓練されないことを示します。
訓練ハイパーパラメータ | 値 |
Learning rate | 1e-3 |
Epochs | 1000 |
Batch size | 512 |
Early stopping | parameter: val_loss, patience: 1 |
Table 3: 訓練ハイパーパラメータ
次の Keras コードは上の Table 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]
以上