Transformers 中的 BERT 模型:双向编码器架构、预训练目标与下游任务实战指南
【免费下载链接】transformers🤗 Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and multimodal models, for both inference and training.项目地址: https://gitcode.com/GitHub_Trending/tra/transformers
BERT(Bidirectional Encoder Representations from Transformers)是自然语言处理领域里程碑式的预训练语言模型,其核心思想是通过随机遮蔽输入 Token 并预测被遮蔽内容,让模型同时利用左侧与右侧上下文进行双向理解。本文以本仓库(Hugging Face Transformers)的官方 BERT 文档为主体,结合src/transformers/models/bert/下的源码实现与tests/models/bert/测试用例,系统讲解 BERT 的模型原理、预训练目标、配置参数、分词器以及从填词到问答等六大下游任务的实战用法。
BERT 模型概览:从论文到 Transformers 实现
BERT 由 Jacob Devlin、Ming-Wei Chang、Kenton Lee 与 Kristina Toutanova 在论文BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding中提出。它是一个在无标注文本上进行预训练的双向 Transformer 编码器,训练目标包含两项:
- 掩码语言建模(Masked Language Modeling, MLM):随机遮蔽句中的部分 Token,让模型结合左右两侧上下文预测被遮蔽的 Token;
- 下一句预测(Next Sentence Prediction, NSP):判断输入的两句文本在语料中是否连续相邻。
论文摘要中的关键结论是:BERT 在所有层上同时对左右上下文进行联合条件建模,从而从无标注文本中学习到深层的双向表示;预训练完成后,只需在输出层追加一个额外的任务头(head)即可微调出面向问答、语言推理等广泛任务的最强模型,且几乎不需要任务相关的架构改动。原始 BERT 在 11 项 NLP 任务上刷新了当时的最优结果,例如将 GLUE 得分提升至 80.5%(绝对提升 7.7 个百分点)、MultiNLI 准确率 86.7%、SQuAD v1.1 F1 达 93.2、SQuAD v2.0 F1 达 83.1。
本仓库中 BERT 的完整实现集中在 src/transformers/models/bert/ 目录,包含 configuration_bert.py(配置类)、modeling_bert.py(PyTorch 模型实现,共约 1390 行)、tokenization_bert.py 与 tokenization_bert_legacy.py(分词器),以及三个用于把原始 TensorFlow 检查点转换为 PyTorch 格式的转换脚本。
BERT 使用要点(Usage Tips)
文档针对 BERT 的工程使用给出了两条核心建议,它们直接源于模型的架构与训练方式:
1. 输入应在右侧填充
BERT 使用绝对位置嵌入(absolute position embeddings),位置信息由嵌入表按绝对位置索引得到。因此,当批内样本长度不一致而需要 padding 时,应统一在右侧补齐[PAD],保证每个 Token 的绝对位置编号在其真实上下文中保持不变。这一点与使用相对位置编码的模型(如 T5、GPT 系)习惯不同,实践中若采用左侧填充,可能引入位置错位并轻微损害效果。
源码佐证:在 modeling_bert.py 的BertEmbeddings中,position_embeddings是一个大小为max_position_embeddings × hidden_size的普通nn.Embedding,前向时直接按position_ids取绝对位置向量并加到词嵌入与段嵌入之上。
2. 输入破坏策略:80% / 10% / 10% 随机掩码
预训练期间,输入会通过随机掩码进行破坏。具体地,通常对每个序列中15%的 Token 做如下处理:
- 80% 概率替换为特殊的
[MASK]Token; - 10% 概率替换为一个与被遮蔽 Token 不同的随机 Token;
- 10% 概率保持原 Token 不变。
这种「不完全遮蔽」策略是为了缓解预训练(只见[MASK])与微调/推理(不见[MASK])之间的分布不一致问题:模型必须学会依赖上下文推断被遮蔽位置的真实 Token,而不是简单地把[MASK]映射回原词。
3. 下一句预测(NSP)的输入构造
BERT 的第二个训练目标是句子级任务。输入由句子 A 与句子 B 组成,中间以分隔 Token[SEP]隔开:
- 50% 概率:句子 B 是语料中紧跟在句子 A 之后的真实下一句(标记为 IsNext);
- 50% 概率:句子 B 是从语料中随机抽取的无关句子(标记为 NotNext)。
模型需要输出二分类结果,判断两句是否连续。该目标帮助模型学习句子间的关系,为问答、自然语言推理等句子对任务打下基础。在 modeling_bert.py 的BertForNextSentencePrediction中可以看到,其头部BertOnlyNSPHead直接作用在BertModel输出的pooled_output上,输出形状为(batch_size, 2)的二分类 logits,labels 取 0(IsNext)或 1(NotNext)。
4. 能力边界:适合 NLU,不适合文本生成
由于 BERT 使用 MLM 与 NSP 目标训练,它在预测掩码 Token、抽取式问答等自然语言理解(NLU)任务上高效且通用,但并非为文本生成而设计。若需要生成能力,可考虑BertLMHeadModel(将 BERT 作为解码器使用时需在配置中设置is_decoder=True)或直接选用 GPT 系自回归模型。
快速上手:三种方式预测 [MASK]
官方英文文档给出了通过Pipeline与AutoModel两种方式预测[MASK]的示例,下面完整呈现并补充命令行方式,模型统一使用google-bert/bert-base-uncased。
方式一:使用 Pipeline(一行代码)
from transformers import pipeline pipeline = pipeline( task="fill-mask", model="google-bert/bert-base-uncased", device=0 ) pipeline("Plants create [MASK] through a process known as photosynthesis.")device=0表示使用第一块 GPU;CPU 环境可省略该参数。fill-mask是 Transformers 内置的「掩码填充」流水线,其内部会调用BertForMaskedLM完成预测。
方式二:使用 AutoModel 手动推理
import torch from transformers import AutoModelForMaskedLM, AutoTokenizer tokenizer = AutoTokenizer.from_pretrained( "google-bert/bert-base-uncased", ) model = AutoModelForMaskedLM.from_pretrained( "google-bert/bert-base-uncased", device_map="auto", attn_implementation="sdpa" ) inputs = tokenizer("Plants create [MASK] through a process known as photosynthesis.", return_tensors="pt").to(model.device) with torch.no_grad(): outputs = model(**inputs) predictions = outputs.logits masked_index = torch.where(inputs['input_ids'] == tokenizer.mask_token_id)[1] predicted_token_id = predictions[0, masked_index].argmax(dim=-1) predicted_token = tokenizer.decode(predicted_token_id) print(f"The predicted token is: {predicted_token}")这段代码的关键步骤:
- 用
AutoTokenizer将句子编码为input_ids、token_type_ids、attention_mask等张量; - 用
AutoModelForMaskedLM加载模型,attn_implementation="sdpa"指定使用 PyTorch 的 Scaled Dot-Product Attention 实现以提升效率,device_map="auto"自动分配设备; - 通过
tokenizer.mask_token_id定位[MASK]在序列中的位置; - 在
logits中取该位置概率最大的 Token id,再用tokenizer.decode还原为文本。
方式三:命令行预测
echo "Plants create [MASK] through a process known as photosynthesis." | \ transformers-cli fill-mask --model google-bert/bert-base-uncased架构深度解析:从源码看 BERT 的内部结构
BertConfig:核心超参数一览
configuration_bert.py 中的BertConfig定义了 BERT 的全部结构超参数,其默认值与google-bert/bert-base-uncased检查点一致:
| 参数 | 默认值 | 含义 |
|---|---|---|
vocab_size | 30522 | 词表大小(uncased 模型) |
hidden_size | 768 | 隐层维度 |
num_hidden_layers | 12 | Transformer 编码器层数 |
num_attention_heads | 12 | 每层注意力头数 |
intermediate_size | 3072 | 前馈网络中间层维度(通常为 4×hidden_size) |
hidden_act | "gelu" | 前馈层激活函数 |
hidden_dropout_prob | 0.1 | 隐层 Dropout 概率 |
attention_probs_dropout_prob | 0.1 | 注意力权重 Dropout 概率 |
max_position_embeddings | 512 | 最大序列长度(绝对位置嵌入表大小) |
type_vocab_size | 2 | 段(token_type)嵌入数量,对应句子 A/B |
initializer_range | 0.02 | 权重初始化标准差 |
layer_norm_eps | 1e-12 | LayerNorm 的 epsilon |
pad_token_id | 0 | padding Token 的 id |
classifier_dropout | None | 分类头 Dropout,缺省时回退到hidden_dropout_prob |
is_decoder/add_cross_attention | False | 是否作为解码器 / 是否添加交叉注意力 |
BertConfig.model_type = "bert"使AutoModel等自动类能够识别该配置。初始化一个随机权重的 BERT 模型只需:
from transformers import BertConfig, BertModel # 初始化一个 bert-base-uncased 风格的配置 configuration = BertConfig() # 从配置初始化模型(随机权重) model = BertModel(configuration) # 访问模型配置 configuration = model.configBertEmbeddings:词嵌入 + 段嵌入 + 绝对位置嵌入
BertEmbeddings(modeling_bert.py)将三类嵌入相加后经过 LayerNorm 与 Dropout:
word_embeddings:nn.Embedding(vocab_size, hidden_size, padding_idx=pad_token_id),词嵌入;position_embeddings:nn.Embedding(max_position_embeddings, hidden_size),绝对位置嵌入;token_type_embeddings:nn.Embedding(type_vocab_size, hidden_size),区分句子 A/B 的段嵌入。
前向过程为embeddings = word + token_type + position,随后做 LayerNorm 和 Dropout。position_ids与token_type_ids以非持久化的 Buffer 形式注册,便于在未显式传入时自动生成。
BertModel:双向编码器 + 池化层
BertModel(modeling_bert.py)由BertEmbeddings、BertEncoder(多层BertLayer)与可选的BertPooler组成,对应文档中的BertModelAPI:
BertEncoder由config.num_hidden_layers个BertLayer堆叠,每个BertLayer内部是「多头自注意力 + 前馈网络」的残差结构,注意力实现通过ALL_ATTENTION_FUNCTIONS注册表支持 eager 与 SDPA 等不同后端(对应文档中attn_implementation="sdpa"的用法);BertPooler取[CLS]位置的隐状态经过线性层与 Tanh 激活,输出(batch_size, hidden_size)的句子级表示,供分类任务使用;forward支持input_ids、attention_mask、token_type_ids、position_ids、inputs_embeds、past_key_values等参数,可返回BaseModelOutputWithPoolingAndCrossAttentions。
由于BertModel可通过is_decoder=True与add_cross_attention=True配置为带交叉注意力的解码器,它既能作为纯编码器使用,也能嵌入 Seq2Seq 架构(源码 docstring 中明确说明了这一行为)。
六大任务头:一套主干,多种输出
所有任务模型共享BertModel主干,仅在顶部挂载不同头部(均在 modeling_bert.py 中实现):
| 模型类 | 任务 | 头部结构 | 输出 |
|---|---|---|---|
BertForPreTraining | 预训练(MLM+NSP) | MLM 头 + NSP 头 | BertForPreTrainingOutput(loss、prediction_logits、seq_relationship_logits) |
BertLMHeadModel | 因果语言建模 | LM 头 + 交叉注意力 | CausalLMOutputWithCrossAttentions |
BertForMaskedLM | 掩码语言建模 | BertOnlyMLMHead | MaskedLMOutput,labels 中-100位置被忽略 |
BertForNextSentencePrediction | 下一句预测 | BertOnlyNSPHead | NextSentencePredictorOutput,labels 取 0/1 |
BertForSequenceClassification | 文本分类/回归 | pooled 输出上的线性层 | SequenceClassifierOutput,num_labels==1时计算 MSE 回归损失 |
BertForMultipleChoice | 多项选择 | 每个选项共享编码器 | MultipleChoiceModelOutput |
BertForTokenClassification | 序列标注(NER 等) | 每个 Token 位置上的线性层 | TokenClassifierOutput |
BertForQuestionAnswering | 抽取式问答 | 起止位置打分头 | QuestionAnsweringModelOutput |
BertForPreTrainingOutput是 BERT 专属的输出数据结构(modeling_bert.py),包含总损失(MLM 损失与 NSP 损失之和)、掩码预测 logits 与句子关系 logits,以及可选的hidden_states与attentions。
分词器:BertTokenizer 家族
BERT 使用 WordPiece 子词分词。仓库中提供了三代分词器:
BertTokenizer(tokenization_bert.py):原生 Python 实现,基于vocab_file(词表)与可选的do_lower_case(是否转小写)、tokenize_chinese_chars(是否按字切分中文)、strip_accents(是否去除重音符号)等参数,提供get_special_tokens_mask与save_vocabulary两个文档化方法;BertTokenizerLegacy(tokenization_bert_legacy.py):旧版 Python 分词器,保留 BasicTokenizer(小写化、标点切分、中文按字切分、去除重音)与 WordpieceTokenizer 两阶段流程;BertTokenizerFast:基于 🤗 Tokenizers 库的 Rust 加速实现。
特殊 Token 约定:[UNK](未知词)、[SEP](句间分隔)、[PAD](填充)、[CLS](句首分类 Token)、[MASK](掩码 Token)。get_special_tokens_mask用于在已有特殊 Token 的序列上标记哪些位置属于特殊 Token,方便训练时排除它们参与损失计算。
下游任务与官方资源导航
将 BERT 应用到具体任务时,可参考本仓库 examples/pytorch/ 下的官方示例脚本(均支持BertFor*系列模型)与任务指南:
- 文本分类(
BertForSequenceClassification):示例脚本 examples/pytorch/text-classification,任务指南 docs/source/en/tasks/sequence_classification; - 序列标注/NER(
BertForTokenClassification):示例脚本 examples/pytorch/token-classification,任务指南 docs/source/en/tasks/token_classification;训练时若希望把词级标签传播到所有 WordPiece 子词,需要自定义标签对齐逻辑; - 掩码语言建模(
BertForMaskedLM):示例脚本 examples/pytorch/language-modeling(支持 BERT/RoBERTa/DistilBERT 的 MLM 训练),任务指南 docs/source/en/tasks/masked_language_modeling; - 抽取式问答(
BertForQuestionAnswering):示例脚本 examples/pytorch/question-answering,任务指南 docs/source/en/tasks/question_answering; - 多项选择(
BertForMultipleChoice):示例脚本 examples/pytorch/multiple-choice,任务指南 docs/source/en/tasks/multiple_choice; - 文本生成:可将 BERT 用作 EncoderDecoder 的编码器进行 warm-start 微调(如 BERT2BERT 摘要),相关思路可参考 examples/pytorch/summarization。
以上任务类在测试中均有覆盖:tests/models/bert/test_modeling_bert.py中的BertModelTest(tests/models/bert/test_modeling_bert.py)同时继承了ModelTesterMixin、GenerationTesterMixin与PipelineTesterMixin,并针对fill-mask、分类、多项选择、下一句预测、预训练、因果 LM 等逐个编写了test_for_*用例;分词器行为由 tests/models/bert/test_tokenization_bert.py 与 tests/models/bert/test_tokenization_bert_legacy.py 验证。
实战路线图
围绕本文内容,推荐的学习与落地路径:
- 理解原理:先通过本文「模型概览」与「使用要点」掌握 MLM、NSP 与 80/10/10 掩码策略;
- 跑通推理:用 Pipeline 或 AutoModel 完成一次
[MASK]预测,验证双向上下文理解能力; - 深入架构:对照 modeling_bert.py 与 configuration_bert.py 阅读嵌入层、编码器层与各类任务头,理解参数如何影响模型结构;
- 微调下游任务:选择 examples/pytorch/ 中与业务最接近的任务脚本(分类、NER、问答等),替换数据集与模型名即可开始微调;
- 迁移旧权重:若持有原始 TensorFlow 检查点,可使用仓库提供的 convert_bert_original_tf_checkpoint_to_pytorch.py 等转换脚本迁移到 PyTorch 格式。
BERT 的「双向编码 + 简单任务头」范式深刻影响了后续 RoBERTa、DistilBERT、ALBERT 等大量模型,理解其架构与使用方式是掌握整个 Transformers 生态的重要起点。
【免费下载链接】transformers🤗 Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and multimodal models, for both inference and training.项目地址: https://gitcode.com/GitHub_Trending/tra/transformers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考