LSTMを無意味に使ってみました。
目的は、文字列を入力したら単語が出力されるニューラルネットワーク(NN)を作ることです。単なるマッピングなので、連想配列を使えば解決ですが、無理やりNNで実現してみます。 入力層は、文字のone-hotベクトルとします。
同じく、出力層は単語のone-hotベクトルとします。 学習データはどこからともなく見つけてきた英単語300個。 入力と出力の長さが異なっているので、そろえるためにブランク記号 _ を使います。 例えば、n e u r o n を入力すると、_ _ _ _ _ neuron が出力されるように学習します。
単語ごとに文字列長が異なるので、足りない分は入力側にも _ を使います。 単語リストは
business company ... neuronというふうに、単に1行に1単語が書かれたファイルです。 このファイルを次のスクリプトの第1引数に指定してスクリプトを実行します。
学習用ライブラリにはTensorFlowベースのKerasを利用しました。 ブランク記号 _ には数値で0を、ASCII文字には1から順に整数を割り当てています。
単語についても同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | #!/usr/bin/python3
# -*- coding: utf-8 -*-
import numpy as np
import sys,os
import numpy.random as random
from keras.models import Sequential
from keras.layers import Dense, Activation, LSTM, TimeDistributedDense
from keras.optimizers import RMSprop
import keras.preprocessing.text as pp
if len(sys.argv) != 2:
print("Usage: seq2seq.py [word list]")
exit(0)
words_file = sys.argv[1]
if not os.path.isfile(words_file):
print(words_file+" is not found.")
exit(0)
random.seed(123)
# 単語データの読み込み
words = []
max_ch_seq = 0
for line in open(words_file):
line = line.rstrip('\n')
chars = [ord(c)-ord('a')+1 for c in list(line)]
col = np.array(chars)
max_ch_seq = max(max_ch_seq, len(col))
words.append([col, line])
# 文字長を揃える
max_ch_seq_orig = max_ch_seq
for w in words:
while len(w[0]) < max_ch_seq:
w[0] = np.append(w[0], 0) # 0はブランク
word_id_map = {}
id = 1
for w in words:
word_id_map[w[1]] = id # 重複がないことが前提
id += 1
input_vec_len = ord('z') - ord('a') + 2 # +1はブランク入力のため
output_vec_len = len(words) + 1 # +1はブランク出力のため
np.set_printoptions(threshold=np.inf)
for w in words:
# 入力(=文字)のone-hotベクトルを作成する
input_vec_seq = np.zeros((len(w[0]), input_vec_len))
input_vec_seq[np.arange(len(w[0])), w[0]] = 1
w.append(input_vec_seq)
# 出力(=単語)のone-hotベクトルを作成する
x = np.zeros(len(w[0]), dtype=int)
x[-1] = word_id_map[w[1]] #最後に単語を入れる
output_vec_seq = np.zeros((len(w[0]),output_vec_len))
output_vec_seq[np.arange(len(w[0])), x] = 1
w.append(output_vec_seq)
# この時点で、wordsの各要素は、
# (文字列(数字), 単語, 文字列のOne-hotベクトル, 単語のOne-hotベクトル)
# となっている。
data_dup = 1 # 何度も同じデータを入れるなら、2以上にする
# 訓練用データを設定する
x_train = np.empty((len(words)*data_dup, max_ch_seq, input_vec_len))
y_train = np.empty((len(words)*data_dup, max_ch_seq, output_vec_len))
for j in range(0, data_dup):
for i in range(0, len(words)):
x_train[i+len(words)*j] = words[i][2]
y_train[i+len(words)*j] = words[i][3]
# 評価用データを設定する
x_val = np.empty((len(words), max_ch_seq, input_vec_len))
y_val = np.empty((len(words), max_ch_seq, output_vec_len))
for i in range(0, len(words)):
x_val[i] = words[i][2]
y_val[i] = words[i][3]
# モデルを作る
batch_size = 50
model = Sequential()
model.add(LSTM(16, return_sequences=True,
batch_input_shape=(batch_size, max_ch_seq, input_vec_len)))
model.add(LSTM(16, return_sequences=True))
model.add(TimeDistributedDense(128))
model.add(TimeDistributedDense(output_vec_len, activation='softmax'))
# 学習する
rmsprop = RMSprop(lr=0.0005, rho=0.9, epsilon=1e-08)
model.compile(loss='categorical_crossentropy', optimizer=rmsprop, metrics=['accuracy'])
model.fit(x_train, y_train, batch_size=batch_size, nb_epoch=5000, validation_data=(x_val, y_val))
# モデルを保存
model.save_weights('model.dat')
# 結果の確認
r = model.predict_classes(batch_size=batch_size, x=x_val)
print(r)
|
Train on 300 samples, validate on 300 samples Epoch 1/5000 300/300 [==============================] - 1s - loss: 5.6607 - acc: 0.4613 - val_loss: 5.5990 - val_acc: 0.8738 Epoch 2/5000 300/300 [==============================] - 1s - loss: 5.5510 - acc: 0.9046 - val_loss: 5.4750 - val_acc: 0.9215 Epoch 3/5000 300/300 [==============================] - 1s - loss: 5.4081 - acc: 0.9231 - val_loss: 5.3032 - val_acc: 0.9231 Epoch 4/5000 300/300 [==============================] - 1s - loss: 5.2126 - acc: 0.9231 - val_loss: 5.0716 - val_acc: 0.9231 Epoch 5/5000 300/300 [==============================] - 1s - loss: 4.9520 - acc: 0.9231 - val_loss: 4.7682 - val_acc: 0.9231 ... Epoch 1009/5000 300/300 [==============================] - 0s - loss: 0.2684 - acc: 0.9367 - val_loss: 0.3089 - val_acc: 0.9241 Epoch 1010/5000 300/300 [==============================] - 1s - loss: 0.2687 - acc: 0.9297 - val_loss: 0.2368 - val_acc: 0.9646 Epoch 1011/5000 300/300 [==============================] - 1s - loss: 0.2416 - acc: 0.9479 - val_loss: 0.2385 - val_acc: 0.9559 ... Epoch 1499/5000 300/300 [==============================] - 1s - loss: 0.2082 - acc: 0.9518 - val_loss: 0.2559 - val_acc: 0.9285 Epoch 1500/5000 300/300 [==============================] - 1s - loss: 0.1796 - acc: 0.9649 - val_loss: 0.1584 - val_acc: 0.9869 Epoch 1501/5000 300/300 [==============================] - 1s - loss: 0.1629 - acc: 0.9759 - val_loss: 0.1578 - val_acc: 0.9874 ... Epoch 1999/5000 300/300 [==============================] - 1s - loss: 0.0943 - acc: 0.9967 - val_loss: 0.0892 - val_acc: 0.9990 Epoch 2000/5000 300/300 [==============================] - 1s - loss: 0.0941 - acc: 0.9967 - val_loss: 0.0890 - val_acc: 0.9987 Epoch 2001/5000 300/300 [==============================] - 1s - loss: 0.1502 - acc: 0.9644 - val_loss: 0.1626 - val_acc: 0.9487 ... Epoch 2999/5000 300/300 [==============================] - 1s - loss: 0.0155 - acc: 0.9995 - val_loss: 0.0123 - val_acc: 1.0000 Epoch 3000/5000 300/300 [==============================] - 1s - loss: 0.0134 - acc: 1.0000 - val_loss: 0.0121 - val_acc: 1.0000 Epoch 3001/5000 300/300 [==============================] - 1s - loss: 0.0134 - acc: 1.0000 - val_loss: 0.0120 - val_acc: 1.0000 ... Epoch 4998/5000 300/300 [==============================] - 1s - loss: 0.0800 - acc: 0.9813 - val_loss: 6.6157e-04 - val_acc: 1.0000 Epoch 4999/5000 300/300 [==============================] - 1s - loss: 6.1272e-04 - acc: 1.0000 - val_loss: 5.0139e-04 - val_acc: 1.0000 Epoch 5000/5000 300/300 [==============================] - 1s - loss: 4.9502e-04 - acc: 1.0000 - val_loss: 4.4888e-04 - val_acc: 1.0000というようなログが出力されます。汎化性能は無視なので、ひたすらlossを小さくしました。やりすぎですね。 99行目のprint文の出力は
[[ 0 0 0 0 0 0 0 0 0 0 0 0 1] [ 0 0 0 0 0 0 0 0 0 0 0 0 2] [ 0 0 0 0 0 0 0 0 0 0 0 0 3] [ 0 0 0 0 0 0 0 0 0 0 0 0 4] [ 0 0 0 0 0 0 0 0 0 0 0 0 5] [ 0 0 0 0 0 0 0 0 0 0 0 0 6] [ 0 0 0 0 0 0 0 0 0 0 0 0 7] [ 0 0 0 0 0 0 0 0 0 0 0 0 8] [ 0 0 0 0 0 0 0 0 0 0 0 0 9] [ 0 0 0 0 0 0 0 0 0 0 0 0 10] ... [ 0 0 0 0 0 0 0 0 0 0 0 0 298] [ 0 0 0 0 0 0 0 0 0 0 0 0 299] [ 0 0 0 0 0 0 0 0 0 0 0 0 300]]となります。きちんと、途中は全てブランク(=0)が出ていて、
単語の番号が最後に正しく出力されているのが分かります。 というわけで、LSTMは結構過去のことを覚えておいてくれることが分かりました。
そして、とても重たい連想配列ができました。
0 件のコメント :
コメントを投稿