0 前言
本篇博客记录了笔者最优化课程的第二次作业(使用梯度下降法求解一次、二次函数)。并在此基础上拓展一些梯度下降法的变体,如:
- 随机梯度下降法(SGD= stochastic gradient descent)
- 小批量梯度下降法(MBGD = mini-batch gradient descent)
1 梯度下降法的原理
下面紧跟的是来自百度百科的解释。
梯度下降法(英语:Gradient descent)是一个一阶最优化算法,通常也称为最速下降法。 要使用梯度下降法找到一个函数的局部极小值,必须向函数上当前点对应梯度(或者是近似梯度)的反方向的规定步长距离点进行迭代搜索。如果相反地向梯度正方向迭代进行搜索,则会接近函数的局部极大值点;这个过程则被称为梯度上升法。
个人理解
其实就是我们学的高数知识,梯度简单理解就是变化率,如果是一维就是斜率了,多维的话就是梯度了,即函数对每个维度的偏导,对每个维度的偏导放一块儿(组成一个向量)就是在求偏导那个点对于整个函数的变化率。
举个例子:比如下山,倘若想以走最短的距离从山顶到达地面,那就要垂直往下走(这显然不太现实,垂直可还好哈哈),所以根据山的地势尽可能的与地面有最大的夹角(就是最陡峭),而人类每步行走的距离是有限的,只能一点一点的往下走且每走一步都要寻找在上一步的基础上的最陡峭的方向(在这解释一下,"人类每步行走的距离是有限的"的含义对应的实际上是梯度下降法中的学习率,也就是步长,试想如果没有学习率这个东西,每一步的步长就取决于梯度的大小,那么就很容易错失良机,即错过最值),那从某个点出发,怎么知道哪个方向是最陡峭的呢?没错,就是梯度,到这应该相信读者就知道梯度下降法求解最优化问题的理所应当了吧🌞🌞
再如求解 y = x 2 y = x^2 y=x2 的最优解,也就是求解 y y y 最小时 x x x 的值。相信初中生都会解,知道导数概念的,直接就对 y y y 求导,令导数等于零就可以了,那个条件下的 x x x 值就是最优解。如果现实中的问题都那么简单就好了哈哈,现实问题中的梯度等于零的解析解往往都是无法显式的给出的,但是我们可以用近似梯度一步一步的逼近最优解,梯度下降法就是这样的一种算法,在一步一步逼近的过程中,目标函数值呈下降趋势,输入参数的值也一步一步逼近最优解。
如果是一元函数的话,梯度实际上就是导数,即
∇
f
(
x
)
=
f
′
(
x
)
\nabla f(x)=f^{'}(x)
∇f(x)=f′(x)
其中迭代的核心公式就是这个
x
(
k
+
1
)
=
x
(
k
)
−
α
k
∇
f
(
x
(
k
)
)
\boldsymbol{x}^{(k+1)}=\boldsymbol{x}^{(k)}-\alpha_k\nabla f(\boldsymbol{x}^{(k)})
x(k+1)=x(k)−αk∇f(x(k))
下面俺就着重剖析一下为什么核心公式长这样儿。
α k ∇ f ( x ( k ) ) \alpha_k\nabla f(\boldsymbol{x}^{(k)}) αk∇f(x(k)) 中为什么梯度 ∇ f ( x ( k ) ) \nabla f(\boldsymbol{x}^{(k)}) ∇f(x(k)) 要乘以 α k \alpha_k αk ,咦!你发没发现它很像微分的形式哎,这个结果理论上是因变量的变化值 也就是 Δ y \Delta y Δy ,用 x x x 减去 Δ y \Delta y Δy ,感觉很奇怪🤔,别着急,听俺娓娓道来🌞
- 首先我们要明确梯度下降法寻找的是极小值点
- 其次方向是梯度下降的方向,也就是 − ∇ f ( x ( k ) ) -\nabla f(\boldsymbol{x}^{(k)}) −∇f(x(k)) 所以要保证 x x x 沿着梯度下降的方向,故由 x − ∇ f ( x ( k ) ) x - \nabla f(\boldsymbol{x}^{(k)}) x−∇f(x(k))
- 但是,接上面下山的例子:“人类每步行走的距离是有限的"的含义对应的实际上是梯度下降法中的学习率,也就是步长,试想如果没有学习率这个东西,每一步的步长就取决于梯度的大小,那么就很容易错失良机,即错过极小值,甚至最小值)”,因此为梯度 − ∇ f ( x ( k ) ) - \nabla f(\boldsymbol{x}^{(k)}) −∇f(x(k)) 量身打造了一个步长(学习率) α k \alpha_k αk 来控制 x x x 下降的程度,使其遇到每一个可能的极小值点,甚至可能是最小值点。
- 故选择学习率 α k \alpha_k αk 可是个技术活儿,如果足够小,则可以保证每次迭代都在减小,但可能导致收敛过慢;如果太大,则不能保证每一次迭代都减小,也不能保证收敛。这货是个超参数,一般凭经验来设置,必要时要根据不同的 α k \alpha_k αk 值的效果进行选择。
相信大家看到这已经搞清楚梯度下降法的原理了,核心公式那块儿也搞清楚了😁
那下面咱就给出具体步骤和代码实现
2 步骤
- 初始化模型参数 ( w w w 和 b b b)。
- 定义学习率 ( α k \alpha_k αk)。
- 定义损失函数 ( L L L)。
- 迭代更新参数 (
w
w
w 和
b
b
b) 直到满足停止条件:
- 计算梯度: ∇ L ( w , b ) = ( ∂ L ∂ w , ∂ L ∂ b ) \nabla L(w, b) = \left(\frac{\partial L}{\partial w}, \frac{\partial L}{\partial b}\right) ∇L(w,b)=(∂w∂L,∂b∂L)
- 更新参数:
w
=
w
−
α
k
∇
L
w = w - \alpha_k \nabla L
w=w−αk∇L
b = b − α k ∇ L b = b - \alpha_k \nabla L b=b−αk∇L
- 停止条件: 最大迭代次数、损失函数收敛、参数变化小于阈值等。
- 达到最大迭代次数 ( N max N_{\text{max}} Nmax): i ≥ N max i \geq N_{\text{max}} i≥Nmax
- 达到最大迭代次数 ( N max N_{\text{max}} Nmax): i ≥ N max i \geq N_{\text{max}} i≥Nmax
- 损失函数收敛: ∣ L ( w i − 1 , b i − 1 ) − L ( w i , b i ) ∣ < ϵ |L(w_{i-1}, b_{i-1}) - L(w_i, b_i)| < \epsilon ∣L(wi−1,bi−1)−L(wi,bi)∣<ϵ
- 参数变化小于阈值 (
δ
\delta
δ):
∣ ∣ w i − w i − 1 ∣ ∣ < δ ||w_i - w_{i-1}|| < \delta ∣∣wi−wi−1∣∣<δ
∣ ∣ b i − b i − 1 ∣ ∣ < δ ||b_i - b_{i-1}|| < \delta ∣∣bi−bi−1∣∣<δ - 梯度变化小于阈值 ( δ grad \delta_{\text{grad}} δgrad): ∣ ∣ ∇ L ( w i , b i ) ∣ ∣ < δ grad ||\nabla L(w_i, b_i)|| < \delta_{\text{grad}} ∣∣∇L(wi,bi)∣∣<δgrad
- 返回训练后的参数 ( w w w 和 b b b)。
下面是我的最优化作业的代码(倘若大佬看到问题,评论区直接说就行,小的定虚心接受并改之哈哈)
2.1 一次函数
using Plots
x = -3:0.3:3
m = length(x)
y = 2x .- 1
y += 1.2 * randn(m)
scatter(x, y, label="Data")
function loop(x, y)
m = length(x)
w, b = 0, 0
η = 0.01
target_error = 0.1 # 设置目标误差阈值
max_iterations = 1000 # 设置最大迭代次数
current_iteration = 0
# 存储每一步的参数值
w_history, b_history = Float64[], Float64[]
# 创建动画对象
anim = @animate while current_iteration < max_iterations
current_iteration += 1
error = w .* x .+ b .- y
gw = x' * error
gb = sum(error)
w -= η * gw
b -= η * gb
# 存储当前的参数值
push!(w_history, w)
push!(b_history, b)
current_error = sum(abs2, error) / m # 计算均方误差
if current_error < target_error
break # 达到目标误差,停止迭代
end
# 清空之前的图像,只保留数据点
plot(x, y, seriestype = :scatter, label="Data")
# 绘制之前的拟合线
for j in 1:current_iteration
plot!(x, w_history[j] .* x .+ b_history[j], label="", legend=false)
end
# 绘制当前拟合线
plot!(x, w .* x .+ b, label="Current Iteration $current_iteration", legend=:topright, xlims=(-3, 3), ylims=(-10, 10))
xlabel!("x")
ylabel!("f(x)")
title!("\$f(x) = 2x - 1\$")
end
gif(anim, "gradient_descent.gif", fps = 10)
end
loop(x, y)
2.2 二次函数
using Plots
# 生成二次函数的数据
x = -3:0.1:3
m = length(x)
y = 3 .* x.^2 .- 2 .* x .+ 1
y += 0.5 * randn(m) # 减小数据噪声
# 存储每一步的参数值
a_history, b_history, c_history = Float64[], Float64[], Float64[]
function quadratic_gradient_descent(x, y)
a, b, c = 0, 0, 0
η = 0.0001
# 创建动画对象
anim = @animate for i in 1:100
# 计算梯度
prediction = a * x.^2 .+ b * x .+ c
error = prediction - y
ga = sum(2 * x.^2 .* error)
gb = sum(2 * x .* error)
gc = sum(2 * error)
# 更新参数
a -= η * ga
b -= η * gb
c -= η * gc
# 存储当前的参数值
push!(a_history, a)
push!(b_history, b)
push!(c_history, c)
# 清空之前的图像,只保留数据点
plot(x, y, seriestype = :scatter, label="Data")
# 绘制之前的拟合曲线,并删除之前的标签
for j in 1:i
plot!(x, a_history[j] .* x.^2 .+ b_history[j] .* x .+ c_history[j], label="", legend=false)
end
# 绘制当前的拟合曲线
plot!(x, a .* x.^2 .+ b .* x .+ c, label="Current Iteration $i", legend=:topright, xlims=(-3, 3), ylims=(-10, 10))
xlabel!("x")
ylabel!("f(x)")
title!("\$f(x) = 3x^2 - 2x + 1\$")
end
gif(anim, "quadratic_gradient_descent.gif", fps = 20)
end
quadratic_gradient_descent(x, y)
3 梯度下降法的变体
3.1 随机梯度下降法(SGD)
# 随机选择一个数据点的索引
i = rand(1:m)
xi, yi = x[i], y[i]
error = w * xi + b - yi
gw = xi * error
gb = error
w -= η * gw
b -= η * gb
3.2 小批量梯度下降法(MBGD)
# 随机选择一个小批量数据点的索引
indices = randperm(m)[1:batch_size]
x_batch, y_batch = x[indices], y[indices]
error = w .* x_batch .+ b .- y_batch
gw = x_batch' * error / batch_size
gb = sum(error) / batch_size
# 使用小批量数据点计算的梯度是对这个方向的估计,
# 但它可能受到选取的数据点的随机性影响。
# 因此,取平均值可以减小随机性的影响,使得梯度方向更稳定。
w -= η * gw
b -= η * gb
4 为什么会出现这些变体呢🤔
下面是使用 GPT3.5 生成的内容并做了修改(懒得码字了哈哈)。
相对于梯度下降法的优点
-
更快的收敛速度:
- 在SGD和MBGD中,每次迭代只使用一个样本或一个小批量的样本来计算梯度和更新参数。这意味着每次迭代的计算成本较低,因此它们通常能够更快地收敛到局部最小值或全局最小值。
- 特别是在深度学习中,由于模型的复杂性,SGD和MBGD的快速收敛可以大大减少训练时间。
-
避免陷入局部最小值:
- 由于SGD和MBGD中引入了随机性,每次迭代的梯度计算都会受到不同的样本子集的影响。这种随机性有助于模型跳出局部最小值,尤其在高维空间中,有助于更广泛地搜索参数空间。
- BGD可能会陷入局部最小值,因为它使用整个数据集的梯度,可能在参数空间中朝着一个方向一直前进。
-
内存效率:
- 在大规模数据集上,加载整个数据集到内存可能会导致内存不足的问题。SGD和MBGD只需要一次加载一个样本或一个小批量的样本,因此在内存效率上更有优势。
-
在线学习:
- SGD特别适合在线学习(Online Learning)的应用,其中模型需要不断地适应新的数据。在这种情况下,SGD可以连续地使用新的数据点进行训练,而无需重新训练整个模型。
-
正则化效果:
- 由于SGD和MBGD中的随机性,它们可以在一定程度上充当正则化的作用,帮助模型更好地泛化到新数据。
需要注意的是,SGD和MBGD也有一些缺点,如更大的训练方差和随机性导致的不稳定性。因此,在实际应用中,通常需要适当调整学习率和迭代次数以平衡它们的优点和缺点。选择SGD、MBGD还是BGD取决于问题的性质和可用计算资源。
总结
好嘞,以上就是笔者要分享的全部内容了,本篇博客记录了笔者最优化课程的第二次作业(使用梯度下降法求解一次、二次函数)。并在此基础上拓展一些梯度下降法的变体,如:
- 随机梯度下降法(SGD= stochastic gradient descent)
- 小批量梯度下降法(MBGD = mini-batch gradient descent)
如果本篇博客帮助到了您,还请麻烦点个赞啥的,感谢感谢哈哈! 倘若大佬们发现文章有问题可以直接评论区斧正,小的定会虚心接受并改之!😁😁😁