【基础机器学习算法原理与实现】使用感知器算法LDA、最小二乘法LSM、Fisher线性判别分析与KNN算法实现鸢尾花数据集的二分类问题

本文详细介绍了PerceptronLA、PseudoIA、LeastSM、LinearDA和KNN五种算法在鸢尾花数据集上的应用,包括线性可分和线性不可分情况下的分类效果分析。通过实例展示,探讨了这些算法在不同数据集上的性能和局限性。

本文设计并实现了PerceptronLA、PseudoIA、LeastSM、LinearDA、KNN等五个算法类,以及DataProcessor的数据处理类。对感知器算法LDA、最小二乘法LSM的伪逆法与梯度下降法、Fisher线性判别分析与KNN算法进行了实现与分析,其中前三种算法都是对一次线性回归的求解。

鸢尾花数据集中第一类鸢尾花“Setosa”和第二类“Versicolor”、第三类“Virginica”的特征分离度非常的高,而第二类“Versicolor”和第三类“Virginica”的分离度很差,即第一类与第二第三类是线性可分的,而第二类和第三类是线性不可分的。因此我们在后续的实验中将以第一类与二三类的分类作为线性可分数据集的实验数据,第二类与第三类的分类作为线性不可分数据集的实验数据。

目录

一、感知器算法

1 感知器算法原理:

(1)线性回归思想

(2)损失函数与损失函数优化

(3)算法步骤

2 感知器算法实现

3 感知器算法结果分析

(1)线性可分数据集的分类

(2)线性不可分数据集的分类

二、 伪逆法算法

1 伪逆法算法原理

2 伪逆法算法实现

3 伪逆法算法结果分析

(1)线性可分数据集的分类

(2)线性不可分数据集的分类

 三、最小二乘法算法

1 最小二乘法算法原理

2 最小二乘法算法实现

3 最小二乘法算法结果分析

(1)线性可分数据集的分类

(2)线性不可分数据集的分类

四、Fisher分类算法

1 Fisher分类算法原理

(1)算法原理

(2)算法步骤

2 Fisher分类算法实现

3 Fisher分类算法结果分析

(1)线性可分数据集的分类

(2)线性不可分数据集的分类

五、 KNN算法

1 KNN算法原理

(1)算法思想

(2)算法步骤

2 KNN算法实现

3 KNN算法结果分析

各个算法结果分析

(1)线性可分数据集

(2)线性不可分数据集


一、感知器算法

 PLA全称Perceptron Linear Algorithm,即线性感知器算法,属于一种最简单的感知机(Perceptron)模型,是一种可以直接得到线性判别函数的线性分类方法。这里实现的是单样本的感知器算法。

1 感知器算法原理

(1)线性回归思想

假设我们有m个样本,每个样本拥有n维特征和一个二元类别输出(),即我们的样本数据为 。

“感知器”思想的前提是数据是线性可分的,因此我们将函数定义为,即,其中。(我们在样本中增加一列,使得能够代表常数项b)

当此时我们的目标是找出一个满足的超平面,使得一类样本满足,另一类样本满足,从而将两类样本分隔开。因此,正确分类的样本即满足,而错误分类的样本满足

(2)损失函数与损失函数优化

损失函数的优化目标,就是使得所有误分类的样本满足到超平面的距离之和最小。

由上述可知,对每一个误分类的样本i,其到超平面的距离为:

观察可知分子和分母都含有 w ,当分子扩大n倍时,分母也将扩大n倍。也就是说,这里的分子分母有着固定的倍数关系。所以我们可以固定分子或分母为1,然后求分子的最小化作为损失函数,这样一来便可以简化我们的损失函数为:

损失函数对w求偏导可得:

可得w的梯度下降迭代公式为:

在SGD下的单一样本梯度下降公式为:

其中i为步长,为样本输出值1或-1,为n+1维列向量。

(3)算法步骤

1. 定义样本,选择解向量w和学习率a的初值

2. 在样本中寻找误分类样本,更新w

3. 循环一定次数或者没有误分类样本时结束循环

2 感知器算法实现

感知器算法被封装在了PerceptronLA类中,分为了初始化,训练,计算损失函数,测试四个函数模块,由于程序每次更新都将计算整体的损失函数,会有非常大的性能开销,如果没有需求可以将计算损失函数模块进行去除。

