NLP 中 Bilstm-attentio的使用

bilstm-attention 理解

bilstm-attention的作用

1:输入空间和输出空间的理解
在NLP的任务框架中,最基本的流程是 初步编码(比如one-hot vector,或者预训练词向量)作为输入,比如一句话进来 我开心,如果使用在分类模型中,我们就需要融合 我开心 这三个字,我们将这三个字 映射成向量,比如 [10,3] 3代表3个字,10代表表示向量的长度。 如果在一个分类的任务中,我们如何做到将其融合成一个词向量的表示呢,直白的来说 我想将 [10,3] 矩阵,从线性代数的角度来看,可以认为 是3个列向量,那么句子向量的表示,就可以表示成 [10,3]*[3,1] [3,1]是什么呢,我们就理解成权重,比如这句话如果在情感识别任务中,那么开心比较重要,那么 [3,1]的矩阵 写成 [0,0,1] 那么我们就可以直接抽取 开心的输入向量了。 输出也就是[10,1]的这个 开心这个词的向量。 但是因为任务是前变万化的,我们就想机器是否可以自适应的来学习 输入向量和输出向量呢,这就是本文的任务所在。

2: 为何要将输入的向量的重新表示

我们是否可以直接使用 [10,3]的输入向量作为输入呢,可以,但是前提是最好这些词的向量预先训练过,我们在学习线性代数的时候,还记得我们经常使用的 PCA呢,我们经常将一个向量表示成 out=w1v1+w2v2+… v1,v2,v3 那一般是什么,这就是我们通过pca计算出来的 独立主成分,它通过主要的特征向量来表示输出向量。 ok,其实大多数编码器就是做的这个工作。

