头图

感知机

重新学习了下感知机的基础,自己实现了一遍代码,手算了下面的更新参数的过程,对感知机模型的了解更深入了。感知机学习的对偶形式还没有看完。

导师上次批评的基础是不是就是指这种,但是这种东西是否考虑的太深入了,不需要了解这么多内容,会用就可以,所以在犹豫是否像下面这样更新《统计学习方法》后续章节(整理要花好久的时间( •̀ ω •́ )✧)。论文任务依旧在压着(┬┬﹏┬┬),还是继续快速看完书,论文也不能落下<( ̄︶ ̄)↗[GO!]

感知机学习是机器学习中的一种经典算法,用于线性可分数据的分类,是二分类的线性分类模型,输入时实例的特征向量,输出可以取+1 和 -1二值。

例子

以下是一个简单的例子,帮助理解感知机学习的过程:

假设我们有一个二维平面上的数据集,分为两类:

  • 类别 +1 (正类):点 (2,3) 和 (3,4)
  • 类别 −1 (负类):点 (1,1) 和 (2,1)

目标是学习一个直线分类器,将这两类分开。感知机模型的假设形式为:

$$y = \text{sign}(w_1x_1 + w_2x_2 + b)$$

其中:

  • \(w_1\) , \( w_2 \)是感知机的权重;
  • \(b\) 是偏置;
  • \({sign}(\cdot)\) 是符号函数,用于确定分类。

感知机学习步骤

  1. 初始化参数
    初始化$$w_1 = 0, w_2 = 0, b = 0$$。
  2. 遍历样本并更新参数
    对于每个样本 \((x_1, x_2)\) 和其对应的标签 y,根据以下规则更新权重和偏置:

    $$w \leftarrow w + \eta y x$$

    $$b \leftarrow b + \eta y$$

    其中,\(\eta\) 是学习率(假设\(\eta = 1\))。

  3. 循环更新直到分类正确
    不断重复以上更新,直到所有样本分类正确。

第1轮更新

初始参数:

$$w_1 = 0, \; w_2 = 0, \; b = 0$$

遍历样本并逐一更新:

  1. 样本 (2,3),y=+1:

    分类错误,更新参数:
    $$w_1 = 0 + 1 \cdot 2 = 2, w_2 = 0 + 1 \cdot 3 = 3, b = 0 + 1 \cdot 1 = 1$$

  2. 样本 (1,1),y=−1:

    z=2⋅1+3⋅1+1=6, y′=sign(6)=+1

    预测错误,更新参数:

    $$w_1\leftarrow w_1 + \eta y x_1 = 2 + 1 \cdot (-1) \cdot 1 = 1$$

    $$w_2\leftarrow w_2 + \eta y x_2 = 3 + 1 \cdot (-1) \cdot 1 = 2$$

    $$b \leftarrow b + \eta y = 1 + 1 \cdot (-1) = 0$$

    更新后参数为:

    $$w_1 = 1, \; w_2 = 2, \; b = 0$$

  3. 样本 (3,4),y=+1(3, 4), y = +1:

    z=1⋅3+2⋅4+0=11, y′=sign(11)=+1

    预测正确,无需更新。

  4. 样本 (2,1),y=−1(2, 1), y = -1:

    z=1⋅2+2⋅1+0=4, y′=sign(4)=+1

    预测错误,更新参数:

    $$w_1 \leftarrow w_1 + \eta y x_1 = 1 + 1 \cdot (-1) \cdot 2 = -1$$

    $$w_2 \leftarrow w_2 + \eta y x_2 = 2 + 1 \cdot (-1) \cdot 1 = 1$$

    $$b \leftarrow b + \eta y = 0 + 1 \cdot (-1) = -1$$

    更新后参数为:

    $$w_1 = -1, \; w_2 = 1, \; b = -1$$


第2轮遍历

  1. 样本 (2,3),y=+1(2, 3), y = +1:

    $$z = -1 \cdot 2 + 1 \cdot 3 - 1 = 0, \quad y' = \text{sign}(0) = +1$$

    预测正确,无需更新。

  2. 样本 (1,1),y=−1(1, 1), y = -1:

    $$z = -1 \cdot 1 + 1 \cdot 1 - 1 = -1, \quad y' = \text{sign}(-1) = -1$$

    预测正确,无需更新。

  3. 样本 (3,4),y=+1(3, 4), y = +1:

    $$z = -1 \cdot 3 + 1 \cdot 4 - 1 = 0, \quad y' = \text{sign}(0) = +1$$

    预测正确,无需更新。

  4. 样本 (2,1),y=−1(2, 1), y = -1:

    $$z = -1 \cdot 2 + 1 \cdot 1 - 1 = -2, \quad y' = \text{sign}(-2) = -1$$

    预测正确,无需更新。

默认情况下,当 z=0 时,约定 sign(0)=+1。


分类器收敛

在第2轮遍历后,所有样本均分类正确,最终分类器参数为:

