经典机器学习模型(一)感知机模型

经典机器学习模型(一)感知机模型

感知机可以说是一个相当重要的机器学习基础模型,是神经网络和支持向量机的基础。

感知机是一个二分类的线性分类模型,之所以说是线性,是因为它的模型是线性形式的。

从《统计学习方法》中,我们知道学习方法的三要素为模型、策略、算法,我们就从三要素来了解下感知机。

1 感知机模型的三要素

1.1 模型

1.1.1 感知机模型

在这里插入图片描述

  • 输入空间

    • 这里 χ \chi χ代表 n n n维实数空间的一个子集
    • 输入的每一个实例 x x x,用一个 n n n维的特征向量表示,它是属于输入空间 χ \chi χ的。
    • 实例 x = ( x ( 1 ) , x ( 2 ) , . . . , x ( n ) ) T x=(x^{(1)},x^{(2)},...,x^{(n)})^T x=(x(1),x(2),...,x(n))T
  • 输出空间

    • 由于这是一个二分类模型,所以这里输出空间是一个只包含+1和-1的一个集合。

    • 1 代表的是正类,-1 代表的是负类,具体的输出则是代表实例 x x x所对应的类别。

  • 感知机

    • 我们定义一个从输入空间到输出空间的函数,这个函数就称作感知机。

    • 这个函数 s i g n ( x ) sign(x) sign(x)叫做符号函数

    • w ⋅ x = ( w ( 1 ) x ( 1 ) , w ( 2 ) x ( 2 ) , . . . , w ( n ) x ( n ) ) T w\cdot x=(w^{(1)}x^{(1)},w^{(2)}x^{(2)},...,w^{(n)}x^{(n)})^T wx=(w(1)x(1),w(2)x(2),...,w(n)x(n))T

  • 假设空间

    • 特征空间里面所有可能的线性函数就称为假设空间。
  • 参数空间

    • 参数 w w w b b b的所有组合,就得到一个 n n n维的空间,也就是参数空间。

1.1.2 感知机模型的几何意义

我们从几何角度来解释一下感知器,如下图所示:

  • 线性方程 w ⋅ x + b = 0 w\cdot x +b = 0 wx+b=0代表着 n n n维特征空间里面的一个超平面 S S S

  • w w w是法向量,垂直于超平面 S S S b b b是相应的截距项。

  • 通过超平面 S S S我们就可以将整个特征空间分为两部分

    • 一部分是正类,其中的实例所对应的输出为 +1;
    • 一部分为负类,它里面的实例所对应的输出为 -1。所以这个超平面被称为分离超平面。
  • 超平面的理解。

    • 如果我们现在的特征空间是一维的,那么想区分正负类实例点,用实数轴上的一个点就可以了,比如零点。
    • 如果特征空间是两维的,实例应该就是二维空间中的一个点,要区分正负类,它的分离超平面对应的应该是一条直线(如下图)。
    • 如果可能空间是三维的,分离超平面应该就是一个二维平面了。
    • 依此类推,如果环境空间是 n n n维的,那么它所对应的超平面其实就是一个 n − 1 n-1 n1维的子空间。
  • 原点到超平面的距离为 − b ∣ ∣ w ∣ ∣ -\frac{b}{||w||} ∣∣w∣∣b

在这里插入图片描述

1.2 策略

如果假设训练数据集线性可分,我们的目标则是希望寻求到一个的分离超平面,把这些实例点完全划分为正负类

但是,要求得这样一个超平面,就需要确定模型的参数,即w和b,这就需要制定一定的学习策略。换而言之,就是要合理地定义感知机相应的损失函数

1.2.1 线性可分数据集

感知机模型,有一个比较严苛的条件,就是要求数据集必须是线性可分的。

线性可分的意思如下:

对于给定的数据集,如果存在某个超平面,使得这个数据集的所有实例点可以完全划分到超平面的两侧,也就是正类和负类。我们就称这个数据集是线性可分的,否则线性不可分。

1.2.2 感知机的损失函数

