感知机学习的过程及代码实现

感知机

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

导师上次批评的基础是不是就是指这种,但是这种东西是否考虑的太深入了,不需要了解这么多内容,会用就可以,所以在犹豫是否像下面这样更新《统计学习方法》后续章节(整理要花好久的时间( •̀ ω •́ )✧)。论文任务依旧在压着(┬┬﹏┬┬),还是继续快速看完书,论文也不能落下<( ̄︶ ̄)↗[GO!]
优化界面的Blog:感知机
感知机学习是机器学习中的一种经典算法,用于线性可分数据的分类,是二分类的线性分类模型,输入时实例的特征向量,输出可以取+1 和 -1二值。

例子

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

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

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

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

y = sign ( w 1 x 1 + w 2 x 2 + b ) y = \text{sign}(w_1x_1 + w_2x_2 + b) y=sign(w1x1+w2x2+b)

其中:

  • w 1 , w 2 w_1, w_2 w1,w2 是感知机的权重;
  • b b b 是偏置;
  • s i g n ( ⋅ ) {sign}(\cdot) sign() 是符号函数,用于确定分类。

感知机学习步骤

  1. 初始化参数
    初始化 w 1 = 0 , w 2 = 0 , b = 0 w_1 = 0, w_2 = 0, b = 0 w1=0,w2=0,b=0

  2. 遍历样本并更新参数
    对于每个样本 ( x 1 , x 2 ) (x_1, x_2) (x1,x2) 和其对应的标签 y,根据以下规则更新权重和偏置:

    w ← w + η y x w \leftarrow w + \eta y x ww+ηyx

    b ← b + η y b \leftarrow b + \eta y bb+ηy

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

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

第1轮更新

初始参数:

w 1 = 0 ,    w 2 = 0 ,    b = 0 w_1 = 0, \; w_2 = 0, \; b = 0 w1=0,w2=0,b=0

遍历样本并逐一更新:

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

    分类错误,更新参数:
    w 1 = 0 + 1 ⋅ 2 = 2 , w 2 = 0 + 1 ⋅ 3 = 3 , b = 0 + 1 ⋅ 1 = 1 w_1 = 0 + 1 \cdot 2 = 2, w_2 = 0 + 1 \cdot 3 = 3, b = 0 + 1 \cdot 1 = 1 w1=0+12=2,w2=0+13=3,b=0+11=1

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

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

    预测错误,更新参数:

    w 1 ← w 1 + η y x 1 = 2 + 1 ⋅ ( − 1 ) ⋅ 1 = 1 w_1\leftarrow w_1 + \eta y x_1 = 2 + 1 \cdot (-1) \cdot 1 = 1 w1w1+ηyx1=2+1(1)1=1

    w 2 ← w 2 + η y x 2 = 3 + 1 ⋅ ( − 1 ) ⋅ 1 = 2 w_2\leftarrow w_2 + \eta y x_2 = 3 + 1 \cdot (-1) \cdot 1 = 2 w2w2+ηyx2=3+1(1)1=2

    b ← b + η y = 1 + 1 ⋅ ( − 1 ) = 0 b \leftarrow b + \eta y = 1 + 1 \cdot (-1) = 0 bb+ηy=1+1(1)=0

    更新后参数为:

    w 1 = 1 ,    w 2 = 2 ,    b = 0 w_1 = 1, \; w_2 = 2, \; b = 0 w1=1,w2=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 ← w 1 + η y x 1 = 1 + 1 ⋅ ( − 1 ) ⋅ 2 = − 1 w_1 \leftarrow w_1 + \eta y x_1 = 1 + 1 \cdot (-1) \cdot 2 = -1 w1w1+ηyx1=1+1(1)2=1

    w 2 ← w 2 + η y x 2 = 2 + 1 ⋅ ( − 1 ) ⋅ 1 = 1 w_2 \leftarrow w_2 + \eta y x_2 = 2 + 1 \cdot (-1) \cdot 1 = 1 w2w2+ηyx2=2+1(1)1=1

    b ← b + η y = 0 + 1 ⋅ ( − 1 ) = − 1 b \leftarrow b + \eta y = 0 + 1 \cdot (-1) = -1 bb+ηy=0+1(1)=1

    更新后参数为:

    w 1 = − 1 ,    w 2 = 1 ,    b = − 1 w_1 = -1, \; w_2 = 1, \; b = -1 w1=1,w2=1,b=1


