点击上方,选择星标或置顶,每天给你送干货
!
阅读大概需要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在法律领域的相关研究工作
好文!在看! |
|