社畜エンジニア発掘戦線

駆けだしAIエンジニア

②線形分類問題(2値分類:バイアス付加)

第二問

今回も2値分類問題にトライします。前回と異なり、パラメータにバイアスを追加します。バイアスを追加することで原点の束縛から開放されてより自由に2値のクラスを分類します。

設問1.ガウシアンノイズを付加した入力x1、x2、正解ラベルtが(x1, x2, t) = (2.5, 2.5, 0), (7.5, 7.5, 1)となるデータセットを作成せよ

データセットのコードは前回と全く同じですが座標を変えました。この座標であれば原点で束縛されている状態(バイアスなし)では正しく分類できません。とりあえずコードに落としましょう。

まずはライブラリのインポート、matplotlibはカラーマップ(cm)も使用します。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm

次にデータセットを作成します。

#dataset
num_of_sam = 40
std_dv = 1.8

group1 = np.array([2.5,2.5])+np.random.randn(num_of_sam, 2)*std_dv
group2 = np.array([7.5,7.5])+np.random.randn(num_of_sam, 2)*std_dv
X = np.vstack((group1, group2))

t_group1 = np.zeros((num_of_sam, 1))
t_group2 = np.ones((num_of_sam, 1))
t = np.vstack((t_group1, t_group2))

グラフ化してみます。

plt.scatter(X[:,0], X[:,1], vmin=0, vmax=1, c=t[:,0], cmap=cm.bwr, marker='o', s=50)
Plt.show()

f:id:sutokun:20190218035748p:plain
確かに原点に固定されていれば正しく分類できない配置です。

設問2.パラメータにバイアスを付加し、活性化関数にシグモイド関数、損失関数にクロスエントロピーを用いて出力と損失量を求めよ。

まずはニューラルネットワークを設計します。基本的に前回と同じですが、バイアスを付加するのでこんな感じ。

f:id:sutokun:20190217124219j:plain:w450

次に、出力と損失量の計算に使用するシグモイド関数とクロスエントロピーPythonで先に定義しておきます。

#function
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def loss(y, t):
    return -np.sum(np.multiply(t, np.log(y)) + np.multiply((1 - t), np.log(1 - y)))

これで、それぞれの関数が簡単に使えるようになりました。

次にパラメータの設定です。今回は重みとバイアスを定義しなければいけません。

#initial setting
W = np.random.randn(2,1)
b = np.random.randn(1,1)

今回バイアスはスカラー値なのでわざわざ行列で書かなくてもいいんですが、今後の多変数への拡張性を込めて行列で書いておきます。

では、出力と損失量を計算します。シグモイドの中身にバイアスが追加されているところに注意です。

y = sigmoid(np.dot(X, W)+b)
E = loss(y, t)

以上で設問2は終了です。

設問3.勾配降下法を用いてパラメータを更新し、イテレーションを500回まわした後のパラメータWとbを求めよ

いつも通り、損失関数をパラメータで微分し、降下法によるパラメータの更新を行います。重みWとバイアスbの微分が必要です。

重みWでの微分{\displaystyle
\frac{\partial E}{\partial W}\ = \frac{\partial y }{\partial W}\  \frac{\partial E }{\partial y}\ 
}

バイアスbでの微分{\displaystyle
\frac{\partial E}{\partial b}\ = \frac{\partial y }{\partial b}\  \frac{\partial E }{\partial y}\ 
}

重みWについては前回計算しているので結果だけ。

{\displaystyle
W_{new} = W_{old} - \mu \cdot  \sum_{i} \Bigl( X_{i}(y_{i}-t_{i}) \Bigr) 
}

バイアスについて計算します。

{\displaystyle
\frac{\partial E}{\partial b}\ = \frac{\partial y }{\partial b}\  \frac{\partial E }{\partial y}\ 
}

微分式の要素をひとつずつ計算します。

