基于Python的自然语言处理系列(16):TorchText + CNN + Teacher Forcing

在本篇文章中,我们将实现 卷积序列到序列学习模型(Convolutional Sequence to Sequence Learning) 。与之前介绍的基于循环神经网络(RNN)的模型不同,卷积模型不依赖递归成分,而是通过卷积层(CNN)来实现序列间的特征提取与学习。我们还将继续使用 Teacher Forcing 来增强训练效果。

模型简介

本模型的核心区别在于使用了 卷积层 来处理输入序列,而非递归结构。卷积层通过滑动窗口和滤波器机制逐步处理输入序列的多个连续词汇片段,并从中提取特征。在这篇教程中,我们将使用 1024 个滤波器,每个滤波器可以"看到"3个连续词汇,从而为模型提取有意义的特征,这些特征会被进一步传递给下一层。

下图展示了基于卷积的序列学习模型的工作原理:

在这个模型中,编码器 将源序列(原始语言的句子)编码成上下文向量(context vector),而 解码器 则基于该上下文向量生成目标序列(翻译后的句子)。为了在卷积网络中体现输入序列的顺序信息,模型还使用了 位置嵌入(Positional Embedding)

实现步骤

1. 数据加载与预处理

首先,我们使用 TorchText 加载 Multi30k 数据集,这个数据集包含英语到德语的翻译句子。为了让卷积神经网络处理更高效,我们将批量处理的方式改为以 batch first 的形式,即批次维度位于最前。这样可以确保 CNN 模型能够正确接收数据。

python 复制代码
import torch, torchdata, torchtext
from torch import nn
import random, math, time

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
SEED = 1234
torch.manual_seed(SEED)
torch.backends.cudnn.deterministic = True

# 加载 Multi30k 数据集
from torchtext.datasets import Multi30k

SRC_LANGUAGE = 'en'
TRG_LANGUAGE = 'de'

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

# 数据预处理,包括分词和构建词汇表
from torchtext.data.utils import get_tokenizer
token_transform = {
    SRC_LANGUAGE: get_tokenizer('spacy', language='en_core_web_sm'),
    TRG_LANGUAGE: get_tokenizer('spacy', language='de_core_news_sm')
}

vocab_transform = {}
from torchtext.vocab import build_vocab_from_iterator

for ln in [SRC_LANGUAGE, TRG_LANGUAGE]:
    vocab_transform[ln] = build_vocab_from_iterator(yield_tokens(train, ln), 
                                                    min_freq=2, specials=['<unk>', '<pad>', '<sos>', '<eos>'])

2. 模型设计

本模型的设计分为 编码器(Encoder)解码器(Decoder) 两部分。编码器负责将输入序列转换为上下文向量,解码器则根据上下文向量生成翻译后的目标序列。

2.1 编码器

编码器的主要任务是将输入的句子转换为特征向量。它使用卷积层从输入句子中提取特征,并通过 位置嵌入 提供位置信息,以保持序列的顺序。

