Pytorch中LSTM和GRU模块的使用
-
LSTM介绍
LSTM和GRU都是由torch.nn提供
(1)LSTM的api
torch.nn.LSTM(input_size, hidden_size, num_layers, batch_first, dropout, bidirectional) a. input_size:输入数据的形状,即embedding_dim b. hidden_size:隐藏层的神经元数量,即每一层有多少个LSTM单元 c. num_layer:即RNN中的LSTM单元的层数 d. batch_first:默认值为False,输入的数据需要[seq_len,batch,feature],如果为True,则为[batch,seq_len,feature] e. dropout:dropout的比例,默认值为0。dropout是一种训练过程中让部分参数随机失活的一种方式,能够提高训练速度,同时能够解决过拟合的问题。这里是在LSTM的最后一层,对每个输出进行dropout f. bidirectional:是否使用双向LSTM,默认是False 实例化LSTM对象之后,不仅需要传入数据,还需要前一次的h_0(前一次的隐藏状态)和C_0(前一次的memory) 即:lstm(input,(h_0,C_0)) LSTM的默认输出为output,(h_n,C_n) a. output:(seq_len, batch, num_directions*hidden_size) b. h_n:(num_layers*num_directions, batch, hidden_size) c. c_n:(num_layers*num_directions, batch, hidden_size) num_directions:如果只是单向,则为1;如果是双向,则为2
(2)LSTM使用示例
a. output:把每个时间步上的结果在seq_len这一维度进行拼接
b. h_n:把不同层的隐藏状态在第0个维度上进行了拼接
c. 双向LSTM:i. 只需要把bidirectional = True ii. output的拼接顺序:正向的第一个拼接反向的最后一个;在最后一个维度进行拼接 iii. hidden_state:正向和反向各自的形状是[batch_size,hidden_size],双向时会在第0个维度拼接;[layers*num_direction,batch_size,hidden_size]; #第一层的正向 #第一层的反向 #第二层的正向 #第二层的反向 #......(以此类推)
代码如下:
import torch.nn as nn import torch batch_size = 10 seq_len = 20 #句子的长度 vocab_size = 100 #字典的数量 embedding_dim = 30 #用长度为30的向量表示一个词语 hidden_size = 18 num_layer = 2 #构造一个batch的数据 input = torch.randint(low=0,high=100,size=[batch_size,seq_len]) #[10,20] #数据经过embedding处理 embedding = nn.Embedding(vocab_size,embedding_dim) input_embeded = embedding(input) #[10,20,30] #把embedding之后的数据传入LSTM lstm = nn.LSTM(input_size=embedding_dim,hidden_size=hidden_size,num_layers=num_layer,batch_first=True) output,(h_n,c_n) = lstm(input_embeded) print(output.size()) #[10,20,18] print("*"*100) print(h_n.size()) #[2*1,10,18] print("*"*100) print(c_n.size()) #[2*1,10,18] #获取最后一个时间步上的输出 last_output = output[:,-1,:] #获取最后一次的hidden_state last_hidden_state = h_n[-1,:,:] print(last_output == last_hidden_state)
(3)GRU的api
a. 由torch.nn提供 b. GRU(参数同LSTM) c. (output,h_n) = gru(input,h_0) d. 形状同LSTM
-
LSTM和GRU的使用注意事项
(1)在第一次调用之前,需要初始化隐藏状态,若不初始化,默认创建全为0的隐藏状态
(2)往往会使用LSTM或者GRU的输出的最后一维的结果,来代表LSTM、GRU对文本处理的结果,其形状为[batch,num_directions*hidden_size]a. 并不是所有模型都会使用最后一维的结果 b. 如果实例化LSTM的过程中,batch_first=False,则output[-1]或者output[-1,:,:]可以获取最后一维 c. 如果实例化LSTM的过程中,batch_first=True,则output[:,-1,:]可以获取最后一维
(3)如果结果是(seq_len,batch_size,num_directionshidden_size),想要把它转化为(batch_size,seq_len,num_directionshidden_size),不能够使用view等变形的方法,需要使用output.permute(1,0,2),即交换0和1轴
(4)使用双向LSTM的时候,往往会分别使用每个方向的最后一次的output,作为当前数据经过双向LSTM的结果
即:torch.cat([h_n[-2,:,:],h_n[-1,:,:]],dim=-1)
最后的表示的size是 [batch_size,hidden_size*2]
(5)上述内容在GRU中同理 -
使用LSTM完成文本情感分类
为了达到更好的效果,对之前的模型做如下修改
1.MAX_LEN=200 2.构建dataset的过程,把数据转化为2分类的问题,pos为1,neg为0,否则25000个样本完成10个类别的划分数据量是不够的 3.在实例化LSTM的时候,使用dropout=0.5,在model.eval()的过程中,dropout自动会为0
修改模型:
class MyModel(nn.Module): def __init__(self): super(MyModel,self).__init__() self.embedding = nn.Embedding(len(ws), 100) # 假如LSTM self.lstm = nn.LSTM(input_size=100,hidden_size=lib.hidden_size,num_layers=lib.num_layers,batch_first=True,bidirectional=lib.bidirectional,dropout=lib.dropout) self.fc = nn.Linear(lib.hidden_size*2,2) def forward(self,input): ''' :param input: [batch_size,max_len] :return: ''' x = self.embedding(input)#[batch_size,max_len,100] ''' x:[batch_size,max_len,2*hidden_size] h_n:[num_layers*2,batch_size,hidden_size] ''' x,(h_n,c_n) = self.lstm(x) #获取两个方向最后一次的output,进行concat操作 output_fw = h_n[-2,:,:] #正向最后一次输出 output_bw = h_n[-1,:,:] #反向最后一次输出 output = torch.cat([output_fw,output_bw],dim=-1) #[batch_size,hidden_size*2] out = self.fc(output) return F.log_softmax(out,dim=-1)
测试模型:
def eval(): loss_list = [] acc_list = [] data_loader = get_dataloader(train=False,batch_size=lib.test_batch_size) for idx,(input,target) in tqdm(enumerate(data_loader),total=len(data_loader),ascii=True,desc="测试:"): input = input.to(lib.device) target = target.to(lib.device) with torch.no_grad(): output = model(input) cur_loss = F.nll_loss(output,target) loss_list.append(cur_loss.cpu().item()) #计算准确率 pred = output.max(dim=-1)[-1] cur_acc = pred.eq(target).float().mean() acc_list.append(cur_acc.cpu().item()) print("total loss,acc",np.mean(loss_list),np.mean(acc_list))