标签TokenClassification

spaCy的NER标签转换成RoBERTa能用的格式

最近在做命名实体识别的项目,遇到了一个挺头疼的问题:spaCy标注出来的数据格式和RoBERTa需要的格式不一样。折腾了好久才搞明白,这里记录一下转换的过程。

先看看我们的数据长什么样:

text = "女 大专 xfwang@dayee.com 身份证 杨洋 无经验 学士"
entities = [
    (27, 29, 'NAME'),
    (1, 2, 'GENDER'),
    (6, 22, 'EMAIL_ADDRESS'),
    (30, 33, 'YEARS_OF_WORK_EXPERIENCE'),
    (34, 36, 'DEGREE')
]

这种格式是字符级别的起止位置 + 标签(spaCy的doc.ents就是这样的)。但是RoBERTa/Longformer做NER的时候,需要的是token级别的BIO/BILOU标签。所以我们得想办法把它们对应起来。


第一步:用RoBERTa的tokenizer分词

这一步很关键,因为RoBERTa用的是BPE分词,跟spaCy的分词方式完全不一样。字符位置和token位置肯定对不上,所以必须重新分词。

from transformers import RobertaTokenizerFast

tokenizer = RobertaTokenizerFast.from_pretrained("roberta-base")

tokens = tokenizer.tokenize(text)
print(tokens)

输出大概是这样的:

['女', 'Ġ大', '专', 'Ġxf', 'wang', '@', 'day', 'ee', '.', 'com', 'Ġ身份证', 'Ġ杨', '洋', 'Ġ无', '经验', 'Ġ学士']

这里的Ġ表示空格,BPE会把空格当成特殊的前缀处理。


第二步:建立字符和token的对应关系

这一步用tokenizer的char_to_token方法,把字符位置映射到token位置。

encoding = tokenizer(text, return_offsets_mapping=True)
offsets = encoding["offset_mapping"]

print(offsets)

输出:

[(0,1), (1,3), (3,4), (4,6), (6,10), (10,11), (11,14), (14,16), (16,17), (17,20), (20,24), (24,26), (26,27), (27,29), (29,31), (31,33)]

每个元组表示(start_char, end_char),这样就能知道每个token对应原文的哪个字符区间了。


第三步:初始化BIO标签

先给所有token都打上"O"标签(Outside,表示不属于任何实体)。

labels = ["O"] * len(offsets)

第四步:把实体的字符位置转换成token位置

对每个(start_char, end_char, label),找到对应的token下标。

for start_char, end_char, ent_label in entities:
    token_start = None
    token_end = None
    for i, (start, end) in enumerate(offsets):
        if start_char >= start and start_char < end:
            token_start = i
        if end_char > start and end_char <= end:
            token_end = i
    # BIO标注
    if token_start is not None and token_end is not None:
        labels[token_start] = "B-" + ent_label
        for j in range(token_start + 1, token_end + 1):
            labels[j] = "I-" + ent_label

第五步:看看最终结果

for tok, lab in zip(tokens, labels):
    print(f"{tok:15} {lab}")

输出大概是这样:

女              B-GENDER
Ġ大             B-DEGREE
专              I-DEGREE
Ġxf             B-EMAIL_ADDRESS
wang            I-EMAIL_ADDRESS
@               I-EMAIL_ADDRESS
day             I-EMAIL_ADDRESS
ee              I-EMAIL_ADDRESS
.               I-EMAIL_ADDRESS
com             I-EMAIL_ADDRESS
Ġ身份证          O
Ġ杨             B-NAME
洋              I-NAME
Ġ无             B-YEARS_OF_WORK_EXPERIENCE
经验            I-YEARS_OF_WORK_EXPERIENCE
Ġ学士           B-DEGREE

总结一下整个流程

经过这么一番折腾,我们成功把:

  • spaCy的字符级span标签
  • 转换成了RoBERTa需要的token级BIO标签

关键就是用offset_mapping来对齐字符位置和token位置。这样就能把spaCy的(start, end, label)格式转成RoBERTa能直接用的**input_ids + labels**对了。

最终的效果大概是这样:

原文:  女 大专 xfwang@dayee.com 身份证 杨洋 无经验 学士
tokens: ['女', '大', '专', 'x', '##fw', '##ang', '@', 'day', '##ee', '.', 'com', '身', '份', '证', '杨', '洋', '无', '经', '验', '学', '士']
labels: ['B-GENDER', 'O', 'O', 'B-EMAIL_ADDRESS', 'I-EMAIL_ADDRESS', 'I-EMAIL_ADDRESS', 'I-EMAIL_ADDRESS', 'I-EMAIL_ADDRESS', 'I-EMAIL_ADDRESS', 'I-EMAIL_ADDRESS', 'I-EMAIL_ADDRESS', 'O', 'O', 'O', 'B-NAME', 'I-NAME', 'B-YEARS_OF_WORK_EXPERIENCE', 'I-YEARS_OF_WORK_EXPERIENCE', 'I-YEARS_OF_WORK_EXPERIENCE', 'B-DEGREE', 'I-DEGREE']

spacy config.cfg

配置文件分成 六大块

  1. 路径 & 系统全局
  2. nlp 基础设置(语言、pipeline、tokenizer、vectors)
  3. 语料库 (corpora)
  4. 训练通用超参数(seed、dropout、early‑stop、epoch/step 等)
  5. 训练子模块(logger、batcher、optimizer)
  6. 初始化 (initialize) 阶段的资源

阅读提示

  • null 在配置里表示 “不指定 / 使用默认值”
  • ${...}变量引用,把前面定义的值插入当前位置。
  • @xxx = "module.v1" 表示 使用 spaCy 的注册工厂(factory)来实例化对应的对象。

下面逐节、逐项解释每个变量的 含义、默认行为、以及在训练/推理时的作用


1️⃣ [paths] – 文件路径占位符

变量含义典型取值说明
train训练语料库的 磁盘路径(可以是 .spacy.jsonl.txt 等)"./train.spacy"null 表示 未指定,需要在运行时通过 -c paths.train=/path/to/train.spacy 覆盖。
dev验证(dev)语料库的路径"./dev.spacy"同上,若没有 dev 集可以保持 null(但 eval_frequency 仍会尝试读取,会报错)。
vectors预训练词向量文件的路径(.vec.bin.spacy"./en_vectors_web_lg.vectors"若为 nullnlp.vectors 会使用 空向量表(即没有外部向量)。
init_tok2vec用于 transfer‑learningtok2vec 权重文件路径"./tok2vec_init.bin"当你想在已有 tok2vec 基础上继续训练时使用。若 null,则不加载。

为什么要把路径放在单独的 [paths] 节?
这样在 命令行spacy train config.cfg -c paths.train=./mytrain.spacy)或 Python 脚本 中可以统一覆盖,避免在多个位置硬编码。


