BERT预训练全流程解析:从数据准备到模型微调

1次阅读
没有评论

共计 2549 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

背景介绍

BERT(Bidirectional Encoder Representations from Transformers)是 Google 在 2018 年提出的预训练语言模型,通过 Masked Language Model(MLM)和 Next Sentence Prediction(NSP)任务进行预训练。预训练的重要性在于模型可以学习通用的语言表示,从而在下游任务(如文本分类、问答系统等)中通过微调快速适配,显著提升性能。

BERT 预训练全流程解析:从数据准备到模型微调

数据准备

文本预处理方法

BERT 的输入数据需要经过一系列预处理步骤:

  1. 分词:使用 WordPiece 分词器,将文本分割成子词单元。例如,”unhappiness” 可能被分割为 ”un”, “##happiness”。
  2. 构造 Masked LM 样本:随机遮盖输入文本中 15% 的 token,其中 80% 替换为[MASK],10% 替换为随机 token,10% 保持不变。
  3. 构造 NSP 样本:50% 的样本为连续的句子对(正样本),50% 为随机组合的句子对(负样本)。

数据并行处理技巧

处理大规模文本数据时,可以采用以下方法加速:

  • 使用多进程并行处理数据,例如 Python 的 multiprocessing 模块。
  • 将预处理后的数据保存为 TFRecord 或 HDF5 格式,便于快速加载。

示例代码

from transformers import BertTokenizer
import numpy as np

tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

def preprocess(text):
    # 分词
    tokens = tokenizer.tokenize(text)
    # 随机遮盖 15% 的 token
    masked_tokens = []
    labels = []
    for token in tokens:
        if np.random.rand() < 0.15:
            rand = np.random.rand()
            if rand < 0.8:
                masked_tokens.append('[MASK]')
            elif rand < 0.9:
                masked_tokens.append(token)
            else:
                masked_tokens.append(tokenizer.vocab[random.choice(list(tokenizer.vocab.keys()))])
            labels.append(tokenizer.convert_tokens_to_ids(token))
        else:
            masked_tokens.append(token)
            labels.append(-100)  # 忽略未遮盖的 token
    return masked_tokens, labels

模型架构

超参数选择

BERT-base 模型的典型超参数如下:

  • Transformer 层数:12
  • 隐藏层维度:768
  • 注意力头数:12
  • 总参数量:约 110M

对于更大规模的模型(如 BERT-large),可以增加层数和隐藏层维度。

注意力机制实现细节

BERT 使用多头自注意力机制,计算过程如下:

  1. 将输入嵌入分为多个头,每个头独立计算注意力。
  2. 计算 Query、Key、Value 矩阵:
    Q = torch.matmul(x, W_q)
    K = torch.matmul(x, W_k)
    V = torch.matmul(x, W_v)
  3. 计算注意力分数并缩放:
    attention_scores = torch.matmul(Q, K.transpose(-1, -2)) / sqrt(d_k)
    attention_probs = torch.softmax(attention_scores, dim=-1)
  4. 输出加权和:
    context = torch.matmul(attention_probs, V)

训练策略

学习率调度

BERT 使用线性预热(Linear Warmup)和学习率衰减策略:

  1. 在前 10% 的训练步数中,学习率从 0 线性增加到初始学习率(如 2e-5)。
  2. 之后线性衰减到 0。

批次大小设置

由于 BERT 模型较大,通常使用较大的批次(如 256 或 512)以提高训练效率。如果显存不足,可以使用梯度累积(Gradient Accumulation)。

混合精度训练

使用 FP16 混合精度训练可以显著减少显存占用并加速训练:

from torch.cuda.amp import GradScaler, autocast

scaler = GradScaler()

with autocast():
    outputs = model(inputs)
    loss = outputs.loss

scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()

性能优化

分布式训练实现

使用 PyTorch 的DistributedDataParallel(DDP)进行多 GPU 训练:

torch.distributed.init_process_group(backend='nccl')
model = torch.nn.parallel.DistributedDataParallel(model)

内存优化技巧

  1. 使用梯度检查点(Gradient Checkpointing):
    from torch.utils.checkpoint import checkpoint
    
    def forward(self, x):
        return checkpoint(self._forward, x)
  2. 优化注意力计算:使用稀疏注意力或内存高效的注意力实现。

避坑指南

常见训练失败原因分析

  1. 梯度爆炸:初始学习率过高或未进行梯度裁剪。
  2. 显存不足:批次大小过大或模型参数过多。
  3. 训练不稳定:未使用学习率预热或混合精度训练配置不当。

超参数调优建议

  1. 初始学习率:通常在 1e- 5 到 5e- 5 之间。
  2. 批次大小:根据显存调整,尽可能使用较大的批次。
  3. 预热步数:总训练步数的 10%。

微调建议

将预训练模型适配到下游任务时,通常需要:

  1. 根据任务类型添加输出层(如分类层)。
  2. 使用较小的学习率(如 5e-6)进行微调。
  3. 冻结部分底层参数,只训练顶层参数。

思考题

如何评估预训练模型的质量?

  1. 下游任务性能:在多个标准任务(如 GLUE 基准)上微调并评估。
  2. 探针任务:设计简单的任务(如词性标注)测试模型的语言理解能力。
  3. 内部评估:检查 MLM 和 NSP 任务的准确率。
正文完
 0
评论(没有评论)