代码+剖析 | 感知机原理剖析及实现

论坛 期权论坛 期权     
深度学习自然语言处理   2019-6-15 11:34   1796   0
点击上方,选择星标置顶,每天给你送干货

阅读大概需要17分钟

跟随小博主,每天进步一丢丢
知乎地址:https://zhuanlan.zhihu.com/p/51197993
个人主页: http://www.pkudodo.com

前言
《统计学习方法》一书在前几天正式看完,由于这本书在一定程度上对于初学者是有一些难度的,趁着热乎劲把自己走过的弯路都写出来,后人也能走得更顺畅一点。
以下是我的github地址,其中有《统计学习方法》书中所有涉及到的算法的实现,也是在写文章的同时编写的。在编写宗旨上是希望所有人看完书以后,按照程序的思路再配合书中的公式,能知道怎么讲所学的都应用上。(有点自恋地讲)程序中的注释可能比大部分的博客内容都多。希望大家能够多多捧场,如果可以的话,为我的github打颗星,也能让更多的人看到。
github:
GitHub|手写实现李航《统计学习方法》书中全部算法
https://link.zhihu.com/?target=https%3A//github.com/Dod-o/Statistical-Learning-Method_Code

正文
感知机的直观理解
感知机应该属于机器学习算法中最简单的一种算法,其原理可以看下图:

比如说我们有一个坐标轴(图中的黑色线),横为x1轴,竖为x2轴。图中的每一个点都是由(x1,x2)坐标决定的。如果我们将这张图应用在判断零件是否合格上,x1表示零件长度,x2表示零件质量,坐标轴表示零件的均值长度和均值重量,并且蓝色的为合格产品,黄色为劣质产品,需要检测剔除。那么很显然如果零件的长度和重量都大于均值,说明这个零件是合格的。也就是在第一象限的所有蓝色点。反之如果两项都小于均值,就是劣质的,比如在第三象限的黄色点。
预测其实简单:拿到一个新的零件,我们测出它的长度x1,质量x2,如果两项都大于均值,说明零件合格。这就是我们人的人工智能。
那么程序怎么知道长度重量都大于均值的零件就是合格的呢?
或者说
它是怎么学会这个规则的呢?
程序训练时输入的是当前图里所有点的信息以及标签,也就是说它知道所有样本x的坐标为(x1, x2),同时它属于蓝色或黄色。对于目前手里的这些点,要是能找到一条直线把它们分开就好了,这样我拿到一个新的零件,知道了它的质量和重量,我就可以判断它在线的哪一侧,就可以知道它可能属于好的或坏的零件了。例如图里的黄、蓝、粉三条线,都可以完美地把当前的两种情况划分开。甚至x1坐标轴或x2坐标轴都能成为一个划分直线(这两个直线均能把所有点正确地分开)。
读者也看到了,对于图中的两堆点,我们有无数条直线可以将其划分开,事实上我们不光要能划分当前的点,当新来的点进来是,也要能很好地将其划分,所以哪条线最好呢?
怎样一条直线属于最佳的划分直线?实际上感知机无法找到一条最佳的直线,它找到的可能是图中所有画出来的线,只要能把所有的点都分开就好了。
[h1]得出结论:[/h1]如果一条直线能够不分错任何一个点,那就是一条好的直线
进一步来说:
如果我们把所有分错的点到直线的距离求和,让分错点的距离和最小(最好是0,这样就表示没有分错的点了),这条直线就是我们要找的。
[h1]感知机的数学角度(配合《统计学习方法》食用更佳)[/h1]首先我们确定一下终极目标:甭管找最佳划分直线啥中间乱七八糟的步骤,反正最后生成一个函数f(x),当我们把新的一个数据x扔进函数以后,它会预测告诉我这是蓝的还是黄的,多简单啊。所以我们不要去考虑中间过程,先把结果定了。

瞧,f(x)不是出来了嘛,sign是啥?wx+b是啥?别着急,我们再看一下sigin函数是什么。

sign好像很简单,当x大于等于0,sign输出1,否则输出-1。那么往前递归一下,wx+b如果大于等于0,f(x)就等于1,反之f(x)等于-1。
那么wx+b是啥?
它就是那条最优的直线。我们把这个公式放在二维情况下看,二维中的直线是这样定义的:y=ax+b。在二维中,w就是a,b还是b。所以wx+b是一条直线(比如说本文最开始那张图中的蓝线)。如果新的点x在蓝线左侧,那么wx+b|wx+b|,求所有误分类点的距离和,也就是求|wx+b|的总和,让它最小化。
等等.....最小化?很简单啊,不用去努力减少分错点的距离和,把w和b等比例缩小就好啦,比如说w改为0.5w,b改为0.5b,线还是那条线,但是值缩小两倍啦!你还不满意?我可以接着缩!缩到0去!所以啊,我们要加点约束,让整个式子除以w的模长。啥意思?就是w不管怎么样,要除以它的单位长度。如果我w和b等比例缩小,那||w||也会等比例缩小,值一动不动,很稳。没有除以模长之前,|wx+b|叫函数间隔,除模长之后叫几何间隔,几何间隔可以认为是物理意义上的实际长度,管你怎么放大缩小,你物理距离就那样,不可能改个数就变。在机器学习中求距离时,通常是使用几何间隔的,否则无法求出解。

