
活体检测这个方向做过的人都知道真正卡脖子的从来不是网络结构而是数据。你可以在论文里把融合模块画得天花乱坠但只要训练数据里没有覆盖真实的攻击形态模型上线第一天就会被一张手机屏幕照片骗过去。我前后在两三个项目里折腾过多模态人脸活体检测数据集RGB 单模态的方案踩过的坑最多后来转向 RGB 深度 近红外的三模态路线才算把误拒率和误放率压到了一个能交付的水平。这套公开的三模态人脸活体检测数据集圈内习惯直接叫它 SURF 系列就是我入门的第一个真正意义上的多模态活体数据集说它是这个领域绕不开的公共课本并不夸张。这篇文章想讲清楚三件事这套数据集到底长什么样、它的标注和模态是怎么组织的拿到数据之后从清点、对齐、预处理到训练评估的完整链路怎么走以及那些官方文档里绝对不会写、但只要你真跑过就一定会撞上的坑。适合刚进这个方向、正准备拿它跑第一个 baseline 的同学也适合已经跑通单模态、想看看多模态融合到底能带来多少收益的从业者。我不会给你堆一堆公式而是把每一步为什么这么做、参数怎么定、出问题怎么查按我自己的实操顺序摊开讲。1. 这套数据集到底解决了什么问题1.1 单模态活体检测的天花板在哪里先说清楚背景不然你很难理解为什么非要搞三个模态。人脸活体检测Presentation Attack Detection业内简称 PAD要防的是假脸——打印的照片、手机屏幕上播放的视频、硅胶或者纸质的面具这些东西被举到摄像头前面试图骗过身份核验系统。早期大家清一色用 RGB 彩色图做思路是让卷积网络去学纹理特征打印纸有网点、屏幕有摩尔纹、面具边缘有反光。这条路在受控环境下确实能跑出很漂亮的数字但它的脆弱性也很明显。问题出在 RGB 相机本质上只是把三维世界投影到二维平面上它丢失了深度信息。一张高清打印的照片在特定光照和角度下它的二维投影和真人脸的二维投影可以做到极其接近剩下的差异就只有纹理细节。而纹理这东西太容易被对抗了换一台打印机、换一种纸张、把照片贴在弧形的硬纸板上做一个立体感模型的判别边界就开始飘。我印象特别深的一次是拿一个在实验室数据上 ACER 做到 2% 以下的模型去测另一批采集的打印攻击误放率直接飙到 20% 以上原因就是训练集里的打印样本全是同一台喷墨打印机出的模型学到的其实是那台打印机的色彩偏差而不是这是纸这件事本身。所以业界的共识逐渐转向单靠 RGB 的纹理线索不够稳必须引入物理层面无法伪造的信号。这就是这套多模态数据集存在的根本理由——它同时提供了彩色图、深度图和近红外图让模型能同时看到长什么样有多立体材质怎么反射红外光这三层信息。1.2 三个模态各自的判别逻辑我把三个模态的互补关系拆开讲这块理解了后面设计融合结构的时候你才知道该在哪一层融。彩色图RGB提供的是最符合人类直觉的视觉信息纹理、颜色、边缘、摩尔纹都在这里。它的优势是分辨率高、细节丰富缺点是这些细节全部来自二维投影可以被高质量打印和高清屏幕很好地模仿。深度图Depth是这套数据集最值钱的部分。它记录的是每个像素到相机的距离真人脸在深度图上是一个有连续曲率的曲面鼻尖最近、脸颊两侧渐远而一张 A4 纸打印的照片在深度图上几乎就是一个平面或者干脆是一大片无效值。面具虽然有一定立体度但它的曲率分布和真人脸有系统性差异尤其是眼窝、鼻翼这些细节区域廉价面具根本做不出来。深度图的另一个价值是它天然抗光照室内灯光怎么变几何形状不变。近红外图NIR看的是材质在近红外波段的反射特性。这一路的判别力经常被低估。真实皮肤在近红外下的反射率和纸张、塑料、硅胶完全不同皮肤有皮下血管带来的细微不均匀而打印纸在近红外下往往呈现出非常均匀、平坦的灰度分布手机屏幕在近红外下会出现明显的亮度异常甚至整片过曝。很多实际项目里NIR 单模态的表现甚至比 RGB 还稳原因就在于它捕捉的是材质属性而不是外观属性。三者组合起来攻击者要想同时骗过纹理、几何和材质三个维度成本会高到不现实。这就是多模态融合在活体检测里几乎成了标配的原因也是这套数据集从发布起就被大量引用、成为事实基准的原因。1.3 数据集的整体规模与组织方式这套数据集的基本盘是上千名受试者、两万多段视频样本按受试者维度切分成训练、验证、测试三部分比例大致是 6:1:3。这里有个非常重要的设计细节划分是按人划分的不是按视频划分的。同一个人的不同视频只会出现在同一个子集里绝不会跨集。这个设计的意义得说透。如果你的划分是按视频随机打散那么同一个人的正样本可能既在训练集又在测试集模型只需要记住这张脸就能在测试集上拿到虚高的分数——它学的不是活体特征是人脸身份特征。GB/T 相关的评测规范里对这一点有明确要求很多论文报出来的漂亮数字在跨人划分下会掉一大截原因就在这里。你自己做实验的时候如果没有官方划分也一定要按身份做 group split这是活体检测里最容易被忽略、又最影响结论可信度的一个点。样本层面每个受试者会采集若干段视频其中既有真人正脸、侧脸、不同光照下的真实活体bona fide也有用各种方式伪造的攻击样本。攻击类型覆盖了打印照片、裁剪/挖洞照片、屏幕显示回放、以及一定数量的面具类攻击具体类别和数量以随数据集发布的说明文档为准我这里不逐一罗列因为不同版本的补充包会有差异你拿到手第一件事就是去核对官方清单而不是照抄某篇博客。值得单独提一句的是这套数据集后来还衍生出了侧重种族、性别、年龄分布均衡的扩展版本以及针对高质量面具的专门子集。这些扩展版本在做公平性分析和抗强攻击能力评估时很有价值但训练成本也更高新手先把基础版本跑通再说。2. 拿到数据后的第一件事清点与理解标注2.1 目录结构与模态文件的对应关系数据解压完第一眼看到的是按受试者 ID 分组的目录树每个受试者下面按真实/攻击分成不同类别再往下是每一段采集的样本目录里面并排躺着三个模态的图。我见过的组织方式大致是这样dataset/ ├── train/ │ ├── 0001/ # 受试者 ID │ │ ├── real/ # 真实活体样本 │ │ │ ├── color/ # RGB 帧 │ │ │ ├── depth/ # 深度帧 │ │ │ └── ir/ # 近红外帧 │ │ └── fake/ # 攻击样本可能再按攻击类型细分 │ └── ... ├── val/ └── test/这里第一个坑就来了不同模态的分辨率并不一致。彩色图通常是最高分辨率的深度图和红外图往往会小一截而且宽高比也可能不同。你不是简单地把三张图 resize 到同一个尺寸就完事了后面第 3 章会专门讲对齐。第二个坑是帧数不匹配。同一段采集里三个模态抽出来的帧数不一定严格相等个别样本可能某个模态少几帧。写 dataloader 的时候必须做一次交集校验否则训练到一半突然报 index out of range你会怀疑是代码写错了其实是数据本身有缺。我习惯在动手写模型之前先跑一个体检脚本把整个数据集的模态完整性、分辨率分布、文件大小分布全部统计一遍。这一步花不了二十分钟但能省掉后面两天的调试。2.2 标注文件的字段该怎么读标注一般以 CSV 或者 JSON 的形式给出核心字段就那么几个样本路径、受试者 ID、真伪标签、攻击类型、模态标识。看起来很简单但有几个字段的口径必须自己确认清楚不然训练出来的模型指标会莫名其妙地不对。字段常见取值需要注意的地方label0/1 或 live/spoof有的版本 1 表示活体有的版本 0 表示活体必须核对attack_type打印、屏幕、面具等训练时可合并为二分类做细粒度分析时要保留subject_id受试者编号用于按人划分和 group 交叉验证modalitycolor/depth/ir部分版本把三模态拆成三行记录splittrain/val/test优先用官方划分不要自己重切我最想强调的一点标签的正负号方向一定要在代码里显式校验一次。做法很简单随机抽 20 条标注人工去对应的目录看一眼是真人还是攻击对着数一遍。这个动作看起来傻但我见过不止一个项目因为标签方向搞反模型训练了三天指标一直在 50% 附近晃还以为是学习率的问题。2.3 数据分布统计先看数再动手很多人的习惯是拿到数据直接开训然后对着 loss 曲线猜问题。我建议先花点时间做分布统计尤其是下面三个维度。真伪比例。活体检测数据集普遍存在不平衡真实样本往往多于攻击样本比例可能在 1.5:1 到 3:1 之间。如果你的训练集比例是 2:1而测试集接近 1:1那你在验证集上选出来的最佳阈值搬到测试集上会系统性偏移。解决办法要么是在采样时做重加权要么是用不依赖阈值的指标比如 TPRFPR来选模型。攻击类型分布。打印类攻击往往占大头面具类样本可能只有几十上百个。如果你的业务场景里面具攻击是主要威胁那这个分布就是严重失配的必须做定向补充或者类别重加权否则模型对面具基本没有判别力。受试者维度的分布。统计一下每个人的样本数你会发现长尾很明显有的人几十段有的人只有几段。做交叉验证的时候要注意别让某个 fold 里全是低样本量的受试者。我一般的做法是写个脚本输出一张统计表存成 CSV跟实验记录放在一起。后面模型表现异常的时候回头翻这张表经常能直接定位到问题。3. 三模态对齐与预处理实操3.1 为什么对齐是整条链路里最不能省的一步多模态融合的前提是同一个像素位置在不同模态里指的是同一个物理点。如果 RGB 里 (120, 80) 是鼻尖而深度图里 (120, 80) 是背景墙那你的融合层就是在拿鼻子和墙壁做特征相加模型不崩才怪。好在这套数据集在采集阶段就用了同步的多传感器方案三个模态在硬件层面已经做过初步配准所以你不需要自己做复杂的立体标定。但这不等于可以直接用。两个原因一是分辨率不同导致的重采样偏移二是边缘的黑边和非人脸区域干扰。深度相机和红外相机的视场FOV和彩色相机不完全一致所以深度图和红外图的四周通常会有无效区域直接 resize 会把有效区域压扁几何关系全乱。正确的做法是先做中心裁剪把三个模态的有效视场裁到一致再统一缩放到目标尺寸。如果你要做人脸区域裁剪那必须只在彩色图上检测人脸框然后把同一个框按比例映射到另外两个模态绝对不能在深度图或红外图上独立检测再各自裁剪——那样三张图的人脸位置会错开融合效果直接报废。3.2 一套可复现的对齐与预处理流程下面是我实际在用的处理函数逻辑是以彩色图为基准检测人脸得到带 padding 的方框再按各模态与彩色图的分辨率比例映射坐标最后统一 resize。import cv2 import numpy as np def align_triplet(rgb, depth, ir, detector, out_size224, pad_ratio0.3): H, W rgb.shape[:2] # 只在彩色图上检测拿到基准框 boxes detector(rgb) if len(boxes) 0: return None # 交给上层做丢弃或回退到中心裁剪 x1, y1, x2, y2 boxes[0] # 加 padding把额头和下巴包进来活体线索很多在这两个区域 cx, cy (x1 x2) / 2, (y1 y2) / 2 side max(x2 - x1, y2 - y1) * (1 pad_ratio) x1 int(max(0, cx - side / 2)); x2 int(min(W, cx side / 2)) y1 int(max(0, cy - side / 2)); y2 int(min(H, cy side / 2)) def crop_and_resize(img, ref_w, ref_h): ih, iw img.shape[:2] sx, sy iw / ref_w, ih / ref_h # 分辨率比例映射 nx1, nx2 int(x1 * sx), int(x2 * sx) ny1, ny2 int(y1 * sy), int(y2 * sy) patch img[ny1:ny2, nx1:nx2] return cv2.resize(patch, (out_size, out_size), interpolationcv2.INTER_LINEAR) rgb_p crop_and_resize(rgb, W, H) depth_p crop_and_resize(depth, W, H) ir_p crop_and_resize(ir, W, H) return rgb_p, depth_p, ir_p这段代码里有两个参数值得展开说。pad_ratio我默认给 0.3是因为活体检测的有效线索分布很广——打印照片的边缘、面具的发际线、屏幕的边框很多都在人脸检测框之外。padding 太小会把边框信息切掉实测 0.25 到 0.35 是个比较稳的区间太小掉点太大引入过多背景噪声。out_size给 224 是为了对齐主流骨干网络的输入如果你的算力允许320 也是个不错的选择因为纹理线索比如打印网点在小尺寸下会被双线性插值糊掉。3.3 深度图和红外图的归一化必须分别处理这是新手最容易一刀切的地方。三个模态的数值分布完全不同用同一套归一化参数会把信息毁掉。彩色图标准做法除以 255再用 ImageNet 的均值和方差标准化没什么好说的。深度图常见是 16 位存储单位可能是毫米。绝对不能直接除以 255那样数值全部溢出。正确做法是先转成 float32然后用数据集级别的统计量做 min-max 或者按分位数截断。我一般用 1% 和 99% 分位数做截断再归一化到 [0,1]这样可以压掉深度相机在边缘产生的离群值。另外深度图里的无效值通常是 0要单独处理成一个固定的填充值比如归一化后的 0并且额外生成一个 mask 通道告诉网络这块是无效区域这个小技巧对提升稳定性帮助很大。红外图通常是单通道 8 位灰度但它的问题是对比度受距离和发射功率影响很大同一批数据里有的样本整体偏暗、有的接近过曝。我的做法是先做一次 CLAHE 自适应直方图均衡再归一化到 [-1, 1]。CLAHE 的 clipLimit 给 2.0、tileGridSize 给 8x8这个组合在我试过的几批数据上最稳。注意所有归一化用的统计量深度分位数、红外均值方差都只能在训练集上算然后原封不动地应用到验证集和测试集。用全量数据算统计量再切分是典型的数据泄漏会让你的验证指标虚高几个点。3.4 增强策略哪些能用哪些是禁区数据增强在活体检测里是一把双刃剑用错了会直接把判别线索毁掉。可以放心用的随机水平翻转注意深度和红外要跟着一起翻、小角度旋转±10 度以内、亮度对比度扰动主要给 RGB、随机擦除小比例。这些都是在模拟采集时的自然变化。必须谨慎的颜色抖动里的饱和度、色相偏移要收着用。活体检测里颜色是很重要的线索你把一张打印照片的色调随机偏移可能就把它变得和真实皮肤的颜色分布接近了等于在教模型学错东西。我的经验是色相抖动范围控制在 ±5 度以内饱和度 ±0.2 以内。绝对不要用的大角度旋转、随机裁剪会破坏人脸的几何结构深度图上的曲率关系被裁断、CutMix 和 MixUp。尤其是 MixUp把一张活体图和一张攻击图按像素加权平均产生的样本在物理上根本不存在深度图上会出现半凸半平的诡异曲面模型学到的是噪声。我试过一次训练 loss 降得很漂亮验证集 ACER 直接翻倍果断放弃。4. 模型设计与多模态融合方案4.1 单模态基线必须先跑出来我强烈建议在任何融合之前先把三个单模态的基线各自跑一遍。理由有三个一是给你一个下界参考如果融合之后还不如最好的单模态说明你的融合模块有问题二是让你知道哪个模态在这套数据上判别力最强从而在融合时给它更高的权重三是排查数据问题时单模态的异常表现往往能直接指向哪个模态的预处理出了错。我的实测经验是深度和红外两个模态单独跑出来的指标通常明显优于 RGB 单模态RGB 反而经常是最弱的那个。这个结论跟很多人的直觉相反但很合理RGB 的纹理线索最容易被高质量攻击模仿而深度和红外的物理线索难以伪造。如果你的 RGB 基线跑出来异常地高先别高兴去查一查是不是数据泄漏了。单模态基线的网络不用太复杂一个在 ImageNet 上预训练的 ResNet18 或者 MobileNetV3 就够输入通道数改一下深度和红外是单通道可以把第一层卷积的权重在通道维度上求和压缩或者直接复制三份。训练 20 到 30 个 epoch学习率用 1e-4 加余弦退火就能得到一个可用的基线。4.2 融合位置的选择早融合、中融合还是晚融合这是多模态活体检测里最核心的设计决策我把三种方案的取舍讲清楚。早融合输入级把三个模态在通道维度拼起来当成一张多通道图送进一个网络。优点是结构最简单、参数量最小、推理最快非常适合端侧部署。缺点是网络在第一层就把三种完全异构的信号混在一起后面很难再针对每个模态做专门的特征提取。实测下来早融合的指标通常比中融合低一到两个点但如果你的目标平台是低端芯片这个代价是可以接受的。中融合特征级每个模态走一个独立的骨干网络分支在中间某一层把特征图拼起来或者做注意力加权。这是目前效果最好、也是论文里用得最多的方案。它的优势在于每个分支可以独立学习模态特有的表征融合发生在语义层面而不是像素层面。代价是参数量和显存占用接近三倍。晚融合决策级三个分支各自输出一个分数最后做加权平均或者学习一个融合权重。优点是极其灵活某个模态缺失的时候可以直接把它的权重置零鲁棒性好。缺点是丢失了跨模态的细粒度交互信息而且需要训练三个独立模型工程复杂度反而更高。我自己的项目里最终选的是中融合具体是在每个分支的第三个 stage 输出后做一次跨模态注意力。原因是这个位置的特征图还有足够的空间分辨率能保留纹理和几何的局部细节同时又已经有了一定的语义抽象融合起来不会太噪。4.3 一个能直接用的跨模态注意力融合模块下面这个模块我用了很久结构简单但效果稳定。核心思路是以 RGB 特征为 query去深度和红外特征里查哪些位置值得关注然后把增强后的信息加回去。import torch import torch.nn as nn class CrossModalFusion(nn.Module): def __init__(self, dim): super().__init__() self.q nn.Conv2d(dim, dim // 8, 1) self.k nn.Conv2d(dim, dim // 8, 1) self.v nn.Conv2d(dim, dim, 1) self.gate nn.Sequential( nn.Conv2d(dim * 2, dim, 1), nn.BatchNorm2d(dim), nn.Sigmoid() ) self.proj nn.Conv2d(dim, dim, 1) def forward(self, feat_rgb, feat_aux): # feat_rgb: [B,C,H,W] 主模态 # feat_aux: [B,C,H,W] 深度或红外 q self.q(feat_rgb).flatten(2) # B, C/8, HW k self.k(feat_aux).flatten(2) # B, C/8, HW v self.v(feat_aux).flatten(2) # B, C, HW attn torch.softmax(torch.bmm(q.transpose(1, 2), k) / (q.shape[1] ** 0.5), dim-1) ctx torch.bmm(v, attn.transpose(1, 2)).view_as(feat_rgb) # 门控决定这一路辅助信息保留多少 g self.gate(torch.cat([feat_rgb, ctx], dim1)) return feat_rgb self.proj(g * ctx)几个实现细节值得注意。第一dim // 8这个压缩比不是随便定的注意力头的维度太大计算量爆炸太小又表达不了跨模态关系8 是个经验值。第二门控那一路很关键——不是所有样本的辅助模态都可信深度图可能有大量无效值红外可能过曝门控让网络自己决定这次要不要听深度图的。第三残差加回主分支而不是替换保证即使辅助模态全废主分支还能单独工作。融合顺序上我一般是先做 RGB 和深度的融合再把结果和红外融合而不是三个一起算注意力。原因是深度和红外的特征分布差异更大两两融合更稳。5. 训练策略与指标评估5.1 别再用准确率了看这四个指标活体检测的评估指标跟普通分类任务完全是两套体系用 accuracy 来报结果基本等于没报。原因是这个场景对两类错误的要求极不对称——把真人误判成攻击误拒用户当场就想砸设备把攻击误判成真人误放那就是安全事故。所以行业标准看的是下面这几个。指标含义关注点APCER攻击样本被误判为真人的比例越低越好安全底线BPCER真实样本被误判为攻击的比例越低越好体验底线ACER(APCER BPCER) / 2综合指标论文常报TPRFPR1e-3固定误放率下的通过率更贴近真实业务约束其中 TPRFPR1e-3 是我最看重的。原因很实际真实业务里你几乎不可能接受千分之三以上的误放率所以你必须知道在这个约束下模型能放行多少真人。ACER 这个均值指标有个陷阱——它把两类错误等权看待但业务上它们根本不等价一个 ACER 很低的模型可能在 FPR 千分之一这个点上表现很烂。计算上有两个坑。一是 APCER 的聚合方式官方规范里同一个攻击样本会采集多帧APCER 是先算每条攻击序列的误放比例再对所有序列取平均而不是把所有帧混在一起算总数。这两种算法结果能差好几个点。二是阈值的选择ACER 是在某个阈值下算出来的这个阈值应该是让 ACER 最小的那个而不是简单的 0.5。很多人直接拿 0.5 当阈值报指标数字会虚高或者虚低。5.2 损失函数与采样策略的搭配类别不平衡的问题在这里必须解决。我的标准配置是加权交叉熵加一个小的对比损失辅助权重 1:0.2 左右。加权交叉熵的权重不要简单地按类别频率的倒数设那样负样本权重会过大模型会过度保守BPCER 飙高。我的做法是按平方根反比设权重比如正负比 2:1那权重就设成 1 和 0.7 左右让模型稍微偏向少数类但不至于失衡。更精细的做法是按攻击类型分组加权面具类样本少就给它更高的权重这样模型不会因为打印样本太多而忽略面具。采样策略上我用的是受试者级别的平衡采样器每个 batch 里保证正负样本数接近同时限制同一个受试者的样本不超过 2 个。第二条很重要因为同一个人在同一光照下的多段视频高度相似如果 batch 里全是同一个人BN 的统计量会严重偏斜训练极不稳定。我一开始没注意这点loss 曲线像心电图一样跳改成受试者约束采样之后就平滑了。优化器用 AdamW学习率 1e-4weight decay 1e-4余弦退火到 1e-6训练 30 到 40 个 epoch。batch size 如果能给到 64 最好显存不够就 32 配合梯度累积。这里提一句融合模型对 batch size 比单模态敏感得多因为 BN 统计量要同时覆盖三个模态的分布batch 太小的话训练前期非常不稳。5.3 我的训练曲线和调参记录第一轮训练我用的纯中融合、无门控、学习率 1e-3。结果训练 5 个 epoch 后验证集 loss 开始上升APCER 从 3% 爬到 15%。这是典型的过拟合加学习率过大深度分支的输入分布和 ImageNet 差太远预训练权重在高学习率下被迅速破坏。第二轮把学习率降到 1e-4骨干网络前两个 stage 冻结加上了门控模块。这次训练稳定了验证集 ACER 收敛在 6% 左右但验证集和测试集之间有将近 4 个点的差距。查了半天发现是测试集里有一类面具攻击在训练集里完全没有覆盖属于典型的分布外泛化问题。这个坑没法靠调参解决只能靠数据层面的补充或者引入更强的正则。第三轮我做了两件事一是把深度图的有效区域 mask 作为额外通道输入二是加了模态随机丢弃训练时以 0.15 的概率把某个辅助模态置零。第二个技巧效果非常明显它对标的是真实部署里深度相机偶尔失效的情况同时也起到了正则作用。最终测试集 ACER 相比第二轮降了大约 1.8 个点TPRFPR1e-3 提升了 4 个点左右。6. 常见问题排查实录6.1 问题速查表下面这张表是我和同事在跑这套数据时真实遇到过的问题按出现频率排序遇到异常可以先对照着过一遍。现象最可能的原因排查与解决训练 loss 不降标签方向反了 / 归一化溢出人工核对 20 条标注检查深度图是否 16 位未转换验证指标虚高但测试崩按视频而非按人划分检查 split 是否按 subject_id 分组深度分支梯度爆炸深度图含大量无效值未处理加 mask 通道无效值统一填充红外分支几乎不学习图像整体过暗或过曝加 CLAHE统计训练集亮度分布融合不如单模态融合位置太早 / 特征未对齐换到 stage3 之后融合检查分辨率一致性同一 batch loss 剧烈震荡batch 内同一受试者样本过多加受试者约束采样推理速度不达标三分支全尺寸推理辅助分支降分辨率或改早融合6.2 几条只有真跑过才知道的经验第一永远不要在测试集上做任何决策。包括选阈值、选模型、调早停轮次。这套数据集的测试集在领域内被引用太多次很多人有意无意地在上面调过参所以你在它上面刷出特别高的分数时要保持警惕。我一般会从训练集里再切一个内部验证集出来做所有决策官方验证集只用来做最终确认。第二跨数据集验证比同分布验证有价值得多。在这套数据上做到 1% 以下的 ACER 并不稀奇但你换一个采集设备、换一批攻击样本指标大概率会掉一大截。如果你要交付真实系统务必留一部分来自不同采集条件的样本做压力测试哪怕只有几百条。这个数字比论文里的漂亮指标有用一百倍。第三注意攻击样本的代际问题。这套数据集采集有些年头了里面的打印攻击用的设备和工艺跟现在相差不小。现在的彩色激光打印、高刷新率屏幕其欺骗能力比当年强很多。所以模型在老数据上表现好不代表能挡住新攻击。可行的做法是定期补充新采集的攻击样本做增量训练或者用域适应的方法让模型对未知攻击类型更敏感。第四样本量小的攻击类别不要硬训。如果某类攻击只有几十个样本强行让模型去学大概率是记住而不是学会。更稳的做法是把它并入一个粗粒度的其他攻击类别或者在损失里用 focal loss 降低简单样本的权重配合强增强做小样本学习。第五记录每一次实验的超参和数据处理版本。多模态实验的变量太多改动一次预处理就可能让指标变动两三个点。我后来强制自己每次实验都存一份配置文件加 git commit回头看的时候能省下大量重复试错的时间。我个人在实际操作中的体会是这套数据集最大的价值不是让你刷一个高分而是逼你把多模态数据的整条链路——对齐、归一化、融合、评估——完完整整走一遍。这条链路上每一个环节的坑换到任何其他多模态任务上都还会再遇到一次。等你把这些坑都踩过一遍再去看那些融合结构的论文你会发现很多设计选择的理由其实就藏在这些工程细节里。