2017/02/25

LSTMで文字列を単語(番号)にマッピング

LSTMを無意味に使ってみました。

目的は、文字列を入力したら単語が出力されるニューラルネットワーク(NN)を作ることです。
単なるマッピングなので、連想配列を使えば解決ですが、無理やりNNで実現してみます。

入力層は、文字のone-hotベクトルとします。
同じく、出力層は単語のone-hotベクトルとします。

学習データはどこからともなく見つけてきた英単語300個。

入力と出力の長さが異なっているので、そろえるためにブランク記号 _ を使います。

例えば、n e u r o n を入力すると、_ _ _ _ _ neuron が出力されるように学習します。
単語ごとに文字列長が異なるので、足りない分は入力側にも _ を使います。

単語リストは

business
company
...
neuron
というふうに、単に1行に1単語が書かれたファイルです。

このファイルを次のスクリプトの第1引数に指定してスクリプトを実行します。
学習用ライブラリにはTensorFlowベースのKerasを利用しました。

ブランク記号 _ には数値で0を、ASCII文字には1から順に整数を割り当てています。
単語についても同じです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import numpy as np
import sys,os
import numpy.random as random
from keras.models import Sequential
from keras.layers import Dense, Activation, LSTM, TimeDistributedDense
from keras.optimizers import RMSprop
import keras.preprocessing.text as pp

if len(sys.argv) != 2:
    print("Usage: seq2seq.py [word list]")
    exit(0)

words_file = sys.argv[1]
if not os.path.isfile(words_file):
    print(words_file+" is not found.")
    exit(0) 

random.seed(123)

# 単語データの読み込み
words = []
max_ch_seq = 0
for line in open(words_file):
    line = line.rstrip('\n')
    chars = [ord(c)-ord('a')+1 for c in list(line)]
    col = np.array(chars)
    max_ch_seq = max(max_ch_seq, len(col))
    words.append([col, line])

# 文字長を揃える
max_ch_seq_orig = max_ch_seq
for w in words:
    while len(w[0]) < max_ch_seq:
        w[0] = np.append(w[0], 0) # 0はブランク

word_id_map = {}
id = 1
for w in words:
    word_id_map[w[1]] = id # 重複がないことが前提
    id += 1

input_vec_len = ord('z') - ord('a') + 2 # +1はブランク入力のため
output_vec_len = len(words) + 1 # +1はブランク出力のため
np.set_printoptions(threshold=np.inf)
for w in words:
    # 入力(=文字)のone-hotベクトルを作成する
    input_vec_seq = np.zeros((len(w[0]), input_vec_len))
    input_vec_seq[np.arange(len(w[0])), w[0]] = 1
    w.append(input_vec_seq)

    # 出力(=単語)のone-hotベクトルを作成する
    x = np.zeros(len(w[0]), dtype=int)
    x[-1] = word_id_map[w[1]] #最後に単語を入れる
    output_vec_seq = np.zeros((len(w[0]),output_vec_len))
    output_vec_seq[np.arange(len(w[0])), x] = 1
    w.append(output_vec_seq)

# この時点で、wordsの各要素は、
#   (文字列(数字), 単語, 文字列のOne-hotベクトル, 単語のOne-hotベクトル)
# となっている。
data_dup = 1  # 何度も同じデータを入れるなら、2以上にする

# 訓練用データを設定する
x_train = np.empty((len(words)*data_dup, max_ch_seq, input_vec_len))
y_train = np.empty((len(words)*data_dup, max_ch_seq, output_vec_len))
for j in range(0, data_dup):
    for i in range(0, len(words)):
        x_train[i+len(words)*j] = words[i][2]
        y_train[i+len(words)*j] = words[i][3]

# 評価用データを設定する
x_val = np.empty((len(words), max_ch_seq, input_vec_len))
y_val = np.empty((len(words), max_ch_seq, output_vec_len))
for i in range(0, len(words)):
    x_val[i] = words[i][2]
    y_val[i] = words[i][3]

# モデルを作る
batch_size = 50
model = Sequential()
model.add(LSTM(16, return_sequences=True,
               batch_input_shape=(batch_size, max_ch_seq, input_vec_len)))
