目次
- 活性化関数をざっくりイメージ
- 活性化関数の整え方のアプローチ
- アプローチによって相性のよいデータ が違う
- Neural Network Consoleのリファレンスで確認
- グラフを見て役割をイメージ
- Pythonで各活性化関数を実装
- 活性化関数には有効範囲があります
- まとめ:活性化関数は特性を意識して使う
活性化関数をざっくりイメージ
活性化関数について、ざっくり整理します。
専門用語を極力使わず、多少の不正確さは目をつぶっても、なんとなくイメージできることを目標にして整理します。
まず、ニューラルネットワークで学習する流れをざっくり図にしてみました。
ニューラルネットワークの目的のひとつとして「既存の正解がわかっているデータで勉強(学習)して、未知のデータから、正解が導けるようになること」があります。
ポイントはどうやって「勉強(学習)」するか?ですが、ニューラルネットワークでは、それを「計算」でやってます。
どういうことかというと。
既存の正解のわかっている入力データにたいして、パラメータ(重み)をかけあわせる計算を行い、その結果をもとに答えを出して、答え合わせを行い、間違ってたら、正解に近づけるようにパラメータ(重み)を少し修正するみたいなことを延々と繰り返して、もっとも。正解に近い結果を計算で求められる確率の高いパラメータ(重み)の値を決めていく・・のを学習と呼んでいるわけです。
上記の図は、それをごく簡易的に書いてます。
上記の図では「計算して結果を整えて、それを次の入力にする」部分を1回しか書いてませんが、実際は、そこが何層にもわかって繰り返されることで、学習の精度をあげようというのが、ニューラルネットワークであり、ディープラーニングなどと言われるゆえんということです。
ここで②の「次の層に渡す結果を整える」部分を担うのが「活性化関数」です。
活性化関数の整え方のアプローチ
なぜ「結果を整える」必要があるのかというと、入力データにはいろいろなものがあるからです。
人間からは「同じように見える」画像でも、内部データ(数値)のレベルではかなり差異があるのが普通なので、それを「同じ」と判断できる可能性をあげるために、なんらかのアプローチをとって整えてやらないといけません。
その整え方のアプローチは、大きく2つに分かれます。
- 計算結果の単位をそろえて比較しやすくする。
- 特徴を際立たせることで比較しやすくする。
です。
役割1:単位をそろえて比較しやすくする
コンピュータのデータって基本数字の塊です。
数字を比較して、これは同じとか似ている・・と判断する一番簡単な方法は、基準をそろえることですが、データとウェイトを掛け合わせて計算した結果が、そんなに都合よく一定の範囲におさまっているわけがない。
だから、活性化関数で一定範囲の中に納まるよう整理し、後工程で特徴を計算しやすくしてあげる=計算結果の単位をそろえて比較しやすくする・・働きです。
この働きをする代表的な活性化関数が、「Tanh(ハイポリックタンジェント)」と「Sigmoid(シグモイド)」です。
その2つの違いは、マッピングする範囲です。
具体的には
ということになります。
役割2:特徴を際立たせる
もうひとつが、特徴を際立たせるように加工することです。
余分な情報(ノイズ)が多いほど、特徴がつかみずらくなるので、ある程度割り切って基準を決めて、余分なものは捨てていくことで、後工程で特徴をつかむ計算をやりやすくする=特徴を際立たせることで比較しやすくする・・アプローチです。
その代表的な活性化関数が「ReLu」です。
ReLuは「0より大きければそのまま、0より小さければ0に置き換えて」出力します。
つまり、マイナスの数字をノイズとみなして切り捨てることで、より特徴をつかみやすくする効果を期待するものなわけです。
アプローチによって相性のよいデータ が違う
2通りのアプローチが必要になるのは、学習対象のデータとの相性があるからです。
例えば「画像データ」です・
画像データはマイナスのないデータ「UINT(Unsigned INT)」の型で表現されることが多い(というか、ほぼそう)です。
もともと、マイナスのないデータでも、重みパラメータをかけて計算した結果がマイナスになることはありますが、それは一種の「ノイズ」だから思い切って切り捨てる・・というアプローチで整えようという考えは理にかなってると思えます。
つまり、前に書いた「ReLu」に代表されるアプローチです。
対して、マイナスのデータがはいっているのが当たり前のものもだったり、マイナス数値は強制的に0にするとかではなく、全体の数値の相対的な関係性を残したまま、一定の範囲にマッピングしなおしたほうが結果がよくなるデータもあります。
何かの測定値だったりとかはそうですね。
温度とか湿度とかの測定値のマイナス以下を全部0になんかしてしまうと、無茶苦茶になるのは容易に想像できます。
そういうものは「Sigmoid」とか「Tanh」なんかの「単位をそろえて比較しやすくする 」アプローチをとったほうが良いはずです。
とはいえ。
いろんなデータがありますし、この時はこう・・なんて正解を導ける法則があるわけではなく、ひとつのモデルの中で、ReluとSigmoidとTanhをかを混在して使う場合も多々あります。
それでも、訳もわからず適当に使ってみる・・よりは、このあたりではこういうデータになっているはずだから、これを使えばいいんじゃないか的に考えられると、ちょっとだけ楽しくはなります。
Neural Network Consoleのリファレンスで確認
活性化関数は、Neural Network Consoleだと「Activationレイヤー」と呼ばれてます。
いっぱい種類がありますが、とりあえず前でとりあげた3つ。
- Tanh
- Sigmoid
- ReLU
にしぼってとりあげます。
さて。
Neural Network Consoleのレイヤーリファレンスの「Activationレイヤー」をみます。
まずは、それぞれのレイヤーリファレンスの記述です。
入力値のTanhによる処理結果を出力します。
o=tanh(i) (oは出力、iは入力を示す)
Sigmoid
入力値のSigmoidによる処理結果を出力します。
確率など0.0~1.0の出力値を得たい場合に使用します。
o=sigmoid(i) (oは出力、iは入力を示す)
ReLU
入力値のReLU(Rectified Linear Unit)による処理結果を出力します。
o=max(0, i) (oは出力、iは入力を示す)
実は、前のほうで日本語で書いていることと、ほぼ同じことが書かれていて、ある程度詳しい人だと、こちらのほうがわかりやすい・・といわれると思います。
でも、今回はそいういう方を対象としてないので、ご容赦を(笑)
グラフを見て役割をイメージ
リファレンスマニュアルのグラフを見ます。
文章で書いていることを視覚化するとこんな感じ・・というイメージを見てください。
-1.0 ~ 1.0 の範囲の数値に整えます。
Sigmoid
0.0 ~ 1.0 の範囲の数値に整えます。
tanhとの違いは、マイナスの数値を残すかどうか?だけ・・ですね。
ReLU
マイナスの数値は0にしてしまう。
プラスの数値はそのまま残す。
ようするに、マイナスの数値はノイズだと判断して捨てている・・ってわけですね。
Pythonで各活性化関数を実装
もう少しだけ踏み込んでみます。
Pythonで各活性化関数を実装してみます。
活性化関数は「numpy」を使うと、1行で書けてしまうくらいのものです。
上記のリファレンスマニュアルのグラフとは、ちょっと範囲を変えてやってます。
import numpy as np def sigmoid(x): return 1 / (1 + np.exp(-x)) def tanh(x): return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x)) def relu(x): return np.maximum(0, x)
これを試しに「-255~255」まで、0.1きざみで生成した数値を処理させてみます。
こんな感じのデータが255まで続く感じです。
-255.0,-254.5,-254.0,-253.5,-253.0,-252.5,-252.0,-251.5,-251.0,-250.5,-250.0,-249.5,-249.0,-248.5,-248.0,-247.5,-247.0,-246.5,-246.0,-245.5,-245.0,-244.5,-244.0,-243.5,-243.0,-242.5,-242.0,-241.5,-241.0,-240.5,-240.0・・・・
グラフにすると、きれいな右肩あがりになります。
上記のデータをReLUを通したものは、こんなグラフになります。
マイナスの数字の部分が全部0になって、プラスの数字はそのまんまです。
わかりやすいですね。
今度は「Tanh」を通した結果のグラフです。
続けで「sigmoid」を通した結果のグラフです。
-1.0~1.0の範囲(tanh)か、0~1.0の範囲(sigmoid)の違いはありますが、とてもよく似た形になってます。
活性化関数には有効範囲があります
リファレンスマニュアルのグラフと形が違うのがわかると思います。
なぜかというと、リファレンスマニュアルのグラフは「-6 ~ 6」のごく狭い範囲だけを表示していて、自分のやったほうはそんな制限をかけていないからです。
実は、TanhもSigmoidには有効な範囲があります。
きれいなグラフを描くのは、せいぜい-10~10位の幅くらいです。
それをこえてしまう値は全部上限・下限にまとめられてしまって、上記のグラフみたいなカクカクした形になってしまいます。
これが何を意味するか?というと。
大きな範囲の数値データをSigmoidやTanhにかけてはダメだということです。
ピンとこないかもしれないので、ためしに以下のような配列を処理してみます。
[255,150,100,10,0,-10,-100,-150,-255]
すると。
tahhの場合は
[ 1. 1. 1. 1. 0. -1. -1. -1. -1.]
sigmoidの場合は。
[1. 1. 1. 0.9999546 0.5 0.0000454 0.0. 0. ]
みたいな出力になります。
tanhは、10以上や-10以下しかない場合は、すっぱり、1か-1になってしまってます。
かつ、0は0のまま残るのです。
sigmoidの場合は、0は「0.5」になります。
1.0から0の範囲にまとめるのだから、当然ですね。
Sigmoidだと、10、-10のあたりがまだ少数データにとどまっているのですが、やっぱり大きな数字は1か0になってしまってます。
こうなると、元のデータの特徴なんか消え失せて、ほとんどが同じデータのように加工されてしまいます。
だから。
Neural Network Consoleのルールのように、極力 -1.0~1.0の範囲に収まるように整理されたデータ)を入力としなさいとなっているわけです。
まとめ:活性化関数は特性を意識して使う
こんな風に、活性化関数には特性があります。
それを考慮せずに、無造作に使う(例えば、画像データを正規化しないで、でいきなりtanhとかをかましてしまうとか・・)と、知らぬ間に情報が消失して、どうにも学習精度があがらない・・みたいなことになります。
こんなことは、知っている人には常識以前の話みたいです。
でも、自分がそうだったように、文系人間には意外とそこがわからないんだよ!ということで、長々整理してみました。
ではでは。
PS:他の部品(レイヤー)については、以下にまとめてます。