感知机

这篇博客主要是为大家介绍一下感知机模型(Perception)。感知机模型是机器学习当中的一个二分类器模型,并且是一种线性分类器。模型的输入为样本的特征,输出为样本类别,或者称之为样本标记。

接下来首先给出感知机模型的定义:


其中为符号函数,定义如下:


可以看出定义公式由三部分组成:,其中分别为权重和偏置,而为样本的特征向量。感知机的工作原理是通过给定某个样本的特征向量作为感知机模型的输入,通过感知机模型的定义式,得出的值为模型的输出,如果为1,则可以预测样本为一个正例样本,如果为-1,则预测样本为一个负例样本,这便是感知机的工作原理。

感知机的几何解释:

实际上,每一个感知机模型对应了一个分类超平面,如果样本的特征空间为2维的,则分类超平面对应于二维空间中的一直线,如果样本的特征空间为3维的,则分类超平面对应于三维空间中的一个平面。如下图所示:


上图为特征空间为二维空间,对应的分类超平面如上图所示,样本点被分为了两类,在分类超平面右侧的为正例(方块),在分类超平面左侧的为负例(圆圈);


上图为特征空间为3维时对应的分类超平面,同样的样本点也被分成了两类。

接下来所要完成的工作就是找到最合适的,从而确定分类超平面。那么问题来了,如何得出一个比较好的呢?实际上,一旦确定了,模型也就确定了,但是构建模型的好坏与的取值有很大的关系,而我们接下来所要做的任务就是,通过某种方法去确定一组比较好的参数

接下来就介绍一下感知机的学习策略(确定的策略)

在介绍学习策略之前,首先介绍两个概念,即线性可分数据集和线性不可分数据集:

线性可分数据集:给定某个数据集,其中正例和负例能够被特征空间中的某个分类超平面完全分开,则成这个数据集是线性可分的;

线性不可分数据集:在特征空间中,没有一个分类超平面能够将数据集中的正例和负例完全分开,则称这个数据集是线性不可分的;

首先我们假设存在一个线性可分数据集,其中,即样本的特征空间为n维的,样本标记,接下来便确定学习策略。确定策略实际上就是确定损失函数(loss function),损失函数是用来衡量预测值和真实值之间的差距大小的,我们所要做的就是对损失函数进行优化,从而得到是损失函数值最小的参数。那么,感知机的损失函数应该如何定义呢?

损失函数可以是误分类点的总数,但是这样的损失函数对于参数不可导,因此优化起来比较困难。我们在这里所采用的损失函数是所有误分类点到分类超平面的距离总和,我们的目标是使这个距离总和尽可能的小。某个点到平面的距离公式为,式中的二范数。

如果样本点被误分类了,即如果样本的真实标记,则,即预测得到的样本标记为;反之,如果样本真实标记,则,即预测得到的样本标记为,因此可以总结如下规律,如果样本被误分类,则符合下式:


故而,我们可以得到误分类点到分类超平面的距离可表示为:


假设针对当前的,误分类点集合为,那么误分类点到超平面的总距离为:


不考虑就得到了感知机的损失函数:


可以看出损失函数是非负的,即如果误分类点越少,则损失函数越小,其值越趋近于0。我们的目标就是确定,使得损失函数能够尽可能的小。

在这里我们采用梯度下降法来对损失函数进行优化,进而求得相对合适的。梯度下降法原理在这里就不再为大家进行介绍了,大家可以从网上或者是一些优化相关书籍上进行学习。这里只介绍一下梯度下降算法的几种形式:

  1. BGD(Batch Gradient Descent):也叫做批量梯度下降,这种形式的梯度下降是通过计算所有训练样本的损失函数值之和的梯度,来对损失函数进行优化,对参数进行更新迭代,也就是说每一次参数的更新,都将使用所有的训练样本。
  2. SGD(Stochastic Gradient Descent):也叫做随机梯度下降,这种形式的梯度下降是一个训练样本计算一次损失,计算一次梯度,对参数进行更新一次。
  3. MSGD(Mini-Batch SGD):小批量随机梯度下降算法是批量梯度下降与随机梯度下降的折中版本,即每次既不取1个训练样本来计算梯度,也不是取全部样本来进行梯度的计算,而是只取训练样本的一部分来进行梯度的计算。

