BERT:双向编码器表示与预训练

一句话概述

BERT(Bidirectional Encoder Representations from Transformers)是Google于2018年提出的革命性预训练语言模型。它使用Transformer的编码器部分,通过两个创新的预训练任务——掩码语言模型(Masked Language Model, MLM)和下一句预测(Next Sentence Prediction, NSP)——在海量无标注文本上进行预训练,学习通用的语言表示。MLM任务随机遮盖输入中的15%的词,让模型根据上下文预测被遮盖的词,这迫使模型学习真正的双向上下文理解。预训练完成后,BERT可以通过微调(Fine-tuning)适配到各种下游NLP任务(文本分类、问答、命名实体识别等),在11项NLP基准上刷新了当时的最优结果。BERT的诞生标志着NLP进入了"预训练+微调"的范式时代,成为后续所有预训练模型(RoBERTa、ALBERT、ELECTRA等)的基石。

💡 核心要点:①BERT使用Transformer的编码器部分,实现真正的双向上下文理解 ②MLM任务随机遮盖15%的词,让模型预测被遮盖词,学习双向表示 ③NSP任务判断两个句子是否连续,学习句子间关系 ④BERT开启"预训练+微调"范式,在11项NLP任务上刷新SOTA

教学与演示

一、BERT的架构:双向Transformer编码器

是什么(定义):BERT的架构是Transformer编码器(Encoder-only)。BERT-base版本包含12层Transformer编码器(L=12),隐藏维度768(H=768),12个注意力头(A=12),总参数量1.1亿。BERT-large版本包含24层(L=24),隐藏维度1024(H=1024),16个注意力头(A=16),总参数量3.4亿。与GPT不同,BERT是双向的——每个位置可以同时看到左右两侧的上下文,这使得BERT在"理解"任务上优于单向的GPT。

大白话 BERT就像一个"完形填空专家"。它读文章时不是从左到右逐字读(像GPT那样),而是"一眼看全文"——看到每个词时,它同时参考了左边和右边的上下文。这就像中国人做英语完形填空:要填入一个词,不仅要看前面的句子,还要看后面的句子,才能准确判断。BERT的双向性让它特别擅长"理解"任务——比如判断两句话是否相似、提取文中的答案、判断文章的情感——这些都需要综合全文信息。

为什么(原理):BERT的双向性源于其使用Transformer编码器(而非解码器)。编码器的自注意力不需要掩码,每个位置可以无限制地关注所有其他位置。这解决了GPT等单向模型的根本局限——GPT只能利用上文信息,而BERT能同时利用上下文。但双向性也有代价:BERT不能直接用于文本生成(因为生成需要"因果性",不能看未来),因此BERT主要用于"理解"任务而非"生成"任务。

import numpy as np

# BERT的架构本质:双向Transformer编码器
# 对比GPT(单向)和BERT(双向)的注意力模式

class BERTEncoder:
    """BERT风格的编码器:无掩码,完全双向"""
    def __init__(self, d_model=8, num_heads=2):
        np.random.seed(42)
        self.d_model = d_model
        self.d_k = d_model // num_heads
        self.num_heads = num_heads
        # 自注意力权重(无掩码版本)
        self.W_Q = np.random.randn(d_model, d_model) * 0.1
        self.W_K = np.random.randn(d_model, d_model) * 0.1
        self.W_V = np.random.randn(d_model, d_model) * 0.1
        self.W_O = np.random.randn(d_model, d_model) * 0.1

    def bidirectional_attention(self, X):
        """双向注意力:每个位置可以看到所有位置(BERT)"""
        n = X.shape[0]
        Q = X @ self.W_Q
        K = X @ self.W_K
        V = X @ self.W_V

        # 关键:没有掩码!所有位置互相可见
        scores = Q @ K.T / np.sqrt(self.d_k)
        scores = scores - np.max(scores, axis=1, keepdims=True)
        attn = np.exp(scores) / np.sum(np.exp(scores), axis=1, keepdims=True)
        return attn

    def causal_attention(self, X):
        """因果注意力:每个位置只能看到之前的位置(GPT)"""
        n = X.shape[0]
        Q = X @ self.W_Q
        K = X @ self.W_K
        V = X @ self.W_V

        scores = Q @ K.T / np.sqrt(self.d_k)
        # 关键:加上掩码,屏蔽未来位置
        mask = np.triu(np.ones((n, n)) * -1e9, k=1)
        scores = scores + mask
        scores = scores - np.max(scores, axis=1, keepdims=True)
        attn = np.exp(scores) / np.sum(np.exp(scores), axis=1, keepdims=True)
        return attn


