機械学習に詳しくなりたいブログ

機械学習や数学について勉強した内容を中心に書きます。100%趣味です。記事は数学的に厳密でなかったり誤りを含んでいるかもしれません。ご指摘頂ければ幸いです。

パーセプトロン

パーセプトロンの概要

パーセプトロンは2クラスを分類する手法です。訓練データ\mathbf x, tが与えられたとき、


  y(\mathbf x) = f(\mathbf{w}^{T} \mathbf{x}) \tag{1}

で表されるモデルを考えます。\mathbf{x}はダミー入力の定数を含みます。また、t \in \{-1,1\}とします。ここで関数f


f(a) = \begin{cases}
    1 & (a \ge 0) \\
    -1 & (a \lt 0)
  \end{cases} \tag{2}

とします。つまり、2つのクラスを±1で表し、関数fは入力が正のときに1のクラス、負のときに-1のクラスと判別するということです。訓練データをそれぞれのクラスに分類できるようなパラメータ\mathbf{w}を求めることによって識別を実現します。

パーセプトロンの学習

モデルの定義より、正しく分類されていれば、任意のデータに対してt \mathbf{w}^{T} \mathbf{x} \ge 0となることがわかります。そこでパーセプトロンでは誤差関数を


\displaystyle E(\mathbf{w}) = -\sum_{n \in C} t_n \mathbf{w}^{T} \mathbf{x}_n \tag{3}

と定義します。Cは誤識別したデータの集合を表します。誤識別されるとt_n \mathbf{w}^{T} \mathbf{x}_n \lt 0となるので、式(3)は誤識別が多いほどE(\mathbf{w})が大きくなることがわかります。最適解は勾配法を用いて求めます。

式(3)の勾配は


\displaystyle \nabla E = - \sum_{n \in C} t_n \mathbf{x}_n \tag{4}

です。(参考:ベクトルの微分)これを用いて最急降下法のアルゴリズムを使って解を求めます。つまり、


\displaystyle \mathbf{w}^{(\tau + 1)} = \mathbf{w}^{(\tau)} + \eta \sum_{n \in C}  t_n \mathbf{x}_n \tag{5}

により、パラメータを更新していきます。求めたいのは最小値なので、式(4)の勾配を減算します。\etaは学習率と呼ばれ、1回の更新量を調整するパラメータで任意に設定します。\tauはアルゴリズムのステップ数です。全てのデータが正しく判別できた時点の\mathbf{w}を求める解とします。実際には、局所解に落ちないように確率的勾配降下法が用いられるようです。誤識別したデータの中からランダムで1つ選択し、


\displaystyle \mathbf{w}^{(\tau + 1)} =  \mathbf{w}^{(\tau)}  + \eta t_n \mathbf{x}_n \tag{6}

で更新していきます。ランダムで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()