ディープラーニング習得、次の一歩

ディープラーニング習得、次の一歩

ディープラーニングで自動筆記 - Kerasを用いた文書生成(後編)

2018年8月8日

「文書生成」チャレンジの後編。ネットワークにLSTM、ライブラリにKeras+TensorFlowを採用し、さらに精度を改善していく。最後に、全然関係ない入力文章から、江戸川乱歩風文書が生成されるかを試す。

石垣哲郎 石垣 哲郎

前回のおさらい

 時系列データの処理に適しているLSTM(Long Short-Term Memory)を応用して、文書生成に挑戦してみようというのが、前回の主旨である。文書を構成する各単語にインデックスを付与すれば、それは単なる数字の羅列である。株価予測のときと同じように、入力の \(n\) 個(前回記事では40)の数字から、次の数字を予測するよう、ニューラルネットを学習させる。これで、理屈の上では、いくらでも文章を紡ぎ出せるはずであった。

 しかしながら、世の中はそんなに甘くない。単純にLSTMレイヤーを積んだだけでは、全然ダメだった。そこで、ニューラルネットを「単語出現頻度を予測するもの」と「頻度グループごとに単語を予測するもの」の2段構えにした。単語出現頻度予測の方は、分類数が語彙数から頻度区分け数(前回記事では7つ)に減少するため、学習が容易になって精度が向上すると考えた。また、頻度グループごとの単語予測は、各単語の出現パターンが似てくるため、こちらも予測精度が向上すると期待した。

 この目論見はある程度的中し、比較的日本語っぽい文章の生成に成功した、というのが、前回の状況である。

本稿のゴール

 ニューラルネットの単語予測の改善により、より日本語らしい文書生成の実現を目指す。ニューラルネット全体の予測精度は、最終的に96%程度まで改善した。このニューラルネットを使って、江戸川乱歩とは全然関係ない入力文章から、江戸川乱歩風文書が生成されるかどうかを見てみる。

 本稿ではKeras(バックエンド:TensorFlow)を使って実装する。また、TensorFlowやKerasはインストール済みを前提に論を進める。

 なお、データ量が多いため、前回同様、本稿に記載するコードを実行する際には、GPUマシンかクラウドのGPUサービス利用を推奨する。

 また、前回作成した「model_words_」で始まる名前のファイル7個や単語分類用ニューラルネットは今回も使用するので注意してほしい。

出現頻度分類ニューラルネットの改善

 前回は7種類に分類したが、分類数を減らせば精度向上が期待できる。しかしそれでは、後続の単語予測ニューラルネットの精度が下がってしまうので、2択分類のニューラルネットを多段(今回の事例では3段)に配置することで、精度向上を図る。word2vecの記事で紹介した、階層化Softmaxに似た発想である。以下にそのイメージを示す。

図1 単語出現頻度分類ニューラルネットの改善イメージ

 以下にリストを示す。リスト1からリスト3までは、前回と同じである。

Python
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import csv
import pandas as pd
import random
import numpy.random as nr
import sys
import h5py
import keras
import math

from __future__ import print_function
from keras.layers.core import Activation
from keras.layers.core import Dense
from keras.layers.core import Dropout
from keras.layers.core import Flatten
from keras.layers.core import Masking
from keras.models import Sequential
from keras.layers import Input
from keras.models import Model
from keras.layers.recurrent import SimpleRNN
from keras.layers.recurrent import LSTM
from keras.layers.embeddings import Embedding
from keras.callbacks import EarlyStopping
from keras.callbacks import ReduceLROnPlateau
from keras.layers.normalization import BatchNormalization
from keras.initializers import glorot_uniform
from keras.initializers import uniform
from keras.initializers import orthogonal
from keras.initializers import TruncatedNormal
from keras.optimizers import RMSprop
from keras import regularizers
from keras.constraints import maxnorm, non_neg
from keras.utils.data_utils import get_file
from keras.utils import np_utils
リスト1 import宣言
Python
# 元データ
df1 = csv.reader(open('rampo_separate.csv', 'r'))
df2 = csv.reader(open('rampo_separate2.csv', 'r'))
df3 = csv.reader(open('rampo_separate3.csv', 'r'))

data1 = [ v for v in df1]
data2 = [ v for v in df2]
data3 = [ v for v in df3]

mat1 = np.array(data1)
mat2 = np.array(data2)
mat3 = np.array(data3)

mat = np.r_[mat1[:, 0], mat2[:, 0], mat3[:, 0]]
print(mat.shape)
リスト2 CSVファイル読み込み
Python
words = sorted(list(set(mat)))
cnt = np.zeros(len(words))

print('total words:', len(words))
word_indices = dict((w, i) for i, w in enumerate(words))  # 単語をキーにインデックス検索
indices_word = dict((i, w) for i, w in enumerate(words))  # インデックスをキーに単語を検索

# 単語の出現数をカウント
for j in range (0, len(mat)):
  cnt[word_indices[mat[j]]] += 1

# 出現頻度の少ない単語を未知語「UNK」で置き換え
words_unk = []                # 未知語一覧

for k in range(0, len(words)):
  if cnt[k] <= 3:
    words_unk.append(words[k])
    words[k] = 'UNK'

print('低頻度語数:', len(words_unk))   # words_unkはUNKに変換された単語のリスト

words = sorted(list(set(words)))
print('total words:', len(words))
word_indices = dict((w, i) for i, w in enumerate(words))  # 単語をキーにインデックス検索
indices_word = dict((i, w) for i, w in enumerate(words))  # インデックスをキーに単語を検索
リスト3 辞書データ作成

 以下のリスト4は、頻度の2択分類訓練データ作成処理である。リスト中の以下の変数の値を変えて、表2のように6パターン実行する。まずはパターン0を実行しておく。

パターン学習対象頻度分類しきい値n_lowern_splitn_upper
パターン0全単語3000300400000
パターン1300未満28028300
パターン228未満1001028
パターン328~30010028100300
パターン4300以上20003002000400000
パターン52000以上15000200015000400000
表2 出現頻度分類訓練パターン

n_lower: 出現頻度がこの値以上の単語が学習対象。

  • n_split: しきい値。ターゲット単語の出現頻度がこの値未満なら0、そうでないなら1をラベル値とする。
  • n_upper: 出現頻度がこの値未満の単語が学習対象。

Python
maxlen = 40        # 入力語数
n_upper = 400000   # 学習対象単語の出現頻度上限
n_split = 300      # 分類しきい値
n_lower = 0        # 学習対象単語の出現頻度下限

mat_urtext = np.zeros((len(mat), 1), dtype=int)
for i in range(0, len(mat)):
  #row = np.zeros(len(words), dtype=np.float32)
  if mat[i] in word_indices :       # 出現頻度の低い単語のインデックスをUNKのそれに置き換え
    if word_indices[mat[i]] != 0 :  # 0パディング対策
      mat_urtext[i, 0] = word_indices[mat[i]]
    else:
      mat_urtext[i, 0] = len(words)
  else:
    mat_urtext[i, 0] = word_indices['UNK']

print(mat_urtext.shape)