# 创建示例句子:"[CLS] 我 爱 北京 [SEP]"
words = ["[CLS]", "我", "爱", "北京", "[SEP]"]
X = np.random.randn(5, 8) * 0.5

bert = BERTEncoder(d_model=8, num_heads=2)

# BERT的双向注意力
bi_attn = bert.bidirectional_attention(X)

# GPT的因果注意力
causal_attn = bert.causal_attention(X)

print("=== BERT双向注意力 vs GPT因果注意力 ===\n")

print("BERT(双向):每个词可以看到所有其他词")
print("注意力矩阵:")
print(f"{'':>8}", end="")
for w in words:
    print(f"  {w:>6}", end="")
print()
for i, w1 in enumerate(words):
    print(f"{w1:>8}", end="")
    for j in range(len(words)):
        print(f"  {bi_attn[i][j]:.4f}", end="")
    print()

print("\nGPT(因果/单向):每个词只能看到左侧的词")
print("注意力矩阵:")
print(f"{'':>8}", end="")
for w in words:
    print(f"  {w:>6}", end="")
print()
for i, w1 in enumerate(words):
    print(f"{w1:>8}", end="")
    for j in range(len(words)):
        print(f"  {causal_attn[i][j]:.4f}", end="")
    print()

print("\n关键区别:")
print("- BERT:'北京'可以看到后面的'[SEP]'(双向)")
print("- GPT:'北京'看不到后面的'[SEP]'(单向,只能看到左侧)")
print("- 双向性使BERT在理解任务上更强,但无法用于生成")
BERT的输入表示\(\text{Input} = E_{\text{token}} + E_{\text{segment}} + E_{\text{position}}\)
大白话 BERT的输入是"三重信息叠加"。就像给每个词贴了三张标签:第一张是"我是谁"(词嵌入——这是什么词),第二张是"我在哪句话"(段嵌入——我是第一句还是第二句),第三张是"我排第几"(位置嵌入——我是第5个词)。三张标签叠在一起,形成一个"全息"的输入表示,然后送入12层Transformer进行深度理解。

什么用(应用):BERT的双向编码器使其特别擅长"理解"任务。在文本分类中,取[CLS]标记的最终输出做分类;在问答任务中,预测答案在原文中的起始和结束位置;在命名实体识别中,对每个词的输出做分类(人名、地名、组织名等);在语义相似度中,比较两个句子[CLS]向量的余弦距离。BERT发布后,几乎所有NLP任务都从"从零训练"转为"BERT微调",效果普遍大幅提升。

哪些坑(缺点):BERT不能直接用于文本生成(因为双向性违反了因果性)。BERT的输入长度限制为512个词(位置嵌入的最大长度),无法处理长文档。BERT的预训练成本高昂——BERT-base在16个TPU上训练了4天。此外,BERT模型体积大(1.1亿参数),推理速度慢,在移动端或实时场景下需要模型压缩。

二、预训练任务:MLM与NSP

是什么(定义):BERT通过两个预训练任务学习语言表示。①掩码语言模型(MLM):随机选择输入中15%的词,其中80%替换为[MASK]标记,10%替换为随机词,10%保持不变,然后让模型预测这些位置的原词。②下一句预测(NSP):输入两个句子A和B,50%的情况下B是A的下一句,50%的情况下B是随机句子,让模型判断B是否是A的下一句。

