机器翻译简而言之,是指使用计算机将文本从一种语言翻译成另一种语言。 它是计算机语言学的一个分支,已经发展了几年。 目前,在美国,翻译业是一个价值 400 亿美元的产业,并且在欧洲和亚洲也正在快速发展。 翻译存在巨大的社会,政府,经济和商业需求,并且 Google,Facebook,eBay 等公司在其应用中广泛使用它。 尤其是 Google 的神经翻译系统是目前最先进的翻译系统之一,能够仅用一种模型执行多种语言的翻译。
早期的机器翻译系统首先将文本中的单词和短语翻译成所需目标语言的相关替代词。 但是,由于以下原因,通过这些简单的技术实现的翻译质量受到限制:
- 从源语言到目标语言的词到词映射并非始终可用。
- 即使在源语言和目标语言之间确实存在精确的词对词映射,这些语言的句法结构通常也不相互对应。 机器翻译中的此问题通常称为对齐错误。
但是,随着循环神经网络(RNN)架构的最新进展,机器翻译不仅提供了更高的翻译质量,而且还提供了更高的翻译质量。这种系统的复杂性远远小于传统系统。
机器翻译系统大致可分为三类:基于规则的机器翻译,统计机器翻译和神经机器翻译。
在本章中,我们将介绍以下主题:
- 基于规则的机器翻译
- 统计机器学习系统
- 神经机器翻译
- 序列到序列的神经翻译
- 神经翻译的损失函数
您将需要具有 Python 3,TensorFlow 和 Keras 的基础知识。
基于经典规则的机器翻译系统严重依赖于将文本从源语言转换为目标语言的规则。 这些规则通常由语言学家创建,通常在句法,语义和词汇层面上起作用。 传统的基于规则的机器翻译系统通常分为三个阶段:
- 分析阶段
- 词汇翻译阶段
- 生成阶段
图 3.1 是典型的基于规则的机器翻译系统的流程图:
图 3.1:基于规则的机器翻译系统的流程图
基于规则的机器翻译的第一阶段是分析阶段,其中分析源语言文本以提取与形态,词性,命名实体识别以及词义歧义有关的信息。 形态信息涉及单词的结构,词干的派生方式,词根的检测等。 词性标记器使用可能的语音标记来标记文本中的每个单词,例如名词,动词,副词,形容词等。 接下来是命名实体识别(NER)任务,该任务尝试将命名实体分类到预定义的存储桶中,例如人员名称,位置,组织名称,以此类推。 NER 之后是单词义消歧,它试图识别句子中如何使用特定单词。
词汇翻译阶段位于分析阶段之后,并分为两个阶段:
- 单词翻译:在单词翻译中,使用双语翻译词典将在分析阶段导出的源根单词翻译为相应的目标根单词。
- 语法翻译:在语法翻译阶段,将进行语法修饰,包括翻译后缀等。
在生成阶段,将对翻译的文本进行验证和更正,以便在将最终翻译的文本作为附件提供之前,就动词而言,就动词,性别以及主语和宾语相对于动词的同意而言,它是正确的。 输出。 在每个步骤中,机器翻译系统都使用预定义的词典。 对于基于规则的机器翻译系统的最低限度的实现,需要以下词典:
- 用于源语言形态分析的词典
- 双语词典,包含源语言单词到目标语言对应单词的映射
- 包含用于目标单词生成的目标语言形态信息的字典
统计机器翻译系统通过在给定源文本的情况下最大化其条件概率来选择目标文本。 例如,假设我们有一个源文本s
,并且我们想要导出目标语言中的最佳等效文本t
。 可以如下得出:
(1)
中P(t / s)
的公式可使用贝叶斯定理扩展如下:
对于给定的源句子,P(s)
将是固定的,因此找到最佳目标翻译结果如下:
您可能想知道为什么直接将P(s / t)P(t)
最大化而不是P(t / s)
会带来优势。 通常,通过将问题分为两个部分来避免P(t / s)
下很可能出现的格式错误的句子,即P(s / t)
和P(t)
,如上式所示:
图 3.2:统计机器翻译架构
从上图可以看出,统计机器翻译问题已分解为三个不同的子问题,如上所述:
- 为目标建立语言模型,使我们能够估计
P(t)
- 从目标语言到源语言构建翻译模型,这使我们能够估计
P(s / t)
- 对可能的目标翻译进行搜索,然后选择最大化
P(s / t)P(t)
的翻译
我们将讨论这三个主题中的每一个,因为这些函数是任何机器翻译问题所固有的。
在语言模型中,句子的概率表示为各个单词或短语的条件概率的乘积。 假设句子t
由单词t[1], t[2], ..., t[n]
。 根据概率的链式规则,句子t
的概率可以表示为:
根据上述公式构建语言模型将需要我们估计几个顺序的条件概率,这在实际中是不可能的。 为了使问题在计算上可行,一个简单的假设是仅根据前一个单词而不是之前的所有单词来对单词进行条件处理。 该假设也称为马尔可夫假设,该模型称为二元模型。 根据二元模型,单词的条件概率可以表示为:
为了进一步改善结果,我们可以使用三元模型,该模型将句子中的特定单词置于其前面两个单词的条件下,如下所示:
对于二元模型,给定训练语料库中偶对(t[1], t[2])
的总数,计算当前单词t[1]
的下一个单词t[2]
的条件概率,并根据语料库中t[1]
出现的次数归一化:
对于三元模型,当前单词t[3]
在两个单词t[1], t[2]
之前的条件概率,可以估计如下:
超出三字母组合模型通常会导致稀疏性。 即使对于双字母组模型,我们也可能会缺少几个双字母组的条件概率,因为它们没有出现在训练语料库中。 但是,那些缺失的二元组可能非常相关,并且估计其条件概率非常重要。 不用说,n
元模型倾向于估计出现在训练数据中的单词对的高条件概率,而忽略没有出现的单词。
困惑度指标用于评估语言模型的有用性。 假设我们已经在训练语料库上训练了一个语言模型,并且使句子或文本上的学习概率模型为P(.)
。 P(.)
的困惑度是根据与训练语料库相同的总体抽取的测试集语料库进行评估的。 如果我们用M
词表示测试集语料库,请说(w[1], w[2], ..., w[M])
,则模型在测试集序列上的困惑性表示如下:
如图所示H
的表达式可衡量每字的不确定性:
根据语言模型,我们可以按如下方式分解测试语料库的概率表达式:
如果我们将测试集中第i
个词的概率表示为条件,则以先前的字为条件P(s[i])
,则测试语料库的概率如下:
这里P(s[i]) = P(w[i] / w[1], w[2], ..., w[i-1])
。 将(1)
和(4)
结合起来,困惑可以写成如下:
假设我们有一个语言模型P(.)
和一个进行评估的测试集I love Machine Learning
。 根据语言模型,测试集的概率如下:
如果语言模型的训练语料也是I love Machine Learning
,则测试集的概率为 1,导致对数概率为0
,且困惑为1
。 这意味着该模型可以完全确定地生成下一个单词。
另一方面,如果我们有一个更现实的训练语料库,其词汇量为N = 20,000
,并且训练数据集在测试数据集上的困惑度为 100,那么平均而言, 为了预测序列中的下一个单词,我们将搜索范围从 20,000 个单词缩小到 100 个单词。
让我们看一下最坏的情况,在这种情况下,我们设法建立一个模型,其中每个单词都与序列中的先前单词无关:
对于M
个单词的测试集,使用(5)
的困惑如下:
如果我们像以前一样有N = 20,000
,那么要预测序列中的任何单词,就需要考虑词汇表中的所有N
个单词,因为它们都是一样的。 在这种情况下,我们无法减少单词的平均搜索空间来预测序列中的单词。
翻译模型可以视为机器翻译模型的核心。 在翻译模型中,我们需要估计概率P(s / t)
,其中s
是源语言句子,t
是目标语言的句子。 在这里,给出了源句,而目标是我们试图找出的句子。 因此,该概率可以称为源句子给定目标句子的可能性。 例如,假设我们正在将源文本从法语翻译为英语。 因此,在P(s / t)
的情况下,我们的目标语言是法语,我们的源语言是英语,而在实际翻译的情况下,即P(s / t)P(t)
,我们的源语言是法语,而我们的目标语言是英语。
该翻译主要包括三个部分:
- 丰富度:并非源语言中的所有单词在目标语言中都有对应的单词。 例如,英语句子
Santanu loves math
法语翻译为Santanu aim les maths
。 如我们所见,英语中的math
一词已翻译成法文的两个单词,即les maths
。 形式上,丰富度定义为目标语言中源语言单词生成的单词数量上的概率分布,并且可以表示为P(n / w[s])
,其中w[s]
代表源词。 而不是使用硬编码的数字n
,而是使用概率分布,因为相同的单词可能会根据上下文生成不同长度的翻译。 - 失真:对于任何机器翻译系统,源句子和目标句子之间的单词到单词的对应关系都很重要。 但是,源语言句子中单词的位置可能并不总是与目标语言句子中对应单词的位置完全同步。 失真通过概率函数
P(p[t], p[s], l)
覆盖了对齐的概念,其中p[t]
和p[t]
分别代表目标词和源词的位置,而l
代表目标句子的长度。 如果源语言是英语,目标语言是法语,则P(p[t] / p[s], l)
表示位置p[s]
的英语单词对应于位置p[s]
中的法语单词,其长度为l
。 - 单词到单词的翻译:最后,我们来进行单词到单词的翻译,这通常由给定源语言单词的目标语言单词的概率分布表示。 对于给定的源语言单词
w[s]
,概率可以表示为P(w[t] / w[s])
,其中w[t]
代表目标语言单词。
对于语言模型,需要在训练过程中估计生育率,失真率和单词到单词的翻译率。
现在,让我们回到估计概率P(s / t)
的原始问题。 如果我们用E
表示英语句子,而用F
表示法语句子,则需要计算P(F / E)
的概率。 为了考虑单词的对齐方式,我们将概率修改为P(F, a / E)
,其中a
表示目标句子在法语中的对齐方式。 这种一致性将有助于我们注入有关畸变和生育能力的信息。
让我们通过一个示例来计算概率P(F, a / E)
。 让一个特定的英语句子由五个单词的句子表示。 e = (e[1], e[2], ..., e[5])
,实际上这是实际法语句子的正确翻译。 f = (f[1], f[2], ..., f[6])
。 另外,让单词的相应对齐方式如下:
e1 -> f6
e2 ->
不对应法语中的任何单词e3 -> f3, f4
e4 -> f1
e5 -> f2
f5 ->
不对应英语中的任何单词
由于这是一个概率模型,因此该算法将尝试使用具有不同对齐方式的不同英语句子,在给定法语句子的情况下,其中具有正确对齐方式的正确英语句子应该具有最高的概率。
让我们将第一个英语单词考虑为e[1]
-它与法语单词f[6]
对齐,并且还会产生一个法语单词,如下:
现在,让我们将对齐方式作为两个成分的组合:失真a[d]
和丰富度f[d]
。 (1)
中的表达式可以重写如下:
如果我们仔细观察, P(f[5] / e[1])
就是翻译概率, P(a[f] / e[1])
是丰富度,而 P(a[d] / e[1], f[5])
是失真概率。 我们需要针对英语句子中与给定法语句子的所有比对中的所有给定英语单词进行此活动,以计算P(F, a / E)
。 最后,我们需要采用最佳英语句子E_hat
和对齐方式a_hat
,以使P(F, a / E)P(E)
的概率最大化。 如下所示:
这里要注意的一件事是,尝试不同的对齐方式和不同的可能单词翻译以寻求最佳翻译可能在计算上变得棘手,因此,需要部署巧妙的算法以在最短的时间内找到最佳翻译。
神经机器翻译(NMT)使用深度神经网络来执行从源语言到目标语言的机器翻译。 神经翻译机将源语言中的文本作为输入序列,并将其编码为隐藏的表示形式,然后将其解码回以生成目标语言中的翻译文本序列。 该 NMT 系统的主要优势之一是,整个机器翻译系统可以从端到端一起进行训练,这与基于规则的机器翻译系统和统计机器翻译系统不同。 一般而言,在[ 神经翻译机架构。
NMT 与其他传统方法相比的一些优点如下:
- 基于损失函数对 NMT 模型的所有参数进行端到端训练,从而降低了模型的复杂性
- 这些 NMT 模型使用的上下文比传统方法大得多,因此产生了更准确的翻译
- NMT 模型可以更好地利用单词和短语的相似性
- RNN 允许生成更好质量的文本,因此,相对于已翻译文本的语法而言,翻译更加准确
下图说明了一种神经翻译机的架构,该结构使用一个 LSTM 作为编码器,将输入源语言序列编码为最终隐藏状态h[f]
和最终存储单元状态c[f]
。 最终的隐藏状态和单元状态[h[f]; c[f]]
将捕获整个输入序列的上下文。 因此,[h[f]; c[f]]
成为解码器网络可适应的良好候选者。
此隐藏状态和单元状态信息[h[f]; c[f]]
作为初始隐藏状态和单元状态被馈送到解码器网络,然后解码器在目标序列上训练,其中输入目标序列相对于输出目标序列滞后一。 根据解码器,输入序列的第一个字是伪字[START]
,而输出标签是字c'est
。 解码器网络仅被训练为一种生成语言模型,在任何时候,输出标签t
都是相对于输入的下一个单词,即y[t] = x[t + 1]
。 唯一的新变化是编码器的最终隐藏状态和单元状态(即[h[f]; c[f]]
) 解码器的隐藏状态和单元状态为翻译提供内容。
这意味着可以将训练过程视为为目标语言(由解码器表示)建立语言模型,该模型以代表源语言的编码器的隐藏状态为条件:
图 3.3:神经机器翻译系统的高级编码器-解码器架构
如果T
是与源语言文本S
相对应的目标语言文本,那么为了进行训练,我们只是试图使P[w](T[s+1] / S, T)
的对数概率相对于W
最大化,其中T[s+1]
表示平移一个时间步骤的目标语言文本,W
表示编码器-解码器架构模型参数。
现在我们已经讨论了编码器-解码器 NMT 的训练过程,现在我们将研究如何在推理过程中使用训练后的模型。
在 NMT(神经翻译机)上进行推理的架构流程与训练 NMT 略有不同。 以下是使用 NMT 执行推理的架构流程:
图 3.4:基于编码器/解码器的神经机器翻译的推理
在推理过程中,源语言输入序列被馈送到编码器网络并产生最终的隐藏状态和单元状态, [h[f]; c[f]]
馈给解码器的隐藏状态和单元状态。 解码器被转换为单个时间步,馈送到解码器的第一个输入是伪[START]
字。 因此,基于[h[f]; c[f]]
和初始虚拟字[START]
,解码器将输出一个字w
,以及新的隐藏状态和单元格状态[h[d]; c[d]]
。 这个单词w
再次以新的隐藏状态和单元状态馈送到解码器。 [h[d]; c[d]]
生成下一个单词。 重复此过程,直到遇到序列结束字符。
我们将建立一个神经机器翻译系统,该系统将学习将英语短句子翻译成法语。 为此,我们将使用位于这个页面上的英语到法语文本语料库(fra-eng/fra.txt
)。
文本数据不能直接输入任何神经网络,因为神经网络只能理解数字。 我们将每个单词视为一个单编码的向量,其长度等于每个语料库中出现的单词的数量。 如果英语语料库包含 1,000 个单词,则一键编码的向量v[e]
的大小为 1,000,即v[e] ∈ R^(1000 x 1)
。
我们将通读英语和法语语料库,并确定它们各自中唯一词的数量。 我们还将通过索引来表示单词,对于该单词的单编码向量,该单词对应的索引将设置为 1,而其余索引将设置为 0。 例如,假设在英语语料库中,我们有四个词:Global warming is real
。 我们可以如下定义每个单词的索引:
单词 | 索引 |
---|---|
global |
0 |
warming |
1 |
is |
2 |
real |
3 |
在这种情况下,我们可以将单词global
的单热编码向量定义为[1,0,0,0]^T
。 类似地,real
的一键编码向量可以表示为[1,0,0,0]^T
。
现在,转到每个句子或记录的源语言输入,我们将有一系列单词表示为一个单编码的向量序列。 下一个明显的问题是如何管理序列长度,因为这可能会有所不同。 最普遍接受的方法是使固定序列长度等于语料库中句子的最大序列长度,或者达到预定的合理长度。 我们将使用目标语句两次:一次作为解码器的翻译输出序列,一次作为解码器的输入序列,唯一的区别是输出序列比输入序列提前一个时间步长。 因此,输入目标序列中的第一个单词将是伪单词[START]
,而输出目标序列中的最后一个单词将是伪单词[END]
,标记句子序列的结尾。
如果目标法语句子是Je m'appelle Santanu
,则解码器中的输入目标和输出目标序列如下:
[START],[Je],[m’appelle] [Santanu]
[Je],[m’appelle] [Santanu][END]
我们选择用制表符表示[START]
,用下一行表示[END]
。
我们将数据创建活动分为三个部分:
- 读取源文件(英文)和目标文本(法文)的输入文件
- 从源语言和目标语言文本构建词汇表
- 将输入的英语和法语语料库处理为数字表示形式,以便可以在神经机器翻译网络中使用它们
此处说明的read_input_file
函数可用于阅读源语言和目标语言文本:
def read_input_file(self,path,num_samples=10e13):
input_texts = []
target_texts = []
input_words = set()
target_words = set()
with codecs.open(path, 'r', encoding='utf-8') as f:
lines = f.read().split('\n')
for line in lines[: min(num_samples, len(lines) - 1)]:
input_text, target_text = line.split('\t')
# \t as the start of sequence
target_text = '\t ' + target_text + ' \n'
# \n as the end of sequence
input_texts.append(input_text)
target_texts.append(target_text)
for word in input_text.split(" "):
if word not in input_words:
input_words.add(word)
for word in target_text.split(" "):
if word not in target_words:
target_words.add(word)
return input_texts,target_texts,input_words,target_words
vocab_generation
函数可用于为源语言和目标语言构建单词的词汇集:
def vocab_generation(self,path,num_samples,verbose=True):
input_texts,target_texts,input_words,target_words =
self.read_input_file(path,num_samples)
input_words = sorted(list(input_words))
target_words = sorted(list(target_words))
self.num_encoder_words = len(input_words)
self.num_decoder_words = len(target_words)
self.max_encoder_seq_length =
max([len(txt.split(" ")) for txt in input_texts])
self.max_decoder_seq_length =
max([len(txt.split(" ")) for txt in target_texts])
if verbose == True:
print('Number of samples:', len(input_texts))
print('Number of unique input tokens:',
self.num_encoder_words)
print('Number of unique output tokens:',
self.num_decoder_words)
print('Max sequence length for inputs:',
self.max_encoder_seq_length)
print('Max sequence length for outputs:',
self.max_decoder_seq_length)
self.input_word_index =
dict([(word, i) for i, word in enumerate(input_words)])
self.target_word_index =
dict([(word, i) for i, word in enumerate(target_words)])
self.reverse_input_word_dict =
dict((i, word) for word, i in self.input_word_index.items())
self.reverse_target_word_dict =
dict((i, word) for word, i in self.target_word_index.items())
process_input
函数利用先前函数中构建的输入和目标文本以及词汇表,将文本数据转换为数字形式,以供神经翻译机架构使用。 process_input
函数的代码如下:
def process_input(self,input_texts,target_texts=None,verbose=True):
encoder_input_data =
np.zeros((len(input_texts), self.max_encoder_seq_length,
self.num_encoder_words), dtype='float32')
decoder_input_data =
np.zeros((len(input_texts), self.max_decoder_seq_length,
self.num_decoder_words), dtype='float32')
decoder_target_data =
np.zeros((len(input_texts), self.max_decoder_seq_length,
self.num_decoder_words), dtype='float32')
if self.mode == 'train':
for i, (input_text, target_text) in
enumerate(zip(input_texts,target_texts)):
for t, word in enumerate(input_text.split(" ")):
try:
encoder_input_data[i, t,
self.input_word_index[word]] = 1.
except:
print(f'word {word}
encoutered for the 1st time, skipped')
for t, word in enumerate(target_text.split(" ")):
# decoder_target_data is ahead of decoder_input_data
by one timestep
decoder_input_data[i, t,
self.target_word_index[word]] = 1.
if t > 0:
# decoder_target_data will be ahead by one timestep
#and will not include the start character.
try:
decoder_target_data[i, t - 1,
self.target_word_index[word]] = 1.
except:
print(f'word {word}
encoutered for the 1st time,skipped')
return
encoder_input_data,decoder_input_data,decoder_target_data,
np.array(input_texts),np.array(target_texts)
else:
for i, input_text in enumerate(input_texts):
for t, word in enumerate(input_text.split(" ")):
try:
encoder_input_data[i, t,
self.input_word_index[word]] = 1.
except:
print(f'word {word}
encoutered for the 1st time, skipped')
return encoder_input_data,None,None,np.array(input_texts),None
encoder_input_data
变量将包含输入源数据,并且将是记录数,时间步数以及每个维度的维数的三维数组。 热编码向量。 类似地,decoder_input_data
将包含输入目标数据,而decoder_target_data
将包含目标标签。 在执行上述函数后,将生成训练机器翻译系统所需的所有相关输入和输出。 以下代码块包含与使用40000
样本执行vocab_generation
函数有关的显示统计信息:
('Number of samples:', 40000)
('Number of unique input tokens:', 8658)
('Number of unique output tokens:', 16297)
('Max sequence length for inputs:', 7)
('Max sequence length for outputs:', 16)
从前面的统计数据可以看出,40000
语料库中输入的英语单词的数量为8658
,文本句子的数量为8658
,而对应的法语单词的数量为16297
。 这表明以下事实:每个英语单词平均发出大约两个法语单词。 同样,我们看到英语句子中的最大单词数为7
,而法语句子中的最大单词数为14
(如果您排除了我们在法语句子中添加的[START]
和[END]
字符) 训练目的。 这也证实了以下事实:平均而言,每个要翻译的英语句子将产生双倍的单词数。
让我们看一下神经翻译机的输入和目标的形状:
('Shape of Source Input Tensor:',(40000, 7, 8658))
('Shape of Target Input Tensor:',(40000, 16, 16297))
(Shape of Target Output Tensor:',(40000, 16, 16297))
编码器数据的形状为(40000, 7, 8658)
,其中第一维用于源语言语句的数量,第二维用于时间步长的数量,最终维是单次热编码向量的大小, 是8658
,与英语词汇中的8658
源语言单词相对应。 类似地,对于目标输入和输出张量,我们看到一热编码向量的大小为16297
,与法语词汇中的16297
单词相对应。 法语句子的时间步长为16
。
如前所述,编码器将通过 LSTM 处理源输入序列,并将源文本编码为有意义的摘要。 有意义的摘要将存储在最后的序列步骤中,即隐藏和单元状态h[f]
和c[f]
。 这些向量在一起(即[h[f]; c[f]]
)提供了有关源文本的有意义上下文,并且训练了解码器来产生具有隐藏和单元状态向量的目标序列[h[f]; c[f]]
。
下图所示“图 3.5”是英语到法语翻译的训练过程的详细图。 英文句子It's a beautiful day
通过 LSTM 转换为含义摘要,然后存储在隐藏和单元格状态向量[h[f]; c[f]]
中。 然后使解码器根据嵌入在[h[f]; c[f]]
中的信息,以输入源语句为条件,生成自己的目标序列。 给定源句,使在时间步t
的解码器预测下一个目标单词,即在时间步t + 1
的单词。 这就是为什么目标输入字和目标输出字之间有一个时间步长的滞后的原因。 对于第一步,解码器在目标文本序列中没有任何先前的单词,因此可用于预测目标单词的唯一信息是以[h[f]; c[f]]
,作为初始隐藏和单元状态向量提供。 像编码器一样,解码器也使用 LSTM,并且如上所述,输出目标序列比输入目标序列提前一个时间步长:
图 3.5:训练时机器翻译网络流程的图示
我们基于“图 3.5”中所示的架构,定义了用于训练函数model_enc_dec
的编码器解码器端到端模型。 在这里,**编码器(LSTM 1)依次获取源语言文本单词,并在编码器(LSTM 1)的最后序列步骤中捕获源语言句子或文本的整个上下文。来自编码器的上下文将作为解码器(LSTM 2)**的初始状态进行馈送,该学习器将根据当前单词来预测下一个单词,因为在训练过程中我们会得到一个句子 / text 作为目标语言,因此解码器只需将其输入移位一个时间步即可形成目标:
def model_enc_dec(self):
#Encoder Model
encoder_inp =
Input(shape=(None,self.num_encoder_words),name='encoder_inp')
encoder = LSTM(self.latent_dim, return_state=True,name='encoder')
encoder_out,state_h, state_c = encoder(encoder_inp)
encoder_states = [state_h, state_c]
#Decoder Model
decoder_inp =
Input(shape=(None,self.num_decoder_words),name='decoder_inp')
decoder_lstm =
LSTM(self.latent_dim, return_sequences=True,
return_state=True,name='decoder_lstm')
decoder_out, _, _ =
decoder_lstm(decoder_inp, initial_state=encoder_states)
decoder_dense =
Dense(self.num_decoder_words,
activation='softmax',name='decoder_dense')
decoder_out = decoder_dense(decoder_out)
print(np.shape(decoder_out))
#Combined Encoder Decoder Model
model = Model([encoder_inp, decoder_inp], decoder_out)
#Encoder Model
encoder_model = Model(encoder_inp,encoder_states)
#Decoder Model
decoder_inp_h = Input(shape=(self.latent_dim,))
decoder_inp_c = Input(shape=(self.latent_dim,))
decoder_input = Input(shape=(None,self.num_decoder_words,))
decoder_inp_state = [decoder_inp_h,decoder_inp_c]
decoder_out,decoder_out_h,decoder_out_c =
decoder_lstm(decoder_input,initial_state=decoder_inp_state)
decoder_out = decoder_dense(decoder_out)
decoder_out_state = [decoder_out_h,decoder_out_c]
decoder_model = Model(inputs =
[decoder_input] + decoder_inp_state,output=
[decoder_out]+ decoder_out_state)
plot_model(model,show_shapes=True, to_file=self.outdir +
'encoder_decoder_training_model.png')
plot_model(encoder_model,show_shapes=True, to_file=self.outdir +
'encoder_model.png')
plot_model(decoder_model,show_shapes=True, to_file=self.outdir +
'decoder_model.png')
return model,encoder_model,decoder_model
虽然训练模型是一个简单的端到端模型,但推理模型并不是那么简单,因为我们不知道每个时间步长之前解码器的输入。 我们将在“构建推理模型”部分中更详细地讨论推理模型。
神经翻译机的损失函数是用于预测模型序列中每个目标单词的平均交叉熵损失。 实际目标词和预测目标词可以是我们所采用的法语语料库中的16,297
个词中的任何一个。 在时间步t
处的目标标签将是单热点编码的向量y[t] ∈ {0,1}^16297
,法语词汇表中每个16,297
个单词的预测输出将采用概率形式。 如果将预测的输出概率向量表示为p[t] ∈ (0, 1)^16297
,则特定句子在每个时间步的平均分类损失由以下给出:
通过汇总所有序列时间步长上的损失,可以得出整个句子的损失,如下所示:
由于我们使用小批量随机梯度下降进行工作,因此可以通过对小批量中所有句子的平均损失来获得小批量的平均成本。 如果我们使用大小为m
的微型批量,则每个微型批量的平均损失如下:
最小批量成本用于计算随机梯度下降的梯度。
我们首先执行model_enc_dec
函数来定义训练模型以及encoder_model
和decoder_model
进行推理,然后将其与categorical_crossentropy
损失和rmsprop
一起编译 ]优化器。 我们可以尝试使用其他优化器,例如 Adam,具有动量的 SDG 等,但是目前,我们将坚持使用rmsprop
。train
函数可以定义如下:
# Run training
def train(self,encoder_input_data,decoder_input_data,
decoder_target_data):
print("Training...")
model,encoder_model,decoder_model = self.model_enc_dec()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy')
model.fit([encoder_input_data, decoder_input_data],
decoder_target_data,
batch_size=self.batch_size,
epochs=self.epochs,
validation_split=0.2)
# Save model
model.save(self.outdir + 'eng_2_french_dumm.h5')
return model,encoder_model,decoder_model
我们在 80% 的数据上训练模型,并将其余 20% 的数据用于验证。 训练/测试拆分由以下定义的函数执行:
def train_test_split(self,num_recs,train_frac=0.8):
rec_indices = np.arange(num_recs)
np.random.shuffle(rec_indices)
train_count = int(num_recs*0.8)
train_indices = rec_indices[:train_count]
test_indices = rec_indices[train_count:]
return train_indices,test_indices
让我们尝试回顾推理模型的工作机制,并了解如何使用已经训练的模型的组件来构建它。 该模型的编码器部分应通过以源语言中的文本句子作为输入来工作,并提供最终的隐藏和单元状态向量[h[f]; c[f]]
作为输出。 我们不能按原样使用解码器网络,因为目标语言输入字不再可以馈送到解码器。 相反,我们将解码器网络折叠为一个步骤,并将该步骤的输出作为下一步的输入。 我们以虚拟字[START]
作为解码器的第一个输入字,以及[h[f]; c[f]]
,用作其初始隐藏状态和单元格状态。 目标输出字w[1]
以及隐藏和单元状态h[1]; c[1]
由解码器使用[START]
和[h[f]; c[f]]
,因为输入再次馈送到解码器以生成下一个字,然后重复该过程,直到解码器输出伪字[END]
为止。 下图说明了推理过程的逐步表示形式,以便于解释:
图 3.6:推理过程的逐步说明
从上图可以看出,解码器第一步的输出为C'est
,而隐藏状态和单元状态为[h'[1]; c'[1]]
。 如虚线所示,将其再次馈送到解码器,以生成下一个字以及下一组隐藏状态和单元状态。 由于解码器输出伪结束字符[END]
,因此重复该过程。
为了进行推断,我们可以按原样使用网络的编码器部分,并进行一些修改以使解码器折叠以使其包含一个时间步。 概括地说,无论 RNN 是一个时间步长还是几个时间步长,与 RNN 相关的权重都不会改变,因为 RNN 的所有时间步长都共享相同的权重。
为了进行推断,我们可以看到训练模型的编码器部分用作函数model_enc_dec
中的encoder_model
。 类似地,使用相同的解码器 LSTM 定义单独的decoder_model
,该解码器将输入作为隐藏状态,单元状态和输入字,并输出目标字以及更新的隐藏和单元状态。 为了清楚起见,再次重复定义了推断模型的函数model_enc_dec
encoder_model
和decoder_model
:
def model_enc_dec(self):
#Encoder Model
encoder_inp =
Input(shape=(None,self.num_encoder_words),name='encoder_inp')
encoder = LSTM(self.latent_dim, return_state=True,name='encoder')
encoder_out,state_h, state_c = encoder(encoder_inp)
encoder_states = [state_h, state_c]
#Decoder Model
decoder_inp =
Input(shape=(None,self.num_decoder_words),name='decoder_inp')
decoder_lstm =
LSTM(self.latent_dim, return_sequences=True,
return_state=True,name='decoder_lstm')
decoder_out, _, _ =
decoder_lstm(decoder_inp, initial_state=encoder_states)
decoder_dense =
Dense(self.num_decoder_words,
activation='softmax',name='decoder_dense')
decoder_out = decoder_dense(decoder_out)
print(np.shape(decoder_out))
#Combined Encoder Decoder Model
model = Model([encoder_inp, decoder_inp], decoder_out)
#Encoder Model
encoder_model = Model(encoder_inp,encoder_states)
#Decoder Model
decoder_inp_h = Input(shape=(self.latent_dim,))
decoder_inp_c = Input(shape=(self.latent_dim,))
decoder_input = Input(shape=(None,self.num_decoder_words,))
decoder_inp_state = [decoder_inp_h,decoder_inp_c]
decoder_out,decoder_out_h,decoder_out_c =
decoder_lstm(decoder_input,initial_state=decoder_inp_state)
decoder_out = decoder_dense(decoder_out)
decoder_out_state = [decoder_out_h,decoder_out_c]
decoder_model = Model(inputs =
[decoder_input] + decoder_inp_state,output=
[decoder_out]+ decoder_out_state)
plot_model(model,to_file=self.outdir +
'encoder_decoder_training_model.png')
plot_model(encoder_model,to_file=self.outdir + 'encoder_model.png')
plot_model(decoder_model,to_file=self.outdir + 'decoder_model.png')
return model,encoder_model,decoder_model
解码器一次将运行一个时间步。 在第一种情况下,它将从编码器获取隐藏状态和单元状态,并根据伪单词[START]
猜测翻译的第一个单词。 第一步中预测的单词,连同生成的隐藏状态和单元状态一起,再次馈送到解码器以预测第二个单词,然后继续进行处理,直到预测出由虚拟单词[END]
表示的句子结尾。
现在,我们已经定义了将源句子/文本翻译成目标语言对应物所需的所有函数,我们将它们组合起来以构建一个函数,该函数会生成翻译后的句子,给定源语言输入序列或句子:
def decode_sequence(self,input_seq,encoder_model,decoder_model):
# Encode the input as state vectors.
states_value = encoder_model.predict(input_seq)
# Generate empty target sequence of length 1.
target_seq = np.zeros((1, 1, self.num_decoder_words))
# Populate the first character of target sequence
with the start character.
target_seq[0, 0, self.target_word_index['\t']] = 1.
# Sampling loop for a batch of sequences
stop_condition = False
decoded_sentence = ''
while not stop_condition:
output_word, h, c = decoder_model.predict(
[target_seq] + states_value)
# Sample a token
sampled_word_index = np.argmax(output_word[0, -1, :])
sampled_char =
self.reverse_target_word_dict[sampled_word_index]
decoded_sentence = decoded_sentence + ' ' + sampled_char
# Exit condition: either hit max length
# or find stop character.
if (sampled_char == '\n' or
len(decoded_sentence) > self.max_decoder_seq_length):
stop_condition = True
# Update the target sequence (of length 1).
target_seq = np.zeros((1, 1, self.num_decoder_words))
target_seq[0, 0, sampled_word_index] = 1.
# Update states
states_value = [h, c]
return decoded_sentence
训练模型后,我们就对保持数据集进行推断并检查翻译质量。 inference
函数可以编码如下:
def inference(self,model,data,encoder_model,decoder_model,in_text):
in_list,out_list = [],[]
for seq_index in range(data.shape[0]):
input_seq = data[seq_index: seq_index + 1]
decoded_sentence =
self.decode_sequence(input_seq,encoder_model,decoder_model)
print('-')
print('Input sentence:', in_text[seq_index])
print('Decoded sentence:',decoded_sentence)
in_list.append(in_text[seq_index])
out_list.append(decoded_sentence)
return in_list,out_list
通过调用 Python 脚本MachineTranslation.py
,可以在保持数据集上训练和验证机器翻译模型,如下所示:
python MachineTranslation.py --path '/home/santanu/ML_DS_Catalog/Machine Translation/fra-eng/fra.txt' --epochs 20 --batch_size 32 -latent_dim 128 --num_samples 40000 --outdir '/home/santanu/ML_DS_Catalog/Machine Translation/' --verbose 1 --mode train
保留数据集中机器翻译模型表现出色的几个英语句子的翻译结果如下,以供参考:
('Input sentence:', u'Go.')
('Decoded sentence:', u' Va ! \n')
('Input sentence:', u'Wait!')
('Decoded sentence:', u' Attendez ! \n')
('Input sentence:', u'Call me.')
('Decoded sentence:', u' Appelle-moi ! \n')
('Input sentence:', u'Drop it!')
('Decoded sentence:', u' Laisse tomber ! \n')
('Input sentence:', u'Be nice.')
('Decoded sentence:', u' Soyez gentil ! \n')
('Input sentence:', u'Be fair.')
('Decoded sentence:', u' Soyez juste ! \n')
('Input sentence:', u"I'm OK.")
('Decoded sentence:', u' Je vais bien. \n')
('Input sentence:', u'I try.')
('Decoded sentence:', u' Je vais essayer.')
但是,在某些情况下,机器翻译的效果不佳,如下所示:
('Input sentence:', u'Attack!')
('Decoded sentence:', u' ma ! \n')
('Input sentence:', u'Get up.')
('Decoded sentence:', u' un ! \n')
总之,先前说明的神经机器翻译实现将相对较短的英语句子翻译为法语的工作相当不错。 我要强调的一件事是使用单热编码向量来表示每种语言中的输入单词。 由于我们使用的是相对较小的 40,000 个单词的语料库,因此词汇量是合理的,因此,我们能够分别使用大小为 8,658 和 16,297 的英语和法语词汇量的一键编码向量。 随着语料库的增加,单热点编码词向量的大小将进一步增加。 比较两个单词时,这种稀疏的高维向量没有任何相似性概念,因为即使两个单词的含义几乎相同,它们的余弦乘积也将是0
。 在下一节中,我们将了解如何以较小的维数进行单词向量嵌入来解决此问题。
代替单热编码向量,可以使用词向量嵌入来表示维的密集空间中的单词,该空间比单热编码向量低得多。 嵌入单词w
的单词向量可以用v[w] ∈ R^m
表示,其中m
是词向量嵌入的维数。 如我们所见,虽然单热编码向量的每个分量只能占用{0,1}的二进制值,但词向量嵌入的分量却可以占用任何实数,因此具有更密集的表示。 相似性和类比的概念也与词向量嵌入有关。
通常通过诸如连续词袋法,skip-gram,GloVe 等技术来训练词向量嵌入。 我们将不对它们的实现进行过多的介绍,但中心思想是以这样的方式定义词向量嵌入:将类似的词紧密放置在m
维欧几里得空间中:
图 3.7:GloVe 嵌入的相似性和类比说明
在上一张图中,我们绘制了男人,女人,国王和女王的 GloVe 词向量嵌入的二维 TSNE 视图 。 我们可以看到,男人和女人具有内在的相似性,国王和女王的情况也是如此。 此外,我们看到国王和男人之间的向量差异与女王和女人的向量差异几乎相同,这可能代表王权的一些概念。 如我们所见,除了表达单词之间的相似性之外,还可以通过单词向量嵌入来表示类似男人:国王:女人:女王之类的东西。 在下一节中,我们将讨论使用 RNN 中的嵌入层将输入单词表示为单词向量嵌入,而不是单编码的向量。
嵌入层将输入单词的索引作为输入,并提供单词的单词向量嵌入作为输出。 嵌入层的大小为R^(dxV)
,其中d
是词向量嵌入的大小,V
是词汇的大小。 嵌入层可以根据问题了解嵌入本身,也可以提供预训练的嵌入层。 在我们的案例中,我们将让神经机器翻译找出对于源语言和目标语言而言,嵌入向量应该是什么,以实现良好的翻译。 结果,我们定义的每个函数都应更改以适应嵌入层。
我们将需要对现有函数进行一些更改,以适应嵌入层。 首先,process_input
将处理输入以在不同的时间步长中具有单词索引,而不是单热编码向量,如下所示:
def process_input(self,input_texts,target_texts=None,verbose=True):
encoder_input_data = np.zeros(
(len(input_texts), self.max_encoder_seq_length),
dtype='float32')
decoder_input_data = np.zeros(
(len(input_texts), self.max_decoder_seq_length),
dtype='float32')
decoder_target_data = np.zeros(
(len(input_texts), self.max_decoder_seq_length,1),
dtype='float32')
if self.mode == 'train':
for i, (input_text, target_text) in
enumerate(zip(input_texts,target_texts)):
for t, word in enumerate(input_text.split(" ")):
try:
encoder_input_data[i, t] =
self.input_word_index[word]
except:
encoder_input_data[i, t] =
self.num_encoder_words
for t, word in enumerate(target_text.split(" ")):
# decoder_target_data is ahead of decoder_input_data
by one timestep
try:
decoder_input_data[i, t] =
self.target_word_index[word]
except:
decoder_input_data[i, t] =
self.num_decoder_words
if t > 0:
# decoder_target_data will be ahead by one timestep
#and will not include the start character.
try:
decoder_target_data[i, t - 1] =
self.target_word_index[word]
except:
decoder_target_data[i, t - 1] =
self.num_decoder_words
print(self.num_encoder_words)
print(self.num_decoder_words)
print(self.embedding_dim)
self.english_emb = np.zeros((self.num_encoder_words + 1,
self.embedding_dim))
self.french_emb = np.zeros((self.num_decoder_words + 1,
self.embedding_dim))
return encoder_input_data,decoder_input_data,decoder_target_data,np.array(input_texts),
np.array(target_texts)
else:
for i, input_text in enumerate(input_texts):
for t, word in enumerate(input_text.split(" ")):
try:
encoder_input_data[i, t] = self.input_word_index[word]
与以前的process_input
函数相比,唯一的变化是,我们不再用单热编码向量表示单词,而是用单词的索引表示。 另外,您是否注意到我们为词汇表中不存在的单词添加了额外的单词索引? 理想情况下,这不是为了训练数据而发生的,但是在测试过程中,可能会出现一个不在词汇表中的全新单词。
以下是来自输入处理的统计信息:
Number of samples: 40000
Number of unique input tokens: 8658
Number of unique output tokens: 16297
Max sequence length for inputs: 7
Max sequence length for outputs: 16
('Shape of Source Input Tensor:', (40000, 7))
('Shape of Target Input Tensor:', (40000, 16))
('Shape of Target Output Tensor:', (40000, 16, 1))
如我们所见,源和目标输入张量现在具有7
和16
时间步长,但是没有一键编码向量的维数。 每个时间步长都包含单词的索引。
下一个变化是关于编码器和解码器网络,以在 LSTM 层之前容纳嵌入层:
def model_enc_dec(self):
#Encoder Model
encoder_inp = Input(shape=(None,),name='encoder_inp')
encoder_inp1 =
Embedding(self.num_encoder_words + 1,
self.embedding_dim,weights=[self.english_emb])
(encoder_inp)
encoder = LSTM(self.latent_dim, return_state=True,name='encoder')
encoder_out,state_h, state_c = encoder(encoder_inp1)
encoder_states = [state_h, state_c]
#Decoder Model
decoder_inp = Input(shape=(None,),name='decoder_inp')
decoder_inp1 =
Embedding(self.num_decoder_words+1,self.embedding_dim,weights=
[self.french_emb])(decoder_inp)
decoder_lstm =
LSTM(self.latent_dim, return_sequences=True,
return_state=True,name='decoder_lstm')
decoder_out, _, _ =
decoder_lstm(decoder_inp1,initial_state=encoder_states)
decoder_dense = Dense(self.num_decoder_words+1,
activation='softmax',name='decoder_dense')
decoder_out = decoder_dense(decoder_out)
print(np.shape(decoder_out))
#Combined Encoder Decoder Model
model = Model([encoder_inp, decoder_inp], decoder_out)
#Encoder Model
encoder_model = Model(encoder_inp,encoder_states)
#Decoder Model
decoder_inp_h = Input(shape=(self.latent_dim,))
decoder_inp_c = Input(shape=(self.latent_dim,))
decoder_inp_state = [decoder_inp_h,decoder_inp_c]
decoder_out,decoder_out_h,decoder_out_c =
decoder_lstm(decoder_inp1,initial_state=decoder_inp_state)
decoder_out = decoder_dense(decoder_out)
decoder_out_state = [decoder_out_h,decoder_out_c]
decoder_model = Model(inputs =
[decoder_inp] + decoder_inp_state,output=
[decoder_out]+ decoder_out_state)
return model,encoder_model,decoder_model
训练模型需要使用sparse_categorical_crossentropy
进行编译,因为输出目标标签表示为索引,而不是一键编码的单词向量:
def train(self,encoder_input_data,decoder_input_data,
decoder_target_data):
print("Training...")
model,encoder_model,decoder_model = self.model_enc_dec()
model.compile(optimizer='rmsprop',
loss='sparse_categorical_crossentropy')
model.fit([encoder_input_data, decoder_input_data],
decoder_target_data,
batch_size=self.batch_size,
epochs=self.epochs,
validation_split=0.2)
# Save model
model.save(self.outdir + 'eng_2_french_dumm.h5')
return model,encoder_model,decoder_model
接下来,我们需要对与推理相关的函数进行修改,以适应与嵌入相关的更改。 现在,用于推断的encoder_model
和decoder_model
分别将嵌入层用于英语和法语词汇。
最后,我们可以使用decoder_model
和encoder_model
如下创建序列生成器函数:
def decode_sequence(self,input_seq,encoder_model,decoder_model):
# Encode the input as state vectors.
states_value = encoder_model.predict(input_seq)
# Generate empty target sequence of length 1.
target_seq = np.zeros((1, 1))
# Populate the first character of target sequence
with the start character.
target_seq[0, 0] = self.target_word_index['\t']
# Sampling loop for a batch of sequences
stop_condition = False
decoded_sentence = ''
while not stop_condition:
output_word, h, c = decoder_model.predict(
[target_seq] + states_value)
# Sample a token
sampled_word_index = np.argmax(output_word[0, -1, :])
try:
sampled_char =
self.reverse_target_word_dict[sampled_word_index]
except:
sampled_char = '<unknown>'
decoded_sentence = decoded_sentence + ' ' + sampled_char
# Exit condition: either hit max length
# or find stop character.
if (sampled_char == '\n' or
len(decoded_sentence) > self.max_decoder_seq_length):
stop_condition = True
# Update the target sequence (of length 1).
target_seq = np.zeros((1, 1))
target_seq[0, 0] = sampled_word_index
# Update states
states_value = [h, c]
return decoded_sentence
可以通过如下运行脚本来调用模型的训练:
python MachineTranslation_word2vec.py --path '/home/santanu/ML_DS_Catalog-/Machine Translation/fra-eng/fra.txt' --epochs 20 --batch_size 32 --latent_dim 128 --num_samples 40000 --outdir '/home/santanu/ML_DS_Catalog-/Machine Translation/' --verbose 1 --mode train --embedding_dim 128
该模型在 GeForce GTX 1070 GPU 上进行了训练,大约需要 9.434 分钟才能训练 32,000 条记录并进行 8,000 条记录的推理。 强烈建议用户使用 GPU,因为 RNN 的计算量很大,可能需要数小时才能在 CPU 上训练相同的模型。
我们可以通过运行 python 脚本MachineTranslation.py
来训练机器翻译模型并在保持数据集上执行验证,如下所示:
python MachineTranslation.py --path '/home/santanu/ML_DS_Catalog/Machine Translation/fra-eng/fra.txt' --epochs 20 --batch_size 32 -latent_dim 128 --num_samples 40000 --outdir '/home/santanu/ML_DS_Catalog/Machine Translation/' --verbose 1 --mode train
从嵌入向量方法获得的结果与单热编码词向量的结果相似。 这里提供了一些来自保持数据集推理的翻译:
Input sentence: Where is my book?
Decoded sentence: Où est mon Tom ?
-
Input sentence: He's a southpaw.
Decoded sentence: Il est en train de
-
Input sentence: He's a very nice boy.
Decoded sentence: C'est un très bon
-
Input sentence: We'll be working.
Decoded sentence: Nous pouvons faire
-
Input sentence: May I have a program?
Decoded sentence: Puis-je une ?
-
Input sentence: Can you make it safe?
Decoded sentence: Peux-tu le faire
-
Input sentence: We walked to my room.
Decoded sentence: Nous avons devons
-
Input sentence: Don't stand too close.
Decoded sentence: Ne vous en prie.
-
Input sentence: Where's the dog?
Decoded sentence: Où est le chien ?
-
Input sentence: He's a hopeless case.
Decoded sentence: Il est un fait de
-
Input sentence: Where were we?
Decoded sentence: Où fut ?
读者现在应该对几种机器翻译方法以及神经翻译机器与传统机器有何不同有很好的理解。 现在,我们还应该深入了解如何从头开始构建神经机器翻译系统,以及如何以有趣的方式扩展该系统。 借助提供的信息和实现演示,建议读者探索其他并行语料库数据集。
在本章中,我们定义了嵌入层,但未使用预训练的嵌入(例如 GloVe,FastText 等)来加载它们。 建议读者使用预训练的词向量嵌入为嵌入层加载,并查看是否会产生更好的结果。 在第 4 章,“使用 GAN 进行时装行业中的样式迁移”中,我们将通过与生成性对抗网络(这是现代的革命)进行与时装业中样式迁移有关的项目。 人工智能领域。