这是 数学重学路线图 阶段五的子页面
优化与梯度下降:让机器”学习”的核心
优化 = 找到最好的那个值
机器学习本质上就是一个优化问题
这一节是 25-微积分直觉 中梯度下降的深入展开
一、什么是优化问题
直觉比喻
你被蒙上眼睛丢到一座山上
任务:找到最低的山谷
你唯一能做的:感受脚下的坡度(梯度),然后往下坡方向走
这就是梯度下降
数学表述
给定目标函数 f(θ),找到参数 θ* 使得 f(θ*) 最小
θ* = argmin f(θ)
优化无处不在
| 场景 | 目标函数 | 要优化的参数 |
| 机器学习 | 损失函数 | 模型权重 |
| 后端调优 | 响应时间 | 连接池大小、缓存大小 |
| 安全模型 | 检测误差 | 分类阈值 |
| 资源分配 | 成本 | 资源配额 |
二、损失函数:衡量”错得多离谱”
MSE(均方误差)
MSE = Σ(预测值 - 真实值)² / n
直觉:每个预测的误差平方后取平均
为什么要平方?
消除正负抵消
放大大误差(惩罚离谱的预测)
交叉熵损失
Loss = -Σ y·log(ŷ)
用于分类问题
当预测概率偏离真实标签时,损失急剧增大
和信息论有关 → 27-信息论基础
Python 计算损失函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import numpy as np
def mse_loss(y_true, y_pred): return np.mean((y_true - y_pred) ** 2)
y_true = np.array([3.0, 5.0, 2.5, 7.0]) y_pred = np.array([2.8, 5.2, 2.0, 6.5]) print(f"MSE = {mse_loss(y_true, y_pred):.4f}")
def binary_cross_entropy(y_true, y_pred): epsilon = 1e-15 y_pred = np.clip(y_pred, epsilon, 1 - epsilon) return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))
y_true = np.array([1, 0, 1, 1]) y_pred = np.array([0.9, 0.1, 0.8, 0.7]) print(f"交叉熵 = {binary_cross_entropy(y_true, y_pred):.4f}")
y_pred_bad = np.array([0.5, 0.5, 0.5, 0.5]) print(f"差预测的交叉熵 = {binary_cross_entropy(y_true, y_pred_bad):.4f}")
|
三、梯度:多元函数的”导数”
从导数到梯度
一元函数的导数 f'(x) → 告诉你往哪走函数增长最快
多元函数的梯度 ∇f(x₁, x₂, ...) → 一个向量,每个分量是对应变量的偏导数
梯度的几何含义
梯度方向 = 函数增长最快的方向
梯度大小 = 增长的速率
负梯度方向 = 函数下降最快的方向
偏导数
多元函数对某一个变量求导,其他变量当常数
例:f(x, y) = x² + 2xy + y³
∂f/∂x = 2x + 2y(把 y 当常数)
∂f/∂y = 2x + 3y²(把 x 当常数)
∇f = (2x + 2y, 2x + 3y²)
Python 计算梯度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import numpy as np
def f(params): x, y = params return x**2 + 2*x*y + y**3
def numerical_gradient(f, params, h=1e-7): """数值梯度:对每个参数微扰""" grad = np.zeros_like(params, dtype=float) for i in range(len(params)): params_plus = params.copy() params_minus = params.copy() params_plus[i] += h params_minus[i] -= h grad[i] = (f(params_plus) - f(params_minus)) / (2 * h) return grad
point = np.array([1.0, 2.0]) grad = numerical_gradient(f, point) print(f"在 (1, 2) 处的梯度: {grad}")
|
四、梯度下降算法
核心公式
θ_new = θ_old - α · ∇f(θ_old)
α 是学习率(步长)
每一步都朝着下降最快的方向走一小步
直觉理解
蒙眼人在山上的策略:
- 感受脚下坡度(计算梯度)
- 朝下坡方向迈一步(更新参数)
- 重复,直到脚下变平(梯度接近零)
手动实现梯度下降(线性回归)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import numpy as np import matplotlib.pyplot as plt
np.random.seed(42) X = np.random.uniform(0, 10, 100) y_true = 3 * X + 7 + np.random.normal(0, 2, 100)
w = 0.0 b = 0.0 lr = 0.001 epochs = 200
loss_history = []
for epoch in range(epochs):
y_pred = w * X + b
loss = np.mean((y_pred - y_true) ** 2) loss_history.append(loss)
dw = 2 * np.mean((y_pred - y_true) * X) db = 2 * np.mean(y_pred - y_true)
w = w - lr * dw b = b - lr * db
if epoch % 50 == 0: print(f"Epoch {epoch:3d}: loss={loss:.4f}, w={w:.4f}, b={b:.4f}")
print(f"\n最终结果: w={w:.4f} (真实=3), b={b:.4f} (真实=7)")
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[0].plot(loss_history) axes[0].set_xlabel('Epoch') axes[0].set_ylabel('Loss') axes[0].set_title('损失函数下降过程') axes[0].grid(True)
axes[1].scatter(X, y_true, alpha=0.5, label='数据点') axes[1].plot([0, 10], [b, w*10+b], 'r-', linewidth=2, label=f'拟合线: y={w:.2f}x+{b:.2f}') axes[1].legend() axes[1].set_title('线性回归结果') axes[1].grid(True)
plt.tight_layout() plt.show()
|
五、学习率:最关键的超参数
学习率太大
每步迈太远,跳过最优点
在最优点附近来回震荡,甚至发散
比喻:跳远运动员在山谷里蹦,越蹦越高
学习率太小
每步迈太短,收敛极慢
可能需要上万步才到最优点
比喻:蜗牛下山
学习率对比实验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import numpy as np import matplotlib.pyplot as plt
def f(x): return (x - 3) ** 2 + 1
def df(x): return 2 * (x - 3)
learning_rates = [0.01, 0.1, 0.5, 0.95]
fig, axes = plt.subplots(2, 2, figsize=(12, 10)) axes = axes.flatten()
for idx, lr in enumerate(learning_rates): x = 0.0 history = [x] for _ in range(30): x = x - lr * df(x) history.append(x)
x_plot = np.linspace(-2, 8, 200) axes[idx].plot(x_plot, f(x_plot), 'b-') axes[idx].plot(history, [f(h) for h in history], 'ro-', markersize=4) axes[idx].set_title(f'学习率 = {lr}') axes[idx].set_ylim(0, 15) axes[idx].grid(True)
plt.suptitle('不同学习率的梯度下降表现', fontsize=14) plt.tight_layout() plt.show()
|
六、局部最优 vs 全局最优
问题
现实中的损失函数不是简单的碗状
可能有很多”坑”(局部最小值)
梯度下降可能掉进一个不是最深的坑里
解决策略
随机初始化:多次随机初始点,取最好的结果
动量 Momentum:给下降过程加”惯性”,冲过小坑
v = β·v + ∇f(θ)
θ = θ - α·v
比喻:球从山上滚下来,有惯性不会卡在小坑里
学习率衰减:开始大步走快速接近,后面小步走精确定位
模拟退火:偶尔接受”上坡”的步骤,跳出局部最优
鞍点问题
高维空间中,鞍点比局部最小值更常见
鞍点:某些方向是最小,某些方向是最大
梯度为零但不是极值点
七、常见优化算法直觉
SGD(随机梯度下降)
不用全部数据算梯度,随机取一小批(mini-batch)
优点:快,有随机性可以跳出局部最优
缺点:震荡大,需要手动调学习率
Momentum(带动量的 SGD)
在 SGD 基础上加了”惯性”
加速收敛,减少震荡
像球在碗里滚动,越滚越快
RMSProp
自适应学习率:对每个参数用不同的学习率
梯度大的参数用小学习率(防震荡)
梯度小的参数用大学习率(加速)
Adam(最常用)
Momentum + RMSProp 的结合
既有动量加速,又有自适应学习率
大多数情况下的默认选择
一张表对比
| 算法 | 核心思想 | 优点 | 缺点 |
| SGD | 随机采样梯度 | 简单 | 需调学习率 |
| Momentum | 加惯性 | 收敛快 | 多一个超参数 |
| RMSProp | 自适应学习率 | 自动调节 | 可能不够稳 |
| Adam | 动量+自适应 | 通用 | 偶尔不如SGD |
八、凸优化简介
什么是凸函数
直觉:像一个碗,只有一个最低点
数学:任意两点连线在函数上方
对凸函数做梯度下降,局部最优 = 全局最优
常见凸函数
x²、|x|、e^x、MSE 损失(对线性模型)
非凸函数
深度学习的损失函数几乎都是非凸的
所以深度学习训练是个”尽力而为”的过程
好消息:实践中找到的局部最优通常也足够好
九、大数据中的优化
模型训练 = 优化问题
数据量 → 百万/亿条
参数量 → 百万/十亿
全量梯度计算不现实 → SGD 和 mini-batch
分布式优化
数据并行:不同机器算不同 batch 的梯度,然后聚合
模型并行:不同机器存储模型的不同部分
超参数调优
学习率、batch size、正则化系数都需要优化
网格搜索、随机搜索、贝叶斯优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| from sklearn.model_selection import GridSearchCV from sklearn.linear_model import Ridge import numpy as np
X = np.random.randn(200, 5) y = X @ np.array([1, 2, 0, -1, 0.5]) + np.random.randn(200) * 0.5
param_grid = {'alpha': [0.001, 0.01, 0.1, 1.0, 10.0]} grid_search = GridSearchCV(Ridge(), param_grid, cv=5, scoring='neg_mean_squared_error') grid_search.fit(X, y)
print(f"最优 alpha: {grid_search.best_params_}") print(f"最优得分: {-grid_search.best_score_:.4f}")
|
十、安全中的优化
对抗样本(梯度攻击)
攻击者利用模型梯度来制造对抗样本
FGSM 攻击原理:
- 计算损失函数对输入的梯度
- 沿梯度方向微调输入
- 人眼看不出区别,但模型判断完全错误
Python FGSM 攻击演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import numpy as np
def sigmoid(z): return 1 / (1 + np.exp(-z))
w, b = 2.0, -3.0 x_original = 2.0 y_true = 1
y_pred = sigmoid(w * x_original + b) print(f"原始预测: {y_pred:.4f} (正确分类为1)")
grad_x = -(y_true - y_pred) * w
epsilon = 1.5 x_adversarial = x_original + epsilon * np.sign(grad_x)
y_pred_adv = sigmoid(w * x_adversarial + b) print(f"对抗样本预测: {y_pred_adv:.4f} (被攻击后可能误分类)") print(f"输入扰动: {x_original} → {x_adversarial}")
|
安全模型训练
对抗训练:训练时加入对抗样本,提高模型鲁棒性
梯度裁剪:限制梯度大小,防止梯度爆炸攻击
差分隐私:给梯度加噪声,防止通过梯度泄露训练数据
十一、后端中的优化思维
连接池大小优化
太小 → 请求排队,响应慢
太大 → 内存消耗大,数据库压力大
最优值 = 使总响应时间最小的连接数
缓存大小优化
缓存太小 → 命中率低,频繁回源
缓存太大 → 内存浪费
最优值 = 使 cost(命中率, 内存消耗) 最小的缓存大小
简单的后端参数优化模拟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import numpy as np import matplotlib.pyplot as plt
def response_time(pool_size): """ 太小: 排队时间长 太大: 数据库压力导致慢查询 最优点在中间 """ queue_delay = 500 / pool_size db_pressure = 0.01 * pool_size ** 1.5 base_time = 50 return base_time + queue_delay + db_pressure
pool = 5.0 lr = 0.5 h = 0.01
for i in range(100): grad = (response_time(pool + h) - response_time(pool - h)) / (2 * h) pool = pool - lr * grad pool = max(1, pool)
print(f"最优连接池大小: {pool:.1f}") print(f"最小响应时间: {response_time(pool):.2f}ms")
sizes = np.linspace(1, 100, 200) times = [response_time(s) for s in sizes] plt.plot(sizes, times, 'b-') plt.axvline(x=pool, color='r', linestyle='--', label=f'最优={pool:.1f}') plt.xlabel('连接池大小') plt.ylabel('平均响应时间 (ms)') plt.title('连接池大小优化') plt.legend() plt.grid(True) plt.show()
|
十二、练习题
题目1:概念理解
为什么梯度下降要沿梯度的反方向走?如果沿梯度正方向走会怎样?
答案
梯度方向是函数增长最快的方向
我们要最小化目标函数,所以要往减小最快的方向走 = 梯度反方向
如果沿梯度正方向走 → 函数值会越来越大(梯度上升,可用于最大化问题)
题目2:学习率实验
用 Python 对 f(x) = x⁴ - 3x³ + 2 做梯度下降,分别尝试学习率 0.01, 0.1, 0.3,观察哪个收敛最快,哪个可能发散
答案
1 2 3 4 5 6 7 8
| def f(x): return x**4 - 3*x**3 + 2 def df(x): return 4*x**3 - 9*x**2
for lr in [0.01, 0.1, 0.3]: x = 4.0 for i in range(50): x = x - lr * df(x) print(f"lr={lr}: x={x:.4f}, f(x)={f(x):.4f}")
|
0.01 收敛慢但稳,0.1 适中,0.3 可能震荡
题目3:手算梯度
函数 f(x, y) = x² + xy + 2y²,求 ∇f 并计算在点 (1, -1) 处的梯度方向
答案
∂f/∂x = 2x + y,在 (1,-1) 处 = 1
∂f/∂y = x + 4y,在 (1,-1) 处 = -3
∇f(1, -1) = (1, -3)
梯度下降方向 = (-1, 3)
题目4:安全应用
解释 FGSM 攻击为什么要用 sign(梯度) 而不是直接用梯度值?
答案
sign() 保证每个维度的扰动大小一致(都是 ±ε)
直接用梯度值会导致某些维度扰动很大、某些很小
sign 版本在 L∞ 范数下是最优攻击(最小扰动最大效果)
且计算简单高效
题目5:综合实践
实现一个带 Momentum 的梯度下降,与普通梯度下降对比在 f(x) = x² + 10*sin(x) 上的表现(这个函数有多个局部最小值)
答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import numpy as np
def f(x): return x**2 + 10*np.sin(x) def df(x): return 2*x + 10*np.cos(x)
x_gd = 5.0 for _ in range(100): x_gd -= 0.05 * df(x_gd)
x_mom = 5.0 v = 0 for _ in range(100): v = 0.9 * v + df(x_mom) x_mom -= 0.05 * v
print(f"普通GD: x={x_gd:.4f}, f={f(x_gd):.4f}") print(f"Momentum: x={x_mom:.4f}, f={f(x_mom):.4f}")
|
十三、总结与关联
梯度下降是机器学习最基础的优化算法
关键要素:损失函数 + 梯度 + 学习率
Adam 是最常用的优化器,但理解 SGD 是基础
关联笔记
← 25-微积分直觉 导数和梯度的基础
→ 27-信息论基础 交叉熵损失的信息论解释
→ 数学重学/22-线性代数直觉 高维梯度与矩阵运算