大白话 MLM就像"做填空题"。出题老师把文章中的15%的词涂黑(80%涂黑,10%换成别的词,10%保持原样),让你猜被涂黑的是什么词。为了猜对,你必须理解整篇文章的上下文。NSP就像"判断两段话是否连贯"——给你两段话,让你判断第二段是不是紧接第一段的。这训练了模型对句子间关系的理解能力。MLM教模型"理解词义",NSP教模型"理解句子关系"。

为什么(原理):MLM的设计有两个关键细节。为什么80/10/10的比例?如果全部替换为[MASK],模型在预训练时只见过[MASK],但微调时没有[MASK]——这会导致预训练和微调之间的不匹配。10%替换为随机词迫使模型不盲目信任输入,而是真正理解上下文来判断一个词是否合理。10%保持不变让模型也学习"这个词是对的,不需要改"的情况。NSP的设计训练了句子间关系理解,这对问答和自然语言推理等任务至关重要。

import numpy as np

# MLM(掩码语言模型)和NSP(下一句预测)的演示
# 这是BERT预训练的核心任务

class MLMDemo:
    def __init__(self, vocab_size=100, d_model=8):
        np.random.seed(42)
        # 模拟词嵌入表
        self.embeddings = np.random.randn(vocab_size, d_model) * 0.5
        # 模拟BERT的预测头(将d_model映射到词表大小)
        self.W_pred = np.random.randn(d_model, vocab_size) * 0.1

    def mask_tokens(self, token_ids, mask_prob=0.15):
        """
        模拟MLM的掩码策略
        80%: 替换为[MASK](假设MASK token id=0)
        10%: 替换为随机词
        10%: 保持不变
        """
        masked_ids = token_ids.copy()
        mask_labels = np.full(len(token_ids), -1)  # -1表示不需要预测

        for i in range(len(token_ids)):
            if np.random.random() < mask_prob:
                mask_labels[i] = token_ids[i]  # 记录原始词(用于计算损失)
                rand = np.random.random()
                if rand < 0.8:
                    masked_ids[i] = 0  # 80%: 替换为[MASK]
                elif rand < 0.9:
                    masked_ids[i] = np.random.randint(1, len(self.embeddings))  # 10%: 随机词
                # else: 10%保持不变

        return masked_ids, mask_labels

    def predict_masked(self, masked_ids, mask_labels):
        """模拟MLM预测:对被遮盖的位置预测原词"""
        print("=== MLM 掩码语言模型演示 ===\n")

        word_map = {1: "我", 2: "爱", 3: "北京", 4: "天安门",
                    5: "的", 6: "长城", 7: "很", 8: "美丽", 0: "[MASK]"}
        sentence_ids = [1, 2, 3, 4, 5, 6, 7, 8]  # "我 爱 北京 天安门 的 长城 很 美丽"

        # 显示原始句子
        print("原始句子:", " ".join(word_map.get(tid, str(tid)) for tid in sentence_ids))

        # 应用掩码
        masked_ids, mask_labels = self.mask_tokens(np.array(sentence_ids), mask_prob=0.25)
        print("掩码后句子:", " ".join(word_map.get(tid, str(tid)) for tid in masked_ids))

        # 展示掩码策略
        print("\n掩码详情:")
        for i, (orig, masked, label) in enumerate(zip(sentence_ids, masked_ids, mask_labels)):
            if label != -1:
                orig_word = word_map.get(orig, str(orig))
                masked_word = word_map.get(masked, str(masked))
                print(f"  位置{i}: '{orig_word}' → '{masked_word}' (需要预测回'{orig_word}')")

        print("\nMLM的关键设计:")
        print("- 80% [MASK]:让模型学会'填空'")
        print("- 10% 随机词:迫使模型不盲目信任输入,真正理解上下文")
        print("- 10% 不变:让模型学会'这个词是对的,不需要改'")