# 単語の出現数をもう一度カウント:UNK置き換えでwords_indeicesが変わっているため
cnt = np.zeros(len(words)+1)
for j in range (0, len(mat)):
  cnt[mat_urtext[j, 0]] += 1

print(cnt.shape)

len_seq = len(mat_urtext)-maxlen
data = []
target = []
for i in range(0, len_seq):
  # 答えの単語の出現頻度がn_lower以上でかつn_upper 未満の場合を学習対象にする
  if cnt[mat_urtext[i+maxlen, :]] >= n_lower and cnt[mat_urtext[i+maxlen, :]] <= n_upper:
    data.append(mat_urtext[i:i+maxlen, :])
    #target.append(mat_urtext[i+maxlen, :])
    # 出現頻度に応じてラベルの値を設定
    if cnt[mat_urtext[i+maxlen, :]] < n_split :  # 頻度がn_split未満なら0
      target.append(0)
    else:      # 頻度がn_split以上なら1
      target.append(1)     
    
x = np.array(data).reshape(len(data), maxlen, 1)
t = np.array(target).reshape(len(data), 1)


z = list(zip(x, t))
nr.seed(12345)
nr.shuffle(z)  # シャッフル
x, t = zip(*z)
x = np.array(x).reshape(len(data), maxlen, 1)
t = np.array(t).reshape(len(data), 1)
print(x.shape, t.shape)

x_train = x    # 元データを訓練用と評価用に分割しない
t_train = t

print(x_train.shape, t_train.shape)
リスト4-1 分類用訓練データ作成

 ニューラルネット本体は以下のとおり。前回のリスト5-1の単語分類用ニューラルネットと少し異なるので注意してほしい。具体的には、分類が2択なので、活性化関数をsigmoidに変更してある。また、dropoutを使用しない方が精度が向上したので、dropout無しにしてある。

Python
class Prediction :
  def __init__(self, maxlen, n_hidden, input_dim, vec_dim, output_dim):
    self.maxlen = maxlen
    self.n_hidden = n_hidden
    self.input_dim = input_dim
    self.output_dim = output_dim
    self.vec_dim = vec_dim

  def create_model(self):
    model = Sequential()
    print('#3')
    model.add(Embedding(self.input_dim, self.vec_dim, input_length=self.maxlen, trainable=True,
              embeddings_initializer=uniform(seed=20170719)))
    model.add(BatchNormalization(axis=-1))
    print('#4')
    model.add(Masking(mask_value=0, input_shape=(self.maxlen, self.vec_dim)))
    model.add(LSTM(self.n_hidden, batch_input_shape=(None, self.maxlen, self.vec_dim),
              kernel_initializer=glorot_uniform(seed=20170719),
              recurrent_initializer=orthogonal(gain=1.0, seed=20170719)
              ))
    print('#5')
    model.add(BatchNormalization(axis=-1))
    print('#6')
    model.add(Dense(self.output_dim, activation='sigmoid', use_bias=True,
              kernel_initializer=glorot_uniform(seed=20170719)))
    model.compile(loss="binary_crossentropy", optimizer="RMSprop", metrics=['binary_accuracy'])
    return model

  # 学習
  def train(self, x_train, t_train, batch_size, epochs, emb_param) :
    early_stopping = EarlyStopping(monitor='loss', patience=4, verbose=1)
    print('#2', t_train.shape)
    model = self.create_model()
    print('#7')
    model.fit(x_train, t_train, batch_size=batch_size, epochs=epochs, verbose=1,
              shuffle=True, callbacks=[early_stopping], validation_split=0.0)
    return model
リスト5-2 単語出現頻度分類用ニューラルネット

 メイン処理は以下のとおり。ポイントは出力次元output_dimが1になっているところである。

Python
n_pattern = 0
vec_dim = 400
epochs = 31
batch_size = 200
input_dim = len(words)+1
output_dim = 1

n_hidden = int(vec_dim*1.5)    # 隠れ層の次元

prediction = Prediction(maxlen, n_hidden, input_dim, vec_dim, output_dim)
emb_param = 'param_classify_by_freq_'+str(n_pattern)+'_'+str(n_lower)+'_'+str(n_split)+'_'+str(n_upper)+'.hdf5'    # パラメーター名
print(emb_param)
row = x_train.shape[0]
x_train = x_train.reshape(row, maxlen)
model = prediction.train(x_train, t_train, batch_size, epochs, emb_param)

model.save_weights(emb_param)  # 学習済みパラメーターセーブ

score = model.evaluate(x_train, t_train, batch_size=batch_size, verbose=1)

print("score:", score)
リスト6-1 単語出現頻度分類メイン処理

 パラメーターファイルは、分類パターンに応じて、以下のようにそれぞれ別名を付与する。

パターン学習対象頻度パラメーターファイル名
パターン0全単語param_classify_by_freq_0_0_300_400000.hdf5
パターン1300未満param_classify_by_freq_1_0_28_300.hdf5
パターン228未満param_classify_by_freq_2_0_10_28.hdf5
パターン328~300param_classify_by_freq_3_28_100_300.hdf5
パターン4300以上param_classify_by_freq_4_300_2000_400000.hdf5
パターン52000以上param_classify_by_freq_5_2000_15000_400000.hdf5
表2 頻度分類パラメーターファイル名
  • リスト1 import宣言
  • リスト2 CSVファイル読み込み
  • リスト3 辞書データ作成
  • リスト4-1 分類用訓練データ作成
  • リスト5-2 単語出現頻度分類用ニューラルネット
  • リスト6-1 単語出現頻度分類メイン処理

 ここまでに示したリスト1~リスト6-1を実行すると、表2に示したパターン0が完了している状態だ。残りの5つのパターン、つまりパターン1~パターン5もここで実行する。

 これにはまず、n_lowern_splitn_upperの値を前掲の表2に示したものに置き換えて、リスト4-1を再実行する。
 次に、n_patternの値をパターン名の「1」~「5」に置き換えて、リスト6-1を再実行すればよい。

 この2つの再実行をパターン5まで繰り返すことで、6つのパラメーターファイルを生成する。

 これにより、各ニューラルネットの正解率は98%以上に改善した。単語出現頻度分類全体では、ニューラルネットが3段になっているので、86%以上の正解率となる。単語予測のほうは90%以上の正解率だったので、全体としての正解率は78%ということになる。

出現頻度分類ニューラルネット改善後の文書生成結果

 生成したパラメーターファイルを使って、文書生成を実行してみる。

 まず、ニューラルネット定義である。分類用と、単語推定用の2種類用意する。なお、その前に、本稿のリスト1~リスト3と前回のリスト4を実行して訓練データを作成しておく必要がある。

Python
# 頻度分類用
class Prediction_freq :
  def __init__(self, maxlen, n_hidden, input_dim, vec_dim, output_dim):
    self.maxlen = maxlen
    self.n_hidden = n_hidden
    self.input_dim = input_dim
    self.output_dim = output_dim
    self.vec_dim = vec_dim
    #self.t_dim = t_dim
        
  def create_model(self):
    model = Sequential()
    print('#3')
    model.add(Embedding(self.input_dim, self.vec_dim, input_length=self.maxlen))
    model.add(BatchNormalization(axis=-1))
    print('#4')
    model.add(Masking(mask_value=0, input_shape=(self.maxlen, self.vec_dim)))
    model.add(LSTM(self.n_hidden, batch_input_shape=(None, self.maxlen, self.vec_dim)))
    print('#5')
    model.add(BatchNormalization(axis=-1))
    print('#6')
    model.add(Dense(self.output_dim, activation='sigmoid'))
    return model

