在上一篇文章中,咱们已经学会了编写一个简单的感知器,并用它来实现一个线性分类器。你应该还记得用来训练感知器的『感知器规则』。然而,咱们并无关心这个规则是怎么获得的。本文经过介绍另一种『感知器』,也就是『线性单元』,来讲明关于机器学习一些基本的概念,好比模型、目标函数、优化算法等等。这些概念对于全部的机器学习算法来讲都是通用的,掌握了这些概念,就掌握了机器学习的基本套路。python
感知器有一个问题,当面对的数据集不是线性可分的时候,『感知器规则』可能没法收敛,这意味着咱们永远也没法完成一个感知器的训练。为了解决这个问题,咱们使用一个可导的线性函数来替代感知器的阶跃函数,这种感知器就叫作线性单元。线性单元在面对线性不可分的数据集时,会收敛到一个最佳的近似上。git
为了简单起见,咱们能够设置线性单元的激活函数为程序员
这样的线性单元以下图所示github
对比此前咱们讲过的感知器算法
这样替换了激活函数以后,线性单元将返回一个实数值而不是0,1分类。所以线性单元用来解决回归问题而不是分类问题。编程
当咱们说模型时,咱们实际上在谈论根据输入预测输出的算法。好比,能够是一我的的工做年限,能够是他的月薪,咱们能够用某种算法来根据一我的的工做年限来预测他的收入。好比:网络
函数叫作假设,而、是它的参数。咱们假设参数,参数,若是一我的的工做年限是5年的话,咱们的模型会预测他的月薪为python2.7
你也许会说,这个模型太不靠谱了。是这样的,由于咱们考虑的因素太少了,仅仅包含了工做年限。若是考虑更多的因素,好比所处的行业、公司、职级等等,可能预测就会靠谱的多。咱们把工做年限、行业、公司、职级这些信息,称之为特征。对于一个工做了5年,在IT行业,百度工做,职级T6这样的人,咱们能够用这样的一个特征向量来表示他机器学习
= (5, IT, 百度, T6)。ide
既然输入变成了一个具有四个特征的向量,相对应的,仅仅一个参数就不够用了,咱们应该使用4个参数,每一个特征对应一个。这样,咱们的模型就变成
其中,对应工做年限,对应行业,对应公司,对应职级。
为了书写和计算方便,咱们能够令等于,同时令对应于特征。因为其实并不存在,咱们能够令它的值永远为1。也就是说
这样上面的式子就能够写成
咱们还能够把上式写成向量的形式
长成这种样子模型就叫作线性模型,由于输出就是输入特征的线性组合。
接下来,咱们须要关心的是这个模型如何训练,也就是参数取什么值最合适。
机器学习有一类学习方法叫作监督学习,它是说为了训练一个模型,咱们要提供这样一堆训练样本:每一个训练样本既包括输入特征,也包括对应的输出(也叫作标记,label)。也就是说,咱们要找到不少人,咱们既知道他们的特征(工做年限,行业...),也知道他们的收入。咱们用这样的样本去训练模型,让模型既看到咱们提出的每一个问题(输入特征),也看到对应问题的答案(标记)。当模型看到足够多的样本以后,它就能总结出其中的一些规律。而后,就能够预测那些它没看过的输入所对应的答案了。
另一类学习方法叫作无监督学习,这种方法的训练样本中只有而没有。模型能够总结出特征的一些规律,可是没法知道其对应的答案。
不少时候,既有又有的训练样本是不多的,大部分样本都只有。好比在语音到文本(STT)的识别任务中,是语音,是这段语音对应的文本。咱们很容易获取大量的语音录音,然而把语音一段一段切分好并标注上对应文字则是很是费力气的事情。这种状况下,为了弥补带标注样本的不足,咱们能够用无监督学习方法先作一些聚类,让模型总结出哪些音节是类似的,而后再用少许的带标注的训练样本,告诉模型其中一些音节对应的文字。这样模型就能够把类似的音节都对应到相应文字上,完成模型的训练。
如今,让咱们只考虑监督学习。
在监督学习下,对于一个样本,咱们知道它的特征,以及标记。同时,咱们还能够根据模型计算获得输出。注意这里面咱们用表示训练样本里面的标记,也就是实际值;用带上划线的表示模型计算的出来的预测值。咱们固然但愿模型计算出来的和越接近越好。
数学上有不少方法来表示的和的接近程度,好比咱们能够用和的差的平方的来表示它们的接近程度
咱们把叫作单个样本的偏差。至于为何前面要乘,是为了后面计算方便。
训练数据中会有不少样本,好比个,咱们能够用训练数据中全部样本的偏差的和,来表示模型的偏差,也就是
上式的表示第一个样本的偏差,表示第二个样本的偏差......。
咱们还能够把上面的式子写成和式的形式。使用和式,不光书写起来简单,逼格也跟着暴涨,一箭双雕。因此必定要写成下面这样
其中
(式2)中,表示第个训练样本的特征,表示第个样本的标记,咱们也能够用元组表示第训练样本。则是模型对第个样本的预测值。
咱们固然但愿对于一个训练数据集来讲,偏差最小越好,也就是(式2)的值越小越好。对于特定的训练数据集来讲,的值都是已知的,因此(式2)实际上是参数的函数。
因而可知,模型的训练,实际上就是求取到合适的,使(式2)取得最小值。这在数学上称做优化问题,而就是咱们优化的目标,称之为目标函数。
大学时咱们学过怎样求函数的极值。函数的极值点,就是它的导数的那个点。所以咱们能够经过解方程,求得函数的极值点。
不过对于计算机来讲,它可不会解方程。可是它能够凭借强大的计算能力,一步一步的去把函数的极值点『试』出来。以下图所示:
首先,咱们随便选择一个点开始,好比上图的点。接下来,每次迭代修改的为,通过数次迭代后最终达到函数最小值点。
你可能要问了,为啥每次修改的值,都能往函数最小值那个方向前进呢?这里的奥秘在于,咱们每次都是向函数的梯度的相反方向来修改。什么是梯度呢?翻开大学高数课的课本,咱们会发现梯度是一个向量,它指向函数值上升最快的方向。显然,梯度的反方向固然就是函数值降低最快的方向了。咱们每次沿着梯度相反方向去修改的值,固然就能走到函数的最小值附近。之因此是最小值附近而不是最小值那个点,是由于咱们每次移动的步长不会那么恰到好处,有可能最后一次迭代走远了越过了最小值那个点。步长的选择是门手艺,若是选择小了,那么就会迭代不少轮才能走到最小值附近;若是选择大了,那可能就会越过最小值很远,收敛不到一个好的点上。
按照上面的讨论,咱们就能够写出梯度降低算法的公式
其中,是梯度算子,就是指的梯度。是步长,也称做学习速率。
对于上一节列出的目标函数(式2)
梯度降低算法能够写成
聪明的你应该能想到,若是要求目标函数的最大值,那么咱们就应该用梯度上升算法,它的参数修改规则是
下面,请先作几回深呼吸,让你的大脑补充足够的新鲜的氧气,咱们要来求取,而后带入上式,就能获得线性单元的参数修改规则。
关于的推导过程,我单独把它们放到一节中。您既能够选择慢慢看,也能够选择无视。在这里,您只须要知道,通过一大串推导,目标函数的梯度是
所以,线性单元的参数修改规则最后是这个样子
有了上面这个式子,咱们就能够根据它来写出训练线性单元的代码了。
须要说明的是,若是每一个样本有M个特征,则上式中的都是M+1维向量(由于咱们加上了一个恒为1的虚拟特征,参考前面的内容),而是标量。用高逼格的数学符号表示,就是
为了让您看明白说的是啥,我吐血写下下面这个解释(写这种公式可累可累了)。由于是M+1维列向量,因此(式3)能够写成
若是您仍是没看明白,建议您也吐血再看一下大学时学过的《线性代数》吧。
这一节你尽能够跳过它,并不太会影响到全文的理解。固然若是你非要弄明白每一个细节,那恭喜你骚年,机器学习的将来必定是属于你的。
首先,咱们先作一个简单的前戏。咱们知道函数的梯度的定义就是它相对于各个变量的偏导数,因此咱们写下下面的式子
可接下来怎么办呢?咱们知道和的导数等于导数的和,因此咱们能够先把求和符号里面的导数求出来,而后再把它们加在一块儿就好了,也就是
如今咱们能够无论高大上的了,先专心把里面的导数求出来。
咱们知道,是与无关的常数,而,下面咱们根据链式求导法则来求导(上大学时好像叫复合函数求导法则)
咱们分别计算上式等号右边的两个偏导数
代入,咱们求得里面的偏导数是
最后代入,求得
至此,大功告成。
若是咱们根据(式3)来训练模型,那么咱们每次更新的迭代,要遍历训练数据中全部的样本进行计算,咱们称这种算法叫作批梯度降低(Batch Gradient Descent)。若是咱们的样本很是大,好比数百万到数亿,那么计算量异常巨大。所以,实用的算法是SGD算法。在SGD算法中,每次更新的迭代,只计算一个样本。这样对于一个具备数百万样本的训练数据,完成一次遍历就会对更新数百万次,效率大大提高。因为样本的噪音和随机性,每次更新并不必定按照减小的方向。然而,虽然存在必定随机性,大量的更新整体上沿着减小的方向前进的,所以最后也能收敛到最小值附近。下图展现了SGD和BGD的区别
如上图,椭圆表示的是函数值的等高线,椭圆中心是函数的最小值点。红色是BGD的逼近曲线,而紫色是SGD的逼近曲线。咱们能够看到BGD是一直向着最低点前进的,而SGD明显躁动了许多,但整体上仍然是向最低点逼近的。
最后须要说明的是,SGD不只仅效率高,并且随机性有时候反而是好事。今天的目标函数是一个『凸函数』,沿着梯度反方向就能找到全局惟一的最小值。然而对于非凸函数来讲,存在许多局部最小值。随机性有助于咱们逃离某些很糟糕的局部最小值,从而得到一个更好的模型。
完整代码请参考GitHub: https://github.com/hanbt/learn_dl/blob/master/linear_unit.py (python2.7)
接下来,让咱们撸一把代码。
由于咱们已经写了感知器的代码,所以咱们先比较一下感知器模型和线性单元模型,看看哪些代码可以复用。
算法 | 感知器 | 线性单元 |
---|---|---|
模型 | ||
训练规则 |
比较的结果使人震惊,原来除了激活函数不一样以外,二者的模型和训练规则是同样的(在上表中,线性单元的优化算法是SGD算法)。那么,咱们只须要把感知器的激活函数进行替换便可。感知器的代码请参考上一篇文章零基础入门深度学习(1) - 感知器,这里就再也不重复了。对于一个养成良好习惯的程序员来讲,重复代码是不可忍受的。你们应该把代码保存在一个代码库中(好比git)。
from perceptron import Perceptron #定义激活函数f f = lambda x: x class LinearUnit(Perceptron): def __init__(self, input_num): '''初始化线性单元,设置输入参数的个数''' Perceptron.__init__(self, input_num, f)
经过继承Perceptron,咱们仅用几行代码就实现了线性单元。这再次证实了面向对象编程范式的强大。
接下来,咱们用简单的数据进行一下测试。
def get_training_dataset(): ''' 捏造5我的的收入数据 ''' # 构建训练数据 # 输入向量列表,每一项是工做年限 input_vecs = [[5], [3], [8], [1.4], [10.1]] # 指望的输出列表,月薪,注意要与输入一一对应 labels = [5500, 2300, 7600, 1800, 11400] return input_vecs, labels def train_linear_unit(): ''' 使用数据训练线性单元 ''' # 建立感知器,输入参数的特征数为1(工做年限) lu = LinearUnit(1) # 训练,迭代10轮, 学习速率为0.01 input_vecs, labels = get_training_dataset() lu.train(input_vecs, labels, 10, 0.01) #返回训练好的线性单元 return lu if __name__ == '__main__': '''训练线性单元''' linear_unit = train_linear_unit() # 打印训练得到的权重 print linear_unit # 测试 print 'Work 3.4 years, monthly salary = %.2f' % linear_unit.predict([3.4]) print 'Work 15 years, monthly salary = %.2f' % linear_unit.predict([15]) print 'Work 1.5 years, monthly salary = %.2f' % linear_unit.predict([1.5]) print 'Work 6.3 years, monthly salary = %.2f' % linear_unit.predict([6.3])
程序运行结果以下图
拟合的直线以下图
事实上,一个机器学习算法其实只有两部分
所以,若是你想最简洁的介绍一个算法,列出这两个函数就好了。
接下来,你会用优化算法去求取目标函数的最小(最大)值。[随机]梯度{降低|上升}算法就是一个优化算法。针对同一个目标函数,不一样的优化算法会推导出不一样的训练规则。咱们后面还会讲其它的优化算法。
其实在机器学习中,算法每每并非关键,真正的关键之处在于选取特征。选取特征须要咱们人类对问题的深入理解,经验、以及思考。而神经网络算法的一个优点,就在于它可以自动学习到应该提取什么特征,从而使算法再也不那么依赖人类,而这也是神经网络之因此吸引人的一个方面。
如今,通过漫长的烧脑,你已经具有了学习神经网络的必备知识。下一篇文章,咱们将介绍本系列文章的主角:神经网络,以及用来训练神经网络的大名鼎鼎的算法:反向传播算法。至于如今,咱们应该暂时忘记一切,尽情奖励本身一下吧。
本想放个日料的,怕被说成不爱国,换成毛爷爷家的红烧肉吧:P