BERT和Transformer的双向性理解

BERT和Transformer的双向性理解

核心区别:注意力机制的限制

Transformer(原始论文)是"单向"还是"双向"?

答案是:看具体是哪一部分。原始Transformer论文包含Encoder和Decoder:

python 复制代码
# 原始Transformer架构
class Transformer(nn.Module):
    def __init__(self):
        self.encoder = TransformerEncoder()  # ✅ 双向的
        self.decoder = TransformerDecoder()  # ❌ 单向的(带掩码)

让我们看代码细节:


1. Transformer Encoder:实际上是双向

python 复制代码
class TransformerEncoderLayer(nn.Module):
    def forward(self, src):
        # 自注意力机制
        # Q, K, V 都来自 src(输入序列)
        attn_output = self.self_attn(src, src, src)
        #                ↑        ↑     ↑
        #              查询      键     值
        
        # 在计算注意力时,每个位置可以看到序列中的所有位置
        # 包括它自己、它前面的位置、它后面的位置

关键代码:Encoder的自注意力没有限制

python 复制代码
def attention(Q, K, V):
    # Q.shape = K.shape = V.shape = [batch, seq_len, dim]
    
    # 计算所有位置对之间的注意力分数
    scores = torch.matmul(Q, K.transpose(-2, -1))  # [batch, seq_len, seq_len]
    # scores[i, j] 表示位置i对位置j的关注程度
    
    # ✅ 没有掩码!每个位置可以看到所有位置
    attn_weights = F.softmax(scores, dim=-1)
    
    # 每个位置的输出是所有位置的加权和
    output = torch.matmul(attn_weights, V)
    return output

例子:句子 "I love NLP"

  • 当计算"love"的表示时:
    • 可以看到前面的 "I"
    • 可以看到后面的 "NLP"
    • 也可以看到自己 "love"
  • 这是真正的双向上下文

2. Transformer Decoder:这才是单向

python 复制代码
class TransformerDecoderLayer(nn.Module):
    def forward(self, tgt, memory):
        # 第一步:带掩码的自注意力(单向的!)
        tgt2 = self.self_attn(tgt, tgt, tgt, attn_mask=causal_mask)
        #                                    ↑ 关键!因果掩码
        
        # 第二步:编码器-解码器注意力(双向的)
        tgt2 = self.cross_attn(tgt2, memory, memory)
        # 这里memory来自encoder,是完整的上下文
        
        return tgt2

关键代码:因果掩码(Causal Mask)

python 复制代码
def generate_causal_mask(seq_len):
    """
    生成下三角矩阵,确保位置i只能看到位置j (j ≤ i)
    """
    mask = torch.tril(torch.ones(seq_len, seq_len))  # 下三角矩阵
    # 例如 seq_len=4:
    # [[1, 0, 0, 0],
    #  [1, 1, 0, 0],
    #  [1, 1, 1, 0],
    #  [1, 1, 1, 1]]
    return mask

在注意力中的应用

python 复制代码
def masked_attention(Q, K, V, mask):
    scores = torch.matmul(Q, K.transpose(-2, -1))
    
    # 应用因果掩码:将未来位置的分数设为负无穷
    scores = scores.masked_fill(mask == 0, float('-inf'))
    # mask=0的位置是未来位置,不能看
    
    attn_weights = F.softmax(scores, dim=-1)
    output = torch.matmul(attn_weights, V)
    return output

例子:生成句子 "I love NLP"

  • 生成第一个词 "I":只能看到起始符 <s>
  • 生成第二个词 "love":只能看到 <s> I,不能看到后面的 "NLP"
  • 生成第三个词 "NLP":只能看到 <s> I love,不能看到更后面的词
  • 这是从左到右的单向

3. BERT:完全基于Encoder,真正的双向

python 复制代码
class BERTModel(nn.Module):
    def __init__(self):
        # BERT只使用Transformer的Encoder部分
        self.encoder_layers = nn.ModuleList([
            TransformerEncoderLayer() for _ in range(12)
        ])
        # 没有Decoder,没有因果掩码!
    
    def forward(self, input_embeddings, attention_mask):
        # attention_mask只用于padding,不是因果掩码!
        # attention_mask = [[1, 1, 1, 0, 0]]  # 1=真实token,0=padding
        
        for layer in self.encoder_layers:
            # 每层都是完全双向的自注意力
            output = layer(input_embeddings, attention_mask)
            # attention_mask确保padding位置不被关注
            # 但所有真实token之间都可以相互关注
        
        return output

BERT中的注意力掩码 vs Transformer Decoder的掩码

python 复制代码
# BERT的attention_mask(用于padding)
bert_mask = torch.tensor([
    [1, 1, 1, 1, 0, 0],  # 前4个是真实token,后2个是padding
    [1, 1, 1, 0, 0, 0]   # 前3个是真实token,后3个是padding
])  # shape: [batch_size, seq_len]

# 转换为注意力掩码
bert_attn_mask = bert_mask.unsqueeze(1).unsqueeze(2)  # [batch, 1, 1, seq_len]
# 广播后,每个位置对所有位置使用相同的掩码模式

# Transformer Decoder的causal_mask(用于单向)
causal_mask = torch.tril(torch.ones(seq_len, seq_len))  # [seq_len, seq_len]
# [[1,0,0,0,0,0],
#  [1,1,0,0,0,0],
#  [1,1,1,0,0,0],
#  [1,1,1,1,0,0],
#  [1,1,1,1,1,0],
#  [1,1,1,1,1,1]]

