ELMo的概念也是很早就出了,应该是18年初的事情了。但我仍然是后知后觉,居然还是等BERT出来很久之后,才知道有这么个东西。这两天才仔细看了下论文和源码,在这里做一些记录,如果有不详实的地方,欢迎指出~

文章目录
前言
一. ELMo原理
1. ELMo整体模型结构
2. 字符编码层
3. biLMs原理
4. 生成ELMo词向量
5. 结合下游NLP任务
二. PyTorch实现
1. 字符编码层
2. biLMs层
3. 生成ELMo词向量
三. 实验
四. 一些分析
1. 使用哪些层的输出?
2. 在哪里加入ELMo?
3. 每层输出的侧重点是什么?
4. 效率分析
五. 总结
传送门
前言

前言
ELMo出自Allen研究所在NAACL2018会议上发表的一篇论文《Deep contextualized word representations》,从论文名称看,应该是提出了一个新的词表征的方法。据他们自己的介绍:ELMo是一个深度带上下文的词表征模型,能同时建模(1)单词使用的复杂特征(例如,语法和语义);(2)这些特征在上下文中会有何变化(如歧义等)。这些词向量从深度双向语言模型(biLM)的隐层状态中衍生出来,biLM是在大规模的语料上面Pretrain的。它们可以灵活轻松地加入到现有的模型中,并且能在很多NLP任务中显著提升现有的表现,比如问答、文本蕴含和情感分析等。听起来非常的exciting,它的原理也十分reasonable!下面就将针对论文及其PyTorch源码进行剖析,具体的资料参见文末的传送门。

这里先声明一点:笔者认为“ELMo”这个名称既可以代表得到词向量的模型,也可以是得出的词向量本身,就像Word2Vec、GloVe这些名称一样,都是可以代表两个含义的。下面提到ELMo时,一般带有“模型”相关字眼的就是指的训练出词向量的模型,而带有“词向量”相关字眼的就是指的得出的词向量。

一. ELMo原理
之前我们一般比较常用的词嵌入的方法是诸如Word2Vec和GloVe这种,但这些词嵌入的训练方式一般都是上下文无关的,并且对于同一个词,不管它处于什么样的语境,它的词向量都是一样的,这样对于那些有歧义的词非常不友好。因此,论文就考虑到了要根据输入的句子作为上下文,来具体计算每个词的表征,提出了ELMo(Embeddings from Language Model)。它的基本思想,用大白话来说就是,还是用训练语言模型的套路,然后把语言模型中间隐含层的输出提取出来,作为这个词在当前上下文情境下的表征,简单但很有用!

1. ELMo整体模型结构
对于ELMo的模型结构,其实论文中并没有给出具体的图(这点对于笔者这种想象力极差的人来说很痛苦),笔者通过整合论文里面的蛛丝马迹以及PyTorch的源码,得出它大概是下面这么个东西(手残党画的丑,勿怪):

 

5. 结合下游NLP任务
一般ELMo模型会在一个超大的语料库上进行预训练,因为是训练语言模型,不需要任何的标签,纯文本就可以,因而这里可以用超大的语料库,这一点的优势是十分明显的。训练完ELMo模型之后,就可以输入一个新句子,得到其中每个单词在当前这个句子上下文下的ELMo词向量了。

论文中提到,在训练的时候,发现使用合适的dropout和L2在ELMo模型上时会提升效果。

此时这个词向量就可以接入到下游的NLP任务中,比如问答、情感分析等。从接入的位置来看,可以与下游NLP任务本身输入的embedding拼接在一起,也可以与其输出拼接在一起。而从模型是否固定来看,又可以将ELMo词向量预先全部提取出来,即固定ELMo模型不让其训练,也可以在训练下游NLP任务时顺带fine-tune这个ELMo模型。总之,使用起来非常的方便,可以插入到任何想插入的地方进行增补。

二. PyTorch实现
这里参考的主要是allennlp里面与ELMo本身有关的部分,涉及到biLMs的模型实现,以及ELMo推理部分,会只列出核心的部分,细枝末节的代码就不列举了。至于如何与下游的NLP任务结合以及fine-tune,还需要读者自己去探索和实践,这里不做说明!

1. 字符编码层
这里实现的就是前面提到的Char Encode Layer。

首先是multi-scale CNN的实现:

# multi-scale CNN# 网络定义
for i, (width, num) in enumerate(filters):conv = torch.nn.Conv1d(in_channels=char_embed_dim,out_channels=num,kernel_size=width,bias=True)self.add_module('char_conv_{}'.format(i), conv)# forward函数
def forward(sef, character_embedding)convs = []for i in range(len(self._convolutions)):conv = getattr(self, 'char_conv_{}'.format(i))convolved = conv(character_embedding)# (batch_size * sequence_length, n_filters for this width)convolved, _ = torch.max(convolved, dim=-1)convolved = activation(convolved)convs.append(convolved)# (batch_size * sequence_length, n_filters)token_embedding = torch.cat(convs, dim=-1)return token_embedding

然后是highway的实现:

# HighWay# 网络定义
self._layers = torch.nn.ModuleList([torch.nn.Linear(input_dim, input_dim * 2)for _ in range(num_layers)])# forward函数
def forward(self, inputs):current_input = inputsfor layer in self._layers:projected_input = layer(current_input)linear_part = current_input# NOTE: if you modify this, think about whether you should modify the initialization# above, too.nonlinear_part, gate = projected_input.chunk(2, dim=-1)nonlinear_part = self._activation(nonlinear_part)gate = torch.sigmoid(gate)current_input = gate * linear_part + (1 - gate) * nonlinear_partreturn current_input

2. biLMs层

这部分实际上是两个不同方向的BiLSTM训练,然后输出经过映射后直接进行拼接即可,代码如下:(以单向单层的为例)

# 网络定义
# input_size:输入embedding的维度
# hidden_size:输入和输出hidden state的维度
# cell_size:LSTMCell的内部维度。
# 一般input_size = hidden_size = D, hidden_size即为h。
self.input_linearity = torch.nn.Linear(input_size, 4 * cell_size, bias=False)
self.state_linearity = torch.nn.Linear(hidden_size, 4 * cell_size, bias=True)
self.state_projection = torch.nn.Linear(cell_size, hidden_size, bias=False)  # forward函数
def forward(self, inputs, batch_lengths, initial_state):for timestep in range(total_timesteps):# Do the projections for all the gates all at once.# Both have shape (batch_size, 4 * cell_size)projected_input = self.input_linearity(timestep_input)projected_state = self.state_linearity(previous_state)# Main LSTM equations using relevant chunks of the big linear# projections of the hidden state and inputs.input_gate = torch.sigmoid(projected_input[:, (0 * self.cell_size):(1 * self.cell_size)] +projected_state[:, (0 * self.cell_size):(1 * self.cell_size)])forget_gate = torch.sigmoid(projected_input[:, (1 * self.cell_size):(2 * self.cell_size)] +projected_state[:, (1 * self.cell_size):(2 * self.cell_size)])memory_init = torch.tanh(projected_input[:, (2 * self.cell_size):(3 * self.cell_size)] +projected_state[:, (2 * self.cell_size):(3 * self.cell_size)])output_gate = torch.sigmoid(projected_input[:, (3 * self.cell_size):(4 * self.cell_size)] +projected_state[:, (3 * self.cell_size):(4 * self.cell_size)])memory = input_gate * memory_init + forget_gate * previous_memory# shape (current_length_index, cell_size)pre_projection_timestep_output = output_gate * torch.tanh(memory)# shape (current_length_index, hidden_size)timestep_output = self.state_projection(pre_projection_timestep_output)output_accumulator[0:current_length_index + 1, index] = timestep_output# Mimic the pytorch API by returning state in the following shape:# (num_layers * num_directions, batch_size, ...). As this# LSTM cell cannot be stacked, the first dimension here is just 1.final_state = (full_batch_previous_state.unsqueeze(0),full_batch_previous_memory.unsqueeze(0))return output_accumulator, final_state

3. 生成ELMo词向量

这部分即为Scalar Mixer,其代码如下:

# 参数定义
self.scalar_parameters = ParameterList([Parameter(torch.FloatTensor([initial_scalar_parameters[i]]),requires_grad=trainable) for iin range(mixture_size)])
self.gamma = Parameter(torch.FloatTensor([1.0]), requires_grad=trainable)# forward函数
def forward(tensors, mask):def _do_layer_norm(tensor, broadcast_mask, num_elements_not_masked):tensor_masked = tensor * broadcast_maskmean = torch.sum(tensor_masked) / num_elements_not_maskedvariance = torch.sum(((tensor_masked - mean) * broadcast_mask)**2) / num_elements_not_maskedreturn (tensor - mean) / torch.sqrt(variance + 1E-12)normed_weights = torch.nn.functional.softmax(torch.cat([parameter for parameterin self.scalar_parameters]), dim=0)normed_weights = torch.split(normed_weights, split_size_or_sections=1)if not self.do_layer_norm:pieces = []for weight, tensor in zip(normed_weights, tensors):pieces.append(weight * tensor)return self.gamma * sum(pieces)else:mask_float = mask.float()broadcast_mask = mask_float.unsqueeze(-1)input_dim = tensors[0].size(-1)num_elements_not_masked = torch.sum(mask_float) * input_dimpieces = []for weight, tensor in zip(normed_weights, tensors):pieces.append(weight * _do_layer_norm(tensor,broadcast_mask, num_elements_not_masked))return self.gamma * sum(pieces)

三. 实验

这里主要列举一些在实际下游任务上结合ELMo的表现,分别是SQuAD(问答任务)、SNLI(文本蕴含)、SRL(语义角色标注)、Coref(共指消解)、NER(命名实体识别)以及SST-5(情感分析任务),其结果如下:

可见,基本都是在一个较低的baseline的情况下,用了ELMo后,达到了超越之前SoTA的效果!

四. 一些分析
论文中,作者也做了一些有趣的分析,从各个角度窥探ELMo的优势和特性。比如:

1. 使用哪些层的输出?
作者探索了使用不同biLMs层带来的效果,以及使用不同的L2范数的权重,如下表所示:

这里面的Last Only指的是只是用biLM最顶层的输出,λ \lambdaλ 指的是L2范数的权重,可见使用所有层的效果普遍比较好,并且较低的L2范数效果也较好,因其让每一层的表示都趋于不同,当L2范数的权重较大时,会让模型所有层的参数值趋于一致,导致模型每层的输出也会趋于一致。

2. 在哪里加入ELMo?
前面提到过,可以在输入和输出的时候加入ELMo向量,作者比较了这两者的不同:

在问答和文本蕴含任务上,是同时在输入和输出加入ELMo的效果较好,而在语义角色标注任务上,则是只在输入加入比较好。论文猜测这个原因可能是因为,在前两个任务上,都需要用到attention,而在输出的时候加入ELMo,能让attention直接看到ELMo的输出,会对整个任务有利。而在语义角色标注上,与任务相关的上下文表征要比biLMs的通用输出更重要一些。

3. 每层输出的侧重点是什么?
论文通过实验得出,在biLMs的低层,表征更侧重于诸如词性等这种语法特征,而在高层的表征则更侧重于语义特征。比如下面的实验结果:

左边的任务是语义消歧,右边的任务是词性标注,可见在语义消歧任务上面,使用第二层的效果比第一层的要好;而在词性标注任务上面,使用第一层的效果反而比使用第二层的效果要好。

总体来看,还是使用所有层输出的效果会更好,具体的weight让模型自己去学就好了。

4. 效率分析
一般而言,用了预训练模型的网络往往收敛的会更快,同时也可以使用更少的数据集。论文通过实验验证了这一点:

比如在SRL任务中,使用了ELMo的模型仅使用1%的数据集就能达到不使用ELMo模型在使用10%数据集的效果!

五. 总结
ELMo具有如下的优良特性:

上下文相关:每个单词的表示取决于使用它的整个上下文。
深度:单词表示组合了深度预训练神经网络的所有层。
基于字符:ELMo表示纯粹基于字符,然后经过CharCNN之后再作为词的表示,解决了OOV问题,而且输入的词表也很小。
资源丰富:有完整的源码、预训练模型、参数以及详尽的调用方式和例子,又是一个造福伸手党的好项目!而且:还有人专门实现了多语的,好像是哈工大搞的,戳这里看项目。
传送门
论文:https://arxiv.org/pdf/1802.05365.pdf
项目首页:https://allennlp.org/elmo
源码:https://github.com/allenai/allennlp (PyTorch,关于ELMo的部分戳这里)
https://github.com/allenai/bilm-tf (TensorFlow)
多语言:https://github.com/HIT-SCIR/ELMoForManyLangs (哈工大CoNLL评测的多国语言ELMo,还有繁体中文的)