# 単語推定用 
class Prediction_words :
  def __init__(self, maxlen, n_hidden, input_dim, vec_dim, output_dim):
    self.maxlen = maxlen
    self.n_hidden = n_hidden
    self.input_dim = input_dim
    self.vec_dim = vec_dim
    self.output_dim = output_dim
        
  def create_model(self):
    model = Sequential()
    print('#3')
    model.add(Embedding(self.input_dim, self.vec_dim, input_length=self.maxlen, trainable=True,
                   embeddings_initializer=uniform(seed=20170719)))
    model.add(BatchNormalization(axis=-1))
    print('#4')
    model.add(Masking(mask_value=0, input_shape=(self.maxlen, self.vec_dim)))
    model.add(LSTM(self.n_hidden, batch_input_shape=(None, self.maxlen, self.vec_dim)))
    print('#5')
    model.add(BatchNormalization(axis=-1))
    print('#6')
    model.add(Dense(self.output_dim, activation='softmax'))
    return model
リスト5-3 文書生成用ニューラルネット定義

 次にパラメーターのロードである。パラメーターファイルの種類と数が変わっているので、それに応じて変更してある。

Python
vec_dim = 400
epochs = 100
batch_size = 200
input_dim = len(words)+1
unk_dim = len(words_unk)+1
output_dim = input_dim
n_sigmoid = 1
n_hidden = int(vec_dim*1.5)  # 隠れ層の次元

# 頻度分類用
prediction_freq = Prediction_freq(maxlen, n_hidden, input_dim, vec_dim, n_sigmoid)
print('頻度分類用ニューラルネット_0活性化')
model_classify_freq_0 = prediction_freq.create_model()
print('頻度分類用ニューラルネット_1活性化')
model_classify_freq_1 = prediction_freq.create_model()
print('頻度分類用ニューラルネット_2活性化')
model_classify_freq_2 = prediction_freq.create_model()
print('頻度分類用ニューラルネット_3活性化')
model_classify_freq_3 = prediction_freq.create_model()
print('頻度分類用ニューラルネット_4活性化')
model_classify_freq_4 = prediction_freq.create_model()
print('頻度分類用ニューラルネット_5活性化')
model_classify_freq_5 = prediction_freq.create_model()
print()

# 単語予測用
prediction_words = Prediction_words(maxlen, n_hidden, input_dim, vec_dim, output_dim)
print('単語分類用ニューラルネット(0_10)活性化')
model_words_0_10 = prediction_words.create_model()
print('単語分類用ニューラルネット(10-28)活性化')
model_words_10_28 = prediction_words.create_model()
print('単語分類用ニューラルネット(28-100)活性化')
model_words_28_100 = prediction_words.create_model()
print('単語分類用ニューラルネット(100-300)活性化')
model_words_100_300 = prediction_words.create_model()
print('単語分類用ニューラルネット(300-2000)活性化')
model_words_300_2000 = prediction_words.create_model()
print('単語分類用ニューラルネット(2000-15000)活性化')
model_words_2000_15000 = prediction_words.create_model()
print('単語分類用ニューラルネット(15000-400000)活性化')
model_words_15000_400000 = prediction_words.create_model()
print()

# パラメーターロード
print('頻度分類用ニューラルネット_0パラメーターロード')
model_classify_freq_0.load_weights('param_classify_by_freq_0_0_300_400000.hdf5')
print('頻度分類用ニューラルネット_1パラメーターロード')
model_classify_freq_1.load_weights('param_classify_by_freq_1_0_28_300.hdf5')
print('頻度分類用ニューラルネット_2パラメーターロード')
model_classify_freq_2.load_weights('param_classify_by_freq_2_0_10_28.hdf5')
print('頻度分類用ニューラルネット_3パラメーターロード')
model_classify_freq_3.load_weights('param_classify_by_freq_3_28_100_300.hdf5')
print('頻度分類用ニューラルネット_4パラメーターロード')
model_classify_freq_4.load_weights('param_classify_by_freq_4_300_2000_400000.hdf5')
print('頻度分類用ニューラルネット_5パラメーターロード')
model_classify_freq_5.load_weights('param_classify_by_freq_5_2000_15000_400000.hdf5')
print()
print('単語分類用ニューラルネット(0-10)パラメーターロード')
model_words_0_10.load_weights('param_words_0_0_10.hdf5')
print('単語分類用ニューラルネット(10-28)パラメーターロード')
model_words_10_28.load_weights('param_words_1_10_28.hdf5')
print('単語分類用ニューラルネット(28-100)パラメーターロード')
model_words_28_100.load_weights('param_words_2_28_100.hdf5')
print('単語分類用ニューラルネット(100-300)パラメーターロード')
model_words_100_300.load_weights('param_words_3_100_300.hdf5')
print('単語分類用ニューラルネット(300-2000)パラメーターロード')
model_words_300_2000.load_weights('param_words_4_300_2000.hdf5')
print('単語分類用ニューラルネット(2000-15000)パラメーターロード')
model_words_2000_15000.load_weights('param_words_5_2000_15000.hdf5')
print('単語分類用ニューラルネット(15000-400000)パラメーターロード')
model_words_15000_400000.load_weights('param_words_6_15000_400000.hdf5')
print()
リスト7-2 文書生成用パラメーターロード

 文書生成のメイン処理である。分類判定が多段のため、分岐が複雑になっている。

Python
n_init = 6000

# 単語
x_validation = x_train[n_init, :, :]
x_validation = x_validation.T
row = x_validation.shape[0]   # 評価データ数
x_validation = x_validation.reshape(row, maxlen)

text_gen = ''                 # 生成テキスト
for i in range(0, maxlen) :
  text_gen += indices_word[x_validation[0, i]]

print(text_gen)
print()


# 正解データ
text_correct = ''
for j in range(0, 4) :
  x_correct = x_train[n_init+j*maxlen, :, :]
  x_correct = x_correct.T
  x_correct = x_correct.reshape(row, maxlen)
  for i in range(0, maxlen) :
    text_correct += indices_word[x_correct[0, i]]

print('正解')
print(text_correct)
print()

