步骤1 数据准备阶段

📌本阶段主要完成训练所需语料的加载、清洗、合并与保存,为后续词表训练和模型训练打好基础。

1.1 环境配置与依赖导入

  • 设置 Hugging Face 镜像源

  • 导入 datasetstransformerstokenizers 等依赖库

1.2 数据集加载与清洗

  • 加载 BookCorpus 与 Wikipedia 数据集

  • 保留 text 字段,去除无用列

  • 合并为一个大语料,并划分为训练集和测试集

1.3 文本保存(供词表训练使用)

  • 将训练集和测试集文本保存到本地 .txt 文件

  • 用于自定义分词器的训练数据源


步骤2 分词器构建阶段

📌本阶段使用训练语料构建自定义的 WordPiece 分词器,为后续模型提供适配的编码工具。

2.1 WordPiece 分词器训练

  • 用训练文本生成自定义词表(vocab)

  • 设置特殊 token、词表大小和最大长度

  • 保存 vocab 和分词器配置文件

2.2 分词器封装与加载

  • 将训练好的分词器加载为 BertTokenizerFast

  • 转为可被 Hugging Face 模型识别的标准接口格式


步骤3 数据编码与格式化阶段

📌本阶段将原始文本编码为模型输入格式,包括 token ID、attention mask 等,同时处理截断和拼接策略。

3.1 编码函数定义与选择

  • 根据是否截断定义不同的编码函数

  • 支持特殊 token 掩码的生成

3.2 数据集编码与转换

  • 使用 map() 批量处理训练集和测试集

  • 设置格式为 PyTorch 张量或 Python 列表

3.3 连续文本拼接(仅非截断模式)

  • 将分词后的文本拼接为长序列

  • 再划分为固定长度的训练块(便于语言模型学习上下文)


步骤4 模型构建与训练阶段

📌本阶段完成 BERT 模型结构的定义与初始化,配置训练参数,并使用 Trainer 启动语言模型的训练过程。

4.1 模型初始化与配置

  • 初始化 BERT 模型结构与词表大小等参数

  • 创建 BertForMaskedLM 语言模型实例

4.2 MLM 数据整理器设置

  • 使用 DataCollatorForLanguageModeling

  • 随机屏蔽输入中的 token 用于训练 [MASK] 预测能力

4.3 训练参数设置

  • 设置训练超参数,如 batch size、训练轮数、日志间隔、保存路径等

4.4 使用 Trainer 启动训练

  • 构建 Trainer 对象并传入数据

  • 启动训练流程,保存模型检查点


步骤5 模型评估与推理阶段

📌本阶段用于评估训练效果,加载模型权重并进行掩码预测任务测试,验证模型的语言理解能力。

5.1 模型与分词器加载

  • 加载保存的模型 checkpoint 和分词器

  • 保证推理阶段与训练配置一致

5.2 构建填空任务管道

  • 使用 pipeline("fill-mask") 创建填空任务

  • 给定含 [MASK] 的句子,输出模型预测结果与置信度评分

附录:代码来自于《大规模语言模型:从理论到实践》,书籍PDF网址:https://intro-llm.github.io/,添加了一点注释便于自己理解代码。

'''1.2.3 预训练语言模型实践'''

import os
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
import json
from datasets import concatenate_datasets, load_dataset
from tokenizers import BertWordPieceTokenizer
from transformers import BertTokenizerFast, BertConfig, BertForMaskedLM, Trainer, TrainingArguments, DataCollatorForLanguageModeling, pipeline
from itertools import chain
"""
datasets:Hugging Face 的 datasets 是一个强大的数据加载和处理库
tokenizers:这是 Hugging Face 的另一个独立库 tokenizers,使用 Rust 编写,用于训练和使用自定义分词器
transformers:这是 Hugging Face 的主力库,用于加载模型、训练模型、文本生成、微调、推理等所有 Transformer 模型相关任务。
            BertTokenizerFast功能:加载自定义或预训练的 vocab.txt + tokenizer.json 分词器
            BertConfig功能:用于配置 BERT 模型结构,如层数、隐藏维度、注意力头数量等
            BertForMaskedLM功能:BERT 模型 + Masked Language Modeling 头(训练时用 [MASK] 预测被遮住的 token)
            Trainer功能:用于训练 Transformer 模型的高阶 API,封装了训练/验证/保存/加载流程。
            TrainingArguments功能:控制训练时的各种超参数,如批大小、训练轮数、保存路径、使用什么设备、日志输出等。
            DataCollatorForLanguageModeling功能:自动进行 Mask 操作(随机替换部分 token 为 [MASK])来训练 BERT。
            pipeline功能:用于快速加载已训练模型,执行 NLP 任务,如文本生成、问答、情感分析等。
            """
