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

» 2018年07月05日 05時00分 公開
[石垣哲郎]
前のページへ 1|2|3       

単語出現頻度による分類と、正解率改善

 学習結果の改善をいろいろ試行錯誤するうちに、出現頻度の近い単語だけを集めて学習すると、精度が改善することに気が付いた。そこで、最初に出現頻度を予測して単語を頻度別に分類し、ついで単語自体を予測するという、2段構えのニューラルネットを考案した。

 単語を出現頻度に応じて、以下の7種類に分類する。

  • 3〜10
  • 10〜28
  • 28〜100
  • 100〜300
  • 300〜2000
  • 2000〜15000
  • 15000以上

 分類の数や境界値は、いろいろ試行錯誤して決定したもので、この値でなければならないという強い理由があるわけではない。

 全体構成のイメージを図3に示す。

図3 単語出現頻度による分類を取り入れたニューラルネット構成 図3 単語出現頻度による分類を取り入れたニューラルネット構成

出現頻度による単語分類

 分類のために、新たにラベルデータを準備する必要がある。以下はそのコードである。

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)

len_seq = len(mat_urtext)-maxlen
data = []
target = []
for i in range(0, len_seq):
  data.append(mat_urtext[i:i+maxlen, :])
  # 出現頻度に応じてラベルの値を設定
  if cnt[mat_urtext[i+maxlen, :]] < 10 :       # 頻度が10未満なら0
    target.append(0)
  elif cnt[mat_urtext[i+maxlen, :]] < 28 :     # 頻度が28未満なら1
    target.append(1)
  elif cnt[mat_urtext[i+maxlen, :]] < 100 :    # 頻度が100未満なら2
    target.append(2)
  elif cnt[mat_urtext[i+maxlen, :]] < 300 :    # 頻度が300未満なら3
    target.append(3)
  elif cnt[mat_urtext[i+maxlen, :]] < 2000 :   # 頻度が2000未満なら4
    target.append(4)
  elif cnt[mat_urtext[i+maxlen, :]] < 15000# 頻度が15000未満なら5
    target.append(5)    
  else  :                                     # 頻度が15000以上なら6
    target.append(6)     
    
x = np.array(data).reshape(len(data), maxlen, 1)
t = np.array(target).reshape(len(data), 1)


z = list(zip(x, t))
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(t_train.max(0))

リスト4-3 分類用訓練データ作成

 ラベルデータtargetの値に、予測対象単語の出現頻度に応じて、0から6までのいずれかを設定している。

 ニューラルネットの定義には変更はない。メイン処理は、ラベルデータの次元が変わる関係上、変更が入る。

vec_dim = 400 
epochs = 100
batch_size = 200
input_dim = len(words)+1
output_dim = 7

#t_dim = len(over_20)+1
n_hidden = int(vec_dim*1.5# 隠れ層の次元

prediction = Prediction(maxlen, n_hidden, input_dim, vec_dim, output_dim)

emb_param = 'param2_1_classify_by_7.hdf5'  # パラメーターファイル名
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)

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

リスト6-2 分類用メイン処理

 変更点は、ニューラルネットのインスタンス生成や学習時に指定する、ラベルデータの次元である。

 実行コードと実行順を整理すると、以下のとおりである。

  • リスト1
  • リスト2
  • リスト3 (ここまで共通)
  • リスト4-3
  • リスト5-1
  • リスト6-2

 正解率は47%程度になった。

出現頻度別単語予測

 次に単語予測である。単語の出現頻度ごとに学習を実施する。訓練データ作成用のコードは以下のとおり。

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)

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, :])

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)


x_train = x
t_train = t

print(x_train.shape, t_train.shape)

リスト4-4 単語予測用訓練データ作成

 学習対象範囲を、n_upperおよびn_lowerによって指定する。

 ニューラルネットの定義(リスト5-1)とメイン処理(リスト6-1)は変更ないが、メイン処理ではパラメーターファイル名が被らないよう、名称定義に注意すること。

 以下、各パラメーターファイルは以下の名称が付与されている前提で、論を進める。

