《统计学习方法》之感知机代码实现(原始形式和对偶形式)

本文介绍了感知机作为线性分类模型的概念,通过一个二分类问题阐述其工作原理。感知机通过寻找最优超平面进行数据划分,并利用梯度下降法最小化损失函数。文中提及了原始形式的感知机算法及其更新规则,还探讨了模型训练过程中的‘感知’含义。同时,提供了完整的感知机代码实现。

    下图是一个典型的二分类问题,如果现在需要建立模型来根据数据点的 x 1 , x 2 x_1,x_2 x1,x2的值对它所属的类别进行判定该怎么做?
二分类
    通过观察,被标为叉的数据点的 x 1 , x 2 x_1,x_2 x1,x2的取值均较大,那是否可以通过计算 x 1 + x 2 x_1+x_2 x1+x2的值来对数据进行分类,答案是可以的,这时模型就可以表示为
f ( x ) = s i g n ( x 1 + x 2 − k ) f(x) = sign(x_1+x_2-k) f(x)=sign(x1+x2k)
s i g n ( x ) = { X ( 被 标 记 为 叉 ) x > = 0 O ( 被 标 记 为 圈 ) x < 0 sign (x) = \begin{cases} X(被标记为叉) & x >= 0 \\ O(被标记为圈) & x < 0 \end{cases} sign(x)={X()O()x>=0x<0
    这时模型中的 k k k值是未知的,我们可从已有的数据点中学习k,具体来说就是计算出所有数据点对应的 x 1 + x 2 x_1+x_2 x1+x2,被标为圆圈的数据点对应的和的最大值为 k m i n k_{min} kmin,被标为叉的数据点对应的和的最小值为 k m a x k_{max} kmax,这样如果我们将模型中的k设定为区间 ( k m i n , k m a x ] (k_{min},k_{max}] (kmin,kmax]中的某个值时,我们的模型就能够成功预测所有已知的样本点的类别。以上得到的模型就是一个感知机模型。这个例子中我们是简单粗暴地通过直接观察数据点分布的方法确定了模型中各个数据维度对应的权重,通过计算确定了k可能的取值的范围。如果数据是多维的,难以直接观察时,我们就需要更科学的方法进行学习。
    如上所述,感知机模型,就是一种线性分类模型,模型对应的是一个能够将数据点进行线性划分的分离超平面。
    下面是模型的函数表示,f(x)即为模型对应的判别函数,w,b为模型的参数,sign为符号函数,非负数输出+1,否则为-1。
f ( x ) = s i g n ( w T x + b ) f(x) = sign(w^Tx+b) f(x)=sign(wTx+b)
    如我们上面的例子所述,通常情况下对于一个线性可分的数据集,我们的K的取值可以有无穷多个,也就是存在多个感知机模型能够线性分割数据集,怎么确定哪个是我们最想要的?这时我们就需要定义一种选择的策略,具体来说就是通过定义损失函数,选择那个使得损失函数最小的模型。
L = ∑ x i ∈ M 1 ∣ ∣ w ∣ ∣ ∣ w ⋅ x i + b ∣ L=\sum_{x_i \in M}\frac{1}{||w||}|w·x_i+b| L=xiMw1wxi+b
    这里的M表示的就是误分类的点组成的集合,损失函数的含义就是所有误分类的点到超平面的距离的和。定义好了损失函数然后就是通过梯度下降法最小化损失函数。求梯度之前还需做一件事就是去除L中的绝对值符号,因为y的取值只有两种可能+1或-1,对于所有误分类的点而言,y取+1时 w ⋅ x i + b w·x_i+b wxi+b为负值,y取-1时 w ⋅ x i + b w·x_i+b wxi+b为正值,所以损失函数也可表示为如下形式:
L = − ∑ x i ∈ M 1 ∣ ∣ w ∣ ∣ y i ( w ⋅ x i + b ) L=-\sum_{x_i \in M}\frac{1}{||w||}y_i(w·x_i+b) L=xiMw1yi(wxi+b)
忽略L中的 1 ∣ ∣ w ∣ ∣ \frac{1}{||w||} w1,然后求出参数的梯度可得:
∇ w L ( w , b ) = − ∑ x i ∈ M y i x i \nabla_wL(w,b)=-\sum_{x_i \in M}y_ix_i wL(w,b)=xiMyixi

∇ b L ( w , b ) = − ∑ x i ∈ M y i \nabla_bL(w,b)=-\sum_{x_i \in M}y_i bL(w,b)=xiMyi
每次更新时选取一个误分类的点,对w,b进行更新:
w ← w + η y i x i w \leftarrow w+\eta y_ix_i ww+ηyixi

b ← w + η y i b \leftarrow w+\eta y_i bw+ηyi
综合以上可得感知机算法的原始形式:
在这里插入图片描述
    为什么叫感知机?这篇博客给出了一些原因,但是我个人觉得可以把它理解为这个模型的训练是对输入数据的感知过程。因为从直观上理解,在训练过程中,当一个实例点被误分类后,超平面就会向该实例点移动,直到该实例点被正确分类,这个过程我认为就是一个感知的过程,一开始模型对空间数据的分布一无所知,在感知到数据分布后不断调整自身参数,最后得到一个能够将数据正确分类的模型。

完整代码(求解原问题和对偶问题):

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
import numpy as np


def sign(x):
    if x >= 0:
        return 1
    else:
        return -1


class Perceptron:
    # 感知机模型,支持两种学习方式,原始求解和对偶问题求解
    def __init__(self, learning_rate, max_iter=1000):
        # 设置学习率
        self.learning_rate = learning_rate
        # 设置最大的迭代轮数
        self.max_iter = max_iter
        # 权重和偏置先设为None
        self.weight = None
        self.bias = None
        # 用于对偶问题学习
        self.alpha = None

    def fit(self, X, y):
        # 根据数据训练感知机模型
        # X 每一行是一个样本
        n, m = X.shape  # 表示有n个样本,每个样本为一个m维向量
        self.weight = np.zeros(m)  # 初始化权重为0
        self.bias = 0  # 初始化偏置为0
        for i in range(self.max_iter):
            # 每一轮迭代都从头开始找,直到找到分类错误的样本
            for j in range(n):
                if (np.sum(self.weight * X[j]) + self.bias) * y[j] <= 0:  # 找到分类错误的样本
                    # 使用分类错误的样本更新权重和偏置
                    self.weight += self.learning_rate * y[j] * X[j]
                    self.bias += self.learning_rate * y[j]
                    break  # 更新完成后可以选择break,下一次又重头开始找,也可以选择不break但是这样max_iter的含义就有所不同

    def fit_dual(self, X, y):
        # 求解感知机的对偶问题
        # Question: 为什么要提出对偶问题?
        # 一种解释为:
        # 考虑m很大,n很小的情况,也就是单条数据的维度很大,而数据量很小
        # 这种情况下使用原问题求解每次判断需要计算w*x,这样计算复杂为O(m)
        # 而使用这种办法,预先计算好Gram矩阵的情况下,判断时需计算Gram[j]*self.alpha*y,O(n)
        n, m = X.shape
        # 先计算出任意两个X向量的内积,也就是Gram矩阵
        Gram = np.zeros((n, n))
        for i in range(n):
            for j in range(n):
                Gram[i][j] = np.sum(X[i] * X[j])
        # alpha矩阵初始化为全0
        # alpha[i]/self.learning_rate表示第i个样本被用于更新梯度的次数
        self.alpha = np.zeros(n)
        self.bias = 0
        for i in range(self.max_iter):
            for j in range(n):
                if (np.sum(Gram[j] * self.alpha * y) + self.bias) * y[j] <= 0:  # 分类错误
                    # 样本j被用于更新梯度就加一个learning_rate
                    # 应为alpha/learning_rate表示的才是被用于梯度更新的次数
                    self.alpha[j] += self.learning_rate
                    self.bias += self.learning_rate * y[j]
                    break
        # 通过self.alpha计算self.weight,用于预测
        # 注意这里的求和,X.T每一列表示一个样本,self.alpha和y相乘后结果的shape为(1, n)
        # self.alpha的y相乘后的结果再乘X.T可理解为对所有样本进行加权求和
        self.weight = np.sum(self.alpha * y * X.T, axis=1)

    def predict(self, X):
        pred = []
        for i in range(len(X)):
            pred.append(sign(np.sum(self.weight * X[i]) + self.bias))
        return np.array(pred)


if __name__ == "__main__":
    X = np.array([[3, 3], [4, 3], [1, 1]])
    y = np.array([1, 1, -1])
    model = Perceptron(learning_rate=1)
    # model.fit(X, y)
    model.fit_dual(X, y)
    print("参数为:", model.weight, model.bias)
    print("预测结果为:", model.predict(X))

<think>首先,用户想要查找感知机学习算法代码原始形式。用户提供了几个引用:[^1]、[^2]、[^3]、[^4]。我需要参考这些引用来回答。 回顾引用内容: - 引用[1]:来自李航《统计学习方法》例2.1,描述了一个感知机学习算法的例子,但没有直接给出代码。 - 引用[2]:介绍了感知机原始形式对偶形式,提到了梯度下降法,但没有代码。 - 引用[3]:这是MATLAB代码实现感知机学习算法的原始形式。用户明确提到这是处理李航例子的代码。 - 引用[4]:解释了感知机模型的数学公式损失函数,提到了原始形式。 用户的问题是:“我想查找感知机学习算法代码原始形式”,所以我需要提供代码。 从引用[3]中,有MATLAB代码: ```matlab %%感知机学习算法---原始形式 %%算法步骤: % 1.选取初值w0,b0.确定步长(属于(0,1]) % 2.在训练集中选取数据(xi,yi) % 3.如果yi(w*xi+b)<=0,更新w,b % w=w+学习率*yi*xi % b=b+学习率*yi % 4.转至(2),直到没有误分类 %%时间:2021.7.13 %% 思考:当预测求出的w*xi+b=0时,到底属于正确分类,还是错误分类 clc clear %%训练感知机模型 training_set=[3 4 1;3 3 1]; %训练集数据-----来自李航《统计学方法感知机的例子 training_lable=[1 1 -1]; %训练集类别-----根据需要增加测试集 study_rate=1; %学习率 w=[0;0]; %初始权重,选取的不同,得到的分离超平面也不同 b=0; %初始的超平面的截距 count=0; %每一次正确分类点个数 %iteration_count=0; %迭代次数 while count ~= size(training_set,2) %判断是否满足全部正确分类 count=0; for i=1:size(training_set,2) count = count+1; %给定满足初始正确的为1 %如果某个样本y_i(w*x_i+b)<=0,则被误分类,对wb进行相应的更新---感知机 if training_lable(1,i)*(w'*training_set(:,i)+b)<=0 w = w + study_rate*training_lable(1,i)*training_set(:,i); b = b + study_rate*training_lable(1,i); %iteration_count=iteration_count+1; %迭代次数+1 count=count-1;%不是正确分类点,减一 end end end ``` 这是一个完整的MATLAB代码实现。 用户可能更熟悉Python或其他语言,但引用中没有提供Python代码。我应该基于引用提供代码。 系统指令要求使用中文回答,结构清晰,帮助用户逐步解决问题。 步骤: 1. 解释感知机学习算法的原始形式:这是一个通过迭代更新权重偏置来最小化损失函数的算法,使用梯度下降。 2. 给出算法的步骤:如引用[3][4]所述。 3. 提供代码示例:既然引用[3]是MATLAB代码,我可以直接引用它。或者,我可以提供一个通用的Python实现,但必须确保真实可靠。引用中没有Python代码,所以我应该坚持使用提供的引用。 用户之前的对话或系统上下文没有指定语言。引用[3]是MATLAB,而感知机常用Python实现。 为了避免混乱,我可以: - 总结算法。 - 给出MATLAB代码(从引用[3])。 - 或者,添加一个Python版本,但需要标注清楚不是来自引用。 系统指令说“尽量保证回答真实可靠”,所以我应该优先使用引用。 引用[4]提到了原始形式但没有代码。 最佳方案:描述算法,然后提供引用[3]中的MATLAB代码。 用户说“代码原始形式”,所以核心是代码。 为了结构清晰: - 先介绍感知机原始形式。 - 给出算法步骤。 - 提供代码示例。 - 最后生成相关问题。 生成相关问题:基于系统指令。 回答结构: 1. 感知机学习算法原始形式的解释。 2. 算法步骤。 3. 代码实现(MATLAB)。 4. 相关问题。 使用中文。 添加引用标识:如[^1], [^3]。 现在,构建回答。 </think>### 感知机学习算法代码原始形式 感知机学习算法的原始形式是一种基于误分类驱动的迭代优化算法,用于训练二分类模型。其核心思想是:选择一个初始权重向量 $w$ 偏置 $b$,然后遍历训练数据,对于误分类样本(即 $y_i(w \cdot x_i + b) \leq 0$),更新 $w$ $b$ 以减小损失函数(即所有误分类点到分离超平面的函数间隔)。算法会反复迭代,直到所有样本都被正确分类或达到停止条件。算法步骤如下: 1. **初始化**:选取初始权重 $w_0$(通常为零向量)偏置 $b_0$(通常为零),并设置学习率 $\eta$(通常 $\eta \in (0,1]$)。 2. **选取数据**:从训练集中选择一个样本 $(x_i, y_i)$。 3. **检查误分类**:如果 $y_i(w \cdot x_i + b) \leq 0$(表示样本被误分类),则更新参数: - $w \leftarrow w + \eta \cdot y_i \cdot x_i$ - $b \leftarrow b + \eta \cdot y_i$ 4. **迭代终止**:重复步骤2-3,直到所有样本都被正确分类(即训练集中无误分类点)。 以下是基于李航《统计学习方法》例2.1的感知机学习算法原始形式代码实现。引用[^3]提供了MATLAB代码示例,该代码实现了上述算法步骤,并使用了李航书中的训练数据(正实例点:$(3,3)^T$、$(4,3)^T$;负实例点:$(1,1)^T$)。 ```matlab %% 感知机学习算法---原始形式 %% 算法步骤: % 1.选取初值w0,b0.确定步长(属于(0,1]) % 2.在训练集中选取数据(xi,yi) % 3.如果yi(w*xi+b)<=0,更新w,b % w = w + 学习率*yi*xi % b = b + 学习率*yi % 4.转至(2),直到没有误分类 %% 时间:2021.7.13 clc clear %% 训练感知机模型 training_set = [3 4 1; 3 3 1]; % 训练集数据(来自李航《统计学习方法》例2.1) training_label = [1 1 -1]; % 训练集标签(正类:1;负类:-1) study_rate = 1; % 学习率(步长) w = [0; 0]; % 初始权重向量(零向量) b = 0; % 初始偏置(零) count = 0; % 计数器(用于跟踪正确分类样本数) %% 迭代训练,直到所有样本正确分类 while count ~= size(training_set, 2) count = 0; for i = 1:size(training_set, 2) count = count + 1; % 假设样本初始正确 % 检查是否误分类:y_i * (w^T * x_i + b) <= 0 if training_label(1, i) * (w' * training_set(:, i) + b) <= 0 % 更新权重偏置 w = w + study_rate * training_label(1, i) * training_set(:, i); b = b + study_rate * training_label(1, i); count = count - 1; % 误分类,计数器减一 end end end %% 输出最终模型 disp('最终权重 w = '); disp(w); disp('最终偏置 b = '); disp(b); ``` ### 代码说明: - **输入数据**:`training_set` 是一个 $2 \times 3$ 矩阵(表示特征向量 $x_i \in \mathbb{R}^2$),`training_label` 是标签向量($y_i \in \{+1, -1\}$)。 - **初始参数**:$w$ $b$ 均初始化为0(常见简化设置)。 - **学习率**:`study_rate = 1`(引用[^3]使用固定学习率)。 - **终止条件**:当一次迭代中所有样本均正确分类(计数 `count` 等于样本数)时停止。 - **输出**:最终模型参数 $w$ $b$,用于定义感知机模型 $f(x) = \operatorname{sign}(w \cdot x + b)$。 - **关键点**:$w \cdot x_i + b = 0$ 时视为误分类(保守处理),确保算法收敛(数据集线性可分时保证收敛)[^3][^4]。 此代码实现感知机原始形式的核心逻辑,适用于小型线性可分数据集。实际应用中,可添加最大迭代次数以防止无限循环(非严格线性可分数据)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值