前言
文章概述《深度学习详解》- 3.1
书中解析了深度学习中遇到的局部极小值与鞍点问题,并提供了相应的解决策略。首先,文章指出在深度学习的优化过程中,容易陷入局部极小值或鞍点,导致优化效果不佳。随后,详细介绍了什么是局部极小值和鞍点,并通过实例说明了如何判断一个临界点属于哪一类。此外,文中还提到了利用海森矩阵分析误差曲面的方法,以及如何通过特征值的正负来区分局部极小值、局部极大值和鞍点。尽管计算海森矩阵的复杂度较高,文中也讨论了逃离鞍点的替代方法,包括基于负特征值方向的参数更新技巧。最后,通过对高维度空间的探讨,文章提出了一种观点,即在高维度中,局部极小值可能并非常见,大多数情况下优化过程会遇到鞍点。通过实验数据的支持,表明即使是那些看似“像”局部极小值的点,实际上可能是更高维度中的鞍点。综上所述,文章全面探讨了深度学习中遇到的优化难题,并提供了解决策略和思考方向。
个人学习笔记以及概念梳理,望对大家有所帮助。
思维导图3.1
梯度下降优化方向
先初步介绍一下基本的形式
Batch Gradient Descent(批量梯度下降):使用所有样本数据来计算梯度,这是最直观的方法,但是计算成本高,且可能因数据固有的噪声而收敛缓慢。
Stochastic Gradient Descent(随机梯度下降):每次更新只使用一个样本来估计梯度,这可以显著降低计算成本,但由于随机性可能导致更新不稳定。
Mini-Batch Gradient Descent(小批量梯度下降):结合了批量梯度下降和随机梯度下降的优点,每次更新使用一个小批量的数据来计算梯度。
详情可见https://www.cnblogs.com/suanfajin/p/18257713
优化的几种策略(补充)
涉及到的一些术语
术语 | 解释 |
局部极小值 | 损失函数上的一个点,在该点附近所有点的损失值均较高,但在全局中可能不是最小值。 |
鞍点 | 损失函数上的一个点,其梯度为零但不是局部极小值或局部极大值,通常意味着在某些方向上损失会增加,在其他方向上会减少。 |
临界点 | 损失函数上梯度为零的点,包括局部极小值、局部极大值和鞍点。 |
海森矩阵 | 一个方阵,包含损失函数关于参数的二阶导数。海森矩阵的特征值可用于确定临界点的类型。 |
特征值 | 一个标量,当一个向量乘以一个矩阵后仅改变大小而不改变方向时,该标量就是该向量对应的特征值。 |
特征向量 | 一个向量,当它乘以一个矩阵后仅改变大小而不改变方向。 |
泰勒级数展开 | 一种数学工具,用来近似函数在某一点附近的值。 |
学习率 | 梯度下降算法中更新参数的速度。 |
学习率调度 | 动态调整学习率的方法,以促进更好的收敛。 |
批量归一化 | 一种技术,通过对网络各层的输入进行标准化处理,来提高模型训练速度和稳定性。 |
梯度消失问题 | 在反向传播过程中,梯度变得非常小,导致权重几乎不再更新的现象。 |
梯度爆炸问题 | 在反向传播过程中,梯度变得非常大,导致权重更新过大,使训练过程不稳定的现象。 |
涉及的一些公式
学习过程遇到的一些问题的理解:
1如何逃离鞍点和局部极小值
逃离局部极小值
逃离鞍点
1.动量法 (Momentum)
使用动量可以帮助优化算法以更快的速度穿过鞍点区域。
进一步可见进阶Task1.2:《深度学习详解》(Datawhale X 李宏毅苹果书 AI夏令营)-优快云博客
2.Nesterov 加速梯度 (NAG)
NAG 是一种改进的动量方法,它预测下一步的位置并根据该位置调整速度。
3.随机梯度下降 (SGD)
随机梯度下降通过引入噪声来帮助逃离鞍点。由于每次更新都是基于一个或几个样本,这可以打破对称性并帮助跳出鞍点。
4.二阶方法
例如牛顿法或拟牛顿法等二阶优化算法能够利用Hessian矩阵的信息来识别鞍点并避开它们。
代码运行
为了不同方法验证逃离鞍点的能力,我们可以构建一个具有明确鞍点特征的人工数据集。
这里,我们将创建一个简单的二维非凸函数,其中包含明显的鞍点,以便于观察和分析。
俯视图和展示图
![]() | ![]() |
计算过程(部分)
通过计算得f(x, y)的二阶偏导数矩阵Hessian:
以及对应的可能的临界点为 (0,0)(0,0), (1,0)(1,0), (1,1)(1,1), (1,−1)(1,−1), (−1,0)(−1,0), (−1,1)(−1,1), (−1,−1)(−1,−1), (0,1)(0,1), 和 (0,−1)(0,−1)。
带入临界点,逐个分析
最后可得
鞍点 (1,0) (−1,0) (0,1) (0,−1) 局部最小点(1,1) (1,−1) (−1,1) (−1,−1) 局部最大点(0,0)
代码部分
开始逃离鞍点的处理,以(1.01,0.01)为例尽可能的逼近(1,0)处的鞍点
简单解释一下原因:
避免因导致数值不稳定导致算法停滞不前 以及 陷入鞍点附近的小凹陷区域,从而导致无法正确逃离鞍点,同时,在实际应用中,我们通常不会确切地知道鞍点的位置,而是从一个估计的位置开始优化,会比较合理。
采用以下四种方法来验证逃离鞍点的能力
1.动量法 (Momentum) 主要代码
def momentum_optimizer(x, y, lr=0.1, momentum=0.9, epochs=100):
velocity_x = 0
velocity_y = 0
path = [(x.item(), y.item())]
for _ in range(epochs):
# 计算梯度
grad_x, grad_y = grad_func(x, y)
# 更新速度
velocity_x = momentum * velocity_x - lr * grad_x
velocity_y = momentum * velocity_y - lr * grad_y
# 更新位置
x_new = x + velocity_x
y_new = y + velocity_y
# 将新的值赋给 x 和 y
x = x_new
y = y_new
path.append((x.item(), y.item()))
return path
2.Nesterov 加速梯度 (NAG) 主要代码
def nesterov_momentum_optimizer(x, y, lr=0.1, momentum=0.9, epochs=100):
velocity_x = 0
velocity_y = 0
path = [(x.item(), y.item())]
for _ in range(epochs):
# 预测下一步的位置
x_pred = x + momentum * velocity_x - lr * velocity_x
y_pred = y + momentum * velocity_y - lr * velocity_y
# 计算梯度
grad_x, grad_y = grad_func(x_pred, y_pred)
# 更新速度
velocity_x = momentum * velocity_x - lr * grad_x
velocity_y = momentum * velocity_y - lr * grad_y
# 更新位置
x_new = x + velocity_x
y_new = y + velocity_y
# 将新的值赋给 x 和 y
x = x_new
y = y_new
path.append((x.item(), y.item()))
return path
3.随机梯度下降 (SGD) 主要代码
def sgd_optimizer(x, y, lr=0.1, epochs=100):
path = [(x.item(), y.item())]
for _ in range(epochs):
# 计算梯度
grad_x, grad_y = grad_func(x, y)
# 更新位置
x_new = x - lr * grad_x
y_new = y - lr * grad_y
# 将新的值赋给 x 和 y
x = x_new
y = y_new
path.append((x.item(), y.item()))
return path
前三个公用部分代码,此处以 动量法 (Momentum) 为例
import torch
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 定义目标函数
def func(x, y):
return x**4 + y**4 - 2*x**2 - 2*y**2
# 使用PyTorch自动计算梯度
def grad_func(x, y):
x = torch.tensor(x, requires_grad=True)
y = torch.tensor(y, requires_grad=True)
z = func(x, y)
z.backward()
return x.grad.item(), y.grad.item()
# 动量下降优化器
def momentum_optimizer(x, y, lr=0.1, momentum=0.9, epochs=100):
velocity_x = 0
velocity_y = 0
path = [(x.item(), y.item())]
for _ in range(epochs):
# 计算梯度
grad_x, grad_y = grad_func(x, y)
# 更新速度
velocity_x = momentum * velocity_x - lr * grad_x
velocity_y = momentum * velocity_y - lr * grad_y
# 更新位置
x_new = x + velocity_x
y_new = y + velocity_y
# 将新的值赋给 x 和 y
x = x_new
y = y_new
path.append((x.item(), y.item()))
return path
# 创建 PyTorch 张量作为起始点
x = torch.tensor(1.01, requires_grad=True)
y = torch.tensor(0.01, requires_grad=True)
# 运行优化器
path = momentum_optimizer(x, y) #注意此处不同方法 函数替换
# 绘制路径
fig = plt.figure(figsize=(10, 5))
# 俯视图
ax1 = fig.add_subplot(1, 2, 1)
X, Y = np.meshgrid(np.linspace(-2, 2, 400), np.linspace(-2, 2, 400))
Z = func(X, Y)
ax1.contour(X, Y, Z, levels=np.logspace(0, 3, 30))
ax1.plot(*zip(*path), marker='o', color='red', linestyle='-', linewidth=2, markersize=4)
# 标记最终位置为蓝色
final_x, final_y = path[-1]
ax1.plot(final_x, final_y, marker='o', color='blue', markersize=10)
ax1.set_title('Contour Plot')
ax1.set_xlabel('x')
ax1.set_ylabel('y')
# 3D 图
ax2 = fig.add_subplot(1, 2, 2, projection='3d')
X, Y = np.meshgrid(np.linspace(-2, 2, 40), np.linspace(-2, 2, 40))
Z = func(X, Y)
ax2.plot_surface(X, Y, Z, cmap='viridis', alpha=0.8, edgecolor='none')
ax2.plot(*zip(*path), [func(*xy) for xy in path], color='red', marker='o', linewidth=2, markersize=5)
# 在3D图中标记最终位置为蓝色
ax2.plot(final_x, final_y, func(final_x, final_y), marker='o', color='blue', markersize=10)
ax2.set_title('3D Surface Plot')
ax2.set_xlabel('x')
ax2.set_ylabel('y')
ax2.set_zlabel('z')
plt.show()
运行结果,依次
动量法 (Momentum) | Nesterov 加速梯度 (NAG) | 随机梯度下降 (SGD) |
![]() |
![]() |
![]() |
对比
4.二阶方法.拟牛顿法等二阶优化算法(BFGS) 整体代码
对于BFGS方法,我们只需要定义目标函数和梯度函数,剩下的优化步骤由minimize
函数自动完成。
import torch
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.optimize import minimize
# 定义目标函数
def func(xy):
x, y = xy
return x**4 + y**4 - 2*x**2 - 2*y**2
# 使用PyTorch自动计算梯度
def grad_func(xy):
x, y = xy
x = torch.tensor(x, requires_grad=True)
y = torch.tensor(y, requires_grad=True)
z = func([x, y])
z.backward()
return x.grad.item(), y.grad.item()
# 起始点
x0 = np.array([1.01, 0.01])
# 使用BFGS方法进行优化
res = minimize(func, x0, jac=grad_func, method='BFGS')
# 记录每一步的位置
path = [x0]
x, y = x0
for _ in range(10):
grad = grad_func([x, y])
if np.linalg.norm(grad) < 1e-9:
break
x, y = res.x
path.append([x, y])
# 绘制路径
fig = plt.figure(figsize=(10, 5))
# 俯视图
ax1 = fig.add_subplot(1, 2, 1)
X, Y = np.meshgrid(np.linspace(-2, 2, 400), np.linspace(-2, 2, 400))
Z = func([X, Y])
ax1.contour(X, Y, Z, levels=np.logspace(0, 3, 30))
ax1.plot(*zip(*path), marker='o', color='red', linestyle='-', linewidth=2, markersize=4)
# 标记最终位置为蓝色
final_x, final_y = path[-1]
ax1.plot(final_x, final_y, marker='o', color='blue', markersize=10)
ax1.set_title('Contour Plot')
ax1.set_xlabel('x')
ax1.set_ylabel('y')
# 3D 图
ax2 = fig.add_subplot(1, 2, 2, projection='3d')
X, Y = np.meshgrid(np.linspace(-2, 2, 40), np.linspace(-2, 2, 40))
Z = func([X, Y])
ax2.plot_surface(X, Y, Z, cmap='viridis', alpha=0.8, edgecolor='none')
ax2.plot(*zip(*path), [func(p) for p in path], color='red', marker='o', linewidth=2, markersize=5)
# 在3D图中标记最终位置为蓝色
ax2.plot(final_x, final_y, func([final_x, final_y]), marker='o', color='blue', markersize=10)
ax2.set_title('3D Surface Plot')
ax2.set_xlabel('x')
ax2.set_ylabel('y')
ax2.set_zlabel('z')
plt.show()
运行结果
初步结论:整体上,都是可以脱离鞍点影响,抵达局部最小点(1,1)。
本次运行中,我们发现BFGS能够快速找到全局最小值,而NAG、SGD和Momentum则依次需要更多次的尝试才能找到全局最小值。
小结
优化策略
- 批量梯度下降: 使用全部数据计算梯度,稳定但计算成本高。
- 随机梯度下降: 每次更新使用单个样本,计算成本低但可能引入噪音。
- 小批量梯度下降: 结合两者优点,使用一小批数据计算梯度。
逃离鞍点的方法
- 动量法: 引入速度的概念,帮助算法更快穿过鞍点区域。
- Nesterov加速梯度: 改进的动量方法,通过预估未来位置来调整速度。
- 随机梯度下降: 利用梯度的随机性帮助跳出鞍点。
- 二阶方法: 如牛顿法或BFGS等,利用二阶导数信息更准确地识别和逃离鞍点。
实验验证
- 构建了一个包含鞍点的二维非凸函数。
- 使用Python和相关库实现不同的优化方法。
- 通过可视化路径,观察不同方法逃离鞍点的情况。