ELMo解读(论文 + PyTorch源码)相关推荐

  1. Transformer-XL解读(论文 + PyTorch源码)

    前言 目前在NLP领域中,处理语言建模问题有两种最先进的架构:RNN和Transformer.RNN按照序列顺序逐个学习输入的单词或字符之间的关系,而Transformer则接收一整段序列,然后使用s ...

  2. XLM解读(论文 + PyTorch源码)

    这篇论文是Facebook在BERT的基础上发展出来的Cross-Lingual版本,即多语的.BERT的github上实际上也有一个多语版本的,但却没有提到是怎么训练的,也没有任何的信息.这里的XL ...

  3. 小白学习pytorch源码(二):setup.py最详细解读

    小白学习pytorch源码(二) pytorch setup.py最全解析 setup.py与setuptools setup.py最详细解读 setup.py 环境检查 setup.py setup ...

  4. PyTorch 源码解读之 torch.utils.data:解析数据处理全流程

    目录 0 前言 1 Dataset 1.1 Map-style dataset 1.2 Iterable-style dataset 1.3 其他 dataset 2 Sampler 3 DataLo ...

  5. pytorch源码解析2——数据处理torch.utils.data

    迭代器 理解 Python 的迭代器是解读 PyTorch 中 torch.utils.data 模块的关键. 在 Dataset, Sampler 和 DataLoader 这三个类中都会用到 py ...

  6. pytorch 测试每一类_DeepFM全方面解析(附pytorch源码)

    写在前面 最近看了DeepFM这个模型.把我学习的思路和总结放上来给大家和未来的自己做个参考和借鉴.文章主要希望能串起学习DeepFM的各个环节,梳理整个学习思路.以"我"的角度浅 ...

  7. 基于深度学习的文本分类6大算法-原理、结构、论文、源码打包分享

    导读:文本分类是NLP领域一项基础工作,在工业界拥有大量且丰富的应用场景.传统的文本分类需要依赖很多词法.句法相关的human-extracted feature,自2012年深度学习技术快速发展之后 ...

  8. 多智能体系统——竞争网络下异构多智能体系统的分组一致性问题 Group consensus of heterogeneous multi-agent system (附论文链接+源码Matlab)

    多智能体系统--竞争网络下异构多智能体系统的分组一致性问题 (附论文链接+源码Matlab) Yu F, Ji L, Yang S. Group consensus for a class of he ...

  9. 基于北斗导航定位系统的设计与实现(论文+程序设计源码+数据库文件)

    摘 要 改革开放新时代的到来,人们的生活发生了翻天覆地的变化,人们的娱乐方式变得更加丰富多彩,人们的活动圈子在不断的扩大,不仅仅局限于眼前的苟且,更向往着诗与远方,人们的活动圈子的扩大就意味着在特殊时 ...

最新文章

  1. linux检查是否安装proc编译器,编译安装 GCC 4.9并验证使用
  2. 疫情影响海外净利润?海尔智家的回答出人意料
  3. boost::stable_vector简单解析
  4. openglshader实现虚拟场景_虚拟演播室设计原则
  5. mysql8安装版安装教程_MySQL8.0版本安装教程
  6. 使用iToolab UnlockGo for Mac如何绕过屏幕时间密码
  7. 内存少导致编译出错,内存增加到24G
  8. css3 animation 位置移动了怎么不占位置_手把手整理CSS3知识汇总【思维导图】
  9. windows制作黑苹果双系统
  10. charles4.0破解和手机抓包
  11. angular中uibpagination里面参数配置
  12. CEOI2017 D1T3 mousetrap 树形dp+二分答案
  13. 商品库存清单案例java_JAVA实现简单的商城库存清单
  14. MySOL(狂殴26K字只为博君一赞)
  15. webpack项目运用(一)打包压缩css文件
  16. MCS-51单片机结构学习总结
  17. iOS报错:108 duplicate symbols for architecture arm64
  18. 从头开始做一个智能家居设备:MQTT协议及使用
  19. 蓝牙室内定位方案-灵思科
  20. 计算机启动显示不正确的分区表,每次开机提示invalid partition table怎么解决?

热门文章

  1. 【golang程序包推荐分享】分享亿点点golang json操作及myJsonMarshal程序包开发的踩坑经历 :)
  2. 人要懂得放下已经发生,却又无法改变的事情
  3. 【Linux系统】基础总结
  4. Python第三方包的egg info 是什么东西
  5. 网路摄像头技术参数介绍
  6. NVIDIA 自动驾驶软件平台
  7. 自动调度GPU的卷积层
  8. 提示和技巧:光线跟踪最佳实践
  9. 2021年大数据Flink(三十七):​​​​​​​Table与SQL ​​​​​​案例四
  10. [JavaScript] JavaScript 值类型使用:与数组有关的API