model.add(LSTM(16, return_sequences=True))
model.add(TimeDistributedDense(128))
model.add(TimeDistributedDense(output_vec_len, activation='softmax'))

# 学習する
rmsprop = RMSprop(lr=0.0005, rho=0.9, epsilon=1e-08)
model.compile(loss='categorical_crossentropy', optimizer=rmsprop, metrics=['accuracy'])
model.fit(x_train, y_train, batch_size=batch_size, nb_epoch=5000, validation_data=(x_val, y_val))

# モデルを保存
model.save_weights('model.dat')

# 結果の確認
r = model.predict_classes(batch_size=batch_size, x=x_val)
print(r)
78行目まではデータの読み込みと整形部分です。80行目からがモデルを作ったり学習したりする部分です。 とても短いですね

さて、実行すると

Train on 300 samples, validate on 300 samples
Epoch 1/5000
300/300 [==============================] - 1s - loss: 5.6607 - acc: 0.4613 - val_loss: 5.5990 - val_acc: 0.8738
Epoch 2/5000
300/300 [==============================] - 1s - loss: 5.5510 - acc: 0.9046 - val_loss: 5.4750 - val_acc: 0.9215
Epoch 3/5000
300/300 [==============================] - 1s - loss: 5.4081 - acc: 0.9231 - val_loss: 5.3032 - val_acc: 0.9231
Epoch 4/5000
300/300 [==============================] - 1s - loss: 5.2126 - acc: 0.9231 - val_loss: 5.0716 - val_acc: 0.9231
Epoch 5/5000
300/300 [==============================] - 1s - loss: 4.9520 - acc: 0.9231 - val_loss: 4.7682 - val_acc: 0.9231

...

Epoch 1009/5000
300/300 [==============================] - 0s - loss: 0.2684 - acc: 0.9367 - val_loss: 0.3089 - val_acc: 0.9241
Epoch 1010/5000
300/300 [==============================] - 1s - loss: 0.2687 - acc: 0.9297 - val_loss: 0.2368 - val_acc: 0.9646
Epoch 1011/5000
300/300 [==============================] - 1s - loss: 0.2416 - acc: 0.9479 - val_loss: 0.2385 - val_acc: 0.9559

...

Epoch 1499/5000
300/300 [==============================] - 1s - loss: 0.2082 - acc: 0.9518 - val_loss: 0.2559 - val_acc: 0.9285
Epoch 1500/5000
300/300 [==============================] - 1s - loss: 0.1796 - acc: 0.9649 - val_loss: 0.1584 - val_acc: 0.9869
Epoch 1501/5000
300/300 [==============================] - 1s - loss: 0.1629 - acc: 0.9759 - val_loss: 0.1578 - val_acc: 0.9874

...

Epoch 1999/5000
300/300 [==============================] - 1s - loss: 0.0943 - acc: 0.9967 - val_loss: 0.0892 - val_acc: 0.9990
Epoch 2000/5000
300/300 [==============================] - 1s - loss: 0.0941 - acc: 0.9967 - val_loss: 0.0890 - val_acc: 0.9987
Epoch 2001/5000
300/300 [==============================] - 1s - loss: 0.1502 - acc: 0.9644 - val_loss: 0.1626 - val_acc: 0.9487

...

Epoch 2999/5000
300/300 [==============================] - 1s - loss: 0.0155 - acc: 0.9995 - val_loss: 0.0123 - val_acc: 1.0000
Epoch 3000/5000
300/300 [==============================] - 1s - loss: 0.0134 - acc: 1.0000 - val_loss: 0.0121 - val_acc: 1.0000
Epoch 3001/5000
300/300 [==============================] - 1s - loss: 0.0134 - acc: 1.0000 - val_loss: 0.0120 - val_acc: 1.0000

...

Epoch 4998/5000
300/300 [==============================] - 1s - loss: 0.0800 - acc: 0.9813 - val_loss: 6.6157e-04 - val_acc: 1.0000
Epoch 4999/5000
300/300 [==============================] - 1s - loss: 6.1272e-04 - acc: 1.0000 - val_loss: 5.0139e-04 - val_acc: 1.0000
Epoch 5000/5000
300/300 [==============================] - 1s - loss: 4.9502e-04 - acc: 1.0000 - val_loss: 4.4888e-04 - val_acc: 1.0000
というようなログが出力されます。汎化性能は無視なので、ひたすらlossを小さくしました。やりすぎですね。

