漫谈ELMo

词嵌入在很长一段时间内对NLP领域有很大的推动的作用,以至于embedding已经成为NLP任务中的一个标准操作。而研究人员却发现,词嵌入虽好,但弊端也明显,于是乎,语言模型就上位了……如果说Word2Vec是NLP领域里面的一个重要里程碑,那么ELMo的诞生应该可以算得上另外一个里程碑了吧!最近我正是在学习ELMo,因此想整理一下跟大家探讨。

词嵌入到底出了什么问题?

词嵌入(如想详细了解可戳这里)操作在NLP中已经成为常规操作,它也使得词义可以通过稠密向量来表达。但人们开始发现这种操作虽然好,但是仍然有一些问题:

  1. 集外词怎么办?统一使用UNK作为oov的token并不能区分各个集外词的词义。
  2. 多义词怎么办?每个词只对应一个向量,对于多语义或者有多层意思的词语而言这样是不够的。

第一个问题中,人们尝试使用字符级的嵌入操作来代替,毕竟字符是可以有限数量的。但第二个问题就是词嵌入的天然硬伤了,因为词语无论出现在什么场景,都只有一个向量作为表示,举个例子说,spark这个词出现在日常对话里面一般都是表示“星火”,但如果出现在一些数据科学技术文章中则可能表示spark这个并行计算平台。

这个问题归根结底就是,词向量只在训练过程考虑了训练语料上下文赋予其语义,而在使用过程没有考虑它在特定的语义环境中到底表达何种意思,所以预训练的词向量是“上下文独立”(context-independent)的。

那么我们在使用词向量时进行fine-tune可以解决这个问题吗?很遗憾,也是不行的,人类语言实在太复杂了,有时不仅仅像上面举例的spark这种领域区别问题,更有可能是表达程度等方面的问题。

所以,我们必须赋予词嵌入一些更大的弹性,即让每个词在特定的词义环境中表达出它该有的意思,也即我们追求的是获得“特定上下文”(context-specific)的表征。

ELMo诞生的前夕

等等,还记得语言模型吗?语言模型每个时间步输出的表征ht=f(xt1,ht1)h_t = f(x_{t-1},h_{t-1})不就正是综合上下文的表征吗?!于是乎,有人就拍案而起:就是你了!

TagLM大概是第一个使用这种想法的模型,咱们直接上图

图一 TagLM模型

咱们先看图的右手边。右手边是一个bi-RNN实现的语言模型(我又要打广告了!如果不了解语言模型请戳这里,不了解RNN请戳这里),即对前向RNN是用前文[x1,x2,...,xt1][x_1,x_2,...,x_{t-1}]预测xtx_{t},而后向RNN用后文[xt+1,xt+2,...,xT][x_{t+1},x_{t+2},...,x_T]预测xtx_{t},每个词xtx_t进入这个LM都会得到一个hidden state,咱们把它标记为htLM=[htforward;htbackward]h_{t}^{LM} = [h_{t}^{forward};h_{t}^{backward}]。这一部分是预训练的,跟咱们使用Word2Vec预训练词向量一个道理。

再看左图下方。每个输入词都会同时经过两个嵌入操作,分别是char-level和word-level的嵌入,两个嵌入操作得到的向量会拼接起来,作为网络主体bi-RNN的input。

然后看左图中部。词向量通过一个L层的bi-RNN得到每个time step的hidden state,咱们标记为ht,lh_{t,l}l表示所在的层标,t则表示输入序列的时间步。以两层的bi-RNN为例,第一层输出的hidden state会拼接上预训练的bi-LM得到的双向隐藏特征,再进入下一层,最后用以NER等序列标注任务。

简单来说,tagLM对之前单纯使用预训练词向量的NLP任务做了简单的优化,就是加上LM embedding,因此同时考虑了上下文独立特征特定上下文特征

ELMo的降临

