LoginSignup
2
4

More than 3 years have passed since last update.

小説からRNNを用いて文章自動生成2(勉強メモ)

Posted at

小説からRNNを用いて文章自動生成

文豪の小説を学習させ、文章の自動生成に取り組んでみた勉強メモ2回目。

夏目漱石の"坊ちゃん"をリカレントニューラルネットワークで学習させ、
坊ちゃんのような軽快な文章の自動生成に挑戦する。

前回の記事"https://qiita.com/umez/items/3f17bab84e61508aed6c"
では、疎な行列のままRNNにいれ、文章にならない文章を作成してしまいました。

今回は単語の分散表現を実装し、結果を確認してみます。

参考URL

【Python】機械学習で文章を自動生成する方法
https://sleepless-se.net/2018/08/20/python-machine-learning/
keras Embedding層
https://keras.io/ja/layers/embeddings/
keras 目的関数
https://keras.io/ja/objectives/

準備

ニューラルネットワークはkerasでモデル作成します。
文章の解析にはjanomeを利用しました。

from keras.layers import Dense, Activation, SimpleRNN
from keras.models import Sequential
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import janome
import numpy as np

前回と同じく、坊ちゃんを分かち書きします。
ローカルにダウンロードしていた坊ちゃんの情報を読み込みます。

FILE_PATH = "./bocchan.txt"
text=""
with open(FILE_PATH, 'r',encoding="Shift-JIS") as f:
    for line in f:
        t = Tokenizer(wakati=True)
        lines = t.tokenize(line)
        text += " ".join(lines)
text[0:500]

'坊っちゃん夏目 漱石+ 目次一親 譲 お やゆ ずり の 無鉄砲 むてっぽうで 小 供 の 時 から 損 ばかり し て いる 。 小学校 に 居る 時分 学校 の 二 階 から 飛び降り て 一 週間 ほど 腰 こし を 抜 ぬかし た 事 が ある 。 なぜ そんな 無闇 むやみ を し た と 聞く 人 が ある かも 知れ ぬ 。 別段 深い 理由 で も ない 。 新築 の 二 階 から 首 を 出し て い たら 、 同級生 の 一 人 が 冗談 じ ょうだんに 、 いくら 威張 いばっ て も 、 そこ から 飛び降りる 事 は 出来 まい 。 弱虫 や ー い 。 と 囃 は やし た から で ある 。 小使 こづかい に 負ぶさっ て 帰っ て 来 た 時 、 おやじ が 大きな 眼 め を し て 二 階 ぐらい から 飛び降り て 腰 を 抜かす 奴 やつ が ある か と 云い っ た から 、 この 次 は 抜かさ ず に 飛ん で 見せ ます と 答え た 。親類 の もの から 西洋 製 の ナイフ を 貰 もらっ て 奇麗 きれい な 刃 は を 日 '

strをリスト型に変換します。
区切り文字は半角の空白です。

#リスト = str.split(‘区切り文字’)
text_list = text.split(" ")
#リスト型になっているか確認
text_list[8]#'無鉄砲'

#分かち書きした単語数を算出
text_uniqlist= list(set(text_list))
len(text_uniqlist)#坊ちゃんの使用単語数は6832

準備として、単語のID化と、学習データの作成を行います。

#坊ちゃんに含まれる単語とIDを辞書形式で保存する
ID2word = dict((i,c) for i, c in enumerate(text_uniqlist))
word2ID = dict((c,i) for i, c in enumerate(text_uniqlist))

#学習データの作成
SEQLEN = 10 #文書の次元数
xtrain_chars = []
ytrain_chars = []

for i in range( 0,len(text_list) - SEQLEN):
    xtrain_chars.append(text_list[i:i+SEQLEN])  #学習対象のデータ一覧
    ytrain_chars.append(text_list[i+SEQLEN])  #予測対象

単語IDを分散表現していきます。
これは、単語IDを固定次元の密ベクトルに変換する作業になります。
本記事では、kerasのEmbedding層(https://keras.io/ja/layers/embeddings/)
を用います。

Embedding層の定義方法ですが、
Embedding(語彙数, 分散ベクトルの次元数, 入力の系列長))となります。

model.add (Embedding(num_word, OUTPUT_DIM,input_length=SEQLEN))

 語彙数:"num_word"
  :坊ちゃんの使用単語数(6832単語)
 分散ベクトルの次元数:"OUTPUT_DIM" 
  :単語IDを何次元のベクトルに変換するか。1つの単語IDが"OUTPUT_DIM"次元の固定密ベクトルに変換されます。
 入力の系列長:"input_length"
  :単語の並びの数(先ほどの"#学習データの作成時"にSEQLEN(=10)単語で定義)

