春江暮客

春江暮客的个人学习分享网站

nanoBERT 与 VHHBERT:纳米抗体语言模型比较与实用入门

2026-09-22 技术
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。

  1. 创建环境:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install 'torch>=2.6' 'transformers==4.57.6'
  1. 保存为 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()
  1. 查看参数并运行:
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 sequenceToken mismatch 检查分词器和输入格式。VHHBERT 使用 " ".join(sequence);本文 nanoBERT 分词器使用连续字符串。
加载模型前就拒绝输入 使用大写的标准氨基酸字母,不要包含 FASTA 标题或空格。本例只接收 1–179 个残基;更长输入需要另外设计并验证处理方法。
权重下载失败 检查网络与权重名称。离线使用时,把对应版本的配置、分词文件和权重分别下载到 models/nanobertmodels/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 生成的概念插画,不代表实验测定的分子结构。

友情链接

其它