NLP文本分类学习笔记5:带attention的文本分类

本节内容有些抽象,自己也可能理解不到位,可能有些错误,请批判性参考

seq2seq#

分为encoder和decoder两部分,如下图所示,每一个部分可以使用CNN,RNN,LSTM等模型,输入2针对不同情况可有可无,模型在翻译,文本摘要生成等方面有广泛应用。

在编码器encoder中可以对输入内容编码,表示为一个特征输出,然后输入到解码器decoder中,对特征进行解码产生输出,如以下翻译的例子,输入encoder中“我喜欢梨”,在decoder中进行翻译

翻译的效果全部依赖于encoder部分最后的输出向量。但是,一方面,翻译过程并不全部依赖于全面所有的内容,例如,对于“like”的翻译,对于前面“喜欢”这一词依赖程度更大。另一方面,最后的输出信息保留句子后面的信息多,保留前面的信息较少。所以提出attention注意力机制。

注意力机制attention#

有点抽象,自己也迷迷糊糊,先试着主观说一说大致思想:
注意力机制就是要关注重要的信息。
重要的信息如何被关注?就是重要的信息的权重要大一些
权重又怎么来?将所有的信息与参数计算后(key)和输入的内容(query)进行比较,哪个信息和输入的内容相关(相似),哪这个信息权重就要大,因此采用一些计算相似度的函数(两个向量的点积等)来计算(甚至训练一个网络),
最后按权重将这些信息(value)相乘再相加,就是最后的输出
以下图为例(结构并不止这一种),“我喜欢梨”这句话经过encoder训练得到了输出h1,h2,h3,在encoder,start同样得到一个输出S1(query),它就和h1,h2,h3与参数计算后的结果(key)分别计算相似性(这里使用了向量点积),将计算结果归一化处理后,就得到了各自的权重w1,w2,w3,各自的权重与h1,h2,h3(value)相乘相加后得到h,与s1相乘作为下一时刻decoder的输入。再重复之上的操作。

带attention机制的文本分类#

NLP文本分类学习笔记4:基于RNN的文本分类中,介绍了使用LSTM分类时,使用的是模型最后的输出
在上一节也介绍了只使用最后一个输出可能存在的问题
所以可以使用attention机制,对LSTM所有的输出进行加权求和来作为最后的输出,attention机制实际上就是求一个这样的权重。

pytorch实现基于LSTM带attention的文本分类#

借助NLP文本分类学习笔记4中LSTM模型,加入attention机制,实现文本分类。网络结构如下所示,详细代码见NLP文本分类学习笔记0
将LSTM所有时刻的输出(key)与随机参数(q)相乘再加一个偏置单元,并经过tanh得到权重
权重经过softmax归一化后作为权重,与将LSTM所有时刻的输出(key)相乘再相加。
把这个带权重的值作为LSTM最后的输出来分类(在NLP文本分类学习笔记4中,是用最后的输出分类)
最后在测试集上的准确率为86.79%
在Attention-Based Bidirectional Long Short-Term Memory Networks for Relation Classification这篇论文中作者使用的注意力机制为

M=tanh(H)

α=softmax(wTM)

r=HαT

h=tanh(r)

也就是将LSTM所有时刻的输出H经过tanh函数运算后,与初始化的随机参数W(w大小与词向量维度相同)相乘后归一化,最后再与H相乘,并再次经过tanh运算,代码为下述代码中注释的部分
最后在测试集上的准确率为87.26%
如下为两种机制的实现

Copy
import json import pickle import torch import torch.nn as nn import torch.nn.functional as F import numpy as np class Config(object): def __init__(self, embedding_pre): self.embedding_path = 'data/embedding.npz' self.embedding_model_path = "mymodel/word2vec.model" self.train_path = 'data/train.df' # 训练集 self.dev_path = 'data/valid.df' # 验证集 self.test_path = 'data/test.df' # 测试集 self.class_path = 'data/class.json' # 类别名单 self.vocab_path = 'data/vocab.pkl' # 词表 self.save_path ='mymodel/attention.pth' # 模型训练结果 self.embedding_pretrained = torch.tensor(np.load(self.embedding_path, allow_pickle=True)["embeddings"].astype( 'float32')) if embedding_pre == True else None # 预训练词向量 self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 设备 self.dropout = 0.5 # 随机失活 self.num_classes = len(json.load(open(self.class_path, encoding='utf-8'))) # 类别数 self.n_vocab = 0 # 词表大小,在运行时赋值 self.epochs = 10 # epoch数 self.batch_size = 128 # mini-batch大小 self.maxlen = 32 # 每句话处理成的长度(短填长切) self.learning_rate = 1e-3 # 学习率 self.embed_size = self.embedding_pretrained.size(1) \ if self.embedding_pretrained is not None else 200 # 字向量维度 self.hidden_size = 128 # lstm隐藏层 self.num_layers = 2 # lstm层数 class myAttention(nn.Module): def __init__(self,input_size): super(myAttention,self).__init__() self.input_size=input_size self.word_weight=nn.Parameter(torch.Tensor(self.input_size)) self.word_bias=nn.Parameter(torch.Tensor(1)) self._create_weights() def _create_weights(self,mean=0.0,std=0.05): self.word_weight.data.normal_(mean,std) self.word_bias.data.normal_(mean,std) def forward(self,inputs): att=torch.einsum('abc,c->ab',(inputs,self.word_weight))+self.word_bias att=torch.tanh(att) att=F.softmax(att,dim=1) att=torch.einsum('abc,ab->ac',(inputs,att)) # #论文中的机制 # att=torch.tanh(inputs) # att=F.softmax(att@self.word_weight,dim=1).unsqueeze(-1) # att=torch.sum(inputs*att,1) # att=torch.tanh(att) return att class Model(nn.Module): def __init__(self, config): super(Model, self).__init__() if config.embedding_pretrained is not None: self.embedding = nn.Embedding.from_pretrained(config.embedding_pretrained, freeze=False) else: vocab = pickle.load(open(config.vocab_path, 'rb')) config.n_vocab=len(vocab.dict) self.embedding = nn.Embedding(config.n_vocab, config.embed_size, padding_idx=config.n_vocab - 1) self.lstm = nn.LSTM(config.embed_size, config.hidden_size, config.num_layers, bidirectional=True, batch_first=True, dropout=config.dropout) self.att=myAttention(config.hidden_size*2) self.fc = nn.Linear(config.hidden_size * 2, config.num_classes) def forward(self, x): emb = self.embedding(x) out, _ = self.lstm(emb) out=self.att(out) out = self.fc(out) return out

