2统计学习方法(第2版)

感知机

最简单的分类器

感知机模型学习策略学习算法

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第2章:感知机(Perceptron)

一、导读

1.1 本章学习目标

第2章介绍感知机——最简单的线性分类模型,也是神经网络的基石。尽管感知机在今天看来非常简单,但它在机器学习发展史上具有里程碑式的意义。1958年Rosenblatt提出感知机后,它曾被认为是通向人工智能的关键一步;1969年Minsky和Papert在《Perceptrons》一书中指出了感知机的局限性(无法解决异或问题),直接导致了人工智能的第一次寒冬。理解感知机,就是理解神经网络和深度学习的历史起点。

本章的核心学习目标包括:

  • 理解感知机模型的数学表达:f(x) = sign(w·x + b) 的几何含义是什么?
  • 掌握感知机的学习策略:为什么最小化误分类点到超平面的距离?
  • 理解感知机学习算法:随机梯度下降法如何在误分类时更新参数?
  • 掌握Novikoff收敛定理:线性可分时,感知机算法为什么能有限步收敛?
  • 理解感知机的局限性:为什么感知机不能解决异或问题?这对后续发展有什么启示?
  • 实现感知机算法:从零开始编码,观察决策边界的形成过程。

1.2 为什么本章如此重要

感知机虽然简单,但它包含了机器学习最核心的思想:通过数据驱动的方式自动调整参数。感知机的学习算法——随机梯度下降——至今仍是深度学习中最基本的优化方法。感知机的多层扩展——多层感知机(MLP)——是现代深度神经网络的基础。

更重要的是,感知机的兴衰史揭示了机器学习发展的核心矛盾:模型能力与问题复杂度之间的匹配。理解这个矛盾,有助于你在后续学习中正确选择模型,避免过度乐观或悲观。

1.3 核心问题引导

在阅读本章时,请带着以下问题思考:

感知机的决策边界是什么几何形状?为什么它只能处理线性可分问题?

感知机的损失函数为什么不能直接用训练误差(误分类数)?

随机梯度下降法在感知机中是如何工作的?每次更新做了什么?

Novikoff定理告诉我们什么?它与学习率有关系吗?

感知机的解为什么不唯一?不同的初始参数会导致不同的解吗?

如何用感知机构建更复杂的分类器?


二、核心概念详解

2.1 感知机模型

感知机(Perceptron)是一种二分类的线性分类模型。给定输入空间 X ⊆ R^n 和输出空间 Y = {+1, -1},感知机学习一个从输入到输出的函数:

f(x) = sign(w · x + b)

其中 w ∈ R^n 是权重向量,b ∈ R 是偏置项,sign 是符号函数:

sign(z) = +1, 如果 z ≥ 0
sign(z) = -1, 如果 z < 0

2.1.1 几何解释

感知机的几何意义非常直观:在 n 维空间中找到一个超平面 `w·x + b = 0`,将正类点和负类点分开。

  • 权重向量 w 是超平面的法向量
  • 偏置 b 决定超平面与原点的距离
  • w·x + b > 0 的点被分为正类
  • w·x + b < 0 的点被分为负类
python
import numpy as np

# 感知机模型的几何解释
# 在二维空间中,超平面就是一条直线 w1*x1 + w2*x2 + b = 0
# 即 x2 = -(w1*x2 + b) / w2

w = np.array([2, 1])  # 权重
b = -3  # 偏置

# 生成数据
x1 = np.linspace(0, 5, 100)
x2_boundary = -(w[0] * x1 + b) / w[1]

# 测试几个点
test_points = np.array([[1, 1], [3, 1], [2, 3], [4, 4]])
for p in test_points:
    score = w @ p + b
    print(f"点 {p}: w·x + b = {score:.1f}, 分类 = {'+' if score >= 0 else '-'}")

2.2 学习策略:最小化误分类距离

2.2.1 为什么不用误分类数作为损失函数