# 感知机模型
class PerceptronLA:
    # 初始化
    def __init__(self, a=0.005):
        # 将哪一类和其他类分开
        self.differentone = clas[differentone]
        # 初始化数据矩阵x,增加值为1的x0列来对应方程中的常量系数,也就是b
        self.xdata = trainingdata.iloc[:, 0:4]
        self.xdata.insert(4, 'x0', 1.0)
        # 初始化结果矩阵y
        tmp = [-1.0 for i in range(trainingdata.shape[0])]
        self.ydata = pd.Series(tmp)
        for i in range(trainingdata.shape[0]):
            if trainingdata.iloc[i, 4] == self.differentone:
                self.ydata.iloc[i] = 1.0
        # 初始化解矩阵w
        tmp = [np.random.randn() * 0.1 for i in range(5)]
        self.w = pd.Series(tmp)
        self.w.index = ["sepal.length", "sepal.width", "petal.length", "petal.width", "x0"]
        # 学习率,传入或者默认0.1
        self.learningRate = a
        # count为训练轮数,cont为训练次数
        self.count = 0
        self.cont = 0

    #计算损失函数
    def lossf(self, flist, tempw):
        LossF = 0
        self.cont += 1
        for i in range(self.xdata.shape[0]):
            sign = self.xdata.iloc[i].dot(self.w) * self.ydata.iloc[i]
            if sign < 0:
                LossF -= sign
        # 利用pla pocket思想,保存当前最优参数,并与修正之后的参数进行结果比较
        # 只有当修正之后损失函数更优时才保存新的参数
        if len(flist) == 0:
            flist.append(LossF)
        elif LossF < flist[-1]:
            flist.append(LossF)
        else :
            self.w = tempw
        return flist


    # 训练
    def train(self):
        err = 1000
        flist = [] #损失函数列表
        # 循环两百轮或者没有错误分类的点
        while self.count < 500 and err > 0:
            err = 0
            self.count += 1
            # 打乱数据
            tmp = [i for i in range(self.xdata.shape[0])]
            random.shuffle(tmp)
            # 循环
            for i in tmp:
                # 符号函数sign = y(w*x + b) 用于判断分类是否正确
                sign = self.xdata.iloc[i].dot(self.w)*self.ydata.iloc[i]
                if sign < 0:
                    # 如果错在误分类点则进行更新
                    err += 1
                    # 梯度下降法优化更新损失函数 w = w + 学习率learningRate * x * y
                    tempw = self.w + self.learningRate * self.ydata.iloc[i] * self.xdata.iloc[i]
                    flist = self.lossf(flist,tempw)
                    # print(self.cont , flist[-1])
            # print("第",self.count,"轮pla训练准确率:  {:.2%}".format(1 - err / self.xdata.shape[0]))
        print("训练轮数: ", self.count)
        return flist

    # 测试准确率
    def test(self):
        tdata = testdata.iloc[:, 0:4]
        tdata.insert(4, 'x0', 1.0)
        cont = 0
        sign = tdata.dot(self.w)
        for i in range(testdata.shape[0]):
            if (sign.iloc[i] > 0 and testdata.iloc[i, 4] == self.differentone) or (
                    sign.iloc[i] < 0 and testdata.iloc[i, 4] != self.differentone):
                cont += 1
        print("感知器算法测试准确率:  {:.2%}".format(cont / testdata.shape[0]))

3 感知器算法结果分析

由本报告1.3中所述将结果分为线性可分数据集与线性不可分分别讨论。

(1)线性可分数据集的分类

对于线性可分数据集,pla算法能够在指定迭代次数得到结果,迭代次数与初始解向量的随机值有关。准确率保持在100%附近,推测错误原因是感知器算法在特定样本学习下造成的局部最优解问题。准确率表如表2.3.1所示,损失函数变化图如图2.3.1所示:

运行次数

第一次

第二次

第三次

第四次

第五次

测试准确率

100.00%

100.00%

97.78%

100.00%

100.00%

表 pla线性可分数据集准确率

图 pla线性可分数据集损失函数变化图

(2)线性不可分数据集的分类

对于线性不可分数据集,pla算法不能够在指定迭代次数得到结果,迭代次数始终为设定的最大轮数,如损失函数图所示,损失函数在一定轮数优化之后再也无法更近一步的优化了。而测试数据集的准确率在500轮迭代后依旧接近100%,测试数据的准确率主要与测试数据的随机划分相关,虽然训练数据集无法做到100%无误分类点,只要测试数据分布合理准确率还是可以达到100%。

运行次数

第一次

第二次

第三次

第四次

第五次

测试准确率

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值