<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>机器翻译 on bitJoy</title><link>https://bitjoy.net/tags/%E6%9C%BA%E5%99%A8%E7%BF%BB%E8%AF%91/</link><description>Recent content in 机器翻译 on bitJoy</description><generator>Hugo -- 0.148.2</generator><language>en</language><lastBuildDate>Mon, 17 Feb 2020 10:44:48 +0800</lastBuildDate><atom:link href="https://bitjoy.net/tags/%E6%9C%BA%E5%99%A8%E7%BF%BB%E8%AF%91/index.xml" rel="self" type="application/rss+xml"/><item><title>CS224N（2.14）Subword Models</title><link>https://bitjoy.net/posts/2020-02-17-cs224n-0214-subword-models/</link><pubDate>Mon, 17 Feb 2020 10:44:48 +0800</pubDate><guid>https://bitjoy.net/posts/2020-02-17-cs224n-0214-subword-models/</guid><description>&lt;p>今天介绍一下subword（子词）模型。之前介绍的NLP模型都是基于word的，对于英文来说是一个个单词，对于中文来说是一个个词语（需要分词）。不过，最近几年，subword模型多起来了，这就是我们今天要介绍的内容。&lt;/p>
&lt;p>对于英文来说，文字的粒度从细到粗依次是character, subword, word，character和word都很好理解，subword相当于英文中的词根、前缀、后缀等，如unfortunately中的un、ly、fortun(e)等就是subword，它们都是有含义的。对于中文来说，只有两层，character和subword是同一层，表示单个的字，而word表示词语。&lt;/p>
&lt;p>之前介绍的基于word的模型，存在out of vocabulary（OOV，未登录词）的问题。以英文为例，现存的英文单词数量太多了，随便加个前缀、后缀，变个时态什么的都变成新的单词了，所以英文单词的词典数量特别大，而且有很多低频稀疏词。很多模型在训练时都会去掉低频词，只保留高频词。那么这就存在一个问题，如果预测时遇到未登录词，则模型不认识，出现OOV的问题。&lt;/p>
&lt;p>为了解决这个问题，一开始想到的是采用character级别的模型，即对26个字母训练word2vec，每个词由其字母的embedding拼接或者求平均得到。但是character级别的模型效果相比于word级别的模型效果差不多，并没有显著优势。而且如果用RNN来训练character级别的模型也有它的问题，就是训练起来非常慢。特别是对英文来说，原来的一个word，现在变成了七八个character，时间步长增加了很多，训练和预测都更久了，而且梯度消失（爆炸）的问题也会更严重。&lt;/p>
&lt;p>后来，人们就想用subword模型作为character和word的折中模型。subword模型主要有两种，它们都能解决未登录词（OOV）的问题，如下图所示。第一种是模型结构和word模型完全一样，只不过把word换成了subword。第二种则是word和character模型的杂交模型。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2020-02-17-cs224n-0214-subword-models/p17.png">&lt;/p>
&lt;p>对于第一种模型，关键问题是怎样得到subword。前面提到character的粒度太细，虽然能解决OOV问题，但效果并不是太好；word模型的word数量太多，存在大量稀疏word，删掉它们又会导致OOV问题，所以打算用subword模型。那么，怎样提取一个单词的subword呢？前面提到，unfortunately中的un、ly、fortun(e)等就是subword，但是对每个词都这样提取subword的话，费时费力不说，也不够智能。&lt;/p>
&lt;p>有人就想出了用BPE算法来提取高频subword。BPE，全称是byte pair encoder，是上世纪提出的一种压缩算法，其核心思想是不断用字母表中不存在的char来代替最高频的char pair。举个例子，对于字符串aaabdaaabac，其字符串中最开始只出现了a/b/c/d这四个char；统计所有char pair，最高频的是aa，用不在字母表中的另一个字符Z代替aa，则原字符串变成了ZabdZabac，字母表变成了a/b/c/d/Z；如此不断进行下去，直到字母表大小达到一定的阈值，或者所有连续的char pair的频数都等于1了。关于BPE算法的进一步介绍请看这里：&lt;a href="https://zhuanlan.zhihu.com/p/38130825">https://zhuanlan.zhihu.com/p/38130825&lt;/a>。&lt;/p>
&lt;p>第一种模型就是用BPE算法来得到高频subword的。比如下图的例子，语料集D中出现了5个low，2个lower等等。最开始，字母表V中是语料集中出现的所有单个字母的集合{l,o,…,d}。然后，发现e s这个char pair出现次数最多，将其作为一个subword加入到V中，同时将D中的es合并看作一个新的char。接着统计发现es t这个char pair（此时es已经是一个char了）出现次数最多，将其作为一个subword加入到V中，如此进行下去。发现没有，我们自动从D中提取了est这个subword，而est就是最高级的后缀。也就是说BPE算法自动提取到了英文的前缀、后缀等subword信息，完全避免了之前费时费力地从unfortunately中手工提取un、ly、fortun(e)的过程。这种方法也能解决未登录词问题，但是粒度又不至于太细。一方面是因为最开始的时候D中原始出现的单个字母都在V中，另一方面，由V中字母组成的未出现词也能由subword构成，比如下图中虽然less没有出现在D中，但可以由l es s这3个subword组成，而这3个subword是在最终的V中的。&lt;/p>
&lt;p>得到高频subword作为V之后，后续在进行NLP任务时，encoder的时候查一下V，把char pair替换为新字符；decoder的时候查一下V，把新字符替换回原来的char pair。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2020-02-17-cs224n-0214-subword-models/p22.png">&lt;/p>
&lt;p>最近比较流行的BERT，字典中既有相对比较常见的词，对于不太常见的词则用subwords/wordpieces来表示。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2020-02-17-cs224n-0214-subword-models/p27.png">&lt;/p>
&lt;p>第二种被称为杂交模型的方法就相对简单了。这种方法是Manning老师提出来的，它就是在D中有这个word时就用word embedding，没有的时候就用char embedding来学习word embedding，非常简单。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2020-02-17-cs224n-0214-subword-models/p40.png">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2020-02-17-cs224n-0214-subword-models/p41.png">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;/tbody>
&lt;/table>
&lt;p>fasttext就是skipgram+n-gram，一个词的embedding=组成这个词的n-gram的embedding的加权求和，所以fasttext也能解决OOV问题。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2020-02-17-cs224n-0214-subword-models/p54.png">&lt;/p></description></item><item><title>CS224N（1.31）Translation, Seq2Seq, Attention</title><link>https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/</link><pubDate>Fri, 02 Aug 2019 11:50:31 +0800</pubDate><guid>https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/</guid><description>&lt;p>今天介绍另一个NLP任务——机器翻译，以及神经网络机器翻译模型seq2seq和一个改进技巧attention。&lt;/p>
&lt;p>机器翻译最早可追溯至1950s，由于冷战的需要，美国开始研制由俄语到英语的翻译机器。当时的机器翻译很简单，就是自动从词典中把对应的词逐个翻译出来。&lt;/p>
&lt;p>后来在1990s~2010s，统计机器翻译（Statistical Machine Translation, SMT）大行其道。假设源语言是法语\(x\)，目标语言是英语\(y\)，机器翻译的目标就是寻找\(y\)，使得\(P(y|x)\)最大，也就是下图的公式。进一步，通过贝叶斯公式可拆分成两个概率的乘积：其中\(P(y)\)就是之前介绍过的语言模型，最简单的可以用n-gram的方法；\(P(x|y)\)是由目标语言到源语言的翻译模型。为什么要把\(P(y|x)\)的求解变成\(P(x|y)*P(y)\)？逐个击破的意思，\(P(x|y)\)专注于翻译模型，翻译好局部的短语或者单词；而\(P(y)\)就是之前学习的语言模型，用来学习整个句子\(y\)的概率，专注于翻译出来的句子从整体上看起来更加通顺、符合语法与逻辑。所以问题就转化为怎样求解\(P(x|y)\)。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p7.png">&lt;/p>
&lt;p>SMT进一步把\(P(x|y)\)分解成\(P(x,a|y)\)，其中\(a\)表示一个对齐alignment，可以认为是两种语言之间单词和单词或短语和短语的一个对齐关系。如下图所示是一个英语和法语的alignment。对齐本身就很复杂，存在1对1，1对多，多对1，多对多等情况，所以\(P(x,a|y)\)的求解在给定\(y\)的情况下，不但要考虑对齐方案\(a\)的情况\(P(a|y)\)，还需要考虑对齐之后词与词的翻译情况\(P(x|a,y)\)，可能的情况非常多。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p10.png">&lt;/p>
&lt;p>那么，SMT怎样找到\(\arg max_y\)呢？穷举所有情况是不可能的，启发式搜索是可行的。形象描述就是在搜索过程中，对概率较低的路径进行剪枝，只保留概率较大的翻译情况。如下图的搜索树，对于概率较低的路径就不往下搜索了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p18.png">&lt;/p>
&lt;p>总之，统计机器翻译非常复杂，有很多的子模块，需要很多的人工干预和特征工程。&lt;/p>
&lt;hr>
&lt;p>2014年，seq2seq模型横空出世，神经网络机器翻译（Neural Machine Translation, NMT）方兴未艾。seq2seq顾名思义，就是从序列到序列的模型，因为机器翻译的源语言和目标语言都是seq。&lt;/p>
&lt;p>seq2seq的NMT如下图所示，它由两个RNN组成，左边的红色部分称为Encoder RNN，它负责对源语言进行编码（Encode）；右边的绿色部分称为Decoder RNN，它负责对目标语言进行解码（Decode）。首先，Encoder RNN可以是任意一个RNN，比如朴素RNN、LSTM或者GRU。Encoder RNN负责对源语言进行编码，学习源语言的隐含特征。Encoder RNN的最后一个神经元的隐状态作为Decoder RNN的初始隐状态。Decoder RNN是一个条件语言模型，一方面它是一个语言模型，即用来生成目标语言的；另一方面，它的初始隐状态是基于Encoder RNN的输出，所以称Decoder RNN是条件语言模型。Decoder RNN在预测的时候，需要把上一个神经元的输出作为下一个神经元的输入，不断的预测下一个词，直到预测输出了结束标志符&amp;lt;END&amp;gt;，预测结束。Encoder RNN的输入是源语言的word embeding，Decoder RNN的输入是目标语言的word embeding。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p24.png">&lt;/p>
&lt;p>seq2seq是一个很强大的模型，不但可以用来做机器翻译，还可以用来做很多NLP任务，比如自动摘要、对话系统等。&lt;/p>
&lt;p>seq2seq作为一个条件语言模型，形式化来说，它直接对\(P(y|x)\)进行建模，在生成\(y\)的过程中，始终有\(x\)作为条件，正如下图的条件概率所示。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p26.png">&lt;/p>
&lt;p>上面介绍了seq2seq的预测过程，seq2seq的训练过程如下图所示。训练的时候，我们同时需要源语言和翻译好的目标语言，分别作为Encoder RNN和Deocder RNN的输入。对于Encoder RNN没什么好说的。Decoder RNN在训练阶段，每一个时间步的输入是提供的正确翻译词，输出是预测的下一个时间步的词的概率分布，比如在\(t=4\)，预测输出是\(\hat y_4\)，而正确答案是“with”，根据交叉熵损失函数，\(J_4=-\log P(“with”)\)。总的损失函数就是所有时间步的损失均值。&lt;/p>
&lt;p>seq2seq的训练过程是end2end的，即把Encoder RNN和Decoder RNN作为一个整体进行训练，不会像SMT一样有很多的子模块单独训练。当然seq2seq也可以单独对encoder和deconder进行训练优化，再组合，但是这个效果不一定会比整体优化encoder和deconder更好。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p27.png">&lt;/p>
&lt;p>上上张图介绍的seq2seq的预测过程，实际上是一个贪心的预测过程，即在Decoder RNN的每一步都贪心选择\(\hat y_t\)概率最大的那个词。但是贪心只能保证每一步是最优的，无法保证预测出来的句子整体是最优的。特别是如果在\(t\)时刻贪心选择的词不是全局最优，会导致\(t\)时刻往后的所有预测词都是错误的，没有回头路了。但是如果每个时间步都穷举所有可能的情况的话，时间复杂度\(O(V^T)\)又太高了。&lt;/p>
&lt;p>Beam search搜索策略是贪心策略和穷举策略的一个折中方案，它在预测的每一步，都保留Top-k高概率的词，作为下一个时间步的输入。k称为beam size，k越大，得到更好结果的可能性更大，但计算消耗也越大。请注意，这里的Top-k高概率不仅仅指当前时刻的\(\hat y_t\)的最高概率，而是截止目前这条路径上的累计概率之和，如下图的公式所示。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p31.png">&lt;/p>
&lt;p>举例如下，假设\(k=2\)，第一个时间步保留Top-2的词为”he”和”I”，他们分别作为下一个时间步的输入。”he”输入预测输出前两名是”hit”和”struck”，则”hit”这条路的累加概率是”he”的概率加上”hit”的概率=-1.7，类似的可以算出其他几个词对应路径的概率打分。最后在这4条路上保留\(k=2\)条路，所以”hit”和”was”对应路径保留，作为下一个时间步的输入；”struck”和”got”对应路径被剪枝。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p34.png">&lt;/p>
&lt;p>最终的搜索树如下图所示，可以看到在每个时间步都只保留了\(k=2\)个节点往下继续搜索。最后”pie”对应的路径打分最高，通过回溯法得到概率最高的翻译句子。请注意，beam search作为一种剪枝策略，并不能保证得到全局最优解，但它能以较大的概率得到全局最优解，同时相比于穷举搜索极大的提高了搜索效率。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p44.png">&lt;/p>
&lt;p>在beam search的过程中，不同路径预测输出结束标志符&amp;lt;END&amp;gt;的时间点可能不一样，有些路径可能提前结束了，称为完全路径，暂时把这些完全路径放一边，其他路径接着beam search。beam search的停止条件有很多种，可以设置一个最大的搜索时间步数，也可以设置收集到的最多的完全路径数。&lt;/p>
&lt;p>当beam search结束时，需要从n条完全路径中选一个打分最高的路径作为最终结果。由于不同路径的长度不一样，而beam search打分是累加项，累加越多打分越低，所以需要用长度对打分进行归一化，如下图所示。那么，为什么不在beam search的过程中就直接用下面的归一化打分来比较呢？因为在树搜索的过程中，每一时刻比较的两条路径的长度是一样的，即分母是一样的，所以归一化打分和非归一化打分的大小关系是一样的，即在beam search的过程中就没必要对打分进行归一化了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p46.png">&lt;/p></description></item></channel></rss>