博客新地址,阅读体验更佳,点击跳转
一直以为自己懂了梯度下降法,直到编程实现一遍,发现有些概念其实理解的并不清晰。
这篇Blog旨在:
- 梯度下降法的推导;
- 常用的几种梯度下降法并编程实现;
- 使用梯度下降法求解线性模型参数的例子;
- 梯度下降中非常重要的学习率设置;
数学回顾笔记(一)——方向导数和梯度回顾了方向导数和梯度的概念,明确了,梯度方向为函数增长最快的方向,负梯度方向为函数减小最快的方向。
梯度下降法是求解无约束最优化问题的常用方法,基于泰勒展开式推出的,
根据泰勒展开式可以无限逼近原表达式。
则,来看梯度下降法。
梯度下降法使用的是一阶泰勒展开式(忽略二阶及以上的高阶无穷小)
但是这里的自变量是向量,则把自变量x变为向量 x⃗ ,将一阶偏微分变为梯度,转换为下式:
我们的目的是使得上式去最小,则向量 gTk和d⃗ 的方向应相反,即 x(k+1)→ 应该是 xk→ 沿着负梯度方向,更新的大小为 α 。
上述就是梯度下降法的思想了,非常简洁明了对不对,唯一的参数就是更新的步长(学习率) α 了。
在我之前的博文James Zhou Blog—线性回归中讲到,通过最大似然法估计线性回归的参数得到了最小二乘的形式,即使用均方误差来作为损失函数。
梯度下降法,通过迭代的方式,使得参数 θ 沿着损失函数下降最快的方向,每次更新 α 步。
则:
上述参数 θ 是个向量,这点在程序中也会显现,对矩阵和向量不是很清楚,这里实现可能会有点问题,我自己也试了挺久的,建议自己实现一遍,这样会更清晰。
到此梯度下降法的推导就结束了。
上述的梯度下降法推导实际是批梯度下降法,即BatchGradientDescent
可以看到,每次参数 θ 的更新都需要使用所有的训练数据:对所有数据计算出loss
,并对参数 θ 求梯度,这样的方式,在样本量比较大的时候,迭代速度非常慢。
所以出现了随机梯度下降法,即StochasticGradientDescent,每次随机选择一个样本,使用其loss
来更新参数 θ 。
伪代码如下:
随机梯度下降,迭代速度很快,但是毕竟不是使用所有样本的loss来求梯度,有的迭代轮次中,不是向着整体最优化的方向。但是时间和性能折中,用随机梯度还是比较多的。
小批量梯度下降法(MiniBatchGradientDescent)是批梯度和随机梯度的结合,每次选择一个Batch大小的样本来求梯度进而更新参数。
代码实现:
def batchGradientDescent(alpha, XMatrix, yMatrix, iterNum):
m, n = np.shape(XMatrix)
theta = np.ones((n, 1))
for i in range(iterNum):
error = yMatrix - np.dot(XMatrix, theta)
theta = theta + alpha * np.dot(error.transpose(), XMatrix).transpose() / m
return theta
def stochasticGradientDescent(alpha, XMatrix, yMatrix, iterNum):
m, n = np.shape(XMatrix)
theta = np.ones((n, 1))
randomNum = random.randint(0, m - 1)
for i in range(iterNum):
error = yMatrix[randomNum] - np.dot(XMatrix[randomNum], theta)
theta = theta + alpha * (error * XMatrix[randomNum]).transpose()
return theta
def miniBatchGradientDescent(alpha, XMatrix, yMatrix, iterNum, batch):
m, n = np.shape(XMatrix)
theta = np.ones((n, 1))
indexList = list(range(m))
slice = random.sample(indexList, batch)
XMatrix = XMatrix[slice]
yMatrix = yMatrix[slice]
for i in range(iterNum):
error = yMatrix - np.dot(XMatrix, theta)
theta = theta + alpha * (error.transpose() * XMatrix).transpose()
return theta
批梯度拟合图:
随机梯度拟合图:
mini-Batch梯度下降拟合图:
梯度下降未收敛图:
当学习率选取的比较大的时候,很可能会出现梯度下降法无法收敛的情况,损失函数值会越来越大。这点要注意!这个错误我一开始找了很久,以为自己的代码有问题,后来发觉是学习率不合适。
损失函数的值随着迭代次数:
我们分析一下原因,为了可视化以简单的一维参数举例(实际参数是多维时,对每一维也可以这样单独分析)
横轴为 θ 值,纵轴为 J(θ) 的值,线性回归的损失函数对参数 θ 是个凸函数,如上图所示。
一开始, θ 取值对应点 A ,当通过梯度下降更新 θ 时,(从图上直观看到,需要减小 θ )如果学习率过大,此时 θ 减的太多,到了 C 点。
这里是个转折点,可以说一步错之后步步错了。
参数 θ 的更新公式为:
还有一个可以使梯度下降加快收敛的方式是,
归一化样本数据
,这个为什么能起作用还不能从数学上学习,直观理解为,对数据进行归一化后,数据之间的距离更近了加快收敛,解释有点牵强,有人明白这个可以解释一下,谢谢!。
归一化样本使用的是sklearn.preprocessing.MinMaxScal.fit_transform()
接口代码就不贴了。
代码和数据地址:GitHub——GradientDescentExample;
参考博文:
- 详解梯度下降法的三种形式BGD、SGD以及MBGD
- 梯度下降算法以及其Python实现
- 梯度下降实用技巧II之学习率
- 梯度下降算法步长和收敛条件的设置的一些看法
- 随机梯度下降 批量梯度下降 确定训练模型的数据规模 判断梯度下降是否收敛
- 梯度下降的原理(泰勒证明)