TensorFlowでLSTMを実装する
以下の記事の続き k17trpsynth.hatenablog.com
目的
LSTMを使って前回作ったRNNを改良したい。加えて、隠れ層の数を複数にしたディープリカレントニューラルネットワークを構築することにも試みた。
方法
利用した文字データなど、前回同様。
まず、前回のBasicRNNCellをBasicLSTMCellに変更する。
cell = tf.nn.rnn_cell.BasicLSTMCell(self.hidden_layer_size, reuse=tf.AUTO_REUSE)
加えて、initial_stateに手を加える必要がある。2つの初期値をタプルで繋げて用意した。
initial_state_c = tf.placeholder(tf.float64, [None, self.hidden_layer_size]) #cellの状態の初期値 initial_state_h = tf.placeholder(tf.float64, [None, self.hidden_layer_size]) #前のcellの出力を参照するのでその初期値 initial_state = (initial_state_c, initial_state_h) #2つの初期値をタプルで繋げる
これは、LSTMのcellが入力と出力を繰り返す中で1つ前のcellの出力を参照するため、その初期値(ここでのinitial_state_h)をcellの状態(state)と合わせて定義していると考えられる。
また今回隠れ層の数を複数にすることも試みた。tf.nn.rnn_cell.MultiRNNCellを用いて以下のように記述する。
multi_cell = tf.nn.rnn_cell.MultiRNNCell([cell] * self.num_hidden_layer) #num_hidden_layer: 隠れ層の数
この際も、cellの数だけその初期値が必要となるため、initial_stateに手を加える必要があり、隠れ層の数だけinitial_stateをタプルで繋げて用意する必要がある。
initial_state_c = tf.placeholder(tf.float64, [None, self.hidden_layer_size]) initial_state_h = tf.placeholder(tf.float64, [None, self.hidden_layer_size]) initial_state = tuple([(initial_state_c, initial_state_h)] * self.num_hidden_layer) #リストで連結したLSTMCellの初期値をタプルに変換
コードの全容は以下の通り。上で述べた部分以外は前回同様である。
import tensorflow as tf import numpy as np import re class Prepare_data: def __init__(self, file): self.chunk_size = 5 #参照する直前の文脈の文字数 self.vocabulary_size = 27 #アルファベット26字 + インデント self.file = open(file) def file_to_text(self): text = self.file.read() return text def text_to_array(self): text = self.file_to_text() text = text.lower() text = text.replace("\n", " ") #改行をインデントに変換 text = re.sub(r"[^a-z ]", "", text) text = re.sub(r"[ ]+", " ", text) code_list = [] for i in range(len(text)): if text[i] == " ": code_list.append(self.vocabulary_size - 1) else: code_list.append(ord(text[i])-ord("a")) code_array = np.array(code_list) #アルファベットとインデントを0~26の数字に変換した配列を作成 return code_array def array_to_one_hot(self): array = self.text_to_array() one_hot = np.eye(self.vocabulary_size)[array] #数字の配列をone-hotベクトルの配列に変換 return one_hot def make_data(self): one_hot = self.array_to_one_hot() data_num = one_hot.shape[0] - self.chunk_size #文の最初の方はデータとして利用できない input_data = np.zeros([data_num, self.chunk_size, self.vocabulary_size]) output_data = np.zeros([data_num, self.vocabulary_size]) for i in range(data_num): output_data[i, :] = one_hot[i + self.chunk_size, :] for j in range(self.chunk_size): input_data[i, j, :] = one_hot[i + j, :] training_num = data_num * 4 // 5 input_train = input_data[: training_num] output_train = output_data[: training_num] input_test = input_data[training_num :] output_test = output_data[training_num :] return input_train, output_train, input_test, output_test class Lstm: def __init__(self): self.input_layer_size = 27 self.hidden_layer_size = 30 self.num_hidden_layer = 1 self.output_layer_size = 27 self.chunk_size = 5 self.batch_size = 128 self.epoch = 100 def inference(self, input, initial_state): w_hidden = tf.Variable(tf.random_normal([self.input_layer_size, self.hidden_layer_size], dtype="float64")) b_hidden = tf.Variable(tf.random_normal([self.hidden_layer_size], dtype="float64")) w_output = tf.Variable(tf.random_normal([self.hidden_layer_size, self.output_layer_size], dtype="float64")) b_output = tf.Variable(tf.random_normal([self.output_layer_size], dtype="float64")) input = tf.reshape(tf.transpose(input, [1, 0, 2]), [-1, self.input_layer_size]) #インプットデータをwとbをかけるために整形 input_to_hidden = (tf.matmul(input, w_hidden) + b_hidden) input_to_hidden = tf.split(input_to_hidden, self.chunk_size) #chunk_sizeだけ連なったリストを作成 cell = tf.nn.rnn_cell.BasicLSTMCell(self.hidden_layer_size, reuse=tf.AUTO_REUSE) #cellの定義 multi_cell = tf.nn.rnn_cell.MultiRNNCell([cell] * self.num_hidden_layer) #隠れ層を複製 hidden_to_output, final_state = tf.contrib.rnn.static_rnn(multi_cell, input_to_hidden, initial_state=initial_state) output = (tf.matmul(hidden_to_output[-1], w_output) + b_output) #chunk_size個のcell出力のうち最後のみを利用 return output def cost(self, output, labels): cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=labels, logits=output)) return cost def training(self, cost): optimizer = tf.train.AdamOptimizer() training = optimizer.minimize(cost) return training def train(self, file): test_data = Prepare_data(file) input_train, output_train, input_test, output_test = test_data.make_data() #引数fileからデータを作成 zero_state = np.zeros([self.batch_size, self.hidden_layer_size], dtype="float64") #cellの状態の初期値を定義 input_data = tf.placeholder(tf.float64, [None, self.chunk_size, self.input_layer_size]) labels = tf.placeholder(tf.float64, [None, self.output_layer_size]) initial_state_c = tf.placeholder(tf.float64, [None, self.hidden_layer_size]) initial_state_h = tf.placeholder(tf.float64, [None, self.hidden_layer_size]) initial_state = tuple([(initial_state_c, initial_state_h)] * self.num_hidden_layer) prediction = self.inference(input_data, initial_state) cost = self.cost(prediction, labels) training = self.training(cost) correct = tf.equal(tf.argmax(prediction, 1), tf.argmax(labels, 1)) accuracy = tf.reduce_mean(tf.cast(correct, dtype="float64")) with tf.Session() as sess: init = tf.global_variables_initializer() sess.run(init) for epoch in range(self.epoch): step = 1 sum_cost = 0 sum_acc = 0 while self.batch_size * step < input_train.shape[0]: input_batch = input_train[self.batch_size * (step - 1) : self.batch_size * step] output_batch = output_train[self.batch_size * (step - 1) : self.batch_size * step] c, _, a= sess.run([cost, training, accuracy], feed_dict = {input_data: input_batch, labels: output_batch, initial_state_c: zero_state, initial_state_h: zero_state}) sum_cost += c sum_acc += a step += 1 ave_cost = sum_cost / step epoch_acc = sum_acc / step print("epoch: {0}, cost: {1}, epoch_accuracy: {2}".format(epoch, ave_cost, epoch_acc)) print("Training finished") saver = tf.train.Saver() saver.save(sess, "./lstm_model") #モデルの変数の保存 zero_state = np.zeros([input_test.shape[0], self.hidden_layer_size], dtype = "float64") #cellの状態の初期値を再定義 a = sess.run(accuracy, feed_dict = {input_data: input_test, labels: output_test, initial_state_c: zero_state, initial_state_h: zero_state}) print("accuracy: {0}".format(a)) def predict(self, context): #訓練データ同様、文脈データの整形 context = context.replace("\n", " ") context = re.sub(r"[^a-z ]", "", context) context = re.sub(r"[ ]+", " ", context) code_list = [] for i in range(self.chunk_size): if context[- self.chunk_size + i] == " ": code_list.append(self.input_layer_size - 1) else: code_list.append(ord(context[- self.chunk_size + i])-ord("a")) code_array = np.array(code_list) one_hot = np.eye(self.input_layer_size)[code_array] input_pred = np.array([one_hot]) zero_state = np.zeros([1, self.hidden_layer_size], dtype="float64") #cellの状態の初期値を再定義 input_data = tf.placeholder(tf.float64, [None, self.chunk_size, self.input_layer_size]) initial_state_c = tf.placeholder(tf.float64, [None, self.hidden_layer_size]) initial_state_h = tf.placeholder(tf.float64, [None, self.hidden_layer_size]) initial_state = tuple([(initial_state_c, initial_state_h)] * self.num_hidden_layer) prediction = tf.nn.softmax(self.inference(input_pred, initial_state)) labels_pred = tf.argmax(prediction, 1) with tf.Session() as sess: saver = tf.train.Saver() saver.restore(sess, "./lstm_model") #保存したモデルの変数をロード p, l = sess.run([prediction, labels_pred], feed_dict = {input_data: input_pred, initial_state_c: zero_state, initial_state_h: zero_state}) for i in range(27): c = "_" if i == 26 else chr(i + ord("a")) print("{0}: {1}".format(c, int(p[0][i] * 10000) / 100)) #文字ごとの確率を表示 print("prediction: {0}{1}".format(context, "_" if l[0] == 26 else chr(l[0] + ord("a")))) #最終的な予測を表示 if __name__=="__main__": test_lstm = Lstm() test_lstm.train("make_data.txt") test_lstm.predict("convenienc")
隠れ層の数=1とした時でも正答率は5割程度となり、前回の正答率4~4.5割から改善が見られた。隠れ層の数を増やしてもあまり正答率の変化は見られなかった。
また、参照する文字数(chunk_size)を5から変化させてみたが、正答率は特に変化せず、5割程度だった。
ただ、このコードでは一度利用したcellが再利用できない。再利用しようとすると以下のエラーがでる。
NotFoundError (see above for traceback): Key Variable_4 not found in checkpoint [[Node: save_1/RestoreV2_4 = RestoreV2[dtypes=[DT_DOUBLE], _device="/job:localhost/replica:0/task:0/device:CPU:0"](_arg_save_1/Const_0_0, save_1/RestoreV2_4/tensor_names, save_1/RestoreV2_4/shape_and_slices)]]
このVariable_4はcell内の変数を表すと考えられ、なぜか再利用時には無くなっている。原因は不明。
追記(2018.2.27)
解決しました。