在这里给大家列出一段用python编写的梯度下降算法求解函数最优解的例子以供参考:

import numpy as np
from numpy import random as rd
np.set_printoptions(linewidth=1000, suppress=True)


def gradient_decent():
    # 这个函数为使用梯度下降求取y = x1 ** 2 + x2 ** 2的最优解
    # 函数在某一点(x1, x2)处的梯度为(2 * x1, 2 * x2)
    # 首先随机挑选出一个梯度下降的起始点(x1_start, x2_start)
    x_start = rd.uniform(-100, 100, 2)
    # 给出学习率alpha
    alpha = 0.1
    # 梯度下降算法的迭代次数
    iter_times = 300
    for i in range(iter_times):
        x_start -= alpha * 2 * x_start
        if i % 30 == 0:
            print("第%d次迭代:" % i, x_start)
    print("最优解:", x_start)


if __name__ == "__main__":
    gradient_decent()

运行结果如下图所示:


可以看出,使用梯度下降算法,最终确实收敛到了最优解(0, 0)处

接下来,我们将使用SGD来对感知机的损失函数进行优化

每一个误分类样本对应的损失函数值为:


由此可计算梯度:



因此,我们便的到了的更新迭代公式,下式中为梯度下降算法的学习率:



通过以上迭代公式,对误分类样本集中的样本进行遍历,每遍历一个误分类样本,对进行一次更新。每更新完所有误分类样本一次,为一个epoch,可以进行多次epoch来进行参数的更新迭代,最终得到最优的参数。从而构建出相应的感知机模型,至此,便求得了最终的感知机模型。当新样本点出现时,只需将其带入到模型中,便可预测得知新样本点的类别。

最后给出感知机学习算法的整体流程:

step1:选取初始值

step2:在训练集中选取数据

step3:如果当前模型对其误分类则使用上述的更新迭代公式进行参数更新

step4:判断是否达到终止条件,如果达到则终止,得到最终模型,否则跳到step2,这里终止条件可以是是否达到迭代次数上限,也可以是误分类样本数量是否为0

到这里,感知机的基本原理已经介绍完毕了,下面附上本人使用python编写的感知机相关程序以供大家参考:

import numpy as np
from numpy import random as rd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
np.set_printoptions(linewidth=1000, suppress=True)


def generate_sample(sample_count, noise_rate):
    # sample_count为样本个数
    # 此函数用于产生样本点x=(x1, x2)为特征向量,y为样本标记
    # 以2 * x1 + 2 * x2 - 1 = 0 为分类超平面构建样本点
    x = rd.uniform(-5, 5, (sample_count, 2))
    y = (2 * x[:, 0] + 2 * x[:, 1] - 1 >= 0).astype(np.int8)
    y[y == 0] = -1
    possitive_sample_x = x[y == 1]
    negative_sample_x = x[y == -1]
    fig = plt.figure()
    ax = plt.subplot(1, 1, 1)
    ax.scatter(possitive_sample_x[:, 0], possitive_sample_x[:, 1], label="possitive sample", marker="*", color="r")
    ax.scatter(negative_sample_x[:, 0], negative_sample_x[:, 1], label="negative sample", marker="^", color="b")
    ax.set_xlabel("x1")
    ax.set_ylabel("x2")
    ax.set_title("samples")
    ax.legend(loc="upper right")
    plt.show()
    # 为特征加一列1,方便加偏置项b
    scalar_1 = np.ones((x.shape[0], 1), dtype=np.float64)
    x = np.concatenate([scalar_1, x], axis=1)
    noise_num = int(sample_count * noise_rate)
    # 构造线性不可分样本集
    for i in range(noise_num):
        rand_int = rd.randint(0, len(y))
        if y[rand_int] == 1:
            y[rand_int] = -1
        else:
            y[rand_int] = 1
    return x, y


