如何理解梯度下降
目录一、梯度下降1.1 什么是梯度下降1.2 梯度下降解决什么问题1.3 梯度下降的损失函数1.4 梯度怎么走损失降的快1.5 梯度下降线性回归的递推公式1.6 代码如何实现梯度下降的更新规则1.7 对数据还要进行特征标准化Z-score1.8 三种梯度下降变体二、梯度下降常见情形与处理方法情形一正常收敛理想状态情形二学习率过小 —— 收敛过慢情形三学习率过大 —— 剧烈震荡情形四发散与梯度爆炸情形五特征尺度失衡 —— 梯度「偏科」情形六梯度消失了解即可情形七鞍点与局部极小了解即可情形八SGD 梯度噪声一、梯度下降1.1 什么是梯度下降梯度下降是一种迭代优化算法。给定一个可微的代价函数J(θ)它沿着负梯度方向一步步更新参数使 J 越来越小——就像在山坡上「沿最陡的下坡方向走路」最终走到谷底局部最小值。梯度下降Gradient Descent不直接解方程而是反复微调参数直到损失足够小。深度学习里几乎所有模型参数都是靠它或其变体训练出来的。1.2 梯度下降解决什么问题我们都知道线性回归在做什么给定房屋面积 x预测房价 y假设函数为其中为权重斜率为偏置截距参数向量。但是它能干什么数据量不大、特征不多时线性回归甚至可以用正规方程一步求出最优解但是换到另一个场景就不行了特征成千上万求逆代价太慢数据量上亿矩阵放不下内存神经网络等复杂模型根本没有解析解所以有了梯度下降不再一步求解而是重复其中为学习率代码中learning_rate为梯度。下文以及配套代码的符号说明符号含义代码变量样本数量m len(x)第 i 个样本特征面积x[i]列area第 i 个样本标签房价y[i]列price预测值y_pred[i]残差error[i]代价函数本篇文章取 MSElossJ 对 w 的偏导dwJ 对 b 的偏导db梯度向量(dw, db)学习率learning_rate特征均值、标准差stats[x_mean],stats[x_std]Z-score 标准化后的特征x_n[i]1.3 梯度下降的损失函数损失函数MSE残差均方误差 MSEMSEMean Squared Error的标准定义本教程令代价函数优化目标J 越大 → 拟合越差J 越小 → 拟合越好把 J 想象成一座「山」参数是你在山上的坐标是这里的海拔高度海拔高 损失大 预测差海拔低 损失小 预测好我们的目标从山上某个点出发走到谷底J 最小代码compute_loss()y_pred w * x b loss np.mean((y_pred - y) ** 2) # J MSE1.4 梯度怎么走损失降的快这一节我们分两步先弄懂「梯度是什么、为什么有用」再推导线性回归里的梯度公式。先想一个问题能往任意方向走吗站在山上的某一点你可以朝任意方向迈一小步只改、只改、或两者一起改。每种走法损失 J 变化快慢不同。例如走法含义只增大沿轴方向只增大沿轴方向同时变斜着走所有梯度下降的核心问题在无数方向里选哪一个能让下降最快一元情形偏导数就是「坡度」先只看固定。把看成的一元函数。偏导数表示增加一点点时变化多快——即方向的坡度。含义想让变小应0增大 →增大上坡减小0增大 →减小下坡增大0该方向平坦沿方向暂不动规律沿 的方向改下降最快至少在不改的前提下。方向同理应沿 改。二元情形梯度把两个坡度「打包」同时有和时把两个偏导数放进一个向量这就是梯度gradient。综合了「往走」和「往走」两个方向的坡度信息它指向函数增长最快的方向最陡的上坡指向下降最快的方向最陡的下坡1.5 梯度下降线性回归的递推公式在1.4的学习后我们了解了如下知识点梯度是什么所有偏导数组成的向量 ∇J∇J描述当前点各方向的坡度为什么用梯度它指出 JJ 增长最快的方向为什么更新用负梯度是 J 下降最快的方向那么我们可以写出他的递推公式来计算这个所谓的坡度也就是梯度对残差用链式法则:预测偏高应减小→ 对的修正方向与符号相关全体样本的累加得到「方向综合坡度」的梯度就是所有残差之和截距对每个样本影响相同1.6 代码如何实现梯度下降的更新规则前面已经说明要让损失降得最快必须沿方向更新参数从初始参数出发每轮迭代向量形式把两个式子合成一行为什么是减号因为指向上坡我们要下坡所以取负。控制这一步迈多大。def train(x, y, w_init0.0, b_init0.0, learning_rate0.01, epochs1000, ...): w, b w_init, b_init for epoch in range(1, epochs 1): loss compute_loss(x, y, w, b) # ① 算当前损失 J dw, db compute_gradients(x, y, w, b) # ② 算坡度 ∇J if max_grad_norm is not None: dw, db clip_gradients(dw, db, max_grad_norm) # 可选梯度裁剪第 8.5 节 w - learning_rate * dw # ③ w ← w - η·∂J/∂w b - learning_rate * db # ④ b ← b - η·∂J/∂b # ⑤ 重复直到 loss 足够小或达到 epochs return w, b, loss_history, ...学习率怎么选现象过小收敛慢需要很多轮适中损失平稳下降过大震荡甚至发散代码中compare_learning_rates()对比0.001,0.01,0.1 的收敛曲线。1.7 对数据还要进行特征标准化Z-score面积约 50~150房价约 200~400量级不同会导致与 尺度悬殊同一难以兼顾。对训练集做Z-score 标准化在上训练得到再还原denormalize_params()代码对应def standardize(x: np.ndarray, y: np.ndarray): stats { x_mean: x.mean(), x_std: x.std(), y_mean: y.mean(), y_std: y.std(), } x_n (x - stats[x_mean]) / stats[x_std] # x̃ (x - μx) / σx y_n (y - stats[y_mean]) / stats[y_std] # ỹ (y - μy) / σy return x_n, y_n, stats def denormalize_params(w_n: float, b_n: float, stats: dict): w w_n * stats[y_std] / stats[x_std] b stats[y_mean] stats[y_std] * b_n - w * stats[x_mean] return w, b1.8 三种梯度下降变体类型每次更新用多少样本特点BGD批量梯度下降全部 m 个梯度准确、稳定数据大时慢SGD随机梯度下降1 个快、有噪声路径抖动Mini-batch一小批如 32深度学习最常用Mini-batch已经成为深度学习训练的业界主流选择二、梯度下降常见情形与处理方法梯度下降并非每次都能「稳稳下山」。下面列举训练中常见情形、如何识别、如何处理并与代码中的演示图对应。(图片序号在上方配套代码与数据集中情形典型表现常见原因处理方法代码/图示正常收敛平稳下降趋近最优适中、特征已标准化保持当前设置图2、图3收敛过慢降得极慢要很多 epoch过小增大学习率搜索图5、图7左剧烈震荡来回跳动不收敛过大跨过谷底减小Momentum图7 中发散 / 梯度爆炸迅速变大变 NaN 或极大极大深层网络连乘梯度减小梯度裁剪图7 右梯度失衡收敛了不动或反之特征/标签量级差悬殊Z-score 标准化图8左 vs 中梯度消失更新几乎为 0参数不动深层 Sigmoid/Tanh 饱和本教程线性回归少见ReLU残差连接合适初始化深度学习阶段学鞍点 / 平坦区梯度接近 0下降极慢高维空间中鞍点众多Momentum、Adam 等自适应优化器进阶SGD 噪声损失曲线毛糙、抖动每步只用 1 个样本估计梯度增大 batchBGD / Mini-batch图6情形一正常收敛理想状态表现损失随 epoch单调下降后期变缓,参数曲线、损失曲线都「顺滑」条件≈0.01标准化后 Z-score 标准化 BGD。对应图示图2 参数收敛、图3 损失下降。情形二学习率过小 —— 收敛过慢表现太小⇒每步走不动在大量 epoch 后仍很高训练「没坏就是慢」处理增大学习率如 0.001→0.01学习率搜索试选下降最快且稳定的图5、图7学习率衰减前期大快速接近后期小精细收敛代码演示demo_learning_rate_abnormal()左图0.0001。# 梯度下降法.py → demo_learning_rate_abnormal() 调用 train() 对比不同 η _, _, history, _, _ train(x, y, learning_rate0.0001, epochs80, verboseFalse)情形三学习率过大 —— 剧烈震荡表现参数更新步长过大反复跨过谷底曲线呈锯齿状上下波动不下降可能在两侧来回跳数学直觉二次碗形损失在谷底附近过大使迭代点被「甩」到山坡另一侧。处理减小最直接Momentum动量积累历史梯度方向平滑震荡AdaGrad / RMSProp / Adam自适应调整每个参数的有效学习率代码演示demo_learning_rate_abnormal()中图0.35。_, _, history, _, _ train(x, y, learning_rate0.35, epochs80, verboseFalse)情形四发散与梯度爆炸表现指数级飙升出现甚至inf/nan绝对值爆炸控制台 loss 打印「越来越大」原因极大单步更新失控深度网络中链式法则连乘梯度指数放大本教程线性回归只有一层爆炸多由过大引起处理大幅减小梯度裁剪Gradient Clipping若代码中clip_gradients(dw, db, max_norm)train(..., max_grad_norm1.0)。def clip_gradients(dw: float, db: float, max_norm: float): 若 ‖∇J‖ τ则缩放梯度防止单步更新过大。 norm np.sqrt(dw ** 2 db ** 2) if norm max_norm: scale max_norm / norm dw * scale db * scale return dw, db # train() 内算完梯度后可选裁剪 dw, db compute_gradients(x, y, w, b) if max_grad_norm is not None: dw, db clip_gradients(dw, db, max_grad_norm) w - learning_rate * dw情形五特征尺度失衡 —— 梯度「偏科」表现 与 量级差倍以上一个参数疯狂更新另一个几乎不动损失震荡或收敛到错误位置原因例如面积用平方毫米∼、房价用元未做标准化。处理Z-score 标准化Min-Max 缩放到 [0,1]手工统一单位不如标准化通用数据集house_price_bad_scale.csvarea_mm2,price_yuan与主数据集同一线性关系仅单位极端。代码演示demo_scale_and_clipping()—— 左图未标准化失控vs 中图标准化稳定。# 梯度下降法.py → demo_scale_and_clipping() x, y load_data(BAD_SCALE_PATH) # 极端尺度数据 x_n, y_n, stats standardize(x, y) _, _, hist_raw, _, _ train(x, y, learning_rate0.01, epochs60, verboseFalse) # 未标准化 _, _, hist_std, _, _ train(x_n, y_n, learning_rate0.01, epochs60, verboseFalse) # 标准化后情形六梯度消失了解即可表现梯度参数几乎不更新。原因深层网络 Sigmoid/Tanh 饱和区连乘导数 →0。单层线性回归几乎不会梯度消失。处理ReLU 激活、残差网络ResNet、LSTM 门控、合适初始化、Batch Norm。情形七鞍点与局部极小了解即可表现梯度接近 0但不在全局最优高维空间中鞍点比局部极小更常见。处理Momentum 帮助「冲过」平坦区Adam 等自适应方法多次随机初始化。情形八SGD 梯度噪声表现损失曲线比 BGD更毛糙上下抖动明显。原因每轮只用 1 个样本是的有噪声估计。处理方法说明用 BGD稳定但慢5000 样本尚可Mini-batch折中深度学习默认减小 SGD 的噪声大时步子小一点学习率衰减后期缩小步长代码演示图6demo_sgd_vs_bgd()。# 梯度下降法.py → demo_sgd_vs_bgd() 核心对比 # BGD用全部 m 个样本算梯度与 train() 相同 dw, db compute_gradients(x, y, w_bgd, b_bgd) w_bgd - lr * dw b_bgd - lr * db # SGD随机抽 1 个样本用单样本梯度更新不再除以 m idx rng.integers(0, m) xi, yi x[idx], y[idx] error w_sgd * xi b_sgd - yi w_sgd - lr * 2.0 * error * xi # ∂J^(i)/∂w 2·e^(i)·x^(i) b_sgd - lr * 2.0 * error # ∂J^(i)/∂b 2·e^(i)