ちなみにEmbeddingレイヤーはモデルの最初のレイヤーとしてのみ利用できます。
Embedding層の入力・出力のサイズは下記の通り。
この層が出力したテンソルを、SimpleRNN層に引き渡していきます。

 入力:(BATCH_SIZE , SEQLEN)
 出力:(BATCH_SIZE , SEQLEN, OUTPUT_DIM)

誤差関数lossは、"sparse_categorical_crossentropy"を使用します。
慣習的な方法では、ラベルにOne-hot encodingを施さなくてはならないのですが、この誤差関数を用いることで、その処理をスキップすることができます。

EPISODE = 5
HIDDEN_SIZE = 128
BATCH_SIZE = 32
OUTPUT_DIM = 300
num_word = len(text_uniqlist)#6832単語

#学習モデルの構築
model = Sequential()
model.add (Embedding(num_word, OUTPUT_DIM,input_length=SEQLEN))#語彙数, 分散ベクトルの次元数, 入力の系列長
model.add (SimpleRNN( HIDDEN_SIZE , return_sequences = False , input_shape = ( SEQLEN, OUTPUT_DIM ),unroll = True ))
model.add ( Dense(num_word) )
model.add ( Activation("softmax"))
model.compile( loss = "sparse_categorical_crossentropy" , optimizer = "rmsprop" )#categorical_crossentropyと同じですが,スパースラベルを取る点で違います

model.summary()

for epoch in range(EPISODE):
    model.fit(np.array(xtrain_chars),np.array(ytrain_chars),batch_size=BATCH_SIZE)

ネットワーク構造は下記の通り。

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, 10, 300)           2049600   
_________________________________________________________________
simple_rnn_1 (SimpleRNN)     (None, 128)               54912     
_________________________________________________________________
dense_1 (Dense)              (None, 6832)              881328    
_________________________________________________________________
activation_1 (Activation)    (None, 6832)              0         
=================================================================

続いて学習したモデルを作成して、文章を生成していきます。

予測した単語IDを取得するために、predict_classesメソッドを用いています。
predictメソッドですと、Embeddingした単語ベクトルが取得されてしまいます。
predict_classesメソッドを使用することで、Embeddingする前の単語IDが
取得可能です。

NUM_PREDS_PER_EPOCH = 60

test_index = np.random.randint(len(xtrain_chars))
test_chars =[]
test_chars = text_list_id[test_index:test_index+SEQLEN]
test_chars = np.array(test_chars)
test_chars = test_chars[np.newaxis, :]


print("(元となる文章):",end="")
for i in range(len(test_chars[0])):
    print(ID2word[test_chars[0][i]],end=" ")

for i in range (NUM_PREDS_PER_EPOCH):
    pred = model.predict(np.array(test_chars)) #予測した単語ベクトルを取得
    pred_label = model.predict_classes(np.array(test_chars)) #予測した単語IDを取得
    test_chars=np.delete(test_chars, 0)
    test_chars=np.append( test_chars, pred_label )
    test_chars = test_chars[np.newaxis, :]
    print()
    print(' ======================== ')
    for i in range(len(test_chars[0])):
        print(ID2word[test_chars[0][i]],end=" ")

(元となる文章):人 を 驚 ろか し や がっ て 、 どう
========================
を 驚 ろか し や がっ て 、 どう し
========================
驚 ろか し や がっ て 、 どう し て
========================
ろか し や がっ て 、 どう し て いる
========================
し や がっ て 、 どう し て いる 。
========================
や がっ て 、 どう し て いる 。 おれ
========================
がっ て 、 どう し て いる 。 おれ は
========================
て 、 どう し て いる 。 おれ は は
========================
、 どう し て いる 。 おれ は は ない
========================
どう し て いる 。 おれ は は ない 。
========================
し て いる 。 おれ は は ない 。 おれ
========================
て いる 。 おれ は は ない 。 おれ は
========================
いる 。 おれ は は ない 。 おれ は は
========================
。 おれ は は ない 。 おれ は は ない
========================
(以下略)

"おれ は は ない 。"を延々と繰り返していく結果となりました。

次回の記事では、学習アルゴリズムを変更or坊ちゃんの要約に取り組んでみようと思います。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4