1、简介

使用RNN来做机器翻译, 机器翻译模型有很多种, 这里介绍sequence-2-sequence模型, 机器翻译是many-2-many多对多的问题。输入的英语长度大于1, 输出的德语长度也大于1,而且输入和输出的长度不固定。

image-20231205095703255

做任何机器学习的应用, 第一步都是处理输出。这里就取一个小规模数据集就行。可用该网站英语翻译语料 (manythings.org)](http://www.manythings.org/anki/))提供的小规模数据集来训练一个seq-2-seq模型。这个网站上有多种语言翻译的数据, 这个文件是德语和英语的翻译。文件中, 坐标是英语句子, 右边是德语句子, 给定一句英语, 如何翻译结果呢, match其中一个德语句子就算完全正确。

image-20231205111531811

处理数据时, 把这些句子用矩阵表示, 首先做预处理, 比如把大小变成小写, 去掉标点符号。

处理之后就要做tokenization, 把一句话变成很多个单词或者字符。做tokenization时, 要用两个不同的tokenizer, 英语用一个, 德语用一个。tokenization之后要建立两个字典, 一个英语字典, 一个德语字典。

image-20231205111946725

tokenization可以是character level也可以是word level, 二者都可以。character level tokenization会把一句话分割成很多个字符, word level tokenization会把一句话分割成很多单词。

为了方便起见, 这里使用character level tokenization, 一句话变成一个list, list中每个元素变成一个字符。但实际的机器翻译都是用word level tokenization, 因为它们数据集足够大。

image-20231205112729018

刚才提到为什么要用两个不同的tokenizer, 这里解释一下。在字符层面, 不同的语言通常有不同的alphabet字母表。 英语有26个拉丁字母, 如果区分大小写就有53个字母。

德语也有26个拉丁字母, 但还有4个不常用字母。中文没有字母, 而是有几千个汉字。日语字符更复杂, 有46个片假名, 还有几百个汉字。两种语言的字符通常是不同的。所以应该用两种不同的tokenizer, 各有各的字母表。

image-20231205113032240

如果是word level tokenization, 就更应该用两种不同的tokenizer和不同的字典。英语和德语的词汇完全不一样, 绝大多数德语单词在英语字典里面找不到。此外, 不同的语言有不同的分词方法, 汉语、日语和欧洲语言的分词方法就不一样。

keras提供的库叫作tokenization, 会自动生成字典。左边是英语字典, 一共有27个字符,包含26个字母和一个空格。27个字符分别对应27个数字。右边是德语字典, 删除了不常用的字符, 只保留了26个字母和一个空格。任务是把英语翻译成德语。英语是原语言, 德语是目标语言。要往目标语言-德语的字典里添加两个符号。一个起始符\t, 一个终止符\n。拿什么做起止符和终止符都可以, 只要不跟字典里面已有的字符冲突即可。

image-20231205114322805

tokenization结束之后, 每句话就变成一个字符的列表, 并且生成一个英语字典和一个德语字典。用这个字典就可以把每个字符映射成一个整数。这样一句话就变成了一个sequence, 每个元素是一个整数。

image-20231205114607373

可以进一步把每个数字用one-hot向量表示, 做完one-hot encoding,每个字符用一个向量表示, 每句话用一个矩阵表示,这个矩阵就是RNN的输入。

image-20231205115058404

2、模型搭建

下面开始搭建一个seq2seq模型, 并且开始训练这个模型。 seq2seq模型有一个encoder编码器和一个decoder解码器。encoder是LSTM或其他的RNN模型。用来从输入的英语句子中提取特征。encoder最后一个状态就是从输入的句子中提取的特征, 包含这句话的信息。encoder其余的状态没有用, 都被丢弃, encoder的输出是LSTM最后的状态h以及最后的传输带C。

image-20231205115436983

seq2seq还有一个decoder用来生成德语。这个decoder其实就是上次讲解的text generation。与上次的text generation文本生成器唯一的区别在于初始状态。上次用的文本生成器的初始状态是一个全零向量。这里的decoder的初始状态是encoder的最后一个状态。encoder最后一个状态h和c是从输入的英语句子中提取的特征向量。概括了输入的英语句子, decoder靠这个状态来知道这句英语是go away。

image-20231205115939703

再强调一下, decoder的初始状态是encoder的最后一个状态。通过encoder最后一个状态, decoder得知输入的英语句子是go away。现在decoder开始生成德语句子, decoder是个LSTM模型, 它每次接受一个输入, 然后输出对下一个字符的预测。第一个输入必须是起始符, 用\t表示。这就是为什么要在德语字典中加入起始符。decoder会输出一个概率分布, 记作向量p。

image-20231205143042665

起始符后面德语的第一个字母是m, 把m做one-hot encoder, 作为标签y, 用标签y和预测p的cross entropy作为损失函数。

image-20231205143231577

希望预测p尽量接近y, 所以损失函数越小越好。有了损失函数就可以反向传播计算梯度。梯度会从损失函数传到decoder, 然后再从decoder一直传到encoder。然后用梯度下降来更新decoder和encoder的模型参数, 让损失函数减小。

然后输入是两个字符, 起始符与字母m。 decoder会输出对下一个字符的预测,记为向量p。输入的字符串的下一个字符是字母a。把a做one hot encoding, 作为标签y。损失函数是标签y与预测p的cross entropy, 然后用反向传播计算梯度, 然后更新decoder和encoder。

image-20231205144742691

再下一个输入是3个字符, 起始符, 字母m和字母a。LSTM输出对下一个字符的预测记为向量p。真实的下一个字母是c, 所以标签y是字母c的one hot向量。同样的道理, 用反向传播计算梯度, 然后做梯度下降更新encoder和decoder。

image-20231205144931769

不断重复这个过程, 直到德语的最后一个字符。

最后一轮, 把整句德语作为decoder输入, 所以用停止符的one-hot向量作为标签y。希望输出的预测尽可能接近标签, 也就是停止符。然后再做一次反向传播, 再更新一次模型参数,不断重复这个训练过程, 拿所有的英语德语二元组来训练decoder和encoder。

image-20231205145303874

3、训练

如果用keras、pytorch或TensorFlow等来搭建一个seq2seq模型, 你需要这么做。encoder的输入是英文句子的one hot encoding,用一个矩阵表示。encoder网络有一层或多层LSTM用来从英文句子中提取特征, LSTM的输出是最后一个状态h与最终的传输带c。decoder网络的初始状态是h与c, 这样可以让encoder和decoder连起来。在做反向传播时, 梯度可以顺着这条线从decoder传播到encoder。decoder网络的输入是德语的上半句话,decoder输出当前状态h, 然后全连接层输出对下一个字符的预测。

image-20231205145946456

训练好了seq2seq模型, 可以拿它把英语翻译成德语。把一句英语的每个字符输入encoder, encoder会在状态h和c中积累这句话的信息。encoder输出最后的状态记作h0和c0, 它们是从这句话里面提取的特征, h0和c0被送给了decoder。

4、推理

encoder的输出h0和c0被记为decoder的初始状态。

image-20231205150620310

这样decoder就知道输入的英文句子是go away。现在decoder就跟文本生成器一样工作。 首先把起始符输入decoder, 有了新的输入, decoder就会更新状态h和传输带c并预测下一个字符, 并输出一个概率分布。

image-20231205150911295

根据概率分布来做抽样, 得到字符词, 然后把c记录下来。

image-20231205153418706

不断重复这个过程, 更新状态并且生成新的字符, 然后用新生成的字符作为下一轮的输入。

运行14轮之后的状态是h14和c14。上一轮生成的字符是字母e, 现在拿它作为输入, 根据decoder输出的概率分布做抽样, 可能碰巧抽到了终止符。一旦抽到终止符, 就终止文本生成并返回记录下来的字符串,这个字符串就是模型翻译得到的德语。

image-20231205153842144

总结上述过程。

使用seq2seq模型做机器翻译模型, 模型有一个encoder网络和一个decoder网络。

image-20231205154704103

encoder的输入是一句英语, 每输入一个词, RNN会更新状态。把输入的信息积累在encoder状态里。

encoder最后一个状态就是从英文句子里提取的特征, encoder只输出最后一个状态, 会扔掉之前的所有状态。

把最后一个状态传递给decoder网络。

image-20231205154933938

把encoder的最后一个状态作为decoder的初始状态。初始化后, decoder网络就知道输入的英文句子了, 然后decoder作为一个文本生成器生成一句德语。首先把起始符作为decoder RNN的输入, decoder RNN会更新状态s1。

image-20231205155202969

然后全连接层输出的预测概率记为p1,根据概率分布p1做抽样得到下一个字符记作z1。

decoder将z1作为输入更新状态s2, 并输出预测的概率为p2, 根据p2抽样得到新的字符z2。

image-20231205155713056

同样的道理decoder网络拿z2作为输入更新状态s3。不断重复这个过程, 抽样得到新的字符记作z, 然后拿z作为下一轮输入更新状态s以及计算概率分布p。

如果抽到了停止符, 那么就终止文本生成, 返回生成的序列。

image-20231205155849425

5、seq2seq模型改进

针对上述的seq2seq模型, 如何做模型改进。

seq2seq模型的原理是这样的。encoder处理输入的英语句子, 把信息压缩到状态向量里面, encoder最后一个状态是整句话的一个概要。理想情况下, encoder最后一个状态包含整句英语的完整信息, 当然那是理想情况。

假如英语句子太长, LSTM就会遗忘。假如英语里面有些信息被遗忘了。那么encoder就不可能有英语句子的完整信息, decoder生成的德语肯定会有遗漏。

一种很显然的改进就是用双向LSTM代替单向LSTM。双向LSTM有两条链, 一条从左到右, 一条从右到左。两条链独立运行,分别从输入中提取特征, 从左到右的最后一个状态ht可能会遗忘最左边的输入。从右到左的这条链的最后一个状态ht’会记住最左边的信息, 把ht和ht‘结合起来,就能更好的记住所有的输入。

image-20231205161216936

综上, 单向LSTM换成双向LSTM可以更好的记住输入的句子,但decoder必须是单向LSTM。decoder就是一个文本生成器, 必须按照顺序生成文本, 所以decoder不能用双向LSTM。

上面用了character level tokenization, 这样比较方便, 不需要用embedding层。但最好还是用word level tokenization。 原因是这样的, 英文平均每个单词有4.5个字母, 如果用单词代替字符,那么输入的序列就能缩短4.5倍, 序列更短就更不能遗忘。

image-20231205161624960

但是想要用word level tokenization, 需要有足够大的数据集。用word level tokenization得到的vocabulary大约是1万。因此one hot向量的维度大约是1万, 必须用word embedding 得到更低维的向量。embedding层的参数量太大了, 用小的数据集无法得到很好的训练。embedding层会有overfitting问题,得有足够大的训练数据集,或者对embedding层做预训练, 才能避免overfitting。

image-20231205162306236

另外一种改进方法是multi task learning, 多任务学习。encoder读入一句英语, 把英语句子概括成最终的状态向量h和c。decoder通过h和c获取英语句子的信息, 然后生成一句德语。训练时比较decoder的预测与真实的德语单词, 从而获得损失函数和梯度,用来更新decoder和encoder。

image-20231205162811779

把英语翻译成德语是一个任务, 还可以多加几个任务。比如把英语句子翻译成英语句子本身。添加一个decoder, 让它根据h和c来生成英语句子。这样一来, encoder只有一个而训练数据多了一倍,所以encoder可以被训练得很好。

image-20231205163020849

还可以添加其他很多任务,英文翻译成很多其他语言的数据集。可以利用这些数据更好的训练encoder。

比如还可添加更多任务,把英语翻译成法语, 西班牙语等, 添加更多的decoder, 让这些decoder生成各种语言。但encoder只有一个。如果用10种语言做训练, 那么训练encoder的数据就多了10倍, encoder可以训练的更好。目标是把英语翻译成德语, 通过借助其他语言可以把encoder变得更好。 虽然德语decoder没有改进, 但是翻译的效果还是会变好。

image-20231205163630778