③線形分類問題(2クラス分類:ワン・ホット エンコーダ)
第三問
- 設問1.ガウシアンノイズを付加した入力x1、x2が(x1, x2) = (2.5, 2.5), (7.5, 7.5)となるデータセットをワン・ホット表現を用いて作成せよ
- 設問2.活性化関数にソフトマックス、損失関数にクロスエントロピーを用いて出力y1、y2と損失量Eを求めよ
- 設問3.勾配降下法でパラメータを400回更新し、その出力を求めよ
- 設問4.更新後のパラメータを用いた出力をデータセットと同じグラフ上にプロットせよ
- まとめ
今回はワン・ホット表現を用いた2クラスの分類問題にトライします。前回ちらっと書きましたが、2値(0と1)では2クラス以上の分類ができません(頭がいい人はできると思います)。そこで、3クラス以上の分類にも対応できるようにワンホット表現を導入して問題を解いていきます。
設問1.ガウシアンノイズを付加した入力x1、x2が(x1, x2) = (2.5, 2.5), (7.5, 7.5)となるデータセットをワン・ホット表現を用いて作成せよ
さて、そのワン・ホット表現ですが、もともとデジタル回路とかの分野で使われて表現のようです。
one-hot(ワン・ホット)は1つだけHigh(1)であり、他はLow(0)であるようなビット列のことを指す。 -by Wikipedia
2進数とはまたちょっと違うんですよね。例えば、5クラスをワンホットで表現すると、0=[1, 0, 0, 0, 0]、1=[0, 1, 0, 0, 0]、2=[0, 0, 1, 0, 0]、3=[0, 0, 0, 1, 0]、4=[0, 0, 0, 0, 1]、となります。プログラミングで数字は0から数えるので、5クラスだと最初は0で最後は4です。
今回の問題は2クラスなので入力x1の正解ラベルtは[1, 0]、x2は[0, 1]となります。では、ここまでをコードに落としていきます。
まずはライブラリのインポート、ワン・ホットではクラス別に直接色付けするので、今回カラーマップは必要ありません。
import numpy as np import matplotlib.pyplot as plt
次にデータセットを作成します。
#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))
ここまでは今まで通りです。では正解ラベルを書いていきます。
[note] 上で書いたワン・ホットの順番が逆になっていますが、間違えただけです。順番が違うだけなので問題ないです。
t_group1 = np.tile([0,1],(num_of_sam,1)) t_group2 = np.tile([1,0],(num_of_sam,1)) T = np.vstack((t_group1, t_group2))
イメージとしては[0, 1]をサンプル数だけnp.tileで並べていくって感じですかね。最後はTとして縦方向に結合します。
では、一度グラフ化してみます。2グループを連結したXとTは計算用です、グラフ化には連結前のそれぞれのデータセットを使います。ということは単にデータセットをグラフ化するだけなら正解ラベルTは必要なく、Xのデータセットに直接色付けをして描画します。
plt.scatter(group1[:,0],group1[:,1],marker='o',color='blue') plt.scatter(group2[:,0],group2[:,1],marker='o',color='red') plt.show()
前回と同じようなグラフになりました。これでワン・ホット表現でのデータセットは完成です。
設問2.活性化関数にソフトマックス、損失関数にクロスエントロピーを用いて出力y1、y2と損失量Eを求めよ
データセットができたところで、次はニューラルネットワークの設計です。ワン・ホット表現を用いるとクラスの数だけ正解ラベルと出力が必要になります。今回は2クラスなので出力は2つ、y1とy2を設定します。なので、設計するニューラルネットワークはこんな感じ。
各パラメータをまとめて大文字の行列で記載しています。出力の数が変わっただけで骨格として大きな違いはありません。
さて新キャラのソフトマックス関数ですが、下記の式で表される関数です。
このソフトマックス関数の特徴として、「出力の総和が1」となる性質があります。つまり、得られた出力がそのまま「正解率」として判断することができます。
すごい便利ですね。ワン・ホットの正解ラベルは、「正解の順番の要素が1、それ以外は0」という表現でした。これは言い換えると、正解の確率は100%としてニューロンからの出力と比較することができます。こういう背景でワン・ホット表現とソフトマックス関数は相性が良いです。
では、このソフトマックス関数をPythonで関数としてコードに落としておきます。ついでにクロスエントロピーも合わせて書いておきます。
#function def softmax(x): return np.exp(x) / np.sum(np.exp(x), axis=1, keepdims=True) def loss(y, t): return -np.sum(np.multiply(t, np.log(y)) + np.multiply((1 - t), np.log(1 - y)))
次に、初期値として重みW(2x2)とバイアスB(1x2)も定義しておきます。前回から出力の数が変わっているので設定する行列の型に注意です。
#initial setting W = np.random.randn(2,2) B = np.random.randn(1,2)
では計算してみます。
Y = softmax(np.dot(X, W)+B) E = loss(Y, T)
関数を使って、そのままの記述ですね。ここまでで設問2は終了です。
設問3.勾配降下法でパラメータを400回更新し、その出力を求めよ
今回もいつ通り損失関数をパラメータで微分し、更新式を求めます。今回、パラメータは行列なので大文字でまとめて表記します。
で、今回は「ソフトマックス関数×クロスエントロピー」の計算なのでそれぞれの式は次のようになります。
この辺の計算はこちらを参照↓
以上より、勾配降下法を用いたパラメータの更新式は以下のようになります。
更新式が求められたので、これをコードに落とします。学習率は0.003とします。
#initial setting W = np.random.randn(2,2) B = np.random.randn(1,2) learning_rate = 0.003 E_save = [] #iteration num_of_itr = 400 for i in range(num_of_itr): #forward propagation Y = softmax(np.dot(X, W)+B) E = loss(Y, T) E_save = np.append(E_save, E) #back propagation dW = X.T.dot(Y-T) dB = np.sum(Y-T, axis=0, keepdims=True) #update W = W - learning_rate*dW 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 = softmax(np.dot(X_grid, W)+B) Y_predict = np.around(Y_grid)
ここからはいろいろやり方があると思うので、あくまで一例です(上級Python使いはどんなふうに書くのか気になります)。
まず、各グリッドごとの出力を横方向に結合します。この結合した行列から正解ラベル[1, 0]と[0, 1]に対応する行を抽出してblue_groupとred_groupに分けます、こんなイメージ。
out_connect = np.hstack((X_grid,Y_predict)) red_group = out_connect[out_connect[:,2]==1] blue_group = out_connect[out_connect[:,3]==1]
最後に、この分類したグループをグラフ化します。
#plot_dataset plt.scatter(group1[:,0],group1[:,1],marker='o',color='blue') plt.scatter(group2[:,0],group2[:,1],marker='o',color='red') #plot_output plt.scatter(red_group[:,0],red_group[:,1],marker='o',alpha=0.3,color='red') plt.scatter(blue_group[:,0],blue_group[:,1],marker='o',alpha=0.3,color='blue') plt.show()
しっかり分類できていますね、これで設問4が終了です。
まとめ
今回も学習が進む過程をGIFでアニメ化します。
ワン・ホットを使っても2値問題と同じように学習が進み、分類できることが確認できます。
今回の問題で2クラスをワン・ホットで分類できるようになりました。ワン・ホットが使えるようになったことで2クラス以上の分類も可能になります。次回の問題では多クラス分類にトライしてみようと思います。
全体コード
github.com
次の問題
④線形分類問題(多クラス分類) - 社畜エンジニア発掘戦線