class NSPDemo:
    def __init__(self):
        pass

    def demonstrate(self):
        print("\n\n=== NSP 下一句预测演示 ===\n")

        # 模拟句子对
        sentence_A = "我 爱 北京 天安门"
        sentence_B_positive = "天安门 上 太阳 升"  # 真实的下一句
        sentence_B_negative = "今天 天气 很 好"  # 随机句子

        print(f"句子A: {sentence_A}")
        print(f"句子B₁(真实下一句): {sentence_B_positive}")
        print(f"句子B₂(随机句子): {sentence_B_negative}")

        print("\nBERT的输入格式:")
        print(f"[CLS] {sentence_A} [SEP] {sentence_B} [SEP]")

        print("\nNSP任务:")
        print("- 50%的训练样本:B是A的真实下一句(标签=IsNext)")
        print("- 50%的训练样本:B是随机句子(标签=NotNext)")
        print("- [CLS]标记的最终输出用于二分类预测")
        print("\nNSP训练了模型对句子间关系的理解能力")
        print("这对问答(QA)和自然语言推理(NLI)任务至关重要")


# 运行演示
mlm = MLMDemo()
mlm.predict_masked(None, None)

nsp = NSPDemo()
nsp.demonstrate()
MLM的损失函数\(L_{\text{MLM}} = -\sum_{i \in \text{masked}} \log P(w_i | \text{context}), \quad P(w_i | \text{context}) = \text{softmax}(h_i W_{\text{pred}}^T + b)\)
大白话 MLM就像"填写被涂掉的词"。模型看完整句话(除了被涂掉的部分),然后猜被涂掉的是什么词。猜错了要扣分(损失),猜对了加分。为了减少扣分,模型必须学会理解上下文——比如"我[MASK]北京",如果理解了上下文,应该猜"爱"而不是"恨"或"吃"。15%的遮盖比例是一个平衡:太少则学习信号不够,太多则上下文信息不足,模型无法合理推断。

什么用(应用):MLM和NSP的预训练策略对后续所有预训练模型产生了深远影响。RoBERTa去掉了NSP(发现NSP帮助不大),增加了数据量和训练步数,效果更好。ALBERT通过参数共享减小模型体积。ELECTRA使用判别器-生成器框架,判别所有词是否被替换(而非仅15%),效率更高。这些改进都基于BERT的MLM框架,验证了"预训练+微调"范式的强大。

哪些坑(缺点):MLM的15%遮盖比例意味着每轮训练只利用了15%的词作为学习信号,效率较低。后续研究(如ELECTRA)通过判别所有词来提高效率。NSP后来被证明对大多数下游任务帮助有限(RoBERTa去掉了NSP),但对句子对任务(如NLI)仍有帮助。此外,MLM引入了[MASK]标记,在预训练和微调之间造成了不匹配(微调时没有[MASK]),虽然80/10/10策略缓解了这一问题。

三、BERT的微调与下游任务

是什么(定义):微调(Fine-tuning)是BERT应用的核心范式。预训练完成后,BERT获得了通用的语言理解能力。在下游任务上,只需在BERT之上添加一个简单的任务特定层(如分类器),然后用少量标注数据对整个模型进行端到端微调。微调通常只需要几轮训练(3-5个epoch),在几分钟到几小时内完成,远快于从零训练。

大白话 微调就像"转岗培训"。BERT在大学(预训练)里学了通用的语言能力——读文章、理解句意、词汇知识。现在要去做具体工作(下游任务),比如做情感分析(判断评论是好评还是差评),只需要一个简短的"岗前培训"(微调)——用几百条标注好评/差评的数据训练几轮,BERT就能胜任了。因为它的语言基础已经很好,学新任务只需要"微调"一下,而不是从头学起。

为什么(原理):微调之所以高效,是因为预训练已经学到了通用的语言特征(词法、语法、语义知识),这些特征对大多数下游任务都有用。微调只需要对顶层特征进行任务特定的调整——底层特征(如词向量、语法结构)通常保持不变或微调幅度很小。这就是"迁移学习"的核心思想:将源任务的知识迁移到目标任务。

import numpy as np

# BERT微调演示:在预训练编码器上添加分类头
# 展示微调如何适配下游任务

class BERTFineTuning:
    def __init__(self, d_model=8, num_classes=3):
        np.random.seed(42)
        self.d_model = d_model
        # 模拟预训练好的BERT编码器(权重已冻结或有微小更新)
        self.W_enc = np.random.randn(d_model, d_model) * 0.3
        # 任务特定的分类头:将[CLS]的输出映射到类别
        self.W_cls = np.random.randn(d_model, num_classes) * 0.1
        self.b_cls = np.zeros(num_classes)

    def encode(self, X):
        """模拟BERT编码器的前向传播(简化版)"""
        # 模拟多层Transformer的计算
        hidden = np.tanh(X @ self.W_enc)  # 简化的编码过程
        return hidden

    def classify(self, X):
        """完整的微调前向传播:编码 + 分类"""
        encoded = self.encode(X)
        # 取[CLS]标记的输出(第一个位置)做分类
        cls_output = encoded[0]  # [CLS]的表示
        # 分类头:线性变换 + softmax
        logits = cls_output @ self.W_cls + self.b_cls
        probs = np.exp(logits) / np.sum(np.exp(logits))
        return probs, encoded


# 创建示例数据:模拟情感分类任务
# 3个句子,每个6维词向量
sentences = {
    "这个电影太棒了": np.random.randn(4, 6) * 0.5,  # 4个词
    "一般般没什么意思": np.random.randn(5, 6) * 0.5,  # 5个词
    "太烂了浪费钱": np.random.randn(3, 6) * 0.5,  # 3个词
}

bert_ft = BERTFineTuning(d_model=6, num_classes=3)

print("=== BERT微调:情感分类任务 ===\n")
labels = ["正面", "中性", "负面"]

for sentence, X in sentences.items():
    probs, encoded = bert_ft.classify(X)
    pred_label = labels[np.argmax(probs)]
    print(f"句子: '{sentence}'")
    print(f"  预测概率: 正面={probs[0]:.3f}, 中性={probs[1]:.3f}, 负面={probs[2]:.3f}")
    print(f"  预测类别: {pred_label}")
    print(f"  [CLS]向量维度: {encoded[0].shape}")
    print()

print("微调的关键特点:")
print("- 编码器参数(W_enc)在微调时也会更新,但幅度很小")
print("- 分类头(W_cls)从零开始学习,是任务特定的")
print("- 整个模型端到端训练,只需要少量标注数据")
BERT的微调流程\(P(y | X) = \text{softmax}(h_{\text{[CLS]}} W_{\text{task}} + b_{\text{task}})\)
大白话 微调就像"给大学生配一个专业导师"。BERT是已经大学毕业的通才(预训练),现在要去做"情感分析"这个专业工作。只需要在BERT上面加一个小小的"专业模块"(分类头),然后让专业导师(标注数据)带几天(微调几步),就能上岗了。因为这个大学生底子好(预训练学到的语言知识),所以学新专业特别快。

什么用(应用):BERT微调在NLP领域产生了革命性影响。文本分类(情感分析、垃圾邮件检测、新闻分类)只需在[CLS]上加一个分类器;命名实体识别在每词输出上加分类器;问答任务中,预测答案的起始位置和结束位置;文本相似度中,比较两个句子[CLS]向量的距离。BERT的微调范式使得NLP任务的开发门槛大幅降低——不再需要从零训练模型,只需要准备少量标注数据即可。

哪些坑(缺点):微调虽然高效,但需要为每个下游任务存储一份完整的BERT模型(1.1亿参数),存储成本高。此外,BERT的512长度限制意味着长文本需要截断或分段处理。微调也有"灾难性遗忘"(catastrophic forgetting)的风险——如果微调步数过多或学习率过大,可能破坏预训练学到的通用知识。Prompt Tuning和Adapter等参数高效微调方法试图解决这些问题。

概念关系图谱