# 応答文生成
for k in range (0, 100) :
  # 単語予測
  # 300
  ret_0 = model_classify_freq_0.predict(x_validation, batch_size=batch_size, verbose=0)     # 評価結果
  ret_0 = ret_0.reshape(row, n_sigmoid)
  flag_0 = ret_0[0, 0]
  # 最大値インデックス
  if flag_0 < 0.5 :                     # 300未満
    ret_1 = model_classify_freq_1.predict(x_validation, batch_size=batch_size, verbose=0)   # 評価結果
    ret_1 = ret_1.reshape(row, n_sigmoid)
    flag_1 = ret_1[0, 0]
    if flag_1 < 0.5 :                   # 28未満
      ret_2  = model_classify_freq_2.predict(x_validation, batch_size=batch_size, verbose=0) # 評価結果
      ret_2 = ret_2.reshape(row, n_sigmoid)
      flag_2 = ret_2[0, 0]
      if flag_2< 0.5 :                  # 10未満
        pred_freq = 0
        ret = model_words_0_10.predict(x_validation, batch_size=batch_size, verbose=0)
      else :                            # 10以上28未満
        pred_freq = 1
        ret = model_words_10_28.predict(x_validation, batch_size=batch_size, verbose=0)
    else :                              # 28以上
      ret_3 = model_classify_freq_3.predict(x_validation, batch_size=batch_size, verbose=0) # 評価結果
      ret_3 = ret_3.reshape(row, n_sigmoid)
      flag_3 = ret_3[0, 0]
      if flag_3 <0.5 :                  # 28以上100未満
        pred_freq = 2
        ret = model_words_28_100.predict(x_validation, batch_size=batch_size, verbose=0)
      else :                            # 100以上300未満
        pred_freq = 3
        ret = model_words_100_300.predict(x_validation, batch_size=batch_size, verbose=0)
  else :                                # 300以上
    ret_4 = model_classify_freq_4.predict(x_validation, batch_size=batch_size, verbose=0)   # 評価結果
    ret_4 = ret_4.reshape(row, n_sigmoid)
    flag_4 = ret_4[0, 0]
    if flag_4 <0.5 :                    # 300以上2000未満
      pred_freq = 4
      ret = model_words_300_2000.predict(x_validation, batch_size=batch_size, verbose=0)
    else :                              # 2000以上
      ret_5 = model_classify_freq_5.predict(x_validation, batch_size=batch_size, verbose=0) # 評価結果
      ret_5 = ret_5.reshape(row, n_sigmoid)
      flag_5 = ret_5[0, 0]
      if flag_5 < 0.5 :                 # 2000以上15000未満
        pred_freq = 5
        ret = model_words_2000_15000.predict(x_validation, batch_size=batch_size, verbose=0)
      else :                            # 15000以上
        pred_freq = 6
        ret = model_words_15000_400000.predict(x_validation, batch_size=batch_size, verbose=0)
           
  ret_word = ret.argmax(1)[0]
  print(pred_freq, '\t', indices_word[ret_word])
  text_gen += indices_word[ret_word]    # 生成文字を追加
  x_validation[0, 0:maxlen-1] = x_validation[0, 1:maxlen]
  x_validation[0, maxlen-1] =  ret_word  # 1文字シフト

print()
print(text_gen)
リスト8-1 文書生成メイン処理

 これらのリストを、以下の順で実行する。

  • リスト1
  • リスト2
  • リスト3
  • 前回記事のリスト4
  • リスト5-3
  • リスト7-2
  • リスト8-1

 結果は以下のとおり。前回よりマシになったかというと、微妙なところである。

「はございますまいか。考えて見れば、この世界の、人目につかぬ隅々では、どの様にUNK、恐ろしい事柄が、行われているか、ほんとうに想像の外《ほか》で
ございます。無論始めの終りは差入屋の方で、犯人の煙草について、差入屋《UNK》という様な差入屋の手が都合のよい――何でも政治上の秘密な運動を行うせるかどうか、実際三島駅に在UNK帰るというのだ。それはどんな問題だというのか、蕗屋の点は考の種類の老婆だったのでしょう。あの旭屋の外に過ぎないので、いやあなめには、いやあなめ」

LSTM多段化による精度改善

 LSTMを3段積むことによって、精度改善を図る。イメージは以下のとおり。

Python
    model.add(LSTM(self.n_hidden, batch_input_shape=(None, self.maxlen, self.vec_dim),
             return_sequences=True,
             kernel_initializer=glorot_uniform(seed=20170719),
             recurrent_initializer=orthogonal(gain=1.0, seed=20170719)))
    model.add(LSTM(self.n_hidden, return_sequences=True,
             kernel_initializer=glorot_uniform(seed=20170719),
             recurrent_initializer=orthogonal(gain=1.0, seed=20170719)))
    model.add(LSTM(self.n_hidden,
             kernel_initializer=glorot_uniform(seed=20170719),
             recurrent_initializer=orthogonal(gain=1.0, seed=20170719)
             ))
リスト9 3段LSTM

 1段目、2段目のreturn_sequences=Trueが重要である(デフォルトはFalse)。こう指定することで、LSTMの全ての出力系列が次のレイヤーに入力される。LSTMを多段に積むときの必須指定である。

 株価予測のときは、多段にしても効果はなかったが、今回は劇的に精度が改善した。段数が多いほど、精度が改善したが、4段以上にしても精度が改善しなかったので、段数を3としてある。

 全てのニューラルネット(単語分類用6本、単語推定用7本、計13本)に(つまり前回のリスト5-1のPredictionクラス、さらに今回のリスト5-2のPrediction_freqクラスとPrediction_wordsクラスのすべてに)、この変更を施す。

単語推定ニューラルネットの出力次元削減

 前段で出現頻度による絞り込みが行われているため、単語推定ニューラルネットでは、全ての単語を出力として想定する必要はない。例えば、頻度10未満と判定された後で動作するニューラルネットは、まさに頻度10未満の単語だけが推定できればよいはずである。すなわち、出力次元を、推定対象に合わせて減少させることができるが、これにより精度の向上が期待できる。

 この考え方に立って見直した、単語推定のコードが以下である。訓練データ作成処理では、出力として想定される単語の一覧を作成し、そのインデックスを正解値とするよう、ラベルデータを作成する。

Python
maxlen = 40      # 入力語数
n_upper = 10     # 学習対象単語の出現頻度上限
n_lower = 0      # 学習対象単語の出現頻度下限

mat_urtext = np.zeros((len(mat), 1), dtype=int)
for i in range(0, len(mat)):
  #row = np.zeros(len(words), dtype=np.float32)
  if mat[i] in word_indices :       # 出現頻度の低い単語のインデックスをUNKのそれに置き換え
    if word_indices[mat[i]] != 0 :  # 0パディング対策
      mat_urtext[i, 0] = word_indices[mat[i]]
    else :
      mat_urtext[i, 0] = len(words)
  else:
    mat_urtext[i, 0] = word_indices['UNK']

print(mat_urtext.shape)

# 単語の出現数をもう一度カウント:UNK置き換えでwords_indeicesが変わっているため
cnt = np.zeros(len(words)+1)
for j in range (0, len(mat)):
  cnt[mat_urtext[j, 0]] += 1

print(cnt.shape)

# 頻度対象内単語のリスト
words_0 = []
for i in range(0, len(words)+1) :
  if cnt[i] >= n_lower and cnt[i] < n_upper:
    words_0.append(i)
    
words_0 = sorted(list(set(words_0)))
w0_indices = dict((w, i) for i, w in enumerate(words_0))  # 単語をキーにインデックス検索
indices_w0 = dict((i, w) for i, w in enumerate(words_0))  # インデックスをキーに単語を検索

print(len(words_0))
len_seq = len(mat_urtext) - maxlen

data = []
target = []

