基于Python的自然语言处理系列(14):TorchText + biGRU + Attention + Teacher Forcing

在前几篇文章中,我们探索了序列到序列(seq2seq)模型的基础,并通过使用双向GRU和上下文向量改进了模型的表现。然而,模型仍然依赖一个固定的上下文向量,这意味着它必须从整个源句中压缩信息,导致在长句子的翻译中可能出现问题。

在本篇文章中,我们将引入注意力机制来解决这个问题。注意力机制允许解码器在每一步解码时不仅仅依赖一个固定的上下文向量,而是能够动态地访问源句中的所有信息。这样,模型可以在解码过程中"关注"到最相关的词,从而提升翻译的准确性,尤其是长句子。

1. 背景

在传统的seq2seq模型中,解码器仅依赖编码器生成的一个上下文向量。尽管我们通过双向GRU改进了模型,但上下文向量仍然需要压缩整个源句的信息,限制了模型的表现。

为了解决这个问题,注意力机制通过计算源句中每个词的权重,让解码器能够动态地关注源句中的不同部分,而不仅仅是依赖一个固定的上下文向量。这不仅提升了模型对长句子的处理能力,还提高了翻译的准确性。

2. 数据加载与预处理

我们继续使用TorchText加载Multi30k数据集,并使用spacy进行标记化处理。数据加载的流程与之前文章中的内容相似。

python 复制代码
from torchtext.datasets import Multi30k
from torchtext.data.utils import get_tokenizer

SRC_LANGUAGE = 'en'
TRG_LANGUAGE = 'de'

train = Multi30k(split=('train'), language_pair=(SRC_LANGUAGE, TRG_LANGUAGE))

token_transform = {}
token_transform[SRC_LANGUAGE] = get_tokenizer('spacy', language='en_core_web_sm')
token_transform[TRG_LANGUAGE] = get_tokenizer('spacy', language='de_core_news_sm')

与前面相同,我们将数据集分为训练集、验证集和测试集,并将文本进行数值化处理。

3. 模型设计

在这个模型中,我们将实现一个结合了双向GRU注意力机制的seq2seq模型。模型结构包括以下几个部分:

3.1 编码器(Encoder)

首先,我们将构建编码器。这里我们使用双向GRU,将输入序列从左到右和从右到左进行编码。编码器输出的隐状态将作为解码器的初始隐状态,并传递给注意力机制。

python 复制代码
class Encoder(nn.Module):
    def __init__(self, input_dim, emb_dim, hid_dim, dropout):
        super().__init__()
        
        self.embedding = nn.Embedding(input_dim, emb_dim)
        self.rnn = nn.GRU(emb_dim, hid_dim, bidirectional=True)
        self.fc = nn.Linear(hid_dim * 2, hid_dim)
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, src):
        embedded = self.dropout(self.embedding(src))
        outputs, hidden = self.rnn(embedded)
        hidden = torch.tanh(self.fc(torch.cat((hidden[-2,:,:], hidden[-1,:,:]), dim=1)))
        return outputs, hidden

3.2 注意力机制(Attention)

注意力机制的作用是计算解码器当前隐状态与源句每个词的隐状态之间的权重,帮助解码器决定应该关注源句的哪些部分。权重越大,说明该词对当前解码步骤越重要。

python 复制代码
class Attention(nn.Module):
    def __init__(self, hid_dim):
        super().__init__()
        self.v = nn.Linear(hid_dim, 1, bias=False)
        self.W = nn.Linear(hid_dim, hid_dim)
        self.U = nn.Linear(hid_dim * 2, hid_dim)

    def forward(self, hidden, encoder_outputs):
        batch_size = encoder_outputs.shape[1]
        src_len = encoder_outputs.shape[0]
        
        hidden = hidden.unsqueeze(1).repeat(1, src_len, 1)
        encoder_outputs = encoder_outputs.permute(1, 0, 2)
        
        energy = torch.tanh(self.W(hidden) + self.U(encoder_outputs))
        attention = self.v(energy).squeeze(2)
        return F.softmax(attention, dim=1)

3.3 解码器(Decoder)

解码器在每个时刻生成一个新的目标词。它使用注意力机制得到的加权源句信息,以及解码器自身的隐状态来生成目标词的预测。

python 复制代码
class Decoder(nn.Module):
    def __init__(self, output_dim, emb_dim, hid_dim, dropout, attention):
        super().__init__()

        self.output_dim = output_dim
        self.attention = attention
        self.embedding = nn.Embedding(output_dim, emb_dim)
        self.gru = nn.GRU((hid_dim * 2) + emb_dim, hid_dim)
        self.fc_out = nn.Linear((hid_dim * 2) + hid_dim + emb_dim, output_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, input, hidden, encoder_outputs):
        input = input.unsqueeze(0)
        embedded = self.dropout(self.embedding(input))

        a = self.attention(hidden, encoder_outputs).unsqueeze(1)
        encoder_outputs = encoder_outputs.permute(1, 0, 2)
        weighted = torch.bmm(a, encoder_outputs).permute(1, 0, 2)

        rnn_input = torch.cat((embedded, weighted), dim=2)
        output, hidden = self.gru(rnn_input, hidden.unsqueeze(0))
        embedded = embedded.squeeze(0)
        output = output.squeeze(0)
        weighted = weighted.squeeze(0)

        prediction = self.fc_out(torch.cat((output, weighted, embedded), dim=1))
        return prediction, hidden.squeeze(0)

