← 返回首页
(Decoder)解码器
发表时间:2025-04-17 15:09:24
(Decoder)解码器

1.Decoder结构

上图红色部分为 Transformer 的 Decoder block 结构,与 Encoder block 相似,但是存在一些区别:

1.1第一个 Multi-Head Attention

Decoder block 的第一个 Multi-Head Attention 采用了 Masked 操作,因为在翻译的过程中是顺序翻译的,即翻译完第 i 个单词,才可以翻译第 i+1 个单词。通过 Masked 操作可以防止第 i 个单词知道 i+1 个单词之后的信息。下面以 "我有一只猫" 翻译成 "I have a cat" 为例,了解一下 Masked 操作。

下面的描述中使用了类似 Teacher Forcing 的概念,不熟悉 Teacher Forcing 的童鞋可以参考以下上一篇文章Seq2Seq 模型详解。在 Decoder 的时候,是需要根据之前的翻译,求解当前最有可能的翻译,如下图所示。首先根据输入 "" 预测出第一个单词为 "I",然后根据输入 " I" 预测下一个单词 "have"。

Decoder 可以在训练的过程中使用 Teacher Forcing 并且并行化训练,即将正确的单词序列 ( I have a cat) 和对应输出 (I have a cat ) 传递到 Decoder。那么在预测第 i 个输出时,就要将第 i+1 之后的单词掩盖住,注意 Mask 操作是在 Self-Attention 的 Softmax 之前使用的,下面用 0 1 2 3 4 5 分别表示 " I have a cat "。

第一步:是 Decoder 的输入矩阵和 Mask 矩阵,输入矩阵包含 " I have a cat" (0, 1, 2, 3, 4) 五个单词的表示向量,Mask 是一个 5×5 的矩阵。在 Mask 可以发现单词 0 只能使用单词 0 的信息,而单词 1 可以使用单词 0, 1 的信息,即只能使用之前的信息。

第二步:接下来的操作和之前的 Self-Attention 一样,通过输入矩阵X计算得到Q,K,V矩阵。然后计算Q和KT的乘积QKT。

第三步:在得到QKT之后需要进行 Softmax,计算 attention score,我们在 Softmax 之前需要使用Mask矩阵遮挡住每一个单词之后的信息,遮挡操作如下:

得到 Mask QKT 之后在 Mask QKT上进行 Softmax,每一行的和都为 1。但是单词 0 在单词 1, 2, 3, 4 上的 attention score 都为 0。

第四步:使用 Mask QKT与矩阵 V相乘,得到输出 Z,则单词 1 的输出向量 Z1 是只包含单词 1 信息的。

第五步:通过上述步骤就可以得到一个 Mask Self-Attention 的输出矩阵 Zi ,然后和 Encoder 类似,通过 Multi-Head Attention 拼接多个输出Zi 然后计算得到第一个 Multi-Head Attention 的输出Z,Z与输入X维度一样。

1.2 第二个 Multi-Head Attention

Decoder block 第二个 Multi-Head Attention 变化不大, 主要的区别在于其中 Self-Attention 的 K, V矩阵不是使用 上一个 Decoder block 的输出计算的,而是使用 Encoder 的编码信息矩阵 C 计算的。

根据 Encoder 的输出 C计算得到 K, V,根据上一个 Decoder block 的输出 Z 计算 Q (如果是第一个 Decoder block 则使用输入矩阵 X 进行计算),后续的计算方法与之前描述的一致。

这样做的好处是在 Decoder 的时候,每一位单词都可以利用到 Encoder 所有单词的信息 (这些信息无需 Mask)。

1.3 Softmax 预测输出单词

Decoder block 最后的部分是利用 Softmax 预测下一个单词,在之前的网络层我们可以得到一个最终的输出 Z,因为 Mask 的存在,使得单词 0 的输出 Z0 只包含单词 0 的信息,如下:

Softmax 根据输出矩阵的每一行预测下一个单词:

这就是 Decoder block 的定义,与 Encoder 一样,Decoder 是由多个 Decoder block 组合而成。

2.Transformer总结

## 3.PyTorch实现Transformer模型进行中文文本预测

下面是一个完整的PyTorch实现,使用Transformer模型来根据输入"我学习"预测出"人工智能":

import torch
import torch.nn as nn
import torch.optim as optim
import math

# 数据预处理
sentence = "我正在学习人工智能"
chars = list(sentence)
vocab = {char: idx for idx, char in enumerate(chars)}
vocab_size = len(chars)
indexed_sentence = [vocab[char] for char in chars]

# 构建训练数据
data = torch.tensor(indexed_sentence[:-1], dtype=torch.long).unsqueeze(1)  # 输入序列
target = torch.tensor(indexed_sentence[1:], dtype=torch.long).unsqueeze(1)  # 目标序列

# 定义位置编码
class PositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=5000):
        super().__init__()
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(1)
        self.register_buffer('pe', pe)

    def forward(self, x):
        x = x + self.pe[:x.size(0)]
        return x

# 定义Transformer模型
class TransformerModel(nn.Module):
    def __init__(self, vocab_size, d_model, nhead, num_layers, dim_feedforward, max_seq_length):
        super().__init__()
        self.d_model = d_model
        self.embedding = nn.Embedding(vocab_size, d_model)
        self.pos_encoder = PositionalEncoding(d_model, max_seq_length)
        encoder_layers = nn.TransformerEncoderLayer(d_model, nhead, dim_feedforward)
        self.transformer_encoder = nn.TransformerEncoder(encoder_layers, num_layers)
        self.fc_out = nn.Linear(d_model, vocab_size)

    def forward(self, src, src_mask=None):
        if src_mask is None:
            device = src.device
            src_mask = nn.Transformer.generate_square_subsequent_mask(len(src)).to(device)
        embedded = self.embedding(src) * math.sqrt(self.d_model)
        embedded = self.pos_encoder(embedded)
        output = self.transformer_encoder(embedded, src_mask)
        output = self.fc_out(output)
        return output

# 初始化模型参数
d_model = 128
nhead = 4
num_layers = 2
dim_feedforward = 256
max_seq_length = 20

model = TransformerModel(vocab_size, d_model, nhead, num_layers, dim_feedforward, max_seq_length)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练模型
num_epochs = 200
for epoch in range(num_epochs):
    model.train()
    optimizer.zero_grad()
    output = model(data)
    loss = criterion(output.view(-1, vocab_size), target.view(-1))
    loss.backward()
    optimizer.step()
    if (epoch + 1) % 20 == 0:
        print(f'Epoch {epoch + 1}, Loss: {loss.item()}')

# 生成预测
def generate(model, start_ids, max_length):
    model.eval()
    generated = start_ids.copy()
    for _ in range(max_length):
        src = torch.tensor(generated, dtype=torch.long).unsqueeze(1)
        with torch.no_grad():
            output = model(src)
        next_token = output[-1, 0, :].argmax().item()
        generated.append(next_token)
    return generated

# 输入“我学习”对应的索引 [0, 3, 4]
start_ids = [0, 3, 4]
generated_ids = generate(model, start_ids, 4)
generated_text = ''.join([chars[idx] for idx in generated_ids[len(start_ids):]])

print("输入:", ''.join([chars[idx] for idx in start_ids]))
print("输出:", generated_text)

运行效果:

Epoch 20, Loss: 0.014755085110664368
Epoch 40, Loss: 0.006446681916713715
Epoch 60, Loss: 0.005328853614628315
Epoch 80, Loss: 0.003554699709638953
Epoch 100, Loss: 0.0032910886220633984
Epoch 120, Loss: 0.0025901016779243946
Epoch 140, Loss: 0.002641542349010706
Epoch 160, Loss: 0.0021434680093079805
Epoch 180, Loss: 0.001933875260874629
Epoch 200, Loss: 0.0017221643356606364
输入: 我学习
输出: 人工智能