特征空间中的任意一点到超平面的距离公式如下:
1 ∣ ∣ w ∣ ∣ ∣ w ⋅ x 0 + b ∣ \frac{1}{||w||}|w\cdot x_0+b| ∣∣w∣∣1wx0+b
如果 x 0 x_0 x0是错误的分类点,那么容易得到下式:
1 ∣ ∣ w ∣ ∣ ∣ w ⋅ x 0 + b ∣ = { − w ⋅ x 0 + b ∣ ∣ w ∣ ∣ , y 0 = 1 w ⋅ x 0 + b ∣ ∣ w ∣ ∣ , y 0 = − 1 = − ( w ⋅ x 0 + b ) y 0 ∣ ∣ w ∣ ∣ \frac{1}{||w||}|w\cdot x_0+b|=\begin{cases} -\frac{w\cdot x_0+b}{||w||}, & y_0 = 1\\ \\ \frac{w\cdot x_0+b}{||w||}, & y_0 = -1 \end{cases}=-\frac{(w\cdot x_0+b)y_0}{||w||} ∣∣w∣∣1wx0+b= ∣∣w∣∣wx0+b,∣∣w∣∣wx0+b,y0=1y0=1=∣∣w∣∣(wx0+b)y0
现在我们要关注的就是这里的错误分类点,设误分类点为 x i x_i xi,那么 x i x_i xi到超平面 S S S的距离为:
− ( w ⋅ x i + b ) y i ∣ ∣ w ∣ ∣ -\frac{(w\cdot x_i+b)y_i}{||w||} ∣∣w∣∣(wxi+b)yi
如果用 M M M代表所有误分类点的集合,我们可以写出所有误分类点到超平面 S S S的距离的总和:
− 1 ∣ ∣ w ∣ ∣ ∑ x i ∈ M ( w ⋅ x i + b ) y i -\frac{1}{||w||}\sum\limits_{x_i\in M}(w\cdot x_i+b)y_i ∣∣w∣∣1xiM(wxi+b)yi
很明显, M M M中所含有的误分类点越少的时候,总距离和应该越小。在没有误分类点的时候, 这个距离和应该为 0。我们希望通过最小化总距离和来求参数。

因此,我们就得到了感知机模型的损失函数如下:
L ( w , b ) = − ∑ x i ∈ M ( w ⋅ x i + b ) y i , M 为误分类点的集合 L(w,b)=-\sum\limits_{x_i\in M}(w\cdot x_i+b)y_i,M为误分类点的集合 L(w,b)=xiM(wxi+b)yiM为误分类点的集合

1.3 算法(原始形式)

  • 假如给定训练数据集,我们通过最小化损失函数,就可以估计得到模型参数。

  • 如何求参数 w w w b b b呢?这就是一个优化问题,寻找使损失函数最小的参数

在这里插入图片描述

想求这个损失函数的极小值,可以先求梯度,即分别求偏导数,得到梯度向量,然后用梯度下降法对参数更新。

需要注意的是,梯度下降法用的是负梯度,所以我们求梯度的时候得到的负号就抵消掉了

  • 如果是批量更新,就需要每次使用所有的误分类点,这会致使每一轮的迭代都需要大量的时间。

  • 随机梯度下降法,每一轮随机选择一个误分类点,迭代的速度会快一些。

    • 这是因为,如果通过这个误分类点进行参数的更新,有可能误分类点就会减少,那么下一步我们可用来选择更新参数的实例点就会减少,这在一定程度上简化计算,节约了时间。
  • 小批量梯度下降法既不是像批量梯度下降法中那样选择了所有的样本点,也不是像随机梯度下降法中随机选取了一个样本点,而是选择部分样本点进行参数更新。

    • 但是,小批量梯度下降法,也面临许多问题,比如每次需要选择多少个样本点?选择哪些样本才合适呢?

损失函数 L ( w , b ) = − ∑ x i ∈ M ( w ⋅ x i + b ) y i , M 为误分类点的集合 对 w 和 b 分别求偏导数,可得梯度为: ∇ w L ( w , b ) = − ∑ x i ∈ M x i y i ∇ b L ( w , b ) = − ∑ x i ∈ M y i 批量梯度下降法: w ← w + η ∑ x i ∈ M x i y i , b ← b + η ∑ x i ∈ M y i 随机梯度下降法 : w ← w + η x i y i , b ← b + η y i 其中, η ( 0 < η < = 1 ) 为步长 损失函数L(w,b)=-\sum\limits_{x_i\in M}(w\cdot x_i+b)y_i,M为误分类点的集合 \\ 对w和b分别求偏导数,可得梯度为: \\ \nabla_w L(w,b)=-\sum\limits_{x_i\in M}x_iy_i \\ \nabla_b L(w,b)=-\sum\limits_{x_i\in M}y_i \\ 批量梯度下降法:w\leftarrow w + \eta\sum\limits_{x_i\in M}x_iy_i,b\leftarrow b + \eta\sum\limits_{x_i\in M}y_i \\ 随机梯度下降法: w\leftarrow w + \eta x_iy_i,b\leftarrow b + \eta y_i\\ 其中,\eta(0<\eta<=1)为步长 \\ 损失函数L(w,b)=xiM(wxi+b)yiM为误分类点的集合wb分别求偏导数,可得梯度为:wL(w,b)=xiMxiyibL(w,b)=xiMyi批量梯度下降法:ww+ηxiMxiyi,bb+ηxiMyi随机梯度下降法:ww+ηxiyi,bb+ηyi其中,η(0<η<=1)为步长

1.3.1 随机梯度下降法

现在我们以随机梯度下降法来讲解感知机的算法,如下:

在这里插入图片描述

  • 首先选择初始值。
  • 接下来,在训练集中随机选取一个实例点,用 y i ( w x i + b ) y_i(wx_i+b) yi(wxi+b)来判断这个点被分离超平面正确分类还是错误分类。
    • 如果被正确分类, y i ( w x i + b ) y_i(wx_i+b) yi(wxi+b)就是大于零的,我们不用管这个实例点了;
    • 如果被错误分类, y i ( w x i + b ) y_i(wx_i+b) yi(wxi+b)就是小于零的,我们可以把这个实例点拿来更新参数。
    • 最麻烦的就是,如果这个实例点恰好位于分离超平面上,那么 y i ( w x i + b ) y_i(wx_i+b) yi(wxi+b)就直接等于零,无法知道此时这个实例点到底是被正确分类还是错误分类。所以,本着宁肯错杀也不能放过的原则,我们把这个实例点拿来更新参数。
  • 这样第三步就完成了。
  • 接着就是步骤的重复,直到所有的实例点都确定被正确分类。

1.3.2 感知机算法例题

在这里插入图片描述

我们这里使用python代码求解该例题:

完整代码可参考:Statistical_Learning_Methods_Impl: 《统计学习方法》第二版 python代码实现

import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
%matplotlib inline

# 1、加载数据
data = np.array([
    [3, 3, 1],
    [4, 3, 1],
    [1, 1, -1]
])
# 获取X和y
X, y = data[:,:-1], data[:,-1]

# 2、画图
plt.scatter(X[:2][0], X[:2][1], label='1')
plt.scatter(X[2:][0], X[2:][0], label='-1')
plt.xlabel('x0')
plt.ylabel('x1')
# 设置x轴和y轴范围
plt.xlim(0, 7)
plt.ylim(0, 7)
plt.legend()

在这里插入图片描述

# 3、创建模型
class Perceptron(object):
    def __init__(self, num_features, l_rate=1, w='0'):
        # 初始化参数w、b以及学习率l_rate
        if w == '0':
            self.w = np.zeros(num_features, dtype=np.float32)
        elif w == '1':
            self.w = np.ones(num_features, dtype=np.float32)
        else:
            raise Exception('Unsupported parameter w')
        self.b = 0
        self.l_rate = l_rate

    def sign(self, x, w, b):
        # 计算:xw + b
        return np.dot(x, w) + b


    def fit(self, X_train, y_train):
        # 随机梯度下降法
        is_wrong = False
        while not is_wrong:
            # 误分类点的个数
            wrong_count = 0
            for index in range(len(X_train)):
                # 取出一个点
                X = X_train[index]
                y = y_train[index]
                # 如果该点为误分类点,就进行迭代
                if y * self.sign(X, self.w, self.b) <= 0:
                    # 更新w
                    self.w += self.l_rate * np.dot(y, X)
                    # 更新b
                    self.b += self.l_rate * y
                    # 误分类点+1
                    wrong_count += 1
                    print(f'取出的误分类点为x{index + 1}, 更新后w = {self.w}, b = {self.b},  wx+b = {self.w[0]}x1 + {self.w[1]}x2 + {self.b}')
            # 一次循环迭代结束后,如果没有误分类点,就结束
            if wrong_count == 0:
                is_wrong = True
        return 'Perceptron Model'

    def predict(self,X_predict):
        y = np.dot(X_predict, self.w) + self.b
        return np.sign(y)

    def score(self):
        pass
# 进行训练
num = len(X[0])
net1 = Perceptron(num_features=num)
net1.fit(X, y)
取出的误分类点为x1, 更新后w = [3. 3.], b = 1,   wx+b = 3.0x1 + 3.0x2 + 1
取出的误分类点为x3, 更新后w = [2. 2.], b = 0,   wx+b = 2.0x1 + 2.0x2 + 0
取出的误分类点为x3, 更新后w = [1. 1.], b = -1,  wx+b = 1.0x1 + 1.0x2 + -1
取出的误分类点为x3, 更新后w = [0. 0.], b = -2,  wx+b = 0.0x1 + 0.0x2 + -2
取出的误分类点为x1, 更新后w = [3. 3.], b = -1,  wx+b = 3.0x1 + 3.0x2 + -1
取出的误分类点为x3, 更新后w = [2. 2.], b = -2,  wx+b = 2.0x1 + 2.0x2 + -2
取出的误分类点为x3, 更新后w = [1. 1.], b = -3,  wx+b = 1.0x1 + 1.0x2 + -3

可以看到和书籍上迭代过程一样:

在这里插入图片描述

经过上述迭代,我们就得到最终模型:
f ( x ) = s i g n ( x ( 1 ) + x ( 2 ) − 3 ) f(x)=sign(x^{(1)}+x^{(2)}-3) f(x)=sign(x(1)+x(2)3)
需要注意的是,感知机模型并不唯一,不同的初值或者说不同的误分类点的顺序,可以得到不同的分离超平面。

我们可以实验下,将w的初始值设置为1,就得到了另一个超平面。

# 进行训练
num = len(X[0])
net2 = Perceptron(num_features=num, w='1')
net2.fit(X, y)
取出的误分类点为x3, 更新后w = [0. 0.], b = -1,  wx+b = 0.0x1 + 0.0x2 + -1
取出的误分类点为x1, 更新后w = [3. 3.], b = 0,  wx+b = 3.0x1 + 3.0x2 + 0
取出的误分类点为x3, 更新后w = [2. 2.], b = -1,  wx+b = 2.0x1 + 2.0x2 + -1
取出的误分类点为x3, 更新后w = [1. 1.], b = -2,  wx+b = 1.0x1 + 1.0x2 + -2
取出的误分类点为x3, 更新后w = [0. 0.], b = -3,  wx+b = 0.0x1 + 0.0x2 + -3
取出的误分类点为x1, 更新后w = [3. 3.], b = -2,  wx+b = 3.0x1 + 3.0x2 + -2
取出的误分类点为x3, 更新后w = [2. 2.], b = -3,  wx+b = 2.0x1 + 2.0x2 + -3
取出的误分类点为x3, 更新后w = [1. 1.], b = -4,  wx+b = 1.0x1 + 1.0x2 + -4

在这里插入图片描述

2 感知机的对偶形式算法

2.1 理解对偶算法

在之前讲解的原始形式的学习算法中,如果实例点 ( x i , y i ) (x_i,y_i) (xi,yi)是误分类点,可以用它更新参数,即
w ← w + η x i y i b ← b + η y i w\leftarrow w + \eta x_iy_i \\ b\leftarrow b + \eta y_i\\ ww+ηxiyibb+ηyi
假如,每一个实例点对于参数更新,做了 n i n_i ni次贡献,那么每个实例点作用到初始参数 w 0 、 b 0 w_0、b_0 w0b0上的增量分别为 a i y i x i a_iy_ix_i aiyixi a i y i a_iy_i aiyi,其中 a i = n i η a_i=n_i\eta ai=niη

还是之前的例子:

在这里插入图片描述

  • 在这个过程中,实例 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)作为误分类点出现两次,所以 n 1 = 2 n_1=2 n1=2,即第一个实例点在迭代中贡献了 2 次。

  • ( x 2 , y 2 ) (x_2,y_2) (x2,y2)并没有出现,所以 n 2 = 0 n_2=0 n2=0,即第二个实例点在迭代中没有贡献。

  • ( x 3 , y 3 ) (x_3,y_3) (x3,y3)出现了5次,所以 n 3 = 5 n_3=5 n3=5,即第三个实例点在迭代中贡献了 5 次。

  • 恰好 n 1 + n 3 = 7 n_1 + n_3=7 n1+n3=7,就是实际迭代的次数。综合所有贡献的增量,就得到最终参数了,与原始算法的结果是相同的。

  • 对偶形式,基本思想就是通过实例点的线性组合来更新参数,其权重由贡献的大小决定的