2️⃣ [system] – 系统层面的全局设置

变量含义说明
seed随机数种子(Python、NumPy、PyTorch、cupy 等)0 表示 固定种子,保证每次运行得到相同的结果(只要其它因素也固定)。若设为 null 则使用 随机种子
gpu_allocatorGPU 内存分配器名称null 使用 spaCy 默认的 pytorch‑cuda 分配器。可设为 "pinned""cuda_async" 等,以优化大模型的显存利用。

3️⃣ [nlp] – 核心 Language 对象的配置

变量含义说明
lang语言代码(enzhde …)null 让 spaCy 自动推断(如果在 initialize 中提供 vocab、vectors 等),否则必须显式指定。
pipeline流水线组件名称列表(执行顺序)例如 ["tok2vec","tagger","parser","ner"]。这里是空列表 [],意味着 不加载任何组件(常用于只想跑 nlp.initialize() 或自己手动添加组件)。
disabled启动时 禁用 的组件例如 ["parser"] 会在 nlp.from_disk 时加载 parser,但在训练前把它标记为 disabled。默认 [](全部启用)。
before_creation / after_creation / after_pipeline_creation可选回调(Python 表达式或函数路径)这些钩子在 Language 对象 创建前/后、以及 pipeline 完成构建后 被调用,可用于自定义修改 nlp(例如注入自定义属性、注册新组件等)。null 表示不使用。
batch_sizenlp.pipenlp.evaluate默认批大小这里是 1000(按 文档数),如果你的模型对显存要求高,可调小。此值仅在 未显式传入 batch size 时使用。

3.1 [nlp.tokenizer] – 分词器工厂

变量含义说明
@tokenizers = "spacy.Tokenizer.v1"通过 spaCy 注册系统 创建 Tokenizer 的工厂spacy.Tokenizer.v1 是默认实现,除非你想使用自定义 Tokenizer(如 my_pkg.MyTokenizer.v1)才需要改动。

3.2 [nlp.vectors] – 词向量表工厂

变量含义说明
@vectors = "spacy.Vectors.v1"使用 spaCy 提供的 向量容器paths.vectors 被指定时,这个工厂会读取向量文件并填充 nlp.vocab。如果路径为空,则创建 空向量表(所有向量默认为 0)。

4️⃣ [components] – 流水线组件的具体配置

当前文件里 没有任何子节(如 [components.tagger]),因为 pipeline = []
当你向 pipeline 中加入组件时,需要在这里添加对应的块,例如:

[components.tagger]
factory = "tagger"
...

5️⃣ [corpora] – 语料库读取器的配置

5.1 [corpora.train] – 训练语料

变量含义说明
@readers = "spacy.Corpus.v1"使用 spaCy 的 Corpus 读取器负责把磁盘文件转化为 Example 对象(包含 gold 标注)。
path = ${paths.train}语料所在路径的 引用paths.trainnull,则此读取器会报错,必须在运行时提供。
gold_preproc = false是否 使用 gold‑标准的句子边界和 token 化true 时会直接把文件里已经分好句、分好词的标注当作“真值”,不再让 spaCy 的 tokenizer 重新切分。
max_length = 0文档长度上限(字符数)0 表示 不限制。若设置成 1000,超过长度的文档会被 截断或丢弃(取决于 reader 实现)。
limit = 0最大读取的样本数0 表示 读取全部。常用于快速调试,只读取前 N 条。
augmenter = null数据增强的工厂或回调若提供(如 "spacy.Augmenter.v1"),在读取每个 Example 时会随机进行 大小写、标点、同义词 替换等。
augmenter 之外的其它字段(如 gold_preproc)同理。

5.2 [corpora.dev] – 验证语料

同训练块,只是 path 指向 paths.dev,其它字段意义完全相同。

为什么两块几乎相同?
因为 训练评估 常常使用相同的读取逻辑,只是数据来源不同。把它们分开可以让你在 training.dev_corpus 中指向 corpora.dev,而不必在每个块里重复写 dev_corpus = "corpora.dev"


6️⃣ [training] – 训练过程的全局超参数

变量含义说明
seed = ${system.seed}随机种子(从 [system] 继承)保证 训练、批次划分、模型初始化 的可重复性。
gpu_allocator = ${system.gpu_allocator}GPU 内存分配器(从 [system] 继承)影响显存分配策略,尤其在多卡或大模型时重要。
dropout = 0.1Dropout 率(对所有支持 dropout 的层生效)训练时随机丢弃 10% 的神经元,防止过拟合。
accumulate_gradient = 1梯度累积步数若设为 >1,会在更新前累计多次 mini‑batch 的梯度,等效于增大 batch size 而不占用更多显存。
patience = 1600Early‑stopping 的容忍步数若在 1600 步eval_frequency 计数)内验证指标没有提升,则提前结束训练。0 表示关闭 early‑stop。
max_epochs = 0最大 epoch 数(遍历完整训练集的次数)0无限 epoch(只受 max_steps 限制)。-1 表示 流式读取(不在内存中一次性加载全部数据),适用于极大语料。
max_steps = 20000训练上限步数(更新次数)0 表示不限制,只受 max_epochs 控制。这里设为 20000,训练将在 20k 步后自动停止(即使 epoch 仍未结束)。
eval_frequency = 200每隔多少步 进行一次 dev 评估评估时会打印 dev 分数、保存 checkpoint(取决于 training.logger)。
score_weights = {}自定义评分权重(用于 nlp.evaluate空字典意味着使用 默认权重"ents_f""tags_acc" 等)。如果想只关注 NER,可写 {"ents_f":1.0}
frozen_components = []冻结的组件列表(不更新权重)例如 ["tok2vec"] 会在训练时保持不变,常用于 微调(只调 classifier)。
annotating_components = []负责产生 gold 标注的组件某些组件(如 entity_ruler) 在训练时会 自动生成 额外标注;把它们列在这里可让 nlp.update 自动把这些标注加入 Example
dev_corpus = "corpora.dev"验证语料的配置块路径训练循环会通过 registry.get("corpora.dev") 读取该语料。
train_corpus = "corpora.train"训练语料的配置块路径同上,用于实际模型更新。
before_to_disk = null保存模型前的回调可用于压缩、添加自定义文件、或移除临时属性。
before_update = null每一步更新前的回调常用于 自定义学习率调度、梯度修改、日志记录等。

7️⃣ [training.logger] – 训练日志记录器

变量含义说明
@loggers = "spacy.ConsoleLogger.v1"使用 控制台日志(打印到 stdout)还有 spacy.FileLogger.v1(写文件)或自定义 logger。它负责把 训练进度、损失、评估分数 输出到终端或文件。

