NLP实践九:Attention原理与文本分类代码实践


本篇文章主要来自自然语言处理中的Attention机制总结,作者:哈哈进步。
这里作为转载和自学整理。
同时也参考了attention各种形式总结,作者 seeInfinite

Attention机制的通用定义

Attention从数学角度来形容是一个变量query到变量一系列(key-value)对的映射,从公式上描述是根据query和key计算values的加权求和的机制。
在这里插入图片描述
在计算attention时主要分为三步:

  • 第一步是将query和每个key进行相似度计算得到权重,常用的相似度函数有点积,拼接,感知机等;
  • 第二步一般是使用一个softmax函数对这些权重进行归一化;
  • 最后将权重和相应的键值value进行加权求和得到最后的attention。
  • 目前在NLP研究中,key和value常常都是同一个,即key=value。
    单纯的文字描述很难理解attention,以下从实际算法角度来理解。

Seq2Seq中的Attention

什么是Seq2Seq

Seq2Seq又叫Encoder-Decoder模型,常见于机器翻译。
在这里插入图片描述
如图中所展示,我们要翻译“知识就是力量。”这句话。Encoder是一个RNN,将要翻译的话转换成向量特征,输入到Decoder中。
在这里插入图片描述
这是Encoder,一个RNN,C是RNN从输入x1,x2,x3,x4x_1,x_2,x_3,x_4中提取的向量,或者说对x1,x2,x3,x4x_1,x_2,x_3,x_4进行一个编码,得到c有多种方式,最简单的方法就是把Encoder的最后一个隐状态赋值给c,还可以对最后的隐状态做一个变换得到c,也可以对所有的隐状态做变换。
在这里插入图片描述
在这里插入图片描述
获得C以后,就使用另一个RNN,Decoder,来对编码C进行解码,或者说根据向量C来学习获得正确的输出。上面两图中是两种输入方式,将C当做之前的初始状态h0输入到Decoder中和将C当做每一步的输入

attention在seq2seq中的应用

首先,先进行数学符号的描述。

  • 在Encoder中,保留每一个RNN单元的隐藏状态(hidden state)h1,h2...hNh_1,h_2...h_N
  • 在Decoder中,每个一个RNN单元的隐藏状态是由输入和上一步的隐藏状态决定的,假设第t步的隐藏状态记为StS_t(下图中的START)

计算attention的第一步

在这里插入图片描述
那么如图所示,在每个第t步利用StS_thih_i(i=1toN)进行dot点积得到图中的Attention Scores,记为uitu^t_i(i=1toN)。从Attention的通用定义来说,此时StS_t就是query,hi=1toNh_{i=1toN}就是一系列values。

计算attention的第二步

在这里插入图片描述
uitu^t_i(i=1toN)通过Softmax来计算每个Encoder的RNN单元所对应Decoder第t步的权重αit\alpha^t_i
αit=euitk=1Neukt\alpha^t_i=\frac{e^{u^t_i}}{\sum_{k=1}^{N}e^{u^t_k}}

计算attention的第三步

在这里插入图片描述
利用αit\alpha^t_i来对Encoder的hidden states hih_i(i=1toN)加权求和得到对应Decoder第t步的attention向量ata^t
at=i=1Nαithia^t=\sum_{i=1}^{N}\alpha^t_ih_i
到这里其实attention的计算就结束了,得到的这个ata^t就已经是decoder的第t时刻的注意力向量了(在后面的文章中,这也称作是上下文向量,context vector,符号表示也可能是用ctc^t来表示的)

计算attention的第四步

在这里插入图片描述
最后将注意力向量at,以及decoder的t时刻的hidden state st,并联起来,然后做后续的步骤(比如加个dense全连接层做标签预测之类的)

几种常见的attention向量计算方式

Soft-attention

在这里插入图片描述

