無から始める Keras のもくじはこちら
前回のおさらい
前回は雑なデータセットを作って雑に学習させた。
入力は、要素がそれぞれ0以上1未満の5次元のベクトル。出力は、そのベクトルの要素の和が2.5以下だったら0、2.5より大きかったら1。いわゆる「2クラス分類」をする。
データセットは以下のように作った。出力はニューラルネットが扱いやすいように、ワンホットな2次元のベクトルにしてやる。
data = np.random.rand(250,5)
labels = np_utils.to_categorical((np.sum(data, axis=1) > 2.5) * 1)
モデルを作るときは以下のようにした。(入力を省略して)2つ層を重ねた。入力は5次元、間の隠れ層が20次元、出力が2次元。
model = Sequential()
model.add(Dense(20, input_dim=5, activation='relu'))
model.add(Dense(2, activation='softmax'))
model.compile('rmsprop', 'categorical_crossentropy', metrics=['accuracy'])
活性化関数(activation)
ニューラルネットでは、前の層からやってきた値の重み付き和をとる。そこにバイアスの定数を加え、最後にある関数を適用する。この適用する関数が活性化関数である。
Activation
というレイヤーを使って活性化関数をかけることもできる。これを使うとパラメータを指定できたりするのだが、まあ使わなくてもいいっちゃいい。
なおバイアスは、意味合い的には負のときに閾値となる(この値を超えないと発火しないよ!)。ちなみにバイアスを常に0にすることもできる(bias=False
を指定する)。
活性化関数の種類
ある程度まとめてみた。何を選べば常にいい、と言えないところが難しい。
全体的にtanhやreluが人気な気がする。
-1から1を取るのは以下の2つ。tanhのほうが急。
入力が0のとき出力も0。0あたりでは線形に効くが、絶対値が大きくなってくると-1か1に張り付く。
- ソフトサイン(softsign)
- ハイパボリックタンジェント(tanh)
0以上1以下のみを取るのは以下の2つ。ハードシグモイドのほうが急で折れ線。シグモイドは滑らか。
入力が0のとき出力は0.5。上と同様0あたりでは線形に効くが、絶対値が大きくなってくると0か1に張り付く。
- シグモイド関数(sigmoid)
- ハードシグモイド(hard_sigmoid)
0以上の値を取るのは以下の2つ。こちらもランプ関数のほうが急で折れ線。ソフトプラスは(かなり)滑らか。
値がある程度(ゼロから見て)大きくなると線形になり、小さくなるとゼロに近くなる感じ。
reluは係数で0未満の値をとることもできる。
- ソフトプラス(softplus)
- ランプ関数(relu)
あと残るのは線形関数(linear)とソフトマックス(softmax)。
線形の方は単純に係数をかけてバイアスを加える(係数とバイアスは学習される?)。ただ活性化関数が線形なのはあまり意味がない気がするのでこれはあまり使わないのかも。
ソフトマックスは、その層から出力されたすべての値に指数関数をかけ(すなわちすべて正にし)、その和を1に正規化する。この関数がかかった後、和は1だしどの値も正なので、確率として判断することができる。だから先ほどの例で、最後にsoftmaxを使ったのである。
最適化手法(optimizer)
compile
の1つ目の引数。いろいろあるが、どんなパラメータをとると誤差が小さくなってくれるかを探す方法。直感的に(最小二乗法などの)自明なパラメータを計算できないので、結局のところイタレーションを回して順々にその誤差を小さくしていきたいのである。
このへんをみるとよい。
sgd, rsmprop, adagrad, adadelta, adam, adamax, nadamが選べる。
全体的に(あまりパラメータを考慮しなくてもなんとなくそれなりに動くという点で)adamを選ぶとよいというのが通説みたい。RNN(再帰型ニューラルネットワーク)では最適化が遅いのでrmspropを使うといいらしい。
結局はどれも微分(勾配)を少しずつ小さくしていく手法。SGDは「確率的勾配降下法」とそのままの名前だ。
なぜ微分を小さくしたいかというと、勾配が小さい(微分が小さい)ほど極値(極大 or 極小)に近いと感じられるから。
誤差関数の微分をちょっとずつ小さくしていけば、誤差関数の値は自動的に極小に近づく(もちろんこれは関数の形による)。でも手法によって近づく効率も違うし、ときどき行き過ぎることもある。局所解に陥る、つまり「確かに極小かもしれないけれど全体として見れば全然いいところにはいない」ことだってある。そういったことをうまく考慮しながら小さくできる手法がこれらである。
目的関数(objective)
compile
の2つ目の引数。小さくしたい関数。誤差(loss)とだいたい同じ意味。
「どんな最適化手法で誤差を小さくしたいか」という話をしたけれど、これは「どんな基準で誤差を決めるか」という話。
ここでいう「差」とは、今のニューラルネットから推測される値と正解の値の差のことをいう。
下のものが選べるが、どれを選んでも「予測が外れるほど大きい」ことには変わりがない。
- 平均二乗誤差(mse:差の2乗の和)
- 平均絶対誤差(msa:差の絶対値の和)
- 平均絶対誤差率(mspa:差を正解の値で割った値(誤差率)の絶対値の和)
- 対数平均二乗誤差(msle:「1を加えた値の対数」の差の2乗の和)
- ヒンジ損失の和(hinge)
- ヒンジ損失の二乗の和(squared_hinge)
- 2クラス分類時の交差エントロピー(binary_crossentropy)
- Nクラス分類時の交差エントロピー(categorical_crossentropy)
- スパースなNクラス分類交差エントロピー(sparse_categorical_crossentropy)
- KLダイバージェンス(kld)
- poisson
- コサイン類似度を負にしたもの(cosine_proximity)
ヒンジ損失は、推定値と正解値をかけた値について、1以上で0、1以下で $1-x$ な関数(符号が同じだと損失が小さい)。
交差エントロピーは(ほぼ)相互情報量のことである。小さいほうがカテゴリ分類がうまくいっていることがなんとなくわかる。
KLダイバージェンスはだいたい確率分布の遠さを表す。KLダイバージェンス規準にすると、確率分布を推定する雰囲気になる。
コサイン類似度(の負)はベクトルの方向の異なり度。ベクトルの方向が近くなるように学習する。したがって、その大きさは無視して学習される。
目的関数とかをアレンジしてみる
同じデータセット、同じ状況で、ちょっとオプションをアレンジしてみよう。
今回は出力を1次元で、-1か1にする(合計が2.5以下なら-1、2.5より大きいなら1)。
符号が大事になるので、損失はヒンジ損失に、出力の活性化関数はtanhにした。
最適化関数はadamにしてみた。
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation
data = np.random.rand(250,5)
labels = (np.sum(data, axis=1) > 2.5) * 2 - 1
model = Sequential([Dense(20, input_dim=5, activation='tanh'), Dense(1, activation='tanh')])
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)
ちょっと学習に時間がかかるけどそこそこの精度は出てるみたい。
まあデータセットがアレなので。