多層パーセプトロン-Stephen Machine Learning-

Machine Learning: An Algorithmic Perspective (Chapman & Hall/Crc Machine Learning & Patrtern Recognition)

Machine Learning: An Algorithmic Perspective (Chapman & Hall/Crc Machine Learning & Patrtern Recognition)

のコードを見ながら実装
アルゴリズムは以下のようになる。
ちなみに、誤差逆伝搬法である(はず)。
1.初期化- Initialisation
2.訓練
 繰り返し
  Forward phase
   隠れ層のそれぞれのニューロンjの活性を計算
    h_j = \sum_{i} x_iv_{ij}
    a_j = g(h_j) = \frac{1}{1 + exp(-\beta h_j)}

   出力層も同様
    h_k = \sum_{j}a_j w_{jk}
    y_k = g(h_k) = \frac{1}{1 + exp(-\beta h_k)}

  Backwards pahse
   出力の誤差を計算
    \delta_{ok} = (t_k - y_k)y_k(1 - y_k)

   隠れ層の誤差を計算
    \delta_{hj} = a_j(1 - a_j)\sum_{k}w_{jk}\delta_{ok}

   出力層の荷重の更新
    w_{jk}^{t + 1} \leftarrow w_{jk}^{t} + \mu \delta_{ok}a_{j}^{hidden}

   隠れ層の荷重の更新
    v_{jk}^{t + 1} \leftarrow v_{jk}^{t} + \mu \delta_{hj} x_i

3.呼び出し
 上記の訓練時のForward phaseを用いる

上記のアルゴリズムのまま実装すると以下のようになる。

#-*- coding:utf-8 -*-
import numpy as np

class MLP:
	""""ニューラルネットワーク"""
	def __init__(self, input, target, nHidden, beta=1.0):
		#コンストラクタ
		#初期値の設定
		self.nData = np.shape(input)[0]
		
		if np.ndim(input) > 1:
			self.nIn = np.shape(input)[1]
		else:	
			self.nIn = 1
		#targetの列の数を調べる
		if np.ndim(target) > 1:	
			self.nOut = np.shape(target)[1]
		else: 
			self.nOut = 1
		
		#隠れ層の数
		self.nHidden = nHidden
		#シグモイド関数に対して正のパラメータを設定
		self.beta = beta
		
		#ネットワークの初期化
		self.Hweight = np.random.rand(self.nIn+1,self.nHidden) - 0.5
		self.Iweight = np.random.rand(self.nHidden+1,self.nOut) - 0.5
	
	def train(self, input, target,eta,update):
		#バイアス項として-1を加える
		input = np.concatenate((-np.ones((self.nData,1)),input),axis=1)
		
		self.updateV = np.zeros((np.shape(self.Hweight)))
		self.updateW = np.zeros((np.shape(self.Iweight)))
		
		for N in xrange(update):
			self.output = self.fwd(input)
			self.bwd(input,target,eta)
		
	def fwd(self,input):
		#Forward phase
		#隠れ層のそれぞれのニューロンの活性を計算する-ロジスティック関数
		self.hidden = np.dot(input,self.Hweight)
		self.hidden = 1.0/(1.0 + np.exp(-self.beta * self.hidden))
		self.hidden = np.concatenate((-np.ones((np.shape(input)[0],1)),self.hidden),axis=1)
		
		#出力層の活性を計算する-ロジスティック関数
		self.output = np.dot(self.hidden,self.Iweight)
		self.output = 1.0/(1.0 + np.exp(- self.beta * self.output))
		return self.output
		
	def bwd(self,input,target,eta):
		#Backward phase
		#出力層の誤差を計算する
		delta_o = (target - self.output)*self.output*(1.0 - self.output)
		#隠れ層の誤差を計算する
		delta_h = self.hidden*(1.0 - self.hidden)*(np.dot(delta_o,(self.Iweight).T))	
		
		self.updateW = eta * np.dot(np.transpose(self.hidden),delta_o)
		self.updateV = eta * np.dot(np.transpose(input),delta_h[:,:-1])
		#隠れ層の荷重を更新する
		self.Hweight += eta * self.updateV
		#出力層の荷重を更新する
		self.Iweight += self.updateW 

しかし、これだけでは精度は結構悪い。
以下に精度を上げるテクニックについてまとめる。

アルゴリズムの実行速度を上げるには、入力と出力のデータを並び替えれば
いい。そうすることで、一回の更新の効果を上げることができる。
np.random.shuffle()を使う。
以下例。

In [1]: import numpy as np

In [2]: arr = np.arange(10)

In [3]: np.random.shuffle(arr)

In [4]: arr
Out[4]: array([0, 5, 2, 8, 6, 9, 4, 1, 7, 3])

In [5]: np.random.shuffle(arr)

In [6]: arr
Out[6]: array([3, 0, 4, 9, 1, 8, 6, 7, 5, 2])

実際には以下を、bwd後に行う。

change = range(self.nData)
np.random.shuffle(change)
input = input[change,:]
output = target[change,:]

また、momentum(慣性項)を導入するとよい。
 w_{jk}^{t + 1} \leftarrow w_{jk}^{t} + \mu \delta_{ok}a_{j}^{hidden}

 w_{ij}^{t+1} \leftarrow w_{ij}^{t} + \eta \delta_o a_j^{hidden} + \alpha \Delta w_{ij}^{t}とする。
momentumはαである。

具体的には、

self.Hweight += eta * self.updateV
self.Iweight += self.updateW 

self.updateW = eta * np.dot(np.transpose(self.hidden),delta_o) + self.momentum*self.updateW
self.updateV = eta * np.dot(np.transpose(input),delta_h[:,:-1]) + self.momentum*self.updateV