Soft attention也就是上面Seq2Seq讲过的那种最常见的attention,是在求注意力分配概率分布的时候,对于输入句子X中任意一个单词都给出个概率,是个概率分布,把attention变量(context vecor)用ctc_t表示,attention得分在经过了softmax过后的权值用αi\alpha_i表示

Hard-attention

在这里插入图片描述
Hard-attention直接从输入句子里面找到某个特定的单词,然后把目标句子单词和这个单词对齐,而其它输入句子中的单词硬性地认为对齐概率为0.
Hard attention的这个pt,我认识是代表未被选中的概率。据说Hard attention 一般用在图像里面,当图像区域被选中时,权重为1,剩下时候为0。

local attention (半软半硬attention)

在这里插入图片描述
Soft attention 每次对齐的时候都要考虑前面的encoder的所有hi,所以计算量会很大,因此一种朴素的思想是只考虑部分窗口内的encoder隐藏输出,其余部分为0,在窗口内使用softmax的方式转换为概率。
在这个模型中,对于是时刻t的每一个目标词汇,模型首先产生一个对齐的位置 pt(aligned position),context vector 由编码器中一个集合的隐藏层状态计算得到,编码器中的隐藏层包含在窗口[pt-D,pt+D]中,D的大小通过经验选择。
上式之中,大S指的是源句子的长度,Wp和vp是指的模型的参数,通过训练得到,为了支持pt附近的对齐点,设置一个围绕pt的高斯分布,其中小s是在以pt为中心的窗口中的整数,pt是一个在[0,S]之间的实数。小Sigma σ 一般取窗口大小的一半。

静态attention

在这里插入图片描述
静态attention:对输出句子共用一个St的attention就够了,一般用在Bilstm的首位hidden state输出拼接起来作为st

几种不同方式的Attention score计算

在这里插入图片描述
我们可以看到,式子中的变量ee代表的就是attention score,α\alpha是attention的权重,aa就是context vector
根据attention score的计算方式不同,我们可以将attention进一步细分分类。attention score的计算主要有以下几种:
在这里插入图片描述
h1,h2...hNh_1,h_2...h_N代表Encoder中每个RNN单元的隐藏状态,ss代表Decoder中第t步隐藏状态
点积:attention score这里有个假设,就是s和h的维数要一样才能进行点积,很好理解。
乘法:W矩阵是训练得到的参数,维度是d2 x d1,d2是s的hidden state输出维数,d1是hi的hidden state维数
加法:对两种hidden state 分别再训练矩阵然后激活过后再乘以一个参数向量变成一个得分。
其中,W1 = d3xd1,W2 = d3*d2,v = d3x1 ,d1,d2,d3分别为h和s还有v的维数,属于超参数

Self-Attention

Self attention也叫做intra-attention在没有任何额外信息的情况下,我们仍然可以通过允许句子使用–self attention机制来处理自己,从句子中提取关注信息。

Self-Attention几种计算方式

第一种:以当前的隐藏状态去计算和前面的隐藏状态的得分,作为当前隐藏单元的attention score
ehi=htTWhie_{h_i}=h_t^TWh_i