第2轮遍历

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

    z = − 1 ⋅ 2 + 1 ⋅ 3 − 1 = 0 , y ′ = sign ( 0 ) = + 1 z = -1 \cdot 2 + 1 \cdot 3 - 1 = 0, \quad y' = \text{sign}(0) = +1 z=12+131=0,y=sign(0)=+1

    预测正确,无需更新。

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

    z = − 1 ⋅ 1 + 1 ⋅ 1 − 1 = − 1 , y ′ = sign ( − 1 ) = − 1 z = -1 \cdot 1 + 1 \cdot 1 - 1 = -1, \quad y' = \text{sign}(-1) = -1 z=11+111=1,y=sign(1)=1

    预测正确,无需更新。

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

    z = − 1 ⋅ 3 + 1 ⋅ 4 − 1 = 0 , y ′ = sign ( 0 ) = + 1 z = -1 \cdot 3 + 1 \cdot 4 - 1 = 0, \quad y' = \text{sign}(0) = +1 z=13+141=0,y=sign(0)=+1

    预测正确,无需更新。

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

    z = − 1 ⋅ 2 + 1 ⋅ 1 − 1 = − 2 , y ′ = sign ( − 2 ) = − 1 z = -1 \cdot 2 + 1 \cdot 1 - 1 = -2, \quad y' = \text{sign}(-2) = -1 z=12+111=2,y=sign(2)=1

    预测正确,无需更新。

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


分类器收敛

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

w 1 = − 1 ,    w 2 = 1 ,    b = − 1 w_1 = -1, \; w_2 = 1, \; b = -1 w1=1,w2=1,b=1

最终分类器:

y = sign ( − x 1 + x 2 − 1 ) y = \text{sign}(-x_1 + x_2 - 1) y=sign(x1+x21)

对应的分割线为:

x 2 − x 1 − 1 = 0 或 x 2 = x 1 + 1 x_2 - x_1 - 1 = 0 \quad \text{或} \quad x_2 = x_1 + 1 x2x11=0x2=x1+1

更新权重过程的理解

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

  1. 误分类点的位置

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

    • 更新公式为: w ← w + η y i x i \mathbf{w} \leftarrow \mathbf{w} + \eta y_i \mathbf{x}_i ww+ηyixi 其中,η 是学习率, y i y_i yi是标签, x i \mathbf{x}_i xi 是误分类样本的特征。
  3. 更新的直观含义

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

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

  4. 偏置的作用

    • 偏置更新公式为:

      b ← b + η y i b \leftarrow b + \eta y_i bb+ηyi

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

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

学习策略损失函数

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

第一种损失函数

感知机的损失函数为:

L ( w , b ) = − ∑ i ∈ M y i ⋅ ( w ⊤ x i + b ) L(w, b) = -\sum_{i \in \mathcal{M}} y_i \cdot (w^\top x_i + b) L(w,b)=iMyi(wxi+b)

其中:

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

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

第二种损失函数

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

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

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

L ( w , b ) = ∑ i ∈ M − y i ( w ⊤ x i + b ) ∥ w ∥ , L(w, b) = \sum_{i \in \mathcal{M}} \frac{-y_i (w^\top x_i + b)}{\|w\|}, L(w,b)=iMwyi(wxi+b),

其中:

  • M 是所有被误分类的样本集合;
  • y i ( w ⊤ x i + b ) ≤ 0 y_i (w^\top x_i + b) \leq 0 yi(wxi+b)0表示样本 i 被误分类;
  • 分子部分 − y i ( w ⊤ x i + b ) -y_i (w^\top x_i + b) yi(wxi+b)是点 x i x_i xi 到超平面的符号距离;
  • 分母 ∥ w ∥ \|w\| 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 确保模型在早期对靠近决策边界的样本进行优化,增强了分界面的鲁棒性。
  • 防止陷入局部最优: 随机初始化避免权重和偏置均为零时模型无法更新的问题。
  • 高效性: 每次仅更新误分类样本,有效减少无关计算。

总结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜猫程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值