2.1 感知机
看模型的范式 | 多元线性回归 |
---|---|
问题是什么 | 线性、分类问题 |
模型是什么 | 线性模型 |
优化指标 | 误分类点到分离超平面的距离 |
求解方法 | 随机梯度下降法 |
评价模型 |
感知机(perceptron)是二分类的线性分类模型,属于监督学习算法。输入为实例的特征向量,输出为实例的类别(取+1和-1)。感知机旨在求出将输入空间中的实例划分为两类的分离超平面。为求得超平面,感知机导入了基于误分类的损失函数,利用随机梯度下降法对损失函数进行最优化求解。感知机具有简单而易于实现的优点,分为原始形式和对偶形式。感知机预测是用学习得到的感知机模型对新的实例进行预测的,因此属于判别模型。感知机是神经网络和支持向量机的基础。
如果训练数据集是线性可分的,则感知机一定能求得分离超平面。如果是非线性可分的数据,则无法获得超平面。
假设训练数据集为$D={(x_{i},y_{i})}{i=1}^{m}$, 其中, $x{i}\in\mathbf{X}\subseteq\mathbf{R}^{n}$, $y_{i}\in\mathbf{Y}={+1,-1}$ 感知机模型为: \(f(x)=\mathrm{sign}(w\cdot x+b)\) 其中, $w$和$b$称为感知机模型参数, $w\in\mathbf{R}^{n}$叫做权重或者权值向量, $b\in\mathbf{R}$叫做偏置。$w\cdot x$表示$w$和$x$的内积。$sign$函数是符号函数: \(\mathrm{sign}(x)=\begin{cases}+1, & x\geq 0\\-1, & x<0\end{cases}\) 感知机模型的其中一个超平面是: \(w\cdot x+b=0\) 其中$w$是超平面的法向量,$b$是超平面的截距。这个超平面将样本点分为正负两类。即对所有$y_i=+1$的样本,都有$w\cdot x_i+b>0$时;对所有$y_i=-1$的样本,都有$w\cdot x_i+b<0$
假设训练数据集是线性可分的,为了找出一个能够将训练数据集正实例点和负实例点完全正确分开的超平面,即确定感知机模型参数$w $、$b$,需要确定一个学习策略,即定义(经验)损失函数,并将损失函数极小化。
损失函数的一个自然选择是误分类点的总数,但是这样的函数不是连续可导函数,不易优化。因此感知机采用的损失函数是误分类点到超平面的总距离
误分类点$x_i$到超平面$S$的距离是
\(-\frac1{\|w\|}y_i\left(w\cdot x_i+b\right)\) 那么,所有误分类点到超平面$S$ 的总距离为: \(-\frac1{\|w\|}\sum_{x_i\in M}y_i\left(w\cdot x_i+b\right)\)
不考虑$\frac1{ | w | }$,就得到感知机学习的损失函数: |
\(L(w,b)=-\sum_{x_i\in M}y_i\:(w\cdot x_i+b)\) 感知机学习模型是误分类驱动的,以下采用随机梯度下降法(SGD)来寻求这样的$w$和$b$。
- 取一个超平面,即任取一个$w_{0}$ 和 $b_{0}$
- 此时可确定一个与$w_0$和$b_0$有关的误分类点集$M$。若$M$为空则学习已完成。否则,经计算
- 任取步长$\eta\epsilon(0,1]$ (一般取1),随机选择一个误分类点$(x_{j_1},y_{j_1})$,对$w_0$和$b_0$进行更新
此时转回Step 2,若$M$仍然非空,则循环操作
下面要介绍的对偶形式是对算法执行速度的优化。
每次梯度的迭代都是选择的一个样本来更新$w$和$b$向量。最终经过若干次的迭代得到最终的结果。对于从来都没有误分类过的样本,它选择参与迭代$w$和$b$修改的次数是0,对于被多次误分类而更新的样本,它参与$w$和$b$迭代修改的次数假设为$n_i$ ,则$w$和$b$关于$(x_i,y_i)$的增量分别为$\alpha_iy_ix_i$和$\alpha_iy_i$,这里$\alpha_i=n_i\eta$。如果令$w$向量初始值为0向量,这样我们最后学习到的的$w$和$b$可以分别表示为: \(w=\sum_{x_i\in M}\eta y_ix_i=\sum_{i=1}^n\alpha_iy_ix_i \\ b=\sum_{x_i\in M}\eta y_i=\sum_{i=1}^n\alpha_iy_i\) 这样的话,在每一步判断误分类条件的地方,我们用$y_i\left(\sum_{j=1}^n\alpha_jy_jx_j\cdot x_i+b\right)\leq0$来判断误分类。这个判断误分类的形式里面是计算两个样本$x_i$ 和$x_j$,而且这个内积计算的结果在下面的迭代次数中可以重用。如果我们事先用矩阵运算计算出所有的样本之间的内积,那么在算法运行时,仅仅一次的矩阵内积运算比多次的循环计算省时。计算量最大的判断误分类这儿就省下了很多的时间,这也是对偶形式的感知机模型比原始形式优的原因。 对偶形式中训练实例仅以内积的形式出现,为了减少计算量,我们可以预先将训练集样本间的内积计算出来,也就是Gram矩阵: \(G=\left[x_i,x_j\right]_{m\times m}\) 对偶形式的算法描述:
给定训练集,其中$x_i\in\mathbb{R}^n$,$y_i\in\mathbb{R}$,学习率 $\eta=1$为例,输出$b$,$\boldsymbol{\alpha}^T=(\alpha_1,\cdotp\cdotp\cdotp,\alpha_n)$,感知机模型$f(\boldsymbol x)=\sum_j=1^n(\alpha_jy_j\boldsymbol{x}_j^T\boldsymbol{x}+b)$
-
$\alpha\leftarrow\mathbf{0},~b\leftarrow0$
-
在训练集中选取数据(x,y,y)
-
如果$y_i\cdot\Sigma_{j=1}^n(\alpha_jy_j\boldsymbol{x}_j^T\boldsymbol{x}_i+b)<0$,表明所选的点是误分类点,这时对$\alpha_i$和$b$进行更新
转至Step 2,直到没有误分类点
参考:
李航 2012 统计学习方法
机器学习算法系列(一)- 感知器学习算法(PLA) - 机器学习算法系列 - SegmentFault 思否
代码示例:
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 生成线性可分数据集
np.random.seed(42) # 固定随机种子保证可复现
# 正类样本:分布在直线 y = x + 1 附近(均值偏移)
X_pos = np.random.randn(50, 2) + np.array([1, 1]) # 50个正样本,均值(1,1)
y_pos = np.ones(50) # 标签+1
# 负类样本:分布在直线 y = x - 1 附近(均值偏移)
X_neg = np.random.randn(50, 2) + np.array([-1, -1]) # 50个负样本,均值(-1,-1)
y_neg = -np.ones(50) # 标签-1
# 合并数据集
X = np.concatenate([X_pos, X_neg]) # 特征矩阵(100×2)
y = np.concatenate([y_pos, y_neg]) # 标签向量(100×1)
# 感知机模型定义
class Perceptron:
def __init__(self, learning_rate=0.01, max_iter=1000):
self.lr = learning_rate # 学习率(控制参数更新步长)
self.max_iter = max_iter # 最大迭代次数(防止无限循环)
self.w = None # 权重向量(维度=特征数)
self.b = None # 偏置项
self.iterations = 0 # 实际迭代次数
def fit(self, X, y):
"""训练模型(核心逻辑)"""
n_samples, n_features = X.shape
# 初始化参数(权重全0,偏置0)
self.w = np.zeros(n_features)
self.b = 0.0
for _ in range(self.max_iter):
misclassified = 0 # 记录本轮误分类样本数
for i in range(n_samples):
# 计算当前样本的预测值(sign(w·x + b))
linear_output = np.dot(X[i], self.w) + self.b
y_pred = np.sign(linear_output)
# 若误分类则更新参数
if y_pred != y[i]:
# 更新规则:w = w + η*y*x;b = b + η*y
self.w += self.lr * y[i] * X[i]
self.b += self.lr * y[i]
misclassified += 1
self.iterations += 1
# 提前终止条件:本轮无任何误分类
if misclassified == 0:
print(f"训练完成!迭代次数:{self.iterations}")
break
else:
print(f"警告:达到最大迭代次数 {self.max_iter},未完全收敛")
def predict(self, X):
"""预测新样本的类别"""
linear_output = np.dot(X, self.w) + self.b
return np.sign(linear_output)
# 3. 模型训练与评估
# 初始化感知机(学习率0.1,最大迭代1000次)
clf = Perceptron(learning_rate=0.1, max_iter=1000)
# 训练模型
clf.fit(X, y)
# 输出训练结果
print("\n训练得到的参数:")
print(f"权重 w: {clf.w.round(4)}") # 保留4位小数
print(f"偏置 b: {clf.b.round(4)}")
# ==================== 4. 结果可视化 ====================
# 绘制原始样本点
plt.figure(figsize=(8, 6))
plt.scatter(X_pos[:, 0], X_pos[:, 1], c='r', s=50, label='正类 (+1)')
plt.scatter(X_neg[:, 0], X_neg[:, 1], c='b', s=50, label='负类 (-1)')
# 绘制决策边界(w·x + b = 0)
w1, w2 = clf.w
b = clf.b
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
# 生成网格点并预测类别
xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max, 100),
np.linspace(x2_min, x2_max, 100))
Z = clf.predict(np.c_[xx1.ravel(), xx2.ravel()])
Z = Z.reshape(xx1.shape)
# 填充决策区域颜色
plt.contourf(xx1, xx2, Z, alpha=0.2, cmap=plt.cm.Paired)
# 绘制决策边界直线
decision_boundary = (-w1 * xx1 - b) / w2
plt.plot(xx1[0], decision_boundary[0], 'k--', linewidth=2, label='决策边界')
plt.xlabel('特征1')
plt.ylabel('特征2')
plt.title('感知机分类结果')
plt.legend()
plt.grid(alpha=0.3)
plt.show()