根据以上的例子,很容易得出下面式子:

在这里插入图片描述

2.2 对偶算法

下面是感觉机学习算法对偶形式的具体步骤,可以看到把 w w w换为了 ∑ j = 1 N a i y i x j \sum\limits_{j=1}^N a_iy_ix_j j=1Naiyixj,梯度下降时候也变成了更新 a i a_i ai

在这里插入图片描述

与原始形式相比,对偶形式有什么优势呢? 这需要,仔细分析对偶形式的迭代条件。

在这里插入图片描述

  • 将迭代条件展开,我们发现,如果训练数据集固定,那么有些值是不需要重复计算的,也就是我们红框里的这 N N N个内积。

  • 如果 ( x i , y i ) (x_i, y_i) (xi,yi)是误分类点,只要读取Gram矩阵第 i i i行的值即可。

  • 我们要做的,就是在得到训练数据集之后,把这 N × N N×N N×N个内积计算出来储存到Gram矩阵,之后每次更新参数的时候读取就可以,这能节省许多计算量。

2.3 对偶算法的案例

还是之前的案例,我们使用对偶算法进行求解。

在这里插入图片描述

  • 先设置初始值,不妨还是取零向量,然后计算Gram矩阵,把9个内积的值都储存下来。

  • 接下来就是判断误分类点,可以选取 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)带入迭代条件中,计算得到零,可以用来更新参数,得到一个新的分离超平面。