と書き換える。
通常、0 < α < 1で、0.9がよく使われるよう。理由はわかりません。

他にも、ロジスティック関数をソフトマックス関数に書き換えるなどがありますが、
割愛して以上の修正をまとめて書き直して、実行方法を書くと以下のようになります。

#-*- coding:utf-8 -*-
import numpy as np

class MLP:
	""""ニューラルネットワーク"""
	def __init__(self, input, target, nHidden, beta=1.0,momentum=0.9):
		#コンストラクタ
		#初期値の設定
		self.nData = np.shape(input)[0]
		
		if np.ndim(input) > 1:
			self.nIn = np.shape(input)[1]
		else:	
			self.nIn = 1
		#targetの列の数を調べる
		if np.ndim(target) > 1:	
			self.nOut = np.shape(target)[1]
		else: 
			self.nOut = 1
		
		#隠れ層の数
		self.nHidden = nHidden
		#シグモイド関数に対して正のパラメータを設定
		self.beta = beta
		#momentumの設定
		self.momentum = momentum
		
		#ネットワークの初期化
		#0.5はバイアス項
		self.Hweight = np.random.rand(self.nIn+1,self.nHidden) - 0.5
		self.Iweight = np.random.rand(self.nHidden+1,self.nOut) - 0.5
	
	def train(self, input, target,eta,update):
		#バイアス項として-1を加える
		input = np.concatenate((-np.ones((self.nData,1)),input),axis=1)
		change = range(self.nData)
		np.random.shuffle(change)
		
		self.updateV = np.zeros((np.shape(self.Hweight)))
		self.updateW = np.zeros((np.shape(self.Iweight)))
		
		for N in xrange(update):
			self.output = self.fwd(input)
			self.bwd(input,target,eta)
			input = input[change,:]
			target = target[change,:]
		
	def fwd(self,input):
		#Forward phase
		#隠れ層のそれぞれのニューロンの活性を計算する-ロジスティック関数
		self.hidden = np.dot(input,self.Hweight)
		self.hidden = 1.0/(1.0 + np.exp(-self.beta * self.hidden))
		self.hidden = np.concatenate((-np.ones((np.shape(input)[0],1)),self.hidden),axis=1)
		
		#出力層の活性を計算する-ロジスティック関数
		self.output = np.dot(self.hidden,self.Iweight)
		self.output = 1.0/(1.0 + np.exp(- self.beta * self.output))
		return self.output
		
	def bwd(self,input,target,eta):
		#Backward phase
		#出力層の誤差を計算する
		delta_o = (target - self.output)*self.output*(1.0 - self.output)
		#隠れ層の誤差を計算する
		delta_h = self.hidden*(1.0 - self.hidden)*(np.dot(delta_o,(self.Iweight).T))	
		
		self.updateW = eta * np.dot(np.transpose(self.hidden),delta_o) + self.momentum*self.updateW
		self.updateV = eta * np.dot(np.transpose(input),delta_h[:,:-1]) + self.momentum*self.updateV
		#隠れ層の荷重を更新する
		self.Hweight += self.updateV
		#出力層の荷重を更新する
		self.Iweight += self.updateW
			
	def confmat(self,input,target):
		input = np.concatenate((-np.ones((np.shape(input)[0],1)),input),axis=1)
		output = self.fwd(input)
		
		nclasses = np.shape(target)[1]
		
		if nclasses ==1:
			nclasses = 2
			output = np.where(output > 0.5,1,0)
		else:
			output = np.argmax(output,1)
			target = np.argmax(target,1)
		
		cm = np.zeros((nclasses,nclasses))
		for i in xrange(nclasses):
			for j in xrange(nclasses):
				cm[i,j] = np.sum(np.where(output == i,1,0)*np.where(target == j,1,0))
		
		print "Confusion matrix is:"
		print cm
		print "Percentage Correct: ",np.trace(cm)/np.sum(cm)*100 

実行結果

In [4]: import numpy as np

In [5]: import my_mlp

In [6]: anddata = np.array([[0,0,0],[0,1,0],[1,0,0],[1,1,1]])

In [9]: p.train(anddata[:,0:2],anddata[:,2:3],0.25,5000)
p.confmat(anddata[:,0:2],anddata[:,2:3])

In [10]: p.confmat(anddata[:,0:2],anddata[:,2:3])
Confusion matrix is:
[[ 3.  0.]
 [ 0.  1.]]
Percentage Correct:  100.0

以下が実行方法

In [1]: import numpy as np

In [2]: import my_mlp

In [3]: anddata = np.array([[0,0,0],[0,1,0],[1,0,0],[1,1,1]])

In [4]: p =  my_mlp.MLP(anddata[:,0:2],anddata[:,2:3],2)

In [5]: p.train(anddata[:,0:2],anddata[:,2:3],0.25,5000)

In [6]: p.confmat(anddata[:,0:2],anddata[:,2:3])
Confusion matrix is:
[[ 3.  0.]
 [ 0.  1.]]
Percentage Correct:  100.0

In [7]: 

In [7]: xordata = np.array([[0,0,0],[0,1,1],[1,0,1],[1,1,0]])

In [8]: p = my_mlp.MLP(xordata[:,0:2],xordata[:,2:3],2)

In [9]: p.train(xordata[:,0:2],xordata[:,2:3],0.25,1000)

In [10]: p.confmat(xordata[:,0:2],xordata[:,2:3])
Confusion matrix is:
[[ 2.  0.]
 [ 0.  2.]]
Percentage Correct:  100.0

次回は放射基底関数Radical Basis Functionを用いてニューラルネットワークの実装もしてみる。