什么是线性单元
感知器有一个问题,当面对的数据集不是线性可分的时候,『感知器规则』可能无法收敛,这意味着我们永远也无法完成一个感知器的训练。为了解决这个问题,我们使用一个可导的线性函数来替代感知器的阶跃函数,这种感知器就叫做线性单元。线性单元在面对线性不可分的数据集时,会收敛到一个最佳的近似上。
为了简单起见,我们可以设置线性单元的激活函数
为
这样的线性单元如下图所示
这样替换了激活函数 之后,线性单元将返回一个实数值而不是 分类。因此线性单元用来解决 回归问题而不是 分类问题。
线性单元的模型
当我们说模型时,我们实际上在谈论根据输入
预测输出
的算法。比如,
可以是一个人的工作年限,
可以是他的月薪,我们可以用某种算法来根据一个人的工作年限来预测他的收入。比如:
函数 叫做假设,而 , 是它的参数。我们假设参数 ,参数 ,如果一个人的工作年限是 年的话,我们的模型会预测他的月薪为
很显然,这个模型太不靠谱了。是这样的,因为我们考虑的因素太少了,仅仅包含了工作年限。如果考虑更多的因素,比如所处的行业、公司、职级等等,可能预测就会靠谱的多。我们把工作年限、行业、公司、职级这些信息,称之为特征。对于一个工作了 年,在 行业,百度工作,职级 这样的人,我们可以用这样的一个特征向量来表示他
既然输入 变成了一个具备四个特征的向量,相对应的,仅仅一个参数 就不够用了,我们应该使用 个参数 ,每个特征对应一个。这样,我们的模型就变成
其中, 对应工作年限, 对应行业, 对应公司, 对应职级。
为了书写和计算方便,我们可以令 等于 ,同时令 对应于特征 。由于 其实并不存在,我们可以令它的值永远为 。也就是说
这样上面的式子就可以写成
我们还可以把上式写成向量的形式
长成这种样子模型就叫做线性模型,因为输出 就是输入特征 的线性组合。
监督学习和无监督学习
接下来,我们需要关心的是这个模型如何训练,也就是参数
取什么值最合适。
机器学习有一类学习方法叫做监督学习,它是说为了训练一个模型,我们要提供这样一堆训练样本:每个训练样本既包括输入特征
,也包括对应的输出
(
也叫做标记,
)。也就是说,我们要找到很多人,我们既知道他们的特征(工作年限,行业,
),也知道他们的收入。我们用这样的样本去训练模型,让模型既看到我们提出的每个问题(输入特征
),也看到对应问题的答案(标记
)。当模型看到足够多的样本之后,它就能总结出其中的一些规律。然后,就可以预测那些它没看过的输入所对应的答案了。
另外一类学习方法叫做无监督学习,这种方法的训练样本中只有 而没有 。模型可以总结出特征 的一些规律,但是无法知道其对应的答案 。
很多时候,既有 又有 的训练样本是很少的,大部分样本都只有 。比如在语音到文本(STT)的识别任务中, 是语音, 是这段语音对应的文本。我们很容易获取大量的语音录音,然而把语音一段一段切分好并标注上对应文字则是非常费力气的事情。这种情况下,为了弥补带标注样本的不足,我们可以用无监督学习方法先做一些聚类,让模型总结出哪些音节是相似的,然后再用少量的带标注的训练样本,告诉模型其中一些音节对应的文字。这样模型就可以把相似的音节都对应到相应文字上,完成模型的训练。
线性单元的目标函数
现在,让我们只考虑监督学习。
在监督学习下,对于一个样本,我们知道它的特征 ,以及标记 。同时,我们还可以根据模型 计算得到输出 。注意这里面我们用 表示训练样本里面的标记,也就是实际值;用带上划线的 表示模型计算的出来的预测值。我们当然希望模型计算出来的 和 越接近越好。
数学上有很多方法来表示的
和
的接近程度,比如我们可以用
和
的差的平方的
来表示它们的接近程度
我们把 叫做单个样本的误差。至于为什么前面要乘 ,是为了后面计算方便。
训练数据中会有很多样本,比如
个,我们可以用训练数据中所有样本的误差的和,来表示模型的误差
,也就是
上式的 表示第一个样本的误差, 表示第二个样本的误差, 。
我们还可以把上面的式子写成和式的形式。使用和式,不光书写起来简单,逼格也跟着暴涨,一举两得。所以一定要写成下面这样
其中
(式2)中, 表示第 个训练样本的特征, 表示第 个样本的标记,我们也可以用元组 表示第 训练样本。 则是模型对第 个样本的预测值。
我们当然希望对于一个训练数据集来说,误差最小越好,也就是(式2)的值越小越好。对于特定的训练数据集来说, 的值都是已知的,所以(式2)其实是参数 的函数。
由此可见,模型的训练,实际上就是求取到合适的 ,使(式2)取得最小值。这在数学上称作优化问题,而 就是我们优化的目标,称之为目标函数。
梯度下降优化算法
大学时我们学过怎样求函数的极值。函数 的极值点,就是它的导数 的那个点。因此我们可以通过解方程 ,求得函数的极值点 。
不过对于计算机来说,它可不会解方程。但是它可以凭借强大的计算能力,一步一步的去把函数的极值点『试』出来。如下图所示:
首先,我们随便选择一个点开始,比如上图的 点。接下来,每次迭代修改 的为 ,经过数次迭代后最终达到函数最小值点。
你可能要问了,为啥每次修改 的值,都能往函数最小值那个方向前进呢?这里的奥秘在于,我们每次都是向函数 的梯度的相反方向来修改 。什么是梯度呢?现梯度是一个向量,它指向函数值上升最快的方向。显然,梯度的反方向当然就是函数值下降最快的方向了。我们每次沿着梯度相反方向去修改 的值,当然就能走到函数的最小值附近。之所以是最小值附近而不是最小值那个点,是因为我们每次移动的步长不会那么恰到好处,有可能最后一次迭代走远了越过了最小值那个点。步长的选择是门手艺,如果选择小了,那么就会迭代很多轮才能走到最小值附近;如果选择大了,那可能就会越过最小值很远,收敛不到一个好的点上。
按照上面的讨论,我们就可以写出梯度下降算法的公式
其中, 是梯度算子, 就是指 的梯度。 是步长,也称作学习速率。
对于上一节列出的目标函数(式2)
梯度下降算法可以写成
如果要求目标函数的最大值,那么我们就应该用梯度上升算法,它的参数修改规则是
下面我们要来求取 ,然后带入上式,就能得到线性单元的参数修改规则。
关于
的推导过程,我单独把它们放到一节中。您既可以选择慢慢看,也可以选择无视。在这里,您只需要知道,经过一大串推导,目标函数
的梯度是
因此,线性单元的参数修改规则最后是这个样子
有了上面这个式子,我们就可以根据它来写出训练线性单元的代码了。
需要说明的是,如果每个样本有
个特征,则上式中的
都是
维向量(因为我们加上了一个恒为
的虚拟特征
,参考前面的内容),而
是标量。用高逼格的数学符号表示,就是
因为 是 维向量,所以(式3)可以写成
的推导
首先,我们先做一个简单的前戏。我们知道函数的梯度的定义就是它相对于各个变量的偏导数,所以我们写下下面的式子
可接下来怎么办呢?我们知道和的导数等于导数的和,所以我们可以先把求和符号 里面的导数求出来,然后再把它们加在一起就行了,也就是
现在我们可以不管高大上的 了,先专心把里面的导数求出来。
我们知道, 是与 无关的常数,而 ,下面我们根据链式求导法则来求导
我们分别计算上式等号右边的两个偏导数
代入,我们求得 里面的偏导数是
最后代入 ,求得
至此,大功告成。
随机梯度下降算法(Stochastic Gradient Descent, SGD)
如果我们根据(式3)来训练模型,那么我们每次更新
的迭代,要遍历训练数据中所有的样本进行计算,我们称这种算法叫做批梯度下降(Batch Gradient Descent)。如果我们的样本非常大,比如数百万到数亿,那么计算量异常巨大。因此,实用的算法是SGD算法。在SGD算法中,每次更新
的迭代,只计算一个样本。这样对于一个具有数百万样本的训练数据,完成一次遍历就会对
更新数百万次,效率大大提升。由于样本的噪音和随机性,每次更新
并不一定按照减少
的方向。然而,虽然存在一定随机性,大量的更新总体上沿着减少
的方向前进的,因此最后也能收敛到最小值附近。下图展示了SGD和BGD的区别
如上图,椭圆表示的是函数值的等高线,椭圆中心是函数的最小值点。红色是BGD的逼近曲线,而紫色是SGD的逼近曲线。我们可以看到BGD是一直向着最低点前进的,而SGD明显躁动了许多,但总体上仍然是向最低点逼近的。
最后需要说明的是,SGD不仅仅效率高,而且随机性有时候反而是好事。今天的目标函数是一个『凸函数』,沿着梯度反方向就能找到全局唯一的最小值。然而对于非凸函数来说,存在许多局部最小值。随机性有助于我们逃离某些很糟糕的局部最小值,从而获得一个更好的模型。
实现线性单元
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
from perceptron import Perceptron
#定义激活函数f
f = lambda x: x
class LinearUnit(Perceptron):
def __init__(self, input_num):
'''初始化线性单元,设置输入参数的个数'''
Perceptron.__init__(self, input_num, f)
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
def plot(linear_unit):
import matplotlib.pyplot as plt
input_vecs, labels = get_training_dataset()
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(map(lambda x: x[0], input_vecs), labels)
weights = linear_unit.weights
bias = linear_unit.bias
x = range(0,12,1)
y = map(lambda x:weights[0] * x + bias, x)
ax.plot(x, y)
plt.show()
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])
plot(linear_unit)
Perceptron的代码见: 实现感知器
小结
事实上,一个机器学习算法其实只有两部分
- 模型。从输入特征 预测输入 的那个函数
- 目标函数。目标函数取最小(最大)值时所对应的参数值,就是模型的参数的最优值。很多时候我们只能获得目标函数的局部最小(最大)值,因此也只能得到模型参数的局部最优值。
因此,如果你想最简洁的介绍一个算法,列出这两个函数就行了。
接下来,你会用优化算法去求取目标函数的最小(最大)值。[随机]梯度{下降|上升}算法就是一个优化算法。针对同一个目标函数,不同的优化算法会推导出不同的训练规则。我们后面还会讲其它的优化算法。
其实在机器学习中,算法往往并不是关键,真正的关键之处在于选取特征。选取特征需要我们人类对问题的深刻理解,经验、以及思考。而神经网络算法的一个优势,就在于它能够自动学习到应该提取什么特征,从而使算法不再那么依赖人类,而这也是神经网络之所以吸引人的一个方面。
转载:线性单元和梯度下降