梯度下降法原理和实现

导数

  • 导数 就是曲线的斜率,是曲线变化快慢的一个反应。
  • 二阶导数 是斜率变化的反应,表现曲线的 凹凸性

偏导数

固定其他变量,对其中一个变量求导。

关于梯度的理解

在一元函数中,梯度就是导数,导数是曲线变化快慢的反应,沿着导数变化快的方向,可以取到最大值。
多元函数类似。
在微积分里面,对多元函数参数求偏导数,把求的各参数的偏导数以向量的形式写出来,就是梯度。
梯度是一个向量,表示某一函数在该点处的方向导数 ,沿着该方向取最大值,即函数在该点处沿着该方向变化最快,变化率最大(即该梯度向量的模)。

求梯度示例

在这里插入图片描述
可以看到,梯度就是分别对每个变量进行微分,然后用逗号分割开,梯度是用<>包括起来,说明梯度其实一个向量。

那么这个梯度向量求出来有什么意义呢?
梯度向量从几何意义上讲,就是函数变化增加最快的地方,沿着梯度向量的方向更容易找到函数的最大值,沿着向量相反的方向,梯度减小最快,更容易找到函数最小值。

例如下面这个可视化的多元函数,从图上的红点到中间的最小值,如果没有方向的走的话,有很多条路线。但是沿着梯度向量走的路线就是这条红线,就能最快找到最小值。梯度向量就是变化率,就是指明函数值变化方向的。
在这里插入图片描述

梯度下降法

常用于求解无约束情况凸函数的极小值,是一种迭代类型的算法,因为凸函数只有一个极值点,故求解出来的极小值就是函数的最小值点。

梯度下降计算公式

在这里插入图片描述
α含义:称为学习率或步长,我们可以通过α来控制每一步走的距离。α不能太大也不能太小,太小的话,可能导致迟迟走不到最低点,太大的话,会导致错过最低点!

为什么要梯度要乘以一个负号?
梯度前加一个负号,就意味着朝着梯度相反的方向前进!我们在前文提到,梯度的方向实际就是函数在此点上升最快的方向!而我们需要朝着下降最快的方向走,自然就是负的梯度的方向,所以此处需要加上负号

此公式的意义是:J是关于Θ的一个函数,我们当前所处的位置为Θ0点,要从这个点走到J的最小值点,也就是山底。首先我们先确定前进的方向,也就是梯度的反向,然后走一段距离的步长,也就是α,走完这个段步长,就到达了Θ1这个点!

单变量函数的梯度下降案例

我们假设有一个单变量的函数:在这里插入图片描述
函数的微分:
在这里插入图片描述
初始化,起点为
在这里插入图片描述
学习率为:α=0.4

根据梯度下降的计算公式开始进行梯度下降的迭代计算过程:在这里插入图片描述
如图,经过四次的运算,也就是走了四步,基本就抵达了函数的最低点,也就是山底:在这里插入图片描述

多变量函数的梯度下降

假设有一个目标函数
在这里插入图片描述
现在要通过梯度下降法计算这个函数的最小值。我们通过观察就能发现最小值其实就是 (0,0)点。但是接下来,我们会从梯度下降算法开始一步步计算到这个最小值!
我们假设初始的起点为:在这里插入图片描述
初始的学习率为:α=0.1
函数的梯度为:在这里插入图片描述
进行多次迭代:在这里插入图片描述
我们发现,已经基本靠近函数的最小值点在这里插入图片描述

用梯度下降法实现线性回归

下面展示用梯度下降法求解线性回归的参数。
在这里插入图片描述
我们将用梯度下降法来拟合出这条直线!

首先,我们需要定义一个代价函数,在此我们选用均方误差代价函数

在这里插入图片描述
此公式中

m是数据集中点的个数
½是一个常量,这样是为了在求梯度的时候,二次方乘下来就和这里的½抵消了,自然就没有多余的常数系数,方便后续的计算,同时对结果不会有影响
y 是数据集中每个点的真实y坐标的值
h 是我们的预测函数,根据每一个输入x,根据Θ 计算得到预测的y值,即

在这里插入图片描述
根据代价函数看到,代价函数中的变量有两个,所以是一个多变量的梯度下降问题,求解出代价函数的梯度,也就是分别对两个变量进行微分
在这里插入图片描述
明确了代价函数和梯度,以及预测的函数形式。我们就可以开始编写代码了。但在这之前,需要说明一点,就是为了方便代码的编写,我们会将所有的公式都转换为矩阵的形式,python中计算矩阵是非常方便的,同时代码也会变得非常的简洁。

