社畜エンジニア発掘戦線

駆けだしAIエンジニア

④線形分類問題(多クラス分類)

第四問

前回はワン・ホット表現を用いて2クラス分類にトライしました。今回は更に多クラス(3クラス以上)の分類に取り組んでみたいと思います・

設問1.ガウシアンノイズを付加した入力x1、x2が(x1, x2) = (5, 5), (0, -5), (-5, 5)となるデータセットをワン・ホット表現を用いて作成せよ

クラス分類の入力が3つになりました。とは言え、前回の2クラスから3クラスになっただけなので、データグループの数を増やせばいいだけです。

まずはライブラリのインポート。

import numpy as np
import matplotlib.pyplot as plt

続いてデータセットを作成します。

#dataset
num_of_sam = 40
std_dv = 1.8

group1 = np.array([5,5])+np.random.randn(num_of_sam, 2)*std_dv
group2 = np.array([0,-5])+np.random.randn(num_of_sam, 2)*std_dv
group3 = np.array([-5,5])+np.random.randn(num_of_sam, 2)*std_dv
X = np.vstack((group1, group2, group3))

group3が増えていますが、今まで通りですね。では正解ラベルを書いていきます。

t_group1 = np.tile([0,0,1],(num_of_sam,1))
t_group2 = np.tile([0,1,0],(num_of_sam,1))
t_group3 = np.tile([1,0,0],(num_of_sam,1))
T = np.vstack((t_group1, t_group2, t_group3))

こちらも前回からgroup3が増えているだけですね。最後にXもTも結合するのを忘れないように注意です。

ではグラフ化してみましょう。

plt.scatter(group1[:,0],group1[:,1],marker='o',color='blue')
plt.scatter(group2[:,0],group2[:,1],marker='o',color='red')
plt.scatter(group3[:,0],group3[:,1],marker='o',color='green')
plt.show()

f:id:sutokun:20190227205352p:plain
確かに3クラスになっていますね、これでこの設問は終了です。

設問2.活性化関数にソフトマックス、損失関数にクロスエントロピーを用いて出力y1、y2と損失量Eを求めよ

こちらも前回と流れは同じですが、データセットと出力の数が変わるので確認しておきます。
f:id:sutokun:20190227205424j:plain
線の数がだいぶ多くなってきました。

入力と出力の数は同じなので、基本的な骨格はこれまでと変わりません。なので、変更するのはパラメータW、Bの行列の型だけです。まずはソフトマックスとクロスエントロピー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とBの設定です。

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

ニューロンの数が2→3となっているのでパラメータの型も変更しています。

では、準備が整ったので実際に計算してみます。

Y = softmax(np.dot(X, W)+B)
E = loss(Y, T)

ここは前回と同じですね。設問2は終了です。

設問3.勾配降下法でパラメータを400回更新し、その出力をデータセットと同じグラフ上にプロットせよ

パラメータの更新式ですが、行列の型が変わっているだけで式は同じです。

重み(W)の更新式:{\displaystyle
W_{new} = W_{old} - \mu \Bigl( X \cdot (Y-T) \Bigr) 
}

バイアス(B)の更新式:{\displaystyle
B_{new} = B_{old} -  \mu \Bigl( 1 \cdot (Y-T) \Bigr) 
}

ではコードに落とします。学習率は0.003とします。

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

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

ではグラフに描画します。グリッドを切ってそれぞれの出力を結合します。詳しくは前回の問題参照で。

#plot
grid_range = 10
resolution = 50
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)

out_connect = np.hstack((X_grid,Y_predict))
green_group = out_connect[out_connect[:,2]==1]
red_group = out_connect[out_connect[:,3]==1]
blue_group = out_connect[out_connect[:,4]==1]

グリッドが切れたので実際にグラフ化します。

#plot_dataset
plt.scatter(group1[:,0],group1[:,1],marker='o',color='blue')
plt.scatter(group2[:,0],group2[:,1],marker='o',color='red')
plt.scatter(group3[:,0],group3[:,1],marker='o',color='green')

#plot_dataset
plt.scatter(green_group[:,0],green_group[:,1],marker='o',alpha=0.3,color='green')
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()

groupが3つになっていますが、書いている内容は同じですね。
f:id:sutokun:20190227205542p:plain
綺麗に分類できました、これで設問3は終了です。
[note] なぜか色の付かない欠損点ができるんですよね、出力ラベルが全部ゼロになってるのかな。

設問4.ガウシアンノイズを付加した入力x1、x2が(x1, x2) = (0, 0), (5, 5), (5, -5), (-5, -5), (-5, 5)となるデータセットを、勾配降下法で分類した結果と同一のグラフにプロットせよ

3クラス分類は2クラス分類からグループを増やしただけで実行することができたので、最後の設問として一気に5クラス分類にトライしてみたいと思います。

