#はじめに
TensorFlow2.0 Advent Calendar 2019の7日目のエントリで、tf.dataを使ったlivedoorコーパスの分かち書きとエンコーダ作成を行いました。
今エントリは前回の続きとして、tf.keras.layers.LSTM
を使用してlivedoorコーパスの分類モデルを作成します。
#分類モデルについて
livedoorコーパスは全部で9つのジャンルに分かれていますが、今回は単純な分類モデルとしてテキストがdokujo-tsushin
か否かの分類モデルを作成します。
#分かち書きデータセット作成
今回のデータセット作成は、大きく分けて以下の流れになります。
- ラベル付きの分かち書きデータセットを用意
- データセットからトークナイザ、エンコーダを作成
- エンコーダを使用して分かち書きテキストをエンコード
ラベル付きの分かち書きデータセットを用意
解凍済みlivedoorコーパスからdokujo-tsushin
とsports-watch
を残し、他をすべて削除します。
$ ls text/
dokujo-tsushin sports-watch
前回の記事を参考に、ラベル付きのデータセットを作成します。
map
にテキストとラベルを渡すところが前回と異なっています。
import os
import tensorflow as tf
import tensorflow_datasets as tfds
import fugashi
import numpy as np
tagger = fugashi.Tagger('-Owakati')
def parse(text):
'''
データセットのテキストを1行ごとに分かち書き
'''
return tagger.parse(text.numpy().decode('utf-8')).split("\n")[0]
@tf.function
def parse_text(text,label):
parsed = tf.py_function(parse,[text],[tf.string])
return parsed[0],label
text_datasets = []
text_dir = os.path.join(os.getcwd(),"text")
for d in tf.compat.v1.gfile.ListDirectory(text_dir):
data_dir = os.path.join(text_dir,d)
label = int(d == "dokujo-tsushin") # ディレクトリがdokujo-tsushinだったらTrue
if os.path.isdir(data_dir):
for file_name in tf.compat.v1.gfile.ListDirectory(data_dir):
text_dataset = tf.data.TextLineDataset(os.path.join(data_dir,file_name)).map(lambda ex: parse_text(ex,label)) # 分かち書きコーパスとラベルのペアをデータセットに指定する
text_datasets.append(text_dataset)
# テキスト単位の配列に変換する
parsed_dataset = text_datasets[0]
for text_dataset in text_datasets[1:]:
parsed_dataset = parsed_dataset.concatenate(text_dataset)
分かち書き結果とラベルが返却されることを確認します。
for txt,label in parsed_dataset.take(3):
print('{} {}'.format(txt.numpy().decode('utf-8'), label))
http :// news . livedoor . com / article / detail / 6317287 / 1
2012 - 02 - 27 T 14 : 54 : 00 + 0900 1
話題 の 格言 『 ボーダー を 着る 女 は 、 95 % モテ ない ! 』 は 本当 か ? 1
エンコーダ、トークナイザを用意
文字列をIDに変換するためのエンコーダとトークナイザを用意します。
トークナイザを用意します。
tokenizer = tfds.features.text.Tokenizer()
トークナイザを使い、データセットからエンコーダを作成します。
vocabulary_set = set()
for line,i in parsed_dataset:
some_tokens = tokenizer.tokenize(line.numpy())
vocabulary_set.update(some_tokens)
encoder = tfds.features.text.TokenTextEncoder(vocabulary_set, tokenizer=tokenizer)
encoder.vocab_size
33784
エンコーダを使用して分かち書きテキストをエンコード
データセットはmap
のチェーンが作れますので、map
を使って分かち書きデータセットをエンコードします。
def dataset_mapper(token,label):
token = encoder.encode(token.numpy())
label= np.array([label])
return token,label
@tf.function
def tf_encode(token,label):
return tf.py_function(dataset_mapper, [token,label], [tf.int64, tf.int64])
new_dataset = parsed_dataset.map(tf_encode)
バッチサイズを指定し、データをバッチサイズ単位に分割します。
BATCH_SIZE = 128
new_dataset = new_dataset.padded_batch(BATCH_SIZE, padded_shapes=([-1],[-1]))
new_dataset = new_dataset.prefetch(tf.data.experimental.AUTOTUNE)
きちんと分割できているようです。
text_batch, label_batch = next(iter(new_dataset))
print(text_batch.shape)
print(label_batch.shape)
(128, 155)
(128, 1)
LSTMモデルの作成
データセットの準備ができたので、モデル作成に入ります。
Tensorflow2.0はkerasと統合されているので、kerasのわかりやすいインターフェイスで書けるのが素晴らしいです。
X = tf.keras.Input(shape=(None,), batch_size=BATCH_SIZE)
embedded = tf.keras.layers.Embedding(encoder.vocab_size, 128)(X)
lstm = tf.keras.layers.Bidirectional(
tf.keras.layers.LSTM(128, dropout=0.4, recurrent_dropout=0.4)
)(embedded)
fully_connected = tf.keras.layers.Dense(units=256, activation='relu')(lstm)
Y = tf.keras.layers.Dense(1, activation='sigmoid',name='final_layer')(fully_connected)
model = tf.keras.Model(inputs=X, outputs=Y)
model.compile(loss='binary_crossentropy',
optimizer=tf.keras.optimizers.Adam(1e-4),
metrics=['accuracy'])
モデルのレイヤを確認します。
model.summary()
Model: "model_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_2 (InputLayer) [(128, None)] 0
_________________________________________________________________
embedding_1 (Embedding) (128, None, 128) 4324352
_________________________________________________________________
bidirectional_1 (Bidirection (128, 256) 263168
_________________________________________________________________
dense_1 (Dense) (128, 256) 65792
_________________________________________________________________
final_layer (Dense) (128, 1) 257
=================================================================
Total params: 4,653,569
Trainable params: 4,653,569
Non-trainable params: 0
_________________________________________________________________
モデルが完成したので、早速学習を開始します。
model.fit(new_dataset,epochs=5)
Epoch 1/5
298/298 [==============================] - 140s 471ms/step - loss: 0.5049 - accuracy: 0.8531
Epoch 2/5
298/298 [==============================] - 137s 459ms/step - loss: 0.7768 - accuracy: 0.5536
Epoch 3/5
298/298 [==============================] - 245s 824ms/step - loss: 0.6405 - accuracy: 0.6531
Epoch 4/5
298/298 [==============================] - 327s 1s/step - loss: 0.5248 - accuracy: 0.8055
Epoch 5/5
298/298 [==============================] - 417s 1s/step - loss: 0.4686 - accuracy: 0.8019
エポック数が少なすぎるため、あまり精度の出ませんでしたが気にせず次に進みます。
Attention
Tensorflow 2.0チュートリアルはサンプルと知見の宝庫でとても素晴らしく、チュートリアルのAttention実装を参考にレイヤを作成します。
チュートリアルにはAttentionはもちろん、他にも様々なタスクの実装サンプルが記述されており、有志の方々が翻訳された日本語版も大変わかりやすいのでまだ読んでいない方は是非ご一読ください。
class Attention(tf.keras.Model):
def __init__(self, units):
super(Attention, self).__init__()
self.W1 = tf.keras.layers.Dense(units)
self.W2 = tf.keras.layers.Dense(units)
self.V = tf.keras.layers.Dense(1)
def call(self, features, hidden):
hidden_with_time_axis = tf.expand_dims(hidden, 1)
score = tf.nn.tanh(self.W1(features) + self.W2(hidden_with_time_axis))
attention_weights = tf.nn.softmax(self.V(score), axis=1)
context_vector = attention_weights * features
context_vector = tf.reduce_sum(context_vector, axis=1)
return context_vector, attention_weights
先ほどのLSTMレイヤを改良し、Attentionレイヤを挟みます。LSTMは双方向LSTMとしたので、今回は順・逆両方の重みを結合したものをAttentionレイヤのinputとしています。
X = tf.keras.Input(shape=(None,), batch_size=BATCH_SIZE)
embedded = tf.keras.layers.Embedding(encoder.vocab_size, 128)(X)
lstm, forward_h, forward_c, backward_h, backward_c = tf.keras.layers.Bidirectional(
tf.keras.layers.LSTM(128,return_sequences=True,return_state=True, dropout=0.4, recurrent_dropout=0.4)
)(embedded)
state_h = tf.keras.layers.Concatenate()([forward_h, backward_h]) # 重みを結合
context,attention_weights = Attention(128)(lstm,state_h) # ここにAttentionレイヤを挟む
fully_connected = tf.keras.layers.Dense(units=256, activation='relu')(context)
Y = tf.keras.layers.Dense(1, activation='sigmoid',name='final_layer')(fully_connected)
model = tf.keras.Model(inputs=X, outputs=Y)
model.compile(loss='binary_crossentropy',
optimizer=tf.keras.optimizers.Adam(1e-4),
metrics=['accuracy'])
グラフを表示し、Attentionレイヤが間に入っていることを確認します。
model.summary()
Model: "model"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_1 (InputLayer) [(128, None)] 0
__________________________________________________________________________________________________
embedding (Embedding) (128, None, 128) 4324352 input_1[0][0]
__________________________________________________________________________________________________
bidirectional (Bidirectional) [(128, None, 256), ( 263168 embedding[0][0]
__________________________________________________________________________________________________
concatenate (Concatenate) (None, 256) 0 bidirectional[0][1]
bidirectional[0][3]
__________________________________________________________________________________________________
attention (Attention) ((128, 256), (128, N 65921 bidirectional[0][0]
__________________________________________________________________________________________________
dense_3 (Dense) (128, 256) 65792 attention[0][0]
__________________________________________________________________________________________________
final_layer (Dense) (128, 1) 257 dense_3[0][0]
==================================================================================================
Total params: 4,719,490
Trainable params: 4,719,490
Non-trainable params: 0
__________________________________________________________________________________________________
Attentionレイヤ付きのモデルが完成したので、学習させます。
model.fit(new_dataset,epochs=5)
精度は悪くなってますが、お財布と時間の関係でこれ以上学習できないため、ここで打ち切りとします。。。
Epoch 1/5
298/298 [==============================] - 123s 413ms/step - loss: 0.4978 - accuracy: 0.8718
Epoch 2/5
298/298 [==============================] - 121s 405ms/step - loss: 0.7790 - accuracy: 0.4971
Epoch 3/5
298/298 [==============================] - 121s 406ms/step - loss: 0.6752 - accuracy: 0.6168
Epoch 4/5
298/298 [==============================] - 121s 405ms/step - loss: 0.6523 - accuracy: 0.6869
Epoch 5/5
298/298 [==============================] - 121s 406ms/step - loss: 0.6395 - accuracy: 0.6895
まとめ
tf.data
とtf.keras
を使うと簡単にLSTMレイヤのモデルが作れます。
また今回のように独自モデルを加えることも容易にできますので、ぜひ試してみてください。