パーセプトロンの概要
パーセプトロンは2クラスを分類する手法です。訓練データが与えられたとき、
で表されるモデルを考えます。はダミー入力の定数を含みます。また、とします。ここで関数は
とします。つまり、2つのクラスを±1で表し、関数は入力が正のときに1のクラス、負のときに-1のクラスと判別するということです。訓練データをそれぞれのクラスに分類できるようなパラメータを求めることによって識別を実現します。
パーセプトロンの学習
モデルの定義より、正しく分類されていれば、任意のデータに対してとなることがわかります。そこでパーセプトロンでは誤差関数を
と定義します。は誤識別したデータの集合を表します。誤識別されるととなるので、式(3)は誤識別が多いほどが大きくなることがわかります。最適解は勾配法を用いて求めます。
式(3)の勾配は
です。(参考:ベクトルの微分)これを用いて最急降下法のアルゴリズムを使って解を求めます。つまり、
により、パラメータを更新していきます。求めたいのは最小値なので、式(4)の勾配を減算します。は学習率と呼ばれ、1回の更新量を調整するパラメータで任意に設定します。はアルゴリズムのステップ数です。全てのデータが正しく判別できた時点のを求める解とします。実際には、局所解に落ちないように確率的勾配降下法が用いられるようです。誤識別したデータの中からランダムで1つ選択し、
で更新していきます。ランダムで1つずつ更新するか、誤識別した全データで勾配を求めて更新するかどうかが違うのみです。
パーセプトロンの実験
さてアルゴリズムは非常に単純なので実装も簡単です。適当に作った2つのデータが識別できるかどうか実験してみました。 ちゃんと識別できていますね。
線形分離について
パーセプトロンは線形分離可能な問題しか扱えないとして、以下のような図をよく見ます。赤と青の点を直線では分離できないという説明です。
しかし入力を非線形変換すれば、パーセプトロンのアルゴリズムで曲線の識別もできます。(参考:入力を非線形変換したパーセプトロン)
、、っと僕は理解しているのですが、あまりこのことに言及している説明を見たことがありません。僕が何か誤解しているのでしょうか?あるいは、線形分離可能な問題しか扱えないのは単純パーセプトロンであって、入力を非線形変換するということは、多層パーセプトロンに相当し、これであれば分離可能であるということでしょうか。入力を変換すれば以下のようにパーセプトロンで識別することができました。
今回のコードです。今回のような単純な例では確率的勾配降下法でも最急降下法でも違いはわかりません。
# パーセプトロン import matplotlib.pyplot as plt import numpy as np from matplotlib import cm # 判別クラスを返す(グラフ表示用) def f(x0, x1): if np.dot(w.T, np.array([x0, x1, 1.0])) > 0: return 0 else: return 1 # 全データ数 N = 300 # 入力次元数 D = 2 # クラス数 K = 2 # 学習率 Eta = 1.0 # ランダムシードを固定 np.random.seed(0) # 2クラス分のデータを作成 mean = np.array([[1, 2.5], [0, -2.5]]) cov = [[1.0, -0.7], [-0.7, 1.0]] x = np.empty([D, N]) t = np.empty([N]) for i in range(N): # -1 , 1 のラベルをランダムで作る t_ = np.random.randint(K) t[i] = 2.0*t_-1.0 x[:, i] = np.random.multivariate_normal(mean[t_], cov) # 定数項をまとめて追加 x = np.vstack([x, np.ones(N)]) # 重みベクトルwを初期化 定数項があるので入力次元+1 w = np.random.uniform(-1, 1, D+1) # 誤りがなくなるまで更新を繰り返す while True: # 更新処理の順をランダムで決める index = np.random.permutation(np.arange(0, N)) break_flag = True # 更新処理 # 確率的勾配降下法 for i in range(N): if t[index[i]] * np.dot(w.T, x[:, index[i]]) < 0: w += Eta * t[index[i]] * x[:, index[i]] break_flag = False # 最急降下法 # grad = 0.0 # for i in range(N): # if t[index[i]] * np.dot(w.T, x[:, index[i]]) < 0: # grad += Eta * t[index[i]] * x[:, index[i]] # break_flag = False # w += grad if break_flag: break plt.xlim(-5, 5) plt.ylim(-5, 5) # 識別直線 x_graph = np.linspace(-5, 5, 2) y_graph = (-w[0] * x_graph - w[2]) / w[1] plt.plot(x_graph, y_graph, color="black") # グラフの色分け a, b = np.meshgrid(np.linspace(-5, 5, 1000), np.linspace(-5, 5, 1000)) vec_f = np.vectorize(f) plt.contourf(a, b, vec_f(a, b), alpha=0.2, cmap=cm.coolwarm) plt.scatter(x[: ,np.where(t==1)][0], x[:,np.where(t==1)][1], color="blue", alpha=0.5) plt.scatter(x[: ,np.where(t==-1)][0], x[:,np.where(t==-1)][1], color="red", alpha=0.5) plt.show()