第一次打开步态数据集的压缩包很多人会愣住没有图像分类那种一张图一个类别的清爽结构取而代之的是一堆以三位数字命名的文件夹点进去是几百张灰度剪影人形只有黑白两色脚底下还带着毛刺。你本能地想点开一张看细节结果发现单看一帧根本分不出是谁——这恰恰是步态数据集最核心的特征它承载的信息不在单帧画面里而在时间维度上在一个完整的行走周期里。步态数据集是步态识别这项任务的燃料。它把一个人怎么走路这种原本只能靠肉眼意会的东西变成了有编号、有视角标签、有序列边界、可以喂进神经网络的矩阵集合。无论你是想复现一篇论文的 rank-1 指标还是准备自己搭摄像头采一套数据做行人检索第一步永远是搞清楚手里的数据集是怎么组织的、哪些字段是标签、哪些文件是干扰项。这篇就把我这些年翻过的坑、对过的目录结构、以及那些官方 README 里通常不会写的细节一次讲清楚。1. 步态数据集到底在存什么一段走路视频被拆成了几层1.1 步态作为一种生物特征的独特之处人脸、指纹、虹膜这些都是静态生物特征采集瞬间就能定身份步态不一样它是行为特征必须观察一段时间才能成立。这带来两个直接后果第一数据集的存储单元不是一张图而是一段序列第二同一个人的同一个序列从不同帧切进去看信息量完全不同——你从侧后方看一个人抬腿的瞬间和看双脚并拢的瞬间得到的判别力差得远。更要紧的是步态识别天然是远距离、非配合场景下的方案。人脸要正脸、要清晰度指纹要接触步态只要你在镜头里走过就行几十米外、背对着镜头、光线一般照样能采。这也是为什么步态数据集的采集场景往往长得像监控视角而不是像人脸库那样规整的正脸照。从信号层面讲一段步态数据里包含的信息大致三层体型轮廓身高、肩宽、腿长比例属于静态属性、运动模式步频、步幅、摆臂幅度、身体起伏属于动态属性、外观干扰衣服、包、鞋、光照属于噪声。所有步态数据集的设计动机本质上都是想把这三层拆开——把前两层留下来当特征把第三层单独标注成条件变量让你能测试模型在换衣服、背包这些干扰下的鲁棒性。1.2 一条样本的完整生命周期从原始视频到对齐剪影序列绝大多数公开步态数据集尤其是剪影类的从原始视频到最终交付给你的文件中间经过了四道处理理解这四道处理比记住数据集有多少人重要得多。第一道是前景分割。给视频逐帧做人体与背景的分离输出前景掩膜。实验室内通常用背景建模比如高斯混合模型对固定背景做建模再差分野外场景就得借助人体解析或分割网络了。这一步的质量直接决定后面所有指标的上限——掩膜里带一条影子或者腿之间连成一坨模型学到的就是错的步态周期。第二道是人体框裁剪与归一化。把前景的上下左右边界裁出来然后按统一高度缩放再把人体水平居中。这一步的目的是消除摄像机远近和人在画面中的位置带来的尺度差异。业内常见的做法是把归一化后的剪影缩到比较小的尺寸比如实验室内小规模数据集常裁到 64×44 这种高度高×宽而人多、分辨率高的场景会保留到 128×88 甚至更大。第三道是时序切分。一条完整的行走视频可能几十秒一般会按固定帧数切成片段常见的是每条训练片段采样 30 帧。采样方式有两种主流变体一种是等间隔采样业内常说的 L 型采样按固定间隔从整段序列里均匀抽帧保留完整周期另一种是随机连续采样M 型采样从序列里随机取一段连续的 30 帧用来模拟真实场景里只拍到半个人走过去的情况。第四道是标注规范化。把受试者编号、视角编号、行走条件、序列号这些元信息编码到文件名或单独的标签文件里。这是最容易在读取环节翻车的地方下一节专门讲。1.3 剪影、骨架、点云三种表示方式的取舍拿到步态数据集之前先想清楚你要用的是哪一种表示因为不同表示对应的数据集完全不同工具链也不通用。表示方式数据形态优点代价典型代表剪影序列灰度二值图0/255数据量小、处理快、方法成熟、复现容易丢失内部纹理换衣干扰大分割质量敏感CASIA-B、OU-MVLP、GREW骨架序列每帧若干关键点坐标尺寸极小、对衣服和光照不敏感、可解释性强依赖姿态估计精度遮挡下关节点漂移严重各类基于 OpenPose/HRNet 的衍生版本3D 点云 / 参数化人体每帧点云或 SMPL 参数天然带深度信息视角不变性好采集成本高、数据量大、公开集少Gait3D 等剪影是绝对的主流。原因很现实它把一个三维问题压成了二维二值图存储和计算都便宜而且二十年来积累的方法和基线足够多你随便找个开源工具箱都能跑起来。骨架路线的优势在于抗外观干扰缺点也明显——姿态估计本身会错而步态识别要的恰恰是毫米级的肢体比例差异关键点抖动几个像素就可能把两个人的距离拉近。3D 路线理论上最优但公开可用的数据规模和场景多样性目前还撑不起工业化部署多用于研究。我的建议是如果你是第一次做步态老老实实从剪影开始。把剪影这条链路打通之后再考虑引入骨架做多模态融合收益会比一开始就上 3D 稳健得多。2. 打开压缩包之前目录结构与标注格式的读法2.1 三种常见的目录组织范式步态数据集的目录结构看起来五花八门归纳起来就三种。第一种是扁平式所有剪影图堆在一个目录下靠文件名编码全部信息。小规模实验室数据集多用这种好处是加载快、不用递归遍历坏处是文件数一多某些文件系统在单个目录下放几十万个文件时会明显掉速。第二种是两级或多级嵌套先按受试者分文件夹再按视角或条件分文件夹最后才是帧图。这种结构人眼看着舒服但读的时候必须写递归遍历而且要小心不同层级的命名规则不一致。第三种是视频直存式数据集不给你剪影只给原始视频片段预处理全靠自己。野外大规模数据集常见这种形式因为场景太复杂官方也没法保证统一的分割质量。用这种数据集的时候你的预处理管线质量会直接变成指标的一部分和别人比数字时要格外小心。不管你拿到的是哪一种先别急着写 DataLoader先花十分钟把目录树打出来看一眼。find . -maxdepth 3 -type d | head -50或者tree -L 3 -d这一条命令能帮你省掉后面几个小时的调试。2.2 文件名本身就是标签解析规则实战以最经典的 CASIA-B 为例它的剪影文件名形如001-nm-01-000.png四个字段依次是受试者编号、行走条件、该条件下的序列序号、帧序号。行走条件用两个字母表示nm是正常行走bg是背包cl是换外套。这种设计非常紧凑——一个文件名就把身份、条件、时序三件事全说清楚了。我踩过的第一个坑就在这里正则写太松。一开始我图省事用split(-)拆字段跑得好好的直到换到另一个数据集人家的文件名里受试者编号是五位、帧号不足位不补零直接全乱套。后来我固定了写法import os, re from collections import defaultdict # 以 CASIA-B 命名风格为例sss-cc-nn-fff.png FILE_PAT re.compile(r^(\d{3})-([a-z]{2})-(\d{2})-(\d{3})\.png$) def scan(root): records [] for dirpath, _, filenames in os.walk(root): for name in filenames: m FILE_PAT.match(name) if not m: continue # 不匹配的一律跳过并单独记录别静默吞掉 subject, cond, seq, frame m.groups() records.append({ path: os.path.join(dirpath, name), subject: subject, condition: cond, sequence: f{subject}-{cond}-{seq}, frame: int(frame), }) return records第二个坑是编码和隐藏文件。有些数据集来自 Windows 环境目录里会混进Thumbs.db、.DS_Store还有中文路径的情况。正则匹配不上就跳过是安全的但你得把跳过的文件名打印出来看一眼否则一旦命名规则和你预期差一点点你会以为数据集是空的。2.3 先做一次数据体检再动手训练写完解析我强烈建议先跑一遍统计确认三件事每个受试者有多少条序列、每条序列有多少帧、视角分布是否均匀。这三件事任何一项异常都意味着要么你的解析错了要么数据本身有缺失。from collections import Counter, defaultdict def audit(records): per_seq defaultdict(list) for r in records: per_seq[r[sequence]].append(r[frame]) lens [len(v) for v in per_seq.values()] print(序列总数:, len(per_seq)) print(序列长度 min/median/max:, min(lens), sorted(lens)[len(lens)//2], max(lens)) print(受试者数:, len({r[subject] for r in records})) print(条件分布:, Counter(r[condition] for r in records)) # 抽查帧号是否连续断帧往往意味着预处理脚本曾经崩过 bad 0 for seq, frames in per_seq.items(): frames sorted(frames) if frames ! list(range(frames[0], frames[0] len(frames))): bad 1 print(帧号不连续的序列数:, bad)这段代码看着朴素但它抓到的问题都很致命。我见过序列长度中位数是 60、但最短只有 3 帧的情况——那几条短序列在训练时会因为凑不够采样帧数被反复复制等于人为放大了它们对梯度的贡献。也见过帧号断档的追查下去发现是当初解压的时候中断过一次。注意不要用文件名排序来代替帧号排序。0009.png和0010.png字符串排序没问题但如果帧号不补零10.png就会排在2.png前面时序彻底乱掉。要么解析出整数帧号再排要么一开始就统一补零重命名。3. 公开数据集怎么选规模、视角与条件变量的对照3.1 小规模实验室数据集适合跑通链路不适合下结论这类数据集的共同特点是人少、机位固定、背景干净、剪影质量高。它们的作用是让你在几十分钟内把整条训练链路跑通验证代码没写错而不是用来证明你的模型有多强。在 100 多人的规模上rank-1 的置信区间宽得吓人——多对几个样本就能涨两个点这种数字拿去做横向对比意义有限。真正值得利用的是它们的条件变量设计。有的数据集会在同一个人身上采集正常行走、背包、换外套三种条件这就给了你一个干净的对照组模型在正常条件下的表现和背包条件下差多少这个差值比绝对精度更有信息量说明模型到底是在看步态还是在看轮廓外形。使用这类数据集时我建议把它当成单元测试先在这个上面把数据加载、对齐、采样、损失函数、评测脚本全部打通指标能对上论文的公开数字再迁移到大集上。反过来做在大集上调试 bug 会让你怀疑人生。3.2 大规模多视角数据集跨视角能力的试金石多视角数据集是步态研究里最有价值的一类因为它同时给了你同一时刻、不同角度的观测。这在真实场景里几乎不可能采到——你不可能在商场里围一圈摄像头拍同一个人。有了它你才能做跨视角评测用 90 度视角的样本做底库用 0 度视角的样本做查询看模型能不能认出来。这背后的逻辑其实很朴素。步态特征里有一部分是视角相关的比如侧面看能看到完整的步幅正面看就只能看到肩宽和身体左右摆动。模型如果只学会了侧面剪影长什么样一换视角就废了。多视角数据集强迫模型去学那些视角不变的成分——身高的绝对比例、步频这类跟角度关系不大的量。规模上目前公开的大规模多视角数据集受试者在万人量级视角数从早期的 11 个扩展到 14 个甚至更多。人数一多受试者之间的体型分布就更接近真实人口模型学到的特征才有泛化性。代价是训练成本——一万多人的数据集光是把剪影读进内存做一次完整 epoch就够你喝一壶的。3.3 换衣、背包与野外场景数据集难度阶梯的顶端换衣服是步态识别最硬的一道坎。剪影类方法本质上是在比轮廓形状一件长款羽绒服能把体型信息遮掉大半一条阔腿裤能让腿型完全变样。所以专门针对换衣设计的数据集通常会让同一批受试者穿多种差异很大的服装反复走逼着模型去找那些衣服遮不住的线索——比如头肩比、走路时的重心起伏节奏。野外场景数据集是另一个维度的挑战。它不做背景分离的善意处理直接给你真实监控环境下的视频光照剧烈变化、有遮挡、有路人干扰、摄像机视角随意。这类数据集的规模往往很大几百小时的视频、上万人的身份但剪影质量参差不齐。用它的时候你的预处理管线会变成影响指标的第一变量和别人比结果时必须说清楚你用的是官方提供的剪影还是自己生成的。3.4 一张对照表帮你快速定位数据集受试者规模视角主要条件变量适合验证什么CASIA-B百人级11 视角正常/背包/换衣链路跑通、跨视角基线OU-MVLP万人级14 视角视角大规模跨视角泛化USF HumanID百人级多机位视角/鞋/路面/包/时间早期方法对比、条件鲁棒性TUM-GAID数百人单视角为主正常/背包/穿罩衣/换鞋两次采集间隔数月时间跨度鲁棒性CCPG百人级双视角四种服装换衣场景专项SUSTech1K千人级11 视角视角/光照/携带/服装多因素复合干扰GREW万人级野外无固定视角自然场景真实监控落地Gait3D千人级数十机位三维点云/参数化人体3D 表示研究提示上表中的规模数字在不同论文里写法可能略有差异有的按受试者算有的按序列数算有的按视频小时数算引用前一定回到官方页面核对原始表述别直接抄二手论文里的表格。选型的思路可以简化为一条先用百人级数据集验证代码正确性再用万人级数据集验证泛化性最后用野外数据集验证工程可行性。三个环节解决的问题完全不同跳过任何一个都会在后面付出代价。4. 评价协议指标数字好看不代表模型真的好4.1 闭集、开集与跨视角协议的区别很多人第一次看论文表格会困惑同样一个模型为什么在两篇论文里的 rank-1 差了七八个点十有八九是评测协议不一样。闭集评测假设查询样本的身份一定在底库里。这是最简单也最乐观的设定只需要算相似度排序取 top-1 命中的比例。绝大多数实验室数据集论文用的是这个协议。开集评测允许查询样本的身份不在底库里模型必须同时判断是不是库里的某个人和是谁。它更接近真实部署因为监控场景里绝大多数出现的人都不在目标名单上。对应的指标也变成了一对——既要看正确识别率也要看误报率。跨视角评测是步态特有的协议。底库和查询来自不同视角且计算 rank-1 时要排除同视角的比对。为什么要排除因为同视角的人和自己比几乎必然排第一不排除的话指标会被虚高到接近 100%这个数字毫无意义。这一条是新手最容易忽略的细节我第一次复现论文时就因为忘了排除同视角指标冲到 99% 还沾沾自喜后来才发现是协议用错了。4.2 Rank-1、mAP 与 TARFAR 各自说明什么Rank-1是最常用的指标含义是相似度最高的那一个候选就是正确身份的样本占比。它直观但对底库里的干扰项质量很敏感——底库里如果有很多和你体型相近的人rank-1 会明显下降。所以它更适合横向比较同一数据集上的不同方法不适合直接当作部署能力的估计。mAP平均精度均值考察的是整个排序列表的质量而不是只看第一名。当底库很大、存在多个正确匹配时mAP 比 rank-1 更能反映模型的整体排序能力。大规模野外数据集普遍会同时报 rank-1 和 mAP。TARFAR是开集场景下的标准指标读作当误报率固定在某个阈值时正确识别率能达到多少。它必须成对出现才有意义——单独说识别率 95%而不说误报率等于没说。常见的写法是 TARFAR1e-3 或者 TARFAR1e-4。指标适用场景主要局限Rank-1闭集、底库较小只看第一名忽略排序质量mAP闭集、底库大、多匹配对底库构成敏感跨数据集不可比TARFAR开集、真实部署评估必须成对报告阈值选择影响大4.3 几个常见的协议误用与复现偏差第一类误用是训练集与测试集身份重叠。步态是身份识别任务测试集里的人绝对不能出现在训练集里否则模型是背答案而不是学步态。有些数据集给的划分文件里训练和测试受试者是分开的但如果你自己按序列随机切分很容易把同一个人的不同序列分到两边指标会虚高得离谱。我见过一个复现项目涨了十几个点最后发现就是划分错了。第二类误用是测试集调参。拿测试集的 rank-1 去选学习率、选采样帧数、选模型结构本质上是在过拟合测试集。正确做法是从训练集里切出验证集所有超参在验证集上定测试集只跑一次。第三类误用是底库构成不一致。有的协议底库每人只有一条序列有的每人有多条平均方式也不一样先算每条查询的 rank-1 再平均还是把所有查询混在一起算。这两种算法在人数不均衡时会给出不同结果。跨论文比较时一定要确认底库构造方式一致。5. 自己采一套步态数据从场地布置到标注质控5.1 拍摄场地、机位与光照自己采数据的第一步不是架相机而是想清楚你要验证什么问题。如果只是验证模型在固定视角下的识别能力一台正面或侧面的相机就够了如果要研究跨视角就得让受试者沿着不同方向走过同一片区域或者布置多机位。机位高度是个容易被忽略的细节。太高的俯角会把人体压扁身高信息损失大太低又容易被行人的脚遮挡。经验上相机高度在一米五到两米之间比较舒服能保持人体轮廓的完整比例。相机到行走路径的距离要留足让受试者在画面里的高度稳定在一个区间内避免一会儿占满画面、一会儿只有几十像素高。光照方面最怕的是侧逆光造成的地面长影。影子会和人体前景连在一起分割出来的人形就带了一条尾巴用这种数据训练出来的模型对轮廓边界的敏感度会跑偏。如果只能在户外采尽量选阴天或者让受试者背向光源行走。室内的话注意别让窗户直射造成的强对比出现在背景里。5.2 采集流程与元数据记录采集流程要标准化否则后面做条件分析会做不下去。我的做法是准备一张采集清单表格每录一段就打勾记录受试者编号、性别、身高、年龄段、当次穿着的服装类型上衣长度、裤子版型、是否携带物品背包/手提袋/无、行走方向、机位编号、采集日期。这些元数据看着琐碎但当你后面要分析模型在长款外套下的性能退化时没有它们就两眼一抹黑。行走方向上最少要让每个人沿同一路径往返走三到五次。往返是有必要的——不同方向对应不同的起始相位可以避免模型学到总是从左脚开始这种伪特征。如果条件允许再补上几组不同步速的行走正常、偏快、偏慢步频是步态里判别力很强的维度有变化的数据能让模型学到更稳的节奏表征。元数据记录建议直接用 CSV 或 JSON不要写在纸上。格式大致如下{ subject_id: S001, session: 2024-03-11, camera: cam_front_01, direction: left_to_right, clothing: long_coat, carrying: backpack, height_cm: 172, notes: 地面有反光已避开 }5.3 标注、对齐与质量筛查标注这块如果受试者是你自己招募的身份标签天然就是对的问题主要出在序列切分上。一段连续录像里可能包含受试者走过去、转身、走回来的完整过程你需要把它切成独立的序列。切分的边界建议放在人完全离开画面的时刻而不是依靠时间戳硬切否则会出现半个人、半个转身这种低质量片段。对齐环节下面这段代码是我常用的版本逻辑是先按前景的紧边界框裁紧再等比缩放到固定高度最后水平居中。注意缩放必须等比如果强行把宽高都拉成固定值胖瘦信息就被抹平了而体型比例恰恰是步态判别的重要线索。import cv2 import numpy as np def align_silhouette(mask, h64, w44): mask: uint8 单通道前景 255背景 0 ys, xs np.where(mask 127) if len(ys) 50: # 前景太少判定为无效帧 return None y0, y1, x0, x1 ys.min(), ys.max(), xs.min(), xs.max() crop mask[y0:y1 1, x0:x1 1] scale h / crop.shape[0] # 按高度等比缩放保留宽高比 new_w max(1, int(round(crop.shape[1] * scale))) resized cv2.resize(crop, (new_w, h), interpolationcv2.INTER_NEAREST) canvas np.zeros((h, w), np.uint8) if new_w w: # 太宽就居中裁掉两侧 start (new_w - w) // 2 canvas[:, :] resized[:, start:start w] else: # 太窄就居中补零 start (w - new_w) // 2 canvas[:, start:start new_w] resized return canvas质量筛查必须做而且要有量化标准不能靠肉眼翻。我一般会统计三个量前景面积占比太小说明分割失败或人太远、紧边界框的宽高比正常人体在走行中大致在 0.4 到 0.8 之间波动明显偏离说明前景里混进了非人体区域、相邻帧之间的前景面积突变突变超过 30% 往往意味着影子进出或分割抖动。这三项筛一遍能过滤掉八成以上的脏数据。注意不要因为追求干净就把所有异常帧全删掉。剪影的所谓噪声在一部分场景下就是真实分布删得太狠会让模型在实际部署时完全不适应。我的做法是保留但标记训练时通过采样权重降低它们的比例而不是一刀切。6. 训练阶段真正影响指标的几个细节6.1 剪影质量与对齐误差的放大效应剪影类的模型对对齐误差非常敏感这一点在论文里很少被强调。原因是剪影本来就是二值图没有纹理和颜色可供模型纠错一旦水平居中偏了十几个像素网络看到的这个人就整体位移了学到的特征会把这个位移当成身份信息的一部分。实测下来水平居中的策略选择比缩放的插值方式重要得多。基于边界框中心对齐在大多数情况下够用但当受试者背着一个单肩包的时候边界框会偏向背包那一侧导致人体本体偏出中心。更稳的做法是先用面积重心做粗对齐再用边界框做微调或者干脆在训练时加随机水平平移增强让模型对几个像素的偏移不敏感。另一个常见问题是脚部被裁掉。等比缩放到固定高度时如果原始框的下边界没裁紧比如背景里有一小块残留前景缩放后人体的实际高度就变小了脚可能溢出画布。这个 bug 表现得很隐蔽——损失曲线看着正常指标就是上不去。排查方法很简单随机抽 20 张对齐后的图存成拼图看一眼脚是不是都在、头是不是都被切了一眼就清楚。6.2 时序建模与采样策略的匹配步态的核心信息在时间轴上所以采样策略必须和模型的时间建模方式匹配。用等间隔采样覆盖完整行走周期去训练一个靠局部时序建模的网络容易浪费掉周期性信息用随机连续采样去训练一个需要完整周期才能提取节奏特征的网络模型会学得很吃力。我的经验是训练阶段用随机连续采样测试阶段用等间隔采样。训练时随机采样能带来更强的鲁棒性因为模型见过各种相位起点测试时用完整周期采样让模型的输入分布尽可能稳定指标才会体现真实水平。这个训练/测试采样策略不一致的做法并不是作弊因为测试时你确实可以拿到完整序列真实部署里也可以累积多帧再判定。采样帧数也不是越多越好。帧数翻倍显存和耗时基本线性上涨但指标提升往往在某个点之后就趋于平坦。因为在固定帧数下帧数越少相邻帧的冗余越高帧数越多冗余越低但计算越贵。常见做法是在 30 帧左右找一个平衡点然后通过实验确认增加帧数是否还有收益。6.3 数据增强里哪些有用、哪些有害步态数据集的增强手段和人脸、通用图像分类很不一样照搬会出问题。随机水平翻转是最有争议的一个。它确实能缓解过拟合但步态是有左右手性的——一个人左右手的摆动幅度往往不对称翻转之后相当于造了一个镜像人。在小数据集上做翻转增强模型可能学到镜像不变的特征这在真实场景下不一定成立。我的做法是数据量小时保留翻转数据量大时关掉或者把翻转概率降到很低。随机透视变换和轻微旋转是真正有价值的。它们模拟的是视角变化和相机安装误差而视角变化恰恰是步态识别最难对付的干扰之一。在只有单视角的数据集上做透视增强能明显提升跨视角泛化的表现。随机擦除要谨慎。擦掉一块剪影相当于人为制造了一个断肢样本模型可能因此学会在不完整轮廓上做判断。如果一定要用擦除区域要小、概率要低并且只在训练后期开启。时序层面的增强反而更值得投入随机丢帧模拟丢包、随机时序裁剪模拟片段边界、轻微的时间缩放模拟步速变化。这些和真实场景的分布偏移更接近。增强手段建议理由随机水平翻转低概率或禁用破坏左右手性可能引入伪不变性随机透视变换推荐模拟视角变化提升跨视角泛化轻微旋转推荐模拟相机安装误差随机擦除谨慎、小面积制造不完整轮廓误导模型随机丢帧/时序裁剪强烈推荐贴近真实丢包与片段边界时间缩放推荐模拟步速差异6.4 小样本条件下的训练技巧很多人手里只有几百人的自有数据这时候直接端到端训练一个大模型几乎必然过拟合。我试过几种办法效果从好到差排序如下。最有效的是先在公开大规模数据集上预训练再在自有数据上微调。步态特征的迁移性比想象中好——体型比例、步频这些底层线索是跨数据集通用的哪怕采集设备完全不同预训练权重也能带来明显收益。其次是用度量学习而不是分类头。小样本下直接做身份分类容易过拟合到具体的人改成三元组损失或带间隔的对比损失让模型专注在同类靠近、异类推远这个更泛化的目标上验证集上的稳定性会好很多。这里有个关键细节采样器必须保证每个 batch 里有足够的正样本对。如果随机采样导致一个 batch 里同一个人只出现一次三元组损失根本构不成对梯度是零训练会静悄悄停摆。用按身份分层的采样器每个 batch 从若干身份里各取几条序列是最基本的配置。第三是冻结底层、只训高层。剪影的底层特征边缘、轮廓跨数据集通用性很强小数据量时冻结前面几层只微调后面几层收敛更快也更稳。最后提醒一句小样本实验里提分最明显的往往不是模型改动而是采样帧数和学习率调度。这两个超参值得多花时间搜一搜收益比换主干网络来得直接。7. 我在这条路上踩过的几个具体的坑第一个坑是中文字符路径导致的静默失败。有次在 Windows 上整理数据目录名带了中文用某些库读图时返回空数组而不是抛异常结果训练时整个 batch 的前景都是全黑的模型照样在跑损失照样在降——降的是随机猜的损失。后来我在数据加载里加了一条断言assert mask.sum() 0一旦读到全黑图立刻报错。这个断言后来救了我好几次。第二个坑是预处理脚本的幂等性。我写过一版对齐脚本第一次跑正常因为某个 bug 重跑了一次结果把已经对齐过的图又对齐了一遍。因为输入本来就是 64×44再裁一次边界框几乎等于没变所以肉眼看不出来但那些恰好被第一次裁掉边缘的样本第二次又被裁掉了一点。这种累积误差在训练时表现为少数样本怎么学都不对。解决办法是给输出目录加标记文件脚本启动时检查是否存在存在就拒绝覆盖。第三个坑是只看整体指标不看分组指标。有段时间模型整体 rank-1 涨了两个点我以为进步了拆开按条件看才发现是正常行走条件下涨得多而换衣条件下反而跌了。整体指标掩盖了退化。从那以后我固定要按条件分组报指标尤其是人数少的条件组单独看一眼。第四个坑是忽略了序列长度不均衡对损失的影响。按序列级别做对比学习时长序列和短序列都被压缩成同样维度的表征但短序列的序列内信息量天生就少它的表征方差更大。如果不做长度归一化或者在采样时对齐长度短序列会成为损失里的高噪声项把训练方向带偏。我的处理是把长度低于一定阈值的序列在训练时降采样测试时仍然全部评测。第五个坑是没有固定随机种子。步态数据的采样环节有随机性M 型采样不同种子下同一配置的指标波动可能超过一个点。有一次我对比两个模型A 比 B 高 0.8 个点换了种子跑第二遍B 又比 A 高 0.3 个点。后来我固定了三个种子取平均实验结论才稳定下来。跑对比实验的时候单次结果基本没有参考价值。这五点里我个人觉得最值得警惕的是第一个和第五个一个会让你在错误的数据上跑出看起来正常的曲线一个会让你在错误的结论上继续投入几周时间。数据加载环节的断言和实验的多种子重复是我现在任何项目开始前都会先搭好的两道保险。