直觉上,我们可能想用"误分类点的个数"作为损失函数。但问题是:误分类数是关于参数 (w, b) 的分段常数函数,不可导,无法用基于梯度的优化方法。

2.2.2 误分类点到超平面的距离

感知机采用的损失函数是误分类点到超平面的总距离。对于误分类点 (x_i, y_i)(即 y_i(w·x_i + b) ≤ 0),它到超平面的距离为:

- y_i(w·x_i + b) / ||w||

忽略 1/||w||(因为它不影响优化方向),得到感知机的损失函数:

L(w, b) = - Σ_{x_i ∈ M} y_i(w·x_i + b)

其中 M 是误分类点的集合。

python
import numpy as np

def perceptron_loss(X, y, w, b):
    """计算感知机损失函数"""
    margins = y * (X @ w + b)
    misclassified = margins <= 0
    if not np.any(misclassified):
        return 0.0, []
    loss = -np.sum(y[misclassified] * (X[misclassified] @ w + b))
    misclassified_indices = np.where(misclassified)[0]
    return loss, misclassified_indices

# 示例
X = np.array([[3, 3], [4, 3], [1, 1], [2, 1]])
y = np.array([1, 1, -1, -1])
w = np.array([1.0, 0.5])
b = -2.0

loss, mis_idx = perceptron_loss(X, y, w, b)
print(f"当前参数: w={w}, b={b}")
print(f"损失: {loss:.2f}")
print(f"误分类点索引: {mis_idx}")

2.3 学习算法:随机梯度下降

2.3.1 原始形式

感知机学习算法的原始形式采用随机梯度下降法。对于每个误分类点 (x_i, y_i),更新参数:

w ← w + η * y_i * x_i
b ← b + η * y_i

其中 η 是学习率。

直觉理解:

  • 如果 y_i = +1 但被误分类(w·x_i + b ≤ 0),则增大 w·x_i + b,使该点更可能被判为正类
  • 如果 y_i = -1 但被误分类(w·x_i + b ≥ 0),则减小 w·x_i + b,使该点更可能被判为负类
python
import numpy as np

class Perceptron:
    def __init__(self, lr=0.01, n_iter=100):
        self.lr = lr
        self.n_iter = n_iter
        self.history = []
    
    def fit(self, X, y):
        self.w = np.zeros(X.shape[1])
        self.b = 0.0
        self.history = []
        
        for epoch in range(self.n_iter):
            misclassified = 0
            for xi, yi in zip(X, y):
                if yi * (np.dot(xi, self.w) + self.b) <= 0:
                    self.w += self.lr * yi * xi
                    self.b += self.lr * yi
                    misclassified += 1
            
            self.history.append({
                'w': self.w.copy(),
                'b': self.b,
                'misclassified': misclassified
            })
            
            if misclassified == 0:
                print(f"收敛于第 {epoch + 1} 轮")
                break
        
        return self
    
    def predict(self, X):
        return np.sign(X @ self.w + self.b)

# 测试
X = np.array([[3, 3], [4, 3], [1, 1], [2, 1], [1, 2], [3, 4]])
y = np.array([1, 1, -1, -1, -1, 1])

model = Perceptron(lr=0.5, n_iter=100)
model.fit(X, y)

print(f"\n最终参数: w={model.w}, b={model.b}")
print(f"预测结果: {model.predict(X)}")
print(f"真实标签: {y}")
print(f"准确率: {np.mean(model.predict(X) == y):.2f}")

2.3.2 对偶形式

感知机学习算法还有对偶形式。对偶形式的核心思想是将 w 和 b 表示为训练样本的线性组合:

w = Σ α_i * y_i * x_i
b = Σ α_i * y_i

其中 α_i 记录第 i 个样本被误分类的次数。对偶形式的好处是可以预先计算 Gram 矩阵 G = X @ X.T,加速内积运算。

python
import numpy as np

