"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

活性化関数「Tanh」「Sigmoid」「ReLu」の仕組みと使い分けを数式なしで整理する/ディープラーニング

ディープラーニングには、活性化関数というのが登場します。

Neural Network Consoleだと「Activationレイヤー」と呼ばれてます。

今回は頭の整理を兼ねて、こいつをざっくり整理してみます。

f:id:arakan_no_boku:20190330090249j:plain

 

Activationレイヤー(活性化関数)

 

いっぱい種類があるので、今回はその中の代表的な3つ。

にしぼってとりあげることにします。

本当は「SoftMax」も重要なんですけど、こちらは今回はとりあげません。

さて。

Neural Network Consoleのレイヤーリファレンスの「Activationレイヤー」をみます。

まずは、それぞれのレイヤーリファレンスの記述です。

Tanh

入力値の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は入力を示す)

この時点で「ああ、なるほど。」思った人は、これ以上読む必要はありません。

処理結果を出力すると、どんな良いことがあるのか?さっぱりわからん。

そう思った人だけ続きをどうぞ(笑)

 

活性化関数がしていること

 

ざっくりと言うなら、

特徴の学習をしやすくするために、データを整える。

働きです。

その整え方のアプローチで、大きく2つに分かれます。

  • 計算結果の単位をそろえて比較しやすくする。
  • 特徴を際立たせることで比較しやすくする。

がそうです。

 

計算結果の単位をそろえて比較しやすくする

 

コンピュータのデータって基本数字の塊です。

数字を比較して、これは同じとか似ている・・とか判断する一番簡単な方法は、基準をそろえることです。

なんですが、活性化関数の前でデータとウェイトを掛け合わせて計算した結果が、そんなに都合よく一定の範囲におさまっているわけがない。

だから、活性化関数の中で一定範囲の中に納まるように整理して、後工程で特徴を計算しやすくしてあげる。

これが。

Tanh(ハイポリックタンジェント)」と「Sigmoid(シグモイド)」の役目です。

その2つの違いは、マッピングする範囲で。

ということになります。

 

特徴を際立たせることで比較しやすくする。

 

もうひとつのアプローチは、特徴を際立たせるように加工することです。

余分な情報(ノイズ)が多いほど、特徴がつかみずらいのは直感的にわかります。

なので。

ある程度割り切って基準を決めて、余分なものは捨てていくことで、後工程で特徴をつかむ計算をやりやすくする。

そんなアプローチです。

それが「ReLu」のアプローチです。

ReLuは「0より大きければそのまま、0より小さければ0に置き換えて」出力します。

つまり、マイナスの数字をノイズとみなして切り捨てることで、より特徴をつかみやすくする効果を期待するものなわけです。

 

活性化関数にはデータによって向き不向きがある

 

何をしているか?がわかると、何となく、どんなデータに、どの活性化関数がむくのかな?というのはイメージできるはずです。

例えば「ReLu」です。

これが「画像データ」と相性が良いのだろうというのは想像しやすいです。

何故か。

画像のデータを読み込んだ時の型は「UINT(Unsigned INT)」。

つまり、マイナスのないデータだからです。

画像を処理してマイナスのデータになったものは「ノイズ」だから切り捨てる・・というのは理にかなってます。

対して、マイナスのデータがはいっているのが当たり前のものもあります。

何かの測定値だったりとかですね。

そういうもののマイナスを切り落とすと無茶苦茶になる可能性があるので、「Sigmoid」とか「Tanh」を使った方がいいかな・・とか。

そういうことが予測できるわけです。

他にも文章データとかいろんなデータがありますし、ひとつのモデルの中で、ReluとSigmoidとTanhをかを混在して使う場合もあります。

それでも、訳もわからず適当に使ってみる・・よりは、どういうことをするのかのイメージをもって、このあたりではこういうデータになっているはずだから、これを使おう・・的に考えられると、ちょっとだけ楽しくはなります。

 

各活性化関数が何しているかわかった上でグラフをみてみる

 

上記のことを頭にいれて、リファレンスマニュアルのグラフを見ると、よりイメージしやすいです。

Tanh

-1.0 ~ 1.0 の範囲の数値に整えます。

f:id:arakan_no_boku:20181104001705j:plain

Sigmoid

0.0 ~ 1.0 の範囲の数値に整えます。

tanhとの違いは、マイナスの数値を残すかどうか?だけ・・ですね。

f:id:arakan_no_boku:20181104001942j:plain

 ReLU

マイナスの数値は0にしてしまう。

プラスの数値はそのまま残す。

ようするに、マイナスの数値はノイズだと判断して捨てている・・ってわけですね。

f:id:arakan_no_boku:20181104002103j:plain

なんとなく、イメージはつきましたので、もう少しだけ深堀します。

 

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・・・・

グラフにすると、きれいな右肩あがりになります。

-255から255まで0.1きざみで増加しているのだから、まあ、当然ですが。

f:id:arakan_no_boku:20181104145544j:plain

 

 これを、まずReLUで処理してみます

 

上記のデータをReLUを通したものは、こんなグラフになります。

f:id:arakan_no_boku:20181104145647j:plain

マイナスの数字の部分が全部0になって、プラスの数字はそのまんまです。

わかりやすいですね。

 

 次にSigmoidとTanhです

 

処理後のデータのグラフです。

Tanh

f:id:arakan_no_boku:20181104153546j:plain

sigmoid

f:id:arakan_no_boku:20181104153617j:plain

-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とかをかましてしまうとか・・)と、知らぬ間に情報が消失して、どうにも学習精度があがらない・・みたいなことになります。

このことは。

知っている人には常識以前の話みたいで、ディープラーニングの本とかでも、書いてあるのを見たことがありません。

でも。

自分がそうだったように、文系人間には意外とそこがわからないんだよ!

ということで、長々整理してみました。

ではでは。