LoginSignup
1
1

More than 1 year has passed since last update.

自然言語処理をとりあえず動かしてみた

Posted at

初めて記事を投稿します。
普段はPHPを使ってわちゃわちゃと開発をしております。

突然ですが私、高校生の頃に物書きになりたいと思っていた時期がありました。
その頃はライトノベルに激ハマりしていたので、将来はライトノベル作家だ! などと夢を胸に秘めていたのです。

なんでならなかったのか?

友人に書いたものを見せたら紙を破られ「こんなのダメに決まってんじゃん」と言われたからです。
ピュアなハートが傷ついてしまった私は物書きを諦めました。

一念発起

そんな私も再び小説熱が盛り返してきました、あの頃とは違う、今なら書ける、超大作がかける! そしてウハウハになる!
自分の力以外を使って。。。

早速作ってもらおう

何を使うか

自然言語処理で使われるものには、RNNとかGRUとか色々ありますが、今回はLSTMを使って作成していこうと思います。
なんでかというとLSTMの文字を見て、qiitaのLGTMが頭に浮かんだからです。はい、直感です。
今回は自動文章生成処理ってどんなものか? というのが目的なので、これでいいのだ(バカボン風)

準備

まず準備として、参考とする小説を決めます。
今回は文章の自動生成処理を動かしてみて、どのようになるかを見ることを前提としています。
ですので青空文庫様にお力をお借りして、太宰治の「走れメロス」を学習に利用します。
後はやる気とPC(Python)さえあれば準備OKです。やる気が一番準備に時間かかり、、、いえ、なんでもありません。

テキストを整形する

さて、まずは青空文庫様より走れメロスをDLして、Pythonファイルと同一フォルダに配置し読み込みます。

import re

# 走れメロスを読み込む
with open("hashire_merosu.txt", mode="r", encoding="shift-jis") as f:
	merosu_text = f.read()

青空文庫様のtxtファイルには不要なもの(ルビとか説明とか)があるので取り除きます。

# 整形するための関数
def aozora_shaping(raw_text):
    # タイトル・著者名を削除
    text = re.sub(r'\A.*?\n.*?\n', '', raw_text)
    # 空白・改行を削除
    text = re.sub(r' | |\n|\r', '', text)
    # ルビ説明文を削除
    text = re.sub(r'-{55}.*?-{55}', '', text)
    # 末尾の文を削除
    text = re.sub(r'底本:.*\Z', '', text)
    # かっこ等の記号を削除
    text = re.sub(r'《.*?》|||[.*?]|〔|〕|#|※', '', text)

    return text

text = aozora_shaping(merosu_text)
出力.
メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。メロスには政治がわからぬ ~長いので省略

無事不要な箇所は取り除かれているようです。

ベクトル化

続いて、文字をベクトル化していく必要がありますので、実施していきます。

設定を記述しておく

設定.
n_rnn = 10
batch_size = 128
epochs = 30
neuron = 256

そして、先程の文章から各文字をリストに保存します。