为了转换为矩阵的计算,我们观察到预测函数的形式
在这里插入图片描述
我们有两个变量,为了对这个公式进行矩阵化,我们可以给每一个点x增加一维,这一维的值固定为1,这一维将会乘到Θ0上。这样就方便我们统一矩阵化的计算在这里插入图片描述
然后我们将代价函数和梯度转化为矩阵向量相乘的形式在这里插入图片描述
完整的代码如下

import numpy as np

# Size of the points dataset.
m = 20

# Points x-coordinate and dummy value (x0, x1).
X0 = np.ones((m, 1))
X1 = np.arange(1, m+1).reshape(m, 1)
X = np.hstack((X0, X1))

# Points y-coordinate
y = np.array([
    3, 4, 5, 5, 2, 4, 7, 8, 11, 8, 12,
    11, 13, 13, 16, 17, 18, 17, 19, 21
]).reshape(m, 1)

# The Learning Rate alpha.
alpha = 0.01

def error_function(theta, X, y):
    '''Error function J definition.'''
    diff = np.dot(X, theta) - y
    return (1./2*m) * np.dot(np.transpose(diff), diff)

def gradient_function(theta, X, y):
    '''Gradient of the function J definition.'''
    diff = np.dot(X, theta) - y
    return (1./m) * np.dot(np.transpose(X), diff)

def gradient_descent(X, y, alpha):
    '''Perform gradient descent.'''
    theta = np.array([1, 1]).reshape(2, 1)
    gradient = gradient_function(theta, X, y)
    while not np.all(np.absolute(gradient) <= 1e-5):
        theta = theta - alpha * gradient
        gradient = gradient_function(theta, X, y)
    return theta

optimal = gradient_descent(X, y, alpha)
print('optimal:', optimal)
print('error function:', error_function(optimal, X, y)[0,0])

运行代码,计算得到的结果如下
在这里插入图片描述
所拟合出的直线如下
在这里插入图片描述

参考:深入浅出–梯度下降法及其实现

缺点

梯度下降不一定能够找到全局的最优解,有可能是一个局部最优解。当然,如果损失函数是凸函数,梯度下降法得到的解就一定是全局最优解。

梯度下降法python实现案例

  • 导入模块
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import math
from mpl_toolkits.mplot3d import Axes3D

# 设置在jupyter中matplotlib的显示情况
%matplotlib inline

# 解决中文显示问题
mpl.rcParams['font.sans-serif'] = [u'SimHei']
mpl.rcParams['axes.unicode_minus'] = False
  • 一元二次函数
# 一维原始图像
def f1(x):
    return 0.5 * (x - 0.25) ** 2
# 导函数
def h1(x):
    return 0.5 * 2 * (x - 0.25)

# 使用梯度下降法求解
GD_X = []
GD_Y = []
x = 4  # 起始位置
alpha = 0.5  # 学习率
f_change = f1(x)
f_current = f_change
GD_X.append(x)
GD_Y.append(f_current)
iter_num = 0
# 变化量大于1e-10并且迭代次数小于100时执行循环体
while f_change > 1e-10 and iter_num < 100:
    iter_num += 1
    x = x - alpha * h1(x)
    tmp = f1(x)
    f_change = np.abs(f_current - tmp)  # 变化量
    f_current  = tmp  # 此时的函数值
    GD_X.append(x)
    GD_Y.append(f_current)
print(u"最终结果为:(%.5f, %.5f)" % (x, f_current))
print(u"迭代过程中X的取值,迭代次数:%d" % iter_num)
print(GD_X)

# 构建数据
X = np.arange(-4, 4.5, 0.05)  # 随机生成-4到4.5,步长为0.05的数
Y = np.array(list(map(lambda t: f1(t), X)))  # X对应的函数值

# 画图
plt.figure(facecolor='w')
plt.plot(X, Y, 'r-', linewidth=2)  # 函数原图像
plt.plot(GD_X, GD_Y, 'bo--', linewidth=2)  # 梯度跌代图
plt.title(u'函数$y=0.5 * (θ - 0.25)^2$; \n学习率:%.3f; 最终解:(%.3f, %.3f);迭代次数:%d' % (alpha, x, f_current, iter_num))
plt.show()

设置不同的学习率,比较结果:
在这里插入图片描述
图中我们可以看出:

学习率比较低的时候,不会出现“之”字型
学习率过大,结果不收敛,找不到最小值
  • 二元函数
# 二维原始图像
def f2(x, y):
    return 0.6 * (x + y) ** 2 - x * y
# 导函数
def hx2(x, y):
    return 0.6 * 2 * (x + y) - y
def hy2(x, y):
    return 0.6 * 2 * (x + y) - x