对于误分类的数据,例如实际应该属于蓝色的点(线的右侧,y>0),但实际上预测出来是在左侧(wx+b=5的作为1,= 5:            labelArr.append(1)        else:            labelArr.append(-1)        #存放标记        #[int(num) for num in curLine[1:]] -> 遍历每一行中除了以第一哥元素(标记)外将所有元素转换成int类型        #[int(num)/255 for num in curLine[1:]] -> 将所有数据除255归一化(非必须步骤,可以不归一化)        dataArr.append([int(num)/255 for num in curLine[1:]])    #返回data和label    return dataArr, labelArrdef perceptron(dataArr, labelArr, iter=50):    '''    感知器训练过程    :param dataArr:训练集的数据 (list)    :param labelArr: 训练集的标签(list)    :param iter: 迭代次数,默认50    :return: 训练好的w和b    '''    print('start to trans')    #将数据转换成矩阵形式(在机器学习中因为通常都是向量的运算,转换称矩阵形式方便运算)    #转换后的数据中每一个样本的向量都是横向的    dataMat = np.mat(dataArr)    #将标签转换成矩阵,之后转置(.T为转置)。    #转置是因为在运算中需要单独取label中的某一个元素,如果是1xN的矩阵的话,无法用label的方式读取    #对于只有1xN的label可以不转换成矩阵,直接label即可,这里转换是为了格式上的统一    labelMat = np.mat(labelArr).T    #获取数据矩阵的大小,为m*n    m, n = np.shape(dataMat)    #创建初始权重w,初始值全为0。    #np.shape(dataMat)的返回值为m,n -> np.shape(dataMat)[1])的值即为n,与    #样本长度保持一致    w = np.zeros((1, np.shape(dataMat)[1]))    #初始化偏置b为0    b = 0    #初始化步长,也就是梯度下降过程中的n,控制梯度下降速率    h = 0.0001    #进行iter次迭代计算    for k in range(iter):        #对于每一个样本进行梯度下降        #李航书中在2.3.1开头部分使用的梯度下降,是全部样本都算一遍以后,统一        #进行一次梯度下降        #在2.3.1的后半部分可以看到(例如公式2.6 2.7),求和符号没有了,此时用        #的是随机梯度下降,即计算一个样本就针对该样本进行一次梯度下降。        #两者的差异各有千秋,但较为常用的是随机梯度下降。        for i in range(m):            #获取当前样本的向量            xi = dataMat            #获取当前样本所对应的标签            yi = labelMat            #判断是否是误分类样本            #误分类样本特诊为: -yi(w*xi+b)>=0,详细可参考书中2.2.2小节            #在书的公式中写的是>0,实际上如果=0,说明改点在超平面上,也是不正确的            if -1 * yi * (w * xi.T + b) >= 0:                #对于误分类样本,进行梯度下降,更新w和b                w = w + h *  yi * xi                b = b + h * yi        #打印训练进度        print('Round %d:%d training' % (k, iter))    #返回训练完的w、b    return w, bdef test(dataArr, labelArr, w, b):    '''    测试准确率    :param dataArr:测试集    :param labelArr: 测试集标签    :param w: 训练获得的权重w    :param b: 训练获得的偏置b    :return: 正确率    '''    print('start to test')    #将数据集转换为矩阵形式方便运算    dataMat = np.mat(dataArr)    #将label转换为矩阵并转置,详细信息参考上文perceptron中    #对于这部分的解说    labelMat = np.mat(labelArr).T    #获取测试数据集矩阵的大小    m, n = np.shape(dataMat)    #错误样本数计数    errorCnt = 0    #遍历所有测试样本    for i in range(m):        #获得单个样本向量        xi = dataMat        #获得该样本标记        yi = labelMat        #获得运算结果        result = -1 * yi * (w * xi.T + b)        #如果-yi(w*xi+b)>=0,说明该样本被误分类,错误样本数加一        if result >= 0: errorCnt += 1    #正确率 = 1 - (样本分类错误数 / 样本总数)    accruRate = 1 - (errorCnt / m)    #返回正确率    return accruRateif __name__ == '__main__':    #获取当前时间    #在文末同样获取当前时间,两时间差即为程序运行时间    start = time.time()    #获取训练集及标签    trainData, trainLabel = loadData('../Mnist/mnist_train.csv')    #获取测试集及标签    testData, testLabel = loadData('../Mnist/mnist_test.csv')    #训练获得权重    w, b = perceptron(trainData, trainLabel, iter = 30)    #进行测试,获得正确率    accruRate = test(testData, testLabel, w, b)    #获取当前时间,作为结束时间    end = time.time()    #显示正确率    print('accuracy rate is:', accruRate)    #显示用时时长    print('time span:', end - start)




推荐阅读:
一大批历史精彩文章啦
详解Transition-based Dependency parser基于转移的依存句法解析器
干货 | 找工作的经验总结(一)
经验 | 初入NLP领域的一些小建议
学术 | 如何写一篇合格的NLP论文
干货 | 那些高产的学者都是怎样工作的?
是时候研读一波导师的论文--一个简单有效的联合模型
近年来NLP在法律领域的相关研究工作




好文!在看!
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:
帖子:
精华:
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP