📑 本页目录(点开跳转)
06 · 小数据这一关
⏱ 24 分钟 | ⭐ 20 条标注时,冻结的预训练表示把 R² 从 0.374 抬到 0.696
🎯 一句话
在这里,标注贵得离谱,而无标注数据几乎免费。 一条标注意味着一次真实的实验 —— 几天、几千块;而序列数据库里躺着几亿条没有标签的序列。 ⭐ 整章只讲一件事:怎么把便宜的那一半,换成贵的那一半。
💸 一、先认清这个比例
普通 ML 里「数据多」通常意味着标注也多。这里完全不是:
| 无标注数据 | 有标注数据 | |
|---|---|---|
| 来源 | 测序、公开数据库 | ⚠️ 一次湿实验 |
| 单条成本 | 接近 0 | 几百到几千元,几天到几周 |
| 量级 | 亿级 | ⚠️ 常常是两位数到三位数 |
💀 两位数不是夸张。 一个新酶的定向进化项目,第一轮拿到手的可能就是 几十个突变体的活性数据。
拿这个去训一个几百维的模型,n < p —— 参数比样本还多。
⭐ 所以这一章的问题不是「怎么把模型调好」,而是: 在只有几十条标签的情况下,怎么让那几亿条无标签数据也出力。
🧊 二、把这件事跑出来
最有效的一招是冻结:先用无标注数据学出一个表示, 然后只在它上面训一个很小的头。下面这段把它和「直接用原始特征」对照:
import numpy as np
AA, L, K = 20, 30, 5 # 20 种氨基酸,长 30,背后 5 维隐变量
def corpus(n, seed):
"""序列由一个 5 维隐变量 z 生成 —— 位置之间因此不独立("共进化")。
标签只由 z 决定,序列是 z 的一个带噪观测。"""
r = np.random.default_rng(seed)
B = np.random.default_rng(0).normal(size=(K, L, AA)) # 生成规则全局固定
z = r.normal(size=(n, K))
logits = np.einsum("nk,klv->nlv", z, B)
p = np.exp(logits - logits.max(-1, keepdims=True))
p /= p.sum(-1, keepdims=True)
seqs = np.array([[r.choice(AA, p=p[i, l]) for l in range(L)] for i in range(n)])
X = np.zeros((n, L * AA))
X[np.arange(n)[:, None], np.arange(L) * AA + seqs] = 1.0
w = np.random.default_rng(1).normal(size=K)
return X, z @ w + r.normal(0, 0.3, n)
# ① 无标注大池子 → PCA 当"预训练表示"。⭐ 这一步不用任何标签
Xpool, _ = corpus(4000, 100)
mu = Xpool.mean(0)
_, _, Vt = np.linalg.svd(Xpool - mu, full_matrices=False)
P = Vt[:16].T # 取前 16 个主成分
Xte, yte = corpus(1000, 999)
Xbig, ybig = corpus(3000, 7) # ② 训练池,下面取前 n 条
def r2(Xtr, ytr, Xte_, lam=1.0):
b = np.linalg.solve(Xtr.T @ Xtr + lam * np.eye(Xtr.shape[1]), Xtr.T @ (ytr - ytr.mean()))
pred = Xte_ @ b + ytr.mean()
return 1 - ((yte - pred) ** 2).sum() / ((yte - yte.mean()) ** 2).sum()
print("%-8s %16s %20s" % ("labels", "raw one-hot", "frozen 16-dim"))
for n in [20, 50, 100, 300, 1000, 3000]:
Xtr, ytr = Xbig[:n], ybig[:n] # ⭐ 嵌套子集:大池子的前 n 条
a = r2(Xtr, ytr, Xte)
b = r2((Xtr - mu) @ P, ytr, (Xte - mu) @ P)
print("%-8d %16.3f %20.3f" % (n, a, b))
真实输出:
labels raw one-hot frozen 16-dim
20 0.374 0.696
50 0.728 0.785
100 0.797 0.849
300 0.783 0.878
1000 0.815 0.884
3000 0.878 0.884
⭐ 20 条标注时:0.374 → 0.696,差不多翻倍。 而那 16 维表示是从 4000 条无标签序列里 用 PCA 抽出来的 —— 一个标签都没用。
⭐⭐ 到 3000 条时两者收敛(0.878 vs 0.884)。这和 01 章量到的是同一件事: 预训练买的是速度,不是上限。 差距从 +0.32 一路收窄到 +0.006。
⚠️ 一个我没有藏起来的毛病:
raw one-hot那一列在 300 那格回落了 (0.797 → 0.783)。那是固定lam=1在不同样本量下正则强度不匹配造成的采样噪声, 不是什么规律。⭐ 该看的是两端:20 条时差 0.32,3000 条时差 0.006。
🎛️ 三、为什么冻结比微调更适合这里
「用预训练模型」有两种用法,⚠️ 在小数据上它们的表现相差很远:
| 冻结 + 小头 | 全量微调 | |
|---|---|---|
| 要拟合的参数 | ⭐ 十几到几百个 | 几百万到几亿 |
| 几十条标签时 | 稳 | 💀 几乎必然过拟合 |
| 需要的算力 | 一台笔记本 | GPU |
| 什么时候该换 | —— | 标签上到几千条以后 |
⭐ 判据很朴素:可训练参数的数量级要和样本量匹配。 上面代码里冻结方案要解的是一个 16 维的岭回归,而原始方案是 600 维 —— 20 个样本对 600 个参数,那是在背答案。
⚠️ 中间档也存在(只解冻最后几层、或用 LoRA 那类低秩适配), 但在几十条标签的量级上,先把冻结这一档做扎实,它是最难做错的一个。
🧰 四、另外四件小数据该做的事
① 别用单次划分,用交叉验证。 50 条数据切出 10 条测试集,那 10 条换一批结论就变了。 ⭐ 用 K 折把每条数据都当一次测试 —— 基本功在 机器学习与深度学习基础 05 · 评估与过拟合。 ⚠️ 但这里的 K 折必须按同源家族分组,理由见 07 章。
② 报区间,不报单点。 50 条数据上的 R² 本身就带着很大的不确定性。⭐ 只报一个数会让你和同事都高估它。
③ 数据增强要尊重对称性。 ⚠️ 不能乱加 —— 把分子旋转一下当新样本是合法的(性质不变), 把序列里一个残基随便换掉当新样本通常不合法(性质可能全变)。 ⭐ 什么变换是安全的,取决于你的问题有什么对称性,那是 05 章的正题。
④ 主动选下一批标注哪些。 既然每条标签都这么贵,就不该随机挑。站内已经有一整章讲这件事: 数据这一关 12 · 标注预算与主动学习。 ⭐ 到 08 章会把它推到更远的地方 —— 那里连样本本身都是你造出来的。
⚠️ 五、小数据上最容易骗到自己的两件事
① 指标好看是因为测试集也小。 20 条测试集上的 R² 波动极大,挑一个好看的划分太容易了。 💀 如果你试过好几种划分才选定一个,那这个数字已经被污染了。
② 用同一批数据既选模型又报成绩。 在 5 个模型里挑测试集上最好的那个,报出来的分数必然虚高 —— 你已经把测试集用作选择依据了。⭐ 小数据上这件事的代价格外大,因为噪声大、可挑的空间也大。
⭐ 这两条本质是同一件事:测试集每被看一次,它作为尺子的价值就掉一点。 数据这一关 13 · 评测集也是数据 有完整的展开, 包括「多大才够」怎么算。
🔗 这一章连到哪里
| 去哪 | 为什么 |
|---|---|
| 01 · 第一天 | ⭐ 同一个结论的另一半:那一章量的是「领域知识 ≈ 一倍数据」,这一章量的是「预训练表示 ≈ 更多数据」。两者都在 3000 条时收敛 —— 先验买速度不买上限 |
| 07 · 评测的坑 | ⚠️ 这一章说「用交叉验证」,而那一章说明为什么这里的 K 折必须按家族分组。不看那一章,这里的 K 折会给你一个虚高的数 |
| 05 · 等变与不变 | 第 4 节第三条:哪些数据增强是安全的,取决于问题的对称性 |
| 数据这一关 12 · 标注预算与主动学习 | ⭐ 标签贵到这个程度时,「下一批标注哪些」本身就是个要优化的问题。那一章讲不确定性采样和批量多样性 |
| 数据这一关 13 · 评测集也是数据 | 第五节两个自欺的完整版,以及「测试集多大才够」的置信区间算法 |
| 机器学习与深度学习基础 05 · 评估与过拟合 | 交叉验证和过拟合的基本功。⚠️ 那里的随机 K 折在这个板块要改成分组 K 折 |
✅ 检查点
- 无标注数据和有标注数据在这个领域的成本差多少?「一条标注」实际意味着什么?
- 20 条标签时,冻结表示把 R² 从多少提到了多少?那个表示是怎么来的、用了多少标签?
- 到 3000 条标签时两种方案的差距是多少?这说明预训练买到的是什么?
- 为什么小数据上「冻结 + 小头」比「全量微调」更合适?判据是什么?
- 数据增强在这里为什么不能乱加?举一个合法的和一个不合法的。
- 小数据上最容易骗到自己的两件事是什么?它们的共同本质是什么?
👀 答案
- 无标注接近 0 成本、亿级;有标注一条就是一次湿实验,几百到几千元、几天到几周,量级常常只有两位数到三位数。
- 从 0.374 提到 0.696(差不多翻倍)。那 16 维表示是从 4000 条无标签序列用 PCA 抽的,一个标签都没用。
- 0.878 vs 0.884,差 0.006,基本收敛(20 条时的差距是 0.32)。⭐ 说明预训练买的是速度不是上限 —— 和 01 章量到的「领域知识 ≈ 一倍数据」是同一件事。
- 因为可训练参数的数量级要和样本量匹配。冻结方案只解一个 16 维岭回归,原始方案是 600 维 —— 20 个样本对 600 个参数是在背答案。全量微调有几百万到几亿参数,几十条标签下几乎必然过拟合。
- 因为增强必须保持标签不变。⭐ 合法:把分子整体旋转(性质不变)。⚠️ 不合法:随便换掉序列里一个残基(性质可能全变)。什么变换安全取决于问题的对称性,见 05 章。
- ①指标好看是因为测试集也小,试过多种划分再挑一个好看的,那个数已经被污染 ②用同一批数据既选模型又报成绩,在 5 个模型里挑测试集最好的那个,分数必然虚高。⭐ 共同本质:测试集每被看一次,它作为尺子的价值就掉一点。
🛑 可以停在这里
到这里你手上有小数据场景最有效的那一招(冻结 + 小头)和它的判据(参数量级配得上样本量), 外加四件该做的事和两个该躲的坑。这些已经够你开工了。
⚠️ 什么时候看下一页:你已经把现有的几十条数据榨干了, 开始想「那下一批实验该做什么」—— 那正是下一章。
⚡ 走神救援
先记住这几件事
- 标签少时,先用冻结表示和小模型建立基线,再判断是否需要更多可训练参数。
- 验证与测试都要考虑同源分组,报告不确定性而不只报一个分数。
- 新增标注、预训练与合法的数据增强解决不同问题,不能用反复挑测试集代替验证。
下一节 👉 07-评测的坑.md