#bookcorpus: 来自小说、故事等长文本语料
bookcorpus = load_dataset("bookcorpus", split = "train", trust_remote_code=True)
#wikipedia: 英文维基百科
wiki = load_dataset("wikipedia", "20220301.en", split = "train", trust_remote_code=True)
# 仅保留"text"列, remove_columns 是 Hugging Face datasets 库中 Dataset 对象的方法,用来从数据集中删除一列或多列字段。
wiki = wiki.remove_columns([col for col in wiki.column_names if col != "text"])

dataset = concatenate_datasets([bookcorpus, wiki])

#将数据集切分为90%用于训练,10%测试
d = dataset.train_test_split(test_size = 0.1) #返回一个 DatasetDict,包含两个键:"train"、"test"

def dataset_to_text(dataset, output_filename = "data.txt"):
    """将数据集文本保存到磁盘的通用函数"""
    with open(output_filename, "w") as f:
        for t in dataset["text"]:
            print(t, file = f) # 将变量 t 的内容写入到文件对象 f 中,而不是默认的终端/控制台输出。


# 将训练集保存为train.txt
dataset_to_text(d["train"], "train.txt")
# 将测试集保存为text.txt
dataset_to_text(d["text"], "text.txt")

### 2.训练词元分析器
special_tokens = [
    "[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]", "<S>", "<T>"
]
# 仅根据训练集合训练词元分析器
files = ["train.txt"]
# BERT中采用的默认词表大小为30522,可以随意修改
vocab_size = 30_522
# 最大序列长度,该数值越小,训练速度越快
max_length = 512
# 是否将长样本截断
truncate_longer_samples = False

# 初始化词元分析器
tokenizer = BertWordPieceTokenizer()
# 训练词元分析器
"""
files:要训练的文本文件路径(一个或多个)。每行应是一段自然语言文本
vocab_size:训练出来的词表大小, 决定了 BERT 能“记住”多少个基本“词”(含字、词根、词缀等),太小理解力不够,太大资源开销大。
special_tokens:special_tokens 是 BERT 使用中的“标点符号”,是模型结构中强依赖的“功能性 token”,必须手动指定。
"""
tokenizer.train(files=files, vocab_size=vocab_size,special_tokens=special_tokens)
#每条输入文本编码成 token 时,最多保留 N 个 token,超过部分自动截断丢弃。
tokenizer.enable_truncation(max_length=max_length)

# 保存词元分析器模型
model_path = "pretrained-bert"
if not os.path.isdir(model_path):
    os.mkdir(model_path)
tokenizer.save_model(model_path)

#将一些词元分析器中的配置保存到配置文件,包括特殊词元、转换为小写、最大序列长度等
with open(os.path.join(model_path,"config.json"), "w") as f:
    tokenizer_cfg = {
        "do_lower_case":True,
        "unk_token":"[UNK]",
        "sep_token":"[SEP]",
        "pad_token":"[PAD]",
        "cls_token":"[CLS]",
        "mask_token":"[MASK]",
        "model_max_length":max_length,
        "max_len":max_length,
    }
    json.dump(tokenizer_cfg, f)

# 当词元分析器进行训练和配置时,将其装载到BertTokenizerFast
# 把刚才训练好的词表和配置“装载”为 transformers 的标准 tokenizer 对象
# 注意,原来的训练 tokenizer 对象和装载后的 transformers tokenizer 对象只是命名成同一个,但是不同类型和用途的。
tokenizer = BertTokenizerFast.from_pretraned(model_path)

def encode_with_truncation(examples):
    """使用词元分析对句子进行处理并截断的映射函数"""
    return tokenizer(
    examples["text"],               # 输入文本,可以是字符串或字符串列表
    truncation=True,                # 超过 max_length 时截断
    padding="max_length",           # 填充到 max_length 长度
    max_length=max_length,          # 设定最大长度
    return_special_tokens_mask=True # 返回特殊 token 掩码,标识哪些 token 是特殊符号
)

def encode_without_truncation(example):
    """使用词元分析对句子进行处理但不截断的映射函数"""
    return tokenizer(example["text"],return_special_tokens_mask=True)

encode = encode_with_truncation if truncate_longer_samples else encode_without_truncation

