pit-rayの備忘録

知識のあうとぷっと

【Python】二値分類はシグモイド関数を使う

今回はニューラルネットワークに関するトピックです。

思うことがありましたら、どうぞお気軽にお寄せください。
早速ですが、本題です。


ニューラルネットワークを用いて推測を行う場合、大きく分けて二種類の分類ができるでしょう。
それは多値分類二値分類です。
それぞれ分けてスコアと確率の算出方法を見ていきましょう。


ここでは自然言語を扱う場合を想定しています。
特に二値分類に関しては、Negative Samplingを想定しています。


スコアの算出

多値分類

これはニューラルネットワークに入力した値が、どのようなクラスに属しているかを推測することを指します。

f:id:pit-ray:20190505173945j:plain

図は入力したxというデータが、ニューラルネットワークの?レイヤを通って、最後のノードにたどり着いた様子を示しています。
灰色はスコアの大きさを示しています。
また、このときのクラスの個数は、直前の重みの列数に等しくなります。

この場合、5つのクラスに分けているということになります。
多値分類の場合、スコアの大きさが確率の大きさに等しいため、上の5つのクラスはこのように表せます。

f:id:pit-ray:20190505180918j:plain

これから、xは二番目のクラスに属している可能性が高いと推測できます。


二値分類

二値分類は、xがYesかNoかを推測することを指します。(ここではそのように定義します)
ここでは、YesクラスとNoクラスに分けるのではなく、xが正解のときと比べてどれだけ似ているかを調べることで、YesかNoかを判断したいと思います。

それでは、xから得られたスコア正解から得られたスコアを比較するにはどうしたらいいでしょうか。
手っ取り早いのは内積を使うことです。

ベクトル\boldsymbol{a} とベクトル\boldsymbol{b} の内積は
  \boldsymbol{a}\cdot\boldsymbol{b}=|\boldsymbol{a}||\boldsymbol{b}|cos \theta
のように表せます。
大事なのは、\theta の意味です。
\theta は二つのベクトルがなす角です。
つまり、どれだけ同じ方向を向いているかを示しています。


ここまでのことを図に表すとこのようになります。

f:id:pit-ray:20190505184834j:plain


ここまでスコアの算出方法を、二つの分類方法に分けてみてきました。
それでは本題となるスコアを確率に変換する方法を考えます。


確率の算出

ここで確率は0から1の値を持つとします。

多値分類

上のスコアの算出にあったとおり、スコアの大きさは確率の大きさと一致します。
ここで注意したいのは、あくまで相対的な大きさが等しいのであって、実際の値は異なるということです。

そのような場合、分母に全体のスコアの和を取り、分子にクラスのスコアをとることで確率を算出できます。
そこで逆伝播を考慮して、ネイピア数を用いることで以下のように書けます。
  y _ k =  \displaystyle{ \frac{e^{a _ k} }{ \displaystyle{\sum_{i=1}^n e^{a _ i}}}}

k はクラスインデックスを指します。

これをソフトマックス関数といいます。

多値分類は、イメージしやすいと思います。


二値分類

二値分類のスコアは内積の結果でした。
繰り返しになりますが、内積は以下の式で定義されます。
  \boldsymbol{a}\cdot\boldsymbol{b}=|\boldsymbol{a}||\boldsymbol{b}|cos \theta

スコアはcos\theta が重要です。
\theta と内積の関係を示すと以下のようになります。

\theta cos\theta 内積(スコア)
0 1
\frac{\pi}{2} 0 0
\pi -1

このようにスコアの大きさは cos \theta に沿って変化していきます。
つまり、非線形に変化していきます。
ここで重要なのは、非線形であるという点です。
内積は大きさだけでなく、三角関数が絡むことで非線形性を持っています。

参考までに cos \theta0\leq\theta\leq\pi )のグラフを示します。
f:id:pit-ray:20190505193935j:plain

この変化を踏まえつつ、逆伝播を考慮してネイピア数を用いると活性化関数はこのように表せます。
  y=\frac{1}{1+e^{(-x)}}

これをシグモイド関数といいます。
グラフを示しますとこうなります。
f:id:pit-ray:20190505194915j:plain
このグラフは、表で示した内積と類似度( cos\theta )をうまく表現しています。
具体的に言うと、
xが正のときには、鋭角なので類似度が高くなり、確率が高くなっています。
その変化の仕方も cos\theta と似ています。

また、xが0のときには、垂直に交わっているので、確率が半分になっています。

さらに、xが負のときには、鈍角なので類似度が低くなり、確率が急激に下がります。
この時の変化も cos\theta を見ると同様の変化をしています。

したがって、シグモイド関数は、内積の変化を表すのに適していると言えます。


まとめ

これまでスコアと確率それぞれの考え方について、多値分類と二値分類で見てきました。
それぞれにおいて以下のような活性化関数を用いればよいです。

分類方法 活性化関数
多値分類 ソフトマックス関数
二値分類 シグモイド関数

 
 

参考文献

斎藤 康毅 『ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装』, 『ゼロから作るDeep Learning ❷ ―自然言語処理』



ゼロつくで直感的に理解できなかったため、まとめました。
参考になれば幸いです。

