浅谈梯度下降法
Frozen_Ladybug · · 算法·理论
注:本专栏来源于之前的笔记,我又查了一些更专业的资料整理而成,欢迎质疑和讨论。
\text{Part I.} 概念
梯度下降是迭代法的一种,可以用于求解最小二乘问题,线性和非线性都可以。
在求解机器学习算法的模型参数,即无约束优化问题时,梯度下降(Gradient Descent)是最常采用的方法之一,另一种常用的方法是最小二乘法。
在求解损失函数的最小值时,可以通过梯度下降法来一步步的迭代求解,得到最小化的损失函数和模型参数值。
反过来,如果我们需要求解损失函数的最大值,这时就需要用梯度上升法来迭代了。
在机器学习中,基于基本的梯度下降法发展了两种梯度下降方法,分别为随机梯度下降法和批量梯度下降法。
对于可微分的数量场
\text{Part II.} 引入
以线性拟合为例。假设目标变量为
使用的损失函数以线性拟合中
需求出一组参量
坐标是
我们的问题是已知样本的坐标,来求解一组
现在需要一种较慢的调整法让我们得到答案,这就是梯度下降法。
\text{Part III.} 算法概述
表达式如下:
其中
我们按照上述表达式一直不停地更新
整个过程中损失函数单调减,但是也可能会导致在极小值点附近两侧卡着动不了。这时候就是奖励函数的用处——使步长减少,梯度收敛至
\text{Part IV.} 梯度下降法的数学计算
-
梯度下降的方向
梯度是一个向量,梯度的方向是函数在指定点上升最快的方向,那么梯度的反方向自然是下降最快的方向了。
-
泛化的
\theta 参数更新公式对参数
\theta_i 的数学推导:其中\delta_i 表示实际值偏离预测值的大小。\theta_i&=\theta_{i-1}-\alpha\cdot\tfrac{\partial J}{\partial\theta_i}\\ &=\theta_{i-1}-\alpha\cdot\tfrac{\partial\frac{1}{n}\sum^n_{i-1}\delta_i^2}{\partial\theta_i}\\ &=\theta_{i-1}-2\alpha\delta_i\tfrac{\partial\delta_i}{\partial\theta_i}\\ &=\theta_{i-1}-2\alpha(Y_i-y_i)x_i \end{aligned} 其中记
2\alpha=\Alpha 为统一学习率。 -
$$\begin{aligned} \theta_0'&=\theta_0-\alpha\cdot\tfrac{\partial J}{\partial\theta_0}\\ &=\theta_0-\alpha\cdot\tfrac{\partial\frac{1}{n}\sum^n_{i-1}\delta_i^2}{\partial\theta_0}\\ &=\theta_0-2\alpha\delta_0\tfrac{\partial\delta_0}{\partial\theta_0}\\ &=\theta_0-2\alpha(Y_0-y_0) \end{aligned}$$ 类似地,记 $2\alpha=\Alpha$ 为统一学习率。
\text{Part V.} 梯度下降法的分类
-
随机梯度下降(Stochastic Gradient Descent,SGD)
每次只使用单个训练样本来更新
\theta 参数,依次遍历训练集,而不是一次更新中考虑所有的样本。计算一次更新一次,直到\theta 收敛或者达到后期更新幅度已经小于我们设置的阀值。 -
批量梯度下降(Batch Gradient Descent,BGD)
每次更新都遍历训练集中所有的样本,以它们的预测误差之和为依据更新。更新完以后,继续以全部样本数据的预测误差之和进行汇总,再更新,直到
\theta 收敛或者达到后期更新幅度已经小于我们设置的阀值。
当训练样本数很大时,批量梯度下降的每次更新都会是计算量很大的操作,而随机梯度下降可以利用单个训练样本立即更新,因此随机梯度下降 通常是一个更快的方法。但随机梯度下降也有一个缺点,那就是
实际情况下,我们一般不用固定的学习率,而是让它随着算法的运行逐渐减小到零,也就是在接近“山底”的时候慢慢减小下降的“步幅”,换成用“小碎步”走,这样它就更容易收敛于全局最小值而不是围绕它振荡了。
\text{Part VI.} 两个例子
这两段分别是求单变量函数的最小值以及根据给定样本求
第一组:
import matplotlib.pyplot as plt
import numpy as np
# fx的函数值
def fx(x):
return x**2
#定义梯度下降算法
def gradient_descent():
times = 10 # 迭代次数
alpha = 0.1 # 学习率
x =10# 设定x的初始值
x_axis = np.linspace(-10, 10) #设定x轴的坐标系
fig = plt.figure(1,figsize=(5,5)) #设定画布大小
ax = fig.add_subplot(1,1,1) #设定画布内只有一个图
ax.set_xlabel('X', fontsize=14)
ax.set_ylabel('Y', fontsize=14)
ax.plot(x_axis,fx(x_axis)) #作图
for i in range(times):
x1 = x
y1= fx(x)
print("第%d次迭代:x=%f,y=%f" % (i + 1, x, y1))
x = x - alpha * 2 * x
y = fx(x)
ax.plot([x1,x], [y1,y], 'ko', lw=1, ls='-', color='coral')
plt.show()
if __name__ == "__main__":
gradient_descent()
第二组:
import numpy as np
import matplotlib.pyplot as plt
#样本数据
x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
y = [3,4,5,5,2,4,7,8,11,8,10,11,13,13,16,17,16,17,18,20]
m = 20 #样本数量
alpha = 0.01#学习率
theta_0 = 1 #初始化theta_0的值
theta_1 = 1 #初始化theta_1的值
#预测目标变量y的值
def predict(theta_0,theta_1, x):
y_predicted = theta_0 + theta_1*x
return y_predicted
#遍历整个样本数据,计算偏差,使用批量梯度下降法
def loop(m, theta_0, theta_1, x, y):
sum1 = 0
sum2 = 0
error = 0
for i in range(m):
a = predict(theta_0, theta_1, x[i]) - y[i]
b = (predict(theta_0, theta_1, x[i]) - y[i])* x[i]
error1 = a*a
sum1 = sum1 + a
sum2 = sum2 + b
error = error + error1
return sum1,sum2,error
#批量梯度下降法进行更新theta的值
def batch_gradient_descent(x, y, theta_0, theta_1, alpha, m):
gradient_1 = (loop(m, theta_0, theta_1, x, y)[1]/m)
while abs(gradient_1) > 0.001:#设定一个阀值,当梯度的绝对值小于0.001时即不再更新了
gradient_0 = (loop(m, theta_0, theta_1, x, y)[0]/m)
gradient_1 = (loop(m, theta_0, theta_1, x, y)[1]/m)
error = (loop(m, theta_0, theta_1, x, y)[2]/m)
theta_0 = theta_0 - alpha*gradient_0
theta_1 = theta_1 - alpha*gradient_1
return(theta_0, theta_1, error)
theta_0 = batch_gradient_descent(x, y, theta_0, theta_1, alpha, m)[0]
theta_1 = batch_gradient_descent(x, y, theta_0, theta_1, alpha, m)[1]
error = batch_gradient_descent(x, y, theta_0, theta_1, alpha, m)[2]
print ("The theta_0 is %f, The theta_1 is %f, The The Mean Squared Error is %f " %(theta_0,theta_1,error))
\text{Part VII.} 梯度下降法的调试
有时候遇到复杂的求导函数,很有可能求解梯度并不容易,在这种情况下,推导出公式以后,通过编程实现,但真正运行的时候,程序并不报错,但求得的梯度是错误的。那如何发现这种问题的原因所在呢?
一种思想就是通过在这个点的左边和右边各找一个临近点,连成的直线的斜率近似地作为改点的斜率,也就是我们所说的导数,其实这也就是导数的定义中的思想。并且将其扩展到高维空间也是一样的。
这种导数的求法从数学的意义上解释是很简单的,但是这样做带来的问题就是时间复杂度将会增加很多,因为需要在一个点进行求二阶导。
调试公式时间复杂度高,每求一次梯度都要花相当多的时间,因此一般这种方式只用于调试。