🏠 总目录📚 本教程 06 · 小数据这一关 ← →
📑 本页目录(点开跳转)

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 折

✅ 检查点

  1. 无标注数据和有标注数据在这个领域的成本差多少?「一条标注」实际意味着什么?
  2. 20 条标签时,冻结表示把 R² 从多少提到了多少?那个表示是怎么来的、用了多少标签?
  3. 到 3000 条标签时两种方案的差距是多少?这说明预训练买到的是什么?
  4. 为什么小数据上「冻结 + 小头」比「全量微调」更合适?判据是什么?
  5. 数据增强在这里为什么不能乱加?举一个合法的和一个不合法的。
  6. 小数据上最容易骗到自己的两件事是什么?它们的共同本质是什么?
👀 答案
  1. 无标注接近 0 成本、亿级;有标注一条就是一次湿实验,几百到几千元、几天到几周,量级常常只有两位数到三位数。
  2. 从 0.374 提到 0.696(差不多翻倍)。那 16 维表示是从 4000 条无标签序列用 PCA 抽的,一个标签都没用。
  3. 0.878 vs 0.884,差 0.006,基本收敛(20 条时的差距是 0.32)。⭐ 说明预训练买的是速度不是上限 —— 和 01 章量到的「领域知识 ≈ 一倍数据」是同一件事。
  4. 因为可训练参数的数量级要和样本量匹配。冻结方案只解一个 16 维岭回归,原始方案是 600 维 —— 20 个样本对 600 个参数是在背答案。全量微调有几百万到几亿参数,几十条标签下几乎必然过拟合。
  5. 因为增强必须保持标签不变。⭐ 合法:把分子整体旋转(性质不变)。⚠️ 不合法:随便换掉序列里一个残基(性质可能全变)。什么变换安全取决于问题的对称性,见 05 章。
  6. ①指标好看是因为测试集也小,试过多种划分再挑一个好看的,那个数已经被污染 ②用同一批数据既选模型又报成绩,在 5 个模型里挑测试集最好的那个,分数必然虚高。⭐ 共同本质:测试集每被看一次,它作为尺子的价值就掉一点。

🛑 可以停在这里

到这里你手上有小数据场景最有效的那一招(冻结 + 小头)和它的判据(参数量级配得上样本量), 外加四件该做的事和两个该躲的坑。这些已经够你开工了。

⚠️ 什么时候看下一页:你已经把现有的几十条数据榨干了, 开始想「那下一批实验该做什么」—— 那正是下一章。

⚡ 走神救援

先记住这几件事

下一节 👉 07-评测的坑.md

打卡记录保存在你的浏览器里,首页能看到总进度