class PerceptronDual:
    def __init__(self, lr=0.01, n_iter=100):
        self.lr = lr
        self.n_iter = n_iter
    
    def fit(self, X, y):
        N = len(y)
        self.alpha = np.zeros(N)
        self.b = 0.0
        self.X = X
        self.y = y
        
        # 预计算 Gram 矩阵
        self.G = X @ X.T
        
        for epoch in range(self.n_iter):
            misclassified = 0
            for i in range(N):
                # 计算 y_i * (Σ α_j * y_j * x_j · x_i + b)
                decision = self.y[i] * (np.sum(self.alpha * self.y * self.G[i]) + self.b)
                if decision <= 0:
                    self.alpha[i] += self.lr
                    self.b += self.lr * self.y[i]
                    misclassified += 1
            
            if misclassified == 0:
                print(f"对偶形式收敛于第 {epoch + 1} 轮")
                break
        
        return self
    
    def predict(self, X):
        # w = Σ α_i * y_i * x_i
        w = np.sum((self.alpha * self.y)[:, np.newaxis] * self.X, axis=0)
        return np.sign(X @ w + self.b)

# 测试
X = np.array([[3, 3], [4, 3], [1, 1], [2, 1]])
y = np.array([1, 1, -1, -1])

model_dual = PerceptronDual(lr=0.5, n_iter=100)
model_dual.fit(X, y)
print(f"对偶形式预测: {model_dual.predict(X)}")

2.4 收敛性定理(Novikoff定理)

Novikoff定理是感知机理论中最重要的结论之一。它证明了:如果训练数据线性可分,感知机学习算法在有限步内收敛。

2.4.1 定理内容

设训练数据集 T = {(x_1, y_1), ..., (x_N, y_N)} 线性可分,即存在超平面 w_opt·x + b_opt = 0 将所有数据正确分类。令:

R = max_i ||x_i||  (最大样本范数)
γ = min_i y_i(w_opt·x_i + b_opt) / ||w_opt||  (最小间隔)

则感知机算法的误分类次数 k 满足:

k ≤ (R / γ)^2

2.4.2 定理的意义

收敛性保证:只要数据线性可分,感知机一定能找到 separating hyperplane

误分类次数上界:上界与样本数 N 无关,只与数据的几何性质有关

学习率无关:上界不依赖于学习率 η(因为 η 可以归一化到 w 中)

间隔的重要性:间隔 γ 越大,收敛越快

python
import numpy as np

def novikoff_bound(X, y):
    """计算 Novikoff 定理的误分类次数上界"""
    # 计算 R
    R = np.max(np.linalg.norm(X, axis=1))
    
    # 找到最优超平面(用SVM的思路近似)
    # 这里简单用感知机收敛后的结果
    model = Perceptron(lr=0.1, n_iter=1000)
    model.fit(X, y)
    
    # 计算间隔 γ
    margins = y * (X @ model.w + model.b) / np.linalg.norm(model.w)
    gamma = np.min(margins)
    
    bound = (R / gamma) ** 2
    return R, gamma, bound

# 示例
np.random.seed(42)
X_pos = np.random.randn(20, 2) + np.array([2, 2])
X_neg = np.random.randn(20, 2) + np.array([-2, -2])
X = np.vstack([X_pos, X_neg])
y = np.array([1] * 20 + [-1] * 20)

R, gamma, bound = novikoff_bound(X, y)
print(f"R = {R:.4f}")
print(f"γ = {gamma:.4f}")
print(f"误分类次数上界 = {bound:.0f}")

2.5 感知机的局限性

2.5.1 异或问题

感知机最大的局限性是不能解决异或(XOR)问题。XOR函数的真值表为:

x1  x2  y
 0   0   0
 0   1   1
 1   0   1
 1   1   0

这四个点不是线性可分的——不存在一条直线能将 (0,0) 和 (1,1) 与 (0,1) 和 (1,0) 分开。

python
import numpy as np

# XOR 数据
X_xor = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y_xor = np.array([-1, 1, 1, -1])  # 用 +1/-1 编码

