外观
DL 设计原则
一句话定义:深度学习的设计原则,是把"改哪里、怎么验证、怎么归因"这些方法论问题固定成习惯,让你每个实验的投入产出比最高——它们比任何单一模型技巧都更决定你能力的上限。
这些原则散落在实践栏目各篇中,本文把它们提炼成可执行的八条并相互贯通。
一、先跑通简单基线,再谈升级
原则:任何任务,先做一个"一定能跑、结果平庸"的基线,再逐项升级。
最简单可靠的基线包括:零规则(全部预测多数类)、线性模型(逻辑回归)、浅层 MLP、官方预训练模型的零样本/直接推理。基线的价值:
- 给所有后续改进提供"参考系":如果增强只比基线高 1 个点,说明问题不在增强。
- 验证管道正确性:基线跑通 = 数据、训练、评估链路通畅。
- 快速排除"任务本身不可解":如果最简单的模型在某子集上都能到 90%,说明任务可学。
基线是第一张拼图
从零构建项目的第一步就该是基线——渐进式教程的 v1 就是教科书式的基线。
二、数据优先:先看数据,再选模型
原则:写模型之前,先花时间看数据、摸数据的"脾气"。
具体动作:
- 画样本:分类任务画 20–50 张图/文本片段,肉眼核对标签与内容。
- 查分布:类别分布、数值范围、缺失值、异常样本、重复样本。
- 想"人怎么解":这个任务人类靠什么线索做?这些线索模型能看见吗(分辨率、上下文长度、特征工程)?
- 定评估:在选模型之前,先定义清楚"什么样算好"(见评估实践)。
一个典型反例:模型死活学不会某类,折腾了一周架构,最后发现是那类的标注标签错了一半。数据问题用架构永远补不回来。数据工程的系统方法见数据与数据工程。
三、模块化与配置化实验
原则:把实验"参数化",让每一次实验都是一次配置变更,而不是一段代码改写。
两个层次:
- 代码模块化:数据、模型、训练、评估、可视化各自独立模块(结构模板见从零构建一个深度学习项目)。改一处不波及其余。
- 超参配置化:所有超参数进
config.yaml或命令行参数,代码里不硬编码:
yaml
# config.yaml
data:
dataset: cifar10
batch_size: 128
augmentation: [random_crop, hflip]
model:
arch: resnet18
pretrained: true
train:
optimizer: adam
lr: 1e-3
scheduler: cosine_with_warmup
epochs: 30
seed: 42python
import yaml, argparse
cfg = yaml.safe_load(open("config.yaml"))
# ... 用 cfg["train"]["lr"] 等读取配置化带来三个直接好处:实验可复制(改一行 yaml 就是新实验)、可追踪(日志里记录 config 全文)、可自动搜索(脚本批量替换配置做网格/贝叶斯搜索)。
四、实验纪律:一次只变一个变量
原则:一个实验只改变一个因素,否则结果无法归因。
这听起来简单,做起来极难——因为诱惑很大:"这次顺便把学习率也调了"。结果:模型好了不知道为什么好,坏了不知道哪个改动导致。
正确流程:
- 改前记录基线(指标、配置、种子)。
- 只动一个变量,其余全锁死(包括随机种子,除非你专门在研究种子)。
- 跑完记录结果,与基线对比。
- 重复,而不是并行乱改。
变量锁定清单
随机种子、数据版本、代码 commit、框架版本、GPU 型号——都是"变量"。一次只变一个,意味着这些也要冻结。复现纪律的完整清单见训练配方与调参。
五、可复现性:种子、版本、文档
原则:实验的产出不是"模型权重",而是"可复现的证据"。
- 种子固定:
torch.manual_seed+numpy+random+cudnn.deterministic(代码见训练配方与调参)。 - 版本固定:锁
requirements.txt(精确版本),记录 Python/CUDA/PyTorch 版本。 - 文档化:README 写明 安装、复现命令、数据来源、超参配置;实验结果(曲线、指标表)落盘。
- 黄金复现:上线/交稿前,同一种子从零再跑一次,确认数字对得上。
可复现性的价值在两周后立刻体现:你忘了当初怎么调出来的,README 和 config 能把你拉回来。
六、渐进复杂度:MLP → CNN → 预训练
原则:模型的复杂度应与"任务证据"同步增长,而不是一开始就上最豪华配置。
标准爬坡路径:
逻辑回归/MLP → 小型 CNN/浅 Transformer → 深度架构 → 预训练迁移 → 更大规模/搜索
验证管道 验证归纳偏置 验证规模效应 利用外部知识 边际收益递减区每一级停留的判断标准:当前层级的验证指标是否已经明显瓶颈(训练 loss 已经很低、验证不再提升、错误集中在某类)。层级跳跃的理由应该来自证据,而不是"大家都用 ResNet"。
这条原则在渐进式教程:三版跑起来里演了一遍:MLP 40% → CNN 78% → 预训练 90%+。它同时避免两个极端:过度设计(用百层模型解决 5000 张图的任务)与过早放弃(MLP 效果差就认为任务不可解)。
七、失败时先怀疑自己的代码,而非模型
原则:当结果与预期不符,默认假设"我的代码/数据有 bug",而不是"模型不行"或"任务太难"。
为什么这条反直觉原则如此有效:
- 模型设计缺陷通常表现为"可解释的差"(能分析出为何差),而代码 bug 通常表现为"莫名其妙的差"。
- 调试成本不对称:检查代码 30 分钟 vs 误以为模型差而重写模型一周。
- 深度学习框架太"宽容":维度不匹配会报错,但语义 bug(标签错位、mask 用错、归一化统计量错)完全不报错,结果却全错。
落地方法:先用调试与诊断的"小样本过拟合到 100%"排除管道,再做梯度检查、消融实验,最后才怀疑模型设计本身。当模型差得"没有道理"时,90% 是代码问题(完整反模式清单见常见陷阱与反模式)。
八、可扩展性思维:现在写的每一行都可能被复用
原则:写代码时默认"这个脚本会被别人(包括三个月后的你)运行、修改、扩展"。
可落地的习惯:
- 函数化而非脚本化:训练循环、评估、可视化写成可复用函数,而不是散落的顶层代码。
- 设备无关:
device从配置读取,代码同时跑 CPU/GPU。 - 显式而非隐式:形状、命名、注释写清楚;魔法数字提取为常量。
- 面向"下一个数据集"设计:
data.py的数据集接口与具体数据集解耦,换数据集只改配置。 - 早期抽公共层:多任务共用的训练/评估骨架尽早抽象(但要避免过度抽象——三条原则会打架)。
可扩展性不是给未来写一堆用不上的抽象,而是让迭代成本随项目增长保持接近线性——这恰好是作品集项目里评估代码质量的核心标准。
权衡与边界:原则之间会打架
这八条原则不是无脑全遵:
- 渐进复杂度 vs 时间预算:比赛/交付有 deadline 时,"直接上预训练 + 已知配方"往往是更优策略,基线放在心里而不是跑出来。
- 一次只变一个变量 vs 实验预算:真正的超参搜索(网格/贝叶斯)本质是"多变量并行",但它的归因靠搜索方法保证,而不是单变量纪律。
- 可扩展性 vs 快速迭代:科研探索期代码越简单越好,过度设计会让每次修改变慢。
判断标准只有一个:当前阶段的目标是"最快得到可信答案"。探索期偏简单直接,交付期偏工程规范。每个阶段结束,问自己一句:下一步换个人(或两个月后的自己)接手,这套代码和记录够不够用?
延伸阅读
- 渐进式教程:三版跑起来——渐进复杂度与基线的完整示范
- 调试与诊断——"先怀疑自己代码"的执行手册
- 训练配方与调参——种子、版本、实验记录的具体清单
- 从零构建一个深度学习项目——模块化结构的模板
- 常见陷阱与反模式——违反这些原则的典型症状
- 评估实践——基线之上,如何定义"好"