$$w_1 = -1, \; w_2 = 1, \; b = -1$$

最终分类器:

$$y = \text{sign}(-x_1 + x_2 - 1)$$

对应的分割线为:

$$x_2 - x_1 - 1 = 0 \quad \text{或} \quad x_2 = x_1 + 1$$

更新权重过程的理解

更新权重的过程实际上是将分类超平面向误分类点的一侧移动,以减少误分类样本的符号距离。具体来说:

  1. 误分类点的位置

    • 对于一个误分类点,其符号距离 $$y_i (\mathbf{w}^\top \mathbf{x}_i + b) \leq 0$$。这意味着当前分类超平面无法正确划分该点(例如,正类点在超平面负侧,或负类点在超平面正侧)。
  2. 权重更新公式

    • 更新公式为: $$\mathbf{w} \leftarrow \mathbf{w} + \eta y_i \mathbf{x}_i$$ 其中,\(\eta\) 是学习率,\(y_i\)是标签,\(\mathbf{x}_i\) 是误分类样本的特征。
  3. 更新的直观含义

    • 如果\(y_i = 1\)(正类点被误分类为负类),更新方向为正向移动\(\mathbf{x}_i\),将超平面向该点靠近。
    • 如果 \(y_i = -1\)(负类点被误分类为正类),更新方向为负向移动 \(\mathbf{x}_i\),使超平面远离该点。

    这种调整使得误分类点在更新后更接近正确侧(即符号距离 \(y_i (\mathbf{w}^\top \mathbf{x}_i + b)\) 增大)。

  4. 偏置的作用

    • 偏置更新公式为:

      $$b \leftarrow b + \eta y_i$$

      • 这相当于平移超平面整体,而不改变其方向。
      • 偏置调整确保误分类点在更新后更加贴近正确分类侧。
  5. 整体效果

    • 权重更新推动超平面朝向修正误分类点的方向移动。
    • 通过逐步调整,最终的分类超平面会使误分类样本越来越少,直到所有样本正确分类(或达到停止条件)。

学习策略损失函数

在上面的例子中,并没有使用损失函数。在感知机学习中,损失函数的选择决定了学习策略的优化目标。感知机使用的是感知机损失函数,这是专为线性分类问题设计的一种简单损失函数。

第一种损失函数

感知机的损失函数为:

$$L(w, b) = -\sum_{i \in \mathcal{M}} y_i \cdot (w^\top x_i + b)$$

其中:

  • M是所有被分类错误的样本集合;
  • \(y_i\)是样本 i 的真实标签;
  • \(w^\top x_i + b\)是样本的预测得分。

关键点:只有分类错误的样本才会产生损失。

第二种损失函数

如果感知机的损失函数是所有误分类点到超平面 S的总距离,那么损失函数的形式会与几何距离相关。

基于误分类点的总距离损失函数

如果损失函数是误分类点到超平面的总距离,则可以写为:

$$L(w, b) = \sum_{i \in \mathcal{M}} \frac{-y_i (w^\top x_i + b)}{\|w\|},$$

其中:

  • M 是所有被误分类的样本集合;
  • \(y_i (w^\top x_i + b) \leq 0\)表示样本 i 被误分类;
  • 分子部分 \(-y_i (w^\top x_i + b)\)是点 \(x_i\) 到超平面的符号距离;
  • 分母 \(|w|\) 使得距离规范化为几何距离。

最终代码实现

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) 将得分转换为类别标签。

运行结果分析

  1. 初始权重和偏置: 随机初始化导致每次运行结果可能不同,但 margin 的引入确保即便初始值不同,最终模型的表现会较为稳健。
  2. 训练过程:

    • 输出的权重 w 和偏置 b 会逐渐调整,直到所有样本正确分类,或者靠近边界的样本满足 margin 要求。
    • 如果所有样本早期就满足要求,训练过程可能会提前终止。
  3. 预测结果:

    Predict: [ 1. -1.  1. -1.]
    • 说明模型成功学习到数据的线性分割规则。

改进后的优点

  • 稳健性提升: margin 确保模型在早期对靠近决策边界的样本进行优化,增强了分界面的鲁棒性。
  • 防止陷入局部最优: 随机初始化避免权重和偏置均为零时模型无法更新的问题。
  • 高效性: 每次仅更新误分类样本,有效减少无关计算。

总结

  • 每次随机初始化会导致训练结果不同,主要因为初始参数影响了优化路径和决策边界的位置。
  • 可以通过固定随机种子、正则化、多次训练取平均等方法减小这种随机性。
  • 如果数据线性可分,不同的随机初始化只会影响最终分界面的具体位置,但所有分界面都能正确分类数据。如果数据不可线性分割,随机初始化的影响会更大,因此可以考虑正则化或改用其他方法。

夨落旳尐孩
1 声望1 粉丝

该吃吃,该喝喝,啥事不往心里搁( •̀ ω •́ )✧