其实都没有很懂梯度下降

博客新地址,阅读体验更佳,点击跳转


一直以为自己懂了梯度下降法,直到编程实现一遍,发现有些概念其实理解的并不清晰。
这篇Blog旨在:
- 梯度下降法的推导;
- 常用的几种梯度下降法并编程实现;
- 使用梯度下降法求解线性模型参数的例子;
- 梯度下降中非常重要的学习率设置;


数学回顾笔记(一)——方向导数和梯度回顾了方向导数和梯度的概念,明确了,梯度方向为函数增长最快的方向,负梯度方向为函数减小最快的方向。
梯度下降法是求解无约束最优化问题的常用方法,基于泰勒展开式推出的,

f(x)=f(x0)+(xx0)f(x0)(1)+(xx0)22!f(x0)(2)+...+(xx0)nn!f(x0)(n)+o(n)

根据泰勒展开式可以无限逼近原表达式。
则,来看梯度下降法。
梯度下降法使用的是一阶泰勒展开式(忽略二阶及以上的高阶无穷小)

f(x)=f(x0)+(xx0)f(x0)(1)

但是这里的自变量是向量,则把自变量x变为向量 x⃗  ,将一阶偏微分变为梯度,转换为下式:

f(xk+αd⃗ )=f(xk)+αgTkd⃗ gTk=g(x(k))=f(x(k))f(x)x(k)

我们的目的是使得上式去最小,则向量 gTkd⃗  的方向应相反,即 x(k+1) 应该是 xk 沿着负梯度方向,更新的大小为 α
上述就是梯度下降法的思想了,非常简洁明了对不对,唯一的参数就是更新的步长(学习率) α 了。


线性模型使用梯度下降法的公式推导。

在我之前的博文James Zhou Blog—线性回归中讲到,通过最大似然法估计线性回归的参数得到了最小二乘的形式,即使用均方误差来作为损失函数。

J(θ)=12mi=1m( f(x(i))y(i) )2

梯度下降法,通过迭代的方式,使得参数 θ 沿着损失函数下降最快的方向,每次更新 α 步。
则:

J(θ)θ=1mi=1m( f(x(i))y(i) )x(i)

上述参数 θ 是个向量,这点在程序中也会显现,对矩阵和向量不是很清楚,这里实现可能会有点问题,我自己也试了挺久的,建议自己实现一遍,这样会更清晰。

θ=θαJ(θ)θ

到此梯度下降法的推导就结束了。


常用的几种梯度下降法并编程实现

上述的梯度下降法推导实际是批梯度下降法,即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 点。
这里是个转折点,可以说一步错之后步步错了。
参数 θ 的更新公式为:

θ=θ+α

那么由曲线某点斜率可知, C 的斜率肯定大于 A C 点的负梯度值大于 A ,所以对 C 处的参数更新后,会到达 D 点,如此往复,损失值会越来越大,如同我上面截图所示,损失值随着迭代次数越来越大了。
所以,需要适当减小学习率,只要保证第一次更新参数 θ 后,损失值比原来小,那么梯度下降一定会收敛,反之则会发散。


还有一个可以使梯度下降加快收敛的方式是,归一化样本数据,这个为什么能起作用还不能从数学上学习,直观理解为,对数据进行归一化后,数据之间的距离更近了加快收敛,解释有点牵强,有人明白这个可以解释一下,谢谢!。
归一化样本使用的是sklearn.preprocessing.MinMaxScal.fit_transform()接口代码就不贴了。


代码和数据地址:GitHub——GradientDescentExample;

参考博文:
- 详解梯度下降法的三种形式BGD、SGD以及MBGD
- 梯度下降算法以及其Python实现
- 梯度下降实用技巧II之学习率
- 梯度下降算法步长和收敛条件的设置的一些看法
- 随机梯度下降 批量梯度下降 确定训练模型的数据规模 判断梯度下降是否收敛
- 梯度下降的原理(泰勒证明)

你可能感兴趣的:(算法)