标签TokenClassification

标签TokenClassification

spaCy的NER标签 → RoBERTa可用的标签

数据结构:

1
2
3
4
5
6
7
8
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标签。 所以转换流程就是:


步骤 1:用RoBERTa的tokenizer分词

RoBERTa的分词器是BPE,不是spaCy的词切分,所以字符位置和token位置不会一一对应,必须重新对齐。

1
2
3
4
5
6
from transformers import RobertaTokenizerFast

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

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

输出示例:

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

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


步骤 2:字符级span → token级span

用tokenizer的 char_to_token 方法,把字符起止位置映射到token位置。

1
2
3
4
encoding = tokenizer(text, return_offsets_mapping=True)
offsets = encoding["offset_mapping"]

print(offsets)

输出:

1
[(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对应哪个字符区间。


步骤 3:BIO标签初始化

先给所有token都标记为 "O"(Outside)。

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

步骤 4:实体字符位置 → 对应token位置

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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

步骤 5:查看结果

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

输出可能是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
女              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
1
2
3
原文:  女 大专 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_allocator GPU 内存分配器名称 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_size nlp.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 中加入组件时,需要在这里添加对应的块,例如:

1
2
3
[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.1 Dropout 率(对所有支持 dropout 的层生效) 训练时随机丢弃 10% 的神经元,防止过拟合。
accumulate_gradient = 1 梯度累积步数 若设为 >1,会在更新前累计多次 mini‑batch 的梯度,等效于增大 batch size 而不占用更多显存。
patience = 1600 Early‑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.9 Adam 一阶矩(动量)衰减系数 与原始 Adam 相同,控制 梯度的指数移动平均
beta2 = 0.999 Adam 二阶矩(方差)衰减系数 控制 梯度平方的指数移动平均
L2_is_weight_decay = true L2 正则 解释为 权重衰减(而非普通的 L2 损失) true 时,更新公式会变成 θ ← θ - lr * (grad + λ * θ),更符合 AdamW 的实现。
L2 = 0.01 权重衰减系数 λ 对所有 可学习参数(除 bias)施加衰减。值越大正则越强,防止过拟合。
grad_clip = 1.0 梯度裁剪阈值(全局 L2 范数) 若梯度范数 > 1.0,则会 按比例缩放,防止梯度爆炸。
use_averages = false 是否使用 参数滑动平均(EMA) true,会在训练结束后把模型参数替换为 历史平均值(有助于提升泛化),但会占用额外显存。
eps = 1e-8 Adam 的 数值稳定项 防止除零错误。
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 = null lookup 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() 不会因缺失信息报错。

📌 小结(关键关联)

区块 关键变量 与其它区块的关联
paths train/dev/vectors/init_tok2vec corporainitializenlp.vectors 引用
system seed/gpu_allocator trainingnlp 继承
nlp pipeline、tokenizer、vectors pipeline 指向 componentsvectorsinitialize.vectors 同步
corpora path、augmenter、gold_preproc path${paths.xxx}augmenter 可在 trainingbatcher 中使用
training max_steps、eval_frequency、optimizer、batcher optimizer.learn_ratelearning‑rate schedule(若另建)配合;batcher.size 决定每 step 的 batch 大小,进而影响 梯度累积显存
initialize vectors、init_tok2vec nlp 对象提供 预训练资源,在 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_ids attention_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(最安全的改动)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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(不推荐)

1
2
3
# 假设你想把词表里 "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(极度危险)

1
2
3
4
# 把 <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 错位,标签会被投射到错误的向量上,导致 损失函数 计算错误,模型学习不到正确的实体边界。
损失函数的 Mask ignore_index=-100(用于 MLM)是基于 特定 IDpad_token_id)来过滤 padding。ID 改动后,ignore_index 失效,会把 padding 位置计入 loss,降低训练质量。
跨模型迁移 BERT‑base 的权重直接加载到 词表不同BERT‑large(或自定义 tokenizer)时,权重会 错位,导致 几乎所有层的输出都不对

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

步骤 代码示例(HuggingFace) 关键点
① 加载原始 tokenizer & model python\nfrom transformers import AutoTokenizer, AutoModel\ntokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")\nmodel = AutoModel.from_pretrained("bert-base-uncased")\n 保证两者版本匹配。
② 添加新 token python\new_tokens = ["[MED]", "[FIN]", "[GEO]"]\nadded = tokenizer.add_tokens(new_tokens) # 只在词表末尾追加\n add_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 & model python\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):

1
2
3
4
5
6
7
8
9
10
11
12
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.transform MLM 头 只在 BertForMaskedLM
classifier.weightclassifier.bias 通用分类/序列标注头 只在 *ForSequenceClassification*ForTokenClassification
qa_outputs.weightqa_outputs.bias QA 头 只在 *ForQuestionAnswering
lm_head.weightlm_head.bias Causal/Masked LM 生成头 只在 *LMHeadModel
adapter.*lora_* Adapter / LoRA 参数 只在使用这些轻量化微调技术的 checkpoint
1
2
3
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

1
2
3
4
5
6
{
"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

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

    1
    2
    3
    4
    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

    1
    2
    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):

1
2
3
4
5
6
7
8
9
[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:

1
2
3
4
5
6
7
8
9
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_size outputs.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: null RoPE(旋转位置编码)参数,θ 越大对长序列越友好。
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. 加载模型
    1
    2
    3
    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
    1
    2
    3
    4
    5
    6
    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(去掉语言模型头)
    1
    2
    3
    4
    5
    6
    [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
    1
    2
    3
    4
    python -m spacy init config ernie_ner.cfg \
    --lang zh \
    --pipeline transformer,ner \
    --optimize efficiency
  3. 微调 NER(和前面示例的 Longformer、BigBird 等一样)
    1
    2
    3
    4
    5
    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)

1
2
3
4
5
6
7
8
9
10
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)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 进行微调(示例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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)

1
2
3
4
5
6
7
8
9
10
11
12
13
# 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"

然后:

1
2
3
4
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则在部分场景速度稍逊,但资源使用较少,适合中等长度长文本。

总结:

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

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