感知机
重新学习了下感知机的基础,自己实现了一遍代码,手算了下面的更新参数的过程,对感知机模型的了解更深入了。感知机学习的对偶形式还没有看完。
导师上次批评的基础是不是就是指这种,但是这种东西是否考虑的太深入了,不需要了解这么多内容,会用就可以,所以在犹豫是否像下面这样更新《统计学习方法》后续章节(整理要花好久的时间( •̀ ω •́ )✧)。论文任务依旧在压着(┬┬﹏┬┬),还是继续快速看完书,论文也不能落下<( ̄︶ ̄)↗[GO!]
感知机学习是机器学习中的一种经典算法,用于线性可分数据的分类,是二分类的线性分类模型,输入时实例的特征向量,输出可以取+1 和 -1二值。
例子
以下是一个简单的例子,帮助理解感知机学习的过程:
假设我们有一个二维平面上的数据集,分为两类:
- 类别 +1 (正类):点 (2,3) 和 (3,4)
- 类别 −1 (负类):点 (1,1) 和 (2,1)
目标是学习一个直线分类器,将这两类分开。感知机模型的假设形式为:
其中:
- 是感知机的权重;
- 是偏置;
- 是符号函数,用于确定分类。
感知机学习步骤
初始化参数
初始化。遍历样本并更新参数
对于每个样本 和其对应的标签 y,根据以下规则更新权重和偏置:其中, 是学习率(假设)。
循环更新直到分类正确
不断重复以上更新,直到所有样本分类正确。
第1轮更新
初始参数:
遍历样本并逐一更新:
样本 (2,3),y=+1:
分类错误,更新参数:
样本 (1,1),y=−1:
z=2⋅1+3⋅1+1=6, y′=sign(6)=+1
预测错误,更新参数:
更新后参数为:
样本 (3,4),y=+1(3, 4), y = +1:
z=1⋅3+2⋅4+0=11, y′=sign(11)=+1
预测正确,无需更新。
样本 (2,1),y=−1(2, 1), y = -1:
z=1⋅2+2⋅1+0=4, y′=sign(4)=+1
预测错误,更新参数:
更新后参数为:
第2轮遍历
样本 (2,3),y=+1(2, 3), y = +1:
预测正确,无需更新。
样本 (1,1),y=−1(1, 1), y = -1:
预测正确,无需更新。
样本 (3,4),y=+1(3, 4), y = +1:
预测正确,无需更新。
样本 (2,1),y=−1(2, 1), y = -1:
预测正确,无需更新。
在 默认情况下,当 z=0 时,约定 sign(0)=+1。
分类器收敛
在第2轮遍历后,所有样本均分类正确,最终分类器参数为:
最终分类器:
对应的分割线为:
更新权重过程的理解
更新权重的过程实际上是将分类超平面向误分类点的一侧移动,以减少误分类样本的符号距离。具体来说:
误分类点的位置:
- 对于一个误分类点,其符号距离 。这意味着当前分类超平面无法正确划分该点(例如,正类点在超平面负侧,或负类点在超平面正侧)。
权重更新公式:
- 更新公式为: 其中,η\etaη 是学习率,是标签, 是误分类样本的特征。
更新的直观含义:
- 如果(正类点被误分类为负类),更新方向为正向移动,将超平面向该点靠近。
- 如果 (负类点被误分类为正类),更新方向为负向移动 ,使超平面远离该点。
这种调整使得误分类点在更新后更接近正确侧(即符号距离 增大)。
偏置的作用:
偏置更新公式为:
- 这相当于平移超平面整体,而不改变其方向。
- 偏置调整确保误分类点在更新后更加贴近正确分类侧。
整体效果:
- 权重更新推动超平面朝向修正误分类点的方向移动。
- 通过逐步调整,最终的分类超平面会使误分类样本越来越少,直到所有样本正确分类(或达到停止条件)。
学习策略损失函数
在上面的例子中,并没有使用损失函数。在感知机学习中,损失函数的选择决定了学习策略的优化目标。感知机使用的是感知机损失函数,这是专为线性分类问题设计的一种简单损失函数。
第一种损失函数:
感知机的损失函数为:
其中:
- M是所有被分类错误的样本集合;
- 是样本 i 的真实标签;
- 是样本的预测得分。
关键点:只有分类错误的样本才会产生损失。
第二种损失函数:
如果感知机的损失函数是所有误分类点到超平面 S的总距离,那么损失函数的形式会与几何距离相关。
基于误分类点的总距离损失函数
如果损失函数是误分类点到超平面的总距离,则可以写为:
其中:
- M 是所有被误分类的样本集合;
- 表示样本 i 被误分类;
- 分子部分 是点 到超平面的符号距离;
- 分母 使得距离规范化为几何距离。
最终代码实现
import numpy as np
class PerceptronWithPerceptronLoss:
def __init__(self, input_dim, learning_rate=0.01):
self.w = np.random.randn(input_dim) * 0.01 # 初始化随机权重
self.b = np.random.randn() * 0.01 # 随机初始化偏置
self.lr = 0.01
# margin: 即使所有样本都被正确分类,也会进行少量训练迭代
# 这种改动可以让模型在早期训练阶段对靠近分界面的样本进行优化
# 提升模型的决策边界稳定性。
def compute_loss(self, X, y, margin=1e-5):
score = y * (np.dot(X, self.w)+self.b)
mask = score <= margin
total_loss = -np.sum(score[mask])
return total_loss, mask
def update_parameters(self, X, y, mask):
misclassified_X = X[mask]
misclassified_y = y[mask]
self.w += np.sum(misclassified_y[:, None]*misclassified_X, axis=0) * self.lr
self.b += np.sum(misclassified_y)*self.lr
print(self.w, self.b)
def fit(self, X, y, epochs=100, tolerance=1e-4):
prev_loss = float('inf')
for epoch in range(epochs):
loss, mask = self.compute_loss(X, y)
if loss < tolerance or abs(prev_loss - loss) < tolerance:
print(f"Converged at epoch {epoch}")
break
self.update_parameters(X, y, mask)
prev_loss = loss
print(f"Epoch {epoch+1}, Loss {loss}")
def predict(self, X):
return np.sign(np.dot(X, self.w)+self.b)
X = np.array([[2, 3], [1, 1], [3, 4], [2, 1]])
y = np.array([1, -1, 1, -1])
model = PerceptronWithPerceptronLoss(input_dim=2, learning_rate=0.01)
model.fit(X, y, epochs=50)
print("Final weights:", model.w)
print("Final bias:", model.b)
predicts = model.predict(X)
print("Predict:", predicts)
这个实现对原始感知机算法做了一些改进,尤其是引入了 margin 参数 来优化模型训练。这可以在训练初期增强模型对靠近决策边界的样本的敏感性,使得模型收敛到更稳健的决策边界。以下是代码运行的关键点和解释:
关键点解析
1. 随机初始化权重和偏置
self.w = np.random.randn(input_dim) * 0.01
self.b = np.random.randn() * 0.01
- 权重 w 和偏置 b被随机初始化为较小值,通常服从标准正态分布再乘以 0.01。
- 这样的初始化避免了原始实现中全零初始化导致的梯度为零问题,同时确保初始状态具有足够的小扰动。
2. 加入 margin 参数
def compute_loss(self, X, y, margin=1e-5):
score = y * (np.dot(X, self.w) + self.b) # 误分类之后才会出现负数的情况
mask = score <= margin # 表示误分类的位置
total_loss = -np.sum(score[mask])
return total_loss, mask
- 作用:
- 即使所有样本都被正确分类(score > 0),margin 的存在确保决策边界仍然对靠近边界的样本进行优化。
- 这可以提升模型的泛化能力,使得决策边界更加稳健。
3. 参数更新
def update_parameters(self, X, y, mask):
misclassified_X = X[mask]
misclassified_y = y[mask]
self.w += np.sum(misclassified_y[:, None] * misclassified_X, axis=0) * self.lr
self.b += np.sum(misclassified_y) * self.lr
- 作用:
- 仅对误分类或边界附近的样本(由 maskmask 定义)进行权重和偏置更新,提升计算效率。
- 更新公式遵循感知机规则,即朝误分类样本方向调整决策边界。
4. 训练收敛条件
if loss < tolerance or abs(prev_loss - loss) < tolerance:
print(f"Converged at epoch {epoch}")
break
- 作用:
- 当总损失 loss低于设定阈值 tolerance,或者当前损失和上一轮损失的变化幅度小于阈值时,认为模型已收敛。
- 这种方法可以防止训练陷入无限迭代。
5. 预测
def predict(self, X):
return np.sign(np.dot(X, self.w) + self.b)
- 作用:
- 根据当前权重和偏置计算每个样本的得分,并通过符号函数 sign(z) 将得分转换为类别标签。
运行结果分析
初始权重和偏置: 随机初始化导致每次运行结果可能不同,但 margin 的引入确保即便初始值不同,最终模型的表现会较为稳健。
训练过程:
- 输出的权重 w 和偏置 b 会逐渐调整,直到所有样本正确分类,或者靠近边界的样本满足 margin 要求。
- 如果所有样本早期就满足要求,训练过程可能会提前终止。
预测结果:
Predict: [ 1. -1. 1. -1.]
- 说明模型成功学习到数据的线性分割规则。
改进后的优点
- 稳健性提升: margin 确保模型在早期对靠近决策边界的样本进行优化,增强了分界面的鲁棒性。
- 防止陷入局部最优: 随机初始化避免权重和偏置均为零时模型无法更新的问题。
- 高效性: 每次仅更新误分类样本,有效减少无关计算。
总结
- 每次随机初始化会导致训练结果不同,主要因为初始参数影响了优化路径和决策边界的位置。
- 可以通过固定随机种子、正则化、多次训练取平均等方法减小这种随机性。
- 如果数据线性可分,不同的随机初始化只会影响最终分界面的具体位置,但所有分界面都能正确分类数据。如果数据不可线性分割,随机初始化的影响会更大,因此可以考虑正则化或改用其他方法。