背景
学习深度学习已经一段时间了,但是学习过程中总觉得缺了点什么,无从动手编程。因此,我还是希望使用写文章的方式来总结自己所学,让自己在这个过程中明白其中的知识点。也希望过段时间自己遗忘什么关键的知识点,能在这个笔记中找回记忆。这是个人的学习笔记,如果出了问题请在https://www.jianshu.com/p/87089a152327指出,谢谢。
前言
我将会跟着斯坦福机器学习的公开课以及在吴恩达网易课上的课程两者结合,梳理一遍自己的所学。这一章节,总结的是梯度下降算法。这个算法在机器学习的监督学习中属于入门级别的算法,很多更加深入的算法有这个算法参与进来。因此这个算法也是第一个总结。
目标
这篇文章的目标是,了解梯度下降的数学原理,以及完成几个吴恩达的作业例子。
总览
我们来思考一个场景。当我们知道一个地段中各种面积的房屋对应的销售价格,当我们想要在同一个地段销售一种面积大小房屋的时候,那对应要把这个价格定在哪里比较合适。
或者用机器学习的话来说,假设影响房屋价格的因素只有房屋大小,让我们预测一下在给定某个房屋大小情况下,这个地段将会销售多少。
这很典型是机器学习中的回归问题,我们往往会选择监督学习,来预测房屋价格。而梯度下降就是解决这种问题的主要方式之一。
这里总结一下,机器学习的定义:
计算机程序从经验E中学习,去解决某一个任务T,有一个用于性能度量的P,通过P对T的测定,它使得在计算机在解决T上的表现上因经验E而提高。
原话有点直接翻译有点拗口,这里稍微修饰一下。
同时,现在的机器学习,粗略划分大致上有两类,第一类是监督学习,第二类是无监督学习。
监督学习的标志,往往是,我们知道一大堆样本数据,而这个数据本身带有预测结果。让人类去教导机器去学习。这种算法,有逻辑回归等。
无监督学习,和上面区别最很明显的特征是,我们获取的一大堆数据,本身是不带有结果标签的。让机器自己去学习,数据里面的特征。这种算法,有k-means(有的叫k-最近邻)等。
而我要总结的梯度下降就是监督学习中基础也是重点。
关于样本的拟合看法
上面说了回归问题,一般往往指代的是线性回归问题。按照上面的说法,我们需要创造一个函数曲线,让这个函数曲线尽可能的和样本数据拟合。
有点像我们初中学的,在坐标系中给一堆有规律的点,自己画一条一元一次的直线尽可能的经过多一点的点。当然现实的情况不可能只会构造一元一次的直线,这样不太可能满足现实的要求。这种情况,用数学的话来说就是我们想要找到一个函数在坐标系上的表现尽可能拟合样本。
但是过度拟合样本上的点,就会出现过拟合的问题。比如一堆杂乱的点(并没有明显的一元一次函数的特征),我们可以选择一次和更高次的函数,更高次的函数在坐标系上表现上更加的弯曲,更容易拟合这些样本。是不是我们就要一开始创造很高次的函数呢?这么做的后果就是一旦我们有了要预测的真实数据,通俗来讲,由于过于拟合样本,反而失去了这个函数的兼容性,导致可能会预测出现很大的偏差。
当然还有欠拟合的情况,就是创造的函数过于简单,导致和大部分的样本数据无法很好的拟合从而导致创造出来的预测的结果出问题。
那我们究竟要怎么找到一个函数,可以很好的拟合这些样本数据呢?
正文
假设我们现在有这么一个函数
此时θ为参数,是我们需要求得的一元函数的系数,x称为特征,是组成训练集中不同的属性。
我们该怎么选择与让这个我们选择一元一次函数尽可能拟合样本数据呢。
假设数据是这样的,很有一元一次函数分布的点,我们通过人眼能够的观察,能够画一条接近结果函数直线出来
通过坐标系可以直观的想象到,假如我们的直线,每个点距离这条直线的距离最短的话,就是我们心中最为理想的直线。这种做法叫做线性回归。
这里就引出了一个新的概念,代价函数。
代价函数
代价函数(Cost Function )是定义在整个训练集上的,是所有样本误差的平均,也就是损失函数的平均。
损失函数(Loss Function )是定义在单个样本上的,算的是一个样本的误差
在损失函数中实际上有很多损失函数可以让我们选择:
- 0-1损失函数;
- 平方损失函数;
- 绝对值损失函数;
- 对数损失函数;
- 指数损失函数...
根据上面的定义,我们可以通过代价函数在数学上表达出每个点距离我们心目中理想的直线误差是多少,从而想办法缩小每个样本距离误差,来确定与值,从而确定一元一次函数是什么。
吴恩达教授在这种情况下选择平方损失函数。下面是这个公式,我们用 来表示这个损失函数,此时有m个训练用的数据集,y为结果。
这里面的意思是先通过我们给的最初假设的函数,把整个训练集的x带入进去,此时假设函数得出的y‘和真实y之间的误差的平方,再每个训练点的误差的平方求和除 训练集合的大小*2(可以看成每个预测点和实际点均方误差的平均值)。
为什么选择这个代价函数,我们先不考虑学习的收敛速度,单单从公式上我对对机器学习中在这个情况下选择的理解:
- 从统计学上看,均方误差往往是用来表达估计量和被估计量之间的差异度。所以均方误差往往是最常见的代价函数。
-
从几何角度上看,还记得当年初中学的欧几里得距离的计算公式吧。
这是当年求两点之间距离的公式,由于此时的场景是我通过自己假设的公式当取一样的x的时候,求得y'和真实y之间的距离,最短那个即为所求。
得出这个结论之后,熟悉微积分的朋友就会知道,如果我们想要 函数极小化(获得全局/局部的最小值),只要对求导让其等于0即可(也就是斜率为0)。下面就是整个梯度下降的流程图
梯度下降
根据上文,梯度下降,只要通过上面的二次代价函数来不断的更新的值,就能获得我们理想的,也就能获得我们想要的函数。至于为什么不直接求出斜率为0时候,主要还是因为很多情况下如数据量很大,我们往往没办法直接求出斜率为0处的答案,往往通过更新的方式让不断往最小值移动,收敛导斜率为0处,让不再变动的时候,就代表这此时已经是极小值了。
更加直观的意义
用更加直观的方式来表达这个问题。用麻省理工教授的话来说,梯度下降其实就是一个爬山问题。我们站在山上的某一个点。此时我们想要一最快的速度下山,此时要做的是四处环绕,发现地势最陡峭的一般看起来就是下山最快的路,我们就会选择这条路下山。不从积分的角度上看,从程序算法的角度上看其实就是一个搜索问题。
但是这么做并不代表下山之后的位置一定是最低谷。这里取决于下山的位置(梯度下降的初始位置),和周边的陡峭度(斜率)。
下面是不通过积分的方式,直接通过python的搜索方式来完成一个爬山问题:
# 向下爬山
def hill_climb(X, Y):
global_x = []
global_y = []
len_x = len(X)
len_y = len(Y)
st_x = randint(0, len_x - 1)
st_y = randint(0, len_y - 1)
# 寻找周围的比当前小的值
def argmax(st_x, st_y, alisx=0, alisy=0):
print("X[0][st_x] %s" % (X[0][st_x]))
cur = func(X[0][st_x], Y[st_y][0])
next = func(X[0][st_x + alisx], Y[st_y + alisy][0])
print("cur : %f,next: %f" % (cur, next))
return cur > next
# 思路找出当前60个点 st 是指步数,当前的步数不能超过函数的范围st_x是x轴移动的坐标,st_y是y轴移动的坐标
while(len_x > st_x >= 0) or (len_y > st_y >= 0):
# 步数在范围内,并且x+1的时候比较和原来的大小
if st_x + 1 < len_x and argmax(st_x, st_y, 1):
st_x += 1
# 步数在范围内,并且y+1的时候比较和原来的大小
elif st_y + 1 < len_y and argmax(st_x, st_y, 0, 1):
st_y += 1
# 步数在范围内,并且x-1的时候比较和原来的大小
elif st_x >= 0 and argmax(st_x, st_y, -1):
st_x -= 1
# 步数在范围内,并且y-1的时候比较和原来的大小
elif st_y >= 0 and argmax(st_x, st_y, 0, -1):
st_y -= 1
else:
break
global_x.append(X[0][st_x])
global_y.append(Y[st_y][0])
return global_x, global_y, func(X[0][st_x], Y[st_y][0])
在这里模拟出一个特殊的中间高,两边低山峰的函数,通一段数据绘制这段函数。取随机一个点,进行下山。每走一步路都会观察此时在这个序列中的数和当前的比较,小的就向下走一步。当然,这里并没有使用找到斜率最大的方式下山。只是一种情况模拟,这样就能走到了山下的位置。
位置和周边斜率的不同导致找到的最小的值不同,如果用更为普遍的图来表示,就如下图:
那又如何避免找到的是局部的最小的值而不是全局最大的值,导致函数收敛不到全局最小值呢?
梯度下降积分含义
实际上在我们用二次代价函数构造的梯度下降是一个凸函数呈碗状,全局只有一个极小值,也就是最小值。如下图:
3维(三元函数)尚能想象,4维(四元函数)向上我们无法想象。我们这里需要稍微证明以下这是一个凸函数。稍微复习以下,要证明一个多元二次可微的函数微凸函数,就是要判断黑赛矩阵为正定,要判定矩阵为正定则是一切顺序主子式均为正。这个矩阵的构成如下图
我们先对求导,也就是对 取求导看看,根据求导链式法则,先对,求导:
把看成,
这样就得出了结果:
(S1) =
(S2) =
换成当每个特征x都有对应的θ,用更为一般的表达方式:
(S3) =
此时j为取第几个θ。
根据黑塞矩阵,让我们对上面这个一般化的求导表达公式再一次对分别不同的
θ求导。
分别对着不同θ,再次求导
这一次换算出来一般表达公式为:
=
在一般的情况下:
对着不同的θ进行求导,:
对应的黑塞矩阵每个元素为:
化简得到矩阵:
这样就判定黑塞矩阵为正定,这就是为什么使用这样的二次代价函数的时候,全局只有唯一一个极小值,也就是呈现碗状,存在最小值。根据数学归纳,就算是更加高的维度,一样是一个正定矩阵,也就是说同样是一个凸函数,存在一个最小值。同时也能得知,代价函数是否是凸函数,和是什么密切相关。
换句话说,我们每一次通过训练集迭代,都通过这个代价函数的偏导更新参数θ,让答案最终收敛到最小值,就代表着此时到了最小值,也就找到了我们需要的。
把语言转化为伪代码:
接着这个代价函数实际上是一个二次代价函数,我们把二次代价函数进行一次求导,就是S3的公式。放到我们最上面的例子里面就是求导之后答案就是公式S1,求导之后答案就是公式S2。
再对上面的伪代码更加详细的描述,为以下的更新方法:
这里
上图的意思是每一次更新请同步更新所有的参数θ,之后再到下一次迭代。这种梯度下降算法叫做批梯度下降。
梯度下降的原理
关于为什么沿着代价函数求导的结果来更新参数θ能最快的达到最小值。其实名字上已经说出了一半的原因了。
梯度是微积分中一个很重要的概念:
在单变量的函数中,梯度其实就是函数的微分,代表着函数在某个给定点的切线的斜率
在多变量函数中,梯度是一个向量,向量有方向,梯度的方向就指出了函数在给定点的上升最快的方向
那么相反我们要下降整个算法也是沿着梯度的方向必定是最快。
这里我们以最初的例子,用一个的等高图来看看其运行原理。
假设在随机的某个点为梯度下降的起点,根据代价函数的偏导不断的更新最终会收敛到了最小点。
关于学习速率(步长)α
关于设置在里面的原因,主要想要更快的收敛到最小值。就比如说,我们要下山,影响我们下山到最低谷的因素除了要走最陡峭的地方,还有每一步走多少会影响到我们下山的速度。
当我们选择了恰当的步长,进行最小化代价函数,将会有如下代价函数将会有如下趋势
当不断的下降,趋紧与一个值,并且增加迭代没有过多的变化的时候,我们往往可以认为梯度函数的最小化已经在最小值的附近。
以下是几种梯度下降出现问题时候,迭代和梯度最小值的坐标图:
根本没有下降,反而最小值不断的上升,在不断的远离最小值。说明梯度下降根本没有正常运行,说明梯度下降算法出问题了。也有可能步长取得大过头了,一开始越过了最小值。此时可以先尝试的取小的学习步长看看情况再做修正。
此时说明了当是这种形势波动的时候,我们应该减小步长说明步长比较大了,一下子冲过了最小点,接着在来回震荡。此时也是要缩小步长。
和情况二类似。
当步长很小,或者说没有设置步长的情况下有影响吗?当然有,实际上我们没有步长或者很小的情况下,梯度下降的函数一样会收敛到最小值,但是由于步长很小,需要达到收敛的迭代次数可能非常长。
这里有一个步长选择的建议。我们绘制出不同步长下,代价函数和迭代之间的关系,找到一个合适的趋势曲线,在根据3三倍的幅度适当的调整步长。
关于梯度下降时候特征处理(归一化/特征缩放)
在处理梯度下降的时候,还有一种因素会导致代价函数下降速度变慢,那就是训练集中每种特征的数据分布情况。
假如有如下的训练集合:
房屋面积(英尺) | 卧室数量(间) | 售价(美元) |
---|---|---|
2104 | 3 | 399900 |
1600 | 3 | 329900 |
2400 | 3 | 369000 |
1416 | 2 | 232000 |
3000 | 4 | 539900 |
1985 | 4 | 299900 |
观察上面的数据,一共有2种特征,房屋面积,卧室数量。我们想要预测的数据是销售的美元。
观察上面的数据,会发现两种特征之间的差几个数量级,如果直接直接用训练集合放入梯度下降,会造成什么结果呢?回顾一下,我们之前是怎么得到出参数θ的。实际上,我们是通过我们的训练集合去通过代价函数不断的更新θ,这样也就导致我们的等高图呈现以下这个样的情况:
由于两者的数量级差距过大导致这个等高线呈椭圆形状,这样我们随机一个点进行梯度下降到最低值,最跨越更多的迭代。
假如说我们的能够把等高图变成接近圆形的形状,也就是说从那个地方出发平均到达最低点的平均耗时就能最低,也就是到最低值的过程更加笔直。最好能如下图:
同时也能推理得知,特征的数量级不要过大,过大会导致等高线每一层的跨越需要走更多的步数。
所以就引出了,特征归一化/缩放处理。
特征归一化/缩放(normalization)
数据标准化(归一化)处理是数据挖掘的一项基础工作,不同评价指标往往具有不同的量纲和量纲单位,这样的情况会影响到数据分析的结果,为了消除指标之间的量纲影响,需要进行数据标准化处理,以解决数据指标之间的可比性。原始数据经过数据标准化处理后,各指标处于同一数量级,适合进行综合对比评价。
以下是两种常用的归一化方法:
1.Standardization 又叫 0均值标准化(Z-score standardization)
0均值归一化方法将原始数据集归一化为均值为0、方差1的数据集,归一化公式如下:
其中,μ、σ分别为原始数据集的均值和方法。该种归一化方式要求原始数据的分布可以近似为高斯分布,否则归一化的效果会变得很糟糕。(实际上大部分自然的分布都和高斯分布近似,所以很常见)
在分类、聚类算法中,需要使用距离来度量相似性的时候、或者使用PCA技术进行降维的时候,这种方法(Z-score standardization)表现更好。
2.线性函数归一化(Min-Max scaling)
在简单缩放中,我们的目的是通过对数据的每一个维度的值进行重新调节(这些维度可能是相互独立的),使得最终的数据向量落在 [0,1]或[ − 1,1] 的区间内(根据数据情况而定)。这对后续的处理十分重要,因为很多默认参数(如 PCA-白化中的 epsilon)都假定数据已被缩放到合理区间。 例子:在处理自然图像时,我们获得的像素值在 [0,255] 区间中,常用的处理是将这些像素值除以 255,使它们缩放到 [0,1] 中.
这种算法是对原始数据的线性变换,使结果落到[0,1]区间,转换函数如下:
max: 样本数据的最大值
min: 为样本数据的最小值
适用场景
这种归一化方法比较适用在数值比较集中的情况。但是,如果max和min不稳定,很容易使得归一化结果不稳定,使得后续使用效果也不稳定,实际使用中可以用经验常量值来替代max和min。而且当有新数据加入时,可能导致max和min的变化,需要重新定义。
在不涉及距离度量、协方差计算、数据不符合正太分布的时候,可以使用第一种方法或其他归一化方法。比如图像处理中,将RGB图像转换为灰度图像后将其值限定在[0 255]的范围。
3.非线性归一化
经常用在数据分化比较大的场景,有些数值很大,有些很小。通过一些数学函数,将原始值进行映射。该方法包括 log、指数,正切等。需要根据数据分布的情况,决定非线性函数的曲线,比如log(V, 2)还是log(V, 10)等。
总结
经过这些处理之后,就能够把特征压缩到1上下,尽可能的缩小了特征的范围。当然这些处理一个好处,到了后面不同的算法对特征的取证范围有一定要求。
随机梯度下降
为什么会出现这种算法呢?在计算批梯度下降的时候有一个计算步骤是要遍历整个训练集合做做累加处理。当到了人口普查,数量级别到了上亿的层面时候,这种批梯度下降由于机能限制,耗时问题。很可能无法满足我们的需要,就需要如下的算法:
这么做的好处有,只需要遍历一次集合,而且是每一次遍历更新θ就增加训练集合加入到代价函数训练。
这么做有一个缺点:
梯度下降的时候,由于训练集合不断的变动,会导致在梯度下降的时候没有办法很好的按照斜率最高的方向下降,下降情况会出波动,可能会扭来扭去甚至会上升一点,但是大体方向是收敛到最低点。按照等高线如下图:
随机梯度算法就可以用作在线学习了,但是注意随机梯度的结果并非完全收敛,而是在收敛结果处波动的,可能由非线性可分的样本引起来的:
可以有如下解决办法:(来自MLIA)
动态更改学习速率a的大小,可以增大或者减小
随机选样本进行学习
正规方程(Normal equaltion)
虽然说梯度下降往往是直接求解代价函数的最小值的时候出现难度而选择的方式,但是并不代表我们无法求解,当数据量小,特征不多的情况下,完全可以直接求解出来,这就是正规方程的由来。
先上特征方程的结果:
现在把看成两种矩阵相乘得来:
θ矩阵:
x训练集合的一条测试数据中所有的特征矩阵:
X特征矩阵,矩阵中的每一行是上面每一个单条训练集矩阵x的转置:
矩阵y为真实结果的矩阵:
也就是说 也就是θ的转置矩阵乘以矩阵X,行列式相乘就是每个元素对应行列积的累加。
而这个θ就是我们想要求得的。还有一个矩阵y就是训练集合的要拟合出来的结果集合。
先放上正规方程:
通过正规方程就能求解整个代价函数的最小值。
下面是证明过程,这里稍微用到了矩阵的迹知识。这里先写下关于矩阵迹有关的知识。
在线性代数中,一个n×n矩阵A的主对角线(从左上方至右下方的对角线)上各个元素的总和被称为矩阵A的迹(或迹数),一般记作tr(A)。
计算:
几个有用矩阵迹的性质:
(性质2)
:某个实数的迹还是自己
如,有:
(性质6)
(性质7)
证明:
根据上面构造出来的X矩阵,可以得到:
这样恰好就是上面的二次代价函数用矩阵来表示。
令代价函数的梯度
化简:
由于这是一个实数可得到:
对里面每一项求导,由于y^Ty\nabla_{θ} tr= θTXTXθ = θ θ^T X^TX\nabla_{θ} tr= θ θTXTXθ = θ Iθ^T X^TX \= X^TXθ + X^TXθ$
根据性质6:
可得:
最后化简可得:
正规方程即为所求。
因此,在数据量不大的时候,完全可以使用正规方程直接求解。
正规方程和梯度下降的对比
梯度下降特点:需要选择学习率,需要大量的迭代。但是在大数据的情况往往能工作十分好。
正规方程:不需要选择学习率,不需要迭代,在大数据的情况下工作十分慢。
以上,梯度下降的基础内容就完成了。这里就挑选吴恩达布置的其中一道实现梯度下降的作业题目。
这里选择使用Octave来完成,python的numpy操作和Octave的很多操作是共同的,所以用Octave构建模型,python来实现是十分好的选择。这里先给出Octave的代码。
实现梯度下降:
这里有做法很多种:
先按照代价函数求导之后的步骤来实现则是:
Errors = predictions - y;
detaJ = sum(Errors .* X);
theta = theta - alpha/m * detaJ';
按照步骤来就是先通过矩阵减法完成矩阵中每个元素的减法,最后在点乘训练集合中所有的数据,接着再更新。
但是实际上还有更好的做法
predictions = X * theta;
Errors = predictions - y;
theta = theta - alpha /m * (X' * Errors);
这么做比上一个,灵活运用了矩阵之间的乘法,按照法则做了一次积的累加处理。
function [theta, J_history] = gradientDescent(X, y, theta, alpha, num_iters)
%GRADIENTDESCENT Performs gradient descent to learn theta
% theta = GRADIENTDESCENT(X, y, theta, alpha, num_iters) updates theta by
% taking num_iters gradient steps with learning rate alpha
% Initialize some useful values
m = length(y); % number of training examples
J_history = zeros(num_iters, 1);
theta_size = length(theta);
for iter = 1:num_iters
% ====================== YOUR CODE HERE ======================
% Instructions: Perform a single gradient step on the parameter vector
% theta.
%
% Hint: While debugging, it can be useful to print out the values
% of the cost function (computeCost) and gradient here.
%
% the first function to solve this problem
% predictions = X * theta;
% Errors = predictions - y;
% theta = theta - alpha /m * (X' * Errors);
predictions = X * theta;
Errors = predictions - y;
detaJ = sum(Errors .* X);
theta = theta - alpha/m * detaJ';
% the second function to solvw this problem
% ============================================================
% Save the cost J in every iteration
J_history(iter) = computeCost(X, y, theta);
end
end
这里的意思是X是特征矩阵,y是实际结果矩阵,theta是参数,alpha是学习步长,num_iters是迭代次数。
总结
这么多的讨论和推导最后其实就是三行代码,就算换到python的中也是十分简单。但是过程并没有看上去的容易。至少我在学习梯度下降的时候花了不少的经历,绕了不少的圈子才敢说自己了解。之后有心情再放出python用numpy写的版本吧。
最后要感谢这些博主,许多资料参考以下博客:
https://www.cnblogs.com/ooon/p/4947688.html
https://blog.csdn.net/u013709270/article/details/78667531
https://www.cnblogs.com/sddai/p/6250094.html
https://blog.csdn.net/zbc1090549839/article/details/44103801