8️⃣ [training.batcher] – 批次划分器(batch generator)

变量含义说明
@batchers = "spacy.batch_by_words.v1"词数 动态划分批次的工厂其它可选:batch_by_sentences.v1batch_by_documents.v1
discard_oversize = false是否丢弃超过当前 batch size 上限的样本true 会把超长样本直接抛掉,false 会把它们放入 单独的 batch(可能导致极大 batch)。
tolerance = 0.2容差:允许 batch 的实际大小在目标大小 ± tolerance * target 之间例如 target=1000,tolerance=0.2 → batch 大小会在 800~1200 之间波动,以更好地填满 GPU。

8.1 [training.batcher.size]batch size 的调度(schedule)

变量含义说明
@schedules = "compounding.v1"复合(compounding) 调度器:从 start 逐步 指数增长stop,每次乘 compound常用于 从小 batch 开始(帮助模型快速收敛)逐步放大,以提升吞吐。
start = 100初始 目标 batch size(词数)第一次迭代大约 100 个词。
stop = 1000最大 目标 batch size随着训练进行,batch size 会慢慢增长到约 1000 词。
compound = 1.001增长因子(每次 batch size ≈ 前一次 × 1.001)该值越大增长越快,若设为 1.0 则保持不变。

实际 batch 大小 = schedule(step) → 受 tolerancediscard_oversize 进一步调节。


9️⃣ [training.optimizer] – 优化器(Adam)及其超参数

变量含义说明
@optimizers = "Adam.v1"使用 Adam 优化器(spaCy 包装版)其它可选:AdamW.v1SGD.v1RAdam.v1 等。
beta1 = 0.9Adam 一阶矩(动量)衰减系数与原始 Adam 相同,控制 梯度的指数移动平均
beta2 = 0.999Adam 二阶矩(方差)衰减系数控制 梯度平方的指数移动平均
L2_is_weight_decay = trueL2 正则 解释为 权重衰减(而非普通的 L2 损失)true 时,更新公式会变成 θ ← θ - lr * (grad + λ * θ),更符合 AdamW 的实现。
L2 = 0.01权重衰减系数 λ对所有 可学习参数(除 bias)施加衰减。值越大正则越强,防止过拟合。
grad_clip = 1.0梯度裁剪阈值(全局 L2 范数)若梯度范数 > 1.0,则会 按比例缩放,防止梯度爆炸。
use_averages = false是否使用 参数滑动平均(EMA)true,会在训练结束后把模型参数替换为 历史平均值(有助于提升泛化),但会占用额外显存。
eps = 1e-8Adam 的 数值稳定项防止除零错误。
learn_rate = 0.001基础学习率(会被学习率调度器覆盖)在每一步,实际使用的学习率 = schedule(step) * learn_rate(如果 schedule 已经把 initial_rate 包含进来,则这里的值通常保持和 initial_rate 相同)。

关联解释

  • learn_rate[training.optimizer.learn_rate](如果你另建该块)配合使用,schedule 会在每 step 调整 learn_rate 的实际数值。
  • L2_is_weight_decayL2 共同决定 权重衰减,而 权重衰减乘以当前学习率,所以它们间接受 schedule 的影响。

🔟 [initialize]nlp.initialize() 时的资源加载