{\displaystyle
\frac{\partial y }{\partial b}\ = \sum_{i} 1 \cdot y_{i}(1-y_{i})
}
{\displaystyle
\frac{\partial E }{\partial y}\  = \frac{\partial }{\partial y}\  \Bigl(-\sum_{i} \Bigl( t_{i} \log y_{i} +(1-t_{i}) \log (1-y_{i}) \Bigr) \Bigr) = \sum_{i} \Bigl( -\frac{t_{i}}{y_{i}} + \frac{1 - t_{i}}{1 - y_{i}} \Bigr)
}
以上より、
{\displaystyle
\frac{\partial E}{\partial b}\ = \sum_{i} 1 \cdot y_{i}(1-y_{i}) \sum_{i} \Bigl( -\frac{t_{i}}{y_{i}} + \frac{1 - t_{i}}{1 - y_{i}} \Bigr) =\sum_{i} (y_{i}-t_{i})
}
Wよりもシンプルな微分式になりました。

この微分式より、バイアスのパラメータの更新式は下記のようになります。

{\displaystyle
b_{new} = b_{old} - \mu \cdot  \sum_{i}  (y_{i}-t_{i}) 
}

これで各パラメータの更新式が求められたので、コードに落とします。今回の学習率は0.003としましょう。

#initial setting
W = np.random.randn(2,1)
b = np.random.randn(1,1)

learning_rate = 0.003
E_save = []

#iteration
num_of_itr = 500
for i in range(num_of_itr):
    #forward propagation
    y = sigmoid(np.dot(X, W)+b)
    E = loss(y, t)
    E_save = np.append(E_save, E)
    #back propagation
    dW = np.sum(X*(y-t),axis=0)
    db = np.sum(y-t)
    #update
    W = W - learning_rate*np.reshape(dW,(2,1))
    b = b - learning_rate*db

いつも通りの流れなので問題ないですね。これで設問3が終了です。

設問4.更新後のパラメータを用いた出力をデータセットと同じグラフ上にプロットせよ

出力のグラフ描画は前回と全く同じなのでガバっとコピペします。

#plot_grid
grid_range = 10
resolution = 60
x1_grid = x2_grid = np.linspace(-grid_range, grid_range, resolution)

xx, yy = np.meshgrid(x1_grid, x2_grid)
X_grid = np.c_[xx.ravel(), yy.ravel()]

Y_grid = sigmoid(np.dot(X_grid, W)+b)
Y_predict = np.around(Y_grid)

#plot_output
plt.scatter(X_grid[:,0], X_grid[:,1], vmin=0, vmax=1, c=Y_predict[:,0], cmap=cm.bwr, marker='o', s=50,alpha=0.2)
plt.scatter(X[:,0], X[:,1], vmin=0, vmax=1, c=t[:,0], cmap=cm.bwr, marker='o', s=50)
plt.show()

f:id:sutokun:20190218040128p:plain
確かに、バイアスを付加したことによって境界線が原点を離れてオフセットし、いい感じに2クラスを分類してくれています。

※損失量のグラフ化は特に確認したいこともないので設問にしていません。コードには入れているのでまた興味があれば確認して下さい。

まとめ

学習が進む過程をGIFでアニメ化します。
f:id:sutokun:20190218040152g:plain
境界線は縦横無尽に動き回っていますね。

今回は2値分類のバイアス問題にトライしました。しかし、分類は0と1の2値で行われるため、このままの設定では2値以上の分類ができません。「2とか3とか使えばええやん」と思うんですが、クロスエントロピーの性質上、0と1での表現でなければ分類は難しいのが現実です。そこで、次回は0と1のみでいくつもの正解ラベルを表現する「ワン・ホット」というテクニックを使って同じ問題を解いてみたいと思います。

全体コード
github.com

次の問題
③線形分類問題(2クラス分類:ワン・ホット エンコーダ) - 社畜エンジニア発掘戦線

元の記事
PYTHONISTA3を使って機械学習(ディープラーニング) - 社畜エンジニア発掘戦線

Twitter
世界の社畜 (@sekai_syachiku) | Twitter