Loss不收敛主因是学习率未随GPU数线性放大:单机多卡时strategy.num_replicas_in_sync返回设备总数(如4卡为4),若仍用原始学习率,等效于步子变大但方向不准,导致优化剧烈抖动、loss震荡;须设learning_rate = 基础学习率 × strategy.num_replicas_in_sync。
Loss不收敛是因为学习率没随GPU数线性放大
单机多卡时,strategy.num_replicas_in_sync 返回的是参与训练的设备总数(比如 4 卡就是 4),但很多人直接用原始学习率跑,导致每卡实际 batch size 变大了,而学习率没跟上——等效于“步子变大但眼睛没睁大”,优化方向剧烈抖动,loss 就来回跳。
这不是学习率“太高”或“太低”的问题,而是它和全局 batch size 不匹配。TensorFlow 默认不自动缩放,必须手动做。
- 显式读取同步副本数:
strategy.num_replicas_in_sync - 基础学习率 × 该数值,才是应设的
learning_rate - 若用
tf.keras.optimizers.schedules,需在构造时就传入放大后的值,不能只在model.compile()里塞原始值
别在验证 loss 上升时盲目调小学习率
看到 val_loss 连续两 epoch 上升就触发 ReduceLROnPlateau,很容易把本该继续下降的训练中途掐断。尤其 warmup 阶段或数据分布突变(如新用户涌入)时,验证集波动是正常的,不代表模型坏了。
真正该看的是:梯度是否还在有效更新、训练 loss 是否持续下降、各层梯度范数是否量级混乱。
- 先加回调:
tf.keras.callbacks.TensorBoard(histogram_freq=1) - 打开 TensorBoard → “Distributions” 标签页 → 查看
gradients/下各层分布 - 若某层梯度标准差 > 权重均值的 10 倍,说明方差爆炸,此时降学习率无效,得加
tf.clip_by_global_norm
BatchNorm 在多卡下统计量不同步会放大震荡
TF 的 tf.keras.layers.BatchNormalization 默认使用每个 device 独立统计,多卡时各卡算各自的 running_mean / running_var,导致前向输出不一致——尤其在小 batch 或训练初期,统计不准 + 分布偏移,loss 就像被随机拽着走。
Python 3.14.3
微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。
这个问题不会报错,但会让 loss 曲线出现周期性毛刺或缓慢爬升假象。
- 确认 BN 层设了
momentum=0.99(默认 0.99 是 OK 的,但有些旧版默认 0.999,要检查) - 迁移学习冻结主干时,BN 层必须显式设
layer.trainable = False,否则它仍偷偷更新统计量 - 若输入未归一化(如
x_train.std()≈ 2.5),BN 输入可能溢出,先做(x - mean) / (std + 1e-8)
loss 突然变 NaN?先查 sqrt 和除法分母
多卡训练中,NaN 往往不是全卡同时出现,而是某卡先崩,再污染梯度同步过程。最常见源头是未防护的数学运算:
-
torch.sqrt或tf.sqrt输入为负(哪怕 -1e-12)→ 直接出NaN - 分母为 0 或极小值(如余弦相似度计算后未加
eps=1e-8)→ 梯度爆炸 - triplet loss 中的 margin 减法结果为负,开方前没
tf.nn.relu或torch.clamp_min
建议所有涉及开方、除法、log 的地方,统一加 eps=1e-8 并做非负 clamp;多卡下更要检查每卡 local batch 是否为空(比如 dataloader 分片不均)。
同步学习率只是起点,真正稳住 loss 需要同时约束梯度尺度、BN 统计行为和数值稳定性。最容易被忽略的是:多卡下 loss 值本身不具可比性,只有经过 all-reduce 后的均值才反映全局状态——但绝大多数调试手段(print、TensorBoard scalar)只抓到单卡值。