# 使用梯度下降法求解
GD_X1 = []
GD_X2 = []
GD_Y = []
x1 = 4
x2 = 4
alpha = 0.5
f_change = f2(x1, x2)
f_current = f_change
GD_X1.append(x1)
GD_X2.append(x2)
GD_Y.append(f_current)
iter_num = 0
while f_change > 1e-10 and iter_num < 100:
    iter_num += 1
    prex1 = x1
    prex2 = x2
    x1 = x1 - alpha * hx2(prex1, prex2)
    x2 = x2 - alpha * hy2(prex1, prex2)
    
    tmp = f2(x1, x2)
    f_change = np.abs(f_current - tmp)
    f_current  = tmp
    GD_X1.append(x1)
    GD_X2.append(x2)
    GD_Y.append(f_current)
print(u"最终结果为:(%.5f, %.5f, %.5f)" % (x1, x2, f_current))
print(u"迭代过程中X的取值,迭代次数:%d" % iter_num)
print(GD_X1)

# 构建数据
X1 = np.arange(-4, 4.5, 0.2)
X2 = np.arange(-4, 4.5, 0.2)
X1, X2 = np.meshgrid(X1, X2)
Y = np.array(list(map(lambda t: f2(t[0], t[1]), zip(X1.flatten(), X2.flatten()))))
Y.shape = X1.shape

# 画图
fig = plt.figure(facecolor='w')
ax = Axes3D(fig)
ax.plot_surface(X1, X2, Y, rstride=1, cstride=1, cmap=plt.cm.jet)
ax.plot(GD_X1, GD_X2, GD_Y, 'bo--')

ax.set_title(u'函数$y=0.6 * (θ1 + θ2)^2 - θ1 * θ2$;\n学习率:%.3f; 最终解:(%.3f, %.3f, %.3f);迭代次数:%d' % (alpha, x1, x2, f_current, iter_num))
plt.show()

同样设置不同的学习率,比较结果
在这里插入图片描述

学习率较小时,迭代次数比较小
学习率增大,迭代次数增加
学习率过大,结果不收敛

调优策略

由于梯度下降法中负梯度方向作为变量的变化方向,所以有可能导致最终求解的值是局部最优解,所以在使用梯度下降的时候,一般需要进行一些调优策略:

  • 学习率的选择
    学习率过大,表示每次迭代更新的时候变化比较大,有可能会跳过最优解;学习率过小,表示每次迭代更新的时候变化比较小,就会导致迭代速度过慢,很长时间都不能结束。
    学习率在模型训练过程中可以动态改变。
    算法初始值的选择
    初始值不同,最终获得的最小值也有可能不同,因为梯度下降法求解的是局部最优解,所以一般情况下,选择多次不同初始值运行算法,并最终返回函数最小情况下的结果值。

  • 标准化
    由于样本不同特征的取值范围不同,可能会导致在各个不同参数上迭代速度不同,为了减少特征值的影响,可以将特征进行标准化操作

梯度下降法的常用方法

批量梯度下降(BGD)
使用所有样本在当前点的梯度值来对变量参数进行更新操作

更新公式如下:
在这里插入图片描述
随机梯度下降法(SGD)
在更新变量参数的时候,随机选取一个样本的梯度值来更新参数

更新公式如下:
θk+1=θk−αΔfθk(xi)\theta^{k+1} =\theta^{k} - \alpha \Delta f_{\thetak}(xi)
小批量梯度下降 MBGD
如果既需要保证算法的训练过程比较快,又需要保证最终参数训练的准确率,可以选择小批量梯度下降法(Mini-batch Gradient Descent,简称MBGD)。MBGD中不是每拿一个样本就更新一次梯度,而且拿b个样本(b一般为10)的平均梯度作为更新方向。迭代公式为:
在这里插入图片描述

总结

  • 对于SGD,由于每次迭代的时候都要对所有的数据集计算求和,计算量就会很大,尤其是训练数据集特别大的时候。此时,我们就可以用随机梯度下降。
  • SGD速度比BGD快(迭代次数少)
  • SGD在某些情况下(全局存在多个相对最优解/J(θ)不是一个二次),SGD有可能跳出某些小的局部最优解,所以不会比BGD坏
  • BGD一定能够得到一个局部最优解(在线性回归模型中一定是得到一个全局最优解),SGD由于随机性的存在可能导致最终结果比BGD的差
  • 注意:优先选择SGD

参考:【机器学习】梯度下降法详解
【机器学习】梯度下降算法
【机器学习】基于梯度下降法的自线性回归模型
【机器学习】回归分析之线性回归

梯度下降法含义及用梯度下降法求解目标函数参数的详细实现过程,写的非常好!:深入浅出–梯度下降法及其实现
特征缩放后为什么会加快梯度下降法收敛速度:数据标准化处理-特征缩放(Feature Scaling)后半部分解释的很到位。

猜你喜欢

转载自blog.csdn.net/u011208984/article/details/106790105