99行目のprint文の出力は

[[  0   0   0   0   0   0   0   0   0   0   0   0   1]
 [  0   0   0   0   0   0   0   0   0   0   0   0   2]
 [  0   0   0   0   0   0   0   0   0   0   0   0   3]
 [  0   0   0   0   0   0   0   0   0   0   0   0   4]
 [  0   0   0   0   0   0   0   0   0   0   0   0   5]
 [  0   0   0   0   0   0   0   0   0   0   0   0   6]
 [  0   0   0   0   0   0   0   0   0   0   0   0   7]
 [  0   0   0   0   0   0   0   0   0   0   0   0   8]
 [  0   0   0   0   0   0   0   0   0   0   0   0   9]
 [  0   0   0   0   0   0   0   0   0   0   0   0  10]

...

 [  0   0   0   0   0   0   0   0   0   0   0   0 298]
 [  0   0   0   0   0   0   0   0   0   0   0   0 299]
 [  0   0   0   0   0   0   0   0   0   0   0   0 300]]
となります。きちんと、途中は全てブランク(=0)が出ていて、
単語の番号が最後に正しく出力されているのが分かります。

というわけで、LSTMは結構過去のことを覚えておいてくれることが分かりました。
そして、とても重たい連想配列ができました。

hg view

いつのまにかMercurialに面白いエクステンションが追加されていました。

hg view

Tck/Tkを使って、履歴表示やファイルの内容の表示を行ってくれます。
TortoiseHGを入れるのが面倒なときに活躍するかも。

.hgrc に

[extensions]
hgk =

を書くだけ。

確認したhgのバージョンは3.1.2です。

ソースコードを色つきHTMLに変換する方法

いつも忘れるので、メモ。

SyntaxHighlighterのようなJavaScriptを使ってソースコードの色づけをする方法もありますが、 NoScriptを使ってスクリプトをブロックする環境で見ている人にとっては許可操作が面倒なので、 HTMLで埋め込むほうが表示が簡単です。

ツールは他にもありますが、ここでは、pygmentize を紹介します。
http://pygments.org/

インストールしてしまえば、あとは使うだけ。

次のようなシェルスクリプトを作って、引数に変換したいソースコードを指定すると、HTMLファイルが作成されます。

1
2
#!/bin/bash
pygmentize -f html -O full -O style=vs,linenos=1 -o $1".html" $1
作成されたHTMLファイルの中身を記事に貼り付ければ完成です。
背景色や境界線を変更したい場合は、行番号とソースコード本体を囲っている<pre>タグにCSSのクラスを指定します。

2017/02/12

MCMC法

Markov Chain Monte Carlo (MCMC) を何のために使うのかが分かる講義(※1)。
https://www.youtube.com/watch?v=-H28H1unn0M

MCMC法をググっても手法ばかりが書かれていて、その目的の説明がすっぽり
抜け落ちているものだらけなので、MCMCって何という人は聞く(見る?)価値あり。

ものすごく短くまとめると、

  • 高次元の確率分布をランダムサンプリングで求める方法がMCMC法
  • そのうちの1つがGibbs Samplingで、条件付き確率分布から ランダムサンプリングできるのであれば、同時確率分布から ランダムサンプリングできるという手法
となる。


もう少しGibbs Samplingについて詳細に書くと、

確率変数の集合をAとして、|A|次元の同時確率分布からランダムサンプリングしたい場合は、
全てのa∈Aに対して、A\{a}の確率変数の値を決めたときに、aの確率分布から
ランダムサンプリングできるのであれば、Gibbs Samplingを使うことができる、

ということ。


(※1) http://qiita.com/shogiai/items/bab2b915df2b8dd6f6f2 からたどり着いた。

2017/02/07

Meta-learning

機械学習におけるメタ学習(Meta-learning)の2015年のレビュー論文を見つけたので、メモ。
Christiane Lemke, Marcin Budka and Bogdan Gabrys, "Metalearning: a survey of trends and technologies," Artf Intell Rev, 44, pp. 117-130, 2015
http://link.springer.com/article/10.1007%2Fs10462-013-9406-y