bilstm-attention 编码实现`

如下 是代码的,语料分析部分,这部分不做分析了

import torch
import numpy as np
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import matplotlib.pyplot as plt
import torch.utils.data as Data

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# Bi-LSTM(Attention) Parameters
batch_size = 4
embedding_dim = 2
n_hidden = 5 # number of hidden units in one cell
num_classes = 2  # 0 or 1

# 3 words sentences (=sequence_length is 3)
sentences = ["i love you", "he loves me", "she likes baseball", "i hate you", "sorry for that", "this is awful"]
labels = [1, 1, 1, 0, 0, 0]  # 1 is good, 0 is not good.

vocab = list(set(" ".join(sentences).split()))
word2idx = {w: i for i, w in enumerate(vocab)}
vocab_size = len(word2idx)

def make_data(sentences):
  inputs = []
  for sen in sentences:
      inputs.append(np.asarray([word2idx[n] for n in sen.split()]))

  targets = []
  for out in labels:
      targets.append(out) # To using Torch Softmax Loss function

  return torch.LongTensor(inputs), torch.LongTensor(targets)

inputs, targets = make_data(sentences)
dataset = Data.TensorDataset(inputs, targets)
loader = Data.DataLoader(dataset, batch_size, True)

我们作中分析如下部分:

class BiLSTM_Attention(nn.Module):
    def __init__(self):
        super(BiLSTM_Attention, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        self.lstm = nn.LSTM(embedding_dim, n_hidden, bidirectional=True)
        self.out = nn.Linear(n_hidden * 2, num_classes)

nn.Embedding 的作用就是将我们输入的 字或者词 转成词输入向量,训练出来的就是基于我们自己语料的词向量模型。 self.lstm 就是我们使用的编码层,我们通过 双向lstm将我们的输入编码输出,然后就是我们上文所述的,我们需要将 比如[10,3]的输入向量,表示成一个总体的我们可以称之为句向量表示的 [10.1] 最后 self.out 就是输出层,很直白,我们需要将最终的输入向量,映射为 最终的分类数。
实现方式一,我们通过隐藏层的参数作为attention的编码。

  def attention_net(self, lstm_output, final_state):
        batch_size = len(lstm_output)
        hidden = final_state.view(batch_size, -1, 1)   # hidden : [batch_size, n_hidden * num_directions(=2), n_layer(=1)]
        attn_weights = torch.bmm(lstm_output, hidden).squeeze(2) # attn_weights : [batch_size, n_step]
        soft_attn_weights = F.softmax(attn_weights, 1)

        # context : [batch_size, n_hidden * num_directions(=2)]
        context = torch.bmm(lstm_output.transpose(1, 2), soft_attn_weights.unsqueeze(2)).squeeze(2)
        return context, soft_attn_weights 

实现方二,我们可以直接通过linear来实现。

  def __init__(self):
        super(BiLSTM_Attention, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        self.lstm = nn.LSTM(embedding_dim, n_hidden, bidirectional=True)
        #这里我们直接使用linear层,来做attention。
        self.attention=nn.Linear(n_hidden*2,1)
        self.out = nn.Linear(n_hidden * 2, num_classes)
    def attention_net(self, lstm_output, final_state):
        batch_size = len(lstm_output)
        hidden = final_state.view(batch_size, -1,
                                  1)  # hidden : [batch_size, n_hidden * num_directions(=2), n_layer(=1)]
        # attn_weights = torch.bmm(lstm_output, hidden).squeeze(2)  # attn_weights : [batch_size, n_step]
        #第二种方式实现。我们通过linear层来实现,在试验中,效果更好。
        attn_weights=self.attention(lstm_output).squeeze(2)
        soft_attn_weights = F.softmax(attn_weights, 1)

        # context : [batch_size, n_hidden * num_directions(=2)]
        context = torch.bmm(lstm_output.transpose(1, 2), soft_attn_weights.unsqueeze(2)).squeeze(2)
        return context, soft_attn_weights

这一层,就是我们这里要使用的注意力模型,注意力模型学的是什么呢,就是学习的我们上文中描述的 [3,1]向量,我们想计算出 针对[10,3]这三个列向量中,每一个列向量对应的权重。最终的context就是将输入空间映射成最终的输出空间。

好了,到目前,我们基本组件已经构建完成,下面我们进行,前向网络的构建。

 def forward(self, X):
        '''
        X: [batch_size, seq_len]
        '''
        input = self.embedding(X) # input : [batch_size, seq_len, embedding_dim]
        input = input.transpose(0, 1) # input : [seq_len, batch_size, embedding_dim]

        # final_hidden_state, final_cell_state : [num_layers(=1) * num_directions(=2), batch_size, n_hidden]
        output, (final_hidden_state, final_cell_state) = self.lstm(input)
        output = output.transpose(0, 1) # output : [batch_size, seq_len, n_hidden]
        attn_output, attention = self.attention_net(output, final_hidden_state)
        return self.out(attn_output), attention # model : [batch_size, num_classes], attention : [batch_size, n_step]

X为输入,输入的维度为 [4,3] 即 batch_size,seq_len.
self.embedding(X) 将X映射进词向量空间。 因此,input 变为 [4,3,2]
因为lstm 是序列模型,所以 batch_size和seq_len 交换,变成[3,4,2].进入self.lstm lstm为一个序列模型,每一个时间步长都会生成一个输出。看下具体lstm的网络 LSTM(2, 5, bidirectional=True)。 那么输出时为 [3,4,52] 因为为双向,所以有两个维度为5的拼接在一起。所以输出为 [3,4,10] lstm网络有两个各隐藏层,分别为 c和h,我们一般只保留最后一个一个时间序列的隐藏层,所有 c和h的输出均为 [2,4,5] 2因为我们是双向的。
下面进入我们的重点代码attention, 上面我们已经描述过,是为了求得针对编码输出的每个向量的输出。编码输出为 [3,4,10] 转置成[batch_size,seq_len,out_dim] [4,3,10] 那么我们需要有一个向量去做attention,这个向量的设计方法挺多,这里选择了用隐藏层的输出[2,4,5] 作为attention,当然我们也可以选择其他。比如直接使用 nn.linear ,后面我们会描述。 我们可以得知 attention_weights 理论为 [4,3,1] ,我们可以去掉batch_size, 那么 encoder 层是 [10,3] 那么 attention 层求得的权重向量为 [3,1] , 然后用我们上述的 讲过的 [10,3]
[3,1] 得到了 [10.1]的句向量。到这里,我们基本做完了基于lstm的注意力机制。

核心重点:

1:设计计算出权重向量的方式。我们这里有两种实现,1:使用隐藏层来实现。2:使用linear层实现。

model = BiLSTM_Attention().to(device)
criterion = nn.CrossEntropyLoss().to(device)
optimizer = optim.Adam(model.parameters(), lr=0.001)

# Training
for epoch in range(500):
    for x, y in loader:
        x, y = x.to(device), y.to(device)
        pred, attention = model(x)
        loss = criterion(pred, y)
        if (epoch + 1) % 10 == 0:
            print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
            writer.add_scalar("Loss/train", loss, epoch)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
writer.flush()
#save the model
torch.save(model.state_dict(),"bi-lstmattention-para")
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值