从零构建并训练基于 BERT 架构的生成式大模型
本文旨在详细阐述从零开始构建并训练基于 BERT 架构的大语言模型的完整流程。与常见的微调(Fine-tuning)不同,本指南侧重于预训练(Pre-training)阶段,涵盖分词器(Tokenizer)的独立训练、模型架构的配置、训练循环的实现以及推理测试。通过该教程,开发者可以深入理解 NLP 底层机制,掌握从数据预处理到模型部署的关键环节。
第一部分:Tokenizer 分词器训练
BERT 模型通常使用 WordPiece 算法进行分词。分词器的训练过程分为四个核心步骤:归一化(Normalize)、预分词(Pre-tokenizer)、模型训练(Model)和后处理(Post-processor)。
1. 环境准备与依赖导入
在开始之前,请确保已安装 tokenizers 和 transformers 库。建议使用 Python 3.8+ 环境。
from tokenizers import Tokenizer, processors
from tokenizers.models import WordPiece
from tokenizers.trainers import WordPieceTrainer
from tokenizers.normalizers import BertNormalizer
from tokenizers.pre_tokenizers import BertPreTokenizer
from tokenizers.decoders import WordPiece as WordPieceDecoder
2. 初始化 Tokenizer 与数据集
我们需要实例化一个 WordPiece 模型,并指定未识别词的标记(unk_token)。同时加载用于训练的文本文件。此处以《三国演义》为例,实际应用中应替换为大规模语料。
tokenizer = Tokenizer(WordPiece(unk_token="[UNK]"))
files = ["./sanguo.txt"]
3. 配置归一化(Normalize)
BERT 标准要求将文本转换为小写并去除多余空格。我们使用 BertNormalizer 进行配置。
tokenizer.normalizer = BertNormalizer(lowercase=True)
4. 配置预分词(Pre-tokenizer)
预分词器负责将原始字符串分割成初步的单词或子词单元。BERT 默认使用空格和标点作为分隔符。
tokenizer.pre_tokenizer = BertPreTokenizer()
5. 添加特殊标记并训练模型
定义特殊标记集合,包括未知词、填充、分类和掩码标记。设置词汇表大小(vocab_size),通常为 30000 至 50000 之间,根据显存和任务需求调整。
special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"]
trainer = WordPieceTrainer(
vocab_size=50000,
show_progress=True,
special_tokens=special_tokens
)
tokenizer.train(files, trainer)
6. 后处理及解码器配置
为了符合 BERT 的输入格式,需要配置 TemplateProcessing 来处理单句和双句的 [CLS] 和 [SEP] 标记位置。同时设置解码器前缀,以便 WordPiece 还原时能正确拼接子词。
class_token_id = tokenizer.token_to_id("[CLS]")
sep_token_id = tokenizer.token_to_id("[SEP]")
tokenizer.post_processor = processors.TemplateProcessing(
single=f"[CLS]:0 $A:0 [SEP]:0",
pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1",
special_tokens=[("[CLS]", class_token_id), ("[SEP]", sep_token_id)],
)
tokenizer.decoder = WordPieceDecoder(prefix="##")
7. 保存分词器
将训练好的分词器保存为 JSON 格式,以便后续加载。
tokenizer.save("tokenizer.json")
若要在 Hugging Face Transformers 中使用此分词器,需将其包装在 BertTokenizerFast 类中。
from transformers import BertTokenizerFast
wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer)
wrapped_tokenizer.save_pretrained("./bert")
此时,./bert 目录下将包含 vocab.txt、tokenizer.json 等必要文件。
第二部分:模型训练
1. 导入训练相关模块
加载模型配置、模型类、数据集加载器及训练参数。
from transformers import (
BertConfig, BertLMHeadModel, BertTokenizer,
LineByLineTextDataset, DataCollatorForLanguageModeling,
Trainer, TrainingArguments
)
注意:LineByLineTextDataset 在新版 Transformers 中可能已被弃用,建议在实际生产环境中使用 datasets 库中的 load_dataset 替代,或自定义 Dataset 类以确保兼容性。
2. 加载分词器
加载第一步中训练并保存的分词器。
tokenizer = BertTokenizer.from_pretrained("./bert")
3. 模型配置与初始化
配置 BERT 模型结构。关键点是将 is_decoder 设置为 True,使其具备自回归生成能力(类似 GPT 模式),尽管底层仍是 BERT 架构。
config = BertConfig(
vocab_size=tokenizer.vocab_size,
is_decoder=True
)
model = BertLMHeadModel(config)
4. 数据加载与处理
加载文本数据并进行分块处理。block_size 决定了每个样本的最大长度,需根据显存大小调整。
dataset = LineByLineTextDataset(
tokenizer=tokenizer,
file_path="./sanguo.txt",
block_size=512
)
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer,
mlm=False,
mlm_probability=0.15
)
5. 训练参数设定
配置训练超参数,包括输出目录、训练轮数、批次大小及保存策略。
training_args = TrainingArguments(
output_dir="./output",
overwrite_output_dir=True,
num_train_epochs=3,
per_device_train_batch_size=4,
save_steps=1000,
save_total_limit=2,
logging_steps=100,
learning_rate=5e-5,
weight_decay=0.01
)
trainer = Trainer(
model=model,
args=training_args,
data_collator=data_collator,
train_dataset=dataset
)
trainer.train()
6. 模型保存
训练完成后,保存模型权重和配置。
model.save_pretrained("./bert_model")
第三部分:模型推理测试
训练完成后,可以使用 Pipeline 接口进行文本生成测试。建议设置随机种子以保证结果可复现。
from transformers import pipeline, set_seed
generator = pipeline("text-generation", model="./bert_model")
set_seed(42)
result = generator("吕布", max_length=50, do_sample=True, top_k=50, temperature=0.7)
print(result)
再次尝试不同的提示词:
result = generator("接着奏乐", max_length=50, do_sample=True, top_k=50, temperature=0.7)
print(result)
第四部分:常见问题与优化建议
1. 显存不足问题
如果训练过程中出现 OOM(Out Of Memory)错误,请尝试以下措施:
- 减小
per_device_train_batch_size。
- 减小
block_size。
- 启用梯度累积(Gradient Accumulation)。
- 使用混合精度训练(FP16),在
TrainingArguments 中设置 fp16=True。
2. 过拟合与欠拟合
- 过拟合:验证集 Loss 上升而训练集 Loss 下降。可增加 Dropout 比例,减少训练轮数,或使用正则化。
- 欠拟合:Loss 下降缓慢或不降。可增加学习率,延长训练时间,或检查数据质量。
3. 生成质量优化
在推理阶段,调整采样参数可显著影响生成效果:
temperature:值越小越保守,值越大越随机。
top_p:核采样,限制候选词范围。
repetition_penalty:惩罚重复出现的 token,防止死循环。
总结
本文详细介绍了从零构建 BERT 架构大模型的完整技术链路。从分词器的 WordPiece 训练,到模型架构的解码器模式配置,再到具体的训练循环实现与推理测试,涵盖了核心代码与关键参数说明。通过实践本教程,开发者能够掌握大模型预训练的基础技能,为进一步探索更复杂的 Transformer 变体打下坚实基础。