PWC-Net光流训练复现:多尺度监督与混合精度实践

发布时间:2026/10/27 5:04:54
PWC-Net光流训练复现:多尺度监督与混合精度实践 训练这一步往往是复现里最容易“看起来跑通了、实际没学到东西”的环节。网络结构照着论文搭出来前向能出光流图打印出来数值也不报错但只要一进训练循环问题就会接连冒出来loss 从第一轮就 NaN、十个 epoch 之后 EPE 卡在 20 像素不降、验证集比训练集好得离谱、显存刚开到 batch 8 就爆。这篇就围绕 PWC-Net 在 Paddle 上的训练部分展开把多尺度监督、课程式数据切换、光流增强的一致性、混合精度下的数值稳定这几件事拆开讲清楚顺带把我在实际跑通时踩过的坑和判断依据都摊在桌面上。适合已经能把前向跑通、卡在“训不起来”或者“训起来但效果不对”这一步的人也适合想搞清楚光流网络训练套路的人。1. 训练总体设计与思路拆解1.1 训练阶段才是复现的真正分水岭PWC-Net 的网络部分其实相对“规矩”金字塔特征提取、光流 warping、代价体积cost volume、光流估计、上下文网络 refinement每一块都有明确的输入输出形状。但训练部分不是它是一堆相互耦合的决策叠在一起——学什么数据、按什么顺序学、每个尺度怎么给监督、增强怎么做才不把标签弄坏、显存和吞吐怎么平衡。这些决策里任何一个出偏差网络都不会报错只会安静地训出一个“会输出彩色光流图但完全没用”的模型。我个人的判断是复现光流网络时网络结构占三成工作量训练管线占七成。原因很朴素结构写错了形状对不上程序立刻崩训练写错了程序一路顺畅你在两周后才发现验证集 EPE 死活下不去。所以训练部分的设计必须先想清楚“监督信号从哪来、以什么尺度、以什么权重、以什么顺序喂进去”再动手写循环。这也是我建议的推进顺序先把单 batch 的 loss 跑通并肉眼验证数值合理再谈多 epoch。1.2 多尺度监督与课程学习构成两条主线PWC-Net 的训练设计有两条主线。第一条是多尺度监督网络在第 2 到第 6 层每一层都输出一个光流估计最终结果由最粗层逐级上采样加残差得到所以监督也必须逐层给。论文里的做法是计算每一层预测与下采样后的真值之间的端点误差EPE再按层加权求和权重从粗到细递减。这个设计的意图很直接——粗层负责大位移和全局结构细层负责边界和小位移细节如果只监督最后一层粗层就没有明确的梯度来源训练早期容易在错误的大位移上打转。第二条是课程学习先在合成数据 FlyingChairs 上训到收敛再换到更复杂、更接近真实场景分布的 FlyingThings3D 上继续训最后用少量真实场景数据KITTI、Sintel 这类做微调。这个顺序不是随便定的——FlyingChairs 数据量适中、位移分布温和适合让网络先学会“warping 和匹配”这件事的基本模式Thing3D 里物体种类和运动模式复杂得多用来提升泛化真实数据量小、标注稀疏只适合低学习率微调用来对齐真实成像特性噪声、曝光、运动模糊。这两条主线决定了训练代码的基本骨架数据加载要支持多阶段切换模型前向要能把中间层输出全部吐出来损失函数要能按尺度对齐并加权。三者缺一不可缺了哪一个都会让训练效果打折。1.3 超参初值怎么定别凭感觉超参没必要一开始就调先把论文和主流复现里的取值当基线跑通再基于自己的观察微调。基线大致是这样优化器用 Adambetas 取 (0.9, 0.999)初始学习率 1e-4权重衰减 4e-4batch size 8输入裁剪尺寸 448×384。这个 batch size 和尺寸不是随便选的它是在 11GB 级别显存上能稳定跑起来的一组值再大就要考虑混合精度或梯度累积。学习率的依据也值得说一句Adam 在 1e-4 这个量级对光流网络比较友好太小1e-5在 Chairs 上会训得非常慢300 epoch 都未必收敛太大1e-3在训练早期代价体积还没学到有效匹配时容易让 loss 剧烈震荡甚至发散。权重衰减 4e-4 略高于常见默认值是因为光流网络的回归头比较容易过拟合到合成数据的纹理上适当加压有帮助。学习率调度上我在 Chairs 阶段用的是分段衰减前 200 epoch 保持 1e-4后 100 epoch 降到 1e-5。如果你用的是余弦退火注意把 warmup 加上——前 500 到 1000 个 iteration 线性升温能明显减少早期 NaN 的概率。这块后面第 4 章会给出具体配置。2. 数据管线从光流文件到可训练张量2.1 数据集选型与阶段配比的实际考量合成数据集和真实数据集在训练里的角色完全不同不能混着喂。FlyingChairs 大约两万三千对图像背景是随机拼贴的纹理前景是椅子的三维模型渲染运动以平面内的刚体变换为主。它的问题是太“干净”没有复杂遮挡、没有真实光照变化优点是位移分布规整、标注绝对准确非常适合作为第一阶段。FlyingThings3D 的规模更大场景里有大量不同形状的物体做复杂的三维运动遮挡和大幅位移的比例明显更高。它能把网络从“学会基本匹配”推到“能处理复杂运动”。真实数据这块KITTI 的光流训练集只有两百对图像Sintel 的训练序列一千多帧量级和合成数据完全不在一个数量级所以只能用来做最后阶段的微调学习率必须降到 1e-5 左右否则一轮就把前面学到的东西覆盖掉了。我在实际配比上的做法是Chairs 阶段跑满Thing3D 阶段取 Chairs 阶段 epoch 数的三分之一左右真实数据微调阶段再取更少的轮次并且每轮都做验证。这个比例不是定理但你如果上来就把三个阶段的数据混在一个 DataLoader 里随机采样结果通常比按阶段训要差因为不同数据源的位移尺度分布差异太大混合采样会让网络的输出分布来回拉扯。2.2 光流文件读取与合法性校验光流的真值文件格式五花八门常见的是.flo二进制和 16 位 PNG 编码两种。.flo的格式是一个 float32 的魔数 202021.25接着两个 int32 表示宽和高然后是宽×高×2 个 float32 表示每个像素的 (u, v) 位移。读的时候最容易出错的地方是宽高的顺序和 reshape 的形状很多人的 bug 就出在这里——读出来图像是转置的训练照样能跑但 loss 永远降不下去。import numpy as np def read_flo(path): with open(path, rb) as f: magic np.fromfile(f, np.float32, count1)[0] if abs(magic - 202021.25) 1e-3: raise ValueError(f光流文件魔数不匹配: {path}) w int(np.fromfile(f, np.int32, count1)[0]) h int(np.fromfile(f, np.int32, count1)[0]) data np.fromfile(f, np.float32, count2 * w * h) if data.size ! 2 * w * h: raise ValueError(f光流数据长度异常: {path}) flow np.reshape(data, (h, w, 2)) return flow # 通道顺序为 (u, v)即水平、垂直读取之后一定要做两道校验。第一道是形状校验光流的 (h, w) 必须和对应两张图像的尺寸完全一致不一致就说明文件配对错了。第二道是数值校验统计光流的最大幅值如果出现 1e6 这种量级的异常值部分数据集的无效区域会用极大值标记必须替换成 0 并同步生成 valid mask否则这个像素产生的梯度足以把整批训练带崩。我见过最典型的情况是某个样本的边缘有一列无效值训练到第几十轮突然 loss 冲到几万查了半天才发现是这里。2.3 数据增强的坑翻转必须和光流同步光流任务的数据增强和分类任务完全不是一个思路。分类任务里你随便翻转、旋转、调色标签不变光流任务里图像做了几何变换光流标签必须做完全一致的变换否则就是在教网络学错误的东西。这不是“效果差一点”的问题是标签直接错掉。水平翻转是最常用的增强正确做法是图像沿宽度方向镜像光流的 u 分量取反、并在宽度方向镜像v 分量只在宽度方向镜像不取反。垂直翻转则反过来v 分量取反。旋转更麻烦光流向量需要跟着旋转矩阵变换很多实现干脆不做旋转只用翻转加裁剪我觉得这个取舍是合理的——收益不大出错概率不小。def random_crop_flip(img1, img2, flow, crop_h, crop_w, do_flip): h, w flow.shape[:2] y0 np.random.randint(0, h - crop_h 1) x0 np.random.randint(0, w - crop_w 1) img1 img1[y0:y0 crop_h, x0:x0 crop_w] img2 img2[y0:y0 crop_h, x0:x0 crop_w] flow flow[y0:y0 crop_h, x0:x0 crop_w].copy() if do_flip: img1 img1[:, ::-1] img2 img2[:, ::-1] flow flow[:, ::-1].copy() flow[..., 0] * -1.0 # u 分量取反 return np.ascontiguousarray(img1), np.ascontiguousarray(img2), flow这里有个细节必须强调flow[:, ::-1]得到的是负步长的视图直接改它会报错或者改到原数组所以要先.copy()再改 u 分量最后再ascontiguousarray保证内存连续。这几个操作我一开始懒得加结果在 DataLoader 多进程下偶发数据错乱排查了很久。颜色增强这块亮度、对比度、饱和度、色调的轻微抖动是可以做的幅度控制在 ±0.4 以内比较安全。但要注意两张图像必须用相同的随机参数不能各抖各的——两张图的成像条件不一致会人为制造出大量虚假的光度差异让网络把精力浪费在补偿这些差异上。同理加高斯噪声也要两张图同步加同样的噪声图。2.4 DataLoader 配置与吞吐优化数据加载经常是训练速度的瓶颈尤其是光流任务里还要读.flo文件、做裁剪翻转CPU 压力不小。Paddle 的paddle.io.DataLoader有几个参数值得注意num_workers建议设成 CPU 物理核数的 0.5 到 0.75 倍设太大反而因为进程切换开销变慢use_shared_memoryTrue能减少进程间数据拷贝drop_lastTrue在 batch norm 相关的层存在时更稳妥persistent_workers在 Paddle 的较新版本里也支持能避免每个 epoch 重新拉起进程。loader paddle.io.DataLoader( dataset, batch_size8, shuffleTrue, num_workers6, drop_lastTrue, use_shared_memoryTrue, return_listTrue, )还有一个常被忽略的点把归一化和 HWC 到 CHW 的转换放在 dataset 里用 numpy 做完比在训练循环里用 Paddle 的算子做要快。因为前者在 worker 进程里并行执行后者在主进程串行执行。我实测下来把这三步归一化、转置、转 tensor挪进 dataset 之后单卡吞吐能提升一到两成具体取决于你的输入尺寸。3. 损失函数设计与多尺度监督实现3.1 EPE 的含义与为什么要做鲁棒化EPE 就是端点误差说白了就是预测光流和真值光流之间的欧氏距离单位是像素。它的计算简单两个二维向量相减取平方和开根号。但直接对全图取平局值有两个问题。一是无效区域光流数据集里总有些像素没有可靠真值遮挡区、出界区这些位置必须用 valid mask 排除否则你是在要求网络预测一个不存在的东西。二是异常值即使有 mask个别像素的真值也可能存在标注噪声平方开根号之后误差被放大会主导整个 loss。所以实践中一般会做一步鲁棒化处理。常见做法是给 EPE 设一个上界超过上界的按上界算等价于把 loss 从 L2 变成近似的 L1 行为对大误差的惩罚不再线性增长。另一种做法是直接用 Charbonnier 损失形式上是sqrt(x^2 eps^2)可导性更好在 0 附近比 L1 平滑训练早期梯度更稳定。def charbonnier(x, eps1e-3): return paddle.sqrt(x * x eps * eps)我在 Chairs 阶段用带 mask 的 EPE 直接训Thing3D 阶段换成 Charbonnier原因是后一阶段数据里的遮挡和噪声更多L2 的梯度会被异常样本带偏。这个切换不是必须的但它确实让我这边的 loss 曲线平滑了不少。3.2 多尺度对齐下采样真值时别忘了缩放置信多尺度监督最容易错的地方是真值下采样时的幅值缩放。网络在第 k 层输出的光流是在该层分辨率下的位移而真值是在原图分辨率下的位移。你如果把真值双线性插值到低分辨率却不缩放数值那低层的监督目标就是错的——一个原本 20 像素的位移在 1/4 分辨率下应该是 5 像素如果你还让它去拟合 20那这一层的损失永远是大的梯度方向也完全错了。正确做法是先把真值插值到目标分辨率再乘以该层的缩放因子也就是目标分辨率与原分辨率的比值。同时 valid mask 也要用最近邻插值下采样不能用双线性否则会在边界处产生 0 到 1 之间的伪 valid 值。def downsample_flow(flow, valid, size): h_ratio size[0] / flow.shape[2] w_ratio size[1] / flow.shape[3] flow_down F.interpolate(flow, sizesize, modebilinear, align_cornersFalse) scale paddle.to_tensor([w_ratio, h_ratio], dtypeflow.dtype).reshape([1, 2, 1, 1]) flow_down flow_down * scale valid_down F.interpolate(valid, sizesize, modenearest) return flow_down, valid_down注意 u 分量对应宽度的缩放、v 分量对应高度的缩放顺序别写反。这个细节我在第一次写的时候搞反了因为输入是 448×384宽×高但 tensor 形状是 [B, C, H, W]两个比例很容易混。写反了的后果是训练能跑、loss 能降但收敛后的精度比正常情况低一截很难通过看曲线发现。3.3 加权求和与代码实现按照公开论文的设定六个层级里第 2 到第 6 层输出预测权重从粗到细大致是 0.005、0.01、0.02、0.08、0.32。这个权重分配的意图很明确越靠近最终输出的层级权重越大因为它直接决定推理时的精度粗层权重小只起引导作用避免粗层的粗糙预测过度干扰细层的学习。class MultiScaleFlowLoss(nn.Layer): def __init__(self, weights(0.32, 0.08, 0.02, 0.01, 0.005)): super().__init__() self.weights weights def forward(self, preds, flow_gt, valid_gt): # preds: 从细到粗排列的列表每个元素 [B, 2, H_k, W_k] total 0.0 logs {} for i, pred in enumerate(preds): gt_k, valid_k downsample_flow(flow_gt, valid_gt, pred.shape[-2:]) diff pred - gt_k epe paddle.sqrt(paddle.sum(diff * diff, axis1) 1e-8) # [B, H, W] w valid_k.squeeze(1).astype(epe.dtype) denom paddle.clip(w.sum(), min1.0) layer_loss (charbonnier(epe) * w).sum() / denom total total self.weights[i] * layer_loss logs[fepe_l{i}] layer_loss.detach() return total, logs这段代码有几个地方值得说。paddle.sum(diff * diff, axis1)是在通道维上求和得到每个像素的平方误差再开根号就是逐像素 EPE。分母用clip(min1.0)是防止某个 batch 里全部是无效像素导致除零。返回logs是为了在训练时打印每一层的 EPE这个信息极其有用——如果某一层的 EPE 一直不降基本可以断定那一层的 warping 或者代价体积实现有问题。注意下采样真值后做 mask 的时候不要用乘 mask 再求均值的方式替代逐像素除法否则有效像素占比不同的样本之间权重会失衡大遮挡的样本会被系统性低估。用有效像素数做分母才是对的。4. 训练循环与关键环节落地4.1 前向输出组织与中间层检查训练循环的第一件事是让模型能把每一层的预测都吐出来。推理的时候你只要最终结果但训练时需要全部。常见做法是给模型的forward加一个开关参数训练时返回列表推理时只返回最后一项。这里有一个工程上的建议在真正开始训练之前先跑一个 batch 的 dry run逐层打印输出的形状和数值范围。一个正常初始化的 PWC-Net第一层的输出光流数值应该在 0 附近小幅波动不应该出现几十上百的量级如果出现多半是 warping 的坐标系搞错了或者代价体积的归一化方式不对。这个检查花五分钟能帮你省掉后面几天的困惑。model.train() img1, img2, flow_gt, valid_gt next(iter(loader)) preds model(img1, img2, with_auxTrue) for i, p in enumerate(preds): print(flevel {i}: shape{p.shape}, fabsmax{p.abs().max().item():.3f}, fmean{p.mean().item():.4f})正常情况下你会看到从粗到细absmax 逐渐增大因为分辨率越高位移的像素数越大但整体量级应该和真值的分布接近。如果某一层突然出现三个数量级的跳跃那一层一定有问题。4.2 优化器、学习率与梯度处理优化器配置这块除了前面说的 Adam 加 4e-4 权重衰减还需要加梯度裁剪。光流网络里 warping 和 grid_sample 的操作在边界处容易出现梯度尖峰不加裁剪的话偶尔会碰到梯度爆炸。裁剪方式建议用全局范数裁剪阈值设 1.0比逐元素裁剪更稳妥。lr_scheduler paddle.optimizer.lr.PiecewiseDecay( boundaries[200 * steps_per_epoch, 250 * steps_per_epoch], values[1e-4, 1e-5, 1e-6], verboseFalse, ) optimizer paddle.optimizer.Adam( learning_ratelr_scheduler, parametersmodel.parameters(), weight_decay4e-4, grad_clippaddle.nn.ClipGradByGlobalNorm(clip_norm1.0), beta10.9, beta20.999, )注意PiecewiseDecay的 boundaries 是按 step 计的不是按 epoch所以要乘上每个 epoch 的步数。这个换算我见过不少人写错结果学习率在第 3 个 epoch 就掉到 1e-6训练看起来在跑实际什么都没学到——因为步长太小参数几乎没动。训练循环本体很直白但有几个细节要写对每步先clear_grad()再前向或者前向之后clear_grad()也行但顺序在整个项目里必须统一loss 要backward()之后立刻step()如果用了梯度累积要记得在累积的最后一个 micro-batch 才 step并且 loss 要除以累积步数。for epoch in range(start_epoch, total_epochs): model.train() for step, (img1, img2, flow_gt, valid_gt) in enumerate(loader): preds model(img1, img2, with_auxTrue) loss, logs criterion(preds, flow_gt, valid_gt) loss.backward() optimizer.step() optimizer.clear_grad() lr_scheduler.step() if step % 50 0: print(fepoch {epoch} step {step} loss {loss.item():.4f} flr {optimizer.get_lr():.2e})4.3 混合精度、显存与吞吐的实测权衡输入尺寸 448×384、batch 8 的配置在 11GB 显存上基本是贴着上限跑的。想跑更大的 batch 或者更大的输入混合精度是最直接的手段。Paddle 的自动混合精度用起来不难paddle.amp.auto_cast包住前向GradScaler负责 loss 缩放。scaler paddle.amp.GradScaler(enableTrue, init_loss_scaling2.0 ** 16) for img1, img2, flow_gt, valid_gt in loader: with paddle.amp.auto_cast(levelO1): preds model(img1, img2, with_auxTrue) loss, logs criterion(preds, flow_gt, valid_gt) scaled scaler.scale(loss) scaled.backward() scaler.step(optimizer) scaler.update() optimizer.clear_grad()不过混合精度在光流任务上有两个需要特别留神的地方。第一warping 的坐标计算对精度敏感grid_sample的输入如果在 fp16 下算采样位置会有量化误差导致 warp 出来的图像有细微错位这个误差会通过多层累积。稳妥的做法是把坐标归一化的部分强制放在 fp32 下算只把卷积部分交给 fp16。第二损失函数里的开根号在 fp16 下容易溢出charbonnier的 eps 要取大一点1e-3 而不是 1e-6并且 loss 本身建议在 fp32 下算。如果你不想折腾混合精度还有一个更省心的办法梯度累积。batch 8 拆成两个 batch 4梯度累两步再更新等效 batch 还是 8显存占用减半。代价是训练速度会慢一点因为多了几次前向。但在显存吃紧的时候这是个很实用的折中。关于吞吐我实测的一个参考是单卡、448×384、batch 8、开启混合精度的前提下每秒大概能处理个位数的 iteration 量级具体数值取决于卡型和数据加载是否成为瓶颈。你可以先跑 100 个 step 计时算出每 epoch 耗时再决定总 epoch 数别一开始就定个 300 轮然后发现要跑一周。4.4 断点续训与日志体系训练动辄几十上百个 epoch断点续训不是可选项是必需品。保存的时候模型参数、优化器状态、学习率调度器状态、当前 epoch 和 step 四个都要存少一个都会导致续训后行为不一致。paddle.save({ model: model.state_dict(), optimizer: optimizer.state_dict(), lr_scheduler: lr_scheduler.state_dict(), epoch: epoch, step: step, }, fckpt/pwcnet_epoch{epoch}.pdparams)日志这块除了总 loss我强烈建议记录三样东西每一层的 EPE、验证集的总 EPE、以及学习率的实际值。第一样帮你定位是哪一层出了问题第二样帮你判断过拟合的时机第三样帮你确认调度器有没有按预期工作。这三样都记下来之后很多问题看一眼曲线就能定位不用去翻代码。提示验证集评估时记得model.eval()并且用paddle.no_grad()包住。光流网络的验证不需要梯度开着梯度评估不仅慢还可能因为 batch norm 的统计量更新影响后续训练。5. 常见问题与排查实录5.1 典型问题速查表现象最可能的原因排查手段处理方式第一个 iteration 就 NaN学习率过大 / 真值含异常值 / 除零打印 loss 各分量、统计真值最大幅值降学习率到 1e-5 试跑、清洗异常真值、分母加 cliploss 缓慢下降但 EPE 卡在 20 以上多尺度真值未做幅值缩放单独算每层 EPE看粗层是否异常大修正下采样缩放因子训练集 EPE 很低、验证集很差过拟合合成数据纹理对比两条曲线、看增强强度加强颜色抖动、加权重衰减、提前切阶段训练中途 loss 突然飙升某个样本数据损坏 / 梯度尖峰记录每步最大梯度范数加全局梯度裁剪、过滤异常样本显存 OOMbatch 或输入尺寸过大打印各层中间张量形状降 batch、开混合精度、梯度累积训练速度远低于预期DataLoader 成瓶颈单独测纯前向耗时提高 num_workers、预处理挪进 dataset光流整体方向正确但系统性偏移翻转增强时 u 分量符号漏改关掉翻转重训几轮对比修正增强逻辑边缘区域光流质量明显差warping 用了零填充而非边界填充检查 grid_sample 的 padding_mode改成 border5.2 两个真实的排查过程第一个是“loss 从第一天就 NaN”。这个问题的迷惑性在于它太早了早到你会怀疑是网络结构写错。我的排查顺序是这样的先把学习率降到 1e-6 跑十步还是 NaN说明不是学习率的问题然后把损失里的sqrt去掉直接用平方误差不 NaN 了说明是开根号在某个位置对负数或者零求值最后定位到是sum(diff*diff)之后没加 eps某些像素恰好预测和真值完全相等平方和为 0开根号之后梯度变成 inf。加个 1e-8 就解决了。这个坑很典型问题不在大方向上在一个极小概率的数值边界上。第二个是“训练两百轮EPE 一直在 18 上下晃”。这个数值太稳定了稳定到不像是在学习。我把每一层的 EPE 打出来发现最粗层的 EPE 一直是个常数几乎不随训练变化。进一步检查发现最粗层的特征图尺寸只有 7×6而我在那里用了和细层一样的裁剪边界处理导致有效像素几乎全被 mask 掉了那一层根本没收到有效监督。改成让 mask 在最粗层保留中心区域之后粗层开始正常下降总 EPE 也随之掉到了个位数。这个教训是每层的 EPE 一定要单独看合并后的总 loss 会掩盖单层失效。5.3 几个容易被忽略的细节数据配对的顺序问题。有些数据集的图像列表和光流列表是两个独立文件按行号对应。如果你在读取的时候对其中一个做了排序或者过滤另一个没同步就会出现图文不匹配。这种数据训出来的模型不会有明显异常只是精度上不去。我的做法是在 dataset 初始化时做一次全量校验检查每一对的文件名能否对应上对不上直接报错退出。尺寸不一致的问题。同一数据集里偶尔会混进几张尺寸不同的图像如果你的 crop 逻辑假设了固定尺寸就会在这些样本上出错。稳妥的写法是先读一张图的尺寸再按比例决定 crop 的上限而不是硬编码。验证指标的选取。光流领域常用的指标除了 EPE还有 FI误差超过 3 像素且超过 5% 的像素占比。EPE 对异常值敏感FI 更能反映实际可用性。两个都记一下判断收敛会更准。6. 收敛与调参经验6.1 阶段切换的时机怎么判断课程学习的三个阶段的切换点不能只看 epoch 数要看验证集指标是否进入平台期。我的判断标准是连续若干个 epoch 验证 EPE 的相对变化小于百分之一且训练集 EPE 还在缓慢下降这时候就可以考虑切换到下一阶段了。切太早网络还没把当前数据分布学透下一阶段的复杂数据会把它带偏切太晚就是在过拟合合成数据的纹理收益递减。阶段切换时有一个必须做的动作把学习率重置回该阶段的起始值。从 Chairs 切到 Thing3D 时我一般保持 1e-4 重新开始衰减从合成数据切到真实数据微调时直接降到 1e-5。原因是新数据分布和旧数据分布有差异用旧阶段末端很小的学习率去适应新分布会非常慢而用太大的学习率则会破坏已经学到的特征。还有一点切换阶段之后的前几十个 iterationloss 通常会往上跳一下这是正常的因为新的数据分布更难。如果跳完之后完全不下降甚至持续上升超过一个 epoch那就要检查是不是数据读取出了问题或者学习率设得太高。6.2 指标解读与过拟合的早期识别训练集 EPE 和验证集 EPE 的差值是最直观的信号。正常训练时两条曲线一起下降验证集因为数据更难绝对值会高一些但趋势一致。当你看到训练集还在降、验证集开始走平甚至回升就是过拟合的起点。这时候有三个选择加强数据增强、加大权重衰减、或者提前进入下一阶段。我在实际操作里更倾向于第一个因为光流任务的过拟合往往是网络记住了合成数据的纹理细节而颜色抖动、亮度变化这类增强正好能破坏这种记忆。把颜色抖动的幅度从 ±0.2 提到 ±0.4往往能看到验证集指标回升一点。如果增强加到很大还是过拟合那说明模型容量相对数据量偏大了这时候再考虑权重衰减。还有一个容易被误判的情况验证集指标比训练集还好。这在光流任务里确实会发生原因是验证集如果是真实场景数据它的运动幅度分布可能比合成训练数据更温和反而更容易预测。遇到这种情况不要高兴太早要检查是不是验证集的数据预处理漏了某一步比如归一化的均值方差和训练时不一致。这种 bug 会让验证结果看起来很好但实际推理时完全不可用。最后分享一个我自己一直在用的习惯每隔一段时间保存一个中间检查点并且用同一组固定的测试图像跑一遍可视化的光流图人对图像的判断往往比数字更敏感。有些时候 EPE 只降了零点几但光流图在物体边界上的贴合度明显变好了这说明网络确实在学有用的东西反过来EPE 降了但边界反而更糊那就是在拟合背景噪声该调整策略了。这种“数字加肉眼”的双重校验比只看 loss 曲线靠谱得多。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询