pyhaya’s diary

機械学習系の記事をメインで書きます

機械学習を原理から理解する パーセプトロン

内容的には、前回の記事の続き的な感じになります。
pyhaya.hatenablog.com

今回は、二値分類をパーセプトロンを使って実装します。パーセプトロンといえば、ニューラルネットワークディープラーニングの基本という感じの学習器です。前回に引き続き、理論を説明した後にPythonでの実装を示します。

対象とする問題

いくつかの特徴量が与えられていて、それに対して正解ラベルが+1, -1で与えられているような状況を考えます。考える仮説空間はhalf-spacesクラス(日本語訳がわからない)です。

仮説空間という言葉が出てくると難しく感じますが、予測に使うのは下のような関数です。


y = sign(\mathbf{w}\cdot\mathbf{x} + b)\tag{1}

 \mathbf{w}, bは重みとバイアスです。基本的には線形関数で、最終的にsignで符号だけ取り出すことで予測値 yを算出しています。

学習アルゴリズム

これは前回の記事と同じなので結果のみ示します。最終的には、

 t_i(\mathbf{w}\cdot\mathbf{x}_i) > 0 \tag{2}

を満たす重みパラメータ\mathbf{w}を求めることが目標になります。

どのように解くか

前回の記事では、これを線形計画問題だと考えて解きました。今回は、線形計画に比べると直感的な方法でパラメータを求めていきます。

まず、パラメータ\mathbf{w} t_1\mathbf{x}_1で初期化します。なぜこのような値で初期化するかというと、こうすることで

t_1(\mathbf{w}\cdot\mathbf{x}_1)=t_1^2x_1^2 > 0 \tag{3}

となって、ひとまず最初のデータに関して学習させることができるためです。

次にやることは \mathbf{w}はこれで最適か調べることです。学習データを(2)式に代入していって、不等式が成り立っているか確かめます。すべての学習データで不等式が成立していればこのパラメータが最適値とすることができます。しかしほとんどの場合はこううまくはいきません。 i番目のデータで

t_i(\mathbf{w}\cdot\mathbf{x}_i) \le 0 \tag{4}

となってしまった場合を考えましょう。どうすればよいでしょう。要するに、左辺を増やす方向に\mathbf{w}を更新すればよいです。どうやるかというと、

 \mathbf{w}\rightarrow \mathbf{w}+y_i\mathbf{x}_i\tag{5}

このとき、(4)式の左辺は

t_i(\mathbf{w}\cdot\mathbf{x}_i) \rightarrow t_i(\mathbf{w}\cdot\mathbf{x}_i)+\| \mathbf{x}_i\|^2\tag{6}

となります。ここで、正解ラベルt_iは±1しかとらないことを使いました。無事左辺を増やせました。このようにして、すべての学習データについて(2)式が成り立つまで値の更新を続けます。

Pythonでの実装

値の更新は、現実問題としてすべての学習データを間違えることなく予測することは不可能なことが多いので、適当なところで打ち切ります。100回の値更新で打ち切るようにしたのが下のプログラムです。

import numpy as np

from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification

def Perceptron(X, y):
	X = np.array(X)
	w = np.zeros(len(X[0]))
	w = y[0] * X[0]

	for count in range(100):
		success = True
		for i in range(len(X)):
			if np.sign(w.dot(X[i])) == np.sign(y[i]):
				continue
			else:
				w += y[i]*X[i]
				success = False
				break
		if success:
			break
	
	return w

if __name__ == '__main__':
	#サンプル生成
	X, y = make_classification(
			n_samples=500,
			n_classes=2,
			n_features=2,
			n_redundant=0,
			class_sep=1.5,
			)

	y = list(map(lambda x: -1 if x == 0 else 1, y))
	X = np.array(X)
	y = np.array(y)
	X_train, X_test, y_train, y_test = train_test_split(X, y)

	w = Perceptron(X_train, y_train)
	accuracy = np.sum(y_test*X_test.dot(w) > 0)
	print(accuracy / len(X_test))

これを実行すると、だいたい95%程度の精度が出ていることが確認できます。