【附源码】基于LSTM-Seq2Seq与注意力机制的英汉机器翻译实现

8 阅读5分钟

【附源码】基于LSTM-Seq2Seq与注意力机制的英汉机器翻译实现

神经机器翻译(Neural Machine Translation, NMT)自2014年提出以来,逐步取代了传统的统计机器翻译方法,成为当前机器翻译领域的主流技术路线。

本文将介绍一种基于序列到序列(Sequence-to-Sequence, Seq2Seq)架构的英汉翻译系统实现,采用双向LSTM编码器配合注意力机制,在PyTorch框架下完成模型构建与训练。

模型架构设计

整体框架

本项目采用经典的Encoder-Decoder架构。编码器负责将源语言序列(英语)压缩为固定长度的上下文向量,解码器则基于该向量逐词生成目标语言序列(中文)。为提升长句翻译质量,在解码器中引入全局注意力机制,使模型在生成每个目标词时能够动态关注源序列的不同位置。

编码器实现

编码器采用双向LSTM(BiLSTM)结构,其核心优势在于能够同时捕获序列的前向和后向上下文信息。具体实现如下:

class Encoder(nn.Module):
    def __init__(self, vocab_size, embed_dim, hidden_dim, num_layers, dropout):
        super(Encoder, self).__init__()
        self.hidden_dim = hidden_dim
        self.num_layers = num_layers
        
        self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
        self.lstm = nn.LSTM(
            embed_dim, hidden_dim, num_layers,
            dropout=dropout if num_layers > 1 else 0,
            bidirectional=True,
            batch_first=True
        )
        # 将双向LSTM的输出维度映射回隐藏层维度
        self.fc_hidden = nn.Linear(hidden_dim * 2, hidden_dim)
        self.fc_cell = nn.Linear(hidden_dim * 2, hidden_dim)
        self.dropout = nn.Dropout(dropout)

双向LSTM的输出维度为hidden_dim * 2,需通过全连接层将其映射回hidden_dim,以匹配解码器的输入维度。此外,使用pack_padded_sequence对变长序列进行打包处理,避免填充符参与计算,提升训练效率。

注意力机制

注意力机制的计算采用加性评分函数(Additive Scoring):

class Attention(nn.Module):
    def __init__(self, hidden_dim):
        super(Attention, self).__init__()
        self.attn = nn.Linear(hidden_dim * 3, hidden_dim)
        self.v = nn.Linear(hidden_dim, 1, bias=False)
    
    def forward(self, hidden, encoder_outputs, mask=None):
        src_len = encoder_outputs.shape[1]
        hidden = hidden.unsqueeze(1).repeat(1, src_len, 1)
        energy = torch.tanh(self.attn(torch.cat((hidden, encoder_outputs), dim=2)))
        attention = self.v(energy).squeeze(2)
        if mask is not None:
            attention = attention.masked_fill(mask == 0, -1e10)
        return torch.softmax(attention, dim=1)

该模块接收解码器当前隐藏状态与编码器全部输出,计算注意力权重分布。掩码机制用于屏蔽填充位置,确保模型不会关注到无效信息。

解码器实现

解码器为单向LSTM,在每个时间步接收上一时刻的输出(或真实标签,取决于教师强制策略)、前一时刻的隐藏状态与细胞状态,以及经注意力加权后的上下文向量:

class Decoder(nn.Module):
    def forward(self, input, hidden, cell, encoder_outputs=None, mask=None):
        input = input.unsqueeze(1)
        embedded = self.dropout(self.embedding(input))
        
        if self.use_attention and encoder_outputs is not None:
            attn_weights = self.attention(hidden[-1], encoder_outputs, mask)
            attn_weights = attn_weights.unsqueeze(1)
            context = torch.bmm(attn_weights, encoder_outputs)
            lstm_input = torch.cat((embedded, context), dim=2)
        else:
            lstm_input = embedded
            context = None
        
        output, (hidden, cell) = self.lstm(lstm_input, (hidden, cell))
        # 输出层融合隐藏状态与上下文向量
        if self.use_attention and context is not None:
            output = torch.cat((output.squeeze(1), context.squeeze(1)), dim=1)
        else:
            output = output.squeeze(1)
        
        prediction = self.fc_out(output)
        return prediction, hidden, cell

