提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目标
1. 知道LSTM和GRU的使用方法及输入输出格式 2. 能够应用LSTM和GRU实现文本情感分类
提示:以下是本篇文章正文内容,下面案例可供参考
一、LSTM介绍
LSTM和GRU都是由torch.nn 提供,通过观察文档,可知LSTM的参数:
torch.nn.LSTM(input_size,hidden_size,num_layers,batch_first,dropout,bidirectional)
input_size :输入数据的形状,即embedding_dimhidden_size :隐藏层的数量,即每一层有多少个LSTM单元num_layer :即RNN中LSTM单元的层数batch_first :表示数据中的batch_size是否在第一个维度,默认值为False,输入的数据格式为[seq_len,batch_size,feature],如果为True,则为[batch_size,seq_len, feature]dropout :dropout的比例,默认值为0,表示不进行dropout。dropout是一种训练过程中让部分参数随机失活的一种方式,能够提高训练速度,同时能够解决过拟合的问题。这里是在LSTM的最后一层,对每个输出进行dropout。bidirectional :是否使用双向LSTM,默认是False
实例化LSTM对象之后,**不仅需要传入数据,还需要前一次的h_0(前一次的隐藏状态)和c_0(前一次memory)**如果没有传h_0和c_0,网络会自动帮我们生成一个全为0且符合结构的状态。 即:lstm(input,(h_0,c_0)) LSTM的默认输出为output,(h_n,c_n)
output :(seq_len,batch_size,num_directions * hidden_size)h_n :(num_layers*num_directions,batch_size,hidden_size)c_n :(num_layers*num_directions,batch_size,hidden_size)
二、LSTM使用实例
假设数据输入为input,形状是[10,20],假设embedding的形状是[100,30]。 代码如下(示例):
import torch.nn as nn
import torch
batch_size = 10
seq_len = 20
embedding_dim = 30
vocab_size = 100
hidden_size = 18
num_layer = 2
input = torch.randint(low=0,high=100,size=[batch_size,seq_len])
embedding = nn.Embedding(num_embeddings=vocab_size,embedding_dim=embedding_dim)
input_embedd = embedding(input)
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_embedd)
print(output.size())
print("*"*100)
print(h_n.size())
print("*"*100)
print(c_n.size())
GRU API
- 也是torch.nn提供
- GRU(参数同LSTM)
- 输出为:output,h_n = gru(input,h_0)
- 形状同LSTM
2.双向LSTM
如果需要使用双向LSTM,则在实例化LSTM的过程中,需要把LSTM的bidriectional 设为True,同时h_0和c_0使用num_layer*2
- output的拼接顺序:正向的第一个拼接反向的最后一个,把最后一个维度进行拼接。
双向LSTM中: output:按照正反计算的结果顺序把第2个维度进行拼接,正向第一个拼接反向的最后一个输出。 hidden state:按照得到的结果在第0个维度进行拼接,正向第一个之后接着是反向第一个。[layers*num_direction,batch_size,hidden_size]
batch_size = 10
seq_len = 20
embedding_dim = 30
vocab_size = 100
hidden_size = 18
num_layer = 2
input = torch.randint(low=0,high=100,size=[batch_size,seq_len])
embedding = nn.Embedding(num_embeddings=vocab_size,embedding_dim=embedding_dim)
input_embedd = embedding(input)
bilstm = nn.LSTM(input_size=embedding_dim,hidden_size=hidden_size,num_layers=num_layer,batch_first=True,bidirectional=True)
h_0 = torch.rand(num_layer*2,batch_size,hidden_size)
c_0 = torch.rand(num_layer*2,batch_size,hidden_size)
output,(h_n,c_n) = bilstm(input_embedd,(h_0,c_0))
print(output)
3. LSTM和GRU的使用注意点
- 在第一次调用之前,需要初始化隐藏状态,如果不初始化,默认创建全为0的隐藏状态
- 往往会使用LSTM或GRU输出的最后一维的结果,来代表LSTM、GRU对文本处理的结果,其形状为
[batch,num_directions*hidden_size] 。 - 并不是所有模型都会使用最后一维的结果 - 如果实例化LSTM的过程中,batch_first=False ,则output[-1] 或output[-1,:,:] 可以获得最后一维 - 如果实例化LSTM的过程中,batch_first=True ,则output[:,-1,:] 可以获得最后一维 - 如果结果是
(seq_len,batch_size,num_directions*hidden_size) ,需要把它转化为(batch_size,seq_len,num_directions*hidden_size) 的形状,不能够不是view等变形的方法,需要使用output.permute(1,0,2) ,即交换0和1轴,实现上述效果 - 使用双向LSTM的时候,往往会分别使用每个方向最后一次的output,作为当前数据经过双向LSTM的结果
- 即:torch.cat([h_1[-2,:,:],h_1[-1,:,:]],dim=-1) - 最后表示的size是[batch_size,hidden_size*2] - 上述内容在GRU中同理
4. 使用LSTM完成文本情感分类
在前面,我们使用了word embedding去实现了toy级别的文本情感分类,那么现在我们在这个模型中加上LSTM层,观察分类结果。 为了达到更好的效果,对之前的模型做了如下修改:
- max_len = 200
- 构建dataset的过程,把数据转化为2分类的问题,pos为1,neg为0,否则25000个样本完成10个类别的划分,数据量是不够的
- 在实例化LSTM的时候,使用dropout=0.5,在model.eval()的过程中,dropout自动会为0.
class MyModel(nn.Module):
def __init__(self):
super().__init__()
super(MyModel,self).__init__()
self.embedding = nn.Embedding(len(lib.ws),100)
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*100,2)
def forward(self, input):
"""
:param input:[batch_size,max_len]
:return:
"""
x = self.embedding(input)
x,(h_n,c_n) = self.lstm(x)
output_fw = h_n[-2,:,:]
output_bw = h_n[-1,:,:]
output = torch.cat([output_fw,output_bw],dim=-1)
x = output.view([-1,lib.max_len*100])
out = self.fc(x)
return F.log_softmax(out,dim=-1)
model = MyModel().to(lib.device)
optimizer = Adam(model.parameters(),0.001)
if os.path.exists("./model/model.pkl"):
model.load_state_dict(torch.load("./model/model.pkl"))
optimizer.load_state_dict(torch.load("./model/optimizer.pkl"))
def train(epoch):
for idx,(input,target) in enumerate(get_dataloader(train=True)):
input.to(lib.device)
target.to(lib.device)
optimizer.zero_grad()
output = model(input)
loss = F.nll_loss(output,target)
loss.backward()
optimizer.step()
print(epoch,idx,loss.item())
if idx%100 == 0:
torch.save(model.state_dict(),"./model/model.pkl")
torch.save(optimizer.state_dict(), "./model/model.pkl")
if __name__ == '__main__':
for i in range(1):
train(i)
模型评估:
def eval():
loss_list = []
acc_list = []
data_loader = get_dataloader(train=False)
for idx, (input, target) in tqdm(enumerate(data_loader),total=len(data_loader)):
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)
pred = output.max(dim=-1)[-1]
cur_acc = pred.eq(target).float().mean()
acc_list.append(cur_acc)
print("total loss acc :",np.mean(loss_list),np.mean(acc_list))
|