頻度区分 パラメーターフェイル名
3〜10 param_words_0_0_10.hdf5
10〜28 param_words_1_10_28.hdf5
28〜100 param_words_2_28_100.hdf5
100〜300 param_words_3_100_300.hdf5
300〜2000 param_words_4_300_2000.hdf5
2000〜15000 param_words_5_2000_15000.hdf5
15000以上 param_words_6_15000_400000.hdf5
表1 パラメーターファイル名

 コードの実行順は以下のとおり。

  • リスト1
  • リスト2
  • リスト3 (ここまで共通)
  • リスト4-4
  • リスト5-1
  • リスト6-1

 つまり、リスト4-4とリスト5-1を実行した後、リスト6-1のemb_paramparam_words_0_0_10.hdf5に変えて実行する。さらに、リスト4-4のn_upper010に、n_lower028に変えて、リスト6-1のemb_paramparam_words_0_0_10.hdf5param_words_1_10_28.hdf5に変えて再実行する。この要領で、表1のすべての頻度区分を順に実行していく。

 この対策により、単語予測の正解率は9割以上に改善した。単語分類と合わせた全体の正解率は、36%以上という計算になる。

結果確認

 正解率改善の結果が文書生成にどれくらい反映されているか、確認する。

 ニューラルネットの活性化と、学習済みパラメーターのロード処理は、以下のとおりである。都合8つのニューラルネットを生成し、おのおのに訓練したパラメーターを設定する。

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

# 頻度分類用
print('頻度分類用ニューラルネット活性化')
prediction_freq = Prediction(maxlen, n_hidden, input_dim, vec_dim, 7)
model_classify_freq_0 = prediction_freq.create_model()
print()
# 単語予測用
prediction_words = Prediction(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('頻度分類用ニューラルネットパラメーターロード')
model_classify_freq_0.load_weights('param2_1_classify_by_7.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-1 文書生成用ニューラルネット活性化とパラメーターロード(出現頻度による単語分類)

 文書生成のメイン処理は以下のとおりである。予測した単語頻度ごとに、専用のニューラルネットを用いて単語を予測する。

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) :
  ret_0  = model_classify_freq_0.predict(x_validation, batch_size=batch_size, verbose=0# 評価結果
  ret_0 = ret_0.reshape(row, 7)
  flag_0 = ret_0.argmax(1)[0
  if flag_0 == 0 :
    ret = model_words_0_10.predict(x_validation, batch_size=batch_size, verbose=0)
    ret_word = ret.argmax(1)[0
  
  elif flag_0==1 :
    ret = model_words_10_28.predict(x_validation, batch_size=batch_size, verbose=0)
  elif flag_0==2 :
    ret = model_words_28_100.predict(x_validation, batch_size=batch_size, verbose=0)
  elif flag_0==3 :
    ret = model_words_100_300.predict(x_validation, batch_size=batch_size, verbose=0)
  elif flag_0==4 :
    ret = model_words_300_2000.predict(x_validation, batch_size=batch_size, verbose=0)
  elif flag_0==5 :
    ret = model_words_2000_15000.predict(x_validation, batch_size=batch_size, verbose=0)
  else :
    ret = model_words_15000_400000.predict(x_validation, batch_size=batch_size, verbose=0)

  ret_word = ret.argmax(1)[0

  #print(flag_0, 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-2
  • リスト5-1
  • リスト7-1
  • リスト8-1

 実行結果は以下のとおりである。

「はございますまいか。考えて見れば、この世界の、人目につかぬ隅々では、どの様にUNK、恐ろしい事柄が、行われているか、ほんとうに想像の外《ほか》で
はありません。あの出来事は、僕がすっかり抜いて置いたんだな。これはどうしてまあ、何という恐ろしい事だ。そこで、僕は随分可能性をハッキリとしたのです。これは自分の訊問に対しても、余りにUNKことがあるのです。彼は思わず、ハッとした様に、あらゆる事情をUNK、最も深く考えていた。彼は、そうして、旅をして、何という事の次第を取出して、恐る恐るの穴に光っている。そして」

 だいぶ日本語らしくなった。「てにをは」も割としっかりしている。しかし、脈絡のなさにはちょっと笑ってしまう。

次回予告

 さらなる正解率改善施策により、どこまで自然な文章にできるか、チャレンジする。

 最初はもっと簡単に文書生成ができると予想していたが、思いの外、苦戦した。正解率がなかなか向上しないのが意外であったが、いくつか施策を準備しているので、次回はその結果を報告する。

「ディープラーニング習得、次の一歩」のインデックス

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

前のページへ 1|2|3       

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

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

メールマガジン登録

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