输出层将LSTM输出与上下文向量拼接后映射至目标词汇表空间,实现词语预测。

数据预处理

词汇表构建

词汇表管理采用Vocabulary类,维护词到索引的双向映射。特殊标记定义如下:

  • <PAD>:填充符,索引0
  • <UNK>:未知词,索引1
  • <SOS>:序列起始符,索引2
  • <EOS>:序列终止符,索引3
class Vocabulary:
    def __init__(self):
        self.word2idx = {PAD_TOKEN: 0, UNK_TOKEN: 1, SOS_TOKEN: 2, EOS_TOKEN: 3}
        self.idx2word = {0: PAD_TOKEN, 1: UNK_TOKEN, 2: SOS_TOKEN, 3: EOS_TOKEN}
        self.word_count = Counter()
        self.n_words = 4
    
    def build(self, min_freq=MIN_FREQ):
        for word, count in self.word_count.most_common(MAX_VOCAB_SIZE):
            if count >= min_freq:
                self.word2idx[word] = self.n_words
                self.idx2word[self.n_words] = word
                self.n_words += 1

低频词过滤阈值设为2,词汇表上限为50000,兼顾覆盖率与计算效率。

文本分词

英语文本采用空格分词,经正则表达式去除标点并统一小写;中文文本使用jieba分词工具:

en_tokens = re.sub(r"[^a-z0-9\s]", " ", en.lower()).split()
zh_tokens = [t for t in jieba.cut(zh) if t.strip()]

数据加载

自定义TranslationDataset继承自torch.utils.data.Dataset,配合collate_fn实现动态填充:

def collate_fn(batch):
    src_batch, tgt_batch = zip(*batch)
    src_len, tgt_len = [len(s) for s in src_batch], [len(t) for t in tgt_batch]
    
    src_pad = torch.zeros(len(batch), max(src_len), dtype=torch.long)
    for i, seq in enumerate(src_batch):
        src_pad[i, :len(seq)] = seq
    # 目标序列同理填充
    return src_pad, tgt_pad, src_len, tgt_len

训练策略

教师强制

训练过程中采用教师强制(Teacher Forcing)技术,以概率teacher_forcing_ratio决定当前时间步的输入是使用真实标签还是上一时刻的预测结果。这一机制有助于加速收敛,但过高的比例可能导致模型对训练数据过拟合。本实现将该比例设为0.5。

优化与正则化

优化器选用Adam,初始学习率0.001。梯度裁剪阈值设为1.0,防止梯度爆炸。损失函数采用交叉熵损失,忽略填充位置的计算:

criterion = nn.CrossEntropyLoss(ignore_index=0)

训练循环

for epoch in range(NUM_EPOCHS):
    train_loss = train_epoch(model, train_loader, optimizer, criterion)
    val_loss = evaluate(model, val_loader, criterion)
    
    if val_loss < best_val_loss:
        best_val_loss = val_loss
        torch.save(model.state_dict(), 'best_model.pt')

模型按7:3比例划分训练集与验证集,共15个epoch,根据验证集损失保存最优模型。

实验配置

超参数取值
嵌入维度256
隐藏层维度512
LSTM层数2
Dropout0.3
批量大小64
训练轮数15

数据集包含29155条英汉平行句对,经处理后英文词汇表规模为4425,中文词汇表规模为6496。模型总参数量约2980万。

总结

本文实现了一个完整的基于Seq2Seq架构的英汉神经机器翻译系统。双向LSTM编码器有效捕获了源序列的双向语义信息,注意力机制的引入使模型能够处理较长句子,缓解了固定长度上下文向量的信息瓶颈问题。该实现可作为神经机器翻译的基线模型,后续可在此基础上引入Transformer架构、子词分词(BPE)等技术进一步提升翻译质量。


作者:谙弆悕博士(Ailan Anjuxi)

⚠️源码已发布到我的Github !