for i in range(0, len_seq):
  # 答えの単語の出現頻度がlower_limit以上でかつnum_fleq 未満の場合を学習対象にする
  if cnt[mat_urtext[i+maxlen, :]] >= n_lower and cnt[mat_urtext[i+maxlen, :]] < n_upper:
    #print(mat_urtext[i+maxlen, :])
    data.append(mat_urtext[i:i+maxlen, :])
    target.append(w0_indices[mat_urtext[i+maxlen, :][0]])

x_train = np.array(data).reshape(len(data), maxlen, 1)
t_train = np.array(target).reshape(len(data), 1)

z = list(zip(x_train, t_train))
nr.seed(12345)
nr.shuffle(z)                 # シャッフル
x_train, t_train = zip(*z)

x = np.array(x_train).reshape(len(data), maxlen, 1)
t = np.array(t_train).reshape(len(data), 1)

for i in range(0, maxlen):
  print(x[2, i, :], indices_word[x[2, i, 0]])
print()
print(t[2, :], indices_word[indices_w0[t[2, 0]]])

x_train = x
t_train = t

print(x_train.shape, t_train.shape)
リスト4-2 単語推定訓練データ作成(出力次元削減)

 リストの中ほどに出てくるwords_0が、出力として想定される単語の一覧である。このリストを、前回記事のリスト4の代わりに実行する。

 ニューラルネットの定義に変更はない。メイン処理もほとんど変らないが、出力次元output_dimの変更が必要である。

Python
n_pattern = 0

vec_dim = 400
epochs = 100
batch_size = 200
input_dim = len(words)+1
output_dim = len(words_0)
n_hidden = int(vec_dim*1.5)  # 隠れ層の次元

prediction = Prediction(maxlen, n_hidden, input_dim, vec_dim, output_dim)
emb_param = 'param_words_'+str(n_pattern)+'_'+str(n_lower)+'_'+str(n_upper)+'.hdf5'  # 学習済みパラメーターファイル名の定義
print (emb_param)
row = x_train.shape[0]
x_train = x_train.reshape(row, maxlen)
model = prediction.train(x_train,
             np_utils.to_categorical(t_train, output_dim), batch_size, epochs, emb_param)

model.save_weights(emb_param)          # 学習済みパラメーターセーブ

score = model.evaluate(x_train.reshape(row, maxlen),
             np_utils.to_categorical(t_train, output_dim), batch_size=batch_size, verbose=1)

print("score:", score)
print()
リスト6-2 単語推定メイン処理(出力次元削減)

 以下の順で実行する。

  • リスト1
  • リスト2
  • リスト3
  • リスト4-2
  • 前回のリスト5-1(LSTM3段化修正後)
  • リスト6-2

 以上を実行すると、前回の表1に示したパターン0が完了している状態だ。ここでは、残りの7つのパターン、つまりパターン1~パターン6もここで実行する必要がある。

 これにはまず、n_lowern_upperの値を前回の表1に示したものに置き換えて、リスト4-2を再実行する。
 次に、n_patternの値をパターン名の「1」~「6」に置き換えて、リスト6-2を再実行すればよい。

 この2つの再実行をパターン6まで繰り返すことで、7つのパラメーターファイルを再生成する。

 以上が単語分類用ニューラルネットのパラメーターファイルになる。頻度分類用ニューラルネットのパラメーターファイルも、ニューラルネットワークの層が増えたので再生成しておこう。こちらは以下の手順を実行する。

  • リスト1
  • リスト2
  • リスト3
  • リスト4-1
  • リスト5-2(LSTM3段化修正後)
  • リスト6-1

 今回の表2に示したパターン0が完了している状態なので、残りの5つのパターン、つまりパターン1~パターン5もここで実行する。

 これにはまず、n_lowern_splitn_upperの値を前掲の表2に示したものに置き換えて、リスト4-1を再実行する。
 次に、n_patternの値をパターン名の「1」~「5」に置き換えて、リスト6-1を再実行すればよい。

 この2つの再実行をパターン5まで繰り返すことで、6つのパラメーターファイルを再生成する。

 これらの施策の効果は圧倒的で、どのニューラルネットも正解率が99%以上となった。

LSTM多段化と出力次元削減実施後の文書生成結果

 文書生成の能力がどれくらい上がったかを確認する。出現頻度で分類された単語のグループごとに、インデックスを付ける必要があるので、リスト4を以下のように修正する。

Python
maxlen = 40                # 入力語数

mat_urtext = np.zeros((len(mat), 1), dtype=int)
for i in range(0, len(mat)):
  #row = np.zeros(len(words), dtype=np.float32)
  if mat[i] in word_indices :       # 出現頻度の低い単語のインデックスをunkのそれに置き換え
    if word_indices[mat[i]] != 0 :  # 0パディング対策
      mat_urtext[i, 0] = word_indices[mat[i]]
    else :
      mat_urtext[i, 0] = len(words)
  else:
    mat_urtext[i, 0] = word_indices['UNK']

print(mat_urtext.shape)


# 単語の出現数をもう一度カウント:UNK置き換えでwords_indeicesが変わっているため
cnt = np.zeros(len(words)+1)
for j in range (0, len(mat)):
  cnt[mat_urtext[j, 0]] += 1

print(cnt.shape)

data = []
target = []

len_seq = len(mat_urtext)-maxlen

#for i in range(0, 10):
for i in range(0, len_seq):
  data.append(mat_urtext[i:i+maxlen, :])
  target.append(mat_urtext[i+maxlen, :])

x_train = np.array(data).reshape(len(data), maxlen, 1)
t_train = np.array(target).reshape(len(data), 1)

print(x_train.shape, t_train.shape)

# 頻度対象内単語のリスト
words_0 = []
w0_indices = []
indices_w0 = []
n_upper = [10, 28, 100, 300, 2000, 15000, 400000]
n_lower = [0, 10, 28, 100, 300, 2000, 15000]
for j in range(0, 7) :
  wk = []
  for i in range(0, len(words)+1) :
    if cnt[i] >= n_lower[j] and cnt[i] < n_upper[j]:
      wk.append(i)
  words_0.append(wk)
    
  words_0[j] = sorted(list(set(words_0[j])))
  wi = dict((w, i) for i, w in enumerate(words_0[j]))  # 単語をキーにインデックス検索
  iw = dict((i, w) for i, w in enumerate(words_0[j]))  # インデックスをキーに単語を検索
  w0_indices.append(wi)
  indices_w0.append(iw)
リスト4-3 単語分類ごとのインデックス付け

 リスト前半のx_trainは訓練用ではなく、文書生成の入力文字列用に作成している。リスト後半が単語分類ごとのインデックス付けで、出現頻度別単語リストがwords_0、単語→インデックス辞書がw0_indices、インデックス→単語辞書がindices_w0である。それぞれ、リストのリストになっている。

 また、単語推定の出力次元削減を実施した関係上、リスト7-2とリスト8-1を以下のように修正する。

Python
vec_dim=400
epochs = 100
batch_size=200
input_dim=len(words)+1
n_sigmoid = 1
n_hidden=int(vec_dim*1.5)  #隠れ層の次元