自注意力机制self-attention#

参考:
https://www.bilibili.com/video/BV1Wv411h7kN?p=38
https://www.cnblogs.com/erable/p/15072941.html
注意力机制是对于目标来提取输入中重要的信息,而自注意力机制是提取输入序列元素间重要的信息,对于一个序列它打破了序列间距离的限制,能够提取到较远的两个输入之间的关系而不用担心距离造成的影响。在文本分类中,使用一层self-attention,可以捕捉序列中任意两词之间的信息。也可以多叠加几层,层之间使用全连接层连接。
对于一个序列A,不需要另外的条件,可以自己得到一个序列B。如下图所示,对于序列中每个元素a,分别与三个参数矩阵相乘得到q,k和v,用q和其它元素的k相乘可以得到该元素与其它元素的相似度,也就是权重,权重经过归一化(softmax,图中未画出,也就是对α1,1,α1,2等归一化),再与各个元素的v相乘,再相加就得到了第一个输出(图中的b1),对于b2等也同样方法求出

pytorch实现基于LSTM带自注意力机制的文本分类#

这里将自注意力机制单独拿出来,加到单词嵌入层之后,其主要思想是能够捕捉到到两个距离远的词之间的联系,网络结构如下所示,也就是对上述机制的简单实现,其中对于“对于序列中每个元素a,分别与三个参数矩阵相乘得到q,k和v”这一过程,直接使用了全连接层nn.Linear来计算,因为其本质就是输入与随机参数矩阵的相乘。最后将o作为LSTM模型的输入
最后在测试集上的准确率为85.35%

Copy
import json import pickle import torch import torch.nn as nn import torch.nn.functional as F import numpy as np class Config(object): def __init__(self, embedding_pre): self.embedding_path = 'data/embedding.npz' self.embedding_model_path = "mymodel/word2vec.model" self.train_path = 'data/train.df' # 训练集 self.dev_path = 'data/valid.df' # 验证集 self.test_path = 'data/test.df' # 测试集 self.class_path = 'data/class.json' # 类别名单 self.vocab_path = 'data/vocab.pkl' # 词表 self.save_path ='mymodel/selfattention.pth' # 模型训练结果 self.embedding_pretrained = torch.tensor(np.load(self.embedding_path, allow_pickle=True)["embeddings"].astype( 'float32')) if embedding_pre == True else None # 预训练词向量 self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 设备 self.dropout = 0.5 # 随机失活 self.num_classes = len(json.load(open(self.class_path, encoding='utf-8'))) # 类别数 self.n_vocab = 0 # 词表大小,在运行时赋值 self.epochs = 10 # epoch数 self.batch_size = 128 # mini-batch大小 self.maxlen = 32 # 每句话处理成的长度(短填长切) self.learning_rate = 1e-3 # 学习率 self.embed_size = self.embedding_pretrained.size(1) \ if self.embedding_pretrained is not None else 200 # 字向量维度 self.hidden_size = 128 # lstm隐藏层 self.num_layers = 2 # lstm层数 class mySelfAttention(nn.Module): def __init__(self,config): super(mySelfAttention, self).__init__() self.WQ=nn.Linear(config.embed_size,config.embed_size,bias=False) self.WK = nn.Linear(config.embed_size,config.embed_size,bias=False) self.WV = nn.Linear(config.embed_size,config.embed_size,bias=False) def forward(self,inputs): Q=self.WQ(inputs) K = self.WQ(inputs).permute(0,2,1) V = self.WQ(inputs) a=F.softmax(Q@K,dim=1) o=a@V return o class Model(nn.Module): def __init__(self, config): super(Model, self).__init__() if config.embedding_pretrained is not None: self.embedding = nn.Embedding.from_pretrained(config.embedding_pretrained, freeze=False) else: vocab = pickle.load(open(config.vocab_path, 'rb')) config.n_vocab=len(vocab.dict) self.embedding = nn.Embedding(config.n_vocab, config.embed_size, padding_idx=config.n_vocab - 1) self.selfatt=mySelfAttention(config) self.lstm = nn.LSTM(config.embed_size, config.hidden_size, config.num_layers, bidirectional=True, batch_first=True, dropout=config.dropout) self.fc = nn.Linear(config.hidden_size * 2, config.num_classes) def forward(self, x): emb = self.embedding(x) selfatt=self.selfatt(emb) out, _ = self.lstm(selfatt) out = self.fc(out[:,-1,:]) return out

多头注意力机制multi-head attention#

基于单词之间关系可能不止一种的思想,使用多组QKV,分别捕捉不同的关系,如下图所示,以两个头为例,在原有的QKV基础上在乘以参数矩阵,得到两组QKV,之后分别得到两个输出b,将b拼接后乘以一个参数,作为最后的输出

posted @   启林O_o  阅读(880)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
点击右上角即可分享
微信分享提示
CONTENTS