# 尝试用感知机学习 XOR
model = Perceptron(lr=0.1, n_iter=1000)
model.fit(X_xor, y_xor)

print(f"XOR 预测: {model.predict(X_xor)}")
print(f"真实标签: {y_xor}")
print(f"准确率: {np.mean(model.predict(X_xor) == y_xor):.2f}")
print("感知机无法学习XOR函数!")

2.5.2 多层感知机的解决方案

XOR问题的解决方案是多层感知机(Multi-Layer Perceptron, MLP)。通过引入隐藏层和非线性激活函数,多层感知机可以学习任意复杂的非线性函数。

python
import numpy as np

class MLP:
    """简单的两层感知机,可以解决XOR问题"""
    def __init__(self, input_dim=2, hidden_dim=4, lr=0.5):
        self.W1 = np.random.randn(input_dim, hidden_dim) * 0.5
        self.b1 = np.zeros(hidden_dim)
        self.W2 = np.random.randn(hidden_dim, 1) * 0.5
        self.b2 = np.zeros(1)
        self.lr = lr
    
    def sigmoid(self, z):
        return 1 / (1 + np.exp(-np.clip(z, -500, 500)))
    
    def forward(self, X):
        self.z1 = X @ self.W1 + self.b1
        self.a1 = self.sigmoid(self.z1)
        self.z2 = self.a1 @ self.W2 + self.b2
        self.a2 = self.sigmoid(self.z2)
        return self.a2
    
    def fit(self, X, y, n_iter=5000):
        y = y.reshape(-1, 1)
        for _ in range(n_iter):
            # 前向传播
            output = self.forward(X)
            
            # 反向传播
            dz2 = output - y
            dW2 = self.a1.T @ dz2 / len(y)
            db2 = np.mean(dz2, axis=0)
            
            da1 = dz2 @ self.W2.T
            dz1 = da1 * self.a1 * (1 - self.a1)
            dW1 = X.T @ dz1 / len(y)
            db1 = np.mean(dz1, axis=0)
            
            # 更新参数
            self.W2 -= self.lr * dW2
            self.b2 -= self.lr * db2
            self.W1 -= self.lr * dW1
            self.b1 -= self.lr * db1
        
        return self
    
    def predict(self, X):
        return (self.forward(X) >= 0.5).astype(int).flatten()

# 用 MLP 解决 XOR
X_xor = np.array([[0, 0], [0, 1], [1, 0], [1, 1]], dtype=float)
y_xor = np.array([0, 1, 1, 0])

mlp = MLP(input_dim=2, hidden_dim=4, lr=1.0)
mlp.fit(X_xor, y_xor, n_iter=5000)

print(f"MLP XOR 预测: {mlp.predict(X_xor)}")
print(f"真实标签: {y_xor}")
print(f"准确率: {np.mean(mlp.predict(X_xor) == y_xor):.2f}")

三、实战案例

3.1 感知机在鸢尾花数据集上的应用

python
import numpy as np
from sklearn.datasets import load_iris

# 加载数据,只取前两类(二分类)
iris = load_iris()
X = iris.data[:100, :2]  # 只用前两个特征
y = np.where(iris.target[:100] == 0, -1, 1)

# 标准化
X = (X - X.mean(axis=0)) / X.std(axis=0)

# 训练感知机
model = Perceptron(lr=0.1, n_iter=100)
model.fit(X, y)

print(f"最终参数: w={model.w}, b={model.b:.4f}")
print(f"训练准确率: {np.mean(model.predict(X) == y):.4f}")
print(f"误分类点数: {np.sum(model.predict(X) != y)}")

# 观察收敛过程
for i, h in enumerate(model.history[:10]):
    print(f"第{i+1}轮: 误分类={h['misclassified']}, w=[{h['w'][0]:.4f}, {h['w'][1]:.4f}], b={h['b']:.4f}")

3.2 学习率对收敛的影响

python
import numpy as np

