パーセプトロン(Perceptron)は、1957年にFrank Rosenblattによって提案された、最も基本的な線形分類モデルです。ニューラルネットワークの原点とも言える手法であり、現代の深層学習の礎となっています。
本記事では、パーセプトロンの数学的な定義から学習アルゴリズムの導出、そしてPythonでのスクラッチ実装までを解説します。
本記事の内容
- パーセプトロンの定義と幾何学的な意味
- 学習アルゴリズム(重みの更新則)の導出
- 収束定理
- Pythonでのスクラッチ実装と可視化
前提知識
この記事を読む前に、以下の概念を理解しておくと読みやすくなります。
- ベクトルの内積の基本
- 線形代数の基礎(超平面の概念)
パーセプトロンとは
パーセプトロンは、入力 $\bm{x} \in \mathbb{R}^d$ を2つのクラス($+1$ または $-1$)に分類する線形分類器です。
イメージとしては、$d$ 次元空間に1つの超平面(決定境界)を引いて、データを2つの領域に分けるモデルです。
パーセプトロンの出力は次のように定義されます。
$$ \begin{equation} \hat{y} = \text{sign}(\bm{w}^T \bm{x} + b) \end{equation} $$
ここで、
- $\bm{w} \in \mathbb{R}^d$:重みベクトル
- $b \in \mathbb{R}$:バイアス項
- $\text{sign}(a)$:$a > 0$ なら $+1$、$a \leq 0$ なら $-1$ を返す符号関数
決定境界は $\bm{w}^T \bm{x} + b = 0$ で定義される超平面です。
幾何学的な理解
2次元の場合を考えてみましょう。重みベクトル $\bm{w} = (w_1, w_2)^T$ に対して、決定境界は
$$ \begin{equation} w_1 x_1 + w_2 x_2 + b = 0 \end{equation} $$
という直線です。
重みベクトル $\bm{w}$ はこの直線に垂直な方向を向いています。$\bm{w}$ の方向側が正のクラス($+1$)、逆側が負のクラス($-1$)に対応します。
データ点 $\bm{x}$ から決定境界までの符号付き距離は次のようになります。
$$ \begin{equation} \text{距離} = \frac{\bm{w}^T \bm{x} + b}{\|\bm{w}\|} \end{equation} $$
正しく分類されたデータ点では $y_i(\bm{w}^T \bm{x}_i + b) > 0$ が成り立ちます。
学習アルゴリズムの導出
パーセプトロンの学習は、誤分類されたデータ点を使って重みを更新していくシンプルなアルゴリズムです。
損失関数
パーセプトロンの損失関数は、誤分類されたデータ点に対する「マージン」の合計として定義されます。
$$ \begin{equation} L(\bm{w}, b) = -\sum_{i \in \mathcal{M}} y_i(\bm{w}^T \bm{x}_i + b) \end{equation} $$
ここで $\mathcal{M}$ は誤分類されたデータ点の集合です。$y_i(\bm{w}^T \bm{x}_i + b) < 0$ であるため、$L \geq 0$ です。全てのデータが正しく分類されれば $L = 0$ になります。
勾配の計算
損失関数の勾配を計算します。
$$ \begin{align} \frac{\partial L}{\partial \bm{w}} &= -\sum_{i \in \mathcal{M}} y_i \bm{x}_i \\ \frac{\partial L}{\partial b} &= -\sum_{i \in \mathcal{M}} y_i \end{align} $$
更新則
確率的勾配降下法(SGD)の考え方で、誤分類されたデータ点を1つずつ選んで更新します。
$$ \begin{align} \bm{w} &\leftarrow \bm{w} + \eta \, y_i \bm{x}_i \\ b &\leftarrow b + \eta \, y_i \end{align} $$
ここで $\eta > 0$ は学習率です。
この更新の直感的な意味を考えてみましょう。
- 正のクラス($y_i = +1$)が誤って負と分類された場合: $\bm{w}$ に $\bm{x}_i$ を加えることで、$\bm{w}^T \bm{x}_i$ の値が増加し、正しい方向に修正される
- 負のクラス($y_i = -1$)が誤って正と分類された場合: $\bm{w}$ から $\bm{x}_i$ を引くことで、$\bm{w}^T \bm{x}_i$ の値が減少し、正しい方向に修正される
パーセプトロンの学習アルゴリズム
- $\bm{w} = \bm{0}$, $b = 0$ で初期化
- 訓練データをランダムに並べ替える
- 各データ点 $(\bm{x}_i, y_i)$ について:
– $y_i(\bm{w}^T \bm{x}_i + b) \leq 0$(誤分類)ならば:
- $\bm{w} \leftarrow \bm{w} + \eta \, y_i \bm{x}_i$
- $b \leftarrow b + \eta \, y_i$
- 誤分類がなくなる(または最大反復回数に達する)まで手順2-3を繰り返す
収束定理
パーセプトロンの重要な理論的性質として、収束定理があります。
定理: 訓練データが線形分離可能であれば、パーセプトロンの学習アルゴリズムは有限回の更新で収束する。
具体的には、データのマージン $\gamma = \min_i y_i(\bm{w}^{*T} \bm{x}_i + b^*)$ と入力の最大ノルム $R = \max_i \|\bm{x}_i\|$ を用いて、更新回数は高々
$$ \begin{equation} \frac{R^2}{\gamma^2} \end{equation} $$
回であることが証明されています。ただし、データが線形分離可能でない場合は収束しません。
Pythonでの実装
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
class Perceptron:
"""パーセプトロン分類器"""
def __init__(self, learning_rate=1.0, max_iter=1000):
self.lr = learning_rate
self.max_iter = max_iter
self.w = None
self.b = None
self.errors_per_epoch = []
def fit(self, X, y):
"""学習"""
n_samples, n_features = X.shape
self.w = np.zeros(n_features)
self.b = 0.0
self.errors_per_epoch = []
for epoch in range(self.max_iter):
errors = 0
# データをシャッフル
indices = np.random.permutation(n_samples)
for i in indices:
# 予測
score = np.dot(self.w, X[i]) + self.b
y_pred = 1 if score > 0 else -1
# 誤分類なら更新
if y_pred != y[i]:
self.w += self.lr * y[i] * X[i]
self.b += self.lr * y[i]
errors += 1
self.errors_per_epoch.append(errors)
if errors == 0:
print(f"エポック {epoch + 1} で収束しました")
break
def predict(self, X):
"""予測"""
scores = X @ self.w + self.b
return np.where(scores > 0, 1, -1)
# 線形分離可能なデータの生成
n = 100
X_pos = np.random.randn(n // 2, 2) + np.array([2, 2])
X_neg = np.random.randn(n // 2, 2) + np.array([-2, -2])
X = np.vstack([X_pos, X_neg])
y = np.array([1] * (n // 2) + [-1] * (n // 2))
# 学習
model = Perceptron(learning_rate=1.0, max_iter=100)
model.fit(X, y)
# 精度
y_pred = model.predict(X)
accuracy = np.mean(y_pred == y)
print(f"分類精度: {accuracy:.4f}")
print(f"重み: w = {model.w}, b = {model.b:.4f}")
# 可視化
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 左: 決定境界
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
np.linspace(y_min, y_max, 200))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
axes[0].contourf(xx, yy, Z, alpha=0.2, cmap='RdBu')
axes[0].scatter(X[y == 1, 0], X[y == 1, 1], c='blue', marker='o',
edgecolors='k', s=30, label='Class +1')
axes[0].scatter(X[y == -1, 0], X[y == -1, 1], c='red', marker='x',
s=30, label='Class -1')
# 決定境界の直線
if model.w[1] != 0:
x_line = np.linspace(x_min, x_max, 100)
y_line = -(model.w[0] * x_line + model.b) / model.w[1]
axes[0].plot(x_line, y_line, 'k-', linewidth=2, label='Decision boundary')
# 重みベクトルの表示
origin = np.array([0, 0])
axes[0].quiver(*origin, *model.w, color='green', scale=10,
width=0.01, label='Weight vector w')
axes[0].set_xlabel('$x_1$')
axes[0].set_ylabel('$x_2$')
axes[0].set_title('Perceptron Decision Boundary')
axes[0].legend(loc='upper left')
# 右: エポックごとの誤分類数
axes[1].plot(range(1, len(model.errors_per_epoch) + 1),
model.errors_per_epoch, 'o-', color='steelblue', markersize=5)
axes[1].set_xlabel('Epoch')
axes[1].set_ylabel('Number of Misclassifications')
axes[1].set_title('Convergence of Perceptron')
axes[1].set_ylim(bottom=0)
plt.tight_layout()
plt.show()
左のグラフでは、パーセプトロンが学習した決定境界(黒い直線)と重みベクトル(緑の矢印)を示しています。重みベクトルが決定境界に垂直であることが確認できます。右のグラフでは、エポックごとの誤分類数が0に収束する様子が見られます。
パーセプトロンの限界
パーセプトロンには重要な限界があります。
- 線形分離不可能なデータに対応できない: XOR問題に代表されるように、直線(超平面)で分離できないデータには収束しない
- マージンを最大化しない: 線形分離可能な場合でも、得られる決定境界は一意ではなく、最適とは限らない
これらの限界を克服するために、多層パーセプトロン(ニューラルネットワーク)やサポートベクターマシン(SVM)が発展しました。
まとめ
本記事では、パーセプトロンについて解説しました。
- パーセプトロンは $\hat{y} = \text{sign}(\bm{w}^T \bm{x} + b)$ で定義される線形分類器である
- 学習は誤分類データに対する重みの更新 $\bm{w} \leftarrow \bm{w} + \eta y_i \bm{x}_i$ で行われる
- 線形分離可能なデータに対しては、有限回の更新で収束することが保証される
- 線形分離不可能な問題には対応できないという限界がある
次のステップとして、以下の記事も参考にしてください。
- ロジスティック回帰 — 確率的な線形分類
- サポートベクターマシン — マージン最大化による分類