python 复制代码
class Encoder(nn.Module):
    def __init__(self, input_dim, emb_dim, hid_dim, n_layers, kernel_size, dropout, device, max_length=100):
        super().__init__()
        
        self.device = device
        self.scale = torch.sqrt(torch.FloatTensor([0.5])).to(device)
        
        self.tok_embedding = nn.Embedding(input_dim, emb_dim)
        self.pos_embedding = nn.Embedding(max_length, emb_dim)
        
        self.emb2hid = nn.Linear(emb_dim, hid_dim)
        self.hid2emb = nn.Linear(hid_dim, emb_dim)
        
        self.convs = nn.ModuleList([nn.Conv1d(in_channels=hid_dim, out_channels=2*hid_dim, kernel_size=kernel_size, padding=(kernel_size - 1) // 2) for _ in range(n_layers)])
        
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, src):
        batch_size = src.shape[0]
        src_len = src.shape[1]
        
        pos = torch.arange(0, src_len).unsqueeze(0).repeat(batch_size, 1).to(self.device)
        tok_embedded = self.tok_embedding(src)
        pos_embedded = self.pos_embedding(pos)
        
        embedded = self.dropout(tok_embedded + pos_embedded)
        conv_input = self.emb2hid(embedded).permute(0, 2, 1)
        
        for conv in self.convs:
            conved = F.glu(conv(self.dropout(conv_input)), dim=1)
            conved = (conved + conv_input) * self.scale
            conv_input = conved
            
        conved = self.hid2emb(conved.permute(0, 2, 1))
        combined = (conved + embedded) * self.scale
        
        return conved, combined
2.2 解码器

解码器的设计与编码器类似,但解码器会生成目标序列。通过卷积层,解码器并行处理序列中的每个词,并结合编码器的输出生成翻译结果。

python 复制代码
class Decoder(nn.Module):
    def __init__(self, output_dim, emb_dim, hid_dim, n_layers, kernel_size, dropout, trg_pad_idx, device, max_length=100):
        super().__init__()
        
        self.kernel_size = kernel_size
        self.trg_pad_idx = trg_pad_idx
        self.device = device
        
        self.scale = torch.sqrt(torch.FloatTensor([0.5])).to(device)
        
        self.tok_embedding = nn.Embedding(output_dim, emb_dim)
        self.pos_embedding = nn.Embedding(max_length, emb_dim)
        
        self.emb2hid = nn.Linear(emb_dim, hid_dim)
        self.hid2emb = nn.Linear(hid_dim, emb_dim)
        
        self.fc_out = nn.Linear(emb_dim, output_dim)
        
        self.convs = nn.ModuleList([nn.Conv1d(in_channels=hid_dim, out_channels=2*hid_dim, kernel_size=kernel_size) for _ in range(n_layers)])
        
        self.dropout = nn.Dropout(dropout)
      
    def calculate_attention(self, embedded, conved, encoder_conved, encoder_combined):
        conved_emb = self.attn_hid2emb(conved.permute(0, 2, 1))
        combined = (conved_emb + embedded) * self.scale
        energy = torch.matmul(combined, encoder_conved.permute(0, 2, 1))
        attention = F.softmax(energy, dim=2)
        attended_encoding = torch.matmul(attention, encoder_combined)
        attended_encoding = self.attn_emb2hid(attended_encoding)
        attended_combined = (conved + attended_encoding.permute(0, 2, 1)) * self.scale
        return attention, attended_combined
        
    def forward(self, trg, encoder_conved, encoder_combined):
        batch_size = trg.shape[0]
        trg_len = trg.shape[1]
        
        pos = torch.arange(0, trg_len).unsqueeze(0).repeat(batch_size, 1).to(self.device)
        tok_embedded = self.tok_embedding(trg)
        pos_embedded = self.pos_embedding(pos)
        embedded = self.dropout(tok_embedded + pos_embedded)
        
        conv_input = self.emb2hid(embedded).permute(0, 2, 1)
        for conv in self.convs:
            padding = torch.zeros(batch_size, conv_input.shape[1], self.kernel_size - 1).fill_(self.trg_pad_idx).to(self.device)
            padded_conv_input = torch.cat((padding, conv_input), dim=2)
            conved = F.glu(conv(padded_conv_input), dim=1)
            attention, conved = self.calculate_attention(embedded, conved, encoder_conved, encoder_combined)
            conved = (conved + conv_input) * self.scale
            conv_input = conved
            
        conved = self.hid2emb(conved.permute(0, 2, 1))
        output = self.fc_out(self.dropout(conved))
        return output, attention

3. 模型训练

python 复制代码
# 定义训练和评估函数
def train(model, loader, optimizer, criterion, clip, loader_length):
    model.train()
    epoch_loss = 0
    for src, trg in loader:
        src, trg = src.to(device), trg.to(device)
        optimizer.zero_grad()
        output, _ = model(src, trg[:, :-1])
        output = output.reshape(-1, output.shape[-1])
        trg = trg[:, 1:].reshape(-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 / loader_length

def evaluate(model, loader, criterion, loader_length):
    model.eval()
    epoch_loss = 0
    with torch.no_grad():
        for src, trg in loader:
            src, trg = src.to(device), trg.to(device)
            output, _ = model(src, trg[:, :-1])
            output = output.reshape(-1, output.shape[-1])
            trg = trg[:, 1:].reshape(-1)
            loss = criterion(output, trg)
            epoch_loss += loss.item()
    return epoch_loss / loader_length

4. 实验与评估

我们使用相同的数据集来训练和评估模型,并在模型性能上进行分析。由于 CNN 并行计算的优势,模型在处理速度上表现出显著提升。

python 复制代码
# 训练模型
best_valid_loss = float('inf')
num_epochs = 10
clip = 0.1
for epoch in range(num_epochs):
    train_loss = train(model, train_loader, optimizer, criterion, clip, train_loader_length)
    valid_loss = evaluate(model, valid_loader, criterion, val_loader_length)
    if valid_loss < best_valid_loss:
        best_valid_loss = valid_loss
        torch.save(model.state_dict(), 'convseq2seq-model.pt')

结语

在这篇文章中,我们成功实现了基于 CNN 的序列到序列学习模型,并结合 Teacher Forcing 技术加速了模型的训练。与传统的 RNNLSTM 模型相比,卷积神经网络具备强大的并行计算能力,使其在处理长序列时更加高效。虽然 CNN 模型没有显式的递归结构,但通过 卷积层位置嵌入,它能够有效捕捉输入序列中的时序信息。

此外,尽管 CNN 模型不能像 RNN 那样灵活地使用 Teacher Forcing,但其速度优势使得它在某些任务中表现出色。在接下来的文章中,我们将探索更加复杂的 Transformer 模型,该模型也具备并行处理能力,并且在多个自然语言处理任务中已证明了其强大的性能。

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

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

谢谢大家的支持!

相关推荐
契合qht53_shine14 分钟前
深度学习 自然语言处理(RNN) day_02
rnn·深度学习·自然语言处理
白熊18819 分钟前
【计算机视觉】OpenCV实战项目:基于Tesseract与OpenCV的字符识别系统深度解析
人工智能·opencv·计算机视觉
kovlistudio1 小时前
机器学习第三讲:监督学习 → 带答案的学习册,如预测房价时需要历史价格数据
人工智能·机器学习
嵌入式仿真实验教学平台1 小时前
「国产嵌入式仿真平台:高精度虚实融合如何终结Proteus时代?」——从教学实验到低空经济,揭秘新一代AI赋能的产业级教学工具
人工智能·学习·proteus·无人机·低空经济·嵌入式仿真·实验教学
正在走向自律1 小时前
Python 数据分析与可视化:开启数据洞察之旅(5/10)
开发语言·人工智能·python·数据挖掘·数据分析
LuvMyLife1 小时前
基于Win在VSCode部署运行OpenVINO模型
人工智能·深度学习·计算机视觉·openvino
fancy1661662 小时前
力扣top100 矩阵置零
人工智能·算法·矩阵
gaosushexiangji2 小时前
基于千眼狼高速摄像机与三色掩模的体三维粒子图像测速PIV技术
人工智能·数码相机·计算机视觉
中电金信2 小时前
重构金融数智化产业版图:中电金信“链主”之道
大数据·人工智能
奋斗者1号3 小时前
Docker 部署 - Crawl4AI 文档 (v0.5.x)
人工智能·爬虫·机器学习