【機械学習 no.4】Pythonで単純パーセプトロンを実装してみる


coding

前回まで,Pythonの勉強も兼ねてK最近傍法を実装しました.今回は,単純パーセプトロンを実装したいと思います.

と言っても,先ずは簡単なものから.クラスラベルは2種類の2次元データとします.データはirisのpetal widthとpetal lengthのみを用いて,Irisの種類はIris-versicolorとIris-virginicaのみとします.ちょっと簡単すぎますが,プログラムの動作確認するにはこれくらいの方が楽でいいです.拡張するのは簡単ですからね.

で,今回はclassを使ってみました.Pythonがオブジェクト指向開発をサポートしてるとは知りませんでした!

先ずは今回作成した単純パーセプトロンの簡単な説明から

今回作成した単純パーセプトロン

単純パーセプトロンは,入力値 \(x_d\) に対してそれぞれ \(w_d\) の重み付け総和を入力とし,この値が0より大きい場合は1を出力し,それ以外は0を出力するものです.これを以下のような図で書くことがあります.

入力は \(d\) 次元の値 \(x_d\) に加え1も入力しますので,3個の入力となります.今回は2次元のデータを扱いますので,\(d=1,2\) となります.重み付け総和は,
\[
L = w_0 + w_1x_1 + w_2x_2
\]
という値を意味します.重み \(w_d\) の値は最初は乱数で適当に設定します.この値 \(L\) に着目し,
\[
y= \begin{cases}
1 & (L>0) \\
0 & (L\le 0)
\end{cases}
\]
と出力するものを単純パーセプトロンと言います.これは,つまり直線 \(L = w_0 + w_1x_1 + w_2x_2 = 0\)を識別境界として,入力データがこの直線より上の領域にあれば \(y=1\) と出力し,下の領域にあれば \(y=0\) と出力することを意味します.

すなわち,単純パーセプトロンの識別境界は「直線」となります.ちなみに,直線上に完全に乗っている場合は,\(y=1\)か0のどちらかに割り当てるとよいと思います.

で,パーセプトロンを「学習」させるとは,重み \(w_d\) の値を変化させて,学習データの誤識別が最も少なくなるような識別境界の傾きと切片を求めることになります.

Pythonで実装しました

そんで,Pythonで実装してみました.今回はclassも用いています.

import numpy as np
import pandas as pd
from matplotlib import pyplot

class Perceptron:
	def __init__(self, alpha):
		self.alpha = alpha
		self.w = np.random.random(3)-0.5
		self.err = []

	def predict(self, test):
		L = self.w[0] + sum(self.w[1:3] * test)
		if L>=0:
			return 1.0
		elif L<0:
			return 0.0
		
	def train(self, train, clnum, itermax=10):
		for iter in range(itermax):
			err1 = 0
			for n in range(len(clnum)):
				r = self.predict(train[n,])
				self.w[1:3] += self.alpha * (clnum[n]-r) * train[n,] 
				self.w[0] += self.alpha * (clnum[n]-r)
				err1 += (clnum[n]-r)**2
			self.err.append(err1)
		

df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data', header=None)

x = df.iloc[50:150,2:4].values
label = df.iloc[50:150,4].values
labnum = np.where(label=='Iris-versicolor', 1.0, 0.0)

correct = 0;
result = np.zeros(100)
for n in np.argsort(np.random.random(100)):
	pp = Perceptron(0.0005)
	ind = np.ones(100, dtype=bool)
	ind[n] = False
	#	
	train = x[ind]
	clnum = labnum[ind]
	test = x[n]
	#
	pp.train(train, clnum, itermax=3000)	
	r = pp.predict(test)
	result[n] = r
	if r==labnum[n]:
		correct += 1

print('correct classification: {}%'.format(100*correct/100))
result2 = np.where(result==1, 'Iris-versicolor', 'Iris-virginica')
print(pd.crosstab(result2, label))

pyplot.plot(pp.err)
pyplot.show()

pyplot.clf()
for xx in np.arange(3,7,0.1):
	for yy in np.arange(1,2.5,0.05):
		r = pp.predict(np.array([xx,yy]))
		if abs(r - 1.0) < 0.0000001:
			pyplot.plot(xx,yy,'s', color='#ffcccc')
		elif abs(r - 0.0) < 0.0000001:
			pyplot.plot(xx,yy,'s', color='#ccccff')

pyplot.plot(x[0:50,0],x[0:50,1],'r.')
pyplot.plot(x[50:100,0],x[50:100,1],'b*')
pyplot.xlabel('petal length')
pyplot.ylabel('petal width')
pyplot.show()

ちょっと長いコードですみません(汗).

実行結果はこちら.先ずは最初のプロットで.これは誤差関数の値の変化を表しています.学習が進んで行って,誤差が少なくなっている様子が分かります.

そして,次のプロットがこちら.これは2次元特徴空間内の識別境界とirisのデータを表したものです.境界付近で誤識別を起こしているデータがいくつかあるのが分かります.また,分離境界が直線なのもわかると思います.

Pythonの文法などに関する備忘録は後日更新します(汗)