🏠 总目录📚 本教程 01 · 第一天:一列领域知识值多少数据 ← →
📑 本页目录(点开跳转)

01 · 第一天:一列领域知识值多少条数据

⏱ 24 分钟 | ⭐ 同一份数据,加一列先验知识 ≈ 把训练集翻一倍


🎯 一句话

科学问题里,「你知道什么」和「你有多少数据」是可以互换的。 这一章用一个能在你机器上跑起来的例子把这个兑换率量出来: 一列领域知识特征,在数据少的时候顶得上一倍数据;而数据一多,它就白给了。


🧪 一、先跑起来

不用装任何东西,只要 numpy。任务是最典型的那一类: 给一条序列,预测它的一个数值性质(稳定性、活性、溶解度都是这个形状)。

我们造一批长度 30 的序列,性质由两件事决定:平均疏水性, 加上一个「位置 4 是酸性 且 位置 21 是碱性」时出现的加成 —— 后者是个成对相互作用。

import numpy as np

AA = "ACDEFGHIKLMNPQRSTVWY"          # 20 种氨基酸
L = 30                                # 序列长度
# 疏水性标度:真实世界里这是【查表查出来的先验知识】,不是从数据里学的
HYDRO = dict(zip(AA, [1.8, 2.5, -3.5, -3.5, 2.8, -0.4, -3.2, 4.5, -3.9, 3.8,
                      1.9, -3.5, -1.6, -3.5, -4.5, -0.8, -0.7, 4.2, -0.9, -1.3]))
idx = {c: i for i, c in enumerate(AA)}


def make(n, seed):
    """造 n 条序列。性质 = 2 x 平均疏水性 + 一个"盐桥"加成,再加噪声。"""
    r = np.random.default_rng(seed)
    seqs = ["".join(r.choice(list(AA), L)) for _ in range(n)]

    def truth(s):
        h = np.mean([HYDRO[c] for c in s])
        pair = 3.0 if (s[4] in "DE" and s[21] in "KR") else 0.0   # ⭐ 位置 4 和 21 的相互作用
        return 2.0 * h + pair

    y = np.array([truth(s) for s in seqs]) + r.normal(0, 0.5, n)

    X1 = np.zeros((n, L * 20))                                     # ① 纯 one-hot
    for k, s in enumerate(seqs):
        for p, c in enumerate(s):
            X1[k, p * 20 + idx[c]] = 1.0
    h = np.array([[np.mean([HYDRO[c] for c in s])] for s in seqs])
    return X1, np.hstack([X1, h]), y                               # ② 多一列疏水性


X1te, X2te, yte = make(400, 999)                                   # 固定测试集


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()))
    p = Xte @ b + ytr.mean()
    return 1 - ((yte - p) ** 2).sum() / ((yte - yte.mean()) ** 2).sum()


print("%-10s %14s %20s" % ("train rows", "one-hot", "one-hot + hydro"))
for n in [450, 900, 1800, 3600]:
    a1, a2, ay = make(n, 7)
    print("%-10d %14.3f %20.3f" % (n, r2(a1, ay, X1te), r2(a2, ay, X2te)))

真实输出:

train rows        one-hot      one-hot + hydro
450                 0.319                0.515
900                 0.535                0.537
1800                0.705                0.705
3600                0.748                0.748
0.30.40.50.60.7测试 R²45090018003600训练条数(每格一次翻倍)差 0.196⭐ 加一列(450) ≈ 翻倍(900)两条重合,优势归零one-hot + 一列疏水性(查表得来的先验)纯 one-hot,什么先验都不给
看两条线什么时候合上:起点差 0.196,而绿色虚线说明这一列先验值一次数据翻倍;到 1800 条两条完全重合。⭐ 领域知识买的是速度,不是上限 —— 因为模型自己也能从 one-hot 学到同一件事,只要样本够。

💱 二、把这张表读明白

这四行里有三个不同的结论,每一个都值得单独记住。

① 数据少的时候,那一列抵得上一倍数据。 450 条时纯 one-hot 只有 0.319,加一列疏水性跳到 0.515; 而把数据翻倍到 900 条、什么特征都不加,是 0.535。 ⭐ 两者几乎相当 —— 一列先验知识 ≈ 一倍数据。在湿实验里,「一倍数据」可能意味着几个月和一笔真金白银。

② 数据一多,那一列就白给了。 1800 条以后两列完全一样(0.705 / 0.705)。⭐ 不是特征失效了,是模型自己从 one-hot 里学会了同一件事 —— 疏水性本来就是 20 个氨基酸各自的一个数,one-hot 加线性模型完全有能力把它拟合出来,只要样本够。

⭐⭐ 这就是领域知识的兑换率:它买的不是上限,是速度。 你付出的先验越多,需要的数据越少;但先验能给到的上限,数据自己也能到。

③ 涨到 0.748 就不再涨了。 因为真实性质里还有那个成对相互作用(位置 4 和 21), 而线性模型对每个位置独立打分,结构上就表达不了「两个位置一起才生效」。 ⚠️ 这不是数据不够,是模型形式错了 —— 再给十倍数据也一样。


🧭 三、于是你有了一张分诊表

上面三条对应三种完全不同的处境,而它们的解法互不通用。 拿到一个科学建模任务,第一件事是判断自己在哪一格:

