Bi-LSTM(attention)代码解析——基于Pytorch

Bi-LSTM(attention)代码解析——基于Pytorch

  • 以下为基于双向LSTM的的attention代码,采用pytorch编辑,接下来结合pytorch的语法和Attention的原理,对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

class BiLSTM_Attention(nn.Module):
    def __init__(self):
        super(BiLSTM_Attention, self).__init__()
        
        self.lstm = nn.LSTM(input_dim, n_hidden, bidirectional=True)
        self.out = nn.Linear(n_hidden * 2, output_dim)
    # lstm_output : [batch_size, n_step, n_hidden * num_directions(=2)], F matrix
    
    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 
    
    
    def forward(self, X):
        '''
        X: [batch_size, seq_len]
        '''
         # X : [batch_size, seq_len, input_dim]
        input = X.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]
  • 第一部分:pytorch的初始定义

    class BiLSTM_Attention(nn.Module):
        def __init__(self):
            super(BiLSTM_Attention, self).__init__()
    
  • 此部分首先定义一个名为 BiLSTM_Attention的python 类,继承 torch.nn 类中的 Module 的特性,此处利用的是python中的类的可继承性

  • KaTeX parse error: Expected group after '_' at position 1: _̲_init__ 为类的初始化函数,当创建了这个类的实例时就会调用该方法,可以利用KaTeX parse error: Expected group after '_' at position 1: _̲_init__ 接受类的参数

  • super() 函数是用于调用父类(超类)的一个方法。

    • super(BiLSTM_Attention, self).KaTeX parse error: Expected group after '_' at position 1: _̲_init__() 首先找到 BiLSTM_Attention 的父类(就是类 nn.Module),然后把类 nn.Module 的对象转换为类 BiLSTM_Attention 的对象,完成BiLSTM_Attention对于其父类 nn.Module对象的继承。
  • 第二部分:计算参数的定义

    self.lstm = nn.LSTM(input_dim, n_hidden, bidirectional=True)
    self.out = nn.Linear(n_hidden * 2, output_dim)
    
  • 这里定义的为神经网络的计算过程,分为两部分,LSTM部分和全连接神经网络部分

    • 首先对nn.LSTM进行简单介绍:
      • LSTM函数的输入为三维变量:
        • 第一维体现的是序列(sequence)结构,也就是序列的个数
        • 第二维度体现的是batch_size,也就是一次性喂给网络多少个个例
        • 第三维体现的是输入的元素(feature of input)
      • LSTM函数的输出也为三维变量:
        • output, (hn, cn)
        • 其中 output为最后一层lstm的每个样本对应隐藏层的输出
        • hn 为最后一层隐含层所有神经元的输出值
        • hc 为最后一层隐含层所有神经元的记忆状态
        • 这里需要说明的是,在每一个隐含层神经元中都具有hc和hn,来源于上一个神经元,其中hc与hn相乘输入到tanh中用来判断上一层的信息是否值得记忆,并作为下一层的记忆判断输入
        • 举例:
          • 定义LSTM = nn.LSEM(20, 40 , bidirectional=True), 其中输入的数据的时间维度为24,个例数为64,则 output的shape为torch.Size([64, 24, 80]),hn的shape为torch.Size([2, 64, 40])
    • LSTM神经网络的定义:采用的为 torch.nn 中的LSTM函数,其定义参数为 输入特征数(input_dim) 隐层神经元数(n_hidden)使用双向传播(bidirectional
    • 全连接神经网络:采用 torch.nn 中的Linear函数定义,定义参数为:全连接层得输入特征数(n_hidden*2),输出特征数(output_dim)
    • 这一部分需要注意得是,LSTM函数输出包括两部分,out和隐含层的输出,在定义中采用的为隐含层双向输出,因此在隐含层参数输入到nn.Linear中时,输入的特征数为 KaTeX parse error: Expected group after '_' at position 2: n_̲_hidden* 2
  • 第三部分: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 
    
  • 这一部分为Attention机制实现的重点,逐句进行解析

  • attention_net的输入参数为: lstm_output 和 final_state

    • lstm_output为LSTM 最后隐含层神经元的对应输出
    • final_state为LSTM 最后隐层所有神经元的隐含输出
  • batch_size = len(lstm_output):通过lstm_output的长度得到输入的样本个数

  • hidden = final_state.view(batch_size, -1, 1) :将LSTM隐含层的隐含层状态输出值(双向)的shape转化为单向,就是将hn的形状:torch.Size([2, 64, 40]),转变为 torch.Size([64, 80, 1])

  • attn_weights = torch.bmm(lstm_output, hidden).squeeze(2)

    • 这一步为对lstm_output和转变顺序后的hidden执行矩阵乘法,举例为: t o r c h . S i z e ( [ 64 , 24 , 80 ] ) ∗ t o r c h . S i z e ( [ 64 , 80 , 1 ] ) = t o r c h . S i z e ( [ 64 , 24 , 1 ] ) torch.Size([64, 24, 80])*torch.Size([64, 80, 1])=torch.Size([64, 24, 1]) torch.Size([64,24,80])torch.Size([64,80,1])=torch.Size([64,24,1]) 该结果可以判断时间维度上哪一个时次的样本对于结果的影响较大
    • squeeze(2) 是对输出结果进行降维,取消输出的第三维,得到的结果为:torch.Size([64, 24])
  • soft_attn_weights = F.softmax(attn_weights, 1)

    • 这一步调用F中的 softmax函数对attn_weights进行处理
      • 其中1为计算的维度,dim=1 时表示按列进行softmax,每一列的加和为1,dim=0时表示按行进行加和,每一行的加和为1
  • context = torch.bmm(lstm_output.transpose(1, 2), soft_attn_weights.unsqueeze(2)).squeeze(2)

    • 执行矩阵乘法,其中lstm_output.transpose(1, 2) 为交换 lstm_output的第2、3维的顺序,soft_attn_weights.unsqueeze(2) 为对soft_attn_weights添加第三维度为1,.squeeze(2) 为取消计算结果的第三维度
  • 第四部分为前向传播过程:

    def forward(self, X):
            input = X
            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, (finalHiddenState, finalCellState) = self.lstm(input)
            #output, (finalHiddenState, finalCellState) = self.lstm2(input)
            output = output.transpose(0, 1) 
            # output : [batch_size, seq_len, n_hidden]
            attn_output, attention = self.attention_net(output, finalHiddenState)
            out = self.out(attn_output)
            out = out.unsqueeze(2)
            return out, attention 
            # model : [batch_size, num_classes], attention : [batch_size, n_step]
    
  • 前向传播过程为数据每次输入后在神经网络中的传播过程

  • 其中X为每次输入的数据,在训练中采用batch方法,每一组数据有64个,每个时次的特征数为54,共有24个时次,则X的shape为torch.Size([64, 24, 54])

  • input = input.transpose(0, 1)

    • 对input的第一维度和第二维度交换位置,满足LSTM的输入需求,就是将torch.Size([64, 24, 54])转变为torch.Size([24, 64, 54])
  • output, (finalHiddenState, finalCellState) = self.lstm(input)

    • LSTM计算,请参考前边LSTM解析
  • output = output.transpose(0, 1)

    • 对output的输出结果的第一维和第二维进行转变,即:torch.Size([24, 64, 80]) —> torch.Size([64, 24, 80])
  • attn_output, attention = self.attention_net(output, finalHiddenState)

    • 进行attention机制的计算,输出的为 torch.Size([64, 80]) 的二维矩阵
  • out = self.out(attn_output)

    • 利用全连接层对out进行计算 ,输出结果为 torch.Size([64, 1])
  • out = out.unsqueeze(2)

    • 给out添加一个维度到第三维,方便之后训练中的计算

参考:

  • https://wmathor.com/ 的相关博客
  • https://www.bilibili.com/video/BV1op4y1U7ag/ UP主 数学家是我的梦想相关视频

你可能感兴趣的:(深度学习,机器学习笔记)