本文设计并实现了PerceptronLA、PseudoIA、LeastSM、LinearDA、KNN等五个算法类,以及DataProcessor的数据处理类。对感知器算法LDA、最小二乘法LSM的伪逆法与梯度下降法、Fisher线性判别分析与KNN算法进行了实现与分析,其中前三种算法都是对一次线性回归的求解。
鸢尾花数据集中第一类鸢尾花“Setosa”和第二类“Versicolor”、第三类“Virginica”的特征分离度非常的高,而第二类“Versicolor”和第三类“Virginica”的分离度很差,即第一类与第二第三类是线性可分的,而第二类和第三类是线性不可分的。因此我们在后续的实验中将以第一类与二三类的分类作为线性可分数据集的实验数据,第二类与第三类的分类作为线性不可分数据集的实验数据。
目录
一、感知器算法
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%。
| 运行次数 |
第一次 |
第二次 |
第三次 |
第四次 |
第五次 |
| 测试准确率 |

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

被折叠的 条评论
为什么被折叠?



