趋近智
大师班
“循环神经网络(RNN)、长短期记忆网络(LSTM)和门控循环单元(GRU)按元素处理序列,但许多问题需要将一个长度的输入序列映射到可能不同长度的输出序列。例如,机器翻译(将法语句子翻译成英语)或文本摘要(将长文章压缩成几句话)。输入和输出的长度通常没有直接关系。标准RNN架构通常为每个输入产生一个输出,因此不直接适用于这些任务。”
为解决此问题,序列到序列(seq2seq)框架应运而生,主要采用长短期记忆网络(LSTM)或门控循环单元(GRU)等循环架构。其核心思想是使用两个独立的循环神经网络:一个处理输入序列(编码器),另一个生成输出序列(解码器)。
seq2seq模型包含两个主要组成部分:
编码器:这个循环神经网络逐个读取输入序列的令牌(例如,单词或子词)。它的目标不是在每一步都生成输出,而是将整个输入序列的信息压缩成一个固定大小的向量表示。这个向量常被称为“上下文向量”或“思维向量”,通常由编码器RNN的最终隐藏状态(以及长短期记忆网络的细胞状态)表示。
解码器:这个循环神经网络将编码器生成的上下文向量作为其初始隐藏状态。然后,它逐个令牌地生成输出序列。在每一步t,解码器接收上下文向量、它自己的前一个隐藏状态ht−1,以及前面生成的输出令牌yt−1作为输入,以生成下一个输出令牌yt并将其隐藏状态更新为ht。生成过程通常以一个特殊的序列开始符<SOS>令牌开始,并持续到生成序列结束符<EOS>令牌或达到最大长度为止。
基于RNN的序列到序列模型的高层结构。编码器处理输入以生成上下文向量,该向量用于初始化解码器以生成输出序列。
编码器处理输入序列X=(x1,x2,...,xn)并输出一个上下文向量c。这个向量c旨在总结整个输入序列。
c=编码器(x1,x2,...,xn)通常,对于长短期记忆网络, c 将是最终的隐藏状态 hn 和细胞状态 Cn。
解码器用这个上下文进行初始化(例如,h0dec=hnenc,C0dec=Cnenc)。然后,它一次生成一个令牌,产生输出序列Y=(y1,y2,...,ym)。下一个令牌yt的概率取决于上下文c、前一个令牌yt−1以及解码器当前的隐藏状态htdec:
P(yt∣y1,...,yt−1,c)=解码器(yt−1,ht−1dec,c)解码器的第一个输入通常是一个特殊的<SOS>令牌(y0=<SOS>)。
我们来概述使用PyTorch nn.LSTM的简化编码器和解码器模块。
import torch
import torch.nn as nn
class EncoderRNN(nn.Module):
def __init__(self, input_size, hidden_size, num_layers=1):
super(EncoderRNN, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
# 注意:假设 input_size 是嵌入维度
self.embedding = nn.Embedding(input_size, hidden_size)
self.lstm = nn.LSTM(
hidden_size, hidden_size, num_layers, batch_first=True
)
def forward(self, input_seq):
# input_seq 形状:(batch_size, seq_length)
embedded = self.embedding(input_seq)
# embedded 形状:(batch_size, seq_length, hidden_size)
# 初始化隐藏状态(为简化起见未显示,
# 默认为零)
# hidden = self.init_hidden(batch_size)
outputs, (hidden, cell) = self.lstm(embedded)
# outputs 形状:(batch_size, seq_length, hidden_size)
# hidden 形状:(num_layers, batch_size, hidden_size)
# cell 形状:(num_layers, batch_size, hidden_size)
# 我们通常使用最终的隐藏状态和细胞状态作为上下文
return hidden, cell
class DecoderRNN(nn.Module):
def __init__(self, hidden_size, output_size, num_layers=1):
super(DecoderRNN, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
# 注意:output_size 是目标语言的词汇表大小
self.embedding = nn.Embedding(output_size, hidden_size)
self.lstm = nn.LSTM(
hidden_size, hidden_size, num_layers, batch_first=True
)
self.out = nn.Linear(hidden_size, output_size)
self.softmax = nn.LogSoftmax(dim=1) # 经常与NLLLoss一起使用
def forward(self, input_token, hidden, cell):
# input_token 形状:(batch_size, 1) -> 单个令牌
# hidden 形状:(num_layers, batch_size, hidden_size)
# cell 形状:(num_layers, batch_size, hidden_size)
embedded = self.embedding(input_token)
# embedded 形状:(batch_size, 1, hidden_size)
# 上下文向量(编码器最终的隐藏/细胞状态)
# 在此处作为初始隐藏/细胞状态传入。
output, (hidden, cell) = self.lstm(embedded, (hidden, cell))
# output 形状:(batch_size, 1, hidden_size)
# 将输出重塑为 (batch_size, hidden_size) 以用于
# 线性层
output = output.squeeze(1)
output = self.out(output)
# output 形状:(batch_size, output_size)
# 可选:应用 softmax 以获得
# 概率/对数概率
# output = self.softmax(output)
return output, hidden, cell
# 示例用法
# input_vocab_size = 10000
# output_vocab_size = 12000
# hidden_dim = 256
# n_layers = 2
# batch_size = 32
# input_length = 50
# encoder = EncoderRNN(input_vocab_size, hidden_dim, n_layers)
# decoder = DecoderRNN(hidden_dim, output_vocab_size, n_layers)
# 示例输入批次(索引)
# input_tensor = torch.randint(
# 0, input_vocab_size, (batch_size, input_length)
# )
# 传入编码器
# encoder_hidden, encoder_cell = encoder(input_tensor)
# 解码器输入以 <SOS> 令牌开始(假设索引为 0)
# decoder_input = torch.full((batch_size, 1), 0, dtype=torch.long)
# decoder_hidden = encoder_hidden # 使用编码器最终的隐藏状态
# decoder_cell = encoder_cell # 使用编码器最终的细胞状态
# 逐步生成输出序列(简化循环)
# max_target_length = 60
# all_decoder_outputs = []
# for _ in range(max_target_length):
# decoder_output, decoder_hidden, decoder_cell = decoder(
# decoder_input, decoder_hidden, decoder_cell
# )
# all_decoder_outputs.append(decoder_output)
#
# # 获取最有可能的下一个令牌(贪婪解码)
# _, top_idx = decoder_output.topk(1)
# # 使用预测的令牌作为下一个输入
# decoder_input = top_idx.detach()
使用循环神经网络的标准编码器-解码器架构在许多任务中被证明是有效的。然而,它依赖于将整个输入序列压缩成一个单一的固定大小的上下文向量。这会产生一个信息瓶颈,尤其对于长输入序列而言问题更突出。当模型生成输出序列的末尾时,它很难记住长输入开头部分的细节。
这一局限性是注意力机制发展的重要推动力。注意力机制让解码器在输出生成过程的每一步,能够选择性地关注输入序列的不同部分,而不是仅仅依赖于单一的上下文向量。这种回顾源输入相关部分的能力大幅提升了机器翻译等任务的性能,并为我们将在下一章中介绍的Transformer架构打下了基础。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造