当前机器翻译任务的主流解决方案是基于神经网络进行建模,通常我们基于神经网络解决机器翻译任务的流程如下:
1.配置环境
torchtext :是一个用于自然语言处理(NLP)任务的库,它提供了丰富的功能,包括数据预处理、词汇构建、序列化和批处理等,特别适合于文本分类、情感分析、机器翻译等任务
jieba
:是一个中文分词库,用于将中文文本切分成有意义的词语sacrebleu:用于评估机器翻译质量的工具,主要通过计算BLEU(Bilingual Evaluation Understudy)得分来衡量生成文本与参考译文之间的相似度
spacy:是一个强大的自然语言处理库,支持70+语言的分词与训练
!pip install torchtext !pip install jieba !pip install sacrebleu
安装spacy用于英文的tokenizer:
!pip install -U pip setuptools wheel -i https://pypi.tuna.tsinghua.edu.cn/simple !pip install -U 'spacy[cuda12x]' -i https://pypi.tuna.tsinghua.edu.cn/simple !pip install ../dataset/en_core_web_trf-3.7.3-py3-none-any.whl !python -m spacy download en_core_web_sm
2.数据预处理
对英文和中文数据进行预处理,并将处理后的数据以元组列表的形式返回。每个元组包含两个列表:一个是英文文本分词en_data: List[str]后的结果(限制在最大长度
MAX_LENGTH
内),另一个是中文文本分词zh_data: List[str]后的结果(同样限制在最大长度内)。
定义一个
collate_fn
函数用于在数据加载过程中处理一个批次的数据,特别是将英文和中文的文本序列转换为张量,并对其进行填充以确保它们具有相同的长度。这个函数通常与 PyTorch 的DataLoader
一起使用,以自定义批次数据的处理方式。
使用了
en_vocab['<pad>']
和zh_vocab['<pad>']
作为填充值。处理空序列:处理批次中单个序列为空的情况,通过
if not en_batch or not zh_batch:
检查了这一点,并返回了空的张量。if not en_batch or not zh_batch: # 如果整个批次为空,返回空张量 return torch.tensor([]), torch.tensor([])
注释和调试:打印语句来调试(例如
"存在为空"
)。if en_item and zh_item: # 确保两个序列都不为空 # print("都不为空") en_batch.append(torch.tensor(en_item)) zh_batch.append(torch.tensor(zh_item)) else: print("存在为空")
构建一个load_data函数,用于加载并预处理训练集、开发集和测试集,并构建相应的数据加载器。
# preprocess_dataset: 处理单个数据集的读取、分词/标记化、截断等 # build_vocab: 根据处理后的数据集构建词汇表 # TranslationDataset: 封装处理后的数据和词汇表的类 # DataLoader: PyTorch中的数据加载器类 # collate_fn: 一个可选的函数,用于在DataLoader中合并样本列表 # read_data: 读取文件内容的函数(在preprocess_dataset中隐含地使用) # en_tokenizer 和 zh_tokenizer 是分词器或标记化器的实例,用于处理英文和中文文本
3.模型构建
定义一个基于GRU(门控循环单元)的编码器类
Encoder
,它是PyTorch中nn.Module
的一个子类。这个编码器通常用于序列到序列(Seq2Seq)模型,如机器翻译、文本摘要等任务中,用于编码输入序列。初始化函数
__init__:
input_dim
: 输入的词汇表大小,即嵌入层需要处理的不同的单词数量。emb_dim
: 嵌入层的维度,即每个单词被嵌入后的向量大小。hid_dim
: GRU隐藏层的维度。n_layers
: GRU的层数,即堆叠的GRU单元的数量。dropout
: 在GRU层之间应用的dropout比率,用于防止过拟合。在初始化函数中,首先通过
super().__init__()
调用父类nn.Module
的初始化方法。然后,定义了几个关键的层:
self.embedding
: 嵌入层,将输入的单词索引(整数)转换为固定大小的密集向量。self.gru
: GRU层,用于处理序列数据。这里的batch_first=True
表示输入张量的第一个维度是批次大小,这符合PyTorch的常规约定。self.dropout
: Dropout层,用于在训练过程中随机丢弃一部分神经元的输出,以减少过拟合。前向传播函数
forward
在
forward
方法中,定义了数据通过编码器时的处理流程:
首先,将输入
src
(一个包含单词索引的二维张量,形状为[batch size, src len]
)通过嵌入层转换为嵌入向量embedded
。然后,应用dropout以减少过拟合。接着,将嵌入向量
embedded
传递给GRU层。GRU层会按照时间步(即序列中的每个单词)处理这些嵌入向量,并输出每个时间步的隐藏状态outputs
以及最后一个时间步的隐藏状态(对于所有层)hidden
。outputs
的形状为[batch size, src len, hid dim * n directions]
,但由于我们使用的是单向GRU,所以n directions
为1,这部分通常可以省略。hidden
的形状为[n layers * n directions, batch size, hid dim]
,同样地,由于单向性,n directions
为1。最后,
forward
方法返回outputs
和hidden
。outputs
可以用于某些任务(如注意力机制),而hidden
通常用作解码器的初始隐藏状态。在
Decoder
的forward
方法中,我们首先处理输入(通常是前一个时间步的预测或真实的输出单词的索引),然后将其嵌入到高维空间中。接着,我们使用注意力机制来计算编码器输出的加权和,这个加权和将作为GRU单元的额外输入。最后,GRU的输出被用来预测下一个单词。def forward(self, src): embedded = self.dropout(self.embedding(src)) outputs, hidden = self.gru(embedded)
def forward(self, hidden, encoder_outputs): batch_size = encoder_outputs.shape[0] src_len = encoder_outputs.shape[1] hidden = hidden.repeat(src_len, 1, 1).transpose(0, 1) energy = torch.tanh(self.attn(torch.cat((hidden, encoder_outputs), dim=2))) attention = self.v(energy).squeeze(2) return F.softmax(attention, dim=1)
4.模型训练
定义一个训练模型的函数
train_model
,该函数接收多个参数,包括模型 (model
)、训练数据迭代器 (train_iterator
)、验证数据迭代器 (valid_iterator
)、优化器 (optimizer
)、损失函数 (criterion
)、训练轮次 (N_EPOCHS
)、以及梯度裁剪的阈值 (CLIP
)。主要目的是在指定的训练轮次内,使用训练数据训练模型,并在每个轮次结束时使用验证数据评估模型的表现。如果模型在验证集上的表现有所改善(即验证损失降低),则保存当前的模型状态。def train_model(model, train_iterator, valid_iterator, optimizer, criterion, N_EPOCHS=10, CLIP=1): best_valid_loss = float('inf')
定义几个关键的常量,包括最大句子长度
MAX_LENGTH
、批量大小BATCH_SIZE
、设备DEVICE
(用于指定是使用 CPU 还是 GPU),以及采样训练集的数量N
(尽管在后续代码中并未直接使用N
)。指定数据集文件的路径,并调用一个假设存在的load_data
函数来加载数据,该函数返回训练集、开发集、测试集的加载器(DataLoader
),以及英文和中文的词汇表。MAX_LENGTH = 100 # 最大句子长度 BATCH_SIZE = 32 DEVICE = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') N = 100 # 采样训练集的数量 train_path = '../dataset/train.txt' dev_en_path = '../dataset/dev_en.txt' dev_zh_path = '../dataset/dev_zh.txt' test_en_path = '../dataset/test_en.txt' train_loader, dev_loader, test_loader, en_vocab, zh_vocab = load_data( train_path, dev_en_path, dev_zh_path, test_en_path )
训练模型:
train_model(model, train_loader, dev_loader, optimizer, criterion, N_EPOCHS, CLIP)
5.翻译质量评价
6.个人心得
在深入探索深度学习的广阔领域时,从baseline代码详解入手无疑是一条高效且扎实的路径。这段经历不仅让我对深度学习的基本原理有了更深刻的理解,还让我在实际操作中逐步掌握了构建、训练和评估模型的关键技能。
一、理论与实践的结合
首先,通过阅读代码,我深刻体会到理论知识与实践操作之间的紧密联系。理论是构建模型的基石,而代码则是将这些理论转化为实际应用的桥梁。通过逐行阅读代码,我能够直观地看到理论概念如何在代码中实现,比如前向传播
forward
等。这种理论与实践相结合的方式,极大地加深了我对深度学习核心概念的理解。二、细节决定成败
在深入运行过程中,我逐渐意识到细节的重要性。从数据预处理、模型架构的设计、超参数的调整到训练过程中的优化策略,每一个细节都可能对模型的性能产生重大影响。通过仔细研究学习代码中的每一个步骤,我初步学会了如何优化这些细节,以提高模型的准确性和效率。
三、调试与反思
在尝试复现过程中,我不可避免地遇到了各种bug和性能瓶颈。通过不断地调试和反思,我学会了如何有效地定位问题、分析原因并寻找解决方案。这个过程虽然充满挑战,但也极大地锻炼了我的问题解决能力和代码调试能力。
四、持续学习与探索
深度学习是一个快速发展的领域,新的算法、模型和技术层出不穷。通过本次夏令营的学习,我意识到持续学习和探索的重要性。只有不断学习最新的研究成果和技术趋势,才能保持自己在这个领域的竞争力。
这个夏令营不简单
#AI夏令营
#Datawhale #夏令营