言語処理100本ノック 2020 (Rev2)の「第9章: RNN, CNN」の85本目「双方向RNN・多層化」記録です。以前やったことあるし、Keras
の力で非常に簡単。過学習のまま結果を出していますが、途中で止めていれば前回ノックより結果良くなっています。
記事「まとめ: 言語処理100本ノックで学べることと成果」に言語処理100本ノック 2015についてはまとめていますが、追加で差分の言語処理100本ノック 2020 (Rev2)についても更新します。
参考リンク
リンク | 備考 |
---|---|
85_双方向RNN・多層化.ipynb | 回答プログラムのGitHubリンク |
言語処理100本ノック 2020 第9章: RNN, CNN | (PyTorchだけど)解き方の参考 |
【言語処理100本ノック 2020】第9章: RNN, CNN | (PyTorchだけど)解き方の参考 |
まとめ: 言語処理100本ノックで学べることと成果 | 言語処理100本ノックまとめ記事 |
環境
GPUを使わないと厳しいので、Google Colaboratory使いました。Pythonやそのパッケージでより新しいバージョンありますが、新機能使っていないので、プリインストールされているものをそのまま使っています。
種類 | バージョン | 内容 |
---|---|---|
Python | 3.7.12 | Google Colaboratoryのバージョン |
2.0.3 | Google Driveのマウントに使用 | |
tensorflow | 2.7.0 | ディープラーニングの主要処理 |
nltk | 3.2.5 | Tokenの辞書作成に使用 |
pandas | 1.1.5 | 行列に関する処理に使用 |
gensim | 3.6.0 | Google Newsデータセットの読込に使用 |
第8章: ニューラルネット
学習内容
深層学習フレームワークを用い,再帰型ニューラルネットワーク(RNN)や畳み込みニューラルネットワーク(CNN)を実装します.
85. 双方向RNN・多層化
順方向と逆方向のRNNの両方を用いて入力テキストをエンコードし,モデルを学習せよ.
\overleftarrow h_{T+1} = 0, \
\overleftarrow h_t = {\rm \overleftarrow{RNN}}(\mathrm{emb}(x_t), \overleftarrow h_{t+1}), \
y = {\rm softmax}(W^{(yh)} [\overrightarrow h_T; \overleftarrow h_1] + b^{(y)})
>ただし,$\overrightarrow h_t \in \mathbb{R}^{d_h}, \overleftarrow h_t \in \mathbb{R}^{d_h}$はそれぞれ,順方向および逆方向のRNNで求めた時刻$t$の隠れ状態ベクトル,${\rm \overleftarrow{RNN}}(x,h)$は入力$x$と次時刻の隠れ状態$h$から前状態を計算するRNNユニット,$W^{(yh)} \in \mathbb{R}^{L \times 2d_h}$は隠れ状態ベクトルからカテゴリを予測するための行列,$b^{(y)} \in \mathbb{R}^{L}$はバイアス項である.また,$[a; b]$はベクトル$a$と$b$の連結を表す。
>さらに,双方向RNNを多層化して実験せよ.
# 回答
## 回答結果
前回ノックとの精度比較です。**全データセットに対して精度が多少改善しています**。ただ、モデルを複雑にしたのでEaly Stoppingをしているため、38Epochで訓練終了にしています。前回ノックではEarly Stoppingしていないので、完全に平等な比較ではないです。
|データセット|Loss|正答率|
|:-:|:-:|:-:|
|訓練|0.384 :arrow_right: 0.374(-0.10)|86.7% :arrow_right: 86.8%(+0.1%)|
|検証|0.470 :arrow_right: 0.458(-0.12)|83.8% :arrow_right: 84.2%(+0.4%)|
|評価|0.446 :arrow_right: 0.423(-0.23)|85.1% :arrow_right: 85.3%(+0.2%)|
参考に評価データセットの結果です。
```:結果
42/42 [==============================] - 1s 12ms/step - loss: 0.4229 - acc: 0.8533
[0.422873854637146, 0.8532934188842773]
また、訓練時間が5分25秒から5分42秒へと微増。ただ、1 Epochの訓練時間は3倍程度に増えています。モデル複雑にしているので当然ですね。
回答プログラム 85_双方向RNN・多層化.ipynb
GitHubには確認用コードも含めていますが、ここには必要なものだけ載せています。
import numpy as np
import nltk
from gensim.models import KeyedVectors
import pandas as pd
import tensorflow as tf
from google.colab import drive
drive.mount('/content/drive')
BASE_PATH = '/content/drive/MyDrive/ColabNotebooks/ML/NLP100_2020/'
max_len = 0
vocabulary = []
w2v_model = KeyedVectors.load_word2vec_format(BASE_PATH+'07.WordVector/input/GoogleNews-vectors-negative300.bin.gz', binary=True)
def read_dataset(type_):
global max_len
global vocabulary
df = pd.read_table(BASE_PATH+'06.MachineLearning/'+type_+'.feature.txt')
df.info()
sr_title = df['title'].str.split().explode()
max_len_ = df['title'].map(lambda x: len(x.split())).max()
if max_len < max_len_:
max_len = max_len_
if len(vocabulary) == 0:
vocabulary = [k for k, v in nltk.FreqDist(sr_title).items() if v > 1]
else:
vocabulary.extend([k for k, v in nltk.FreqDist(sr_title).items() if v > 1])
y = df['category'].replace({'b':0, 't':1, 'e':2, 'm':3})
return df['title'], tf.keras.utils.to_categorical(y, dtype='int32') # 4値分類なので訓練・検証・テスト共通でone-hot化
X_train, y_train = read_dataset('train')
X_valid, y_valid = read_dataset('valid')
X_test, y_test = read_dataset('test')
# setで重複削除し、タプル形式に設定
tup_voc = tuple(set(vocabulary))
print(f'vocabulary size before removing duplicates: {len(vocabulary)}')
print(f'vocabulary size after removing duplicates: {len(tup_voc)}')
print(f'sample vocabulary: {tup_voc[:10]}')
print(f'max length is {max_len}')
vectorize_layer = tf.keras.layers.TextVectorization(
output_mode='int',
vocabulary=tup_voc,
output_sequence_length=max_len)
print(f'vocabulary size is {vectorize_layer.vocabulary_size()}')
embedding_dim = 300
hits = 0
misses = 0
embedding_matrix = np.zeros((vectorize_layer.vocabulary_size(), embedding_dim))
for i, word in enumerate(vectorize_layer.get_vocabulary()):
try:
embedding_matrix[i] = w2v_model.get_vector(word)
hits += 1
# Words not found in embedding index will be all-zeros.
# This includes the representation for "padding" and "OOV"
except:
misses += 1
if misses < 7: # Show 6 words as example
print(word)
print("Converted %d words (%d misses)" % (hits, misses))
embedding_layer = tf.keras.layers.Embedding(
vectorize_layer.vocabulary_size(),
embedding_dim,
embeddings_initializer=tf.keras.initializers.Constant(embedding_matrix),
trainable=False
)
model = tf.keras.models.Sequential()
model.add(tf.keras.Input(shape=(1,), dtype=tf.string))
model.add(vectorize_layer)
model.add(embedding_layer)
model.add(tf.keras.layers.Bidirectional(tf.keras.layers.GRU(50, return_sequences=True)))
model.add(tf.keras.layers.Bidirectional(tf.keras.layers.GRU(50)))
model.add(tf.keras.layers.Dense(4, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['acc'])
model.summary()
model.fit(X_train, y_train, epochs=100, validation_data=(X_valid, y_valid),
callbacks=[tf.keras.callbacks.EarlyStopping(patience=5, restore_best_weights=True)])
model.evaluate(X_test, y_test)
回答解説
双方向と多層化
Kerasだと非常に簡単です。Bidrectional
を加えて双方向にし、レイヤを2つ重ねるだけです。そして、1層目にはreturn_sequences=True
を追加します(ここを忘れていたため、エラーが出て少し悩みました)。
model.add(tf.keras.layers.Bidirectional(tf.keras.layers.GRU(50, return_sequences=True)))
model.add(tf.keras.layers.Bidirectional(tf.keras.layers.GRU(50)))
Early Stopping
100Epochでは過学習がひどかったので、EalyStoppingのコールバック関数を使っています。検証Lossが下げ止まって5Epoch経ったら最良の結果を残すようにしています。
model.fit(X_train, y_train, epochs=100, validation_data=(X_valid, y_valid),
callbacks=[tf.keras.callbacks.EarlyStopping(patience=5, restore_best_weights=True)])