实验目的:
基于mindspore搭建seq2seq模型实现文本翻译
实验内容:
- 数据准备:
-
使用的数据集为Multi30K数据集,大规模的图像-文本数据集,包含30K+图片,每张图片对应两类不同的文本描述:
- 英语描述,及对应的德语翻译;
- 五个独立的、非翻译而来的英语和德语描述,描述中包含的细节并不相同;
-
数据下载模块:
-
使用使用download进行数据下载,并将tar.gz文件解压到指定文件夹。
下载好的数据集目录结构如下:
-
-
数据预处理
-
加载数据集,目前数据为句子形式的文本,需要进行分词,即将句子拆解为单独的词元(token,可以为字符或者单词);
定义一个名为Multi30K的类,用于加载和处理Multi30K数据集。该类通过读取下载的文件,对德语(de)和英语(en)文本进行分词处理,并以元组形式组织成(德语句子, 英语句子)对,便于后续处理。
创建Multi30K实例分别对应训练、验证和测试数据集。
打印出一个测试数据集中的德英句子对作为预览。 -
将每个词元映射到从0开始的数字索引中(为节约存储空间,可过滤掉词频低的词元),词元和数字索引所构成的集合叫做词典(vocabulary);
-
添加特殊占位符,标明序列的起始与结束,统一序列长度,并创建数据迭代器;
定义了一个Vocab类,用于根据词频构建词汇表,包括特殊标记如(未知词)、(填充)、(句首)和(句尾)
-
使用collections中的Counter和OrderedDict统计英/德语每个单词在整体文本中出现的频率。构建词频字典,然后再将词频字典转为词典。
通过build_vocab函数统计训练数据集中德语和英语的词频,并利用这些统计信息创建德语和英语的词汇表实例,同时设置最小词频阈值为2。
-
数据迭代器
进一步处理数据(包括批处理,添加起始和终止符号,统一序列长度)后,将数据以张量的形式返回。创建数据迭代器需要如下参数:
- dataset:分词后的数据集
- de_vocab:德语词典
- en_vocab:英语词典
- batch_size:批量大小,即一个batch中包含多少个序列
- max_len:序列最大长度,为最长有效文本长度 + 2(序列开始、序列结束占位符),如不满则补齐,如超过则丢弃
- drop_remainder:是否在最后一个batch未满时,丢弃该batch
定义了一个Iterator类,用于生成训练、验证和测试数据的批次。它实现了对数据集的遍历、序列长度排序、填充至固定长度、编码为数字索引以及转换为MindSpore的Tensor格式等功能,以适配神经网络模型的输入要求。
配置三个迭代器实例,分别对应训练、验证和测试数据集,设置了批大小为128,最大序列长度为32,且在训练迭代器中启用了丢弃末尾不足一个批次的数据的功能。
-
- 模型构建:
- 定义基础组件:
- Encoder:负责编码输入序列。它首先利用嵌入层(Embedding Layer)将输入的单词索引转换为向量表示,接着通过双向GRU(Gated Recurrent Unit)捕获序列的前后上下文信息,并通过全连接层(Dense Layer)整合双向GRU的输出,为解码器提供初始隐藏状态。
- Attention:注意力机制使解码器能够根据当前解码状态有选择地关注输入序列的不同部分。它计算解码器隐藏状态与编码器输出之间的匹配程度,生成注意力权重,并据此加权求和编码器的输出,形成上下文向量。
- Decoder:解码器在编码器提供的上下文信息的基础上,逐步生成目标序列。它同样使用GRU更新隐藏状态,并结合注意力机制的结果及当前输入(或上一时刻的预测),最终通过全连接层预测下一个单词。
- 实现Seq2Seq模型
- Seq2Seq类:整合了编码器和解码器,实现了整体的序列到序列翻译流程。它还包含了一个create_mask方法来标识源序列中的填充(pad)位置,以在计算注意力权重时排除这些位置的影响。
- 在construct方法中,模型首先运行编码器处理输入序列,然后以开始标记()启动解码器。解码过程通过循环进行,每一步根据前一时间步的输出(或目标序列的真实词元,取决于是否使用Teacher Forcing策略)以及编码器输出和当前隐藏状态,通过注意力机制更新隐藏状态并预测下一个单词,直到达到目标序列长度。
- 模型训练:
- 初始化参数
- 定义模型所需的各种超参数,包括词汇表大小(输入输出维度)、嵌入层维度、隐藏层维度、Dropout比例以及填充(pad)索引等。
- 设置计算与返回数据类型,以及是否在昇腾(Ascend)硬件上运行的标志。
- 实例化模型组件:Attention层、Encoder、Decoder以及Seq2Seq模型本身,还包括优化器(Adam)和损失函数(CrossEntropyLoss),其中损失函数会忽略填充项的损失计算。
- 自定义函数
- clip_by_norm函数实现了梯度裁剪,确保训练过程中的稳定性和效率
- forward_fn定义了前向传播过程,计算模型输出及损失。
- train_step完成单步训练,包括前向传播、梯度计算、梯度裁剪和参数更新。
- train和evaluate函数分别用于模型的训练和验证,通过迭代数据集进行模型的训练或评估,并监控损失。
- 训练与评估循环
- 模型进行指定轮数的训练(num_epochs),每轮结束后评估验证集上的性能。
- 使用tqdm库显示训练进度条,增强用户体验。
- 每轮训练后,根据验证集上的损失决定是否保存当前最佳模型。
- 模型推理:
- 翻译句子功能
- 实现了translate_sentence函数,将输入的德语文本转换成英语
- 文本预处理、编码、通过模型预测解码,最后将预测的索引转回英语单词。
- 模型加载与测试
- 加载训练好的模型参数,对测试数据集中的示例进行翻译,并展示源句、目标句及模型预测的结果。
- src = [‘ein’, ‘mann’, ‘mit’, ‘einem’, ‘orangefarbenen’, ‘hut’, ‘,’, ‘der’, ‘etwas’, ‘anstarrt’, ‘.’]
trg = [‘a’, ‘man’, ‘in’, ‘an’, ‘orange’, ‘hat’, ‘starring’, ‘at’, ‘something’, ‘.’] - predicted trg = [‘a’, ‘man’, ‘in’, ‘an’, ‘orange’, ‘hat’, ‘is’, ‘something’, ‘something’, ‘.’]
- src = [‘ein’, ‘mann’, ‘mit’, ‘einem’, ‘orangefarbenen’, ‘hut’, ‘,’, ‘der’, ‘etwas’, ‘anstarrt’, ‘.’]
- 应用nltk库中的BLEU评分来量化模型的翻译质量,整个测试数据集上的表现通过calculate_bleu函数计算得出。
- 加载训练好的模型参数,对测试数据集中的示例进行翻译,并展示源句、目标句及模型预测的结果。
实验原理:
-
编码器(Encoder):
在编码器中,我们输入一个序列 X = { x 1 , x 2 , . . . , x T } X=\{x_1, x_2, ..., x_T\} X={ x