概念核心含义与AI的关系关联概念
BERT双向编码器表示,通过MLM和NSP预训练开启NLP预训练+微调范式Transformer编码器、MLM
MLM(掩码语言模型)随机遮盖词并预测,学习双向上下文BERT的核心预训练任务完形填空、自监督学习
NSP(下一句预测)判断两个句子是否连续训练句子间关系理解句子对任务、NLI
[CLS]标记特殊标记,聚合整个输入序列的信息用于分类任务的表示句子表示、池化
[SEP]标记分隔两个句子的标记标记句子边界段嵌入、句子对
微调(Fine-tuning)在预训练模型上适配下游任务使BERT适配各种NLP任务迁移学习、Adapter
WordPieceBERT使用的子词分词方法将罕见词分解为常见子词BPE、SentencePiece
预训练+微调范式先在大数据预训练,再在小数据微调改变NLP研究范式,降低任务门槛迁移学习、自监督学习

重点答疑

Q1: BERT和GPT的核心区别是什么?

三个层面:①架构——BERT是编码器(双向),GPT是解码器(单向/因果);②预训练任务——BERT用MLM(填空),GPT用LM(预测下一个词);③适用场景——BERT擅长理解(分类、问答、NER),GPT擅长生成(文本续写、对话)。这导致了根本性的差异:BERT不能直接生成文本(因为双向性),GPT在理解任务上不如BERT(因为单向限制)。

Q2: MLM中为什么要有10%随机替换和10%不变?

如果100%都用[MASK]标记,模型会在预训练时学会"看到[MASK]就预测",但微调时没有[MASK]——这造成训练和推理不匹配。10%随机替换迫使模型检查每个词是否合理——即使不是[MASK],也可能是错的,模型不能盲目信任输入。10%不变让模型学习"这个词不需要改"——否则模型会倾向于把所有词都预测为别的词。

Q3: NSP后来被证明效果不大,为什么?

RoBERTa的研究发现,去掉NSP只使用MLM,在大多数下游任务上效果相同甚至更好。原因可能是:NSP任务太简单(50%概率判断随机句子),模型学到了浅层的主题特征而非深层的句子关系;MLM本身已经足够强,能学到大部分语言知识;NSP可能干扰了MLM的学习(因为输入格式变复杂了)。但NSP对需要句子对推理的任务(如NLI)仍有帮助。

章节单词汇总

英文音标术语/释义
BERT/bɜːrt/双向编码器表示模型,Google的预训练语言模型
Masked Language Model (MLM)/mæskt ˈlæŋɡwɪdʒ ˈmɑːdəl/掩码语言模型,随机遮盖词并预测
Next Sentence Prediction (NSP)/nekst ˈsentəns prɪˈdɪkʃən/下一句预测,判断两句是否连续
Fine-tuning/faɪn ˈtjuːnɪŋ/微调,在预训练模型上适配下游任务
WordPiece/wɜːrd piːs/子词分词方法,将词分解为更小的子词单元
Pre-training/priː ˈtreɪnɪŋ/预训练,在大规模无标注数据上训练通用表示
[CLS] Token/klæs ˈtoʊkən/分类标记,BERT用于聚合整个序列信息
[SEP] Token/sep ˈtoʊkən/分隔标记,分隔两个句子
[MASK] Token/mæsk ˈtoʊkən/掩码标记,MLM中被遮盖词的位置标记
Segment Embedding/ˈseɡmənt ɪmˈbedɪŋ/段嵌入,标记词属于句子A还是句子B

面试练习

Q1 [单选] BERT使用的是什么架构?

  • A. Transformer解码器
  • B. Transformer编码器
  • C. 完整的编码器-解码器
  • D. LSTM
解答:BERT使用Transformer的编码器部分(Encoder-only),实现双向上下文理解。GPT使用解码器(Decoder-only),原始Transformer使用完整的编码器-解码器。

Q2 [单选] BERT的MLM任务中,多少比例的输入词会被遮盖?

  • A. 5%
  • B. 15%
  • C. 50%
  • D. 100%
