第18课 - 梯度下降的数学原理
学习目标
通过本课学习,你将能够:
- 理解梯度下降的直观概念和几何意义
- 掌握梯度下降算法的数学推导过程
- 实现简单的梯度下降算法来优化函数
- 分析学习率、迭代次数对优化效果的影响
- 应用梯度下降解决实际函数优化问题
核心概念
1. 什么是梯度下降?
想象你蒙着眼睛站在一座山上,想要找到山谷的最低点。最聪明的策略是:每次都沿着当前位置最陡峭的下坡方向走一小步。这就是梯度下降的核心思想!
在数学上:
- 山的形状 → 损失函数 $J(\theta)$
- 你的位置 → 当前参数 $\theta$
- 最陡峭的下坡方向 → 负梯度 $-\nabla J(\theta)$
- 每一步的大小 → 学习率 $\alpha$
2. 数学公式推导
对于一元函数 $f(x)$,梯度下降的更新规则为: $$x_{new} = x_{old} - \alpha \cdot \frac{df}{dx}(x_{old})$$
对于多元函数 $J(\theta_1, \theta_2, ..., \theta_n)$: $$\theta_j^{new} = \theta_j^{old} - \alpha \cdot \frac{\partial J}{\partial \theta_j}(\theta_1^{old}, ..., \theta_n^{old})$$
关键点:
- 梯度指向函数值增加最快的方向
- 负梯度指向函数值下降最快的方向
- 学习率 $\alpha$ 控制每一步的大小
3. 学习率的重要性
- 学习率太小:收敛速度慢,需要很多次迭代
- 学习率太大:可能越过最低点,导致震荡甚至发散
- 理想的学习率:既能快速收敛,又不会引起震荡
代码示例
示例1:一元函数的梯度下降
import numpy as np
import matplotlib.pyplot as plt
def gradient_descent_1d(f, df, x_init, learning_rate=0.01, iterations=100):
"""
一元函数的梯度下降实现
参数:
f: 目标函数
df: 目标函数的导数
x_init: 初始x值
learning_rate: 学习率α
iterations: 迭代次数
返回:
x_history: x值的历史记录
y_history: 对应的函数值历史
"""
x = x_init
x_history = [x]
y_history = [f(x)]
for i in range(iterations):
# 计算当前梯度(导数)
gradient = df(x)
# 梯度下降更新规则
x = x - learning_rate * gradient
# 记录历史
x_history.append(x)
y_history.append(f(x))
return x_history, y_history
# 定义测试函数: f(x) = x^2
def f(x):
return x ** 2
# 定义导数: f'(x) = 2x
def df(x):
return 2 * x
# 设置初始参数
x_init = 5.0 # 起始点
learning_rate = 0.1
iterations = 20
# 运行梯度下降
x_history, y_history = gradient_descent_1d(f, df, x_init, learning_rate, iterations)
# 可视化过程
plt.figure(figsize=(10, 6))
# 绘制原函数
x_plot = np.linspace(-6, 6, 100)
y_plot = f(x_plot)
plt.plot(x_plot, y_plot, 'b-', label='f(x) = x²')
# 绘制梯度下降路径
plt.plot(x_history, y_history, 'ro-', markersize=5, label='梯度下降路径')
# 标记起点和终点
plt.plot(x_init, f(x_init), 'g^', markersize=10, label='起点')
plt.plot(x_history[-1], y_history[-1], 'rs', markersize=10, label='终点')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.title('梯度下降优化 f(x) = x²')
plt.legend()
plt.grid(True)
plt.show()
print(f"起始点: x = {x_init:.4f}, f(x) = {f(x_init):.4f}")
print(f"最终点: x = {x_history[-1]:.6f}, f(x) = {y_history[-1]:.6f}")
print(f"迭代次数: {iterations}")
示例2:二元函数的梯度下降(更贴近机器学习场景)
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def gradient_descent_2d(f, grad_f, theta_init, learning_rate=0.01, iterations=100):
"""
二元函数的梯度下降实现
参数:
f: 目标函数 J(θ1, θ2)
grad_f: 梯度函数 [∂J/∂θ1, ∂J/∂θ2]
theta_init: 初始参数 [θ1, θ2]
learning_rate: 学习率α
iterations: 迭代次数
"""
theta = np.array(theta_init, dtype=float)
theta_history = [theta.copy()]
J_history = [f(theta[0], theta[1])]
for i in range(iterations):
# 计算梯度
gradient = grad_f(theta[0], theta[1])
# 参数更新
theta = theta - learning_rate * gradient
# 记录历史
theta_history.append(theta.copy())
J_history.append(f(theta[0], theta[1]))
return np.array(theta_history), np.array(J_history)
# 定义二元函数: J(θ1, θ2) = θ1² + θ2²
def J(theta1, theta2):
return theta1**2 + theta2**2
# 定义梯度函数
def grad_J(theta1, theta2):
return np.array([2*theta1, 2*theta2])
# 设置初始参数
theta_init = [4.0, 4.0] # 起始点
learning_rate = 0.1
iterations = 30
# 运行梯度下降
theta_history, J_history = gradient_descent_2d(J, grad_J, theta_init, learning_rate, iterations)
# 创建可视化
fig = plt.figure(figsize=(15, 5))
# 3D曲面图
ax1 = fig.add_subplot(131, projection='3d')
θ1 = np.linspace(-5, 5, 50)
θ2 = np.linspace(-5, 5, 50)
Θ1, Θ2 = np.meshgrid(θ1, θ2)
Z = J(Θ1, Θ2)
ax1.plot_surface(Θ1, Θ2, Z, alpha=0.6, cmap='viridis')
ax1.plot(theta_history[:, 0], theta_history[:, 1], J_history, 'r.-',
markersize=5, linewidth=2, label='优化路径')
ax1.set_xlabel('θ1')
ax1.set_ylabel('θ2')
ax1.set_zlabel('J(θ1, θ2)')
ax1.set_title('3D曲面图')
# 等高线图
ax2 = fig.add_subplot(132)
contour = ax2.contour(Θ1, Θ2, Z, levels=20)
ax2.plot(theta_history[:, 0], theta_history[:, 1], 'ro-', markersize=4)
ax2.plot(theta_init[0], theta_init[1], 'g^', markersize=8, label='起点')
ax2.plot(theta_history[-1, 0], theta_history[-1, 1], 'rs', markersize=8, label='终点')
ax2.set_xlabel('θ1')
ax2.set_ylabel('θ2')
ax2.set_title('等高线图')
ax2.legend()
ax2.grid(True)
# 损失值变化图
ax3 = fig.add_subplot(133)
ax3.plot(J_history, 'b-', linewidth=2)
ax3.set_xlabel('迭代次数')
ax3.set_ylabel('损失值 J(θ1, θ2)')
ax3.set_title('损失值随迭代变化')
ax3.grid(True)
plt.tight_layout()
plt.show()
print("梯度下降过程:")
for i in range(0, len(theta_history), 5):
print(f"迭代 {i:2d}: θ1={theta_history[i,0]:.4f}, θ2={theta_history[i,1]:.4f}, J={J_history[i]:.4f}")
实践练习
练习1:学习率的影响(基础)
任务:修改示例1中的学习率,观察不同学习率(0.001, 0.1, 0.5)对收敛速度的影响。
要求:
- 绘制三种学习率下的梯度下降路径在同一图上
- 记录每种学习率达到最小值需要的迭代次数
- 解释你的观察结果
预期输出:包含三条不同颜色轨迹的图表,以及关于学习率影响的简短分析。
练习2:实现多变量梯度下降(中等)
任务:为函数 $J(\theta_1, \theta_2) = (\theta_1 - 1)^2 + 2(\theta_2 - 2)^2$ 实现梯度下降。
要求:
- 手动计算该函数的梯度
- 实现梯度下降算法
- 从初始点 (5, 5) 开始,找到最小值
- 绘制优化过程的等高线图
预期输出:梯度下降的迭代过程表格,以及包含优化路径的等高线图。
练习3:实际应用挑战(高级)
任务:使用梯度下降拟合一组数据点。
数据:
# 线性数据:y = 2x + 3 + 噪声
np.random.seed(42)
X = np.random.rand(100, 1) * 10
y = 2 * X + 3 + np.random.randn(100, 1) * 0.5
要求:
- 定义线性回归模型:$y = \theta_0 + \theta_1 x$
- 定义均方误差损失函数
- 计算损失函数的梯度
- 使用梯度下降找到最佳参数 $\theta_0, \theta_1$
- 绘制拟合直线和数据点
预期输出:参数的学习过程图,以及最终拟合结果的散点图。
常见错误
1. 学习率选择不当
错误:使用过大的学习率,导致算法发散
# 错误示例
learning_rate = 10.0 # 对于f(x)=x²来说太大了
正确做法:从小学习率开始尝试,逐步调整
2. 梯度计算错误
错误:计算梯度时忘记链式法则或符号错误
# 错误:对 f(x) = x³ 计算梯度
def df_wrong(x):
return 3*x # 错误:应该是 3*x²
def df_correct(x):
return 3*x**2 # 正确
建议:使用自动微分工具(如PyTorch、TensorFlow)或仔细验证手动计算的梯度
3. 未初始化参数
错误:忘记初始化参数或使用不合适的初始化值
# 错误:使用零初始化可能导致对称性问题
theta = np.zeros(2)
# 更好:使用小的随机值
theta = np.random.randn(2) * 0.01
4. 忽视收敛条件
错误:只设置最大迭代次数,不检查是否收敛
# 不完善的实现
for i in range(max_iter):
# 没有检查梯度是否足够小或损失是否变化
改进:添加收敛检查条件:
if np.linalg.norm(gradient) < tolerance:
print("梯度足够小,已收敛")
break
小结
关键要点回顾
-
梯度下降的本质:通过迭代更新参数,沿着损失函数的负梯度方向移动,逐步找到函数的最小值点
-
核心数学原理:
- 更新规则:$\theta_{new} = \theta_{old} - \alpha \cdot \nabla J(\theta_{old})$
- 梯度提供函数变化最快的方向信息
- 学习率控制优化步长
-
实践考虑:
- 学习率需要仔细调整(太小收敛慢,太大可能发散)
- 随机初始化参数通常优于零初始化
- 监控损失值变化以判断收敛情况
-
实际应用:
- 梯度下降是大多数机器学习算法(线性回归、神经网络等)的核心优化方法
- 现代深度学习框架提供了自动微分功能,简化了梯度计算
- 有许多变体算法(SGD、Adam等)改进了基本梯度下降的性能
下一步学习
掌握了梯度下降的数学原理后,你已经为理解更复杂的优化算法打下了基础。在下一课中,我们将学习如何实际加载和处理数据,这是将理论应用于实践的第一步。
思考题:为什么说梯度下降是机器学习的"引擎"?它在模型训练过程中扮演了什么角色?