受到TagLM的启发,ELMo就应运而生,它的降临将彻底改变之前的局面:词嵌入不再是一个向量查找表,而是一个function!换句话说,以前咱们可以根据单个词的index查找出对应的词向量,但现在不一样了,我们需要把整句话放进这个函数里面,我们才能获得每个词的表征向量。而且以前同一个index查找出来的词向量肯定是相同的,但现在每个词在任何不同语境下都会有不同的表征。

老规矩先上图

图二 ELMo模型

这个大概是我能找到的最棒的动图了。ELMo是在TagLM的基础上进行了若干的优化。

  1. 与TagLM只采用顶层输出不同,ELMo的representation使用了bi-LSTM所有层的hidden state的以及token的表征。以L=2为例,bi-LM输出的表征为
    Rk={xkLM,hk,jforward,hk,jbackwardj=1,2}={hk,jLMj=0,1,2} \begin{aligned} R_k &=\{x_k^{LM},h_{k,j}^{forward},h_{k,j}^{backward}|j=1,2\}\\ &=\{h_{k,j}^{LM}|j=0,1,2\} \end{aligned}
    当j=0时表示输入token的表征,这一部分大体跟TagLM是一样的。原论文作者指出,低层表征容易捕获语法信息,语法信息在序列标注如POS、NER等任务中作用比较大,而高层表征却更好地捕获语义信息,在机器翻译、智能问答等任务里面比较有用。为了综合语义和语法信息,最好的办法就是将所有信息汇总起来。

  2. 各表征的汇总方式是
    ELMoktask=γtaskj=0Lsjtaskhk,jLM ELMo_{k}^{task} = {\gamma}^{task}\sum_{j=0}^{L}s_{j}^{task}h_{k,j}^{LM}
    其中γtask\gamma^{task}是一个可训练单值参数,用于对特定任务场景下对表征向量的调整;sjtasks_{j}^{task}是softmax正则化的参数。(原文没有细说,经过查证,这个应该是一个用作加权平均各层表征的向量,即sR1×Ls{\in}R^{1{\times}L},只是各层权重归一化的方法是使用softmax)

    累加号要成立的话,那么意味着各层的output都必须是相同维度的,对LSTM来说这个还是比较好操作的东西,毕竟输出维度(即units)多少都是可以设定的,但是要求输入token表征也是一样的维度。原论文说每层units都是4096,但都会映射到512进行skip-connection(按原论文的描述,第一层与第二层的映射output应该相加,这个结构在上图中没有画出来)

  3. 最后一点,ELMo只采用char-level的特征,就是每个词采用多个kernel size一共2048个卷积核/层的CNN获得字符序列特征,默认设置是两层,这个CNN也使用了skip-connection,并同样映射到512维,而且模型参数在每个时间步是共享的。

至此,ELMo的细节应该都抠完了。

ELMo咋用?

ELMo其实就是多层bi-LM,所以它需要在大规模unlabel语料中进行预训练。在具体的NLP任务中,预训练好的ELMo相当于一个函数,将输入句子中每个词映射成对应的实数向量,然后再将其feed到后续的模型里面。

在具体NLP任务中,最简单的做法是将ELMo的模型参数固定住,这样梯度信号并不会backprop过来,整个ELMo唯一受影响的就是γ\gamma参数和ss参数。但更通行的,也是效果更好的做法,就是在具体的NLP任务中做transfer learning,即对预训练模型做fine-tune,就像在各种CV任务中一样。

所以说ELMo开启了NLP的新历程,因为从这之后,NLP任务也可以像CV任务一样,选择一个合适的back bone,再针对自己需要解决任务去设计模型。

参考资料

  • https://www.analyticsvidhya.com/blog/2019/03/learn-to-use-elmo-to-extract-features-from-text/
  • https://arxiv.org/pdf/1802.05365.pdf
  • https://blog.csdn.net/sinat_29819401/article/details/90669304
  • https://blog.csdn.net/sinat_29819401/article/details/94015798
  • https://blog.csdn.net/sinat_29819401/article/details/94176245
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章