class Perception(object):

    def __init__(self, sample_count, noise_rate, alpha, rho, test_size, epochs, lamda):
        """

        :param test_size: 测试样本占总样本的比例
        :param noise_rate: 为了构造线性不可分数据而添加的噪音样本,此参数用于指定噪音样本数占总样本数的比例
        :param sample_count: 总的样本容量
        :param alpha: 梯度下降的学习率
        :param rho: 学习率衰减系数
        :param epochs: 进行多少个epoch
        :param lamda: 正则项系数
        """
        # self.x, self.y为随机产生的样本点,self.x为特征向量,self.y为分类标记
        self.x, self.y = generate_sample(sample_count, noise_rate)
        # self.x_train, self.y_train分别为训练集的输入和标签
        # self.x_test, self.y_test分别为测试集的输入和标签
        self.x_train, self.x_test, self.y_train, self.y_test = train_test_split(self.x, self.y, test_size=test_size, random_state=1)
        self.alpha = alpha
        self.rho = rho
        # 初始化权重向量w为self.weight,其中第一个即self.weight[0, 0]为偏置b
        self.weight = rd.uniform(-10, 10, (1, self.x_test.shape[1]))
        self.epochs = epochs
        self.lamda = lamda

    def train(self):
        print("开始训练...")
        for i in range(self.epochs):
            # 第一层循环用于控制进行多少次epoch
            # 将当前权重下正例点索引以及负例点索引取出
            positive_index = (np.dot(self.x_train, self.weight.T).ravel() >= 0).astype(np.int8)
            negative_index = -(np.dot(self.x_train, self.weight.T).ravel() < 0).astype(np.int8)
            # label为当前权重向量下的训练集的分类标记
            label = positive_index + negative_index
            # 对比真实标记self.y_train以及当前分类标记label找出在当前权重self.weight下的误分类点的索引wrong_classification_sample_index
            wrong_classification_sample_index = label != self.y_train
            # 将当前误分类点取出,当前误分类点集合为wrong_classification_samples,wrong_sample_true_label为当前误分类点的真实标记
            wrong_classification_samples = self.x_train[wrong_classification_sample_index]
            wrong_sample_true_label = self.y_train[wrong_classification_sample_index]
            # sgd:随机梯度下降
            for x, y in zip(wrong_classification_samples, wrong_sample_true_label):
                self.weight += (self.alpha * y * x - 2 * self.lamda * self.weight)
            self.alpha *= self.rho
            y_train_pred_positive = (np.dot(self.x_train, self.weight.T) >= 0).astype(np.int8)
            y_train_pred_negative = -(np.dot(self.x_train, self.weight.T) < 0).astype(np.int8)
            y_train_pred = y_train_pred_positive + y_train_pred_negative
            accuracy_rate = accuracy_score(self.y_train, y_train_pred)
            print("epoch %d 训练集准确率: " % (i + 1), "%.2f%s" % (accuracy_rate * 100, "%"))
            if np.any(np.isnan(self.weight[0])):
                raise Exception("正则项系数设置不合理,应该设置小一些")
        print("\n训练得到:")
        self.b = self.weight[0, 0]
        self.w1 = self.weight[:, 1:].ravel()[0]
        self.w2 = self.weight[:, 1:].ravel()[1]
        print("b =", self.weight[0, 0])
        print("[w1, w2] =", self.weight[:, 1:].ravel())
        print("真实的分类超平面:%s" % "2 * x1 + 2 * x2 - 1 = 0")
        print("训练得到的分类超平面:%f * x1 + %f * x2 + (%f) = 0" % (self.weight[0, 1] / np.abs(self.weight[0, 0]),
                        self.weight[0, 2] / np.abs(self.weight[0, 0]), self.weight[0, 0] / np.abs(self.weight[0, 0])))
        print("================================")

    def test(self):
        print("开始测试...")
        y_test_positive_pred = (np.dot(self.x_test, self.weight.T) >= 0).astype(np.int8)
        y_test_negative_pred = -(np.dot(self.x_test, self.weight.T) < 0).astype(np.int8)
        self.y_test_pred = (y_test_negative_pred + y_test_positive_pred).ravel()
        accuracy_rate = accuracy_score(self.y_test, self.y_test_pred)
        print("测试集准确率为:%.2f%s" % (accuracy_rate * 100, "%"))

    def draw_test(self):
        # x_test_wrong_classified_samples为测试样本上误分类的样本点集合
        # x_test_right_classified_samples为测试样本上正确分类样本点集合
        # y_test_right_classified为正确分类样本点对应的标记
        x_test_wrong_classified_samples = self.x_test[(self.y_test_pred - self.y_test).ravel() != 0]
        self.wrong_count = x_test_wrong_classified_samples.shape[0]
        print("测试集错误分类样本数为:%d" % self.wrong_count)
        print("测试集样本容量:%d" % self.x_test.shape[0])
        x_test_right_classified_samples = self.x_test[(self.y_test_pred - self.y_test).ravel() == 0]
        y_test_right_classified = self.y_test[(self.y_test_pred - self.y_test).ravel() == 0]
        # x_test_positive为测试集中正确分类样本点中的正例
        # x_test_negative为测试集中错误分类样本点中的负例
        x_test_positive = x_test_right_classified_samples[y_test_right_classified == 1]
        x_test_negative = x_test_right_classified_samples[y_test_right_classified == -1]
        fig = plt.figure()
        ax = plt.subplot(1, 1, 1)
        ax.scatter(x_test_positive[:, 1].ravel(), x_test_positive[:, -1].ravel(), marker="*", color="r", label="positive test samples")
        ax.scatter(x_test_negative[:, 1].ravel(), x_test_negative[:, -1].ravel(), marker="^", color="y", label="negative test samples")
        if list(x_test_wrong_classified_samples):
            ax.scatter(x_test_wrong_classified_samples[:, 1].ravel(), x_test_wrong_classified_samples[:, -1].ravel(), marker="o", color="g", label="wrong classified samples")
        x1 = np.linspace(-5, 5, 1000)
        x2 = (-self.b - self.w1 * x1) / self.w2
        ax.plot(x1, x2, color="b", label="the classification hyperplane obtained by training")
        ax.legend(loc="upper right")
        ax.set_title("predict result of test dataset")
        plt.show()


