pyhaya’s diary

プログラミング、特にPythonについての記事を書きます。Djangoや機械学習などホットな話題をわかりやすく説明していきたいと思います。

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

内容的には、前回の記事の続き的な感じになります。
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%程度の精度が出ていることが確認できます。