变量含义说明
vectors = ${paths.vectors}初始化时加载 外部向量(与 [nlp.vectors] 同步)若为 null,则 nlp.vocab 中的向量表保持空。
init_tok2vec = ${paths.init_tok2vec}预训练的 tok2vec 权重路径(用于 transfer‑learning)常见于 微调 已有模型的 tok2vec 部分。
vocab_data = null额外的 词表数据(如 vocab.json若提供,会在 nlp.vocab.from_disk 时读取。
lookups = nulllookup tables(比如 lemma_lookup, morph_lookup适用于自定义语言的 词形还原形态学等。
tokenizer = {}传递给 Tokenizer 初始化的 关键字参数{"use_legacy": false}
components = {}为每个 pipeline 组件 传递的 初始化参数(键是组件名)例如 components.tagger = {"learn_from": "gold_tag"}
before_init = null / after_init = null回调:在 nlp.initialize() 前后执行可以用于 检查资源动态生成标签集合写日志等。

何时会调用 initialize

  • 训练前spacy train 自动调用)
  • 预训练模型spacy pretrain
  • 手动脚本中使用 nlp.initialize()预加载词表、标签、向量,确保后续 nlp.update() 不会因缺失信息报错。

📌 小结(关键关联)

区块关键变量与其它区块的关联
pathstrain/dev/vectors/init_tok2veccorporainitializenlp.vectors 引用
systemseed/gpu_allocatortrainingnlp 继承
nlppipeline、tokenizer、vectorspipeline 指向 componentsvectorsinitialize.vectors 同步
corporapath、augmenter、gold_preprocpath${paths.xxx}augmenter 可在 trainingbatcher 中使用
trainingmax_steps、eval_frequency、optimizer、batcheroptimizer.learn_ratelearning‑rate schedule(若另建)配合;batcher.size 决定每 step 的 batch 大小,进而影响 梯度累积显存
initializevectors、init_tok2vecnlp 对象提供 预训练资源,在 training 开始前完成

调参技巧(快速参考)

  1. 学习率:先把 initial_rate(在 schedule)和 optimizer.learn_rate 设为相同值(如 1e‑3),再观察 loss 曲线;若收敛慢可把 initial_rate 降到 5e‑4。
  2. 批次大小compoundingstartstop 影响显存占用;对显存紧张的 GPU 把 stop 降到 500,或把 tolerance 调小。
  3. 正则L2 = 0.01 对大模型通常足够;如果出现 过拟合(dev 分数下降),可以把 L2 加大到 0.020.05
  4. 梯度裁剪:对 序列标注NER 这类梯度可能爆炸的任务,保持 grad_clip = 1.0;若出现 nan,尝试调小到 0.5
  5. 早停patienceeval_frequency 配合使用;如果每 200 步评估一次,patience=1600 相当于 最多 8 次(1600/200)没有提升才停止。

token词表固定吗

1️⃣ 词表(Vocabulary)到底是「固定」还是「可改」?

视角说明
预训练阶段词表 在模型训练之前 就已经确定。所有的 权重矩阵(尤其是 token‑embedding)的行数等于词表大小 vocab_size,因此在正式 预训练 完成后,词表实际上是 固定的
下游微调阶段你可以 在已有词表的基础上 做两件事:
1️⃣ 保持原词表不变(最常见的做法),直接使用预训练权重;
2️⃣ 扩展或替换词表(例如加入行业专有词、字符集、emoji 等),这会导致 embedding 层的维度变化,需要特殊处理。
总结- 原始预训练模型的词表是固定的,因为它和模型参数是耦合的。
- 在下游任务里,你可以 增删 token,只要遵循下面的「映射影响」规则。

2️⃣ 词表改变会产生哪些直接影响?

影响维度具体表现需要的额外操作
① token‑embedding 矩阵Embedding 大小 = vocab_size × hidden_dim。如果词表大小改变,embedding 的 行数 必须相应增/删。- 增词 → 用 随机初始化(或使用已有子词的均值)来补齐新增行。
- 删词 → 直接删除对应行,且对应的 预训练权重 会被丢弃。
② 预训练权重的对应关系每个 ID 对应 embedding 矩阵的 第 ID 行。如果你把某个 token 的 ID 改动(比如把 [CLS] 从 101 改成 1),原来的 embedding 行会被错误地“搬走”,导致模型在该位置上学到 错误的向量- 不建议随意改动已有 token 的 ID。若必须改动(比如想把 <pad> 放在词表最后),必须 重新加载/重新训练 整个模型的 embedding(甚至全部参数)。
③ 特殊 token 的功能[PAD][CLS][SEP][MASK][UNK]模型代码 中有硬编码的 默认 ID(如 pad_token_id=0unk_token_id=100 等),很多函数(attention_maskposition_idsloss)都会依据这些 ID 做特殊处理。- 改动 特殊 token 的 ID 时,需要在 Tokenizer 配置模型配置 (config.json)、以及 代码里显式传入(如 model.config.pad_token_id = new_id)全部保持一致。
④ Attention mask / token_type_idsattention_mask 依据 非‑padding 的位置(mask=1)生成。如果 pad_token_id 改了,tokenizer 仍会把原来的 0 当作 padding,导致 mask 错误- 确保 tokenizer.pad_token_idmodel.config.pad_token_id 同步。
⑤ 位置编码(position_ids)与 token ID 本身无关,但 序列长度 必须 ≤ max_position_embeddings。若扩充词表导致 序列更长(例如加入很多子词),需要 增大 max_position_embeddings(或使用相对位置编码)。- 重新训练/微调时可通过 model.resize_position_embeddings(new_max_len) 调整。
⑥ 迁移学习/跨模型兼容两个模型如果 词表不一致,它们的 embedding 行 不对应,直接把一个模型的权重加载到另一个模型会产生 错位,导致性能大幅下降。- 使用 model.resize_token_embeddings(new_vocab_size) 让模型自动 扩展(新增行随机初始化)或 裁剪(删除多余行),再 继续微调

3️⃣ 具体案例:改动词表会怎样「看得见」?

3.1 只 增加 新 token(最安全的改动)

from transformers import BertTokenizerFast, BertModel

tokenizer = BertTokenizerFast.from_pretrained("bert-base-uncased")
model     = BertModel.from_pretrained("bert-base-uncased")

# 假设我们要加入三个行业专有词
new_tokens = ["[MED]", "[FIN]", "[GEO]"]          # 这里用方括号只是举例
num_added = tokenizer.add_tokens(new_tokens)     # <-- 自动在词表末尾追加

print("新增 token 数量:", num_added)            # 3
print("新词表大小:", len(tokenizer))            # 30523 (原来 30522)

# 对模型的 embedding 进行对应扩展
model.resize_token_embeddings(len(tokenizer))

# 现在可以直接 fine‑tune,新增 token 的 embedding 会是随机初始化
  • 影响[MED][FIN][GEO]ID 会是原词表最后的三个(如 30522、30523、30524)。
  • 好处:原有 token‑ID 保持不变,所有预训练权重仍然对应原来的向量,只有 新增行 需要在微调阶段学习。

3.2 删除 现有 token(不推荐)

# 假设你想把词表里 "the" 这个 token 删除
old_id = tokenizer.convert_tokens_to_ids("the")
# 直接删除会导致 ID 错位,模型会把原来 "the" 的 embedding 用在别的 token 上
  • 后果:所有 ID > old_id 的 token 会向前平移 1 位,embedding 行全部错位,模型在每一步都会使用错误的向量,导致 几乎所有下游任务的性能崩溃
  • 唯一可行的做法重新训练一个完整的 tokenizer(重新生成 vocab.txt),从头训练或 从头微调(使用随机初始化的 embedding),这等价于 换模型

3.3 更改 特殊 token 的 ID(极度危险)

# 把 <pad> 的 ID 从 0 改成 5
tokenizer.pad_token = "[PAD]"          # 确保 token 本身仍在词表中
tokenizer.pad_token_id = 5
model.config.pad_token_id = 5
  • 如果不同步attention_mask 仍会把原来的 0 当作 padding,导致模型把实际 token 当作 padding,注意力会 忽略 这些位置,输出全是 0。
  • 如果同步:模型仍然可以工作,但 所有已有的 checkpoint(在 pad_token_id=0 时训练得到的)在 梯度计算 时会把错误的 mask 传进来,微调效果会变差
  • 推荐保持默认 ID(0、101、102、103、100)不动,若必须改动请 重新训练在微调前彻底重新生成 attention_mask

4️⃣ 为什么词表的 ID 映射 会影响模型表现?

机制解释
Embedding 行对应唯一语义每个 ID 在 embedding 矩阵中对应一个 固定向量。如果 ID 与语义不匹配(比如把 “北京” 的向量当成 “Apple”),模型的 上下文表示 会被错误地引导。
Transformer 的自注意力注意力公式 softmax(QKᵀ / √d) 中的 Q/K 来自 token embedding。错误的向量会导致 错误的注意力分布,从而影响所有后续层的计算。
任务头的标签映射对于 Token Classification(NER),标签是 逐 token 的。如果 token ID 错位,标签会被投射到错误的向量上,导致 损失函数 计算错误,模型学习不到正确的实体边界。
损失函数的 Maskignore_index=-100(用于 MLM)是基于 特定 IDpad_token_id)来过滤 padding。ID 改动后,ignore_index 失效,会把 padding 位置计入 loss,降低训练质量。
跨模型迁移BERT‑base 的权重直接加载到 词表不同BERT‑large(或自定义 tokenizer)时,权重会 错位,导致 几乎所有层的输出都不对

5️⃣ 如何安全地 扩展微调 词表?

