词嵌入在很长一段时间内对NLP领域有很大的推动的作用,以至于embedding已经成为NLP任务中的一个标准操作。而研究人员却发现,词嵌入虽好,但弊端也明显,于是乎,语言模型就上位了……如果说Word2Vec是NLP领域里面的一个重要里程碑,那么ELMo的诞生应该可以算得上另外一个里程碑了吧!最近我正是在学习ELMo,因此想整理一下跟大家探讨。
词嵌入到底出了什么问题?
词嵌入(如想详细了解可戳这里)操作在NLP中已经成为常规操作,它也使得词义可以通过稠密向量来表达。但人们开始发现这种操作虽然好,但是仍然有一些问题:
- 集外词怎么办?统一使用UNK作为oov的token并不能区分各个集外词的词义。
- 多义词怎么办?每个词只对应一个向量,对于多语义或者有多层意思的词语而言这样是不够的。
第一个问题中,人们尝试使用字符级的嵌入操作来代替,毕竟字符是可以有限数量的。但第二个问题就是词嵌入的天然硬伤了,因为词语无论出现在什么场景,都只有一个向量作为表示,举个例子说,spark
这个词出现在日常对话里面一般都是表示“星火”,但如果出现在一些数据科学技术文章中则可能表示spark这个并行计算平台。
这个问题归根结底就是,词向量只在训练过程考虑了训练语料上下文赋予其语义,而在使用过程没有考虑它在特定的语义环境中到底表达何种意思,所以预训练的词向量是“上下文独立”(context-independent)的。
那么我们在使用词向量时进行fine-tune可以解决这个问题吗?很遗憾,也是不行的,人类语言实在太复杂了,有时不仅仅像上面举例的spark
这种领域区别问题,更有可能是表达程度等方面的问题。
所以,我们必须赋予词嵌入一些更大的弹性,即让每个词在特定的词义环境中表达出它该有的意思,也即我们追求的是获得“特定上下文”(context-specific)的表征。
ELMo诞生的前夕
等等,还记得语言模型吗?语言模型每个时间步输出的表征不就正是综合上下文的表征吗?!于是乎,有人就拍案而起:就是你了!
TagLM大概是第一个使用这种想法的模型,咱们直接上图
图一 TagLM模型
咱们先看图的右手边。右手边是一个bi-RNN实现的语言模型(我又要打广告了!如果不了解语言模型请戳这里,不了解RNN请戳这里),即对前向RNN是用前文预测,而后向RNN用后文预测,每个词进入这个LM都会得到一个hidden state,咱们把它标记为。这一部分是预训练的,跟咱们使用Word2Vec预训练词向量一个道理。
再看左图下方。每个输入词都会同时经过两个嵌入操作,分别是char-level和word-level的嵌入,两个嵌入操作得到的向量会拼接起来,作为网络主体bi-RNN的input。
然后看左图中部。词向量通过一个L层的bi-RNN得到每个time step的hidden state,咱们标记为,l
表示所在的层标,t
则表示输入序列的时间步。以两层的bi-RNN为例,第一层输出的hidden state会拼接上预训练的bi-LM得到的双向隐藏特征,再进入下一层,最后用以NER等序列标注任务。
简单来说,tagLM对之前单纯使用预训练词向量的NLP任务做了简单的优化,就是加上LM embedding,因此同时考虑了上下文独立特征和特定上下文特征。
ELMo的降临
受到TagLM的启发,ELMo就应运而生,它的降临将彻底改变之前的局面:词嵌入不再是一个向量查找表,而是一个function!换句话说,以前咱们可以根据单个词的index查找出对应的词向量,但现在不一样了,我们需要把整句话放进这个函数里面,我们才能获得每个词的表征向量。而且以前同一个index查找出来的词向量肯定是相同的,但现在每个词在任何不同语境下都会有不同的表征。
老规矩先上图
图二 ELMo模型
这个大概是我能找到的最棒的动图了。ELMo是在TagLM的基础上进行了若干的优化。
-
与TagLM只采用顶层输出不同,ELMo的representation使用了bi-LSTM所有层的hidden state的以及token的表征。以L=2为例,bi-LM输出的表征为
当j=0时表示输入token的表征,这一部分大体跟TagLM是一样的。原论文作者指出,低层表征容易捕获语法信息,语法信息在序列标注如POS、NER等任务中作用比较大,而高层表征却更好地捕获语义信息,在机器翻译、智能问答等任务里面比较有用。为了综合语义和语法信息,最好的办法就是将所有信息汇总起来。 -
各表征的汇总方式是
其中是一个可训练单值参数,用于对特定任务场景下对表征向量的调整;是softmax正则化的参数。(原文没有细说,经过查证,这个应该是一个用作加权平均各层表征的向量,即,只是各层权重归一化的方法是使用softmax)
累加号要成立的话,那么意味着各层的output都必须是相同维度的,对LSTM来说这个还是比较好操作的东西,毕竟输出维度(即units)多少都是可以设定的,但是要求输入token表征也是一样的维度。原论文说每层units都是4096,但都会映射到512进行skip-connection(按原论文的描述,第一层与第二层的映射output应该相加,这个结构在上图中没有画出来)。 -
最后一点,ELMo只采用char-level的特征,就是每个词采用多个kernel size一共2048个卷积核/层的CNN获得字符序列特征,默认设置是两层,这个CNN也使用了skip-connection,并同样映射到512维,而且模型参数在每个时间步是共享的。
至此,ELMo的细节应该都抠完了。
ELMo咋用?
ELMo其实就是多层bi-LM,所以它需要在大规模unlabel语料中进行预训练。在具体的NLP任务中,预训练好的ELMo相当于一个函数,将输入句子中每个词映射成对应的实数向量,然后再将其feed到后续的模型里面。
在具体NLP任务中,最简单的做法是将ELMo的模型参数固定住,这样梯度信号并不会backprop过来,整个ELMo唯一受影响的就是参数和参数。但更通行的,也是效果更好的做法,就是在具体的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