#頻度分類用
prediction_freq = Prediction_freq(maxlen,n_hidden,input_dim,vec_dim,n_sigmoid)
print('頻度分類用ニューラルネット_0活性化')
model_classify_freq_0=prediction_freq.create_model()
print('頻度分類用ニューラルネット_1活性化')
model_classify_freq_1=prediction_freq.create_model()
print('頻度分類用ニューラルネット_2活性化')
model_classify_freq_2=prediction_freq.create_model()
print('頻度分類用ニューラルネット_3活性化')
model_classify_freq_3=prediction_freq.create_model()
print('頻度分類用ニューラルネット_4活性化')
model_classify_freq_4=prediction_freq.create_model()
print('頻度分類用ニューラルネット_5活性化')
model_classify_freq_5=prediction_freq.create_model()
print()

output_dim = [0]*7
for i in range(0,7) :
    output_dim[i] = len(words_0[i])

#単語予測用
print('単語分類用ニューラルネット(0_10)活性化')
prediction_words = Prediction_words(maxlen,n_hidden,input_dim,vec_dim,output_dim[0])
model_words_0_10=prediction_words.create_model()
print('単語分類用ニューラルネット(10-28)活性化')
prediction_words = Prediction_words(maxlen,n_hidden,input_dim,vec_dim,output_dim[1])
model_words_10_28=prediction_words.create_model()
print('単語分類用ニューラルネット(28-100)活性化')
prediction_words = Prediction_words(maxlen,n_hidden,input_dim,vec_dim,output_dim[2])
model_words_28_100=prediction_words.create_model()
print('単語分類用ニューラルネット(100-300)活性化')
prediction_words = Prediction_words(maxlen,n_hidden,input_dim,vec_dim,output_dim[3])
model_words_100_300=prediction_words.create_model()
print('単語分類用ニューラルネット(300-2000)活性化')
prediction_words = Prediction_words(maxlen,n_hidden,input_dim,vec_dim,output_dim[4])
model_words_300_2000=prediction_words.create_model()
print('単語分類用ニューラルネット(2000-15000)活性化')
prediction_words = Prediction_words(maxlen,n_hidden,input_dim,vec_dim,output_dim[5])
model_words_2000_15000=prediction_words.create_model()
print('単語分類用ニューラルネット(15000-400000)活性化')
prediction_words = Prediction_words(maxlen,n_hidden,input_dim,vec_dim,output_dim[6])
model_words_15000_400000=prediction_words.create_model()
print()

# パラメーターロード
print('頻度分類用ニューラルネット_0パラメーターロード')
model_classify_freq_0.load_weights('param_classify_by_freq_0_0_300_400000.hdf5')
print('頻度分類用ニューラルネット_1パラメーターロード')
model_classify_freq_1.load_weights('param_classify_by_freq_1_0_28_300.hdf5')
print('頻度分類用ニューラルネット_2パラメーターロード')
model_classify_freq_2.load_weights('param_classify_by_freq_2_0_10_28.hdf5')
print('頻度分類用ニューラルネット_3パラメーターロード')
model_classify_freq_3.load_weights('param_classify_by_freq_3_28_100_300.hdf5')
print('頻度分類用ニューラルネット_4パラメーターロード')
model_classify_freq_4.load_weights('param_classify_by_freq_4_300_2000_400000.hdf5')
print('頻度分類用ニューラルネット_5パラメーターロード')
model_classify_freq_5.load_weights('param_classify_by_freq_5_2000_15000_400000.hdf5')
print()

print('単語分類用ニューラルネット(0-10)パラメーターロード')
model_words_0_10.load_weights('param_words_0_0_10.hdf5')
print('単語分類用ニューラルネット(10-28)パラメーターロード')
model_words_10_28.load_weights('param_words_1_10_28.hdf5')
print('単語分類用ニューラルネット(28-100)パラメーターロード')
model_words_28_100.load_weights('param_words_2_28_100.hdf5')
print('単語分類用ニューラルネット(100-300)パラメーターロード')
model_words_100_300.load_weights('param_words_3_100_300.hdf5')
print('単語分類用ニューラルネット(300-2000)パラメーターロード')
model_words_300_2000.load_weights('param_words_4_300_2000.hdf5')
print('単語分類用ニューラルネット(2000-15000)パラメーターロード')
model_words_2000_15000.load_weights('param_words_5_2000_15000.hdf5')
print('単語分類用ニューラルネット(15000-400000)パラメーターロード')
model_words_15000_400000.load_weights('param_words_6_15000_400000.hdf5')
print()
リスト7-3 文書生成用パラメーターロード
Python
n_init = 6000

x_validation = x_train[n_init, :, :]
x_validation = x_validation.T
row = x_validation.shape[0]     # 評価データ数
x_validation = x_validation.reshape(row, maxlen)

text_gen = ''                   # 生成テキスト
for i in range(0, maxlen) :
  text_gen += indices_word[x_validation[0, i]]

print(text_gen)
print()

# 正解データ
text_correct = ''
for j in range(0, 4) :
  x_correct = x_train[n_init+j*maxlen, :, :]
  x_correct = x_correct.T
  #row = x_correct.shape[0]     # 評価データ数
  x_correct = x_correct.reshape(row, maxlen)
  for i in range(0, maxlen) :
    text_correct += indices_word[x_correct[0, i]]

print('正解')
print(text_correct)
print()