步骤代码示例(HuggingFace)关键点
① 加载原始 tokenizer & modelpython\nfrom transformers import AutoTokenizer, AutoModel\ntokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")\nmodel = AutoModel.from_pretrained("bert-base-uncased")\n保证两者版本匹配。
② 添加新 tokenpython\new_tokens = ["[MED]", "[FIN]", "[GEO]"]\nadded = tokenizer.add_tokens(new_tokens) # 只在词表末尾追加\nadd_tokens 会返回新增数量。
③ 调整模型的 embedding 大小python\nmodel.resize_token_embeddings(len(tokenizer)) # 自动在 embedding 末尾补零/随机\n只会 新增行,原有行保持不变。
④ (可选)初始化新 token 的向量python\nimport torch\nwith torch.no_grad():\n # 这里把新 token 的向量初始化为已有 token 均值\n old_emb = model.get_input_embeddings().weight[:-added]\n mean_vec = old_emb.mean(dim=0)\n model.get_input_embeddings().weight[-added:] = mean_vec\n随机初始化也可以,微调时会自行学习。
⑤ 重新保存 tokenizer & modelpython\ntokenizer.save_pretrained("./my_bert_extended")\nmodel.save_pretrained("./my_bert_extended")\n以后直接 from_pretrained("./my_bert_extended") 使用。
⑥ 开始下游任务微调按常规 Trainerpipeline 等方式进行。注意 batch size学习率,因为新增向量初始较弱,可能需要稍大一点的学习率。

常见的「扩展」技巧

场景建议
行业专有词(医学、金融)把专有词 完整放进词表(不拆成子词),这样实体在 NER 中会得到 完整的向量,提升识别准确率。
emoji / 表情符号这些字符往往不在原始英文 BERT 词表中,直接 add_tokens在微调数据 中出现即可。
多语言混合对于中英混合的任务,可使用 SentencePiece Unigram 生成统一词表,或在已有英文词表上 追加中文子词(注意 tokenizer.do_lower_case=False)。
[UNK] 替换为更细粒度的子词重新训练 SentencePiece(或 WordPiece)时,将 unk_id 设为 词表末尾,这样在微调时仍然可以使用 ignore_index=-100 来过滤。

6️⃣ 何时必须 重新训练 整个模型?

条件必要重新训练的原因
删除或重新排列已有 token 的 ID这会导致 所有 embedding 行错位,必须从头训练或使用 全新 tokenizer 重新训练。
更改特殊 token的默认 ID 并希望保持原有权重只能在 重新训练 时让模型学习新 ID 对应的向量,否则旧权重会被错误映射。
大幅度改变 max_position_embeddings(超过原来的 512/1024)且不使用 model.resize_position_embeddings位置编码矩阵也需要重新学习,否则超过范围的位置信息会被截断或错误映射。
从一个语言(英文)直接迁移到另一语言(中文)词表差异太大,必须 从头预训练使用跨语言模型(如 XLM‑R、mBERT)并重新微调。

7️⃣ 小结(要点回顾)

  1. 词表在预训练时是固定的,因为它与模型的 embedding 行一一对应。
  2. 下游任务可以安全地
    • 增加 新 token(add_tokensresize_token_embeddings),
    • 保持 所有已有 token 的 ID 不变。
  3. 删除或改动已有 token 的 ID 会导致 embedding 行错位,从而 破坏模型的所有层——只能重新训练。
  4. 特殊 token[PAD][CLS][SEP][MASK][UNK])的 ID 在 模型代码Tokenizerconfig 中都有硬编码,改动时必须 同步更新,否则会出现 mask 错误、attention 错位等问题。
  5. 词表扩展的影响:新增 token 的向量是随机(或均值)初始化,需要 微调 让它们学到有意义的表示;但原有权重保持不变,模型的整体能力基本不受影响。
  6. 实战建议
    • 检查 len(tokenizer)model.config.vocab_size 是否一致。
    • model.resize_token_embeddings(new_vocab_size) 自动扩展/裁剪。
    • 特殊 token 使用 tokenizer.pad_token_id, tokenizer.cls_token_id 等属性,而不是硬写数字。
    • 若需要 大量新词(>5% 词表),考虑 重新训练 tokenizer(SentencePiece/WordPiece)并 从头或继续预训练model.train())以获得更好的子词分布。

一句话概括
词表的 ID ↔ embedding 行 是模型的核心映射,只要保持这一映射不变(即不改动已有 token 的 ID),你就可以安全地 增添新 token,并通过 resize_token_embeddings 让模型接受这些新词;若破坏了映射,就必须 重新训练(或重新生成 tokenizer),否则模型的所有表示都会错位,性能会瞬间崩溃。

bert和任务头

1️⃣ 「BERT 本身」和「任务头」的边界

