导数
- 导数 就是曲线的斜率,是曲线变化快慢的一个反应。
- 二阶导数 是斜率变化的反应,表现曲线的 凹凸性
偏导数
固定其他变量,对其中一个变量求导。
关于梯度的理解
在一元函数中,梯度就是导数,导数是曲线变化快慢的反应,沿着导数变化快的方向,可以取到最大值。
多元函数类似。
在微积分里面,对多元函数参数求偏导数,把求的各参数的偏导数以向量的形式写出来,就是梯度。
梯度是一个向量,表示某一函数在该点处的方向导数 ,沿着该方向取最大值,即函数在该点处沿着该方向变化最快,变化率最大(即该梯度向量的模)。
求梯度示例
可以看到,梯度就是分别对每个变量进行微分,然后用逗号分割开,梯度是用<>包括起来,说明梯度其实一个向量。
那么这个梯度向量求出来有什么意义呢?
梯度向量从几何意义上讲,就是函数变化增加最快的地方,沿着梯度向量的方向更容易找到函数的最大值,沿着向量相反的方向,梯度减小最快,更容易找到函数最小值。
例如下面这个可视化的多元函数,从图上的红点到中间的最小值,如果没有方向的走的话,有很多条路线。但是沿着梯度向量走的路线就是这条红线,就能最快找到最小值。梯度向量就是变化率,就是指明函数值变化方向的。
梯度下降法
常用于求解无约束情况下凸函数的极小值,是一种迭代类型的算法,因为凸函数只有一个极值点,故求解出来的极小值就是函数的最小值点。
梯度下降计算公式
α含义:称为学习率或步长,我们可以通过α来控制每一步走的距离。α不能太大也不能太小,太小的话,可能导致迟迟走不到最低点,太大的话,会导致错过最低点!
为什么要梯度要乘以一个负号?
梯度前加一个负号,就意味着朝着梯度相反的方向前进!我们在前文提到,梯度的方向实际就是函数在此点上升最快的方向!而我们需要朝着下降最快的方向走,自然就是负的梯度的方向,所以此处需要加上负号
此公式的意义是: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)
在更新变量参数的时候,随机选取一个样本的梯度值来更新参数
更新公式如下:
小批量梯度下降 MBGD
如果既需要保证算法的训练过程比较快,又需要保证最终参数训练的准确率,可以选择小批量梯度下降法(Mini-batch Gradient Descent,简称MBGD)。MBGD中不是每拿一个样本就更新一次梯度,而且拿b个样本(b一般为10)的平均梯度作为更新方向。迭代公式为:
总结
- 对于SGD,由于每次迭代的时候都要对所有的数据集计算求和,计算量就会很大,尤其是训练数据集特别大的时候。此时,我们就可以用随机梯度下降。
- SGD速度比BGD快(迭代次数少)
- SGD在某些情况下(全局存在多个相对最优解/J(θ)不是一个二次),SGD有可能跳出某些小的局部最优解,所以不会比BGD坏
- BGD一定能够得到一个局部最优解(在线性回归模型中一定是得到一个全局最优解),SGD由于随机性的存在可能导致最终结果比BGD的差
- 注意:优先选择SGD
参考:【机器学习】梯度下降法详解
【机器学习】梯度下降算法
【机器学习】基于梯度下降法的自线性回归模型
【机器学习】回归分析之线性回归
梯度下降法含义及用梯度下降法求解目标函数参数的详细实现过程,写的非常好!:深入浅出–梯度下降法及其实现
特征缩放后为什么会加快梯度下降法收敛速度:数据标准化处理-特征缩放(Feature Scaling)后半部分解释的很到位。