nanoBERT 与 VHHBERT:纳米抗体语言模型比较与实用入门
nanoBERT 和 VHHBERT 都从纳米抗体序列中学习规律,也都能为下游模型提供特征。不过,两者论文中的主要实验回答了不同的问题:nanoBERT 侧重合理的氨基酸替换,VHHBERT 则与 VHH 序列语料库和抗原结合预测基准一起发布。
如果刚开始一个项目,我建议先在相同的数据划分上比较两者的向量表示,再考虑微调。训练语料更多、隐藏层维度更大,或在另一篇论文中分数更高,都不能直接决定哪个模型更适合你的任务。
本文延续本站的抗体模型评估文章,给出一个具体的纳米抗体示例。资料与公开权重核对于 2026 年 9 月 22 日。下面的代码用于检查软件行为,并非新的生物学性能评测。
两个模型学习什么
VHH 是骆驼科动物重链抗体的可变结构域。两个模型都使用掩码语言建模:遮住部分残基,再根据周围序列恢复它们。模型由此得到包含上下文信息的残基向量;配合语言模型预测头,还能预测残基。可从 nanoBERT 仓库和 VHHBERT 模型卡查看实现。
| 项目 | nanoBERT | VHHBERT |
|---|---|---|
| 预训练数据 | INDI 中的 1,000 万条纳米抗体序列 | VHHCorpus-2M 中用于训练的 200 万条序列 |
| 论文中的模型规模 | small 约 1,400 万参数;big 约 8,600 万参数 | 约 8,600 万参数 |
| 本文使用的公开权重 | NaturalAntibody/nanoBERT |
COGNANO/VHHBERT |
| 该权重的隐藏状态维度 | 320 | 768 |
| 建议的入门用途 | 残基评分与序列特征 | 序列特征与下游预测 |
训练数据和模型规模来自 nanoBERT 论文及 VHHBERT 模型卡。VHHCorpus-2M 共含 2,040,988 条序列,其中 2,000,000 条用于训练,40,988 条用于验证,见数据集说明。
不要把所有名为 nanoBERT 的权重都当成论文中的大模型。公开的 nanoBERT 配置写明了 6 层和 320 维隐藏状态,本文示例固定使用这个版本。VHHBERT 的公开编码器为 12 层、768 维。这些维度描述特征向量的大小,不能直接代表预测准确率。
论文证据支持哪些结论
nanoBERT 研究从骆驼科胚系基因参考不完整的问题出发,提出不依赖可靠基因归属的建模方法。论文比较了它与人类抗体模型、ESM-2 的掩码残基恢复能力,也探索了天然性判断和热稳定性等下游任务。这些结果支持在筛选合理替换时尝试纳米抗体专用模型,但不能把残基概率解释成针对某个抗原的亲和力测量。见原始论文。
VHHBERT 论文用完整的监督学习流程评估结合预测:VHH 编码器、冻结的 ESM-2 抗原编码器,以及训练得到的分类器。其中 VHH 编码器也进行了微调,训练和测试样本来自不同羊驼。VHHBERT 的 F1 为 0.608 ± 0.012,AUPRC 为 0.650 ± 0.025;同一实验中,AntiBERTa2-CSSP 的两项指标都更高。这是论文报告的五次不同随机种子实验结果,并非下方示例跑出的分数。见表 4 与实验设置。
这项比较考察的是:在基准已有的抗原集合内,模型能否迁移到另一只动物产生的 VHH。它没有证明模型对任意新抗原都有效,也没有给出 nanoBERT 与 VHHBERT 的直接排名,因为表中没有 nanoBERT。
让输出对应实际问题
我建议从下列实验开始,再根据留出数据选择模型:
| 问题 | 起步实验 | 需要的证据 |
|---|---|---|
| 哪些替换符合序列上下文? | 遮住一个残基,比较候选分数 | 留出序列上的恢复能力,以及后续任务测量 |
| 能否从序列预测某项已测性质? | 冻结向量加小型回归或分类模型 | 分开近缘序列的数据划分,以及对应任务指标 |
| 这个 VHH 是否结合这个抗原? | 同时接收两者信息的监督模型 | 结合标签,以及符合实际使用场景的测试集 |
向量是数值特征,不是测量结果。残基分数取决于输入序列,也不是经过校准的结合能力提升概率。多个突变组合还需要整体评估;把单点突变分数相加,相当于忽略了位点之间的相互作用。
公平比较时,固定标签、数据划分、下游学习器和调参预算,并加入氨基酸组成等简单基线。预处理只在训练数据上拟合,用多个随机种子重复训练,同时报告预测质量和计算成本。如果目标是迁移到新抗原,测试集就应同时隔离抗原与近缘 VHH。
运行两个编码器,并检查残基对齐
分词很容易悄悄出错。本文所用版本中,nanoBERT 的 RobertaTokenizer 接收连续的氨基酸字符串;VHHBERT 使用 BertTokenizer,输入残基之间需要空格,而它的编码器仍然是 RoBERTa。选对模型类,不代表输入一定被正确分词。VHHBERT 模型卡说明了分词器类型;下面的代码会逐个核对残基 token。
- 创建环境:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install 'torch>=2.6' 'transformers==4.57.6'
- 保存为
compare_embeddings.py:
"""Check token alignment and embedding shapes; this is not a biological benchmark."""
import argparse
from pathlib import Path
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--sequence", default="ACDEFGHIKLMNPQRSTVWY")
parser.add_argument("--local-root", type=Path)
args = parser.parse_args()
sequence = args.sequence
if not 1 <= len(sequence) <= 179:
parser.error("Use 1-179 residues; this demo never truncates")
if set(sequence) - set("ACDEFGHIKLMNPQRSTVWY"):
parser.error("Use the 20 standard uppercase amino acid letters")
import torch
from transformers import BertTokenizer, RobertaTokenizer, RobertaModel
specs = [
("nanobert", "NaturalAntibody/nanoBERT",
"edc8182ad89a827f8737fa572c6b5fac6197e6b0", RobertaTokenizer),
("vhhbert", "COGNANO/VHHBERT",
"cd6341d2700b93cd3d8d66cef360ad01cca19a73", BertTokenizer),
]
for name, repo, revision, tokenizer_class in specs:
source = str(args.local_root / name) if args.local_root else repo
options = {"local_files_only": True} if args.local_root else {"revision": revision}
tokenizer = tokenizer_class.from_pretrained(source, **options)
text = " ".join(sequence) if name == "vhhbert" else sequence
batch = tokenizer(text, return_tensors="pt", return_special_tokens_mask=True)
special = batch.pop("special_tokens_mask").bool()
keep = batch["attention_mask"].bool() & ~special
ids = batch["input_ids"][0, keep[0]].tolist()
assert tokenizer.unk_token_id not in ids, "Unknown token in sequence"
assert tokenizer.convert_ids_to_tokens(ids) == list(sequence), "Token mismatch"
# Both checkpoints use a RoBERTa encoder, despite different tokenizers.
model = RobertaModel.from_pretrained(source, add_pooling_layer=False, **options).eval()
with torch.inference_mode():
hidden = model(**batch).last_hidden_state
residues = hidden[0, keep[0]]
pooled = residues.mean(dim=0)
assert residues.shape == (len(sequence), model.config.hidden_size)
assert torch.isfinite(residues).all()
print(name, "residues:", tuple(residues.shape), "pooled:", tuple(pooled.shape))
del model
if __name__ == "__main__":
main()
- 查看参数并运行:
python compare_embeddings.py --help
python compare_embeddings.py
输出:
nanobert residues: (20, 320) pooled: (320,)
vhhbert residues: (20, 768) pooled: (768,)
这个人工构造的 20 残基输入仅用于软件检查,不是具有已知功能的纳米抗体。示例在平均前移除特殊 token,核对残基与 token 是否一一对应,并拒绝非法字符和超出保守上限 179 个残基的输入,不会静默截断。可用 --sequence 提供蛋白质序列,但仍需单独核实其生物学来源。
CPU 检查使用 Python 3.14.7、PyTorch 2.14.0、Transformers 4.57.6,以及代码中固定的模型版本。两组输出形状均符合预期,所有残基向量数值均为有限值。首次运行会下载权重。只加载编码器而不加载掩码预测头时,可能出现预测头权重未使用的提示;本例提取的是特征向量,不是残基概率。
平均池化只是起点。它可能稀释集中在短 CDR 区域的信息。我会使用一致的抗体编号方法,比较全序列池化和按区域提取的特征。不同宽度的向量也需要分别训练下游模型,不能直接互换。
常见问题
| 现象 | 原因与处理方法 |
|---|---|
Unknown token in sequence 或 Token mismatch |
检查分词器和输入格式。VHHBERT 使用 " ".join(sequence);本文 nanoBERT 分词器使用连续字符串。 |
| 加载模型前就拒绝输入 | 使用大写的标准氨基酸字母,不要包含 FASTA 标题或空格。本例只接收 1–179 个残基;更长输入需要另外设计并验证处理方法。 |
| 权重下载失败 | 检查网络与权重名称。离线使用时,把对应版本的配置、分词文件和权重分别下载到 models/nanobert 与 models/vhhbert,然后运行 python compare_embeddings.py --local-root models。 |
如何开始第一个实验
面对新的带标签 VHH 数据集,可以先固定 nanoBERT 与 VHHBERT 的权重,提取向量,再配合同一种规模适中的下游学习器。增加模型复杂度前,先按序列簇和 CDR 长度分析错误。建立有效基线后再尝试微调,并始终保留不参与模型选择的测试集。
如果要预测残基,应使用带掩码语言模型预测头的类,而不是本例的纯编码器类。nanoBERT 使用指南展示了相应接口。如果要复现结合预测研究,应使用作者的 AVIDa-SARS-CoV-2 代码,包括其中的抗原输入与训练步骤。
公开文件分别标注了许可:nanoBERT 权重为 CC BY-NC-SA 4.0,VHHBERT 权重为 MIT,VHHCorpus-2M 数据集为 CC BY-NC 4.0。实验记录中应同时保留模型和数据集的信息。
如果需要更广泛的基线,可继续阅读本站的 ESM 模型家族比较和 ESM-2 向量提取教程。
封面为 AI 生成的概念插画,不代表实验测定的分子结构。
- 原文作者:春江暮客
- 原文链接:https://www.bobobk.com/nanobert-vhhbert-comparison.html
- 版权声明:本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。