まずはデータセットの作成です。

import numpy as np
import matplotlib.pyplot as plt

#dataset
num_of_sam = 40
std_dv = 1.8

group1 = np.array([0,0])+np.random.randn(num_of_sam, 2)*std_dv
group2 = np.array([5,5])+np.random.randn(num_of_sam, 2)*std_dv
group3 = np.array([5,-5])+np.random.randn(num_of_sam, 2)*std_dv
group4 = np.array([-5,-5])+np.random.randn(num_of_sam, 2)*std_dv
group5 = np.array([-5,5])+np.random.randn(num_of_sam, 2)*std_dv
X = np.vstack((group1, group2, group3, group4, group5))

t_group1 = np.tile([0,0,0,0,1],(num_of_sam,1))
t_group2 = np.tile([0,0,0,1,0],(num_of_sam,1))
t_group3 = np.tile([0,0,1,0,0],(num_of_sam,1))
t_group4 = np.tile([0,1,0,0,0],(num_of_sam,1))
t_group5 = np.tile([1,0,0,0,0],(num_of_sam,1))
T = np.vstack((t_group1, t_group2, t_group3, t_group4, t_group5))

コピペコピペ&コピペです。さすがに5グループ分も同じ記述が続くとブサイクに見えるので、この書き方は微妙です。もっとうまい記述はあると思いますが、とりあえず動きが見たいので今回コーディングの改良はスルーで。

グラフ化します。

plt.scatter(group1[:,0],group1[:,1], marker='o',color='yellow')
plt.scatter(group2[:,0],group2[:,1], marker='o',color='black')
plt.scatter(group3[:,0],group3[:,1], marker='o',color='red')
plt.scatter(group4[:,0],group4[:,1], marker='o',color='green')
plt.scatter(group5[:,0],group5[:,1], marker='o',color='blue')

plt.show()

f:id:sutokun:20190227210006p:plain
いい感じですね。

引き続きコピペを勧めます。活性化関数と損失関数は今まで通り「ソフトマックス」と「クロスエントロピー」です。

#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とBはニューロンの数に合わせて変更が必要です。

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

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

爆速で進んでいますが、ほぼ上記のコピペです。

さて、結果をグラフ化します。まずはグリッド切りから。

grid_range = 10
resolution = 50
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)

out_connect = np.hstack((X_grid,Y_predict))
blue_group = out_connect[out_connect[:,2]==1]
green_group = out_connect[out_connect[:,3]==1]
red_group = out_connect[out_connect[:,4]==1]
black_group = out_connect[out_connect[:,5]==1]
yellow_group = out_connect[out_connect[:,6]==1]

実際にグラフ化します。

#plot_dataset
plt.scatter(group1[:,0],group1[:,1],marker='o',color='yellow')
plt.scatter(group2[:,0],group2[:,1],marker='o',color='black')
plt.scatter(group3[:,0],group3[:,1],marker='o',color='red')
plt.scatter(group4[:,0],group4[:,1],marker='o',color='green')
plt.scatter(group5[:,0],group5[:,1],marker='o',color='blue')

#plot_output
plt.scatter(blue_group[:,0],blue_group[:,1],marker='o',alpha=0.3,color='blue')
plt.scatter(red_group[:,0],red_group[:,1],marker='o',alpha=0.3,color='red')
plt.scatter(green_group[:,0],green_group[:,1],marker='o',alpha=0.3,color='green')
plt.scatter(black_group[:,0],black_group[:,1],marker='o',alpha=0.3,color='black')
plt.scatter(yellow_group[:,0],yellow_group[:,1],marker='o',alpha=0.3,color='yellow')

plt.show()

ほんと5グループも並ぶとコードはブサイクですね。まあ気にせず今回は力技でいきましょう。
f:id:sutokun:20190227210712p:plain
うまく分類できました。線形分類ですが5クラスちゃんと分けられるんですね。へーすごい。

まとめ

今回も学習が進む過程をGIFでアニメ化します、2本立てです。
f:id:sutokun:20190227210854g:plain f:id:sutokun:20190227210828g:plain

さてさて、直線を使ってクラスを分類する「線形分類」を長々と扱ってきました。次回からは直線では分類できないようなデータセット(ドーナツ型とか)を分類できるような非線形問題にトライしていきたいと思います。これまではニューラルネットワークにとって比較的問簡単な問題だったので、パラメータの更新には勾配降下法しか使わなくてもそれなりに結果を出すことができました。しかし、そろそろ問題も複雑になってくるので、他の更新方法だとどのような違いが見られるのかも扱っていければと思います。

全体コード

  • 3クラス分類

github.com

  • 5クラス分類

github.com

次の問題
①非形分類問題(2クラス分類) - 社畜エンジニア発掘戦線

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

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