def main():
    perception = Perception(200, 0, 0.1, 0.9, 0.4, 100, 0.005)
    perception.train()
    perception.test()
    perception.draw_test()


if __name__ == "__main__":
    main()

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PyCharm是一款功能强大的Python集成开发环(IDE),它提供了丰富的功能和工具来帮助开发者编写、调试和管理Python代码。感知机是种二类的线性类算法,可以用于解决简单的类问题。在PyCharm中实现感知机可以按照以下步骤进行: 1. 创建一个新的Python项目:在PyCharm中,你可以选择创建一个新的Python项目。这将为你提供一个干净的项目结构,以便开始编写代码。 2. 导入必要的库:感知机算法通常需要使用NumPy等库来进行数值计算和矩阵操作。你可以使用PyCharm的包管理工具(如pip)来安装这些库,并在代码中导入它们。 3. 定义感知机类:在Python中,你可以定义一个感知机类来封装感知机算法的实现。这个类可以包含初始化权重、训练模型、预测等方法。 4. 实现训练方法:在感知机类中,你可以实现训练方法来更新权重并逐步优化模型。训练方法通常会迭代遍历训练数据集,根据预测结果和真实标签来更新权重。 5. 实现预测方法:感知机的预测方法可以根据输入特征和学习到的权重来预测样本的类别。你可以在感知机类中实现一个预测方法,用于对新样本进行类。 6. 编写主程序:在主程序中,你可以创建一个感知机对象,加载训练数据集,调用训练方法进行模型训练,然后使用预测方法对测试数据集进行类,并输出结果。 7. 调试和优化:使用PyCharm的调试功能可以帮助你检查代码中的错误和问题,并进行必要的优化和改进。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值