症状 说明什么 该做的
加特征立刻涨 数据不够,先验还有空间 ⭐ 继续挖领域知识,这是最便宜的一档
加特征没反应、加数据还在涨 先验已经饱和 去搞数据(或用预训练模型代替数据,见 03 章)
加数据也不涨了 模型形式表达不了真实关系 换模型结构,比如让它能表达成对交互

💀 最常见的浪费是搞错格子:明明卡在第三格(模型表达不了),却一直在第一格里加特征、 在第二格里堆数据。⚠️ 两者都会「有一点点效果」,于是你会一直做下去 —— 这是最贵的一种陷阱。

⭐ 判据很简单,就是上面那张表:同时沿两个方向各推一步,看哪个方向动了。 花不了多少时间,能省掉几个月。


🚧 四、开始之前先说清楚三件事

这个板块后面每一章都建立在这三条上,现在就要接受它们。

① 你的数据比你以为的少。 表里那 450 条是独立的 450 条。真实数据库里的一万条常常只有几百条是真独立的 —— 因为它们彼此同源。⚠️ 这件事会同时毁掉你的训练和你的评估, 而且默认的随机划分会让你完全看不出来。这是 07 章的正题, 也是整个板块最要紧的一章。

② 数据不是天上掉的,是有人做实验做出来的。 所以它带着实验的痕迹:不同批次、不同仪器、只有成功的才被记录。 ⭐ 反过来,这也意味着你能去生产新数据 —— 这是 AI4S 和普通 ML 最大的不同, 08 章讲怎么利用它。

③ 有些先验不该当特征加,该当结构内建。 疏水性这种「每个位置各自一个数」的先验,加成一列就行。 但「整体旋转一下还是同一个分子」这种先验加不成特征 —— ⭐ 它得写进模型结构里,那是 05 章的正题。


🔗 这一章连到哪里

去哪 为什么
07 · 评测的坑 ⭐⭐ 上面那张表的前提是「测试集真的独立」 —— 而科学数据默认不满足。那一章实测:同一份数据只改划分方式,R² 从 0.71 掉到 −1.20
05 · 等变与不变 第 4 节第三条的展开:旋转不变这类先验为什么只能内建进结构
03 · 蛋白质语言模型 分诊表第二格的现代解法:没有更多标注数据时,用无标注序列预训练出来的表示顶上
机器学习与深度学习基础 03 · 线性模型 这一章用的岭回归、以及 lam 那个正则项在做什么。⭐ 如果 np.linalg.solve 那行你看不懂,先去那一章
机器学习与深度学习基础 05 · 评估与过拟合 R² 和训练/测试划分的基本功。⚠️ 但那里的随机划分在这个板块里不能直接用,原因见 07

✅ 检查点

  1. 450 条数据时,加一列疏水性把 R² 从多少提到了多少?这个提升相当于把数据加到多少条?
  2. 为什么 1800 条之后,那一列特征完全不起作用了?是特征失效了吗?
  3. R² 涨到 0.748 就不动了,是数据不够还是别的原因?
  4. 分诊表的三格分别对应什么症状、什么解法?
  5. 为什么说「你的数据比你以为的少」?这件事最先会在哪里坑到你?
  6. 疏水性这种先验和「旋转不变」这种先验,处理方式有什么根本不同?
👀 答案
  1. 从 0.319 提到 0.515。而纯 one-hot 要到 900 条才有 0.535 —— ⭐ 一列先验 ≈ 一倍数据。
  2. 不是特征失效,是模型自己从 one-hot 学会了同一件事。 疏水性本来就是每个氨基酸各自的一个数,one-hot 加线性模型完全有能力拟合出来,只要样本够。⭐ 领域知识买的是速度,不是上限。
  3. 是模型形式不对。 真实性质里有位置 4 和 21 的成对相互作用,而线性模型对每个位置独立打分,结构上就表达不了「两个位置一起才生效」。再给十倍数据也一样。
  4. ①加特征立刻涨 → 数据不够、先验还有空间 → 继续挖领域知识(最便宜)②加特征没反应但加数据还涨 → 先验饱和 → 搞数据或用预训练表示 ③加数据也不涨 → 模型表达不了 → 换结构。💀 最常见的浪费是搞错格子,而错的方向也会有一点点效果,于是你会一直做下去。
  5. 因为数据库里的条目大量同源,一万条可能只有几百条真独立。最先坑到你的是评估 —— 默认的随机划分会把几乎一样的样本分到训练和测试两边,指标虚高而你看不出来。见 07 章。
  6. 疏水性是「每个位置各自一个数」,加成一列特征就行;而「整体旋转一下还是同一个分子」加不成特征,只能写进模型结构里。见 05 章。

🛑 可以停在这里

读到这里你已经有了这个板块最核心的那个直觉:先验和数据可以互换,而且兑换率是能量出来的, 以及一张判断「现在该往哪个方向使劲」的分诊表。这两样东西已经能用了。

⚠️ 什么时候看下一页:你想知道序列这种数据到底该怎么表示 —— 上面用的 one-hot 是最笨的一种,而它有几个具体的毛病。

⚡ 走神救援

先记住这几件事

下一节 👉 02-序列不是文本.md

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