组件作用典型文件 / 参数是否在 预训练 时出现
BERT 基础模型 (BertModel, BertForMaskedLM…)只负责 上下文化的词向量(最后几层的 hidden states)config.hidden_size, config.num_hidden_layers✅ 只在 ML NSP) 上训练,不含任何下游任务的线性层
任务头 (task‑specific head)把 BERT 最后一层的 hidden state 映射到 具体任务的标签空间(分类、序列标注、问答、序列生成 …)classifier.weight, classifier.bias, qa_outputs, lm_head, crf❌ 只有在 微调(fine‑tuning或 AdapterPrompt 等技术里才会出现
Adapter / Prompt / LoRA轻量化的 可插拔 层,通常只修改 BERT 的内部几层或添加外部投影adapter_layers,lora_A, lora_B`也是 微调阶段 的产物,预训练模型里没有

结论

  • 原始的 bert-base-uncasedbert-large‑cased 等 checkpoint 只提供 通用隐藏向量,没有任何 NER、分类或其他任务的专用头。
  • 任何 “跑分” (GLUE、CoNLL‑2003、SQuAD 等)都是 在这些通用向量之上训练(或 微调)一个 任务头(有时连同全部参数一起微调,有时只微调头部)。

2️⃣ 怎样判断一个 checkpoint 已经 包含了任务头?

2.1 看模型类(AutoModel…

类名说明典型 config 字段
AutoModel / BertModel只有 基础 BERT,输出 (last_hidden_state, pooler_output, hidden_states…)config.is_decoder=False, 没有 num_labelsclassifier
AutoModelForSequenceClassification / BertForSequenceClassification 分类头 (classifier Linear)config.num_labels(>2)
AutoModelForTokenClassification / BertForTokenClassification NER/POS 头 (classifier Linear)config.num_labels(token‑level)
AutoModelForQuestionAnswering / BertForQuestionAnswering QA 头 (qa_outputs Linear)config.start_n_top, config.end_n_top
AutoModelForMaskedLM / BertForMaskedLM 语言模型头 (lm_head)config.vocab_size
AutoModelForCausalLM / BertLMHeadModel 生成头 (lm_head)同上

快速检查Python):

from transformers import AutoConfig, AutoModel, AutoModelForTokenClassification

model_name = "bert-base-uncased"                     # 只含 BERT
cfg = AutoConfig.from_pretrained(model_name)
print("architectures:", cfg.architectures)           # ['BertModel']
print("num_labels:", getattr(cfg, "num_labels", None))  # None → 没有任务头

# 如果是已经微调好的 NER checkpoint
ner_name = "dslim/bert-base-NER"
cfg2 = AutoConfig.from_pretrained(ner_name)
print(cfg2.architectures)   # ['BertForTokenClassification']
print("num_labels =", cfg2.num_labels)  # e.g. 9 (B‑ORG, I‑ORG, …)

2.2 看模型的 权重文件pytorch_model.bin

权重名称说明是否出现说明了什么
cls.predictions.biascls.predictions.transformMLM 头只在 BertForMaskedLM
classifier.weightclassifier.bias通用分类/序列标注头只在 *ForSequenceClassification*ForTokenClassification
qa_outputs.weightqa_outputs.biasQA 头只在 *ForQuestionAnswering
lm_head.weightlm_head.biasCausal/Masked LM 生成头只在 *LMHeadModel
adapter.*lora_*Adapter / LoRA 参数只在使用这些轻量化微调技术的 checkpoint
import torch, os
state = torch.load("pytorch_model.bin", map_location="cpu")
print([k for k in state.keys() "classifier" in k])   # → ['classifier.weight', 'classifier.bias']

如果 只看到 embeddings.*, encoder.*,没有 classifierqa_outputslm_head,说明 没有任务头

2.3 看 config.json 中的 special fields

{
  "architectures": ["BertForTokenClassification"],
  "id2label": {"0":"O","1":"B-PER","2":"I-PER", ...},
  "label2id": {"O":0,"B-PER":1, ...},
  "num_labels": 9
}
  • architectures 指明模型类。
  • id2label/label2id / num_labels 只在 下游微调 时出现。
  • 如果这些字段缺失,则模型是 纯 BERT

3️⃣ “跑分” 是怎么来的?

场景训练方式评分对象常见误解
GLUE / SuperGLUE(文本分类/相似度)微调 整个 BERT(或只微调头)在验证集/测试集上 计算 accuracy / F1 / MCC 等指标只看 预训练模型(如 bert-base-uncased)是没有这些分数的;分数来自 微调后的 checkpoint
CoNLL‑2003 NER在标注数据上 微调 BertForTokenClassification(加入 CRF)实体级 F1同上,只有 加入 NER 头 并在 NER 数据上训练后才会有分数
SQuAD微调 BertForQuestionAnswering(输出 start/end logits)Exact Match / F1需要 问答头,原始 BERT 只能输出隐藏向量,无法直接算分
仅用预训练 BERT 做特征提取冻结全部参数,只取 last_hidden_state 作为特征下游任务 需要自己训练 classifier这种情况下 模型本身不产生分数,分数来自你自己训练的 classifier

所以:如果你在某篇论文或模型库里看到 “BERT‑base achieves 92.3 % on SST‑2”,那 一定 是在 SST‑2 上微调(或在 冻结 BERT + 训练一个小 linear classifier)后得到的 微调 checkpoint,不是原始 bert-base-uncased

4️⃣ 如何确认 最后几层 是否已经被 任务头 “吃掉” 了?

  1. 检查 requires_grad

    for name, param in model.named_parameters():
        if param.requires_grad:
            print(name)   # 只会列出被训练的层
    
    • 若只看到 classifier.*(或 qa_outputs.*)被 requires_grad=True,说明 BERT 本体被冻结,只有头在训练。
    • 若所有 encoder.layer.* 也在列表中,说明 全模型微调
  2. 输出张量的形状

    outputs = model(**batch)          # batch 为 tokenized inputs
    if hasattr(outputs, "logits"):
        print("logits shape:", outputs.logits.shape)   # (batch, seq_len, num_labels) for token classification
    print("hidden shape:", outputs.hidden_states[-1].shape)  # (batch, seq_len, hidden_size)
    
    • logits 的最后一维是 任务标签数不等于 hidden_size → 说明有 线性映射层
    • hidden_states[-1] 仍然保留 原始 BERT 隐藏向量,未被 “吞掉”。
  3. 查看 model.config

    print(model.config.hidden_size)   # e.g. 768
    print(getattr(model.config, "num_labels", None))   # e.g. 9 (NER)  → 任务头存在
    

5️⃣ 如何在 spaCy 里把 BERT 接入 NER(如果你只看到 BERT 没有任务头)

spaCy 的 spacy‑transformers自动在 BERT 之上加一个 NER 头(Transition‑Based Parser):

[components.transformer]
factory = "transformer"
model.name = "bert-base-uncased"   # 只加载 BERT 本体

[components.ner]
factory = "ner"
model.hidden_width = 512          # MLP 宽度(不影响 BERT 输出维度)
extra_state_tokens = false
nO = null                         # 自动取 transformer.hidden_size (768)
  • transformer 只提供 hidden vectors(shape = (batch, subword_len, 768))。
  • ner 会在内部 Linear(hidden, num_labels) 上做 token‑classification(如果你在 config 中写 nO = null,spaCy 会把 hidden_size 自动写进去)。
  • 训练时,spaCy 会 微调整个 BERT(默认)或者只微调 NER 头(通过 trainer.optimizerfreeze 参数控制)。

如果你想自己手动加 NER 头(不走 spaCy),可以直接使用 HuggingFace:

from transformers import AutoModelForTokenClassification, AutoTokenizer

model = AutoModelForTokenClassification.from_pretrained(
    "bert-base-uncased",
    num_labels=9,                     # 你的实体类别数
    id2label={0:"O",1:"B-PER",2:"I-PER",...},
    label2id={v:k for k,v in id2label.items()}
)
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
  • 这里 AutoModelForTokenClassification在ERT 最后一层 上自动添加 nn.Linear(hidden_size, num_labels)(以及可选的 CRFdropout)。
  • 只要把 num_labelsid2labellabel2id 填好,模型在 forward 时返回 logits(shape = (batch, seq_len, num_labels))。

6️⃣ 小结:一步步判断与使用

步骤目的关键代码/文件
① 看文件判断 checkpoint 是否已经包含任务头.json architectures, num_labelspytorch_model.bin → 是否有 classifier.*
② 用 AutoModel 系列加载自动匹配对应的类(只有 BERT → AutoModel; 有头 →AutoModelFor…`)from transformers import AutoModel, AutoModelForTokenClassification
③ 检查 requires_grad确认是 全模型微调 还是 只训练头for n in model.named_parameters(): print(n, p.requires_grad)
④ 看输出 shape确认 logits 的最后一维是 标签数,而 hidden_states[-1] 仍是 hidden_sizeoutputs.logits.shape, outputs.hidden_states[-1].shape
⑤ 评估分数来源分数是 微调后 的 checkpoint 还是 仅预训练 模型阅读模型文档/论文 → “trained on X dataset”, “fine‑tuned on Y”
⑥ 在 spaCy / HuggingFace 中接入 NER如果只有 BERT 本体,手动或自动添加 NER 头spaCy config(extra_state_tokens = false, nO = null)或 AutoModelForTokenClassification

