無から始めるKeras 第3回

  • 6
    いいね
  • 0
    コメント

無から始める Keras のもくじはこちら

前回のおさらい

前回は雑なデータセットを作って雑に学習させた。

入力は 5 次元で、要素がそれぞれ 0 以上 1 未満。出力は 1 次元で、入力の要素の和が 2.5 以下だったら -1、2.5 より大きかったら 1。いわゆる「2 クラス分類」をする。

データセットは以下のように作った。出力の方は 0 or 1 にしてから 2 をかけて -1 している(-1 か 1 になる)。

data = np.random.rand(250,5)
labels = (np.sum(data, axis=1) > 2.5) * 2 - 1

モデルは以下のようにした。活性化関数には -1 から 1 を値域としてとれる tanh を使って、損失には符号に対してよく利くヒンジ損失を使った。

model = Sequential([Dense(20, input_dim=5, activation='tanh'), Dense(1, activation='tanh')])
model.compile('adam', 'hinge', metrics=['accuracy'])

Sequential モデルとは

第 1 回でも述べたように、Sequential モデルは単純に、今の層のそれぞれのノードに、前の層の全ノードから矢印を引っ張ってくるイメージである。
確かにこれでもニューラルネットワークとして機能するのだが、ちょっとおもしろくない。

これを解決するのが Functional API である。なんか 2 つのニューラルネットワークの出力を入れてみたり、レイヤーを共有してみたりと、結構複雑なことができるようになる。

Functional API とは

前回と同じ形のニューラルネットワークを作ってみよう。
同じ形なので作るのが無意味な雰囲気がするが、そこは練習である。

Functional API は簡単に言えば「矢印のつなぎ方をぐちゃぐちゃにできる」ものである。Sequential モデルでは A → B → C という形でしかできなかった。
でも Functional API では

A
 ↘
  C → D → E
 ↗
B

みたいなことができるようになる。今回はやらないけど。

Functional API を使う

入力層

まずは入力の層。

from keras.layers import Input, Dense
from keras.models import Model

inputs = Input(shape=(5,))

この Input という名前の層なのだが、残念ながらドキュメントがない。

shape は入力の次元を表している。
(5,) という表記は Python を知らないとなんやねんという感じだが、ただ単純に 5 という要素だけもつ Tuple(配列みたいなもの)である。カンマがあるので複数要素があるように見えるが、要素は 1 個である。カンマがないとただの数値と見分けがつかないからなのだが、本当にクソ記法だと思う。Python はこういうところが嫌い。

隠れ層

x = Dense(20, activation='tanh')(inputs)

ノード数が20で、入力が inputs であるような層。

dense は「密な」という意味だが、前の層のすべてのノードから、この層のすべてのノードに対して、すべての矢印を引っ張るので、そういう意味で密なんだと思う。

出力層

predictions = Dense(1, activation='tanh')(x)

こちらは隠れ層とほぼ同じ。説明はいらないでしょう。

Functional API の意味付け

Functional(関数型)というのはどういうことか。

よく見ると Dense はそのインスタンスを関数呼び出ししていることがわかる。
ドキュメントには、レイヤーのインスタンスを関数呼び出しすると、テンソルが返ると書いてある。

そもそもニューラルネットワークというのは、おおよそテンソル積(もっと単純化すれば行列積)である。
入力ベクトルに行列をかけて、さらに行列をかけて……というのがそもそもの本質になる。
1 回層を通り抜けることが、1 回行列をかけるのとだいたい同じ意味になる。
その行列(テンソル)をかけ合わせるイメージを、関数呼び出しと照らし合わせている。

関数呼び出しをすると、入力したテンソルが、指定した意味合いをもって変形されて、またテンソルとなって出力される。そういう感じのイメージでとらえるとわかりやすい気がする。

まったく厳密なことを言っていないのでアレだけど。

ちなみに Input レイヤーだけはそもそもテンソルが返ってくる(ので関数呼び出しがいらない)。

今回のプログラムまとめ

import numpy as np
from keras.layers import Input, Dense
from keras.models import Model

data = np.random.rand(250,5)
labels = (np.sum(data, axis=1) > 2.5) * 2 - 1

inputs = Input(shape=(5,))
x = Dense(20, activation='tanh')(inputs)
predictions = Dense(1, activation='tanh')(x)

model = Model(input=inputs, output=predictions)
model.compile('adam', 'hinge', metrics=['accuracy'])
model.fit(data, labels, nb_epoch=150, validation_split=0.2)

test = np.random.rand(200, 5)
predict = np.sign(model.predict(test).flatten())
real = (np.sum(test, axis=1) > 2.5) * 2 - 1
print(sum(predict == real) / 200.0)

書き方以外は第 2 回とまったく同じなので、ほぼ同じ精度が出ると思う。