技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

LSTM で文章生成

white-azalea.hatenablog.jp

これの続き。

概要

LSTM は学習したあと、与えられた一定長のデータから、続きを予測するというDeeplarningの一種だが、そこで文章生成を試してみたという内容です。

データの作成

ではどうやって学習されるかですが、まずは使われてる文字種を探ります。
学習に使ったのは太宰治の「惜別」。選んだ理由は「有名文豪で、文章の長いやつ」という適当な理由。
太宰治は短編ばかりで長いやつがなかなかないのよねぇ…)

データソースは青空文庫

www.aozora.gr.jp

著作権切れてるってこういうときに扱いやすいですね。

chars_list = []
def readchars(line):
    for c in line:
        if not c in chars_list:
            chars_list.append(c)

text = ''
with open('dazai.txt', encoding='utf-8') as f:
    text = f.read()
    readchars(text)

n_chars = len(chars_list)
print('文字種 : ', n_chars)
print('文字数 : ', len(text))

結果は

文字種 :  2154
文字数 :  91002

これを1文字 2154 ビットのビット配列にする(One-Hot 表現※)。
※ One-hot 表現は、「あいう」の3種の文字を One-Hot で置き換えると [1,0,0], [0,1,0], [0,0,1] のような、どこか1つだけが 1 となっている配列で表現すること。

まずは変換テーブル作って

chars_list = sorted(list(set(text)))
n_chars = len(chars_list)

# 文字->インデックス値
char_to_index = {}
# インデックス->文字
index_to_char = {}
for i, char in enumerate(chars_list):
    char_to_index[char] = i
    index_to_char[i] = char

で、これを使って変換する。

# 時系列の長さ(20文字分)
n_time = 20

# 時系列データ
seq_chars = []  
next_chars = []
for i in range(0, len(text) - n_time):
    seq_chars.append(text[i: i + n_time])
    next_chars.append(text[i + n_time])

# 変換開始
input_data = np.zeros((len(seq_chars), n_time, n_chars), dtype=bool)
correct_data = np.zeros((len(seq_chars), n_chars), dtype=bool)
for i, chars in enumerate(seq_chars):
    correct_data[i, char_to_index[next_chars[i]]] = 1
    for j, char in enumerate(chars):
        input_data[i, j, char_to_index[char]] = 1

input_data は「1次元目=事前データ」「2次元目=次に続く文字」「3次元目=文字のOne-Hot表現」。
こんな漢字で配列に変換して食わせる。

出力層は恒等関数で出力し、画面に表示する前に index_to_char 使って逆変換をかければ良い。

そして、以下がその結果である。先頭文字列は「これは日本の東北地方の某村に開業している」(書き出し)を指定。

Epoch: 1/50  710/710  Error: 38.97258947692782
これは日本の東北地方の某村に開業している。のであしよ。のとたののでは、しかないかないる。といかいか、そ生のの自にその私にに順ていて、のにんはにってっていい。ではすりいるのでは、そのんえは、のをは、十んのうな、如をんのきしうのちので、といるでと時いた。どは、そのれしているのでは、いでないた。できいう。このでは、そのは、そんののとををたのでにといていいい。かのど事のは、そのは、といんで、心とないた。で、そんうで、さんにのですあうで、そにゃな

Epoch: 11/50  710/710  Error: 27.03339931626056
これは日本の東北地方の某村に開業しているのです。」
 私はその日本のは、日本のの御の中ではないから、その時の同一の事を見て、この事には、その時には、その時の中のたちは、その日本の本人のようにも、自分のたちは、その日本の革命の思想のおいです。」という事になった。それですから、それは、私の下宿の小さんなのだから、それでも、その頃のお事を、それでも、それからも、私の周さんのおめて、そのようです。」ともうかしいのです。それから、という事などこの

Epoch: 21/50  710/710  Error: 22.203776773089785
これは日本の東北地方の某村に開業している事があるか、周さんが、まあ、こんなにして、私のように思われる。この時、これは、あなたは、その頃の、だから、これは、その頃の、それからも、いまにも無い。などは、そんなに老人の体を作っている。
「ええ、それから、いや、それは、これは、ただけて、その日は一つでにないません。それは、この時、と言いています。」
 私は、その頃、それはどうだ。」
 私は、その文明の学生は、日本ではないと思っているのだ。いまい

Epoch: 31/50  710/710  Error: 21.313044093698576
これは日本の東北地方の某村に開業しているので、自分はこの先生の講義には、それを感じ、わし、ただ、それをごて、そのような気がして、「どうも、その地方の一元の、お国の事を言うので、自分の生活のまたこの三年の一枚の民衆の一緒にどんどしたのである。自分の田舎の自分の下宿にも、その仙台の自分の自分の生徒だ、と言い、その選ばれた秀才に対して、「変incer に出た。そのような感心、という事を言う事を、出て、その東洋の威力に依って、このまたために、そ

Epoch: 41/50  710/710  Error: 18.67849606292455
これは日本の東北地方の某村に開業しているのです。」
「いや、どうも、どうも、そのまいにこの私は、いや、周さんと逢って、そのままで、日本の維新は、子供が、また、その上、そのようなものです。あした。いい事もまた私は、それに、自分は周さんの話したのです。僕は、その頃、一緒になって、そのままに来たので、どうも、そのようないい方であった。周さんは、その頃、そのままざまのなので、実は、どうも、いや、どうです。」と言って、新後の十年がいうのです。」と

Epoch: 50/50  710/710  Error: 17.55149063553411
これは日本の東北地方の某村に開業しているのだ。自分の下宿には何も何とか、一面親しく、一つとり、周さんと逢って置かれていたのですが、そうして、大丈夫かね。私の顔を見ると、私はそれに対して、たしかにおそのように思われた。そうして、あの人たちには、その一時の松島に於ける事が多くりにならぬ振りをしていた。それに、自分はその日本の一群の蘭学のお客になって、それに、また、あんなことしていました。そうして、日本へ来ても、それにならざるを得ないような気

ちなみに学習時間は3時間ほど(Core i9 - 9900KF で)。
出してみて思ったのは、支離滅裂な文章だなぁ…と。一方で文字レベルで生成している割にはランダムでは無いなと感じた次第だ。

中間層ニューロン等増やして、もっと学習させればよりマトモになっていくのではと思う。
少なくとも学習が進むたびにかろうじて単語は整ってきている。

おまけで、小説の中に出てきていない、オリジナルな20文字を食わせて生成させてみた。

Seed: さあ、どうしようか。スパイの可能性は避け

生成された文章:
さあ、どうしようか。スパイの可能性は避ける、となるかと言えない。日本の一般の家庭では、そのうちに、周さんと逢ったりして、そうして、日本へ来てばかりにいたが、津田氏とったくなったら、いいかね。」と先生はクラスの者たちに、そこそは明治の高い者のかくをうしている。それに、自分はその日、とっと呼びと言い、そのごろ、その後にはじると、そのような青いやがり得るだ。
  雲ちょっと笑いなかったのです。僕は、このごろ、山の毒です。お父さんは、それに書き

謎の反応を返してるwwww
面白いといえば面白いが、先行が 20 文字というが 20 文字前のことを忘れて話してるようなものなのだから、やはり日本語にはかなり弱いのではなだろうかと思った次第だ。