連載
» 2018年08月08日 05時00分 公開

ディープラーニング習得、次の一歩:ディープラーニングで自動筆記 − Kerasを用いた文書生成(後編) (2/3)

[石垣哲郎,著]

LSTM多段化による精度改善

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

    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未満の単語だけが推定できればよいはずである。すなわち、出力次元を、推定対象に合わせて減少させることができるが、これにより精度の向上が期待できる。

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

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の変更が必要である。

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を以下のように修正する。

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を以下のように修正する。

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 文書生成用パラメーターロード

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》の外出には、注意に注意を加えて、少しも物音を立てず、又人目に触れない様にしていましたので、当然、危険はありませんでしたが、それにしても、数ヶ月という、長い」

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。