無から始める Keras のもくじはこちら
前回のおさらい
前回は雑なデータセットを作って雑に学習させた。
今までは単純な Sequential モデルを使っていたが、より柔軟性がありレイヤーを積み上げる雰囲気が出る Functional API を使った。
入力は 5 次元で、要素がそれぞれ 0 以上 1 未満。出力は 1 次元で、入力の要素の和が 2.5 以下だったら -1、2.5 より大きかったら 1。いわゆる「2 クラス分類」をした。
入力層は 5 次元。
隠れ層は 20 次元で、活性化関数は tanh とした。
出力層は 1 次元で、こちらも活性化関数は tanh とした。
学習時の損失関数はヒンジ損失を使用した。
モデルの重み
いままでは雰囲気で学習し雰囲気で推定していた。(!)
ただ学習の方を実装するのは若干面倒くさい。たしかに微分を計算してイタレーションするのは本当は実装するべきなのだが、まあここは Keras くんという神(ほんとうは中にいる TensorFlow もしくは Theano くんが神)なのでちょっと無視することにしよう。
せめて推定くらいパラメータからできるようになっておこう、というのが今回の目標である。
何度もいうように、各レイヤーは「入力に対して重み付け和をとる」「バイアスを加える」「(あるなら)活性化関数をかける」の 3 操作をする。
この 3 操作を手でやって、ちゃんと動くことを確認してみよう。
モデルの学習
あまり大きなモデルだと手計算が面倒くさいので、もっと小さくする。
入力を 2 次元(各要素は 0 以上 1 未満)、出力は入力の和が 0.5 以下だったら -1、0.5 より大きかったら 1 とする。
隠れ層は 5 次元とし、その他の条件は先程と同じにした。
今回は値が知りたいので対話型環境でやるとよい。
前回とは異なり、テンソル(層に対して入力を与えた状態)ではなく、層そのままで扱っている。
モデルに入力するときにそのテンソルを返している。
import numpy as np
from keras.layers import Input, Dense
from keras.models import Model
data = np.random.rand(250,2)
labels = (np.sum(data, axis=1) > 0.5) * 2 - 1
input = Input(shape=(2,))
hidden = Dense(5, activation='tanh')
output = Dense(1, activation='tanh')
model = Model(input=input, output=output(hidden(input)))
model.compile('adam', 'hinge', metrics=['accuracy'])
model.fit(data, labels, nb_epoch=150, validation_split=0.2)
重みの取得
さて、次に重みを知る。重みは層にたいして get_weights()
することで得られる。
hidden.get_weights()
出力は以下のようになる。最初の配列が重みベクトル、次がバイアス。
[array([[-1.08239257, 0.32482854, 0.95010394, 0.00501535, -0.47380614],
[-0.56682748, 1.15749049, 0.91618514, 0.37518814, -0.67639047]], dtype=float32),
array([-0.18290569, 0.21453567, 0.01353107, 0.27740911, 0.09604219], dtype=float32)]
output
に対しても同様に重みを得ると以下の通り。
[array([[-0.8775745 ],
[ 1.09351909],
[ 0.21981503],
[ 1.31380796],
[-0.10301871]], dtype=float32),
array([ 0.27410847], dtype=float32)]
手計算
さて、実際に計算してみよう。
入力を $\boldsymbol{x}$(2 次元の縦ベクトル)とする。
このとき、上の出力から、隠れ層の出力 $\boldsymbol{h}$ は以下のようになる(tanh は要素にかける、${}^\top$ は転置を表す)。
\boldsymbol{h} = \tanh\left(
\begin{bmatrix}
-1.08239257 & 0.32482854 & 0.95010394 & 0.00501535 & -0.47380614 \\
-0.56682748 & 1.15749049 & 0.91618514 & 0.37518814 & -0.67639047
\end{bmatrix}^\top\boldsymbol{x} + \begin{bmatrix}
-0.18290569 \\ 0.21453567 \\ 0.01353107 \\ 0.27740911 \\ 0.09604219
\end{bmatrix}\right)
そして出力層の出力 $\boldsymbol{y}$ は以下のように計算できる。
\boldsymbol{y} = \tanh\left(
\begin{bmatrix}
-0.8775745 \\ 1.09351909 \\ 0.21981503 \\ 1.31380796 \\ -0.10301871
\end{bmatrix}^\top\boldsymbol{h} + 0.27410847\right)
これを元に手計算してみよう。ここでは
\boldsymbol{x} = \begin{bmatrix}0.3 \\ 0.1\end{bmatrix}
としてみる。ここからは若干小数点以下の桁数を減らす。
\begin{array}{rl}
\boldsymbol{h} &= \tanh\left(
\begin{bmatrix}
-1.0824 & 0.3248 & 0.9501 & 0.0050 & -0.4738 \\
-0.5668 & 1.1575 & 0.9162 & 0.3752 & -0.6764
\end{bmatrix}^\top\begin{bmatrix}0.3 \\ 0.1\end{bmatrix} + \begin{bmatrix}
-0.1829 \\ 0.2145 \\ 0.0135 \\ 0.2774 \\ 0.0960
\end{bmatrix}\right) \\
&= \tanh\left(
\begin{bmatrix}-0.3814 \\ 0.2132 \\ 0.3766 \\ 0.0390 \\-0.2098\end{bmatrix} + \begin{bmatrix}
-0.1829 \\ 0.2145 \\ 0.0135 \\ 0.2774 \\ 0.0960
\end{bmatrix}
\right) \\
&= \begin{bmatrix}-0.5112 \\ 0.4034 \\ 0.3715 \\ 0.3063 \\ -0.1133\end{bmatrix}
\\\\
\boldsymbol{y} &= \tanh\left(
\begin{bmatrix}
-0.8776 \\ 1.0935 \\ 0.2198 \\ 1.3138 \\ -0.1030
\end{bmatrix}^\top
\begin{bmatrix}-0.5112 \\ 0.4034 \\ 0.3715 \\ 0.3063 \\ -0.1133\end{bmatrix}
+ 0.2741\right) \\
&= \tanh\left(1.3855 + 0.2741\right) \\
&= 0.9302
\end{array}
ん? 0.3+0.1 は 0.5 以下なのに正の値だぞ……。
実際に計算させる
model.predict(np.array([[0.3, 0.1]]))
結果は
array([[ 0.93015909]], dtype=float32)
というわけで、たしかに手計算でも同じ推定ができることがわかった(ただ推定は間違っていた)。
次元数が少なくモデルも適当なのであまりちゃんと学習ができないということがわかった……(85% くらい)。
まあとりあえず計算はできていたみたいなのでよしとしましょう。
絶対に正解するモデルをつくる
重みを作れれば絶対に正解するモデルを作れるので、作ってみよう。
入力を両方重み 1 で足して、-0.5 を引けばよいのだから、これを set_weights()
してやる。
import numpy as np
from keras.layers import Input, Dense
from keras.models import Model
data = np.random.rand(250,2)
labels = (np.sum(data, axis=1) > 0.5) * 2 - 1
input = Input(shape=(2,))
output = Dense(1, activation='tanh')
model = Model(input=input, output=output(input))
model.compile('adam', 'hinge', metrics=['accuracy'])
output.set_weights([np.array([[1.0], [1.0]]), np.array([-0.5])])
ひどい。これで走らせてみよう。
test = np.random.rand(200, 2)
predict = np.sign(model.predict(test).flatten())
real = (np.sum(test, axis=1) > 0.5) * 2 - 1
print(sum(predict == real) / 200.0)
いえ〜い。100%。
次回はもうちょっと大きなデータセットを扱います。