3. 扫描结果的数据结构与组织

多维参数扫描结束后,我们手里往往是一堆“散装”的结果:每个参数组合对应一个输出值,可能还附带若干中间量、时间戳、版本号。如果这些结果只是零散地打印在日志里,或者以无结构的文本堆叠,后续的可视化就无从下手。本节要解决的核心问题,是把“参数组合 → 结果”这一映射关系,组织成规整、可索引、可扩展的结构化数据,让可视化层能够按任意维度切片、聚合和对比。

先明确三个基本概念。维度(dimension)是扫描中独立变化的参数轴,例如学习率、批大小、网络层数;每个维度有若干取值,所有维度取值的笛卡尔积构成扫描空间。度量(measure)是在每个参数组合上观测到的数值结果,例如准确率、推理延迟、损失值;度量是可视化的“高度”或“颜色”。元数据(metadata)则是描述这次扫描本身的附属信息,例如扫描时间、代码版本、随机种子、硬件型号、参数单位。三者混在一起存储,是后续分析混乱的根源;分开组织,才能让可视化代码只关心“维度怎么切、度量怎么画”。

判断一个字段属于哪一类,可以问两个问题:它是否在扫描中被主动改变?是则为维度。它是否是每个组合上被测量出来的数值?是则为度量。它是否对所有组合都相同、只用于解释结果来源?是则为元数据。

从“长表”开始:最通用的组织形态

最直接也最通用的结构是长表(long format):每一行对应一个参数组合的一次观测,列包括各维度字段、度量字段和必要的元数据字段。以一次三参数扫描为例,假设学习率取 {0.001, 0.01, 0.1},批大小取 {32, 64},层数取 {4, 8},共 3×2×2=12 个组合,每个组合重复 3 次取平均。长表大致如下:

lrbatchlayersrepeataccuracylatency_msrun_id
0.00132410.81218.4r001
0.00132420.80918.1r001
0.00132430.81518.6r001
0.0164810.88741.2r001
…………………

长表的优点是“一行一观测”,天然适合追加写入、按条件过滤和分组聚合。重复实验用 repeat 列区分,而不是把三次结果塞进三个列名,这样重复次数变化时表结构不变。缺点是同一组合的多行存在冗余维度值,存储上略浪费,但换来的是查询和透视的极大灵活性。实际工程中,长表通常作为“事实表”落盘为 CSV 或 Parquet,再由分析脚本透视成宽表或直接喂给绘图库。

多维数组:适合稠密网格与快速切片

当所有维度都是离散且取值规整的网格时,多维数组是更紧凑的选择。把维度按固定顺序排列,例如 (lr, batch, layers),则度量可以存成一个形状为 (3, 2, 2) 的三维数组,数组下标与维度取值一一对应。此时需要额外维护一份“轴标签”映射,例如 axis0 = [0.001, 0.01, 0.1],否则数组本身无法自解释。多维数组的优势在于切片极快:取某一层数下的所有学习率与批大小组合,就是一次简单的索引操作;热力图、切片矩阵图可以直接消费这种结构。代价是它要求网格完整——若某个组合缺失或取值不规则,就得用哨兵值填充,容易与真实度量混淆。

扫描结果的三层数据组织 维度 dimension lr = [0.001, 0.01, 0.1] batch = [32, 64] layers = [4, 8] 扫描空间 = 3×2×2 = 12 组合 度量 measure accuracy(准确率) latency_ms(延迟) loss(损失) 每个组合上被测量的数值 元数据 metadata run_id / 时间戳 代码版本 / 随机种子 硬件型号 / 参数单位 对所有组合相同,解释来源 长表(事实表):一行一观测 lr batch layers repeat accuracy latency 0.001 32 4 1 0.812 18.4 0.01 64 8 1 0.887 41.2 透视 / 聚合 → 可视化

数据库与索引:让“按维度切片”成为一次查询

当扫描规模上升到数千甚至数万组合、且需要反复按不同维度组合查询时,把长表放进关系型或列式数据库更合适。典型做法是建一张事实表,维度字段和度量字段各占一列,并在常用过滤维度上建索引或分区。例如按 run_id 分区、在 (lr, batch) 上建复合索引,那么“取批大小为 64 的所有记录画学习率-准确率曲线”就是一次带 WHERE 的查询,而不是全表扫描。列式存储(如 Parquet)对“只读少数度量列”的聚合尤其友好,因为同列数据连续存放,压缩率高、扫描量小。这里的关键设计决策是:把维度、度量、元数据分列存放,而不是把整个参数组合序列化成一个字符串塞进单列——后者会让任何按维度过滤的查询退化为字符串解析。

组织时容易踩的坑

  • 维度取值类型不一致。学习率写成字符串 "0.01" 与数值 0.01 混存,排序和坐标轴刻度就会错乱。落盘前统一类型,并在元数据中记录单位。
  • 缺失组合没有显式标记。网格中某组合因失败未产出结果时,用 NaN 或专门的 status 字段标记,而不是留空行——空行在透视时会被静默丢弃,导致热力图出现“假空洞”。
  • 重复实验的聚合口径不明。三次重复是取均值、中位数还是保留全部?应在元数据中写明聚合方式,长表保留原始重复行,宽表再按声明口径聚合,避免“同一张图两种口径”。
  • 元数据散落在文件名里。把版本号、种子写进文件名(如 run_r001_seed42.csv)看似省事,但无法被查询和关联。应作为列或独立元数据表存储。

把上述结构落地后,可视化阶段的工作会变得非常轻:热力图取两个维度做透视、折线图固定其余维度取切片、平行坐标直接消费长表的维度列与度量列。数据结构规整,图才能规整;这一步做扎实,后面所有图形都只是对同一份结构化数据的不同的“看的方式”。