flag_1 = flag_2 = flag_3 = 0
# 応答文生成
for k in range (0, 100) :
  # 単語予測
  # 300
  ret_0 = model_classify_freq_0.predict(x_validation,  batch_size=batch_size,  verbose=0)   # 評価結果
  ret_0 = ret_0.reshape(row, n_sigmoid)
  flag_0 = ret_0[0, 0]
  # 最大値インデックス
  if flag_0 < 0.5 :                     # 300未満
    ret_1 = model_classify_freq_1.predict(x_validation, batch_size=batch_size, verbose=0)   # 評価結果
    ret_1 = ret_1.reshape(row, n_sigmoid)
    flag_1 = ret_1[0, 0]
    if flag_1 < 0.5 :                   # 28未満
      ret_2 = model_classify_freq_2.predict(x_validation, batch_size=batch_size, verbose=0) # 評価結果
      ret_2 = ret_2.reshape(row, n_sigmoid)
      flag_2 = ret_2[0, 0]
      if flag_2< 0.5 :                  # 10未満
        pred_freq = 0
        ret = model_words_0_10.predict(x_validation, batch_size=batch_size, verbose=0)
        ret_w0 = ret.argmax(1)[0]
        ret_word = indices_w0[0][ret_w0]
      else :                            # 10以上28未満
        pred_freq = 1
        ret = model_words_10_28.predict(x_validation, batch_size=batch_size, verbose=0)
        ret_w0 = ret.argmax(1)[0]
        ret_word = indices_w0[1][ret_w0]
    else :                              # 28以上
      ret_3 = model_classify_freq_3.predict(x_validation, batch_size=batch_size, verbose=0) # 評価結果
      ret_3 = ret_3.reshape(row, n_sigmoid)
      flag_3 = ret_3[0, 0]
      if flag_3 <0.5 :                  # 28以上100未満
        pred_freq = 2
        ret = model_words_28_100.predict(x_validation, batch_size=batch_size, verbose=0)
        ret_w0 = ret.argmax(1)[0]
        ret_word = indices_w0[2][ret_w0]
      else :                            # 100以上300未満
        pred_freq = 3
        ret = model_words_100_300.predict(x_validation, batch_size=batch_size, verbose=0)
        ret_w0 = ret.argmax(1)[0]
        ret_word = indices_w0[3][ret_w0]
  else :                                # 300以上
    ret_4 = model_classify_freq_4.predict(x_validation, batch_size=batch_size, verbose=0)   # 評価結果
    ret_4 = ret_4.reshape(row, n_sigmoid)
    flag_4 = ret_4[0, 0]
    if flag_4 <0.5 :                    # 300以上2000未満
      pred_freq = 4
      ret = model_words_300_2000.predict(x_validation, batch_size=batch_size, verbose=0)
      ret_w0 = ret.argmax(1)[0]
      ret_word = indices_w0[4][ret_w0]
    else :                              # 2000以上
      ret_5 = model_classify_freq_5.predict(x_validation, batch_size=batch_size, verbose=0) # 評価結果
      ret_5 = ret_5.reshape(row, n_sigmoid)
      flag_5 = ret_5[0, 0]
      if flag_5 < 0.5 :                 # 2000以上15000未満
        pred_freq = 5
        ret = model_words_2000_15000.predict(x_validation, batch_size=batch_size, verbose=0)
        ret_w0 = ret.argmax(1)[0]
        ret_word = indices_w0[5][ret_w0]
      else :                            # 15000以上
        pred_freq = 6
        ret = model_words_15000_400000.predict(x_validation, batch_size=batch_size, verbose=0)
        ret_w0 = ret.argmax(1)[0]
        ret_word = indices_w0[6][ret_w0]
           
 
  print(pred_freq, '\t', indices_word[ret_word])
  text_gen += indices_word[ret_word]     # 生成文字を追加
  #pred_parts = index_parts
  x_validation[0, 0:maxlen-1] = x_validation[0, 1:maxlen]
  x_validation[0, maxlen-1] =  ret_word  # 1文字シフト

print()
print(text_gen)
リスト8-2 文書生成メイン処理(出力次元削減対応)

 リスト8-1に代えて、これを実行する。

 最終的なリストの実行順は、以下のとおり。

  • リスト1
  • リスト2
  • リスト3
  • リスト4-3
  • リスト5-3(LSTM3段化修正後)
  • リスト7-3
  • リスト8-2

 文書生成結果は以下のとおり。100発100中である。

 まず、「お題」となる入力文字列は以下のとおり。

「はございますまいか。考えて見れば、この世界の、人目につかぬ隅々では、どの様にUNK、恐ろしい事柄が、行われているか、ほんとうに想像の外《ほか》で」

 これに続く文章の生成結果は以下のとおり。

「ございます。無論始めの予定では、盗みの目的を果しさえすれば、すぐにもホテルを逃げ出す積《つも》りでいたのですが、世にも奇怪な喜びに、夢中になった私は、逃げ出すどころか、いつまでもいつまでも、椅子の中をUNKのUNKにして、その生活を続けていたのでございます。UNK《UNK》の外出には、注意に注意を加えて、少しも物音を立てず、又人目に触れない様にしていましたので、当然、危険はありませんでしたが、それにしても、数ヶ月という、長い」

 ちなみに正解は以下のとおり。

「ございます。無論始めの予定では、盗みの目的を果しさえすれば、すぐにもホテルを逃げ出す積《つも》りでいたのですが、世にも奇怪な喜びに、夢中になった私は、逃げ出すどころか、いつまでもいつまでも、椅子の中をUNKのUNKにして、その生活を続けていたのでございます。UNK《UNK》の外出には、注意に注意を加えて、少しも物音を立てず、又人目に触れない様にしていましたので、当然、危険はありませんでしたが、それにしても、数ヶ月という、長い」

江戸川乱歩に無関係な文章を入力にしてみる

 江戸川乱歩に無関係の、訓練データでない文章を入力にして、どの程度、江戸川乱歩風の文章が生成されるか試してみる。

 文書生成メイン処理(つまり先ほどのリスト8-2)を、以下のリストに取り換える。それ以外は、先ほどと同じリストの実行順で実行する。

Python
df_input = csv.reader(open('input.csv', 'r'))
data_input = [ v for v in df_input]
mat_input = np.array(data_input)
mat_input = mat_input[:, 0]

print(mat_input.shape)
text_gen = ''                         # 生成テキスト
x_validation = np.zeros((1, maxlen))  # 入力データ
# 入力データx_validationに入力文の単語インデックスを設定
for i in range(0, len(mat_input)) :
  text_gen += mat_input[i]
  #x_validation 1文字シフト
  x_validation[0, 0:maxlen-1] = x_validation[0, 1:maxlen]
  if mat_input[i] in words :
    x_validation[0, maxlen-1] = word_indices[mat_input[i]]
  else :
    x_validation[0, maxlen-1] = word_indices['UNK']

print(text_gen)
print()

row = x_validation.shape[0]            # 評価データ数

print()

flag_1 = flag_2 = flag_3 = 0
# 応答文生成
for k in range (0, 400) :
  # 単語予測
  # 300
  ret_0 = model_classify_freq_0.predict(x_validation, batch_size=batch_size, verbose=0)     # 評価結果
  ret_0 = ret_0.reshape(row, n_sigmoid)
  flag_0 = ret_0[0, 0]
  # 最大値インデックス
  if flag_0 < 0.5 :                     # 300未満
    ret_1 = model_classify_freq_1.predict(x_validation, batch_size=batch_size, verbose=0)   # 評価結果
    ret_1 = ret_1.reshape(row, n_sigmoid)
    flag_1 = ret_1[0, 0]
    if flag_1 < 0.5 :                   # 28未満
      ret_2 = model_classify_freq_2.predict(x_validation, batch_size=batch_size, verbose=0) # 評価結果
      ret_2 = ret_2.reshape(row, n_sigmoid)
      flag_2 = ret_2[0, 0]
      if flag_2< 0.5 :                  # 10未満
        pred_freq = 0
        ret = model_words_0_10.predict(x_validation, batch_size=batch_size, verbose=0)
        ret_w0 = ret.argmax(1)[0]
        ret_word = indices_w0[0][ret_w0]
      else :                            # 10以上28未満
        pred_freq = 1
        ret = model_words_10_28.predict(x_validation, batch_size=batch_size, verbose=0)
        ret_w0 = ret.argmax(1)[0]
        ret_word = indices_w0[1][ret_w0]
    else :                              # 28以上
      ret_3 = model_classify_freq_3.predict(x_validation, batch_size=batch_size, verbose=0) # 評価結果
      ret_3 = ret_3.reshape(row, n_sigmoid)
      flag_3 = ret_3[0, 0]
      if flag_3 <0.5 :                  # 28以上100未満
        pred_freq = 2
        ret = model_words_28_100.predict(x_validation, batch_size=batch_size, verbose=0)
        ret_w0 = ret.argmax(1)[0]
        ret_word = indices_w0[2][ret_w0]
      else :                            # 100以上300未満
        pred_freq = 3
        ret = model_words_100_300.predict(x_validation, batch_size=batch_size, verbose=0)
        ret_w0 = ret.argmax(1)[0]
        ret_word = indices_w0[3][ret_w0]
  else :                                # 300以上
    ret_4 = model_classify_freq_4.predict(x_validation, batch_size=batch_size, verbose=0)   # 評価結果
    ret_4 = ret_4.reshape(row, n_sigmoid)
    flag_4 = ret_4[0, 0]
    if flag_4 <0.5 :                    # 300以上2000未満
      pred_freq = 4
      ret = model_words_300_2000.predict(x_validation, batch_size=batch_size, verbose=0)
      ret_w0 = ret.argmax(1)[0]
      ret_word = indices_w0[4][ret_w0]
    else :                              # 2000以上
      ret_5 = model_classify_freq_5.predict(x_validation, batch_size=batch_size, verbose=0) # 評価結果
      ret_5 = ret_5.reshape(row, n_sigmoid)
      flag_5 = ret_5[0, 0]
      if flag_5 < 0.5 :                 # 2000以上15000未満
        pred_freq = 5
        ret = model_words_2000_15000.predict(x_validation, batch_size=batch_size, verbose=0)
        ret_w0 = ret.argmax(1)[0]
        ret_word = indices_w0[5][ret_w0]
      else :                            # 15000以上
        pred_freq = 6
        ret = model_words_15000_400000.predict(x_validation, batch_size=batch_size, verbose=0)
        ret_w0 = ret.argmax(1)[0]
        ret_word = indices_w0[6][ret_w0]


  print(pred_freq, '\t', indices_word[ret_word])
  text_gen += indices_word[ret_word]    # 生成文字を追加

  x_validation[0, 0:maxlen-1] = x_validation[0, 1:maxlen]
  x_validation[0, maxlen-1] = ret_word  # 1文字シフト

