大模型原理与实践:第三章-预训练语言模型详解_第1部分-Encoder-only(BERT、RoBERTa、ALBERT)

预训练语言模型详解

总目录

  1. 第一章 NLP基础概念完整指南

    1. 第1部分-概念和发展历史
    2. 第2部分-各种任务(实体识别、关系抽取、文本摘要、机器翻译、自动问答)
    3. 第3部分-文本表示(词向量、语言模型、ELMo)
  2. 第二章 Transformer 架构原理

    1. 第1部分-注意力机制
    2. 第2部分Encoder-Decoder架构
    3. 第3部分-完整Transformer模型)
  3. 第三章 预训练语言模型

    1. 第1部分-Encoder-only(BERT、RoBERTa、ALBERT)
    2. 第2部分-Encoder-Decoder-T5
    3. 第3部分-Decoder-Only(GPT、LLama、GLM)
  4. [第四章 大语言模型](#第四章 大语言模型)

    1. 第1部分-待写
  5. [第五章 动手搭建大模型](#第五章 动手搭建大模型)

    1. 第1部分-待写
  6. [第六章 大模型训练实践](#第六章 大模型训练实践)

    1. 第1部分-待写
  7. [第七章 大模型应用](#第七章 大模型应用)

    1. 第1部分-待写

目录

  1. 引言

  2. Encoder-only 预训练语言模型


引言

在前一章中,我们详细探讨了为自然语言处理(NLP)领域带来革命性变化的注意力机制,以及基于此机制构建的Transformer模型。Transformer的出现标志着NLP模型发展的一个重要里程碑。正如我们所学,Transformer主要由Encoder(编码器)和Decoder(解码器)两部分构成,这两个组件各有独特的结构和输入输出特性。

针对Encoder和Decoder的不同特点,结合ELMo提出的预训练思路,研究者们开始探索对Transformer进行优化的多种方向。例如:

  • Google选择仅使用Encoder层,通过堆叠多层Encoder并提出掩码语言模型(Masked Language Model, MLM)预训练任务,打造了在自然语言理解(Natural Language Understanding, NLU)任务上表现卓越的BERT模型
  • OpenAI则选择了Decoder层,采用传统的语言模型(Language Model, LM)任务,通过不断扩大模型参数和预训练语料规模,开发了在自然语言生成(Natural Language Generation, NLG)任务上优势明显的GPT系列模型,这也成为了当今大型语言模型(LLM)的基础架构
  • 此外,还有同时保留Encoder和Decoder的方案,如Google发布的T5模型,构建了完整的预训练Transformer架构

本章将按照Encoder-Only、Encoder-Decoder、Decoder-Only的顺序,系统介绍Transformer时代的各个主流预训练模型。我们将深入剖析三种核心模型架构、每种架构选择的预训练任务及其独特优势,这些内容也构成了目前所有主流LLM的理论基础。


Encoder-only 预训练语言模型

BERT模型

BERT(Bidirectional Encoder Representations from Transformers,基于Transformer的双向编码器表示)是Google团队于2018年发布的里程碑式预训练语言模型。该模型在论文《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》中首次提出,在包括GLUE、MultiNLI等七个自然语言处理评测任务上实现了当时的最优性能(State Of The Art, SOTA)。

自BERT推出以来,"预训练+微调"的范式成为NLP任务的主流方法。BERT不仅自身在不断迭代优化,还催生了MacBERT、BART等一系列基于BERT改进的模型。可以说,BERT是NLP发展的阶段性成果,标志着预训练模型统治地位的确立。直到大型语言模型(LLM)时代的到来,NLP领域的主导地位才从BERT系模型逐渐转移。即使在LLM时代,要深入理解LLM与NLP,BERT仍然是不可绕过的重要一环。

思想沿承

BERT统一了多种核心思想,主要包括:

  1. Transformer架构:如第二章所述,2017年发表的《Attention is All You Need》论文提出了完全基于注意力机制、摒弃RNN和LSTM结构的Transformer模型,带来了全新的模型架构。BERT沿承了Transformer的思想,在其基础上进行优化,通过堆叠Encoder结构并扩大模型参数,打造了在NLU任务上表现卓越的模型架构。
  2. 预训练+微调范式:同样在2018年,ELMo模型的诞生标志着预训练+微调范式的确立。ELMo基于双向LSTM架构,在大规模训练数据上使用语言模型进行预训练,再针对下游任务进行微调,展现出更优越的性能。BERT采用了该范式,并通过调整为Transformer架构、引入更适合文本理解的MLM预训练任务,将预训练-微调范式推向高潮。
模型架构---Encoder Only

BERT的模型架构由Transformer的Encoder部分堆叠而成。其主要结构包括三个核心组件:

整体架构

  • Embedding层:将输入的token转换为高维向量表示
  • Encoder块:由N层Encoder Layer堆叠而成
  • Prediction Heads:分类头,用于将隐藏状态映射到任务的输出空间

BERT有两个标准版本:

  • BERT-base:12层Encoder Layer,768维隐藏层,总参数量110M
  • BERT-large:24层Encoder Layer,1024维隐藏层,总参数量340M

处理流程

python 复制代码
# 伪代码示例:BERT的前向传播过程
def bert_forward(text):
    # 1. 分词和编码
    input_ids = tokenizer(text)  # 将文本转换为token IDs
    
    # 2. Embedding层
    token_embeddings = embedding_layer(input_ids)  # token嵌入
    position_embeddings = position_embedding_layer(input_ids)  # 位置编码
    hidden_states = token_embeddings + position_embeddings
    
    # 3. 通过N层Encoder Layer
    for encoder_layer in encoder_layers:
        hidden_states = encoder_layer(hidden_states)
    
    # 4. Prediction heads
    logits = prediction_heads(hidden_states)  # 输出分类概率
    
    return logits

Encoder Layer结构

每一层Encoder Layer包含以下组件:

python 复制代码
class EncoderLayer:
    def forward(self, hidden_states):
        # 1. 多头自注意力机制
        attention_output = multi_head_attention(
            query=hidden_states,
            key=hidden_states,
            value=hidden_states
        )
        
        # 2. 残差连接和LayerNorm
        hidden_states = layer_norm(hidden_states + attention_output)
        
        # 3. 前馈神经网络 (Intermediate层)
        intermediate_output = feed_forward(hidden_states)
        
        # 4. 再次残差连接和LayerNorm
        hidden_states = layer_norm(hidden_states + intermediate_output)
        
        return hidden_states

关键技术细节

  1. 分词方法:BERT采用WordPiece作为分词方法,这是一种基于统计的子词切分算法。其核心思想是将单词拆解为子词单元,例如"playing"可能被切分为["play", "##ing"]。
  2. 激活函数:BERT使用GELU(Gaussian Error Linear Unit,高斯误差线性单元)激活函数;

GELU的核心思路是将随机正则的思想引入激活函数,通过输入自身的概率分布来决定是否保留该神经元。

  1. 位置编码:改进版BERT将相对位置编码融合在注意力机制中,将位置编码视为可训练的权重参数。这种方法能够拟合更丰富的相对位置信息,但也增加了模型参数,且无法处理超过训练长度的输入(BERT的最大上下文长度为512个token)。
预训练任务---MLM + NSP

BERT的重大创新在于提出了两个新的预训练任务:

1. 掩码语言模型(Masked Language Model, MLM)

MLM模拟"完形填空"的过程,在文本序列中随机遮蔽部分token,然后要求模型根据未被遮蔽的token预测被遮蔽的token。

示例:

复制代码
输入:I <MASK> you because you are <MASK>
输出:<MASK> → love; <MASK> → wonderful

MLM的改进策略

为解决预训练和微调不一致的问题,BERT采用了以下策略:

  • 随机选择15%的token进行处理
  • 其中80%被替换为
  • 10%被替换为随机token
  • 10%保持不变
python 复制代码
def mask_tokens(tokens, mask_prob=0.15):
    """
    对输入tokens进行掩码处理
    """
    masked_tokens = tokens.copy()
    labels = tokens.copy()
    
    for i in range(len(tokens)):
        if random.random() < mask_prob:
            prob = random.random()
            if prob < 0.8:
                # 80%的概率替换为[MASK]
                masked_tokens[i] = '[MASK]'
            elif prob < 0.9:
                # 10%的概率替换为随机token
                masked_tokens[i] = random.choice(vocab)
            # 10%的概率保持不变
        else:
            # 未被选中的token,标签设为-100(不计算损失)
            labels[i] = -100
    
    return masked_tokens, labels

2. 下一句预测(Next Sentence Prediction, NSP)

NSP任务用于训练模型理解句子间的关系,判断两个句子是否为连续的上下文。

示例:

复制代码
正样本:
Sentence A: I love you.
Sentence B: Because you are wonderful.
标签: 1(连续上下文)

负样本:
Sentence A: I love you.
Sentence B: Because today's dinner is so nice.
标签: 0(非连续上下文)

预训练规模

BERT的预训练使用了:

  • 800M的BooksCorpus语料
  • 2500M的英文维基百科语料
  • 总计约3.3B token
  • 90%的数据使用128的上下文长度训练
  • 10%的数据使用512的上下文长度训练
  • Batch size为256,训练1M步(40个Epoch)
  • BERT-base使用16块TPU训练4天
  • BERT-large使用64块TPU训练4天
下游任务微调

BERT确立了预训练-微调的两阶段思想,即在海量无监督语料上预训练获得通用的文本理解能力,再在特定下游任务上进行微调。

关键设计

  1. 特殊token [CLS]:在每个输入序列首部添加[CLS] token,其对应的特征向量代表整个句子的语义表征

  2. 微调策略:

    • 文本分类任务:直接修改prediction_heads的分类头
    • 序列标注任务:集成多层隐含层向量后输出标注结果
    • 文本生成任务:取Encoder输出直接解码
python 复制代码
# 微调示例:文本分类任务
class BERTForClassification:
    def __init__(self, bert_model, num_classes):
        self.bert = bert_model
        self.classifier = nn.Linear(bert_model.hidden_size, num_classes)
    
    def forward(self, input_ids, attention_mask):
        # 获取BERT的输出
        outputs = self.bert(input_ids, attention_mask=attention_mask)
        
        # 取[CLS] token的隐藏状态
        cls_hidden_state = outputs[0][:, 0, :]
        
        # 通过分类器得到logits
        logits = self.classifier(cls_hidden_state)
        
        return logits

BERT一经提出,直接在11个NLP任务上取得SOTA效果,成为NLU方向当之无愧的霸主。直到LLM时代,BERT仍在许多标注数据丰富的NLU任务上保持最优效果。


RoBERTa模型

RoBERTa(Robustly Optimized BERT Approach)是Facebook于2023年7月发布的BERT优化版本。RoBERTa的核心思想是:BERT使用的13GB预训练数据是否已经让模型充分拟合?如果使用更多预训练语料、优化预训练任务和训练超参数,是否可以进一步提升模型性能?

优化一:去掉NSP预训练任务

有学者质疑NSP任务过于简单,无法显著提升下游任务性能,甚至可能带来负面效果。RoBERTa设置了四组对比实验:

  1. 段落构建的MLM + NSP:BERT原始设置,输入为一对片段
  2. 文档对构建的MLM + NSP:输入为一对句子,通过增大batch保持token数量相同
  3. 跨越文档的MLM:去掉NSP,输入为从一个或多个文档中连续采样的完整句子
  4. 单文档的MLM:去掉NSP,限制输入只能从一个文档中采样

实验结果表明,后两组显著优于前两组,且单文档的MLM在下游任务微调时性能最佳。因此,RoBERTa在预训练中去掉了NSP,只使用MLM任务。

动态遮蔽策略

RoBERTa将Mask操作从数据处理阶段移至训练阶段,实现动态遮蔽,使每个Epoch的训练数据Mask位置都不同。

python 复制代码
class DynamicMaskingDataset:
    """
    动态遮蔽数据集
    """
    def __getitem__(self, index):
        text = self.texts[index]
        tokens = self.tokenizer(text)
        
        # 每次获取样本时动态进行掩码
        masked_tokens, labels = self.mask_tokens(tokens)
        
        return {
            'input_ids': masked_tokens,
            'labels': labels
        }
优化二:更大规模的预训练数据和预训练步长

RoBERTa使用了更大规模的预训练数据:

  • BookCorpus(BERT使用)
  • 英文维基百科(BERT使用)
  • CC-NEWS(CommonCrawl新闻领域英文部分)
  • OPENWEBTEXT(英文网页)
  • STORIES(CommonCrawl故事风格子集)
  • 总计160GB数据,是BERT的10倍

训练策略优化

  1. 更大的Batch Size:从256增加到8K,既提高优化速度,也提升最终性能
  2. 更长的训练步长:训练500K步(约66个Epoch)
  3. 固定序列长度:全部使用512长度训练,不再采用BERT的渐进式策略
python 复制代码
# 训练配置对比
bert_config = {
    'batch_size': 256,
    'max_length': [128, 512],  # 先用128训练,再用512
    'training_steps': 1000000,
    'total_tokens': '3.3B'
}

roberta_config = {
    'batch_size': 8192,
    'max_length': 512,  # 全程使用512
    'training_steps': 500000,
    'total_tokens': '160GB (约50B+)'
}

计算资源:训练一个RoBERTa,Meta使用了1024块V100(32GB显存)训练1天。

优化三:更大的BPE词表

RoBERTa采用BPE(Byte Pair Encoding,字节对编码)作为Tokenizer的编码策略,并将词表大小从BERT的30K扩大到50K。

BPE编码原理

BPE以子词对作为分词单位。例如:

  • "Hello World" → ["Hel", "lo", "Wor", "ld"]
  • 对于中文"我"(UTF-8编码为"E68891")→ ["E68", "891"]
python 复制代码
class BPETokenizer:
    """
    BPE分词器示例
    """
    def __init__(self, vocab_size=50000):
        self.vocab_size = vocab_size
        self.vocab = {}
    
    def train(self, corpus):
        """
        训练BPE词表
        """
        # 1. 初始化:每个字符作为一个token
        vocab = self.get_initial_vocab(corpus)
        
        # 2. 迭代合并最频繁的字符对
        while len(vocab) < self.vocab_size:
            # 找到最频繁的字符对
            most_frequent_pair = self.find_most_frequent_pair(corpus, vocab)
            
            # 合并该字符对
            vocab[most_frequent_pair] = len(vocab)
            
            # 更新语料
            corpus = self.merge_pair(corpus, most_frequent_pair)
        
        self.vocab = vocab

通过上述三方面优化,RoBERTa成功刷新了多个下游任务的SOTA,证明了更大规模预训练的重要意义,这也为LLM的诞生奠定了基础。


ALBERT模型

ALBERT(A Lite BERT)由Google于2020年提出,从降低模型参数、保持模型能力的角度对BERT进行优化。通过优化模型结构和改进NSP任务,ALBERT以更小的参数量实现了超越BERT的性能。

优化一:Embedding参数分解

BERT的Embedding层参数矩阵维度为V×HV \times H V×H(词表大小 × 隐藏层维度)。对于BERT-large,这相当于30K × 1024 = 30M参数。当隐藏层维度增加到2048时,Embedding参数会膨胀到61M。

ALBERT的解决方案

将Embedding层的输出维度与隐藏层维度解耦,在Embedding层后添加一个线性变换矩阵。

参数量对比:

  • BERT : V × H V \times H V×H
  • ALBERT : V × E + E × H V \times E + E \times H V×E+E×H(当 E ≪ H E \ll H E≪H 时,参数量显著减少)

其中:

  • V V V 表示词汇表大小(Vocabulary size)
  • H H H 表示隐藏层维度(Hidden size)
  • E E E 表示Embedding维度(Embedding size)
python 复制代码
class FactorizedEmbedding:
    """
    分解的Embedding层
    """
    def __init__(self, vocab_size, embedding_size, hidden_size):
        # 第一步:从vocab_size映射到较小的embedding_size
        self.word_embeddings = nn.Embedding(vocab_size, embedding_size)
        
        # 第二步:从embedding_size映射到hidden_size
        self.projection = nn.Linear(embedding_size, hidden_size)
    
    def forward(self, input_ids):
        # 词嵌入
        embeddings = self.word_embeddings(input_ids)  # [batch, seq_len, embedding_size]
        
        # 投影到隐藏层维度
        hidden_states = self.projection(embeddings)  # [batch, seq_len, hidden_size]
        
        return hidden_states

# 参数量计算示例
vocab_size = 30000
hidden_size = 1024
embedding_size = 128

# BERT的Embedding参数量
bert_params = vocab_size * hidden_size  # 30,720,000

# ALBERT的Embedding参数量
albert_params = vocab_size * embedding_size + embedding_size * hidden_size
# 3,840,000 + 131,072 = 3,971,072

print(f"参数减少: {(1 - albert_params/bert_params)*100:.1f}%")  # 减少约87%
优化二:跨层参数共享

ALBERT发现各Encoder层的参数呈现高度一致性,因此提出让所有Encoder层共享参数。

实现方式

python 复制代码
class ALBERTEncoder:
    """
    ALBERT的Encoder:所有层共享参数
    """
    def __init__(self, hidden_size, num_layers=24):
        # 只初始化一个EncoderLayer
        self.shared_layer = EncoderLayer(hidden_size)
        self.num_layers = num_layers
    
    def forward(self, hidden_states):
        # 虽然只有一层参数,但仍进行num_layers次计算
        for _ in range(self.num_layers):
            hidden_states = self.shared_layer(hidden_states)
        
        return hidden_states

效果对比

复制代码
模型层数隐藏层维度参数量
BERT-large241024334M
ALBERT-xlarge24204859M

ALBERT-xlarge虽然参数量仅为BERT-large的18%,但效果更优。

局限性:虽然参数量大幅减少,但训练和推理速度并未显著提升,因为仍需进行24次Encoder Layer计算。这也是ALBERT最终未能完全取代BERT的重要原因。

优化三:SOP预训练任务

ALBERT提出了句子顺序预测(Sentence Order Prediction, SOP)任务来替代NSP。

NSP vs SOP

复制代码
NSP任务:
正样本: [Sent A: "I love you."] [Sent B: "Because you are wonderful."]
负样本: [Sent A: "I love you."] [Sent B: "Today's dinner is nice."]
问题: 负样本来自不同文档,过于简单

SOP任务:
正样本: [Sent A: "I love you."] [Sent B: "Because you are wonderful."]
负样本: [Sent A: "Because you are wonderful."] [Sent B: "I love you."]
优势: 模型需要理解句子间的顺序关系,难度更高
python 复制代码
def create_sop_training_sample(sentence_a, sentence_b):
    """
    创建SOP训练样本
    """
    if random.random() < 0.5:
        # 50%概率:正样本(保持原顺序)
        return {
            'sentence_a': sentence_a,
            'sentence_b': sentence_b,
            'label': 1
        }
    else:
        # 50%概率:负样本(交换顺序)
        return {
            'sentence_a': sentence_b,
            'sentence_b': sentence_a,
            'label': 0
        }

实验证明:MLM + SOP > 仅MLM > MLM + NSP,SOP确实能显著提升模型效果。

通过上述三点优化,ALBERT成功以更小的参数实现了更强的性能,其"更宽模型"的思路为后续研究提供了重要参考价值。

相关推荐
攻城狮7号2 小时前
NVIDIA开源Audio2Face模型与SDK,数字人表情迎来“灵魂”时刻
人工智能·nvidia·开源模型·audio2face
许泽宇的技术分享2 小时前
Flutter + Ollama:开启本地AI的全平台新纪元 —— 从零剖析一款现代化AI客户端的技术奥秘
人工智能·flutter
大翻哥哥2 小时前
Python 2025:异步革命与AI驱动下的开发新范式
开发语言·人工智能·python
安娜的信息安全说2 小时前
Hugging Face vs Ollama:云端协作与本地隐私的AI平台对决
人工智能·hugging face·ollama
watersink3 小时前
文档解析Logics-Parsing
人工智能
学习的学习者3 小时前
CS课程项目设计19:基于DeepFace人脸识别库的课堂签到系统
人工智能·python·深度学习·人脸识别算法
不惑_3 小时前
AI大模型是怎么工作的?从石头分类说起
人工智能·分类·数据挖掘
IT古董3 小时前
【第五章:计算机视觉-项目实战之生成对抗网络实战】2.基于SRGAN的图像超分辨率实战-(2)实战1:DCGAN模型搭建
人工智能·生成对抗网络·计算机视觉
yourkin6663 小时前
李宏毅-Generative AI-第一课
人工智能