首页 / 资讯中心 / 文章详情

DiMP解析:如何将训练与跟踪深度耦合实现判别式模型预测

DiMP解析:如何将训练与跟踪深度耦合实现判别式模型预测 ★ FEATURED ARTICLE
DiMP 这个名字老做目标跟踪的人应该都不陌生。它的全称是 Discriminative Model Prediction中文可以译成“判别式模型预测”。很多人第一次看到这四个字很奇怪跟踪不是“匹配”或者“回归”吗怎么还扯上“模型预测”了这个疑问正好点到了 DiMP 的核心——它把 Train 和 Tracking 两件事拧在了一起而且是深度耦合的那种。我刚接触它的时候最直观的感受是它的训练过程跟传统跟踪方法很不一样。传统方法通常“离线训练一个网络在线只用前向推理”网络权重在测试时是固定的。DiMP 却把“训练”本身做成了网络的一部分。在线跟踪时每一帧你其实都在跑一个小型优化循环这个循环不是改变主干网络参数而是更新一个“目标模型”。换句话说DiMP 的 Train 阶段要解决两件事一是让目标模型初始化得准二是让目标模型的在线更新“学得会”。而 Tracking 阶段则是把这两件事以极小的代价部署到逐帧推理里。这篇文章就按我自己的理解把 DiMP 的“训练”和“跟踪”拆开讲重点说清楚每条设计背后的“为什么”再附上实操中容易踩的坑。适合那些想在开源工程上改结构、重新训练、或者一直没看懂在线优化那部分代码的人。1. 再想 DiMP它到底把“训练”和“跟踪”掰成了几块1.1 从相关滤波和两阶段跟踪说起要理解 DiMP最好先回到它要解决的问题上。早期的判别相关滤波方法本质上是“在每一帧训练一个线性分类器”目标区域作为正样本周围背景作为负样本用岭回归把滤波器学出来下一帧用这个滤波器去卷积搜索区域响应最大的地方就是目标。这套思路在线性模型里非常优雅速度也快但问题在于表征能力有限一旦目标发生形变、遮挡、旋转线性分类器就很容易被背景“带走”。后来的深度跟踪方法走了另一条路。以孪生网络为代表的思路是第一帧提取模板特征后面每一帧提取搜索区域特征两者做互相关谁跟模板最像谁就是目标。这个方法的缺点也很明显模板是死的。目标一变初始模板就是过期信息你不能指望一个固定特征抓到所有变化。再后来出现的两阶段跟踪方法比如把目标分割和边界框回归分开做虽然精度上去了但“目标模型”这个概念依然很弱。你在线调整的往往是尺度、宽高比这些几何参数而不是对“目标长什么样”的语义判别。DiMP 的思路是在这两者之间找了个中间态既要有深度网络的强表征又要有判别方法“在线更新”的能力。它把目标跟踪重新定义成一个“学习问题”给定第一帧和后续帧的样本我能不能学到一个针对当前目标的判别函数这个函数必须在帧与帧之间持续被修正。1.2 训练和跟踪的边界到底划在哪DiMP 最容易被误解的地方是它把“训练”这个词用在了两个层级上。第一个层级是离线训练。这个阶段要学的东西包括特征提取器、目标模型初始化器、模型预测器的更新规则、IoU 回归分支的参数。说白了它学的是“怎么学会跟踪”的能力。你可以把这部分理解成一个元学习过程虽然 DiMP 在形式上没有刻意用 MAML 那套完整的元学习框架但它确实把“在线学习的损失”纳入了离线训练的优化目标里。这一点非常关键离线阶段不只是让网络预测得准还要让“在线更新过程”在若干步之后预测得准。第二个层级是在线跟踪阶段。这个阶段依然有“训练”但是针对当前视频中的单个目标用当前帧和过去帧的图像块去调整一个很小的目标模型向量。这个模型向量不是主干网络的全部参数通常只是分类分支里的一个特征向量或者一组滤波器参数。因为规模小它才能在测试时以帧率级别完成更新。在这个过程中训练和跟踪并不是割裂的。离线训练把“怎么更新”这件事变成了网络的某种先验在线跟踪则按照这个先验去拟合当前目标。所以 DiMP 的代码里你会发现测试时也会出现 optimizer、loss、backbone 这些词汇它们不是装饰而是真的在跑反向传播。阶段学习对象更新范围数据来源为什么这么设计离线训练特征提取器、初始化器、更新器、IoU分支整个网络参数大规模跟踪数据集让网络具备判别通用目标的能力在线训练目标模型向量分类分支向量极少量参数当前视频帧让目标模型适应当前目标的表观变化1.3 一个容易忽略的关键选择模型预测器与模型更新器分开大多数人对 DiMP 的印象是“会在线更新的跟踪器”但真正让它区别于传统相关滤波器的是把“更新策略”也端到端学出来的做法。传统相关滤波的更新公式是人设计的比如线性插值、固定学习率、峰值旁瓣比判断遮挡等。DiMP 的在线更新不是手写规则而是一个被离线训练过的优化过程。这个优化过程可以粗略理解成一个深度网络版的“最速下降法”。它有一个单独的模块通常叫 target model predictor。这个模块在离线训练时学会了一种通用的“从少量样本中构建判别模型”的方式。在线跟踪时它接收当前特征和标签输出一个新的模型向量这个向量能对下一帧的搜索区域给出置信度分数。整个过程不需要人在线设计更新策略只需要控制迭代步数和步长。如果你看过 DiMP 的源码会发现分类分支里有一个Optimizer类。那个类做的事不是传统意义上的参数更新而是在模型空间里做“内部循环”。这种内部循环让你可以放心地说DiMP 的在线阶段确实是在“训练”一个小模型只不过这个小模型是极度轻量的。2. 训练阶段拆解把跟踪问题变成一个适合作梯度下降的问题2.1 训练样本怎么构造直接影响能不能收敛先说数据。深度跟踪器的训练跟图像分类不太一样它需要的是“成对”的数据一个是模板帧一个是搜索帧。模板帧用来提供初始目标信息搜索帧是后续某帧里可能包含目标的更大范围图像。离线训练时一般从同一视频里间隔一定帧数取两帧一个当模板一个当搜索区域。间隔太近模型学到的是“目标几乎没动”这种错觉间隔太远目标可能已经严重变形模型又觉得太难。实际操作中很多人会采用混合间隔近到 1 帧、远到几十帧甚至上百帧都取一些模拟不同变化剧烈程度。两个图像块的大小也是需要刻意控制的。模板图像块通常等于目标框扩大若干倍后的区域搜索图像块则更大一般是对角线长度扩到模板的 3 到 5 倍。这样设计的原因是搜索区域太大背景噪声太多分类器容易混淆搜索区域太小目标快速运动时容易跟丢。DiMP 系列的默认配置里模板和搜索区域的比例关系已经调过一次但换数据集时你还是应该自己验一遍。数据增强也不能少。随机水平翻转、颜色扰动、尺度抖动、模糊都是标配。特别值得注意的是尺度抖动。因为跟踪中目标框可能忽大忽小训练时如果不给目标框加一点随机扰动模型会对“精确框”过于敏感测试时只要初始框稍微偏一点跟踪就崩了。如果你是自建数据集训练千万别把所有视频的时长都截成一样。长视频和短视频混合训练能帮助模型适应“跟踪一段时间后目标逐渐变化”的真实情况。很多模型在短片段上表现很好一到长视频就漂往往就是因为训练数据里都是刚出现目标的前几十帧。2.2 网络各分支的职责与损失搭配DiMP 的主干网络负责提特征这个没什么好说的关键在于它把提取出来的特征送给了两个完全不同的头。第一个头是分类分支也叫目标模型预测器。它输出一个空间置信度图表示目标在搜索区域内每个位置出现的概率。这个分支的训练不是简单地用交叉熵而是采用一种类似 Focal Loss 的判别损失。因为搜索区域里绝大部分是背景正负样本严重不平衡普通交叉熵会让模型倾向于“输出全背景”。Focal Loss 把难分样本的梯度放大把易分背景的梯度压下去正好解决这个问题。第二个头是 IoU 回归分支。它不再输出概率图而是对当前候选框进行精细回归。这个分支在训练时输入是目标候选区域的特征和目标模型特征输出是预测框和真实框之间的 IoU。训练标签很好构造在真实框附近随机扰动出大量候选框然后计算每个候选框和真实框的 IoU作为回归目标。这个分支的损失通常是简单的平滑 L1但它解决了跟踪中一个核心问题——分类输出只能给中心点不能给准确的边界框。两个分支的损失不是简单地加在一起。很多工程里会把分类损失和 IoU 损失分别设权重形成总损失。改动权重时要注意分类损失主导“目标锁定”能力IoU 损失主导“目标框精度”。如果发现跟踪时目标框总是偏大或偏小多半是 IoU 分支训练不足如果目标总在框里乱飘那可能是分类分支的置信度不够尖锐。2.3 离线训练里一个容易被忽略的细节更新过程的元损失DiMP 最核心的训练技巧在于它不只用当前帧的预测结果来算损失还会模拟在线更新的过程把“更新后的模型在后续帧上的表现”也计入损失。你可以这样理解假设我有第 t 帧的特征初始模型参数通过初始化器得到一个初值然后用这个初值去预测第 t 帧的置信度得到第一个损失接着执行若干步模型更新再拿更新后的模型去预测第 t1 帧或者同一帧的另一增强版本得到第二个损失。两个损失都回传模型更新器才能学会“更新之后效果更好”这件事。如果你实现 DiMP 时没有把这一步加上只是单纯训练一个网络来预测目标置信度那在线更新的时候必然翻车。因为网络从来没有见过“更新”这件事它学到的只是静态匹配而不是动态拟合。训练时还有一个非常隐蔽的操作模型更新时要用 stop gradient 断开部分路径。具体来说特征提取器的梯度通常不回传到更新步骤里或者只在特定条件下回传。否则在线更新这一步会让整个网络的训练变得极其不稳定。你可能发现损失下降很快但验证集精度很乱多半就是梯度路径没有处理好。我建议新手先不要动主干网络的结构先用默认配置把 DiMP 复现出来。主干网络内部细节、层与层之间的聚合方式都经过了很多轮的消融实验轻易改动会导致分类分支和 IoU 分支的输入分布不一致结果就是训练时损失正常、在线跟踪稀烂。先跑通再改。2.4 一组参考性比较强的训练超参下面这张表是我在多个工程里调过之后觉得比较通用的起步配置。你可以把它作为基线再结合自己的数据集微调。配置项建议值我的解释模板尺寸112 x 112 到 176 x 176太小丢细节太大占显存搜索区域尺寸模板的 4 倍左右平衡搜索范围和计算量训练帧间隔随机 1 到 80 帧覆盖短时和长时变化分类损失Focal Lossgamma 取 1 到 3压制背景不平衡IoU 回归平滑 L1对离群候选框更鲁棒在线更新步数训练时模拟 3 到 5 步太少学不到更新太多训练不稳主干网络学习率1e-5 到 1e-4主干是预训练好的不能学太快优化器Adam跟踪器训练里最稳基本够用这里的每一项都不是拍脑袋定的。比如在线更新步数训练时模拟 3 到 5 步在线测试时通常就要用 10 到 20 步甚至更多因为测试时你有更多计算预算。如果你训练时模拟的步数太少模型更新器学到的只是在几步内的改进跑在线部署时多更新几次行为就会变得不可控。3. 跟踪阶段拆解把“训练”搬进测试过程的艺术3.1 第一帧初始化不是存个模板图就完事了很多深度跟踪器的第一帧处理是“存一张模板图像”后面的帧拿它来比较。DiMP 不是这样。它在第一帧时会做一次完整的初始化计算把第一帧的目标区域裁剪出来送进主干网络提取特征然后用初始化器生成一个初始目标模型向量。这个向量不是图像也不是普通特征图而是一个可以被分类分支直接使用的滤波器权重。你可以把它想象成一张“针对这个目标定制化”的判别模板。这一步计算量不大也就是一次前向加一次轻量优化。但它的意义在于从第一帧开始模型就知道“要重点响应这个目标而不是任何跟目标长得像的东西”。在线更新也从第一帧就开始而不是等目标变了才启动。如果你改代码时发现第一帧耗时会稍微偏高那是正常的因为它做了不止一次前向还有初始化优化器的迭代。3.2 在线更新循环每一帧都跑一个极小的“训练任务”从第二帧开始DiMP 就进入了逐帧跟踪循环。整个循环可以压缩成下面这段伪代码for t in range(1, len(sequence)): # 1. 以上一帧目标中心为中心裁出搜索区域 search_crop crop(frame_t, prev_center, search_size) # 2. 提取搜索区域特征 feat_search backbone(search_crop) # 3. 用当前目标模型预测置信度图 score_map predictor(feat_search, target_model) # 4. 从置信度图找到峰值位置 dy, dx argmax(score_map) center_t prev_center (dy, dx) # 5. 用 IoU 分支精修目标框 bbox_raw make_bbox(center_t, prev_size) bbox_t iou_branch(features, target_model, bbox_raw) # 6. 是否需要更新目标模型 if should_update(score_map, t): target_model optimizer.step( features[feat_search], labelsmake_labels(bbox_t), target_modeltarget_model )第一步到第五步几乎所有跟踪器都是类似的。区别在第六步。传统方法可能会用“当前帧特征和模板做移动平均”来更新模板DiMP 则是把当前帧当作一个新的训练样本让目标模型向量往“当前外观”方向再挪一步。这个步骤本质上是在线优化给定当前特征、预测的边界框和构造的标签在模型空间里做一步梯度下降。这里有三个值得注意的点。第一在线更新不是每帧都做。有些实现里只有置信度足够高时才更新置信度低的帧可能是遮挡、模糊或目标暂时离开画面这种数据更新进去只会污染模型。更稳妥的做法是设置一个低置信度阈值低到一定程度就跳过更新同时把当前跟踪结果标记为低置信度后续再恢复。第二在线更新的标签不是从真值来的是当前预测框“假设它就是目标”构造出来的。这意味着自监督闭环预测准标签可靠模型越跟越好预测偏了标签也偏模型可能越跟越偏。这就是为什么在线更新步长不能太大也不能把所有帧都用于更新。很多崩溃案例不是模型能力不够而是脏标签把模型带偏了。第三在线更新时主干网络不参与反向传播。每个循环回传的梯度只更新目标模型向量不回传给特征提取器。这样可以大幅减少计算量也可以避免在线阶段因为少量样本微调主干网络而破坏掉通用特征。调试的时候如果你发现显存爆浆或者在线推理极慢先看看是不是不小心把主干的梯度打开了。3.3 尺度估计与边界修正IoU 分支的工作方式分类分支输出置信度图能告诉我们“目标中心在哪里”但百分比图本身不包含精确的宽高信息。如果只用中心位置匹配上一帧尺寸遇到目标走近变大或者转身变窄框就会不准确。DiMP 用 IoU 分支来解决这个问题。IoU 分支的输入是当前目标候选区域的特征通常从上一步预测框附近裁剪出来。这个分支会对候选框做微调输出一组边界框回归偏移或者直接对多个候选框打分。常见的做法是生成若干个不同尺度和宽高比的候选框让 IoU 分支输出每个框和“真实目标”之间的重叠度预测然后选择得分最高的那个框。这个过程相当于在局部做了个小范围的搜索不过是在特征层面做的比传统多尺度金字塔检测要高效。这里有一个经验IoU 分支不希望输入框和真实目标相差太远。如果目标突然快速移动上一步的中心预测可能偏得离谱这时候直接用 IoU 分支去修正效果不会很好。比较好的处理顺序是先用分类分支的置信度图锁定大概位置再用 IoU 分支做精细回归。如果怀疑目标尺度发生剧烈变化可以在 IoU 分支候选框里加入多个尺度项。3.4 更新频率和鲁棒性宁可少跟不要乱跟在线更新的频率是 DiMP 实际工程里最值得调的参数之一。有段时间我试过每帧都更新结果目标稍微遮挡一下模型就开始往遮挡物上漂。后来改成“只有得分高且跟踪框和预测框的 IoU 大时才更新”鲁棒性立刻上来了。这个现象背后的原因也很清楚DiMP 的在线更新是自监督信号驱动的低置信度样本的标签本身就不靠谱强行更新会让模型学到错误信息。更新的步长同样需要控制。步长太大模型容易遗忘过去的信息步长太小模型跟不上目标的变化。在实际实现里这个“步长”往往由优化器内部的学习率决定。有的实现会在每帧做多步更新但每步步长很小这样既有更新效果又不会突变。测试时建议先记录一下不同帧数下的目标模型向量变化量如果变化太大说明步长过激需要降下来。4. 实现与复现中的经验和坑4.1 显存与训练速度的平衡DiMP 系列的训练是显存杀手这个必须提前有心理准备。主干网络要处理两个输入模板和搜索区域分类分支要在线模拟多步优化IoU 分支还要对候选框做处理。这些东西加在一起单卡 12GB 根本不够跑较大的 batch。我的建议是先用 batch size 2 到 4 把流程跑通确认数据流没问题再考虑上大规模并行。显存不够时优先把“在线更新模拟的步数”降下来而不是把输入分辨率降太多。输入分辨率直接决定目标细节多少降了会严重影响跟踪精度。步数下降只会影响模型更新器的训练强度可以通过延长训练轮次来弥补。梯度累积也是一个可用手段。每一步只做前向和反传更新多次再执行一次优化器 step能有效降低显存峰值。但要注意梯度累积会影响 BatchNorm 的统计量建议配合同步 BatchNorm 使用或者干脆不要用大的 batch只用累积来避开显存峰值。4.2 在线更新效果的调试方法训练完成后评估阶段最容易出现的问题有两个一是在线更新完全不起作用二是在线更新把目标模型带偏。第一种问题表现为去掉在线更新和不加在线更新精度差不多甚至加了还更差。这时候你先检查训练时是否真的模拟了在线更新过程。有些工程在训练阶段为了加速直接跳过了模型更新器只在测试阶段用这是最典型的错误。没有经过元学习的更新器本质上就是一个随机初始化形状的模块用起来自然没有效果。第二种问题表现为跟踪跑着跑着突然偏到背景上然后越偏越远。先检查置信度阈值设置得是否太低。如果置信度阈值几乎不产生更新那问题可能出在标签构造上。标签是围绕预测框生成的如果预测框已经偏了生成的标签本身就是错的更新就是往错的方向走。解决办法是置信度低时不仅不更新还要把当前帧的目标模型“冻结”住等目标重新出现后再恢复更新。4.3 评估指标与可视化确认光看平均成功率曲线是不够的。DiMP 这种跟踪器对初始帧的敏感性很强你得看不同初始情况下的表现差异。建议跑评估时记录每个视频序列在“前 10 帧就发生漂移”和“后 200 帧才发生漂移”两类情况。前者多半是初始化或者第一帧特征提取有问题后者多半是在线更新策略有问题。可视化时不要只画跟踪框要同时把置信度图叠加到图像上。我曾见过一个情况框看起来没偏但置信度图的光斑已经移到了目标旁边说明分类分支已经在慢慢丢失目标。这种隐患只看最终框是很晚才能发现的。把置信度图做成热力图输出能帮助你很早就感知到模型在往错误方向漂移。4.4 加速训练的两个可直接上手的技巧第一个是混合精度训练。DiMP 的主干网络和分类分支都有大量卷积混合精度可以把显存占用降低三分之一以上。有人担心在线更新部分用混合精度会不稳定我的经验是分类分支的更新器保持 FP32其他前向部分用 FP16这样效果比较好。第二个是把数据加载做成异步。跟踪训练集的视频很长单帧裁剪要做大量解码工作如果数据加载是同步的GPU 会有一大半时间在等待。异步加载、预取、缓存最近用过的图像帧能让训练吞吐量提升一大截。老代码里经常有collate_fn里做大量图像处理的写法性能很差建议从数据管线层面重写。5. 训练与跟踪联调的常见问题速查5.1 典型报错与处理方案以下是我在复现和修改 DiMP 过程中遇到过的比较有代表性的问题整理成了一张速查表。现象可能原因建议处理训练损失下降但测试跟踪很快漂移在线更新模拟缺失或权重过小检查训练损失中是否包含更新后的预测损失在线阶段显存暴涨主干网络梯度未断开检查在线更新时是否误开了 backbone 的 gradIoU 输出框抖动剧烈IoU 分支训练不足增加候选框扰动范围加大 IoU 损失权重目标响应分数整体偏低分类分支正负样本不平衡调整 Focal Loss 的 gamma检查搜索区域裁剪是否过大模型更新后分数反而下降在线学习率过大调小优化器内部学习率减少每帧更新步数遮挡后无法恢复遮挡帧参与更新增加低置信度跳过更新逻辑多卡训练时精度掉得多BatchNorm 统计量不一致使用同步 BatchNorm减少梯度累积步数推理速度远低于预期在线更新步数过多或候选框太多观察置信度变化曲线找到收益临界点5.2 现象反常时先别调参先查数据流有一段时间我在自己的数据上训练 DiMP损失降得很漂亮成功率也很高但一到长视频就拉胯。我反复调模型更新步数、置信度阈值都没用。最后发现问题是训练数据里视频片段的长度分布太集中模型见到的都是开头几十帧几乎没见过“目标已经跟踪一段时间后外观大变”的情况。这个教训让我形成了习惯出现问题先看数据流再看损失最后才调结构。具体来说我会做三件事。第一把训练时构造的“模板-搜索”对直接可视化确认模板里目标占比合理搜索区域里目标没有出现在边缘。第二把在线更新时用到的标签也可视化确认标签中心跟预测目标中心没有系统性偏移。第三打印在线更新前后目标模型向量和特征之间的余弦相似度看看每次更新到底改了什么。这三件事做完很多“玄学”问题都会变成明牌。5.3 若干值得长期保留的调试习惯如果你准备在 DiMP 这个框架上做二次开发我给你几个我自己长期保留的习惯。第一每次改完网络结构先用一个只有十来个视频的小验证集跑一遍全程。这个小验证集要故意包含快速运动、遮挡、相似物体干扰这三种情况。十来个视频最多跑几分钟能立刻暴露出 80% 的关联问题。第二给在线更新器的超参数单独建一个日志分组。学习率、更新步数、置信度阈值这些每次测试都记录清楚。否则调了几十组之后你根本不知道哪个组合对应哪个精度。这看起来是很小的事但做过调参工程的人都知道真正消耗时间的不是调参而是不知道自己在调什么。第三保留不同训练 checkpoint 的在线跟踪可视化结果。我习惯每隔一定训练轮次就保存一组当前模型在某个固定测试序列上的输出。这样你能看到随着训练推进在线更新行为有没有逐渐变稳。很多问题不需要等到完整评估就能被发现。我在实际使用 DiMP 的过程中最大的体会是千万不要把训练和跟踪当成两个独立阶段去理解。你对 Track 阶段怎么用模型直接决定了 Train 阶段应该怎么设计数据、怎么构造损失。反过来Train 阶段有没有把“在线更新”这个动作真正优化到也直接决定 Track 阶段跑得稳不稳。所以“再想 DiMP 的 TrainTracking”本质上就是想把这两件事放到同一个坐标系里去审视。每当我觉得某个环节玄而又玄的时候我都会回到那个最基本的问题网络在跟踪时到底是在学习什么我的训练过程有没有让这种学习变得可能。想清楚这一点大部分问题其实都有解。
阅读完成 · 觉得有帮助?
咨询建站