分類問題が線形分離可能であれば、単純パーセプトロンで解決できる。例として、パーセプトロンで論理回路を作るというものがある。それについては
を参照。
論理回路の場合、座標上の4点(0, 0), (0, 1), (1, 0), (1, 1)を考える。各点は、論理回路の2つの入力の組に対応する。信号を流す(真)が1、信号を流さない(偽)が0だ。これらの点を、対応する論理回路の出力が0か1かで分離するような直線を見つければいい。それが、パーセプトロンの重み(パラメータ)を設定することに相当する。ただし、適切な重みの設定をするのは人間だ。
人間が設定しなくても、適切な重みを自動で学習できることが望ましい。そこで、scikit-learnに含まれるiris(アヤメの品種データ)を使って、パーセプトロンの学習アルゴリズムを試してみる。
まず、irisのデータの中身を確認する。確認するには、視覚化してみるのが分かりやすい。視覚化にはseabornを使うことにする。ちょうどseabornにもサンプルデータとしてirisが入っている。データの意味を知りたいだけだから、ひとまずこちらを使う。
視覚化してみると、アヤメの各個体のデータであることが分かる。特徴量として、がく片の長さ(sepal length)、がく片の幅(sepal width)、花弁の長さ(petal length)、花弁の幅(petal width)が入っている。そして、そのアヤメがどの品種かを示すラベル(species)も入っている。“setosa”, “versicolor”, “virginica”の3種類だ。
ただし、scikit-learnに含まれるデータでは、品種は0, 1, 2とダミー変数になっている。また、特徴量はdataというキーでアクセスし、ラベルはtargetというキーでアクセスするようになっている。
ここで、問題を単純に設定して、“setosa”, “versicolor”の2種類を分離することにする。特徴量は、sepal widthとpetal lengthのみ使用する。
さて、どのような規則でパーセプトロンの重みを更新していくかだ。一般的な式を書いてしまうとこうなる。
- は、i番目の入力ニューロンとj番目の出力ニューロンの接続の重み(k回目の更新後)
- は、i番目の入力
- は、j番目の出力
- は、j番目のターゲット出力(ラベル)
- は、学習率
今回は出力が1つだから、jは無視していい。
誤差が小さくなるように、(学習率を掛けて)少しずつ重みを更新している。更新量は、誤差と入力の大きさに比例する(デルタ則)。
データが線形分離可能であれば、このアルゴリズムは解に収束する。パーセプトロンの収束定理と呼ばれるものだ。ただし、解は一意ではない。無限にある。
実装して動かしてみると、あっという間に収束することが分かる。データを全て見ないうちに。問題が簡単過ぎるのかもしれない。
import numpy as np
import pandas as pd
from sklearn import datasets, model_selection
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
df = sns.load_dataset('iris')
df.head()
sns.set(style='ticks')
sns.pairplot(df,
hue='species',
markers=["o", "s", "x"])\
.savefig('iris.png')
iris = datasets.load_iris()
type(iris)
iris.keys()
iris_data = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_data.head()
iris.target
iris_df = iris_data.copy()
iris_df['species'] = iris.target
iris_df.head()
iris_df = iris_df.iloc[:, [1, 2, 4]]
iris_df = iris_df[iris_df['species'].isin([0, 1])]
iris_df.head()
X = iris_df.iloc[:, :2].values
Y = iris_df.species.values
x_train, x_test, y_train, y_test = model_selection.train_test_split(X, Y)
class Perceptron:
def __init__(self, n_iter=10, eta=0.01):
self.n_iter = n_iter
self.eta = eta
def output(self, input):
weighted_sum = np.dot(input, self.__weights[1:]) + self.__weights[0]
return self.__activate(weighted_sum)
def fit(self, X, Y):
self.__weights = np.zeros(X.shape[1] + 1)
for i in range(self.n_iter):
for j, (x, y) in enumerate(zip(X, Y)):
y_output = self.output(x)
diff = y - y_output
if diff != 0:
print('iter: {}, y_index: {}, diff: {}'.format(i, j, diff))
self.__weights += self.eta * diff * np.hstack((1, x))
def __activate(self, weighted_sum):
return self.__heaviside_step(weighted_sum)
def __heaviside_step(self, z):
return np.where(z < 0, 0, 1)
perceptron = Perceptron()
perceptron.fit(x_train, y_train)
perceptron.output(x_test)
y_test
ソースコード