最近笔者在啃 Transformer 的书籍,其中有个结论:
BERT 仅使用 Transformer 的编码器部分,而不使用解码器部分.
这本书只讲了结论,没有讲原因。于是笔者做了一番功能,去查了一些资料进行学习。
在自然语言处理领域,BERT (Bidirectional Encoder Representations from Transformers
) 通过 Transformer 的编码器实现了双向预训练,并且在多个任务中取得了卓越的表现。
Transformer 是一种基于注意力机制的神经网络架构,最初在论文 Attention is All You Need
中被提出。它包括编码器和解码器两个主要部分。
编码器
编码器的主要功能是接收输入序列,将其转换为一个上下文相关的表示。编码器通过多头自注意力机制和前馈神经网络对输入的每个位置进行建模,从而捕捉输入序列中不同词语之间的依赖关系。
解码器
解码器的主要功能是生成输出序列。它不仅需要使用编码器生成的上下文表示,还需要通过掩码自注意力机制生成当前时间步的预测,确保输出的生成是基于已经生成的内容,而不会看到未来的词语。
通过对编码器和解码器的功能分析,可以看出两者的侧重点不同:编码器适合生成丰富的上下文表示,而解码器更适合生成语言序列。
BERT 的任务目标
BERT 的设计目标是提供深层双向表示,从而在预训练后可以很好地适配多种下游任务,如句子分类、文本匹配和问答任务。
为了实现这一目标,BERT 的预训练任务包括:
Masked Language Modeling (MLM)
:通过随机掩盖输入序列中的部分单词,要求模型预测这些被掩盖的单词。Next Sentence Prediction (NSP)
:判断两个句子是否相邻。
这些任务决定了 BERT 的设计重点是对输入序列的全面理解,而不是生成新的序列。
为什么编码器适合 BERT 的任务?
编码器的双向自注意力机制允许模型同时考虑上下文的左右两侧。这种双向表示对于理解语言中的语义和句法关系至关重要。例如:
- 在句子
The bank is on the river bank.
中,bank
的含义依赖于其上下文。如果仅使用单向表示(如解码器的方式),模型只能依赖左侧或右侧的上下文,难以全面理解整个句子。
编码器通过多头自注意力机制捕捉词语之间的依赖关系,而无需考虑生成序列的问题,从而专注于输入序列的表示学习。
解码器为何不适合 BERT
解码器的设计目的是生成序列,而这一过程要求掩盖未来的词语,以确保生成的正确性。这种单向性限制了模型对上下文的全面理解能力。例如,在解码器的自注意力机制中:
- 给定句子
The cat sat on the mat.
,当生成sat
时,解码器只能看到The cat
,而无法利用右侧的上下文on the mat
。
这种单向限制对于 BERT 的目标任务(如 MLM
)是不可接受的,因为 MLM
要求模型能够同时利用上下文的左右信息。
真实世界案例分析
案例 1:文本分类任务
在新闻分类任务中,BERT 需要根据文章的内容分类。如果模型只能看到单向上下文,它可能会遗漏重要信息。例如:
-
句子
The stock market experienced a significant drop due to geopolitical tensions.
如果模型只能看到
The stock market experienced a significant drop
,它可能会将其误分类为经济新闻。而通过编码器的双向机制,模型可以看到完整句子并准确判断类别。
案例 2:问答任务
在问答任务中,BERT 需要从上下文中提取答案。例如:
-
问题:
Where is the Eiffel Tower located?
-
上下文:
The Eiffel Tower is located in Paris, which is the capital of France.
如果模型只能单向查看上下文,它可能无法准确定位
Paris
,因为其依赖于前后信息的结合。
代码示例:编码器与解码器的对比
以下是一个简单的代码示例,展示编码器和解码器在处理输入序列时的差异。
import torch
from torch import nn
from transformers import BertModel, BertTokenizer
# 使用编码器 (BERT)
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')
sentence = "The stock market experienced a significant drop due to geopolitical tensions."
inputs = tokenizer(sentence, return_tensors='pt')
# BERT 编码器生成双向表示
outputs = model(**inputs)
print("Encoder Outputs:", outputs.last_hidden_state.shape)
# 解码器部分示例
class SimpleDecoder(nn.Module):
def __init__(self, vocab_size, hidden_dim):
super().__init__()
self.embedding = nn.Embedding(vocab_size, hidden_dim)
self.lstm = nn.LSTM(hidden_dim, hidden_dim, batch_first=True)
self.fc = nn.Linear(hidden_dim, vocab_size)
def forward(self, x):
embedded = self.embedding(x)
output, _ = self.lstm(embedded)
return self.fc(output)
# 解码器仅使用单向表示生成序列
decoder = SimpleDecoder(vocab_size=30522, hidden_dim=768)
x = torch.randint(0, 30522, (1, 10))
decoder_outputs = decoder(x)
print("Decoder Outputs:", decoder_outputs.shape)
在上述代码中,BERT 的编码器能够生成包含上下文关系的表示,而解码器更注重生成序列的逐步预测。对于需要双向表示的任务,编码器显然更胜一筹。
结论
通过分析 BERT 的设计目标和 Transformer 的架构特点,可以清楚地看到编码器的双向特性是实现语言理解任务的关键,而解码器的单向特性更适合生成任务。通过仅使用编码器,BERT 专注于表示学习,从而在多种下游任务中取得优异的性能。