感知机学习笔记
作者:一个兴趣使然的程序员lvh
邮箱:1726341330@qq.com
最新更改时间:2019-10-19
原创文章,转载请注明出处https://blog.csdn.net/qq_44899812/article/details/102635736
关于分类问题,最古老也是最简单的算法就是1956年提出的感知机了。所以感知机也是学习分类问题的入门问题。
目录
导引
看完这个导引你大概就能知道感知机是什么,他是如何运作的了。
二维平面上的线性二分类
感知机只能解决线性可分问题,至于为什么,后面会讲到。
注解:线性可分:对于二维空间来说,线性可分是指用一条直线可以把两种数据完全分类开来,在三维空间中就是指可以用一个平面把两种数据完全分类开来,以此类推,在高维空间就是指用一个超平面把两种数据完全分类开来。 假如有三类数据,在二维空间里面如果可以用一条直线可以把某一类数据和其他两类划分开来,就称这类数据是线性可分的
如下图。如何把黑点和红点区分开来?就是一个线性可分的二分类问题.
很简单,一条直线不就可以把它们分开来吗。(如下图)
直线上方一类,下方一类。只要判别这个点在上方还是下方就好了。
设这条直线方程是 w0+w1*x1+W2*x2=0
,假如w0+w1*x1+W2*x2>0
所指区域是直线上方,w0+w1*x1+W2*x2<0
就表示直线下方的区域。
我们就可以用w0+w1*x1+W2*x2
的正负来判断一个点是黑点还是红点了。
注解:有的人可能有点不适应这种写法,回想高中的y=kx+b,y>kx+b就是直线上方,y<kx+b就是直线下方,不是一样的的吗,只是换了种写法
那么,只要能找到上图这条直线,这个分类不就解决了吗!
那怎么找到这条直线呢?
对于人来说很简单,因为我们有着复杂的大脑结构,但是如何教会机器去找这条直线呢?
机器的好处在于不厌其烦,我们可以让他随便找一条直线,然后让他朝着损失最小的方向不断摸索。
首先让机器随便做一个直线。如下图,可以看出来错误百出,红点分到黑的里面,黑点分到红的里面。
接下来我们就要减小损失。
如果我们以误分类点个数为损失函数,就会不知道朝哪个方向去减小损失。
所以我们换一种损失函数,以**所有误分类点到直线的总距离
**为损失函数。因为距离是非负的,所以损失函数最小值为0,取最小值时每个误差点的误差也必为0,即全部正确。
以所有误分类点到直线的总距离为损失函数
有了这个损失函数之后,就开始一个点一个点的去减小损失。遇到错误的点就把直线往错误点那边靠,来减小损失。遇到正确的就不动。
比如遇到了下图淡紫色光晕的黑点,这个点带入到直线方程 w0+w1*x1+W2*x2<0
,在直线下方的,但他的实际应该在直线上方 ,w0+w1*x1+W2*x2应该大于0
。这就算分错了。
发现这个点分错了,就要朝损失函数最小的方向去调整。
我们想让这个误分类点到直线的距离减小,所以调整直线,让直线往这个误分点移动一些,如下图。(这个样子叫随机梯度下降法,一个点一个点的调,后面会具体讲)
或者移动多了,发现这个点已经不是误分类点了,这样更好。
像这样,遇到误分类点就调整,遇到正确的点就不动。
慢慢的,直线就会移动到我们想要的样子。
当没有了误分类点了,他也就不动了。(所以说感知机是误分类驱动的)
这,就是一个最简单的感知机。
当然,对平面上的点进行分类只是具体问题的抽象罢了。
如果把横坐标看成是一朵花的叶子长度,纵坐标看成是这朵花的花萼宽度,那么我们是不是只要知道了某朵花的这两个特征,就可以用一条直线,对这朵花的种类进行分类了呢?
二维以上空间的二分类
实际中只要两个特征就能分类的东西很少,所以不能只考虑二维分类。
二维空间里面的二分类我们用的是直线,w0+w1*x1+w2*x2=0
那三维空间的分类用平面不就好了吗,w0+w1*x1+w2*x2+w3*x3=0
那四维空间里面,就用一个四维的超平面,w0+w1*x1+w2*x2+w3*x3+w4*x4=0
(虽然人类很难想象高维空间,但是计算方法是一样的啊,我们照样计算)。
有了上面的铺垫我们大概知道了,感知机其实就是用超平面(偶尔退化成平面,直线),来进行线性分类。
n维的空间上,分类超平面就是
w
0
∗
x
0
+
w
1
∗
x
1
+
w
2
∗
x
2
+
.
.
.
+
w
n
∗
x
n
=
0
w0*x0+w1*x1+w2*x2+...+wn*xn=0
w0∗x0+w1∗x1+w2∗x2+...+wn∗xn=0
超平面分出来的结果无非就是两个:
点带进去,
w0+w1*x1+w2*x2+w3*x3+...+wn*xn>0
,在超平面平面的一侧
w0+w1*x1+w2*x2+w3*x3+...+wn*xn<0
,在超平面的另一侧
(=0的情况比较少,不考虑)
我们把>0的一侧的点叫做1类,<0的一侧的点叫做-1类,
这样从二维到n维的线性分类思路不就清晰了吗?
单个神经元
上面讲的超平面其实就是一个单个的神经元,多个神经元就是多个超平面,
我们这里讲的单层感知机只用了一个神经元。
单个神经元的结构模型
单个神经元就是一个超平面。
一个超平面的包含的元素不就是下图这些吗,
x1,x2,x3,…,xn ,n个自变量。
w1,w2,w3,…,wn是自变量前面的系数。
w1*x1+w2*x2+w3*x3+...+wn*xn
再加上一个偏置
wo*x0
就是超平面的方程了。
w
0
+
w
1
∗
x
1
+
w
2
∗
x
2
+
w
3
∗
x
3
+
.
.
.
+
w
n
∗
x
n
=
0
w0+w1*x1+w2*x2+w3*x3+...+wn*xn=0
w0+w1∗x1+w2∗x2+w3∗x3+...+wn∗xn=0
(偏置相当于y=kx+b里面的b,这里x0是1,写成x0是为了形式上统一)
再把具体的点带进去,就有了>0和<0两种情况,这两种情况分别对应着1类和-1类。
单个神经元模型
整个过程可以简写成下面这种公式。(sign()代表到-1和1的映射)
f
(
x
)
=
s
i
g
n
(
w
i
⃗
∗
x
i
⃗
)
f(x)=sign(\vec {wi}*\vec {xi})
f(x)=sign(wi∗xi)
感知机符号化
sign()函数图像
(上面的式子化为方便起见,加权和用向量内积表示了)
w
i
⃗
=
(
w
0
,
w
1
,
w
2
,
.
.
.
,
w
n
)
\vec {wi}=(w0,w1,w2,...,wn)
wi=(w0,w1,w2,...,wn)
x i ⃗ = ( x 0 , x 1 , x 2 , . . . , x n ) \vec {xi}=(x0,x1,x2,...,xn) xi=(x0,x1,x2,...,xn)
( w 0 , w 1 , . . . w n ) ∗ ( x 0 , x 1 , . . . x n ) = w 0 ∗ x 0 + w 1 ∗ x 1 + . . . + w n ∗ x n (w0,w1,...wn)*(x0,x1,...xn)=w0*x0+w1*x1+...+wn*xn (w0,w1,...wn)∗(x0,x1,...xn)=w0∗x0+w1∗x1+...+wn∗xn
向量内积公式
符号说明
下面具体讲解一下这些符号在实际问题中的含义。(符号说明可以先跳过,看不懂再回头看)
-
x1,x2,x3,x4…xn : 一个xi对应着样本的一个特征。(例如iris数据集里面的x1对应花萼长度,x2对应花萼宽度。。。)
-
y: 样本的实际类别,或者说标签。这里是二分类,所以y取-1,1(比如A花用1来表示,B花用-1来表示)。
-
w1,w2,w3,w4…wn: 一个wi对应着一个特征的权值。
-
x0,w0:
w0*x0
是偏置,x0取1
。注意 x0并不是一个特征,w0*x0
也可以写成b,这样设只是为了形式上统一。 -
f(): 激活函数,这里用的是
sign()
(符号函数),如下图,sign只是用来把加权和映射到-1和1上面而以。
sign()
- -1,1: 类别,或者说标签(计算出来的),这里有两类,用-1,1表示,当然也可以用别的方法表示,但是这么表示比较方便。
具体操作
看过了导引,我们对感知机的操作有了一个大概的了解,但具体怎么操做,如何判断一个点是不是误分类点?如何计算距离?怎么去调整参数?
下面来一个个的讲解。
如何判断误分类点
w 0 + w 1 ∗ x 1 + w 2 ∗ x 2 + w 3 ∗ x 3 + . . . + w n ∗ x n = 0 w0+w1*x1+w2*x2+w3*x3+...+wn*xn=0 w0+w1∗x1+w2∗x2+w3∗x3+...+wn∗xn=0
超平面方程
我们令w0+w1*x1+w2*x2+...+wn*xn>0
即∑>0
的一侧被分类为1,w0+w1*x1+w2*x2+...+wn*xn<0
即∑<0
的一侧被分类为-1
如果说样本实际标签为1,代入点(x1,x2,x3,…,xn)到超平面方程
,求出来,w0+w1*x1+w2*x2+...+wn*xn>0
,那他就在标签为1的一侧,这就算分对了。
如果计算出来的加权和与实际标签异号,比如实际标签-1,但代入点(x1,x2,x3,…,xn)到超平面方程
求出来∑>0,两者异号,那肯定是分错了。
注解:有的人可能会困惑,为什么∑>0就是标签为1,∑<0就是标签为-1,为什么不是反过来,其实这个反过来也是一样的结果,可以假设一下,如果∑>0就是标签-1,∑<0就是标签1,那他求出来的分界线,不还是我们要的那个分界线吗
那我们就可以把误分类判断总结为只要满足,代入超平面方程和实际标签异号的点
,就是误分类点 。简写为下面的方程。
y
∗
(
w
i
⃗
∗
x
i
⃗
)
<
0
y*(\vec {wi}*\vec {xi})<0
y∗(wi∗xi)<0
方程2 误分类点判断
损失函数探索
以误分类点个数为损失函数(了解就行)
(这里以误分类点个数为损失函数是感知机算法的思考历程,如果只是想应用现成算法可以跳过)
我们最开始的朴素的想法是,以误分类点个数为损失函数,只要损失函数为0,就代表全部分对了。
Q()是我们自己定义的一个用来计算误分类点个数的函数,出现一个误分类点(y*∑<0),就加1,如果不是,就加0。
∑
[
Q
(
y
∗
w
i
⃗
∗
x
i
⃗
)
]
\sum[Q(y*\vec {wi}*\vec {xi})]
∑[Q(y∗wi∗xi)]
损失函数1
Q
(
x
)
=
{
1
,
x
<
0
0
,
x
>
=
0
Q(x)= \begin{cases} 1,x<0\\ 0,x>=0 \end{cases}
Q(x)={1,x<00,x>=0
Q()图像
虽然这个想法看起来可以,可是这个损失函数是不可导的,难以对他求最小值点,所以我们只能换一种思路。
以所有误分类点到超平面的距离的和为损失函数
如果所有误分类点到超平面的距离之和为0的话,不就等价于没有误分类点了吗?那我们只要朝着所有误分类点的总误差距离最小的方向调整参数,就能做到没有误分类点了。
点(x1,x2,x3,…,xn)到平面w0+w1*X1+w2*X2+w3*X3+...+wn*Xn=0
的距离公式可以回忆高中知识
d
=
∣
w
0
+
w
1
∗
x
1
+
w
2
∗
x
2
+
.
.
.
+
w
n
∗
x
n
∣
w
1
2
+
w
2
2
+
.
.
.
+
w
n
2
d=\frac{|w0+w1*x1+w2*x2+...+wn*xn|}{\sqrt{w1^2+w2^2+...+wn^2}}
d=w12+w22+...+wn2∣w0+w1∗x1+w2∗x2+...+wn∗xn∣
距离公式
因为误分类点的y和∑异号,所以我们可以用y来去绝对值!
d
=
−
y
∗
(
w
0
+
w
1
∗
x
1
+
w
2
∗
x
2
+
.
.
.
+
w
n
∗
x
n
)
w
1
2
+
w
2
2
+
.
.
.
+
w
n
2
=
−
y
∗
(
x
⃗
∗
w
⃗
)
∣
∣
w
⃗
∣
∣
d=\frac{-y*(w0+w1*x1+w2*x2+...+wn*xn)}{\sqrt{w1^2+w2^2+...+wn^2}}=\frac{-y*(\vec x*\vec w)}{||\vec w||}
d=w12+w22+...+wn2−y∗(w0+w1∗x1+w2∗x2+...+wn∗xn)=∣∣w∣∣−y∗(x∗w)
距离公式改进版
(||w||是向量w的范数,也就是模)
M是误分类点的集合,把误分类的所有点到超平面的距离加起来就是我们的目标函数了。
∑
x
∈
M
(
−
y
i
∗
(
w
⃗
i
∗
x
⃗
i
)
∣
∣
w
⃗
i
∣
∣
)
\sum_{x\in M}(\frac{-yi*(\vec wi*\vec xi)}{||\vec wi||})
x∈M∑(∣∣wi∣∣−yi∗(wi∗xi))
损失函数2
现在我们的任务就是使损失函数2最小,自变量是参数**(w0,w1,w2,w3,…,wn)**。
(因为距离是非负数,所以最小值是0)
对于求最小值点,我们高中的常用方法是求导,找到导数为0的点就是最小值点。
但是那是对于初等函数而言,对于很多复杂的函数我们根本没办法求导。那就要用一些其他方法了。
方法又很多,这里只挑选了三种梯度下降法讲解(感知机用的是随即梯度下降法),有兴趣的同学可以了解一下其他的优化方法。https://www.cnblogs.com/huangyc/p/9801261.html
梯度下降法
可以想象一个场景,一个盲人站在山上某一处,想尽快到达山的最低点,但他只能感知到自己脚下的路哪个方向是最陡峭的,所以他每次就沿着最陡峭的方向往下迈一步,如果他某一次往前迈出一步发现自己下降的非常小,小于一个阈值,他就认为自己已经到了最低的地方了。
我们的损失函数求最小值过程不就可以类比盲人下山吗,我们无法求出整个函数的导数(盲人),但我们总是有办法对一个点求梯度[^注解1](找到最陡峭的方向),我们就沿着这个梯度方向的反方向(因为梯度方向是上升最快的方向)去改变参数(迈出一步),每次改变之后再继续求梯度,再沿着梯度方向反方向改变参数,最终当有一次我们改变参数后发现改变的非常小,小于一个阈值,我们就认为已经到低谷了(到达最低点)。
注解1:梯度,对于单变量函数来说就是斜率。对于多变量函数来说,就是各个自变量的偏导数组成的向量,是函数增加最快的方向,例如f(x,y)=x^2+y,在(x0,y0)点的梯度是(对x的偏导数,对y的偏导数),应该是(2x0,1)
图片来源于网络
步长η(学习率)
下山的时候,要是步长太大可能会正好错过最低点(如下图,从左往右,快到最低点的时候因为步子太大了,就错过了最低点),如果步长太小又走的太慢。
所以对于步长我们要综合考虑速度和准确度。
批量梯度下降法 BGD(Batch Gradient Descent)
如果上面盲人下山的比喻你懂了,那你基本上懂了批量梯度下降法。
批量梯度下降法中,我们的算法就是这个盲人,损失函数就是大山,我们的目的就是找到损失函数的最小值。
这里的损失函数是所有误分类点的总误差距离,所以说利用BGD求出来的下降方向,肯定总误差下降最快的方向,所需要的步数肯定是最少的,从图像上上看一般是比较合理的(如图1,为一个等高线图上面的下降路径)。
图1(BGD)
如果损失函数不是凸函数,有可能就会掉到一个局部最小值里面,所以我们要多做几次,比较最小值。(如下图)
如果损失函数是凸函数的话那么BGD的结果肯定是全局最小值(如下图的凸函数)
虽然我们用所有误差点的总误差作为损失函数会让计算出来的方向是对于全局最优的,但是这也面临了一个问题:
每一次计算梯度都要考虑到所有的误差点,从实际操作来看,每一次计算梯度都要把整个样本集都过一遍才能得出一个梯度
如果样本集特别大,那么每过一遍都会花费大量的时间,我们肯定希望能够快点到山下,不然天都黑了。所以对于量比较大的样本集我们就必须找到其他方法。
随机梯度下降法 SGD(Stochastic Gradient Descent)
随机梯度下降法和批量梯度下降法的区别在于:批量梯度下降法的损失函数是所有误分类点的总误差,而随机梯度下降法的损失函数是某一个误差点的误差
,也就是说我们每次只考虑一个点误差要最小,不考虑全局。
这样的话,每遇到一个误分类点都更新一下参数,更新的速度就很快,批量梯度下降法还在慢慢求梯度,随机梯度下降法已经走了好几步了。
随机梯度下降法,实际是就是想用某个误分类的误差梯度来近似所有误分类点的误差梯度。
我们每次朝着一个点误差下降最快的方向去变化,这样有比较大概率总误差减小了。
但这样做自然是有缺陷的,某个点误差减小最快方向,不一定是总误差减小最快方向,极端情况有可能增加误差(如下图,原来是蓝色线,变成了绿色线,为了让黄色光晕的红点误差减小,让黑色光晕的红点误分了,这就是随机梯度下降法的短视性)
如下图,从图像上看路线非常盲目,走了很多弯路,从实际操作来看,更新次数非常多(不是最短路径,走的步数当然多)。
但是这样速度非常快,虽然每一步走的不一定是对于全局最快的下降方向,但是他的步频非常高,总的来看要比批量梯度下降法快的多。
SGD
但因为他的方向并不是全局最优,所以他可能到不了全局最优点,而是在最优点附件徘徊。准确度降低了。
小批量梯度下降法MBGD(Mini-Batch Gradient Descent)
小批量梯度下降法其实是批量梯度下降法和随机梯度下降法的折中。
我们把样本均匀分成好多小组,每次考虑误差的时候考虑一个小组的总误差,这样就缓解了只考虑一个或者考虑所有的弊端。
三种梯度下降法小结
批量梯度下降法(BGD):
优点:准确度高(如果是凸函数则一定能到最优解),更新次数少。
缺点:迭代太慢(每走一步要找方向找半天),容易陷入局部最优。
随机梯度下降法(SGD):
优点:快。有可能碰巧跳出局部最小值。
缺点:准确度低(最后可能在最优解旁边徘徊),迭代次数太多。
小批量梯度下降法(MBGD):
优缺点中和了BGD和SGD。
下面放一张图,来大概对比一下这三种下降法的迭代过程。
利用梯度下降法使损失函数最小
回到主题,现在既然有了优化方法(随机梯度下降法),那我们就用随机梯度下降法的方式来下降**损失函数2
**。
∑
x
∈
M
(
−
y
i
∗
(
w
⃗
i
∗
x
⃗
i
)
∣
∣
w
⃗
i
∣
∣
)
\sum_{x\in M}(\frac{-yi*(\vec wi*\vec xi)}{||\vec wi||})
x∈M∑(∣∣wi∣∣−yi∗(wi∗xi))
损失函数2
随机梯度下降法的思路是,每遇到一个误分类点就更新一次参数,最终近似达到最优解。
现在开始训练样本集,假设这会碰上了一个被判断为误分类的点
y
∗
∑
0
n
(
w
i
∗
x
i
)
<
0
y*\sum_{0}^{n}(wi*xi)<0
y∗0∑n(wi∗xi)<0
方程2 误分类点判断
我们就对这个点的误差点离超平面的距离求梯度
d
=
−
y
∗
(
x
⃗
∗
w
⃗
)
∣
∣
w
⃗
∣
∣
d=\frac{-y*(\vec x*\vec w)}{||\vec w||}
d=∣∣w∣∣−y∗(x∗w)
距离公式改进版
我们发现分母上的||w||运算起来很麻烦,其实他不是必要的,可以省略他。
注解:因为我们只需要距离为0就可以了,d为0的时候分子为0,分母其实是无所谓的,换句话说,分母在整个距离公式里面只是起一个缩放效果,对梯度的方向没有影响的(一个向量乘以或者除以一个不为0的数,方向不变)
那我们就忽略分母来表达距离,如下(这种距离表达又被称作函数间隔)
d
=
−
y
∗
(
x
⃗
∗
w
⃗
)
d=-y*(\vec x*\vec w)
d=−y∗(x∗w)
距离公式再改版 函数间隔
对各个wi求导
d
=
−
y
∗
(
w
0
∗
1
+
w
1
∗
x
1
+
w
2
∗
x
2
+
w
3
∗
x
3
+
.
.
.
.
+
w
n
∗
x
n
)
d=-y*(w0*1+w1*x1+w2*x2+w3*x3+....+wn*xn)
d=−y∗(w0∗1+w1∗x1+w2∗x2+w3∗x3+....+wn∗xn)
展开来比较容易理解
Δ
w
i
=
−
y
∗
x
i
\Delta wi=-y*xi
Δwi=−y∗xi
这样就求出了梯度(-y*x1,-y*x2,-y*x3,...,-y*xn)
我们就把原来的参数减去步长η乘以梯度(因为梯度是上升最快方向,我们要的是下降最快方向,所以加上负的步长η乘以梯度),误差距离就朝着减小的方向迈出了一步。
w
i
⃗
−
η
∗
Δ
w
i
⃗
\vec {wi}-\eta *\Delta \vec {wi}
wi−η∗Δwi
具体到每个wi
w
i
=
w
i
−
η
Δ
w
i
=
w
i
+
η
∗
y
∗
x
i
wi=wi-\eta\Delta wi=wi+η*y*xi
wi=wi−ηΔwi=wi+η∗y∗xi
之所以乘以一个步长η前面说过,是为了控制步子不要太大或者太小。
我们就这么一遍一遍的对着样本集进行训练,每遇到一个误分类点就更新一次,所有样本点都过了一遍了之后,就从头开始再训练一遍,直到某一遍,训练下来一个误分类点都没有找到,训练就可以结束了。
最后求出来的参数(w0,w1,w2,w3,…,wn)所组成的这个超平面,
w
0
+
w
1
∗
x
1
+
w
2
∗
x
2
+
w
3
∗
x
3
+
.
.
.
+
w
n
∗
x
n
=
0
w0+w1*x1+w2*x2+w3*x3+...+wn*xn=0
w0+w1∗x1+w2∗x2+w3∗x3+...+wn∗xn=0
就是我们要找的,能把所有数据点正确的一分为二的超平面。
我们训练的目的是为了以后的分类,就像高中的时候我们做练习是为了高考,练习做的正确率高并不能代表高考能考得好,那我们怎么评判我们做练习的效果呢?
模拟考试。一般我们会在训练完训练集之后再找一个测试集,这个测试集和模拟考一样是有答案的,让训练好的超平面去分这个测试集,最后分完的结果和答案一比对,就知道学的怎么样了。
学习性能,泛化性能,学习次数
平时练习的正确率就是学习性能
,考试的时候能表现出来的正确率就是泛化性能
。我们希望的是平时正确率高,泛化的也好,最不希望的是平时练习好,泛化性能很低。
有的人一道题做一遍相当于别人一道题做十遍,不一样的学习次数
一样的结果,这就是学习效率的问题。
评价一个学习还有很多方面要考虑,这里感知机我们先考虑这三个方面。
算法设计
我们来梳理一下整个的流程
简单来说就是
- 输入
- 训练
- 测试
因为要看学习性能,泛化性能,学习次数,所以我们会另外加一些东西
再具体一些流程如下
-
初始化一个超平面。(随机设置参数)
-
输入一个训练集数据,训练总数++。
-
用超平面对这个数据进行分类。
-
分类错误,按照随机梯度下降法更新参数,训练样本个数++。
分类正确,训练正确次数++,训练样本个数++。
-
训练集全输入完。学习次数++。
-
判断:如果这一遍什么都没有错,或者大于最大学习次数(自己设定的),就结束学习。否则,从第2步到第6步再来一遍。
-
输入测试集数据。
-
判断正确,正确个数++。判断错误,不做操作。
-
测试集全分类完。
-
输出结果:学习性能(训练正确数/训练样本个数),泛化性能(测试正确数/测试样本个数),迭代次数(更新了几次),学习次数(整个样本集刷了几遍)
下面是流程图
代码实现
#include<iostream>
#include<stdlib.h>
#include<cmath>
#include<time.h>
#include<conio.h>
using namespace std;
#define maxtime 100//最大学习次数
#define N 4//样本特征数
int type;
double x[N];
double s;//激活前的值
int y;//激活后的值
double w[N],b;
long k;//迭代次数
long c;//学习次数
double rate=0.1;//学习率
//sign函数
int sign(double s)
{
if(s>0)
return 1;
else
return -1;
}
//输入
void input(FILE *fp)
{
for(int i=0;i<N;i++)
fscanf(fp,"%lf",&x[i]);
}
//随机设置权值
void setw()
{
srand((unsigned int)time(NULL));//随机数
for(int i=0;i<N;i++)
w[i]=(double)(rand()%10)/10;//我把他设置在0.0到1.0之间,没有特别要求
b=(double)(rand()%10)/10;
}
int main()
{
FILE *fp1,*fp2,*fp3;
fp1=fopen("iris_in.txt","r");//训练集
fp2=fopen("theout.txt","w");//打印结果
fp3=fopen("iris_test.txt","r");//测试集
int i,j,m,p,q;
int flag;//用于判断训练要不要结束
int num;//训练集样本数
int tnum;//测试集样本数
int right;//训练正确数
int right2;//测试正确数
k=0;
c=0;
num=0;
tnum=0;
right=0;
right2=0;
setw();//初始化超平面
while(c<maxtime)//只要小于最大训练次数就一直训练
{
flag=0;
c++;//训练次数++
while(fscanf(fp1,"%d",&type)!=EOF)//只要还有数据
{
input(fp1);//就输入一组数据
num++;//训练样本数++
//求加权和
s=0;
for(i=0;i<N;i++)
s+=w[i]*x[i];
s+=b;
//判断有没有误分类
if(type*s<0)//错误了,更新参数
{
flag=1;
for(i=0;i<N;i++)
w[i]+=rate*type*x[i];
b+=rate*type;
k++;//迭代次数++
}
else//正确了,正确数++
{
right++;
}
}
//如果一遍学习下来没有错误,flag还是0,学习就结束了
if(flag==0)
{
break;
}
rewind(fp1);
}
right2=0;
//测试
while(fscanf(fp3,"%d",&type)!=EOF)
{
input(fp3);
tnum++;
s=0;
for(i=0;i<N;i++)
s+=w[i]*x[i];
s+=b;
if(type*s>0)
{
right2++;
}
}
fprintf(fp2,"学习率%lf 学习次数%ld 迭代次数%ld 学习性能%lf 泛化性能%lf \n",rate,c,k,(double)right/(double)num,(double)right2/(double)tnum);
return 0;
}
实验样例
实际应用才是我们研究算法的最终目的,我选取了UCI数据库上面的一些数据来分类。
iris数据集
数据来源 https://archive.ics.uci.edu/ml/datasets/Iris
iris(鸢尾花)数据集是分类问题中非常经典的一个数据集。
我们利用某朵鸢尾花的几个特征对这朵鸢尾花的品种进行细分。
uci上数据库上的鸢尾花有三种 1.Setosa 2. Versicolour 3. Virginica 。
他的四个特征是:
sepal length in cm(花萼长度)
sepal width in cm(花萼宽度)
petal length in cm(花瓣长度)
petal width in cm(花瓣宽度)
我们从中选取了Setosa 的数据和 Versicolour 的数据,对这两种花进行二分类
原始数据展示(只展示二十行)
-1 7.00 3.20 4.70 1.40
-1 6.40 3.20 4.50 1.50
-1 6.90 3.10 4.90 1.50
-1 5.50 2.30 4.00 1.30
-1 6.50 2.80 4.60 1.50
-1 5.70 2.80 4.50 1.30
-1 6.30 3.30 4.70 1.60
-1 4.90 2.40 3.30 1.00
-1 6.60 2.90 4.60 1.30
-1 5.20 2.70 3.90 1.40
1 6.80 3.20 5.90 2.30
1 6.70 3.30 5.70 2.50
1 6.70 3.00 5.20 2.30
1 6.30 2.50 5.00 1.90
1 6.50 3.00 5.20 2.00
1 6.20 3.40 5.40 2.30
1 5.90 3.00 5.10 1.80
1 6.30 3.40 5.60 2.40
1 6.40 3.10 5.50 1.80
1 6.00 3.00 4.80 1.80
第一列是类别,1代表Setosa类鸢尾花,-1代表 Versicolour类鸢尾花。
后面四列分别对应鸢尾花的一个特征。
实验1
数据集iris 特征数4 训练数据量62 测试数据量22 最大学习次数1000
学习率(步长) | 学习性能 | 泛化性能 | 学习次数 | 迭代次数 | |
---|---|---|---|---|---|
0.1 | 0.954774 | 0.954545 | 1000 | 2804 | |
0.3 | 0.955306 | 0.954545 | 1000 | 2771 | |
0.5 | 0.955516 | 0.954545 | 1000 | 2758 | |
1.0 | 0.955242 | 1.000000 | 1000 | 2775 |
可以发现学习次数达到了最大学习次数,说明并没有学习到可以把训练样本完全分开,所以我调整最大学习次数到10000,看看他能不能收敛。
实验2
数据集iris 特征数4 训练数据量62 测试数据量22 最大学习次数10000
学习率(步长) | 学习性能 | 泛化性能 | 学习次数 | 迭代次数 | |
---|---|---|---|---|---|
0.1 | 0.939169 | 1.000000 | 10000 | 37715 | |
0.3 | 0.939194 | 1.000000 | 10000 | 37700 | |
0.5 | 0.939176 | 1.000000 | 10000 | 37711 | |
1.0 | 0.939123 | 1.000000 | 10000 | 37744 |
泛化性能上升了,但并没有收敛,我怀疑训练样本并不线性可分。
训练数量的多少很多时候会影响泛化性能,我们改变训练数量试一下。
实验3
数据集iris 特征数4 训练数据量30 测试数据量22 最大学习次数1000
学习率(步长) | 学习性能 | 泛化性能 | 学习次数 | 迭代次数 | |
---|---|---|---|---|---|
0.1 | 0.934133 | 0.954545 | 125 | 247 | |
0.3 | 0.934127 | 0.954545 | 126 | 249 | |
0.5 | 0.934167 | 0.954545 | 120 | 237 | |
1.0 | 0.934167 | 0.954545 | 120 | 237 |
减少了一些的训练数据后发现只学了一百多次,还没有到最大学习次数就收敛了,说明这30个训练数据是线性可分的。 不过泛化性能也打了折扣。
wine数据集
数据来源 https://archive.ics.uci.edu/ml/datasets/Wine
wine数据集是对意大利同一地区生产的三种酒,通过大量分析得出的数据。
这些数据包括了这三种酒的13种不同的成分,如下
Alcohol(酒精)
Malic acid(苹果酸)
As(灰)
Alcalinity of ash(灰分的碱度)
Magnesium(镁)
Total phenols(总酚)
Flavanoids(黄酮类化合物)
Nonflavanoid phenols(非黄烷类酚类)
Proanthocyanins(原花色素)
Color intensity(颜色强度)
Hue(色调)
OD280/OD315 of diluted wines(稀释葡萄酒的OD280/OD315)
Proline(脯胺酸)
同样我们选取其中两种来做二分类。
原数据展示(只展示20行)
-1 12.37 1.07 2.10 18.50 88.00 3.52 3.75 0.24 1.95 4.50 1.04 2.77 660.00
1 13.39 1.77 2.62 16.10 93.00 2.85 2.94 0.34 1.45 4.80 0.92 3.22 1195.00
1 13.30 1.72 2.14 17.00 94.00 2.40 2.19 0.27 1.35 3.95 1.02 2.77 1285.00
1 13.87 1.90 2.80 19.40 107.00 2.95 2.97 0.37 1.76 4.50 1.25 3.40 915.00
1 14.02 1.68 2.21 16.00 96.00 2.65 2.33 0.26 1.98 4.70 1.04 3.59 1035.00
1 13.73 1.50 2.70 22.50 101.00 3.00 3.25 0.29 2.38 5.70 1.19 2.71 1285.00
1 13.58 1.66 2.36 19.10 106.00 2.86 3.19 0.22 1.95 6.90 1.09 2.88 1515.00
1 13.68 1.83 2.36 17.20 104.00 2.42 2.69 0.42 1.97 3.84 1.23 2.87 990.00
1 13.76 1.53 2.70 19.50 132.00 2.95 2.74 0.50 1.35 5.40 1.25 3.00 1235.00
1 13.51 1.80 2.65 19.00 110.00 2.35 2.53 0.29 1.54 4.20 1.10 2.87 1095.00
-1 11.82 1.72 1.88 19.50 86.00 2.50 1.64 0.37 1.42 2.06 0.94 2.44 415.00
-1 12.51 1.73 1.98 20.50 85.00 2.20 1.92 0.32 1.48 2.94 1.04 3.57 672.00
-1 12.42 2.55 2.27 22.00 90.00 1.68 1.84 0.66 1.42 2.70 0.86 3.30 315.00
-1 12.25 1.73 2.12 19.00 80.00 1.65 2.03 0.37 1.63 3.40 1.00 3.17 510.00
-1 12.72 1.75 2.28 22.50 84.00 1.38 1.76 0.48 1.63 3.30 0.88 2.42 488.00
-1 12.22 1.29 1.94 19.00 92.00 2.36 2.04 0.39 2.08 2.70 0.86 3.02 312.00
-1 11.61 1.35 2.70 20.00 94.00 2.74 2.92 0.29 2.49 2.65 0.96 3.26 680.00
-1 11.46 3.74 1.82 19.50 107.00 3.18 2.58 0.24 3.58 2.90 0.75 2.81 562.00
-1 12.52 2.43 2.17 21.00 88.00 2.55 2.27 0.26 1.22 2.00 0.90 2.78 325.00
-1 11.76 2.68 2.92 20.00 103.00 1.75 2.03 0.60 1.05 3.80 1.23 2.50 607.00
第一列代表类别,1假设是A种酒,-1假设是B种酒。
后面的十三列分别是十三个特征的值
实验4
数据集wine 特征数13 训练数据量93 测试数据量37 最大学习次数1000
学习率(步长) | 学习性能 | 泛化性能 | 学习次数 | 迭代次数 | |
---|---|---|---|---|---|
0.1 | 0.974000 | 0.594595 | 1000 | 2418 | |
0.3 | 0.974000 | 1.000000 | 1000 | 2418 | |
0.5 | 0.974011 | 0.594595 | 1000 | 2417 | |
1.0 | 0.973989 | 0.594595 | 1000 | 2419 |
发现学习性能非常高,泛化性能很差。(除了学习率是0.3的)
我怀疑是学习率不合理或者是学习次数不够或者训练样本的问题,下面分别做两个实验来验证
实验5
多调几次的学习率,试图找到学习率上面的问题。
数据集wine 特征数13 训练数据量93 测试数据量37 最大学习次数1000
学习率(步长) | 学习性能 | 泛化性能 | 学习次数 | 迭代次数 | |
---|---|---|---|---|---|
0.01 | 0.973978 | 0.594595 | 1000 | 2420 | |
0.03 | 0.973968 | 0.594595 | 1000 | 2421 | |
0.05 | 0.973978 | 0.972973 | 1000 | 2420 | |
0.07 | 0.974000 | 0.594595 | 1000 | 2418 | |
0.09 | 0.973989 | 0.621622 | 1000 | 2419 | |
0.20 | 0.974000 | 1.000000 | 1000 | 2418 | |
0.25 | 0.974011 | 0.594595 | 1000 | 2417 | |
0.35 | 0.974000 | 1.000000 | 1000 | 2418 | |
0.40 | 0.974000 | 1.000000 | 1000 | 2418 | |
0.50 | 0.973989 | 0.594595 | 1000 | 2419 | |
0.60 | 0.973989 | 0.594595 | 1000 | 2419 | |
0.70 | 0.973989 | 0.594595 | 1000 | 2419 | |
0.80 | 0.973989 | 0.594595 | 1000 | 2419 |
我试图改变学习率来找到最佳学习率,发现调学习率就像调焦一样,变化非常大,我认为这里面偶然因素比较大。
实验6
增加学习次数上限,看看能不能收敛。
数据集wine 特征数13 训练数据量93 测试数据量37 最大学习次数50000
学习率(步长) | 学习性能 | 泛化性能 | 学习次数 | 迭代次数 | |
---|---|---|---|---|---|
0.1 | 0.968665 | 0.972973 | 50000 | 145709 | |
0.3 | 0.968657 | 1.000000 | 50000 | 145744 | |
0.5 | 0.968666 | 0.918919 | 50000 | 145705 | |
1.0 | 0.968673 | 1.000000 | 50000 | 145672 |
增加了学习次数上限后,虽然训练还没有收敛,但在50000次的学习下泛化性能普遍上升了。
实验7
实验6
让我怀疑这个数据集可能是线性可分的,但大量的学习太耗费时间,所以我选择了0.3的学习率单独做一组大次数的学习。
数据集wine 特征数13 训练数据量93 测试数据量37 最大学习次数500000
学习率(步长) | 学习性能 | 泛化性能 | 学习次数 | 迭代次数 | |
---|---|---|---|---|---|
0.3 | 0.970060 | 1.000000 | 500000 | 1392197 |
虽然没有能收敛,但在足够大的学习次数下泛化性能还是可靠的。
实验8
因为我训练样本里面类别为1的聚在一起,为-1的聚在一起,我猜想是这个原因使得实验1
的学习性能虚高而泛化性能极差.
就像一个学生做练习的时候一直重复做一种题目,会导致在练习的时候这种题目的正确率很高,但到了考试的时候各种各样的题目他就难以适应了。
所以我把训练集打乱再做了一次。
数据集wine打乱 特征数13 训练数据量93 测试数据量37 最大学习次数1000
学习率(步长) | 学习性能 | 泛化性能 | 学习次数 | 迭代次数 | |
---|---|---|---|---|---|
0.1 | 0.893645 | 0.945946 | 1000 | 9891 | |
0.3 | 0.893495 | 0.810811 | 1000 | 9905 | |
0.5 | 0.893785 | 0.972973 | 1000 | 9878 | |
1.0 | 0.893269 | 0.972973 | 1000 | 9926 |
实验8
是用打乱的数据集做的。
实验4
(下图)是用没有打乱的数据集,用一样的学习率,一样的最大学习次数做的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ur9Nc7x5-1571450311250)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571386502207.png)]
对比可以发现,同样的条件下,打乱数据集学习性能稍微差了谢,但泛化性能普遍上升,从迭代次数可以看出来,打乱数据集在学习中不停的遇到错误,磕磕绊绊,而未打乱的数据集迭代次数少很多,但也因此未打乱的泛化比不过打乱的。
实验9
前面实验6
做了50000次的学习也没有收敛,实验8打乱数据集,提高了泛化能力,让我猜想,如果用打乱的数据集做,是不是会很快的收敛呢?
数据集wine 特征数13 训练数据量93 测试数据量37 最大学习次数50000
学习率(步长) | 学习性能 | 泛化性能 | 学习次数 | 迭代次数 | |
---|---|---|---|---|---|
0.1 | 0.932495 | 1.000000 | 50000 | 313899 | |
0.3 | 0.932515 | 1.000000 | 50000 | 313805 | |
0.5 | 0.932504 | 1.000000 | 50000 | 313857 | |
1.0 | 0.932500 | 1.000000 | 50000 | 313875 |
异或实验
你可以用一条直线把红点和蓝点完全分开吗?
你可以发现,无论你做哪一条直线都没有办法把红点和蓝点完全分开来。
这就是感知机的最大缺陷,只能处理线性可分问题
。
异或问题是感知机算法的当头一棒。让上世纪六十年代的人从感知机的热潮中冷静过来,一大批的学者也因此对人工智能表示失望,认为人们对于人工智能的假想只不过是玩玩罢了。人工智能也在那个时候陷入冷门。
抱着复现历史难题的目的我自己捏了一个异或的数据。数据分布如下图。
原数据展示(只展示20行)
36.48 9.67 -1
33.64 8.39 -1
33.64 7.95 -1
30.32 7.63 -1
27.68 7.39 -1
25.92 7.31 -1
25.32 7.07 -1
24.88 5.79 -1
24.36 3.55 -1
24.24 1.39 -1
24.68 21.11 1
24.52 19.55 1
24.52 17.63 1
26.68 16.07 1
28.00 15.59 1
30.48 15.27 1
26.36 20.39 1
26.32 19.43 1
27.48 18.35 1
29.24 17.47 1
前面两列是点的横纵坐标,第三列是点的类别。
数据集lvh1 特征数2 训练数据量108 测试数据量42 最大学习次数1000
学习率(步长) | 学习性能 | 泛化性能 | 学习次数 | 迭代次数 | |
---|---|---|---|---|---|
0.1 | 0.938339 | 0.604651 | 1000 | 6721 | |
0.3 | 0.938275 | 0.604651 | 1000 | 6728 | |
0.5 | 0.939073 | 0.534884 | 1000 | 6641 | |
1.0 | 0.937495 | 0.534884 | 1000 | 6813 |
学习性能虚高应该是因为数据集没有打乱红点聚在一起,黑点聚在一起导致的。泛化性能和预期的差不多,接近0.5。
线性不可分问题困扰了人们很久,因为感知机的输出是离散的(-1,1),没有办法用基于梯度的方法来优化。
这个问题直到感知机提出后近30年才被人解决(bp算法的提出),分类算法的才得以破冰,之所以这么久没有人想出来,是因为人们很难跳出离散输出的定式思维。这是理念上的变革。
就像bengio说:可见很多看起来显而易见的想法只有在事后才变得显而易见。
如果在阅读中有疑惑或者发现有写的不对的地方,欢迎大家提出来。
我的邮箱:1726341330@qq.com
转载请注明出处https://blog.csdn.net/qq_44899812/article/details/102635736