1. 先想清楚:130M 量级的数据集预处理到底在解决什么问题
第一次拿到 HD_VG_130M 这类量级的高清视频数据集分片清单时,很多人的第一反应是「直接开个多进程脚本解码不就行了」。我最初也是这么想的,结果跑了不到两天就撞上一堆问题:解封装报错、时间戳错位、显存打满、单个分片里混进几十个台标和字幕条,训练侧加载器还在等预处理结果。回过头看,HD_VG_130M 数据集预处理这件事的难点从来不是「会不会写解码代码」,而是「在上亿条样本、TB 到 PB 级存储、几十张卡并行的情况下,如何把一条流水线做得既快又稳、还能随时复现」。
HD_VG_130M 这个量级的数据集,通常指的是千万到上亿条视频-文本对,以高清(720p 及以上)为主,覆盖多种内容类型。它不是一个可以一次性塞进内存的「数据集」,而是一堆分布在不同存储节点上的分片。你要做的事情,是把这些原始分片变成训练框架能直接读取的、经过清洗与对齐的样本单元。这个过程中,每一条样本要经过元数据校验、镜头切分、画质打分、内容过滤、caption 清洗、重采样、重新打包这七八道工序,每一道工序都可能成为瓶颈,也都可能悄悄污染数据。
这篇文章我想讲的就是这套流程的完整落地方法:为什么这样设计流水线、每一道关卡的具体算法和阈值怎么定、工程上怎么避免「跑三天崩一次」、以及那些文档里不会写但实际会遇到的问题。适合两类人参考——一类是正在搭视频生成训练数据管线的工程师,另一类是手头有一批原始视频素材、想知道怎么把它清洗成高质量训练集的研究者。哪怕你手上只有几千条视频,这套分层的思路也照样适用,只是可以砍掉分布式那一层。
2. 整体设计:为什么要把流水线拆成六段而不是一个大脚本
2.1 从「原始堆料」到「可训练样本」的三次信息损耗
很多人低估了预处理阶段的信息损耗。一条原始视频从落盘到变成训练样本,至少要经历三次不可逆的损失:
第一次是解码损失。高清视频大多是长 GOP 的 H.264/H.265 编码,只有关键帧是完整的,后续帧靠运动补偿预测重建。你随机 seek 到一个非关键帧位置,解码器必须先回溯到最近的关键帧再往前解码,这既慢又可能引入色彩偏差。如果你的预处理脚本不做关键帧对齐,切出来的片段首帧质量会明显低于其他帧。
第二次是重采样损失。原始素材的分辨率五花八门,有 1080p 的、有 4K 的、有竖屏的、也有带黑边的。统一到训练分辨率时,缩放算法选错了、宽高比处理错了,都会带来不可逆的模糊或形变。我见过最典型的问题是把 21:9 的素材直接拉伸成 16:9,人物看上去像被压扁了半头。
第三次是过滤损失。画质打分、内容过滤、caption 清洗,每一道都是「宁可错杀」的策略。阈值定得太松,脏数据混进训练集,模型学出一堆噪点和水印;阈值定得太严,几十万条样本被误杀,数据多样性和数量都不够。这个平衡点只能靠抽样人工复核来找。
理解这三次损耗,就能理解为什么流水线的设计原则是「尽可能早地做不可逆操作,尽可能晚地做可逆决策」。解码和重采样是不可逆的,要一次做对;过滤阈值是可调的,要留出重跑的空间。
2.2 分层的六个阶段与它们各自的职责边界
我把整套预处理拆成六段,每一段都有明确的输入输出契约,段与段之间通过中间清单文件解耦:
| 阶段 | 核心职责 | 输入 | 输出 | 可重跑性 |
|---|---|---|---|---|
| 阶段一 | 元数据解析与完整性校验 | 原始分片清单 | 清洗后的元数据表 | 完全可重跑 |
| 阶段二 | 镜头切分与片段索引 | 元数据表 | 片段级时间戳清单 | 完全可重跑 |
| 阶段三 | 技术质量与内容打分 | 片段清单 | 带分数的片段清单 | 完全可重跑 |
| 阶段四 | Caption 生成与重写 | 片段清单 + 关键帧 | 文本对清单 | 部分可重跑 |
| 阶段五 | 解码重采样与打包 | 通过筛选的清单 | 训练用分片包 | 建议一次成 |
| 阶段六 | 抽样复核与配比统计 | 分片包 + 统计指标 | 质量报告 + 采样权重 | 可随时执行 |
这样拆的好处很直接:阶段一到四全是轻量计算,CPU 密集型,跑挂了重跑成本极低,而且可以在几十台机器上随便并行;阶段五涉及大量 IO 和解码,最贵,所以它只处理已经通过筛选的样本,垃圾数据根本不会浪费解码算力。我算过一笔账,如果先解码再过滤,整体耗时大约是「先过滤再解码」的三到五倍,因为大部分低质样本的解码完全是无用功。
另一个隐藏收益是调试友好。当最终训练效果不对时,你可以沿着中间清单逐段回溯,看是哪个阶段把好样本误杀了,或者哪个阶段放进了脏数据。如果是一个大脚本从头跑到尾,你只能靠猜。
2.3 先算账再动手:存储、带宽与算力的三个硬约束
动手写代码之前,有三笔账必须先算清楚,否则做到一半会被迫推倒重来。
存储账。130M 条原始视频,假设平均每条 15 秒、1080p、码率 6Mbps,单条大约 11MB,总量就是 1.4PB 左右。中间产物(关键帧图片、打分结果、caption 文件)如果不加控制,很容易再膨胀出一倍。我的做法是关键帧只在打分阶段临时落盘,打分成批处理后立刻清理,最终只保留文本清单和打包好的训练分片。打包后的训练分片用低码率重编码,通常能压到原始的 30% 到 50%。
带宽账。分布式场景下,瓶颈往往不是 CPU 而是对象存储的读取带宽。单台机器解码 1080p 视频,如果从远端拉流,网卡很容易跑满。我通常会把数据按分片划分成「亲和组」,让每台机器只处理物理上离它最近的几个分片,减少跨区域流量;同时对阶段三的抽帧操作做「一次读取、多指标复用」——同一批帧既算清晰度又算模糊度还算美学分,避免同一段视频被读三遍。
算力账。阶段五的解码重编码是最重的。实测下来,单核 CPU 解码 1080p 并重编码到 720p,速度大约是实时速度的 3 到 8 倍(取决于编码器预设)。保守按 5 倍算,130M 乘 15 秒等于 1.95 亿秒素材,单核需要 3900 万秒,约 450 核天。如果你只有 32 核,那就是 14 天。这个数字必须在开工前想明白,否则排期一定会崩。有条件的话,阶段五考虑用硬件编码器,能提速数倍,代价是压缩效率略差一点。
3. 镜头切分与片段索引:把长视频拆成可训练的单元
3.1 元数据表的结构设计与必备字段
流水线的第一件事是建一张可靠的元数据表。这张表是整个流程的「主键」,后面所有中间结果都靠它关联。我习惯用 Parquet 存,字段设计大致如下:
# 元数据表核心字段示意(字段名自定义,重点是语义齐全) schema = { "sample_id": "str", # 全局唯一,建议 分片号_文件号 的确定性哈希 "source_uri": "str", # 原始文件定位 "shard_id": "int", # 所属分片,用于任务亲和处理 "duration_sec": "float", # 时长,从容器元数据读取 "width": "int", "height": "int", "fps": "float", "codec": "str", "bitrate_kbps": "int", "has_audio": "bool", "raw_caption": "str", # 原始附带的描述文本 "license_tag": "str", # 来源标记,用于后续配比 "decode_ok": "bool", # 能不能正常解出首帧 }这里有几个容易踩的坑。第一是 sample_id 的生成方式:千万不要用自增 ID,一旦分片重跑、顺序变了,ID 就对不上了。用「分片号 + 文件相对路径」的哈希,天然幂等。第二是时长来源:容器元数据里的 duration 有时不准,特别是被拼接过的文件,必须以实际解码结果为准,做一次校验,误差超过 5% 的标记为异常。第三是 decode_ok 字段:这一列要在建表阶段就做一遍轻量探测(只解首帧),把彻底损坏的文件提前踢出去。我遇到过整批分片里有 3% 左右的文件解封装直接报错,如果留到阶段五才发现,那批任务会集体失败,白排一次队。
3.2 镜头边界检测的三种方案与选型依据
长视频直接按固定时长切分是很粗的做法,会把一个镜头从中间截断,导致片段首尾出现突变,模型学到的运动连续性就被破坏了。所以要做镜头切分。
常见的三种方案:
固定时长滑窗,最简单,成本几乎为零。缺点是切点随机,一个 3 秒的片段里可能有两次跳切。这种方案只适合做粗筛,不适合做最终训练样本。
基于帧间差异的检测,先降采样到低分辨率灰度,计算相邻帧的直方图差或像素差,超过阈值即判为切换点。这种方法快、无依赖、对硬切敏感,但对渐变转场(淡入淡出、叠化)基本无感。实现上,主流开源工具里都有现成实现,也可以自己用十几行 OpenCV 代码搞定。
基于特征相似度的检测,抽帧后用轻量视觉特征计算余弦相似度,相似度突降处判为切点。这种方式对渐变转场更敏感,误检也少,代价是要跑一遍特征提取,成本高一个量级。
我的做法是两段式混合:先用帧间差异做粗检,把明显的硬切找出来;再对「疑似切点」附近的小窗口用特征相似度做二次确认。这样既照顾了准确率,又把特征提取的次数压到很低。阈值方面,帧间差异我用 0.35 左右(归一化后的直方图距离),特征相似度用 0.75 作为切点判定线,这两个数不是金标准,必须在你自己的数据上抽 200 条看一眼再定。
注意:镜头切分只负责「找出切点」,不负责「决定片段长度」。切点找出来之后,还要做一次合并——太短的镜头(小于 1 秒)并到相邻镜头里,太长的镜头(超过 30 秒)再按滑窗二次切分。这一步不做,你的片段时长分布会非常难看,长尾严重。
3.3 片段采样与时间戳对齐的实现细节
切点确定后,生成片段清单。每条记录包含 sample_id、起始时间戳、结束时间戳、时长、帧数。这里有三个细节值得单独说。
时间戳要用容器的时间基换算。视频容器里的时间戳单位是 timescale 的倒数,直接拿来当秒数用会错得离谱。正确做法是读流的 time_base,把 PTS 换算成秒。我见过有人直接用帧序号除以 fps,这在恒定帧率的文件里勉强能用,遇到可变帧率就彻底乱套。
片段起点要吸附到最近的关键帧。这是为了阶段五解码时能直接从关键帧开始,避免回溯解码。做法是记录切点时间戳后,向前找最近的关键帧位置作为实际解码起点,把这段偏移量记在清单里,作为后续裁剪的补偿。这个字段不加,阶段五就要在解码后做二次裁剪,白白多解几帧。
保存中间帧的时间戳映射。阶段四要用关键帧生成 caption,阶段五要保证打包的帧和 caption 对得上。我的做法是在片段清单里额外存一列「采样帧的绝对时间戳」,通常是片段的 0%、25%、50%、75% 四个位置。这样后面任何环节重跑,都能精确重现同一批帧。
4. 画质与内容双重过滤:清洗流水线的四道关卡
4.1 技术质量指标的算法选择与计算成本
技术质量是最好量化的一层,也是最容易做过头的一层。我通常只保留四个指标,多了没必要:
清晰度用拉普拉斯方差衡量,值越低越糊。这个指标的问题是对内容敏感——大面积天空的画面方差天然就低,会被误判成模糊。改进办法是先做一次分块计算,取各块的方差中位数而不是全图方差,能显著降低误判。
模糊与运动模糊要分开判断。静态模糊看高频能量占比,运动模糊看特定方向的梯度衰减。如果算力允许,用一个轻量的图像质量评估模型代替手工指标,效果好很多;如果算力紧张,手工指标配合分块统计也够用。
曝光异常分两端:过曝看高光像素占比,欠曝看暗部像素占比。阈值我一般设高光超过 15% 或暗部超过 40% 就标记。这里要留意夜景内容,夜景天然暗部多,直接用统一阈值会误杀一大片,所以内容类型判断要放在曝光判断之前。
抖动用相邻采样帧之间的全局位移估计,位移方差超过阈值判为抖动。这个指标计算成本相对高,我一般只在抽检阶段做,不完全铺开。
计算成本上,这四个指标都可以在降采样到 256 短边的图上算,速度快十倍而结论几乎不变。这一条经验能省掉大量算力。
4.2 美学与语义打分的实用取舍
美学打分本质上是给画面的构图、色彩、光影打印象分。开源社区有现成的轻量评分模型可以直接用,输出一个 0 到 10 的分数。这里的关键不是模型选哪个,而是阈值怎么定。
我最初按直觉设了 4.5 分,结果发现通过率只有 30%,整个数据集被砍掉一大半。后来改成先用 2000 条做分布统计,看分数的实际分布曲线,把阈值放在 30 分位数附近,通过率就合理了。这个经验值得记住:任何打分阈值都应该基于你自己数据的分布来定,而不是照搬别人的数字。
语义打分是另一回事,主要用来做内容分类和后续配比。比如把样本粗分成「人物」「风景」「物体」「动画」「文字场景」几大类。这一步不需要特别精确,用轻量视觉模型或者现成的分类器都行,重点是把明显不该进训练集的类型挑出来——比如纯色背景的测试卡、带大段文字的画面、监控视角的画面,这些对生成模型训练基本是噪声。
4.3 水印、台标与字幕的检测与处理
这是高清数据集里最容易被忽视、危害也最大的一类脏数据。水印和台标会固定在画面的某个角落,模型学会之后,生成结果上会莫名其妙冒出个角标,非常难看。
检测思路上,我推荐两条腿走路。第一条是基于位置先验的检测:统计大量样本的四个角落区域,如果某个小区域在多条样本里呈现高度相似的结构(边缘密度、颜色分布一致),那大概率是台标。第二条是基于单样本的检测:对单张图做文字区域检测,如果检测框在四条边附近呈水平或垂直排列,判为字幕或角标。
处理上,不要试图去水印(修补痕迹本身就是新的伪影),直接丢弃更划算。如果某类内容因为带台标被大量丢弃,可以退一步做区域裁剪,把角标所在的边条裁掉,剩下的画面仍可用。字幕处理要更谨慎,字幕往往压在画面下方关键区域,裁剪会破坏构图,一般建议整条丢弃,或者只在字幕出现的时段内做时间裁剪。
提示:水印检测的正样本极度稀缺,不要指望训练一个分类器。基于位置先验的统计方法在 130M 量级的样本上反而更可靠,因为样本足够多,先验很容易被统计出来。
4.4 阈值怎么定:召回率与纯度的权衡方法
所有过滤阈值都指向同一个矛盾:漏放和误杀。我的做法是建立一张「阈值-通过率-抽样质量」的对照表,每次调参都更新它:
| 阈值组合 | 通过率 | 抽检 200 条中的明显脏数据 | 结论 |
|---|---|---|---|
| 宽松组 | 78% | 34 条 | 脏数据太多,训练会受影响 |
| 中间组 | 52% | 9 条 | 可接受,作为基线 |
| 严格组 | 21% | 2 条 | 质量高了但数量损失过大 |
抽检这一步不能省。不用抽太多,200 条就能看出趋势,但要保证每次调阈值后重新抽,不要复用同一批样本。我一般在抽检时会顺手记录「误杀样本」的典型特征,攒够几十条之后往往能发现某一类内容被系统性地误伤,然后针对那一类单独放宽阈值——这就叫分类型阈值,比全局一刀切效果好得多。
5. 视频-文本对齐:Caption 的生成、重写与清洗
5.1 为什么一条好 Caption 比十条平庸 Caption 更值钱
训练视频生成模型时,文本侧的信息质量直接决定模型能不能学到「文本指令到画面变化」的映射。原始数据集里附带的描述文本质量参差不齐,有的是上传者随手写的一句话,有的是自动生成的关键词串,还有的干脆是文件名。
粗糙的文本会带来三个具体问题。一是信息冗余,caption 里塞满「高清」「完整版」「超清画质」这类无信息量的词,模型会把它们当成有意义的条件。二是描述错位,文本讲的是整段视频,而你的片段只是其中 5 秒,前者的描述可能完全不适用。三是风格漂移,有的样本是短句,有的是长篇影评,模型对不同长度的容忍度不一致。
我的经验是:宁少勿滥。与其保留 130M 条半死不活的样本,不如精挑 60M 条高质量对,后者训出来的模型明显更听话。
5.2 生成与重写的两段式策略
针对原始 caption 质量不一的问题,我用两段式处理。
第一段是判定。对每条原始文本做质量初判,规则包括:长度是否在 8 到 120 个词之间、是否包含大量无意义符号、是否与画面内容明显不符(用图文相似度粗筛)、是否包含不适合的表述。通过初判的保留,不通过的进入第二段。
第二段是重写。对需要重写的样本,用视觉语言模型基于采样帧生成新描述。这里有个设计要点:不要只喂一帧,要喂多帧并以时间顺序标注,这样生成的描述能体现运动信息,而不只是静态画面。提示词模板我一般写成这样:
以下是一个视频片段按时间顺序的 4 张关键帧。 请用一句自然语言描述这个片段: 1) 主体是什么;2) 主体在做什么动作或发生了什么变化;3) 场景环境与整体氛围。 要求:不超过 40 个词,不出现镜头术语,不出现画质评价词,不使用列表。重写完成后,还要做一次回检:把新 caption 和采样帧再算一次相似度,低于基线的丢弃。这一步能挡掉不少模型幻觉——比如画面明明是城市街道,生成结果却说成森林。
5.3 文本侧的清洗规则清单
这部分规则看起来很琐碎,但每一条都是从脏数据里总结出来的。我整理成一张可以直接照抄的表:
| 规则 | 处理方式 | 原因 |
|---|---|---|
| 去除画质类词汇(高清、超清、4K、无水印等) | 删除该词 | 无信息量且会污染条件 |
| 去除平台/来源类词串 | 删除整句 | 与内容无关 |
| 去除纯标点、纯符号、重复字符(如「!!!!!」) | 删除该字符 | 干扰分词 |
| 连续重复同一短语超过 2 次 | 截断到第一次 | 常见的自动生成缺陷 |
| 长度超过 120 词 | 截断或重写 | 长文本与短视频不匹配 |
| 只含 1 到 2 个词 | 重新生成 | 信息量不足 |
| 与画面相似度低于基线 | 丢弃或重写 | 图文错位 |
| 含时间/日期/人名等实体 | 保留但打标 | 便于后续配比控制 |
清洗完成后的文本,我还会做一次长度分布统计。正常的分布应该集中在 15 到 35 个词之间,如果出现双峰(一堆极短、一堆极长),说明某一类样本的处理策略有问题,需要回去查。
6. 解码重采样与分片打包的工程实现
6.1 解码后端选择与关键帧对齐的收益
到了这一步,样本已经洗干净了,剩下的是把视频真正解出来、缩放到目标分辨率、重新编码。解码后端的选择会直接影响整体吞吐。
纯 CPU 的软件解码最稳,兼容性最好,但对高分辨率高码率素材速度一般。带硬件加速的解码在支持的格式上能快好几倍,但对某些编码参数会有兼容问题,遇到异常要能自动降级回软件解码。我的做法是主用硬件、兜底软件,在任务级别记录降级次数,降级率异常升高的分片单独拎出来排查。
关键帧对齐的收益在这一步体现得最明显。因为前一阶段已经把片段的起点吸附到了关键帧,这里可以直接 seek 到指定位置精确解码,不需要回溯。实测下来,相比「seek 到任意位置再往前找关键帧」的做法,解码速度能提升 20% 到 40%,而且首帧质量稳定。
6.2 分辨率、帧率、时长的统一策略与参数计算
训练侧的输入要求通常是固定的,所以要做统一。但统一不等于硬拉伸,这是最容易做错的地方。
分辨率的处理顺序建议是:先裁黑边(用边缘检测找出实际画面区域),再做等比缩放,最后填充到目标尺寸。填充用镜像填充或者模糊填充,比纯黑边效果好——纯黑边会让模型学到「画面四周有黑框」这个无意义先验。
宽高比差异大的素材(比如竖屏转横屏),建议直接丢弃而不是强行转换,因为裁剪会丢一半信息,填充会引入大片伪影,两种都不划算。
帧率的处理要避免粗暴丢帧。目标 16fps、原始 30fps,如果用「每隔一帧取一帧」的做法,运动会被切成顿挫的跳跃。正确做法是按时间戳重采样,或者采用帧混合。我一般选择按时间戳均匀取帧,这样即使原始帧率不规整,输出也是时间上均匀的。
时长方面,把片段统一到固定帧数(比如 49 帧或 81 帧),短的用末帧重复补齐或者循环补齐,长的从中间截取。补齐策略要分类型:静态场景适合末帧重复,动态场景适合循环。
重编码的参数我一般这样设:CRF 在 18 到 23 之间,预设选偏速度的一档,关键帧间隔设成和片段长度对齐(每个片段一个 GOP),这样训练侧读取时可以完全避免跨 GOP 解码。
6.3 分片打包格式与索引文件设计
打包这一步决定了训练时的数据加载效率。我的经验是小文件是万恶之源。130M 条样本如果每条存一个视频文件,文件系统元数据操作会成为瓶颈,训练时数据加载器大量时间耗在 open/close 上。
正确做法是打包成大的分片文件。常用的方案有基于 tar 的分片格式,每个分片几百 MB 到 2GB,内含若干样本;配合一个单独的索引文件,记录每个样本在分片内的偏移量。这样加载时先读索引,再按偏移量直接读取,IO 效率高得多。
索引文件里至少要包含:sample_id、所属分片文件名、分片内偏移、样本长度、caption 文本、以及各阶段打分的摘要。索引本身用列式格式存,方便训练时按条件筛选采样。
还有一个小细节:分片内样本要打散。不要在打包时按原始顺序排列,否则每个分片里的内容类型高度集中,训练时一个 batch 里全是同类样本,梯度方向单一。我一般会在打包前做一次全局打散,让每个分片都是内容类型的均匀混合。
6.4 训练侧加载器的配合与预取设置
预处理做完了,还要确认训练侧能吃下去。这一步经常被忽略,结果预处理做得很漂亮,训练时数据加载却成了瓶颈。
关键点有三个。一是预取的进程数和线程数,要和存储的并发能力匹配,设太少读不满带宽,设太多反而互相抢锁。我一般从 4 个进程起步,观察 GPU 利用率,如果利用率掉到 90% 以下就加。二是解码尽量放在 CPU 侧异步做,不要让主训练循环等解码。三是缓存策略,对高频访问的小索引文件做常驻内存,对分片文件做惰性加载加有限缓存。这几条做下来,通常能把数据加载的等待时间压到训练步耗时的 5% 以内。
7. 常见问题与排查速查
7.1 排查表:从现象到根因
| 现象 | 可能根因 | 排查动作 |
|---|---|---|
| 整批任务集体失败在同一位置 | 分片文件损坏或存储读取中断 | 校验该分片校验和,重新拉取 |
| 片段首帧发黑或发绿 | seek 位置不在关键帧上 | 检查时间戳换算与关键帧吸附逻辑 |
| 输出片段时长普遍偏短 | 容器 duration 不准确 | 改用实际解码帧数计算时长 |
| 通过率突然大幅下降 | 阈值被误改或新分片内容类型不同 | 比对阈值配置,抽检 200 条看分布 |
| 重编码后画质明显变糊 | CRF 设太大或缩放算法太激进 | 降低 CRF,改用质量更好的缩放算法 |
| Caption 与画面对不上 | 采样帧与文本时间错位 | 核对帧时间戳映射字段 |
| 训练时 GPU 利用率周期性掉底 | 数据加载瓶颈或分片过小 | 加大分片体积,增加预取进程 |
| 部分样本重编码后体积暴涨 | 场景复杂度高且 CRF 过小 | 对高复杂度场景单独设参数上限 |
| 显存不足 | 打分模型 batch 设太大 | 降 batch,或改为单帧串行推理 |
| 打包后总数对不上 | 打散或去重环节丢了样本 | 逐阶段核对计数,找出丢失点 |
7.2 三条从实际踩坑中总结的硬经验
第一条:给每个阶段都加「计数守恒」校验。阶段一的输入条数,减去被过滤的条数,必须等于阶段二的输入条数。这个校验看着笨,但它是发现「静默丢样本」的唯一可靠手段。我曾经遇到过一次,某台机器磁盘写满导致部分中间文件被截断,没有计数校验,这个问题拖了三天才被发现。
第二条:抽取固定的一批「金标样本」贯穿全流程。挑 500 条有代表性的样本(清晰的和模糊的、正常和异常的、单镜头和多镜头的各来一些),人工标注它们应该在每个阶段的结果。每次改配置,先跑这 500 条,看结果是否与预期一致。这比目测大样本快得多,也更能发现回归问题。
第三条:所有随机性都要有种子。抽帧选择、打散顺序、采样权重,凡是有随机的地方都要能通过种子复现。不然某次训练效果不对,你连「是哪批数据不一样」都查不出来。
8. 一些踩过的坑和最后的经验
还有一个坑值得单独拎出来说:日期和来源信息要不要保留。很多人觉得这些是噪声,直接清掉。我的经验是保留但打标,理由是可以做数据配比控制。比如某批来源的样本画质普遍偏好但内容单一,如果不打标,它会静默地拉偏整个数据集的分布;打了标,你就能精确控制它的采样权重。
另一个反复出现的问题是长时间运行的内存泄漏。打分阶段用视觉模型做推理,如果框架版本和驱动不完全匹配,很容易出现每处理几百条显存或内存就涨一点的情况,跑几十万条之后进程被系统杀掉。解决办法是给每个工作进程设一个处理条数上限,达到就优雅退出、由调度器重新拉起,用「短命进程」换稳定性。这个技巧在长时间批处理里非常好用,比花时间追查泄漏源头性价比高得多。
关于阈值,我个人最深的体会是不要追求一次调到位。我前后调了六七轮,每轮都只看一个指标的变化,比如「泛化到下游任务的效果」。一次改太多变量,你永远不知道是哪个改动起了作用。慢一点,但结论可靠。
最后分享一个很实用的小做法:给每个分片生成一张可视化缩略图拼版。把一个分片里随机 36 个样本的中间帧拼成一张 6x6 的图,几十秒就能扫一遍,脏数据一眼就能看出来。这个习惯帮我抓到过好几次「指标全部正常但画面明显有问题」的隐蔽缺陷,比如整批样本都带有细微的水平条纹伪影,单看指标完全看不出异常。