3.4 Seq2Seq模型

将编码器、解码器和注意力机制组合起来,我们构建了完整的seq2seq模型。

python 复制代码
class Seq2SeqAttention(nn.Module):
    def __init__(self, encoder, decoder, device):
        super().__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.device = device

    def forward(self, src, trg, teacher_forcing_ratio=0.5):
        batch_size = src.shape[1]
        trg_len = trg.shape[0]
        trg_vocab_size = self.decoder.output_dim
        outputs = torch.zeros(trg_len, batch_size, trg_vocab_size).to(self.device)

        encoder_outputs, hidden = self.encoder(src)
        input_ = trg[0, :]

        for t in range(1, trg_len):
            output, hidden = self.decoder(input_, hidden, encoder_outputs)
            outputs[t] = output
            top1 = output.argmax(1)
            input_ = trg[t] if random.random() < teacher_forcing_ratio else top1

        return outputs

4. 训练与评估

我们使用与前几篇文章相同的训练和评估函数。为了防止梯度爆炸,我们在训练过程中应用梯度裁剪。

python 复制代码
def train(model, iterator, optimizer, criterion, clip):
    model.train()
    epoch_loss = 0
    for i, (src, trg) in enumerate(iterator):
        src, trg = src.to(device), trg.to(device)
        optimizer.zero_grad()
        output = model(src, trg)
        output_dim = output.shape[-1]
        output = output[1:].view(-1, output_dim)
        trg = trg[1:].view(-1)
        loss = criterion(output, trg)
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), clip)
        optimizer.step()
        epoch_loss += loss.item()
    return epoch_loss / len(iterator)

def evaluate(model, iterator, criterion):
    model.eval()
    epoch_loss = 0
    with torch.no_grad():
        for i, (src, trg) in enumerate(iterator):
            src, trg = src.to(device), trg.to(device)
            output = model(src, trg, 0)
            output_dim = output.shape[-1]
            output = output[1:].view(-1, output_dim)
            trg = trg[1:].view(-1)
            loss = criterion(output, trg)
            epoch_loss += loss.item()
    return epoch_loss / len(iterator)

训练模型:

python 复制代码
for epoch in range(10):
    train_loss = train(model, train_loader, optimizer, criterion, 1)
    val_loss = evaluate(model, valid_loader, criterion)
    print(f'Epoch {epoch+1} | Train Loss: {train_loss:.3f}, Val Loss: {val_loss:.3f}')

结语

通过在seq2seq模型中引入注意力机制,我们成功提升了模型对长句子的处理能力。在每一个解码步骤中,解码器能够灵活地访问源句中的每个词,而不再依赖一个固定的上下文向量。这大大减少了信息压缩问题,使得模型在翻译复杂句子时更加精准。

尽管注意力机制为模型带来了显著的改进,但训练时间相对增加。尤其在处理长句子时,模型需要计算源句中每个词的注意力权重,增加了计算复杂度。

在下一篇文章中,我们将结合双向GRU注意力机制 以及Packed Padded SequencesMasking技术,进一步优化模型的训练过程。同时,我们将展示如何通过这些技术处理不同长度的输入序列,并通过可视化注意力权重,更深入地理解模型在解码时关注哪些词。敬请期待!

如果你觉得这篇博文对你有帮助,请点赞、收藏、关注我,并且可以打赏支持我!

欢迎关注我的后续博文,我将分享更多关于人工智能、自然语言处理和计算机视觉的精彩内容。

谢谢大家的支持!

相关推荐
AltmanChan几秒前
大语言模型安全威胁
人工智能·安全·语言模型
985小水博一枚呀4 分钟前
【深度学习滑坡制图|论文解读2】基于融合CNN-Transformer网络和深度迁移学习的遥感影像滑坡制图方法
人工智能·深度学习·神经网络·cnn·transformer·迁移学习
数据与后端架构提升之路14 分钟前
从神经元到神经网络:深度学习的进化之旅
人工智能·神经网络·学习
爱技术的小伙子19 分钟前
【ChatGPT】如何通过逐步提示提高ChatGPT的细节描写
人工智能·chatgpt
深度学习实战训练营2 小时前
基于CNN-RNN的影像报告生成
人工智能·深度学习
昨日之日20064 小时前
Moonshine - 新型开源ASR(语音识别)模型,体积小,速度快,比OpenAI Whisper快五倍 本地一键整合包下载
人工智能·whisper·语音识别
浮生如梦_4 小时前
Halcon基于laws纹理特征的SVM分类
图像处理·人工智能·算法·支持向量机·计算机视觉·分类·视觉检测
深度学习lover4 小时前
<项目代码>YOLOv8 苹果腐烂识别<目标检测>
人工智能·python·yolo·目标检测·计算机视觉·苹果腐烂识别
热爱跑步的恒川5 小时前
【论文复现】基于图卷积网络的轻量化推荐模型
网络·人工智能·开源·aigc·ai编程
阡之尘埃7 小时前
Python数据分析案例61——信贷风控评分卡模型(A卡)(scorecardpy 全面解析)
人工智能·python·机器学习·数据分析·智能风控·信贷风控