ようやくちょこちょこ研究はじめたたかたかです.
てくてく登校
研究のねたそのものはまだ記事にできないので,てくてく登校した話を….っておい,タイトルの件はどーなった,って方はずっと下の方へスクロールしませう(すいません (^^;).
いつもの遠回りルートでの登校でしたが,ええ天気やったので写真をちょこちょこ撮りながらてくてく.
瀬田の唐橋と桜(仮).
「紫式部の泉」そばの…これ何の木?柳?てか「紫式部の泉」って何? (?_?)
朝食中の鴨かもなみなさん.晩秋の頃はちょっと近づくとすぐ逃げてた気がしますが,近頃は平気で遊歩道上をうろうろしてます.といっても,さすがに横の階段登ろうとしたらいっせいに飛んでかはりました.お食事中ごめんなさい.
石山寺付近の桜(仮).
瀬田川ぐるりさんぽ道 ってカテゴリをつくりました.
シグモイドの対数が \( -\infty \) になるのを防ぐ
さて,お待たせしました,本題です (^^)
出力層ニューロンの活性化関数にシグモイドを用いるニューラルネットを考えます.「ニューロンへの入力の重み付き和+しきい値」を \( y \) とするとき,出力は
\[ z = \frac{1}{1+\exp{ (-y) }} =\frac{\exp{y}}{1+\exp{y}} \]
ですね.で,コスト関数として交差エントロピーを用いることにします.出力の教師を \( t \) とするとき,\( z \) と \( t \) の交差エントロピーを \( h \) とおくと,
\[ h = -t \log{ z } - (1-t)\log{(1-z)} \]
です.
学習中や学習後にこの値を計算して表示させることはよくあるわけですが,\( z \rightarrow 0 \) または \( z \rightarrow 1 \) でそれぞれ \( \log{z} \rightarrow -\infty \) および \( \log{(1-z)} \rightarrow -\infty \) となっちゃうので,\( h \) の値が -inf になったり,さらにその和をとると nan が出てきたり,そういう素敵なことになります.学習そのものは \( h \) の値は使わない( \( h \) の微分を使います)ので,実用面だけを言えば放っとく手もあり(どうせクロスバリデーションとかするにしても誤識別率で評価するやろしってことで)ですが…いやんですよねぇ.
というわけで,何とかする方法を3通り.
その1: \( z \) の値をクリッピング
小さな値 \( \theta > 0 \) を適当に定めて,
\[ z \leftarrow \min\left( \max( z, \theta ), 1 - \theta \right) \]
とする.簡単でよいですが,\( \log{z}, \log{(1-z)} \) の値が \( \log{\theta} \) より小さくなりません.なので,交差エントロピーの値はあまり正確に求まりません.\( -\infty \) よりは無限大ましかもですが.
その2: \( y \) の値で場合分け
\( z \) を求めたときの \( y \) の値も記憶してあって使える場合,次のようにすることができます.
\[ \log{z} = -\log \left( 1+\exp(-y) \right) = y - \log\left( 1 + \exp(y) \right) \]
なので,適当な(大きな)値 \( \theta > 0 \) を定めて,\( y > \theta \) なら真ん中の式使って \( \exp(-y) \rightarrow 0 \) とみなして \( \log{z} = 0 \) として,\( y < -\theta \) なら右側の式使って \( \exp(y) \rightarrow 0 \) とみなして \( \log{z} = y \) とする,と.\( \log{(1-z)} \) も同様.
その3: log1p関数を使う
その2の式を眺めてると,微小な \( x \) の値に対して \( \log{(1+x)} \) を計算する処理が入ってることがわかります.この値は,一般の正実数に対して対数を計算する関数では精度よく求まらないので,専用の関数が用意されてたりします.C言語でもPython+Numpyでも,log1p てのがそれです.
C言語関数辞典 - log1p, log1pf, log1pl
numpy.log1p — NumPy v1.9 Manual
例えば \( y \) の正負で上記 \( \log z \) の式の真ん中使うか右側使うか場合分けして…,とかいうことができますね(その2のように3通りに場合分けする方がもっとよいかもしれませんが).
実験
上記3通りを実装して実験してみました.
3通りどれでも -inf は回避できて,精度はその1 < その2 < その3となってることがわかります(いや,真値を手計算して確認したわけやないんですが…(^^; ).そもそもシグモイドの計算がオーバーフローしてるんもありますが.
これで交差エントロピーをちゃんと計算できるようになりました.
余談1: Theano での場合
Theano では,tensor.nnet.binary_crossentropy で交差エントロピーを計算できます:
シグモイドなニューロンの出力計算につづいてこのひとを呼ぶような関数を作った場合,softplus関数(log1pと同じことをするっぽい)
http://deeplearning.net/software/theano/library/tensor/nnet/nnet.html#tensor.nnet.softplus
を呼ぶように Theano が最適化を行なうので,上記の問題は起こらない(要はその3と同様のことをやってる)ようです.
それでも交差エントロピーだけ計算する Theano 関数を定義したい場合もあるわけで,そういうときは softplus 関数を自分で呼んでその3と同じことすれば ok でしょう.
余談2: Softmax と logsumexp
活性化関数として softmax を使うロジスティック回帰でも,似たような問題が起こります.そっちは
\[ \log\left( \sum_i \exp x_i \right) \]
という値をうまく計算する工夫が必要になるわけなんですが,Python + Scipy (Numpy にはない)には logsumexp ちゅうずばりな関数があるので,それを使えば ok です.
scipy.misc.logsumexp — SciPy v0.15.1 Reference Guide
こちらもどうぞ: Logistic Regression してみる - まんぼう日記