【Python】二次元配列に対応したソフトマックス関数

近頃、ディープラーニングに興味があり、そのアウトプットの一環としての記事になります。
特に新しい内容ではありません。
特に配列の基本が分かっていれば読む必要はありません。


では書きます。

ソフトマックス関数とは

多値分類に限定するとニューラルネットワークの入力層から得られたデータを、出力層にてどのように料理するかを決める活性化関数は主に二つあります。

活性化関数 処理
恒等関数 入力信号をそのまま出力
ソフトマックス関数 入力信号を正規化して出力

(二値分類の場合は、シグモイド関数などを用いる場合があります。)

データを正規化するメリットは、そのデータを確率的に比較できる点が挙げられます。
例えば、対象のものが犬か猫か判別する場合、
( 犬, 猫 ) = ( 0.3, 4.5 )
このような入力信号の場合、これを正規化すると
( 犬, 猫 ) = ( 0.0625, 0.9375 )
これは犬の確率は6.25%で、猫の確率は93.75%だといえます。

このように正規化することで、データを確率的に見ることができます。
しかし、推論においてはデータの大小が分かれば判別できるため、一般的にはソフトマックス関数は使用しません。
一方学習においては、損失関数を適用するため、正規化したものが必要になります。

では早速ソフトマックス関数の実装を考えていきます。
まず、ソフトマックス関数は次の式で表されます。
  y _ k =  \displaystyle{ \frac{e^{a _ k} }{ \displaystyle{\sum_{i=1}^n e^{a _ i}}}}

k というのは、上の例でいうと猫や犬などのクラスインデックスです。
つまり、k 番目の出力を求める計算式を示しています。
このように指数関数を通したデータを用いて、正規化します。

一次元のソフトマックス関数を実装する

numpyを用いて実装すると次のようになります。

import numpy as np

def softmax( x ):
    return np.exp( x ) / np.sum( np.exp( x ) )

しかしこのような実装ではうまくいきません。
なぜなら、指数関数は簡単に桁が膨大になり、オーバーフローしやすいからです。

この対策として、データを定数の足し算として扱う方法があります。
先ほどのソフトマックス関数を少しだけ変形します。

\ \ \ \ y _ k =  \displaystyle{ \frac{e^{a _ k} }{ \displaystyle{\sum_{i=1}^n e^{a _ i}}}}
= \displaystyle{ \frac{Ce^{a _ k} }{ \displaystyle{C\sum_{i=1}^n e^{a _ i}}}}
= \displaystyle{ \frac{e^{a _ k+ \log C} }{ \displaystyle{\sum_{i=1}^n e^{a _ i + \log C }}}}
= \displaystyle{ \frac{e^{a _ k+ C'} }{ \displaystyle{\sum_{i=1}^n e^{a _ i + C' }}}}

よって、変数 a に同じ量だけ加算しても結果は変わりません。

今後は、それぞれの入力信号を最大の値で引き算したものをデータとして使っていきます。
Pythonで実装すると次のようになります。

import numpy as np

def softmax( x ):
    x = x - np.max( x ) #New
    return np.exp( x ) / np.sum( np.exp( x ) )

これが一次元配列に対するソフトマックス関数になります。

二次元に対応したソフトマックス関数を実装する

numpy.ndarrayのメンバであるndimで場合分けをします。

import numpy as np

def softmax( x ):
    if x.ndim == 2:
        x = x - np.max( x, axis = 1 )
        y = np.exp( x ) / np.sum( np.exp( x ), axis = 1 )
        return y

    x = x - np.max( x )
    return np.exp( x ) / np.sum( np.exp( x ) )

この実装は間違っています。
まずは次の図をご覧ください。
f:id:pit-ray:20190303195318j:plain:w250
これは二次元の場合のaxis(軸)の方向を示しています。
データはaxisが1の方向に並んでいます。


では、ほかの例に関して詳しく見ていきます。
maxメソッドをaxis = 1で実行すると次のようになります。
f:id:pit-ray:20190303195806j:plain:w300
上の例では、maxで得られる配列は( 5, 3 )という配列になります。
しかし、これを元の配列から引くと問題が生じます。
max配列は横に並ぶからです。

理想
f:id:pit-ray:20190401170720j:plain

実際
f:id:pit-ray:20190401170735j:plain

このようになってしまいます。

見てわかる通り、max関数を通す前に転置すればうまくいきます。
実際のコードは次のようになります。

import numpy as np

def softmax( x ):
    if x.ndim == 2:
        x = x.T
        x = x - np.max( x, axis = 0 )
        y = np.exp( x ) / np.sum( np.exp( x ), axis = 0 )
        return y.T

    x = x - np.max( x )
    return np.exp( x ) / np.sum( np.exp( x ) )

一度転置することでmaxで得られた横向きの配列が、元のデータに合うようにブロードキャストされます。
転置するのでデータは縦向きになり、axis = 0であることに注意しましょう。

他にmaxで得られた配列を転置でもいいかと思います。

以上が二次元配列に対応したソフトマックス関数になります。
maxで得られる形に注意しろというだけの中身のない記事でした。

参考文献

斎藤 康毅 『ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装』

(はてなブログのtex構文を使ってみたかっただけ)