第二种:以当前状态本身去计算得分作为当前单元attention score
ehi=tanh(wThi+b)e_{h_i}=tanh(w^Th_i+b)
ww是dx1的向量,hih_i是dx1,b是(1,)(第二个公式
或者:
ehi=vaTtanh(Wahi)e_{h_i}=v_a^Ttanh(W_ah_i)
vav_aWaW_a是参数,通过训练得到,WaW_a维数dxd,hih_i维数dx1,vav_a维数dx1
矩阵形式为:
A=softmax(VaTtanh(WaHT))A=softmax(V_aTtanh(W_aH^T))
C=AHC=AH
H是nx2u(双向lstm的结果拼接,每个单向LSTM的hidden units是u),WaW_a是dx2u,VaV_a是rxd,得到的attention 矩阵 A是rxn。C = rx2u,按论文意思来看就是把一句话编码成了一个rx2u的矩阵.

key-value-Attention

Key-value attention 是将hih_i拆分成了两部分[keyt;valuet][key_t;value_t]然后使用的时候只针对key部分计算attention权重,然后加权求和的时候只使用value部分进行加权求和。公式如下,attention权重计算:
在这里插入图片描述
其中呢,这个L是attention窗口长度
WY=kxk,ki=kx1,Wh = kxk,1^T是一个1xL的向量
w 是一个kx1的向量
Wr = kxk,
Wx = kxk
最后得到kx1的ht*与ht一起参与下一步的运算

代码实践

使用THUCNEWS数据集,数据处理等代码请参考以前的文章。
模型结构为:GRU+Attention
模型定义:

class Attention(nn.Module):
    def __init__(self, feature_dim, step_dim, bias=True, **kwargs):
        super(Attention, self).__init__(**kwargs)
        
        self.supports_masking = True

        self.bias = bias
        self.feature_dim = feature_dim
        self.step_dim = step_dim
        self.features_dim = 0
        
        weight = torch.zeros(feature_dim, 1)
        nn.init.xavier_uniform_(weight)
        self.weight = nn.Parameter(weight)
        
        if bias:
            self.b = nn.Parameter(torch.zeros(step_dim))
        
    def forward(self, x, mask=None):
        feature_dim = self.feature_dim
        step_dim = self.step_dim

        eij = torch.mm(
            x.contiguous().view(-1, feature_dim), 
            self.weight
        ).view(-1, step_dim)
        
        if self.bias:
            eij = eij + self.b
            
        eij = torch.tanh(eij)
        a = torch.exp(eij)
        
        if mask is not None:
            a = a * mask

        a = a / torch.sum(a, 1, keepdim=True) + 1e-10

        weighted_input = x * torch.unsqueeze(a, -1)
        return torch.sum(weighted_input, 1)
class GRU_Attention(nn.Module):
    def __init__(self,word_embeddings,hidden_size = 40):
        super(GRU_Attention, self).__init__()
        
        self.embed_size = 200
        self.hidden_size = hidden_size
        self.maxlen = 400
        self.label_num =10
        
        self.embeddings = nn.Embedding(len(word_embeddings),self.embed_size)
        self.embeddings.weight.data.copy_(torch.from_numpy(word_embeddings))
        self.embeddings.weight.requires_grad = False

        
        self.embedding_dropout = nn.Dropout2d(0.1)
        self.gru = nn.GRU(self.embed_size, self.hidden_size, bidirectional=True, batch_first=True)
        self.gru_attention = Attention(self.hidden_size * 2, self.maxlen)
       
        self.linear1 = nn.Linear(self.hidden_size * 4, self.hidden_size // 2)
        self.linear2 = nn.Linear(self.hidden_size // 2, self.label_num)
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(0.1)
 
    
    def forward(self, input):
        h_embedding = self.embeddings(input)
#         h_embedding = torch.squeeze(
#             self.embedding_dropout(torch.unsqueeze(h_embedding, 0)))
        

        h_gru, hh_gru = self.gru(h_embedding)
        h_gru_atten = self.gru_attention(h_gru)
        

        # global max pooling
        gru_max_pool, _ = torch.max(h_gru, 1)

        conc = torch.cat((h_gru_atten, gru_max_pool), 1)
        conc = self.relu(self.linear1(conc))
        conc = self.dropout(conc)
        out =  self.linear2(conc)
        
        return out

训练:

import os
os.environ['CUDA_VISIBLE_DEVICES']='0'
t.cuda.set_device(0)
GRU_Attention = GRU_Attention(embed_weight)
criterion = nn.CrossEntropyLoss()
optimizer = optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, GRU_Attention.parameters()), lr=0.001)

train(train_loader, val_loader,
         GRU_Attention, 'cuda',
         criterion, optimizer,  
         num_epochs=20)

没有更多推荐了,返回首页