解答:BERT随机选择15%的词进行遮盖。其中80%替换为[MASK],10%替换为随机词,10%保持不变。这个比例在"足够学习信号"和"足够上下文信息"之间取得了平衡。

Q3 [单选] BERT的[CLS]标记有什么作用?

  • A. 标记句子的开始
  • B. 聚合整个输入序列的信息,用于分类任务
  • C. 标记被遮盖的词
  • D. 标记句子的结束
解答:[CLS]是专门为分类任务设计的特殊标记。它位于输入序列的最开始,经过Transformer编码后,其输出向量聚合了整个序列的信息,用于下游分类任务。

Q4 [多选] 关于BERT的预训练,以下哪些说法是正确的?

  • A. BERT使用MLM和NSP两个预训练任务
  • B. MLM中80%的遮盖词替换为[MASK],10%为随机词,10%不变
  • C. BERT在预训练时使用标注数据
  • D. BERT-base有12层,1.1亿参数
  • E. BERT可以用于文本生成任务
解答:BERT使用MLM和NSP两个预训练任务,MLM有80/10/10的替换策略,BERT-base有12层、1.1亿参数。BERT使用无标注数据进行预训练(自监督学习),且由于双向性,不能直接用于文本生成。

Q5 [单选] BERT-base中的"base"指的是什么参数规模?

  • A. 8层,512维,8头
  • B. 12层,768维,12头
  • C. 24层,1024维,16头
  • D. 6层,512维,8头
解答:BERT-base:L=12层,H=768维,A=12头,总参数1.1亿。BERT-large:L=24层,H=1024维,A=16头,总参数3.4亿。

Q6 [单选] 为什么BERT在微调时可以适配不同的下游任务?

  • A. 因为BERT架构可以动态改变
  • B. 因为预训练学到了通用的语言表示,只需在顶层添加任务特定层
  • C. 因为BERT在微调时重新训练所有参数
  • D. 因为BERT的参数量足够大
解答:BERT通过预训练学到了通用的语言表示(语法、语义知识),这些知识对大多数下游任务都有用。微调时只需在BERT之上添加简单的任务特定层(如分类器),用少量标注数据训练即可。

Q7 [多选] 关于BERT的输入表示,以下哪些是正确的?

  • A. 输入由词嵌入、段嵌入、位置嵌入三部分组成
  • B. 段嵌入用于区分句子A和句子B
  • C. 位置嵌入是可学习的,最大支持512个位置
  • D. BERT使用正弦位置编码
  • E. 三部分通过逐元素相加合并
解答:BERT的输入 = 词嵌入 + 段嵌入 + 位置嵌入,三者相加。段嵌入区分两个句子(用于NSP),位置嵌入是可学习的(非正弦编码),最大512个位置。

Q8 [单选] 在BERT微调中,通常使用多大的学习率?

  • A. 0.1-0.5
  • B. 1e-3-5e-3
  • C. 2e-5-5e-5
  • D. 1e-7-5e-7
解答:BERT微调通常使用很小的学习率(2e-5到5e-5),这是为了避免破坏预训练学到的知识。太大的学习率会导致"灾难性遗忘"。

Q9 [多选] 以下哪些是BERT的局限性?

  • A. 输入长度限制为512个词
  • B. 不能直接用于文本生成
  • C. 预训练成本高
  • D. 推理速度慢,不适合实时场景
  • E. 不支持中文
解答:BERT的局限包括:512长度限制(位置嵌入上限)、不能生成文本(双向性)、预训练成本高(16 TPU × 4天)、推理速度慢(1.1亿参数)。BERT支持中文(有BERT-Chinese版本)。

Q10 [单选] 以下哪个模型是BERT的改进版本,去掉了NSP任务?

  • A. ALBERT
  • B. RoBERTa
  • C. ELECTRA
  • D. DistilBERT
解答:RoBERTa(Facebook, 2019)去掉了NSP任务,使用更大的数据和更长的训练时间,证明NSP并非必要。ALBERT通过参数共享减小模型体积,ELECTRA使用判别器-生成器框架,DistilBERT是BERT的蒸馏版本。