def test_learning_rates():
    """测试不同学习率对感知机收敛的影响"""
    np.random.seed(42)
    
    # 生成线性可分数据
    X_pos = np.random.randn(50, 2) + np.array([2, 2])
    X_neg = np.random.randn(50, 2) + np.array([-1, -1])
    X = np.vstack([X_pos, X_neg])
    y = np.array([1] * 50 + [-1] * 50)
    
    learning_rates = [0.001, 0.01, 0.1, 1.0, 10.0]
    
    print(f"{'学习率':>10} {'收敛轮数':>10} {'最终w':>20} {'最终b':>10}")
    print("-" * 55)
    
    for lr in learning_rates:
        model = Perceptron(lr=lr, n_iter=1000)
        model.fit(X, y)
        n_epochs = len(model.history)
        w_str = f"[{model.w[0]:.2f}, {model.w[1]:.2f}]"
        print(f"{lr:>10.3f} {n_epochs:>10} {w_str:>20} {model.b:>10.2f}")

test_learning_rates()

3.3 感知机决策边界可视化

python
import numpy as np

def visualize_decision_boundary():
    """可视化感知机的决策边界形成过程"""
    np.random.seed(42)
    
    # 生成数据
    X_pos = np.random.randn(30, 2) + np.array([2, 2])
    X_neg = np.random.randn(30, 2) + np.array([-1, -1])
    X = np.vstack([X_pos, X_neg])
    y = np.array([1] * 30 + [-1] * 30)
    
    model = Perceptron(lr=0.5, n_iter=100)
    model.fit(X, y)
    
    # 输出决策边界方程
    w1, w2 = model.w
    b = model.b
    print(f"决策边界: {w1:.4f}*x1 + {w2:.4f}*x2 + {b:.4f} = 0")
    print(f"即 x2 = {-w1/w2:.4f}*x1 + {-b/w2:.4f}")
    
    # 计算间隔
    margins = y * (X @ model.w + model.b) / np.linalg.norm(model.w)
    print(f"最小间隔: {np.min(margins):.4f}")
    print(f"平均间隔: {np.mean(margins):.4f}")
    
    # 支持向量(距离决策边界最近的点)
    sv_idx = np.argmin(np.abs(margins))
    print(f"最近点: X[{sv_idx}] = {X[sv_idx]}, 间隔 = {margins[sv_idx]:.4f}")

visualize_decision_boundary()

四、本章小结

第2章介绍了感知机——最简单的线性分类模型:

模型:f(x) = sign(w·x + b),几何上是在特征空间中寻找一个分离超平面。

策略:最小化误分类点到超平面的总距离。这个损失函数是关于参数连续可导的(在误分类区域),可以用梯度下降法优化。

算法:随机梯度下降法,每次遇到误分类点时更新参数。原始形式和对偶形式是等价的,对偶形式适合加速计算。

收敛性:Novikoff定理保证了线性可分时的有限步收敛,上界为 (R/γ)^2。

局限性:感知机只能处理线性可分问题,不能解决XOR等非线性问题。多层感知机通过引入隐藏层和非线性激活函数克服了这一局限。

感知机的核心思想——通过梯度下降自动调整参数——是贯穿整个机器学习和深度学习的核心方法论。


五、思考与练习

思考题

感知机的解为什么不唯一?不同的初始参数和学习率会导致不同的解吗?

如果把感知机的 sign 函数换成 sigmoid 函数,会发生什么变化?这与逻辑斯谛回归有什么关系?

Novikoff定理中的间隔 γ 对收敛速度有什么影响?间隔越大越好还是越小越好?

感知机的对偶形式在什么情况下比原始形式更高效?

如何用感知机实现多分类?

编程练习

从零实现感知机算法,在鸢尾花数据集(二分类)上测试,并可视化决策边界的形成过程。

实现感知机的对偶形式,对比原始形式和对偶形式的收敛速度和最终参数。

实现多层感知机(MLP),在XOR数据上验证其非线性分类能力。调整隐藏层大小,观察对学习效果的影响。


2/12感知机
0%