其实看到这个题目会感觉很奇怪,因为bert中本来就有MultiheadAttention机制,那为何还要加入呢?其实是介绍一种水论文(大胆!!!)的方法,之前看到一篇更离谱的论文,论文的模型为(rnn-lstm架构)。所以,一个普通的模型,加入MultiheadAttention,然后在搞点其他的小动作,也许某些中文核心就够了。
本文的框架是bert-bilstm-softmax做ner任务(别问为什么不用crf,就是任性)。下面展示本模型的model
class BertBilstm(nn.Module):
def __init__(self,
output_size,
embed_size,
num_layers,
hidden_size,
drop_prob,
pretrained_path):
super(BertBilstm, self).__init__()
self.output_size = output_size
# 加载Bert预训练模型
self.bert = AutoModel.from_pretrained(pretrained_path)
先加载了一个预训练模型,这里的预训练模型输出的肯定是个tuple(之前犯了错,想看看究竟是什么东西,于是就size了一下发现出错,原来是tuple的原因),这里只需要知道预训练bert模型的输出的第一个列表也就是embed_x = self.bert(x)[0]是词向量,这里的词向量的维度是不能超过768的,因为这个模型最大就是768维的词向量,如果用更好的预训练模型比如roberta之类的会到1024对应模型的参数就会越多,计算时间也就越长。预训练模型介绍完毕。
第二个就该到bilstm了,这个太普通了,但是如果找细节还是会发现很多问题。
# 定义BiLSTM层
self.bilstm = nn.LSTM(bidirectional=True,
num_layers=num_layers,
input_size=embed_size,
hidden_size=hidden_size,
batch_first=True,
dropout=drop_prob)
这里的参数网上介绍有很多,bidirectional=true的意思是双向lstm也就是bilstm,num_layers就是层数(这里设定为1),input_size就是输入到bilstm的词向量维度,之前bert输出的是768,那这个地方出入就是768(车间流水线的感觉),hidden_size就是输出的维度(可以这么理解),因为是bilstm就相当于来回,那么输出的就是2*hidden_size,我这里hidden_size设定为256,那么从bilstm输出的维度就是512了。
这里放一张偷过来的解释图(其实这个是便于理解的图),x(t)为当前时许的输入(这里网络上经常有人加入这里是一个词或者一个字用10维度的词向量表示),和前一时刻的输出concat为一个向量,经过4个参数矩阵变化形成输出,这里的c(t)是长时记忆(看图也看的出来),h(t)为短时记忆,会输出结果。里面的计算过程都很详细。
bilstm的输出结果是output,(c_n,h_n) ,原文档对于输出的解释是output是最后一层每个时序的h(t)的拼接,而(c_n,h_n)是最后一个时序的输出。
一般不经常用(h_n,c_n),只会用到out_put也就是代码中的lstmout。这里的代码中,_的意思是(c_n,h_n)我都不要了,
lstmout, _ = self.bilstm(embed_x)
因为是batch_first=TRUE,所以输出的lstmout(也就是output)的输出size是(batch_size,seq_len,hidden_size*num_directions)上面文档中写了,这里的batch_size网上解释很多,就是把整个数据切成batch_size大小的batch,第二个是时序的意思,通俗来说就是一个数据有几个词,这个设定一般在dataloader中,就是训练集,测试集,验证集制作的函数当中,我的代码是设定为256就是要是一句话少于256用padding加上,多于256砍掉。好现在bilstm输出的结果是256个词,每个词512维度。
接下来到了MultiheadAttention,MultiheadAttention的selfheadAttention网上有太多介绍,有的自己封装,有的直接用torch,这里我用的torch的
#定于多头注意力层
self.attention = nn.MultiheadAttention(embed_dim = 512,num_heads = 8,dropout = 0.1)
很简单,bilstm出来的是512为,那么MultiheadAttention方法的第一个参数就是输入的词向量维度为512,第二个参数是num_heads就是有几个头,这个数值一定能被第一个参数整除也就是512/8.好了现在就是全部model
import torch
import torch.nn as nn
from transformers import AutoModel
class BertBilstm(nn.Module):
def __init__(self,
output_size,
embed_size,
num_layers,
hidden_size,
drop_prob,
pretrained_path):
super(BertBilstm, self).__init__()
self.output_size = output_size
# 加载Bert预训练模型
self.bert = AutoModel.from_pretrained(pretrained_path)
# 定义BiLSTM层
self.bilstm = nn.LSTM(bidirectional=True,
num_layers=num_layers,
input_size=embed_size,
hidden_size=hidden_size,
batch_first=True,
dropout=drop_prob) #(64,256,512)
#定于多头注意力层
self.attention = nn.MultiheadAttention(embed_dim = 512,num_heads = 8,dropout = 0.1)
# 定义全连接层
self.fc = nn.Linear(2 * hidden_size, output_size)
def forward(self, x):
# Bert模型太大,参数固定住
with torch.no_grad():
embed_x = self.bert(x)[0]
lstmout, _ = self.bilstm(embed_x)
print(lstmout.shape)
output,_ = self.attention(lstmout,lstmout,lstmout)
return self.fc(output)
if __name__ == "__main__":
pass
output,_=self.attention(lstmout,lstmout,lstmout)是MultiheadAttention的输出,这三个参数是query,key,value的维度是(batch_size,seq_len,hidden_size*num_directions)=(64,256,512)