在这里插入图片描述

  • 用这个新得到的分离超平面,对三个实例点分类,选取错误分类点,继续进行更新。
    在这里插入图片描述

  • 之后,重复步骤,直到没有误分类点,停止迭代。

在这里插入图片描述

2.4 上述过程的代码实现

Statistical_Learning_Methods_Impl: 《统计学习方法》第二版 python代码实现

import numpy as np

class PerceptronDuality(object):
    def __init__(self, num_features, eta=1):
        # 初始化参数G、α、w、b
        self.G = np.zeros([num_features, num_features], dtype=np.float32)
        self.α = np.zeros(num_features)
        self.b = 0
        self.eta = eta
        # w[0] * x + w[1] * y + b = 0
        self.w = [0., 0.]

    def fit(self, data, label):
        # 1、计算 Gram 矩阵
        for i in range(0, len(data)):
            for j in range(0, len(data)):
                self.G[i][j] = data[i][0] * data[j][0] + data[i][1] * data[j][1]

        print('Gram矩阵:\n', self.G)

        separated = False  # 标记是否完全分离
        while not separated:
            separated = True

            # 遍历训练集,每次取出点 (data[i][0], data[i][1])
            for i in range(0, len(data)):
                sum = 0
                for j in range(0, len(data)):
                    # 计算 ∑(α*yj*xj)x的值,相当于原始形式中的 wx
                    sum += self.α[j] * label[j] * self.G[j][i]
                # 判断是否为误分类点
                if (sum + self.b) * label[i] <= 0:
                    self.α[i] += self.eta          # 更新 α 的值
                    self.b += self.eta * label[i]  # 更新 b 的值
                    print(f'误分类点为x{i + 1},α = {self.α}, b = {self.b}')

                    separated = False  # 置回标记

        # 求出 w 的值
        for i in range(0, len(self.α)):
            self.w[0] += self.α[i] * label[i] * data[i][0]
            self.w[1] += self.α[i] * label[i] * data[i][1]

        return 'PerceptronDuality Model'

if __name__ == '__main__':
    data = np.array([
        [3, 3, 1],
        [4, 3, 1],
        [1, 1, -1]
    ])

    X, y = data[:, :-1], data[:, -1]
    # 进行训练
    num = len(X)
    net = PerceptronDuality(num_features=num)

    net.fit(X, y)

可以看到和之前手动计算的一致。

Gram矩阵:
 [[18. 21.  6.]
  [21. 25.  7.]
  [ 6.  7.  2.]]
误分类点为x1,α = [1. 0. 0.], b = 1
误分类点为x3,α = [1. 0. 1.], b = 0
误分类点为x3,α = [1. 0. 2.], b = -1
误分类点为x3,α = [1. 0. 3.], b = -2
误分类点为x1,α = [2. 0. 3.], b = -1
误分类点为x3,α = [2. 0. 4.], b = -2
误分类点为x3,α = [2. 0. 5.], b = -3

参考书籍:
李航老师《统计学习方法》第二版

  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值