【PyTorch】6 法语英语翻译RNN实战 |
您所在的位置:网站首页 › 法语语音翻译成中文 › 【PyTorch】6 法语英语翻译RNN实战 |
基于注意力机制的 seq2seq 神经网络翻译
1. 本文工作2. 加载数据文件3. Seq2Seq 模型4. GRU5. 准备训练数据6. 编码器7. 解码器7.1 简单解码器7.2 Attention解码器
8. 训练模型9. 评价10. 可视化注意力11. 全部代码小结
这是官方NLP From Scratch的一个教程(3/3),原中文链接,在这个项目中,我们将搭建神经网络,将法语翻译成英语,本文是其详细的注解 1. 本文工作编码器网络将输入序列压缩为一个向量,而解码器网络将该向量展开为一个新序列 需要加载名为’data/eng-fra.txt’的文件 编码方式:将一种语言中的每个单词表示为一个单向矢量: 这其实也是one-hot编码,但与之前所有字母中可能存在的几十个字符相比,单词有很多,因此编码向量要大得多。 这里作弊并整理数据以使每种语言仅使用几千个单词 首先创建函数: def readLangs(lang1, lang2, reverse=False)读文件进来,发现有一些:\u202f这种符号,所以再创建函数: def normalizeString(s)进行标准化,看一下re库的这两个函数用法: s = 'I love you!?,' s = re.sub(r"([.!?])", r" \1", s) print(s) s = re.sub(r"[^a-zA-Z.!?]+", r"6", s) print(s) I love you ! ?, I6love6you!?6生成的pairs就是: [...['plastic surgery alone will not make you any less ugly .', 'la chirurgie plastique seule ne vous rendra pas moins laid .']...]如果需要其他语言 → 英语,可以用reversed()函数,返回的是一个反转的迭代器,需要使用list()函数,原理如下: x = ['plastic surgery alone will not make you any less ugly .', 'la chirurgie plastique seule ne vous rendra pas moins laid .'] pairs = list(reversed(x)) print(pairs) ['la chirurgie plastique seule ne vous rendra pas moins laid .', 'plastic surgery alone will not make you any less ugly .']没有list就是: 希望为每个单词创建一个唯一的索引,定义一个类class Lang,该类具有单词→索引(word2index)和索引→单词(index2word)字典,以及每个要使用的单词word2count的计数,以便以后替换稀有词 在这里,最大长度为 10 个字(包括结尾的标点符号),并且过滤翻译成“i’m”或“he’s”等形式的句子(考虑到前面已替换掉撇号的情况) 关于startswith(str, beg=0,end=len(string))函数,用于检查字符串是否是以指定子字符串开头,如果是则返回 True,否则返回 False。如果参数 beg 和 end 指定值,则在指定范围内检查 这里直接把原文prepareData函数写道主函数里面,原文默认英语→其他语言,但是它两次的结果演示都是其他语言 → 英语,所以默认reverse=True才对: print("Counted words:") print(input_lang.name, input_lang.n_words) print(output_lang.name, output_lang.n_words) print(random.choice(pairs)) Counted words: fra 4345 eng 2803 ['il fait une promenade .', 'he is taking a walk .'] 3. Seq2Seq 模型序列到序列网络或 seq2seq 网络或编码器解码器网络是由两个称为编码器和解码器的 RNN 组成的模型,编码器读取输入序列并输出单个向量,而解码器读取该向量以产生输出序列 与使用单个 RNN 进行序列预测(每个输入对应一个输出)不同,seq2seq 模型使我们摆脱了序列长度和顺序的限制,这使其非常适合在两种语言之间进行翻译 例如:Je ne suis pas le chat noir(我不是黑猫)单个词的百度翻译: Je 我ne NE公司suis 跟踪pas 没有le 在chat 猫noir 黑色输入句子中的大多数单词在输出句子中具有直接翻译,但是顺序略有不同,例如 “chat noir”和“black cat”,由于采用“ ne / pas”结构,因此在输入句子中还有一个单词。 直接从输入单词的序列中产生正确的翻译将是困难的 使用 seq2seq 模型,编码器创建单个矢量,在理想情况下,该矢量将输入序列的“含义”编码为单个矢量-句子的某些 N 维空间中的单个点 4. GRU先来看一下门控循环单元(gated recurrent unit, GRU) 参考此视频 LSTM门控网络结构过于复杂与冗余 GRU将遗忘门和输入门合并成更新门,同时将记忆单元与隐藏层合并成了重置门,进而让整个结构运算变得更加简化且性能得以增强。
当重置门接近于0时,隐藏状态被迫忽略先前的隐藏状态,仅用当前输入进行复位 这有效地使隐藏状态可以丢弃将来以后发现不相关的任何信息,从而允许更紧凑的表示 另一方面,更新门控制从前一个隐藏状态将有多少信息转移到当前隐藏状态。这类似于LSTM网络中的记忆单元,并有助于RNN记住长期信息 运算过程如下: 对于每一对,我们将需要一个输入张量(输入句子中单词的索引)和目标张量(目标句子中单词的索引),创建这些向量时,将 EOS 令牌附加到两个序列上 对于pairs[0],即['j ai ans .', 'i m .'],由tensorFromPair(pair)函数返回的input_tensor为: tensor([[2], [3], [4], [5], [1]], device='cuda:0')其大小为torch.Size([5, 1]),target_tensor为: tensor([[2], [3], [4], [1]], device='cuda:0')其大小为torch.Size([4, 1]) 6. 编码器seq2seq 网络的编码器是 RNN,它为输入句子中的每个单词输出一些值。 对于每个输入字,编码器输出一个向量和一个隐藏状态,并将隐藏状态用于下一个输入字 解码器是另一个 RNN,它采用编码器输出矢量并输出单词序列来创建翻译 7.1 简单解码器在最简单的 seq2seq 解码器中,我们仅使用编码器的最后一个输出。 最后的输出有时称为上下文向量,因为它对整个序列的上下文进行编码。 该上下文向量用作解码器的初始隐藏状态,在解码的每个步骤中,为解码器提供输入令牌和隐藏状态。 初始输入令牌是字符串开始,第一个隐藏状态是上下文向量(编码器的最后一个隐藏状态) 如果仅上下文向量在编码器和解码器之间传递,则该单个向量承担对整个句子进行编码的负担,Attention使解码器网络可以针对解码器自身输出的每一步,“专注”于编码器输出的不同部分。 首先,我们计算一组Attention权重。 将这些与编码器输出向量相乘以创建加权组合。 结果(在代码中称为attn_applied)应包含有关输入序列特定部分的信息,从而帮助解码器选择正确的输出字 计算注意力权重的方法是使用另一个前馈层attn,并使用解码器的输入和隐藏状态作为输入。 由于训练数据中包含各种大小的句子,因此要实际创建和训练该层,我们必须选择可以应用的最大句子长度(输入长度,用于编码器输出)。最大长度的句子将使用所有注意权重,而较短的句子将仅使用前几个
为了进行训练,我们通过编码器运行输入语句,并跟踪每个输出和最新的隐藏状态。 然后,为解码器提供SOS_TOKEN令牌作为其第一个输入,并将编码器的最后一个隐藏状态作为其第一个隐藏状态 “教师强制”的概念是使用实际目标输出作为每个下一个输入,而不是使用解码器的猜测作为下一个输入。 使用教师强制会导致其收敛更快,但是当使用受过训练的网络时,可能会显示不稳定 您可以观察到以教师为主导的网络的输出,这些输出阅读的是连贯的语法,但却偏离了正确的翻译-直观地,它学会了代表输出语法,并且一旦老师说了最初的几个单词就可以“理解”含义,但是,它还没有正确地学习如何从翻译中创建句子 由于 PyTorch 的 autograd 具有给我们的自由,我们可以通过简单的 if 语句随意选择是否使用教师强迫。 调高teacher_forcing_ratio以使用更多功能 再看一下: x = torch.rand(2,3,4) print(x) topv, topi = x.topk(1) print(topv, topi) tensor([[[0.9181, 0.4981, 0.9914, 0.2432], [0.6078, 0.0700, 0.5814, 0.1134], [0.5261, 0.4351, 0.7423, 0.5492]], [[0.4094, 0.0585, 0.6755, 0.5136], [0.2214, 0.1035, 0.8093, 0.9017], [0.3362, 0.2959, 0.6774, 0.8712]]]) tensor([[[0.9914], [0.6078], [0.7423]], [[0.6755], [0.9017], [0.8712]]]) tensor([[[2], [0], [2]], [[2], [3], [3]]])关于 x = torch.rand(1, 2560) topv, topi = x.topk(1) print(topi) print(topi.squeeze()) # 将输入张量形状中的1 去除并返回,如果输入是形如(A×1×B×1×C×1×D),那么输出形状就为: (A×B×C×D) print(topi.squeeze().detach()) # 返回一个新的Tensor,从当前图中脱离出来,该tensor不会要求更新梯度,也就是梯度在这中断了 print(topi.squeeze().detach().item()) tensor([[1217]]) tensor(1217) tensor(1217) 1217关于绘图,原代码把纵轴的间隔设成了0.2: import matplotlib.pyplot as plt plt.switch_backend('agg') import matplotlib.ticker as ticker import numpy as np def showPlot(points): plt.figure() fig, ax = plt.subplots() # this locator puts ticks at regular intervals loc = ticker.MultipleLocator(base=0.2) ax.yaxis.set_major_locator(loc) plt.plot(points)训练所得曲线如图所示: |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |