引言
梯度下降是一种用于寻找函数最小值的优化算法,它在机器学习中广泛用于训练模型,如线性回归、神经网络等
一、梯度下降的基本概念
1.1 目标函数
在机器学习中,这通常是损失函数(如均方误差、交叉熵等),我们希望最小化它以训练模型
1.2 梯度
目标函数相对于每个参数的偏导数向量。它指向目标函数增长最快的方向
1.3 学习率(步长)
决定了在梯度下降过程中每一步移动的距离大小
二、梯度下降的步骤
2.1 初始化参数
随机选择一个参数的初始值或者基于某些启发式方法
2.2 计算梯度
计算目标函数在当前参数值处的梯度
2.3 更新参数
根据梯度和学习率来更新参数
θ
θ
θ=
θ
θ
θ−
α
α
α⋅
∇
θ
∇_θ
∇θ
J
(
θ
)
J(θ)
J(θ)
其中,
θ
θ
θ是参数向量,
α
α
α是学习率,
J
(
θ
)
J(θ)
J(θ)是目标函数,
∇
θ
∇_θ
∇θ
J
(
θ
)
J(θ)
J(θ)是目标函数的梯度
2.4 重复步骤2和3
重复计算梯度并更新参数,直到满足停止条件(如梯度变得非常小或者达到预设的迭代次数)
三、梯度下降的变体
3.1 批量梯度下降(Batch Gradient Descent)
使用所有样本来计算梯度
3.2 随机梯度下降(Stochastic Gradient Descent, SGD)
每次迭代使用一个样本来计算梯度
3.3 小批量梯度下降(Mini-batch Gradient Descent)
每次迭代使用一部分样本来计算梯度
四、注意事项
4.1 学习率的选择
学习率太大可能导致算法无法收敛,太小则收敛速度过慢。
4.2 局部最小值和鞍点
梯度下降可能会陷入局部最小值或鞍点,尤其是在非凸优化问题中
4.3 梯度消失/爆炸
在深度学习中,梯度可能会在反向传播过程中变得非常小(消失)或非常大(爆炸),影响训练
在实现梯度下降时,可能需要调整多个超参数(如学习率、迭代次数等)以获得最佳性能。此外,还有一些高级的梯度下降变种和优化技术,如动量(Momentum)、自适应学习率(如AdaGrad、RMSprop、Adam等),它们可以帮助算法更快地收敛
五、如何使用梯度下降自动化优化 w w w 和 b b b
5.1 工具
在本次实验中,我们将使用以下工具:
- NumPy,一个流行的科学计算库
- Matplotlib,一个流行的绘图数据库
- 在本地目录的
lab_utils.py
文件中的绘图例程
import math, copy
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('./deeplearning.mplstyle')
from lab_utils_uni import plt_house_x, plt_contour_wgrad, plt_divergence, plt_gradients
5.2 问题陈述
让我们使用之前相同的两个数据点 :一个1000平方英尺的房子以30万美元的价格售出,一个2000平方英尺的房子以50万美元的价格售出。
尺寸(1000平方英尺) | 价格(以千美元计) |
---|---|
1 | 300 |
2 | 500 |
# 加载数据集
x_train = np.array([1.0, 2.0]) # 特征
y_train = np.array([300.0, 500.0]) # 目标值
5.3 计算成本
# 计算成本的函数
def compute_cost(x, y, w, b):
m = x.shape[0]
cost = 0
for i in range(m):
f_wb = w * x[i] + b
cost = cost + (f_wb - y[i])**2
total_cost = 1 / (2 * m) * cost
return total_cost
5.4 梯度下降概述
到目前为止,已经开发了一个线性模型,用于预测
f
w
,
b
(
x
(
i
)
)
f_{w,b}(x(i))
fw,b(x(i)):
f
w
,
b
(
x
(
i
)
)
=
w
⋅
x
(
i
)
+
b
f_{w,b}(x(i)) = w \cdot x(i) + b
fw,b(x(i))=w⋅x(i)+b
在线性回归中,利用输入训练数据来拟合参数
w
w
w 和
b
b
b
通过最小化预测
f
w
,
b
(
x
(
i
)
)
f_{w,b}(x(i))
fw,b(x(i)) 和实际数据
y
(
i
)
y(i)
y(i) 之间的误差来衡量
这个度量被称为成本
J
(
w
,
b
)
J(w, b)
J(w,b)
在训练中,需要衡量所有训练样本
x
(
i
)
,
y
(
i
)
x(i), y(i)
x(i),y(i) 的成本:
J
(
w
,
b
)
=
1
2
m
∑
i
=
0
m
−
1
(
f
w
,
b
(
x
(
i
)
)
−
y
(
i
)
)
2
J(w, b) = \frac{1}{2m} \sum_{i=0}^{m-1} (f_{w,b}(x(i)) - y(i))^2
J(w,b)=2m1∑i=0m−1(fw,b(x(i))−y(i))2
梯度下降被描述为:
- 重复 w , b w, b w,b 直到收敛:
- w = w − α ∂ J ( w , b ) ∂ w w = w - \alpha \frac{\partial J(w, b)}{\partial w} w=w−α∂w∂J(w,b)
- b = b − α ∂ J ( w , b ) ∂ b b = b - \alpha \frac{\partial J(w, b)}{\partial b} b=b−α∂b∂J(w,b)
- 其中,参数 w w w 和 b b b 同时更新
梯度定义为:
∂
J
(
w
,
b
)
∂
w
=
1
m
∑
i
=
0
m
−
1
(
f
w
,
b
(
x
(
i
)
)
−
y
(
i
)
)
x
(
i
)
\frac{\partial J(w, b)}{\partial w} = \frac{1}{m} \sum_{i=0}^{m-1} (f_{w,b}(x(i)) - y(i)) x(i)
∂w∂J(w,b)=m1∑i=0m−1(fw,b(x(i))−y(i))x(i)
∂
J
(
w
,
b
)
∂
b
=
1
m
∑
i
=
0
m
−
1
(
f
w
,
b
(
x
(
i
)
)
−
y
(
i
)
)
\frac{\partial J(w, b)}{\partial b} = \frac{1}{m} \sum_{i=0}^{m-1} (f_{w,b}(x(i)) - y(i))
∂b∂J(w,b)=m1∑i=0m−1(fw,b(x(i))−y(i))
5.5 实现梯度下降
实现一个特征的梯度下降算法,需要以下三个函数:
compute_gradient
实现定义的方程式compute_cost
实现上面的方程式gradient_descent
,使用compute_gradient
和compute_cost
以下是对这些函数的实现和如何使用它们来找到最优的 w w w 和 b b b 值的详细说明:- 包含偏导数的Python变量的命名遵循这种模式,∂𝐽(𝑤,𝑏)∂𝑏 将被称为 d j d b dj_db djdb
- w.r.t 是 With Respect To 的缩写,例如偏导数 𝐽(𝑤𝑏) 关于 𝑏 的偏导数。
5.6 计算梯度
计算梯度实现了上面的方程式,并返回 ∂𝐽(𝑤,𝑏)∂𝑤 和 ∂𝐽(𝑤,𝑏)∂𝑏。内嵌的注释描述了操作
# compute_gradient函数的实现
def compute_gradient(x, y, w, b):
"""
Computes the gradient for linear regression
Args:
x (ndarray (m,)): Data, m examples
y (ndarray (m,)): target values
w,b (scalar) : model parameters
Returns
dj_dw (scalar): The gradient of the cost w.r.t. the parameters w
dj_db (scalar): The gradient of the cost w.r.t. the parameter b
"""
# 训练样本的数量
m = x.shape[0]
dj_dw = 0
dj_db = 0
for i in range(m):
f_wb = w * x[i] + b
dj_dw_i = (f_wb - y[i]) * x[i]
dj_db_i = f_wb - y[i]
dj_db += dj_db_i
dj_dw += dj_dw_i
dj_dw = dj_dw / m
dj_db = dj_db / m
return dj_dw, dj_db
文章中描述了梯度下降如何利用目标函数相对于参数在某个点的偏导数来更新该参数
使用compute_gradient
函数来找到并绘制我们成本函数相对于其中一个参数的偏导数,比如
w
0
w_0
w0:
plt_gradients(x_train,y_train, compute_cost, compute_gradient)
plt.show()
输出结果:
在上图中,左边的图显示了 ∂𝐽(𝑤,𝑏)∂𝑤 或成本曲线相对于 𝑤 在三个点的斜率。在图的右边,导数是正的,而在左边则是负的。由于“碗形”的形状,偏导数将总是引导梯度下降向成本为零的底部
左边的图固定了 𝑏=100。梯度下降将利用 ∂𝐽(𝑤,𝑏)∂𝑤 和 ∂𝐽(𝑤,𝑏)∂𝑏 来更新参数。右边的“箭头图”提供了一种查看两个参数梯度的方法。箭头的大小反映了该点的梯度大小。箭头的方向和斜率反映了该点处 ∂𝐽(𝑤,𝑏)∂𝑤 和 ∂𝐽(𝑤,𝑏)∂𝑏 的比率
请注意,梯度指向最小值之外。回顾上面的方程式。缩放的梯度被从当前的 𝑤 或 𝑏 值中减去。这会使参数移动到将减少成本的方向
5.7 梯度下降
现在可以计算梯度,就可以在下方的gradient_descent
中实现梯度下降,实现细节在注释中进行了描述。下面,使用这个函数在训练数据上找到 𝑤 和 𝑏 的最优值
def gradient_descent(x, y, w_in, b_in, alpha, num_iters, cost_function, gradient_function):
"""
执行梯度下降来拟合 w,b。通过使用学习率 alpha 进行 num_iters 步的梯度下降来更新 w,b。
参数:
x (ndarray (m,)) : 数据集,包含 m 个样本
y (ndarray (m,)) : 目标值
w_in,b_in (标量): 模型参数的初始值
alpha (float): 学习率
num_iters (int): 运行梯度下降的迭代次数
cost_function: 用于产生成本的函数
gradient_function: 用于产生梯度的函数
返回:
w (标量): 运行梯度下降后更新的参数值
b (标量): 运行梯度下降后更新的参数值
J_history (列表): 成本值的历史记录
p_history (列表): 参数 [w,b] 历史记录
"""
w = copy.deepcopy(w_in) # 避免修改全局 w_in
# 用于存储每个迭代周期的成本 J 和 w 的数组,主要用于后续的图形化展示
J_history = []
p_history = []
b = b_in
w = w_in
for i in range(num_iters):
# 计算梯度并使用 gradient_function 更新参数
dj_dw, dj_db = gradient_function(x, y, w , b)
# 使用方程 (3) 更新参数
b = b - alpha * dj_db
w = w - alpha * dj_dw
# 保存每个迭代周期的成本 J
if i<100000: # 防止资源耗尽
J_history.append( cost_function(x, y, w , b))
p_history.append([w,b])
# 每隔一定迭代次数打印成本
if i% math.ceil(num_iters/10) == 0:
print(f"迭代 {i:4}: 成本 {J_history[-1]:0.2e} ",
f"dj_dw: {dj_dw: 0.3e}, dj_db: {dj_db: 0.3e} ",
f"w: {w: 0.3e}, b:{b: 0.5e}")
return w, b, J_history, p_history # 返回 w 和 J,w 历史记录用于图形化展示
# 初始化参数
w_init = 0
b_init = 0
# 一些梯度下降设置
iterations = 10000 # 设置梯度下降的迭代次数
tmp_alpha = 1.0e-2 # 设置初始学习率
# 运行梯度下降
w_final, b_final, J_hist, p_hist = gradient_descent(x_train ,y_train, w_init, b_init, tmp_alpha,
iterations, compute_cost, compute_gradient)
print(f"(w,b) 由梯度下降找到的值: ({w_final:8.4f},{b_final:8.4f})")
输出结果:
仔细观察上述打印的梯度下降过程,注意其一些特点:
- 成本开始时很大,然后迅速下降,这与讲座中的幻灯片描述一致
- 部分导数 ( dj_dw ) 和 ( dj_db ) 也变小,起初很快,然后逐渐变慢。如图所示,随着过程接近“碗底”,由于该点的导数值较小,进展变得较慢
- 尽管学习率 ( \alpha ) 保持固定,但进展仍然变慢
5.8 梯度下降过程中的成本与迭代次数
成本与迭代次数的图表是梯度下降过程中进度的一个有用的衡量指标。在成功的运行中,成本应该始终下降。初始时,成本的变化非常迅速,因此将初始下降放在不同的尺度上与最终下降相比是有用的。在下面的图表中,请注意成本轴上的尺度以及迭代步骤
# 绘制成本与迭代次数
fig, (ax1, ax2) = plt.subplots(1, 2, constrained_layout=True, figsize=(12,4))
ax1.plot(J_hist[:100])
ax2.plot(1000 + np.arange(len(J_hist[1000:])), J_hist[1000:])
ax1.set_title("成本与迭代(开始)"); ax2.set_title("成本与迭代 (结束)")
ax1.set_ylabel('成本') ; ax2.set_ylabel('成本')
ax1.set_xlabel('迭代步骤') ; ax2.set_xlabel('迭代步骤')
plt.show()
输出结果:
预测
现在已经发现了参数
w
w
w 和
b
b
b 的最优值,可以使用学到的参数来预测房价。如预期,预测值与同一住房的训练值几乎相同。此外,不在预测中的值与预期值一致。
print(f"1000平方英尺房子的预测 {w_final*1.0 + b_final:0.1f} 千美元")
print(f"1200平方英尺房子的预测 {w_final*1.2 + b_final:0.1f} 千美元")
print(f"2000平方英尺房子的预测 {w_final*2.0 + b_final:0.1f} 千美元")
输出结果
5.9 绘图
可以通过在成本 ( w , b ) ( w, b ) (w,b)的等高线图上绘制成本来展示梯度下降在其执行过程中的进度。
fig, ax = plt.subplots(1,1, figsize=(12, 6))
plt_contour_wgrad(x_train, y_train, p_hist, ax)
输出结果
在上面的等高线图中,显示了 w w w 和 b b b 范围内的 J ( w , b ) J(w, b) J(w,b)。成本水平由环形表示。覆盖在上面的,使用红色箭头,是梯度下降的路径。这里有一些需要注意的事情:
- 路径稳步(单调)地朝着其目标前进。
- 初始步骤比接近目标时的步骤要大得多。
- 放大,我们可以看到梯度下降的最终步骤。注意,随着梯度接近零,步骤之间的距离缩小了。
fig, ax = plt.subplots(1,1, figsize=(12, 4))
plt_contour_wgrad(x_train, y_train, p_hist, ax, w_range=[180, 220, 0.5], b_range=[80, 120, 0.5],
contours=[1,5,10,20],resolution=0.5)
输出结果:
增加学习率
在讲座中,有关学习率
α
\alpha
α 的适当值的讨论与方程式有关。较大的
α
\alpha
α 会使梯度下降更快地收敛到解决方案。但是,如果它太大,梯度下降将会发散。在上面的例子中,有一个收敛良好的解决方案。
让我们尝试增加
α
\alpha
α 的值,看看会发生什么:
# 初始化参数
w_init = 0
b_init = 0
# 将alpha设置为一个大值
iterations = 10
tmp_alpha = 8.0e-1
# 运行梯度下降
w_final, b_final, J_hist, p_hist = gradient_descent(x_train ,y_train, w_init, b_init, tmp_alpha,
iterations, compute_cost, compute_gradient)
在上面的图中, w w w 和 b b b 在每次迭代中都在正负之间弹跳,其绝对值随着每次迭代而增加。此外,每次迭代中 ∂ J ( w , b ) / ∂ w \partial J(w,b)/\partial w ∂J(w,b)/∂w 的符号都会改变,而成本却在增加而不是减少。这是一个明显的迹象,表明学习率太大,解决方案正在发散
六、总结
- 深入了解单变量梯度下降的细节
- 开发一个计算梯度的例行程序
- 可视化梯度的本质
- 完成一个梯度下降例行程序
- 利用梯度下降找到参数
- 检查学习率大小的影响