# 对训练数据集进行分词处理
# batched:一次传入一批样本(批处理),而不是一条一条处理(效率更高)
"""
输入输出示例
输入:
{
  'text': ['Hello world'],
  'label': [1]
}

输出:
{
  'text': 'Hello world',
  'label': 1,
  'input_ids': [101, 7592, 2088, 102, 0, 0, 0, 0, 0, 0],
  'attention_mask': [1, 1, 1, 1, 0, 0, 0, 0, 0, 0]
  'special_tokens_mask':……
}

"""
train_dataset = d["train"].map(encode, batched=True)
# 对测试数据集进行分词处理
test_dataset = d["test"].map(encode, batched=True)
if truncate_longer_samples:
    # 移除其他列,将input_ids和attention_mask设置为pytorch张量
    train_dataset.set_format(type="torch", columns=["input_ids", "attention_mask"])
    test_dataset.set_format(type="torch", columns=["input_ids", "attention_mask"])
else:
    # 移除其他列,将它们保留为python列表
    test_dataset.set_format(columns = ["input_ids", "attention_mask", "special_tokens_mask"])
    train_dataset.set_format(columns = ["input_ids", "attention_mask", "special_tokens_mask"])
"""
dataset.set_format(
    type=None,               # 输出类型,如 'torch', 'numpy', 'pandas', 或 None(还原)
    columns=None,            # 保留的字段名列表
    output_all_columns=False # 是否返回所有字段,还是只返回 columns 指定的
)
"""


# 主要数据处理函数,拼接数据集中的所有文本并生成最大序列长度的块
def group_texts(examples):
    # 拼接所有文本
    concatenated_examples = {k: list(chain(*examples[k])) for k in examples.keys()}
    total_length = len(concatenated_examples[list(examples.keys())[0]])
    # 舍弃了剩余部分,如果模型支持填充而不是舍弃,则可以根据需要自定义这部分
    if total_length >= max_length:
        total_length = (total_length // max_length) * max_length
    # 按照最大长度分割成块
    result = {
        k: [t[i : i + max_length] for i in range(0, total_length, max_length)]
        for k, t in concatenated_examples.items()
    }
    return result

# 请注意,使用 batched=True,此映射一次处理 1000 个文本
# 因此,group_texts 会为这 1000 个文本组抛弃不足的部分
# 可以在这里调整 batch_size,但较高的值可能会使预处理速度变慢
#
# 为了加速这一部分,使用了多进程处理
if not truncate_longer_samples:
    train_dataset = train_dataset.map(group_texts, batched=True,
                                      desc=f"Grouping texts in chunks of {max_length}")
    test_dataset = test_dataset.map(group_texts, batched=True,
                                    desc=f"Grouping texts in chunks of {max_length}")
    # 将它们从列表转换为 PyTorch 张量
    train_dataset.set_format("torch")
    test_dataset.set_format("torch")

# 使用配置文件初始化模型
model_config = BertConfig(vocab_size=vocab_size, max_position_embeddings=max_length)
model = BertForMaskedLM(config=model_config)

# 初始化数据整理器,随机屏蔽 20%(默认为 15%)的标记
# 用于掩盖语言建模(MLM)任务
data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer, mlm=True, mlm_probability=0.2
)

training_args = TrainingArguments(
    output_dir=model_path,          # 输出目录,用于保存模型检查点
    evaluation_strategy="steps",    # 每隔 `logging_steps` 步进行一次评估
    overwrite_output_dir=True,
    num_train_epochs=10,            # 训练时的轮数,可以根据需要进行调整
    per_device_train_batch_size=10, # 训练批量大小,可以根据 GPU 内存容量将其设置得尽可能大
    gradient_accumulation_steps=8,  # 在更新权重之前累积梯度
    per_device_eval_batch_size=64,  # 评估批量大小
    logging_steps=1000,             # 每隔 1000 步进行一次评估,记录并保存模型检查点
    save_steps=1000,
    # load_best_model_at_end=True,  # 是否在训练结束时加载最佳模型(根据损失)
    # save_total_limit=3,           # 如果磁盘空间有限,则可以限制只保存 3 个模型权重
)

trainer = Trainer(
    model=model,
    args=training_args,
    data_collator=data_collator,
    train_dataset=train_dataset,
    eval_dataset=test_dataset,
)

# 训练模型
trainer.train()

# 加载模型检查点
model = BertForMaskedLM.from_pretrained(os.path.join(model_path, "checkpoint-10000"))
# 加载词元分析器
tokenizer = BertTokenizerFast.from_pretrained(model_path)

fill_mask = pipeline("fill-mask", model=model, tokenizer=tokenizer)

# 进行预测
examples = [
  "Today's most trending hashtags on [MASK] is Donald Trump",
  "The [MASK] was cloudy yesterday, but today it's rainy.",
]
for example in examples:
  for prediction in fill_mask(example):
    print(f"{prediction['sequence']}, confidence: {prediction['score']}")
  print("="*50)

Logo

更多推荐