4. 通过MLM任务体现的"双向性"

这才是BERT被称为"双向"的真正原因!让我们看预训练代码:

python 复制代码
class BERTForPreTraining(nn.Module):
    def forward(self, input_ids, mlm_labels):
        # 输入: [CLS] 我 [MASK] 吃 苹果 [SEP]
        #         101 1043  103 2003 3899 102
        
        # 1. 获取BERT的隐藏状态
        hidden_states = self.bert(input_ids)  # [batch, seq_len, hidden_dim]
        
        # 2. MLM预测
        mlm_logits = self.mlm_head(hidden_states)  # 预测每个位置的词
        
        # 关键:预测[MASK]时,BERT能看到整个上下文!
        # 位置2是[MASK],要预测它是"爱"
        # 在计算位置2的表示时,BERT能看到:
        # - 前面的 [CLS], "我"
        # - 后面的 "吃", "苹果", [SEP]

对比单向模型(如GPT)

python 复制代码
class GPTModel(nn.Module):
    def forward(self, input_ids):
        # 输入: [CLS] 我 [MASK] 吃 苹果
        # 使用因果掩码,每个位置只能看到前面的词
        
        # 当预测[MASK](位置2)时:
        # 只能看到: [CLS] 我
        # 不能看到: 吃 苹果
        
        # 所以GPT无法像BERT那样利用完整上下文

5. 可视化对比

BERT的双向注意力

复制代码
句子: [CLS] 我 [MASK] 吃 苹果 [SEP]

当计算[MASK]的表示时,注意力可以看向:
        ↑    ↑   ↑    ↑    ↑
      [CLS] 我 [MASK] 吃  苹果  [SEP]
              ← 中心  →
          双向都能看!

GPT的单向注意力

复制代码
句子: [CLS] 我 [MASK] 吃 苹果

当计算[MASK]的表示时,注意力只能看向:
        ↑    ↑
      [CLS] 我 [MASK] 吃  苹果
        只能看左边!
        不能看右边!

原始Transformer Decoder的单向注意力

复制代码
生成过程:
步骤1: [CLS] → 预测"我"         (只能看[CLS])
步骤2: [CLS] 我 → 预测"[MASK]"   (只能看[CLS] 我)
步骤3: [CLS] 我 [MASK] → 预测"吃" (只能看[CLS] 我 [MASK])

6. 为什么这个区别如此重要?

对于理解任务:

python 复制代码
# 完形填空:"中国的首都是[MASK]"
# BERT: 能看到"中国"和"首都" → 容易预测"北京"
# GPT: 只能看到前面的词 → 更难预测

# 命名实体识别:"[CLS] 张 三 去 北 京 [SEP]"
# BERT: "北"能看到前面的"去"和后面的"京" → 容易识别为地名
# GPT: "北"只能看到前面的词 → 更难识别

对于生成任务:

python 复制代码
# 文本生成:"今天天气很好,"
# BERT: 不适合!因为训练时没见过[MASK]在中间的情况
# GPT: 完美适合!就是训练来做这个的

总结表格

特性 Transformer Encoder Transformer Decoder BERT GPT
架构组件 Encoder部分 Decoder部分 只使用Encoder 只使用Decoder
注意力类型 完全双向自注意力 带掩码的自注意力(单向) 完全双向自注意力 带因果掩码的自注意力(单向)
能否看到未来 ✅ 可以 ❌ 不可以 ✅ 可以 ❌ 不可以
掩码作用 只掩码padding 掩码未来位置 + padding 只掩码padding 掩码未来位置 + padding
适合任务 理解任务(分类、NER等) 生成任务(翻译、摘要等) 理解任务 生成任务
预训练目标 N/A 自回归语言建模 MLM(掩码语言建模) 自回归语言建模

关键结论

  1. 说"Transformer是单向的"不准确 ,应该说 "Transformer Decoder是单向的"
  2. BERT是双向的 ,因为它:
    • 只使用Transformer的Encoder部分
    • 没有因果掩码限制
    • 通过MLM任务学习利用完整上下文
  3. 这个"双向性"是BERT在理解类任务上表现出色的根本原因

所以当人们说"BERT是双向的",他们真正想表达的是:"BERT在预测每个词时,都能利用该词左边和右边的所有上下文信息",而这正是通过去掉Decoder的因果掩码,结合MLM预训练任务实现的。

相关推荐
十铭忘2 小时前
SAM2跟踪的理解19——位置编码
人工智能·深度学习·计算机视觉
张二娃同学2 小时前
深度学习入门篇——Github的使用和项目的导入
人工智能·git·深度学习·开源·github
一个处女座的程序猿O(∩_∩)O2 小时前
transformer模型:彻底改变AI格局的革命性架构
人工智能·深度学习·transformer
Damon小智2 小时前
【TextIn大模型加速器 + 火山引擎】跨国药企多语言手册智能翻译系统设计与实现
人工智能·ai·ocr·agent·火山引擎
2501_936146042 小时前
YOLOv26鱼类目标检测与计数任务实现与优化
人工智能·yolo·目标检测
老吴学AI2 小时前
范式转移:生成式AI如何重新定义“智能应用
人工智能·aigc·agent
540_5402 小时前
ADVANCE Day33
人工智能·python·机器学习
8K超高清3 小时前
风机叶片运维:隐藏于绿色能源背后的挑战
网络·人工智能·科技·5g·智能硬件
白日做梦Q3 小时前
数据增强策略:不仅仅是旋转和翻转
人工智能·深度学习