print()
print(text_gen)
リスト8-3 文書生成メイン処理(出力次元削減対応)

 リスト8-2との違いは、最初の方の、入力文字列設定処理である。リスト8-2では、訓練データの中から初期値を選択していたが、リスト8-3では、入力ファイル「input.csv」で与えられる文字列を入力している。input.csvは入力文章をJUMAN++を用いてCSVファイルに整形したものである。

 今回は、川端康成「雪国」の冒頭を使ってみた。その結果は以下のとおりである。少し長めに(200語)、文書生成してみた。

 まず、「お題」となる入力文字列は以下のとおり。

「国境の長いトンネルを抜けると雪国であった。夜の底が白くなった。信号所に汽車が止まった。向側の座席から娘が立って来て、島村の前のガラス窓を落した。」

 これに続く文章の生成結果は以下のとおり。

「そして、中央に通り、見通しの末が記されていた。そこに転がっている。だが、黒い影が見た。そして、さして残してもどうことだ」そこで私は、これがどんな態度で、死んだ大阪の町から鳥異様な見えたでもあるというよりも、雪子さんのそっくりの全身、そっとあの興奮を取出し、つくさえも、まるで忘れてしまうようなのか、表題潤様子を睨みつけていたが、この世に飽きUNKいた。この玉の中には、一体なんのためにも、その勢いをUNKことなぞできない。だが、どちらかといえば、若い女性であったが、ああした行動をUNK終ると、UNKにUNKれた闇の中に、さいぜん老人は、真赤な糸《くだ》を出し、手足を綺麗にUNK、やっと気を沈めて、ソッと註文を示す。「掘り返して、ぼんやりとしたものだ」のであろう、右の方に、三つの廊下から、イライラし実験室にUNK時、その薄暗いボートをUNK、用意のような様子で、黒い覆面の黒布が、こみ上げてくるおかしさにつれ去る×UNKられていると、サッと大阪の腕を掴んで、口から飛びこんで行った。「あんたは、真にせまったと鉄一円札の束をUNK力で、それに属する女を、この大阪のポケットから上をたよりにUNKいた。そして、その一人はだれもない。服そうに髭にUNKと、また躊躇をUNKない場合が、小さく固っていた。彼らは三尺程の写真を取出して、写真で大丈夫UNK別にUNKを待つ始めた。くれるぞ、それは彼の耳の側へ駈けつけて置いて見たが、UNKも哀れな、一通の病気のステッキをUNK。つまりトランクの男によって千円の如何なる水泳ことの外には何の動作も、直ちにUNK行って、」

 なお、正解は当然ないので示していない。

 訓練データでは100発100中だったので、もう少し小説らしい文章になると期待していたが、汎化性能を犠牲にしているので、これはやむを得ないところかもしれない。

おわりに

 今回の記事には入っていないが、「UNK」の復元にも挑戦してみた。以下のリンク先の、筆者のプレゼンの中で言及している。

 今回の方式は、コンセプトが分かりやすく、当初は実装も容易と思われたので、読める文章が生成されることを期待していた。正解率を向上させれば使い物になると考えたが、任意の文字列を入力にしたときにはいまひとつであった。訓練データの規模をさらに大きくすれば、あるいは、より精度の高い文書生成が可能になるかもしれない。

 今回は、正解率を向上させるのに苦労した。恐らく訓練データのばらつきが大きかったためと想像している。分類数を少なくしたり、似たような傾向のデータをグルーピングしたりすると正解率を向上させることができたが、これは他の局面にも応用できると考えている。

  • このエントリーをはてなブックマークに追加
ディープラーニング習得、次の一歩
1. Kerasを用いたディープラーニング(LSTM)による株価予測

ディープラーニングのチュートリアルが一通り終わったら、次に何をやる? 今回は、誰にでも簡単にできる「株価予測」をテーマに、LSTMのニューラルネットワークを、Kerasを使って実装する方法を説明する。

2017年12月11日(月)
ディープラーニング習得、次の一歩
2. 挑戦! word2vecで自然言語処理(Keras+TensorFlow使用)

自然言語のベクトル化手法の一つである「word2vec」を使って、単語間の関連性を表現してみよう。Keras(+TensorFlow)を使って実装する。

2018年1月30日(火)
ディープラーニング習得、次の一歩
3. word2vecリターンズ! 品詞分類による精度改善

Keras(+TensorFlow)を使って自然言語のベクトル化手法「word2vec」を実装。学習データに品詞分類を追加することによって、前回よりも予測精度が改善するかを検証する。

2018年4月5日(木)
ディープラーニング習得、次の一歩
4. ディープラーニングで自動筆記 - Kerasを用いた文書生成(前編)

ディープラーニングによる自然言語処理の一つ「文書生成」にチャレンジしてみよう。ネットワークにLSTM、ライブラリにKeras+TensorFlowを採用し、徐々に精度を改善していくステップを説明する。

2018年7月5日(木)
ディープラーニング習得、次の一歩
5. 【現在、表示中】≫ ディープラーニングで自動筆記 - Kerasを用いた文書生成(後編)

「文書生成」チャレンジの後編。ネットワークにLSTM、ライブラリにKeras+TensorFlowを採用し、さらに精度を改善していく。最後に、全然関係ない入力文章から、江戸川乱歩風文書が生成されるかを試す。

2018年8月8日(水)
Deep Insider の SNS :