# setで重複をなくし、各文字をリストに保存する
chars = sorted(list(set(text)))
出力.
['―', '、', '。', '々', '「', '」', 'あ', 'い', 'う', 'え', 'お', 'か' ~長いので省略

ちゃんとリスト化されているようです。
続いて、作成した文字リストで辞書を作成していきます。

char_index = {}
index_char = {}

# 文字がキー、インデックスが値の辞書
for i, char in enumerate(chars):
	char_index[char] = i

# 文字が値、インデックスがキーの辞書
for i, char in enumerate(chars):
	index_char[i] = char
出力.
# char_index
{'―': 0, '、': 1, '。': 2, '々': 3, '「': 4, '」': 5, 'あ': 6 ~省略

# index_char
{0: '―', 1: '、', 2: '。', 3: '々', 4: '「', 5: '」', 6: 'あ ~省略

続いて、時系列データと、それから予測すべき文字を取り出すための処理を記述します。

for i in range(0, len(text) - n_rnn):
	time_chars.append(text[i: i+ n_rnn])
	next_chars.append(text[i+ n_rnn])
出力.
# time_chars
['メロスは激怒した。必', 'ロスは激怒した。必ず', 'スは激怒した。必ず、', 'は激怒した。必ず、か' ~省略

# next_chars
['ず', '、', 'か', 'の', '邪', '智', '暴', '虐', 'の', '王', 'を', '除', 'か', 'な', 'け', 'れ', 'ば', 'な', 'ら', 'ぬ' ~省略

ふむふむ、できてそうです。
続いて、入力と正解をone-hot表現で表します。

import numpy as np

x = np.zeros((len(time_chars), n_rnn, len(chars)), dtype=np.bool_)
t = np.zeros((len(time_chars), len(chars)), dtype=np.bool_)

for i, t_cs in enumerate(time_chars):
	t[i, char_index[next_chars[i]]] = 1 # 正解
	for j, char in enumerate(t_cs):
		x[i, j, char_index[char]] = 1 # 入力

print(x.shape)
print(t.shape)
出力.
(9796, 10, 815) # サンプル数、時系列、文字の数
(9796, 815) # サンプル数、文字の数

LSTMモデル

続いていよいよ、LSTMのモデルを構築していきます。
Kerasを使うので、まずはKerasをインストールします。

kerasのインストール

File "~/syosetu.py", line 3, in <module>
from keras.models import Sequential

インストールしないで実施しようとすると怒られたので(当たり前)、kerasをインストールします。
tensorflowのインストールが推奨されているようなので、まずはtensorflowを入れる

pip3 install --upgrade tensorflow

続いてkerasのインストール

pip3 install keras

インポートしてみる

from keras.models import Sequential
from keras.layers import Dense, LSTM
from keras.callbacks import LambdaCallback

実行して怒られなければOKだと思います
ではモデルを構築していきます

model_lstm = Sequential()

# コンパイル
model_lstm.add(LSTM(neuron, input_shape=(n_rnn, len(chars))))
model_lstm.add(Dense(len(chars), activation="softmax"))
model_lstm.compile(loss="categorical_crossentropy", optimizer="adam")

# サマリーを出力
print(model_lstm.summary())
出力.
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #
=================================================================
 lstm (LSTM)                 (None, 256)               1097728

 dense (Dense)               (None, 815)               209455

=================================================================
Total params: 1,307,183
Trainable params: 1,307,183
Non-trainable params: 0
_________________________________________________________________
None

パラメータ数がすごい数ですね(小学生並の感想)

文章作成

# エポック終了後に文字を出力する関数
def on_epoch_end(epoch, logs):
	print("エポック後", epoch)

    # 確率分布を調整する定数
	beta = 5
	prev_text = text[0:n_rnn]
	created_text = prev_text

	print("シード", created_text)

	for i in range(800): # 800文字生成
        # 入力をone-hot表現にする
		x_pred = np.zeros((1, n_rnn, len(chars)))
		for j, char in enumerate(prev_text):
			x_pred[0, j, char_index[char]] = 1

        # 予測を行い、次の文字を得る
		y = model.predict(x_pred)
		p_power = y[0] ** beta
		next_index = np.random.choice(len(p_power), p=p_power/np.sum(p_power))
		next_char = index_char[next_index]

		created_text += next_char
        # 最新の時系列が入る
		prev_text = prev_text[1:] + next_char

	print(created_text)
	print()

# エポック終了時に実行
epock_end_callback = LambdaCallback(on_epoch_end=on_epoch_end)

# 学習させる
model = model_lstm 
history_lstm = model_lstm.fit(x, t, batch_size=batch_size, epochs=epochs, callbacks=[epock_end_callback])

さて、いよいよお手並み拝見です。
私はウハウハできるのでしょうか!!!!!!!




1回目出力.
Epoch 1/30
76/77 [============================>.] - ETA: 0s - loss: 5.4609エポック後 0
シード メロスは激怒した。必
メロスは激怒した。必、、。の。いた。。のた。をに、、。。。。い。た。の。い。。た。。。。のの。た。。、の。、。。
い。。を、、の。のい。。。。にのいい。の。。の。い、い。。。。。。。の。。く、。た。。。。。。、、、、。のたいいだ。、
。のの。る。。。。。い。。。たた。る。。のい。。。、、た、いのて。。。。、。。、。、、。、。い。。い。、を。たのの。。
、た。。い。。をのい。。、のた。を、、、。。のらの。。。。、。を。。、。、の、い。。。いには。。。。を。。い。。。、。
。。にだは、。。。。、の、。はい。い、の。。。。、。し。。をたのて。。。く、、。のの、。。。。。。。。。。の。。のた、
、。。いい、。。、、。。をのの。。、のいた。、。、。をの。い。。。。る。い。い。。い。。ら。。。。。。。、い、。。は。。
。た。。。。い。。をを。。な。。た、。、。い。、い、。いっ。。をいい。のいのた。。、。のい。。。い。。。、、、。の、。
。を。。。をた。。。た。。。。。。。。。。。、、の、。、い。。。、。。いい。。。。。。。。。いい、。。、。。。を、。。、。い、
。。ない。。の、、。だ、。。ら。。い。、く、、とた。。。。。。。、、。、。たいの。。、の、た。た。、。い。の。。。、。。
をの。。の。。。。ス。を。。、を。、、い。。い、の。。の、。、の。。。、。。、の。の。の。。い。。、。、、。た。たい。
い。。、な。、。。の。、。。。、。。、のた。な、、。い。の。。、。。。、の。。、。、。、。。。。。の。。。ののてる。の
。。。。。。。。、、、、。の、。。。の。。、、、。の。。、の、い。。。、。の。の。、、。るた。。。、た。。。い。。。。う。。
、を。の。。た。の。の。。い、。を。の。。。。。、なのをい。、。のい。。。い。。、い。、にを。、。。。た。。とをた。
の、た。を、。。た。。。の。、。、。。を。。い。、いの。。。、、い。、、、。

何を言っているのかさっぱりわかりません。
これでは私はウハウハできません。。。

今回エポック数を30にしているので、30回目の結果を見てみましょう。

30回目出力.
Epoch 30/30
77/77 [==============================] - ETA: 0s - loss: 1.3715エポック後 29
シード メロスは激怒した。必
メロスは激怒した。必呆、メロスの市にして来るとようにああ、ああなが狂うただ。あった。私は、どっとみたなから、何もなっていいのだ。
私は、ち遠の士とした。きってもるのである。メロスは、まって行るとだろう。私は、ちちどもなわぬ。全くして、わなの心であるのだ。
私は、いまえななった。私の命は、わずなわれにもうから、何も無いのおまえになった。メロスは、悠々とと仕度を聞いた。
たの、メロスの人を殺した。「あるのだ。」としているのだ。」メロスは、ましてはりをたんだ。「あったち、あのだちだから、わしても問をされた。
「あるのだ。」「どうない。」「メロスは、まだ。そう、ども、いいまの気がりを見っていた。。
「それては、まった。「あのだ。」「どう。」メロスは、おまえに行くないない。」「どう、その男をして下さいた。」「その、いのだのだ。」「それない。」
「どう。メロスは、おまえのと婿を事いた。「れて、メロスは走った。「あるたのだ。」「どう、その心を殺して下るのだ。」
「王の、人の心を殺した。私はなのでものだ。ありない、私は、わずの市に走ったから、私は、ちまったのだ。」
「どう、私は、まのだが、それからぬば、わからは無いの顔おまえ、それなら、間いいい。おまえには、その男いのだのだ。
私は、信実のだけた。私の疲のたち、ちととっとして、きのとでであるのだ。私は、信じてくれた。私は、私の信実裏た。」
とはは、こ込ですまでの気ををついていり、ころでった。「ああ、あのた。あのだが、どうでもわしのいを、きりって、それであるのだ。
私は、信じてくれた。」として、私の心を殺してやる事だ。」「メロ、は、おまえであるた。いの人は、ちちととわしくして下さいた。
メロスは、まだよう、私を殺される。メロスは、まがられて、村のたのであるのだ。陽は、ものも無くであった。
メロスは、それかられば、このでいますのそのだって、ふのしてくれなように深いし、私は、ちんんだんだっな

最初に比べると大分まともに見えます!
とはいえ小説とは呼べないレベルですね。。。

結論

試行回数も参考資料も少ないので、ウハウハな小説にするには、まだまだ程遠いですね。
俺はようやくのぼりはじめたばかりだからな、このはてしなく遠い男坂をよ!

〜完〜




嘘です。完にしません。

GRUでも試してみる

GRUでも同じことをしたいと思います。
記述を少し変えるだけでできるみたいなので。

# GRU、Denseをインポートする
from keras.layers import Dense GRU

model_gru = Sequential()
model_gru.add(GRU(n_mid, input_shape=(n_rnn, len(chars))))
model_gru.add(Dense(len(chars), activation="softmax"))
model_gru.compile(loss="categorical_crossentropy", optimizer="adam")

model = model_gru 
history_lstm = model_gru.fit(x, t, batch_size=batch_size, epochs=epochs, callbacks=[epock_end_callback])

この記述で実行します。

1回目.
Epoch 1/30
77/77 [==============================] - ETA: 0s - loss: 5.4536エポック後 0
シード メロスは激怒した。必
メロスは激怒した。必の、、、いはい、、、、、の、、、い、の、い、、のス、、いの、、にをのはの、、、の、。、、、、。の、、、。
の、。の、、、、、、、、の、いたに、の、、の、、の、、い、ののの、、。、、、、、、。、、、の、、、いい、の、、れ、の、の、、、
の、、にのに。、に、にに、、は、にの、とは、た、のに、ののい。、、のに、、、、の、に、、のた、の、のにのにの、に、、
い、、、い、う。の、、、。、た、の、にの、の、、に、、、なのにのたい、に、、、い、。、い、、、のい、の、。。いに、、にの、
、に、に、、、、、、。、ののい、、のの、。い、にのののの、に、の、、、い、、にの、のになの、、、、、、、、のの、、、、のな、
、に、のに、、いいの。、にに、、、、、、な。、、、、の、、の、の、、に、、、いの、のの、、の、、、、、、に、の、は、の、、い、
いに、、、のの、、の、れ、の、の、、、、の、、は、の、、、、、、、の、、に、い、のに、、の、、、の、、い。、、、。の、、、、、
、、い、の、、、、、いの。、、、の、、。、の、、にの、いに、、、、、、い。い、のののロ、て、、。、、にの、に、。。、たに、、、、
。のる、、、、ののは、、の、、、、に。の、いののの、に、、、の、、っ、いの。、、、の、、、い、、の。に、い、、、、、、、に、、
にに、のい、に。の、、、、、は、、の、。た、、、に、、の、、、、、、、、、ての、、、の、、、。、、の。の、、、、、いいの、、のの。
の、、い。の、にの、、のに、、れ、、に、、は、、の、い、、、い。に、のた、に、い、、いのに、の、のい。、、い、、。のた、に、
、た、。の、、、、、、た、いの、に、たの、にの、い、、。る、。いに、、、に、、の、い、、、、ののの、のに、い、、のた、のの、
、の、の、、ののの、のに、にの、、、、の、、、に、にの、、のた、ののい、にの、、いの、、、、の、、、いの、、、、の、、、。、

相変わらず何を言っているのかさっぱりわかりませんね。




続いて同じく30回目を見ていきましょう。

30回目.
Epoch 30/30
77/77 [==============================] - ETA: 0s - loss: 0.4364エポック後 29
シード メロスは激怒した。必
メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。メロスには政治がわからぬ。
メロスは、それから花婿の肩をたたいて、「仕度の無いのはお互さまさ。私の家にも、宝といっては、妹と羊だけだ。
他には、何も無い。全部あげよう。もう既に、メロスの弟になったことを誇ってくれ。」花婿は揉み手して、てれた。
それから、皇后さまを。それから、賢臣のアレキス様を。」「それは、そのマントを打るせて。そうして、メロスの弟になっところになっていない。
」メロスは、ます、三日目の日暮まで、ここに帰って来なかったら、あの友人を絞め殺して下さい。たの、それでも私の無い。
おまえの兄は、何もといただかりに、なんの市でしっとうろうった。「市に用事、おまえた。私の命から、皇后さまを。
それから、賢臣のアレキス様を。」「おどろいた。国王は乱心か。」「いいえ、乱心ではございませぬ。人を、信ずる事が出来ぬ、というのです。
」メロスは腕に唸りをつけてセリヌンティウスは、縄打たれた。メロスは、すぐに出発した。初夏、満天の星である。
メロスは、メロスの弟になったことを誇ってくれ。」花婿は揉み手して、てれた。その心には、神も哀れと思ったか、ついに憐愍を垂れてくれた。
押し流されつつも、見事、対岸の樹木の幹に、すがりつく事が出来たのである。ありがたい。
メロスは、それから花婿の肩をたたいて、「仕度の無いのはお互さまさ。私の家にも、宝といっては、妹と羊だけだ。
他には、何も無い。全部あげよう。もう一つ、メロスの弟になったことを誇ってくれ。」花婿は揉み手して、てれた。
その心には、ここんないなんでもった。メロスの頭は、からっぽだか。夜であるのだ。私は信頼されている。
私は、信頼に報いなければならぬ。いまはた王だ。私は王は、一気にかった。セリヌンティウスは、縄打たれた。
メロスは、それから花婿の肩をたたいて、「仕度の無いのはお互さまさ。私の家にも、宝といって

LSTMと見比べてみると面白いですね。こっちの方が文章としては良さそう。
参考データを増やしてみたり、エポック数を増やしてみたりすれば、もう少し精度の高い自動生成がされると思います。

今回は文章生成を実際に触ってみることをゴールとしていたので、ここまで!
正直、まだ中で何が起きているかの詳細は把握できていない。。。今後の課題。

今後

AIが作曲したものに、AIで歌詞つけられたら面白そう。

参考にしたサイト

1
1
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
1
1