介绍
elmo是用于解决静态词向量无法一词多义的模型。
在介绍如何实现elmo模型的时候,此处穿插进来Conv1d layer(一维卷积层)
。
本文代码以plm-nlp-code chp6为准,可直接参考。
Conv1d
介绍
卷积有Conv1d(序列),Conv2d(图像),Conv3d(立体数据),主要区别在于不同方向上进行卷积。因为文字是一维结构的,从而在nlp领域使用Conv1d。
一维卷积适合在句子对于时序结构上体现不重要的方面有更加的优势。比如一句话中关键词位置的变动不影响句子的语义。
但是对时序结构通常效果并不好,因为时间序列通常不满足平移不变的假设。
此处不过多介绍关于Conv1d的原理,感兴趣可看一维卷积tensorflow2版本的Conv1D以及Pytroch的nn.Conv1d用法。
此处只关心input和output,卷积核和padding等。
假设o
为上一层的hidden_size输出维度,kernel_size
为卷积核大小,padding
为使用边界填充,stride
为步长,那么:
卷积后的维度: (o
- kernel_size
+ 2 * padding
) / stride
+ 1
池化后的维度: (o
- kernel_size
) / stride
+ 1
示例
torch中的输入为(batch_size, hidden_size, seq_length)。
- 步长为1,不使用padding
1
2
3
4
5
6
7
8m = nn.Conv1d(16, 33, 3, stride=1)
input = torch.randn(20, 16, 50)
output = m(input)
print(output.shape)
torch.Size([20, 33, 48])
(50 - 3)/ 1 + 1 = 48 - 步长为2,不使用padding
1
2
3
4
5
6
7
8m = nn.Conv1d(16, 33, 3, stride=2)
input = torch.randn(20, 16, 50)
output = m(input)
print(output.shape)
torch.Size([20, 33, 24])
(50 - 3)/ 2 + 1 = 24 - 步长为1,使用padding
1
2
3
4
5
6
7
8m = nn.Conv1d(16, 33, 3, stride=1,padding=True)
input = torch.randn(20, 16, 50)
output = m(input)
print(output.shape)
torch.Size([20, 33, 50])
(50 - 3 + 2)/ 1 + 1 = 50 - 步长为2,使用padding
1
2
3
4
5
6
7
8
9m = nn.Conv1d(16, 33, 3, stride=2, padding=True)
input = torch.randn(20, 16, 50)
output = m(input)
print(output.shape)
torch.Size([20, 33, 25])
(50 - 3 + 2)/ 2 + 1 = 25
Elmo过程
数据集
构建语料
这地方主要分成两部分:
- 构建词和字级别的词典
- 通过词
1 | def load_corpus(path, max_tok_len=None, max_seq_len=None): |
构建训练数据集
1 | class BiLMDataset(Dataset): |
模型结构
1 | BiLM( |
这个网络结构和ELMoForManyLangs基本一样,除了没有使用lstm。
1 | Model( |
HighWay
这个讲解可以看Highway net。但是这个给我的直观感觉有点类似lstm的门控制机制,用于遗忘与记忆。
ConvTokenEmbedding
1 | class ConvTokenEmbedder(nn.Module): |
这个地方比较有意思,使用不同大小的kernel_size来捕捉上下文信息,这地方没啥可说的。
主要在concat后的操作,比如说不同的kernel_size后的concat到一起后,它到底代表什么含义呢?比如我可以降维到2,表示分类。
后面它用了一个linear以及view重新获得了seq_len每一个token的hidden_size。这地方骚气。
训练过程
太懒了,目前先跳过。。。
简单来说就是使用不同大小的kernel_size的卷积核来捕捉上下文信息,注意是char level的。随后经过一个前向的lstm和一个后向的lstm捕捉不同方向的语义信息。那么从整体网络看来,不同层代表的含义也就比较清晰明了,token embedding更倾向是词法等信息,往后就更倾向深层次的信息,比如语义。那么从使用角度上一是可以按需使用自己的层,二是可以给予不同层不同的权重。
总结
感觉看完了没有很清晰明了的感觉,有可能是中间涉及到太多的转换,elmo解决一词多义的原理可能更多是使用lstm这种以及用char level token来拟合词。
另外elmo不同layer代表的含义不一样,这个在SIFRank中有不同的用法,后续可以关注。