核心概念

  • 原始 BERT 只提供 通用的上下文向量,没有任何任务的输出层。
  • 所有跑分(GLUE、CoNLL‑2003、SQuAD 等)都是 在这些向量之上 训练(或微调)任务头(线性层、CRF、QA 输出层等)得到的。
  • 只要检查 config.json、权重名称、requires_grad,就能快速判断 是否已经做了任务适配,以及 是否只训练了头部

Ernie‑4.5‑Causal‑LM 是什么模型?

项目说明
模型名称Ernie4_5ForCausalLM(Ernie‑4.5 系列的 Causal LM
模型类型自回归(Causal)语言模型(decoder‑only)
主要用途语言生成、续写、对话、摘要、代码生成等 生成式任务
预训练目标Causal Language Modeling(预测下一个 token)
是否包含 MLM / NSP没有。Causal LM 只用 `loss = -log p(x_t
是否可直接用于 NER不直接;如果要做 NER,需要在此模型之上 添加一个 token‑classification 头(例如 ErnieForTokenClassification),或把它当作 Encoder(只保留 transformer 部分)再接上 spaCy、HuggingFace‑Trainer 的 NER 头。

2️⃣ 配置文件里各字段的含义(简要)

字段含义
model_type: "ernie4_5"该模型属于 Ernie‑4.5 系列(百度推出的 “Enhanced Representation through Knowledge Integration”),是基于 GPT‑style 的架构。
hidden_size: 1024num_hidden_layers: 18num_attention_heads: 16典型的 base‑size Transformer,参数约 120‑130 M(不算词表)。
max_position_embeddings: 131072支持 最长 131 072(≈ 128 k)位置(使用 Rope 旋转位置编码)。
head_dim: 128num_key_value_heads: 2多查询(MHA)配置:每个注意力 head 维度 128,KV 头数 2(即 多查询注意力,有助于推理时的 KV 缓存)
rope_theta: 500000.0rope_scaling: nullRoPE(旋转位置编码)参数,θ 越大对长序列越友好。
torch_dtype: "bfloat16"推荐使用 bfloat16(在支持的硬件上可提升算力/显存效率)。
use_cache: truenum_key_value_heads: 2表示模型已经实现 KV‑Cache,适合 高效推理(一次生成可复用缓存)。
vocab_size: 103424词表大小约 103k,采用 BPE/WordPiece(与 AutoTokenizer 配合)。
hidden_act: "silu"use_bias: falserms_norm_eps: 1e-05激活函数 SiLU (Swish)、无 bias、RMSNorm(比 LayerNorm 更轻量)。
bos_token_id: 1eos_token_id: 2pad_token_id: 0开始、结束、填充 token 的 ID。

3️⃣ 与 “预训练” /  “NSP” 的关系

任务是否在此模型里出现
Causal LM (自回归)✅(模型的唯一目标)
Masked LM (MLM)❌(没有 lm_head 用于掩码)
Next‑Sentence Prediction (NSP)❌(没有 next_sentence_head
序列标注(NER)❌(需要额外的 token‑classification 头)

因此,它是一个“预训练的自回归语言模型”,而不是针对 MLM 或 NSP 的预训练模型。如果你看到 Ernie4_5ForCausalLM,就可以把它当作 GPT‑style生成模型 来使用。


4️⃣ 如何把它用于 中文 NER(两种常用思路)

方案 A:直接在上层加 NER 头(推荐)

  1. 加载模型
    from transformers import AutoModelForCausalLM, AutoTokenizer
    tokenizer = AutoTokenizer.from_pretrained("your/ernie4_5_causal", use_fast=True)
    model = AutoModelForCausalLM.from_pretrained("your/ernie4_5_causal")
    
  2. **在其上添加 TokenClassificationHead(官方提供 ErnieForTokenClassification
    from transformers import AutoModelForTokenClassification, AutoConfig
    cfg = AutoConfig.from_pretrained("your/ernie4_5_causal", num_labels=NUM_LABELS)
    model = AutoModelForTokenClassification.from_pretrained(
            "your/ernie4_5_causal",
            config=cfg
    )
    
    • 这里 NUM_LABELS 对应你的实体类别数(如 5:["O","B-PER","I-PER","B-LOC","I-LOC"] 等)。
  3. 使用 Traineraccelerate 进行 token‑classification 微调(输入是 input_ids,标签是 token‑level 标签)。

优点:直接利用已有的 Causal LM 权重,迁移学习效果好。
缺点:模型本身是 decoder‑only,在训练时会把每个 token 的 全部隐藏层 作为特征输出,计算量略大于 encoder‑only(如 BERT、RoBERTa)但仍可接受(显存≈ 8‑10 GB)。

方案 B:只保留 Encoder,配合 spaCy‑transformers(推荐用于 spaCy)

  1. 在 spaCy‑transformers 中只加载 Encoder(去掉语言模型头)
    [components.transformer.model]
    @architectures = "spacy-transformers.TransformerModel.v3"
    name = "your/ernie4_5_causal"
    tokenizer_config = {"use_fast": true}
    # 只保留 Encoder,省显存
    extra_layers = []   # <-- 关键
    
  2. 在 spaCy pipeline 中加入 ner 组件(如 nerentity_ruler
    python -m spacy init config ernie_ner.cfg \
         --lang zh \
         --pipeline transformer,ner \
         --optimize efficiency
    
  3. 微调 NER(和前面示例的 Longformer、BigBird 等一样)
    python -m spacy train ernie_ner.cfg \
         --paths.train train.spacy \
         --paths.dev dev.spacy \
         --output ./output_ner \
         --gpu-id 0
    

好处:只保留 Encoder,显存占用约 30‑40% 更低;适合 spaCy 的 token‑level 任务(NER、POS、依存)且不需要额外的 MLM 头。

如何选择?

场景推荐方案
仅做 NER、对显存有要求方案 B(只保留 Encoder)
想在同一个模型上先做生成(对话/摘要)再做 NER先方案 A(带 token‑classification head)并在需要时切换为 AutoModelForCausalLM 进行生成,切换为 AutoModelForTokenClassification 进行 NER(同一 checkpoint)
希望在业务领域(医学、金融等)继续 MLM 预训练先使用 Ernie4_5ForCausalLM 继续 Causal LM 预训练,再在 B 步骤中接入 NER(只保留 Encoder)
需要超长序列(> 32 k)该模型的 max_position_embeddings 已达 131 072,在 bfloat16 下可直接处理超长文本(需显存 ≥ 12 GB),但要在 spaCy 中设置 max_batch_items 为对应的 token 数目。

5️⃣ 关键代码示例(完整流程)

5.1 下载并加载模型(PyTorch)

from transformers import AutoModelForCausalLM, AutoTokenizer, AutoConfig
import torch

model_name = "your_org/ernie4_5_causal"   # 如 "bert-base-zh" 等
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=True)
model = AutoModelForCausalLM.from_pretrained(
        model_name,
        torch_dtype=torch.bfloat16,     # 取决于硬件,CPU 可用 float32
        device_map="auto"
)

5.2 添加 Token‑Classification 头(用于 NER)

num_labels = 5   # 示例:O, B-PER, I-PER, B-LOC, I-LOC
config = AutoConfig.from_pretrained(
        model_name,
        num_labels=num_labels,
        is_encoder_decoder=False,
        # 以下参数保持原模型结构
        hidden_size=1024,
        num_hidden_layers=18,
        num_attention_heads=16,
        # ...
)

# 用同一权重初始化 token‑classification 模型
from transformers import AutoModelForTokenClassification
ner_model = AutoModelForTokenClassification.from_pretrained(
        model_name,
        config=config,
)

5.3 使用 Trainer 进行微调(示例)

from datasets import load_dataset
from transformers import DataCollatorForTokenClassification, Trainer, TrainingArguments

# 假设已有 CoNLL‑style 数据集
dataset = load_dataset("conll2003", split="train")
def tokenize_and_align_labels(example):
    tokenized = tokenizer(example["tokens"], is_split_into_words=True, truncation=True, max_length=4096)
    # 这里需要把 word‑level 标签映射到 sub‑word
    # ...
    return tokenized

train_dataset = dataset.map(tokenize_and_align_examples, batched=True)

training_args = TrainingArguments(
    output_dir="./ner_ernie",
    per_device_train_batch_size=8,
    gradient_accumulation_steps=2,
    learning_rate=5e-5,
    num_train_epochs=3,
    fp16=True,        # 若支持 bfloat16,可改为 bf16
    eval_strategy="steps",
    eval_steps=500,
    logging_steps=100,
)

trainer = Trainer(
    model=ner_model,
    args=training_args,
    train_dataset=train_dataset,
    data_collator=DataCollatorForTokenClassification(tokenizer),
)

trainer.train()

5.4 在 spaCy 中使用(仅 Encoder)

# big_ernie.cfg
[components.transformer]
factory = "transformer"
max_batch_items = 8192

[components.transformer.model]
@architectures = "spacy-transformers.TransformerModel.v3"
name = "your_org/ernie4_5_causal"
tokenizer_config = {"use_fast": true}
extra_layers = []   # 只保留 encoder

[components.ner]
factory = "ner"

然后:

python -m spacy train big_ernie.cfg \
   --paths.train ./train.spacy \
   --paths.dev ./dev.spacy \
   --output ./output_ernie

6️⃣ 小结

关键点说明
模型类型Causal LM(自回归),Ernie4_5ForCausalLM
是否是 MLM / NSP不是,只进行 下一词预测(Causal LM)
是否已预训练已预训练(在大规模中文语料上做了 Causal LM 预训练)
如何用它做 NER① 在模型上 添加 token‑classification 头AutoModelForTokenClassification)进行微调;
② 或 只保留 encoder,在 spaCy‑transformers 中接入 ner 组件进行微调。
适合的下游任务文本生成、对话、摘要、代码生成;经微调后 可用于实体抽取、序列标注(NER)等。
注意事项- 需要 bfloat16fp16 才能在显存有限的 GPU 上高效运行。
- 若要在 spaCy 中使用,务必在 cfg 中 extra_layers = [],否则会加载 MLM 头导致显存浪费。
- max_position_embeddings=131072 可直接处理 超长文本(> 8 k),只要显存足够即可。

BigBird和Longformer推理速度

参数量:

Longformer基于BERT架构,参数量与BERT-base相当,约1.1亿参数,具体取决于变体大小。

BigBird参数量稍大,类似于BERT-large级别,约3亿多参数,因其引入稀疏注意力机制稍有增加。

DeBERTa-v3-Long版本参数量取决于具体的模型大小,比如base型约8600万主干参数,另有较大词表嵌入参数,总体略大于BERT-base。

RoBERTa-Long则是RoBERTa模型的长文本版本,参数量接近RoBERTa-base或large,分别约1.25亿或3.55亿参数。

推理速度:

Longformer采用局部+全局稀疏注意力,推理速度相对提升,适合长文本但仍高于普通BERT。

BigBird使用块稀疏注意力结合随机和全局注意力,推理复杂度近似线性,推理速度优于Longformer,特别适合超长序列。

DeBERTa-v3-Long采用增强解码器结构,聚焦于改进表示能力,推理速度相较于普通BERT略慢,具体取决于实现。

RoBERTa-Long基于RoBERTa进化,实现在Longformer、BigBird等框架下,速度表现介于Longformer和BigBird之间,根据具体优化不同有所差异。

最大上下文长度:

Longformer支持最大输入长度可达4096 tokens及以上。

BigBird支持最长输入长度在4096到8192 tokens,具体实现可调。

DeBERTa-v3-Long支持较长上下文,通常为2048到4096 tokens。

RoBERTa-Long支持最多4096 tokens左右。

  • Longformer使用了局部窗口注意力结合少量全局注意力,降低了Transformer原有的O(n²)复杂度,推理速度显著比标准Transformer快,但其复杂度仍大约是O(n√n)(局部+全局注意力混合),推理速度随序列长度增长有一定上升。
  • BigBird采用块稀疏注意力机制,包括全局token、局部滑动窗口token和随机token三部分。该机制使模型的复杂度接近线性O(n),理论上在长序列推理时比Longformer更快,特别是超长序列(如4000+ tokens)情况下。
  • 论文和实测结果中,BigBird在长文本任务的效果和推理速度整体优于Longformer,尤其在处理非常长的序列时,BigBird能更高效完成计算,同时保持较好性能。Longformer推理速度虽快于标准BERT,但实际速度比BigBird稍慢。
  • 具体论文中提到,BigBird的推理速度几乎能保持与普通Transformer相当的性能优势(线性复杂度),而Longformer则在部分场景速度稍逊,但资源使用较少,适合中等长度长文本。

总结:

模型推理复杂度推理速度表现适用场景
LongformerO(n√n)快于标准Transformer,但通常慢于BigBird中等长度长文本(数千tokens)
BigBird近O(n)推理速度更快,特别是超长文本极长文本(4k+ tokens及以上)

因此,BigBird在理论复杂度和实测推理速度上均优于Longformer,尤其适合非常长的文本处理任务。