第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 < 02.1.1 几何解释
感知机的几何意义非常直观:在 n 维空间中找到一个超平面 `w·x + b = 0`,将正类点和负类点分开。
- 权重向量
w是超平面的法向量 - 偏置
b决定超平面与原点的距离 w·x + b > 0的点被分为正类w·x + b < 0的点被分为负类
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 是误分类点的集合。
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,使该点更可能被判为负类
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,加速内积运算。
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 / γ)^22.4.2 定理的意义
收敛性保证:只要数据线性可分,感知机一定能找到 separating hyperplane
误分类次数上界:上界与样本数 N 无关,只与数据的几何性质有关
学习率无关:上界不依赖于学习率 η(因为 η 可以归一化到 w 中)
间隔的重要性:间隔 γ 越大,收敛越快
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) 分开。
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)。通过引入隐藏层和非线性激活函数,多层感知机可以学习任意复杂的非线性函数。
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 感知机在鸢尾花数据集上的应用
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 学习率对收敛的影响
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 感知机决策边界可视化
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数据上验证其非线性分类能力。调整隐藏层大小,观察对学习效果的影响。