Pytorch之RNN实战

RNN原理

  • 循环神经网络:处理序列模型,权值共享。

    Pytorch之RNN实战

    h[t] = fw(h[t-1], x[t])	#fw is some function with parameters W
    h[t] = tanh(W[h,h]*h[t-1] + W[x,h]*x[t])    #to be specific
    y[t] = W[h,y]*h[t]
    
  • Sequence to Sequence 模型示意图

    Pytorch之RNN实战

  • 语言模型示意图

    Pytorch之RNN实战

RNN实战

  • 3个句子,一个句子10个单词,一个单词100维

    Pytorch之RNN实战

  • 基础用法

    rnn = nn.RNN(100,10)	#input_size(feature_len)=100,hidden_len = 10
    rnn._parameters.keys()  #how many tensor(4个l0层的w和b)
    
    nn.RNN(input_size,hidden_size, num_layers = 1)
    #h0 : [layer, batchsz, hidden]  x : [seq, batchsz, input ]
    #ht : [layer, batchsz, hidden] out: [seq, batchsz, hidden]
    out, ht = forward(x, h0)
    #对于多层rnn,out不变(所有时间最后mem状态),ht变为[2,~,~](所有层最后时间状态)
    
    cell = nn.RNNcell()	#参数与nn.RNN()相同,但是每个时间戳都要输入xt
    for xt in x:
    	h1 = cell(xt,h1)
        
    #如果你想在输入x时按这个格式:[batchsz, seq_len, input]
    #需要在nn.RNN()中加参数: batch_first = True
    
  • 建一个非常简单的RNN模型(以一个简单时间序列预测为例)

    class Net(nn.Module):
    	def __init__(self,input_size,hidden_size):
            super(net, self).__init__()
            self.rnn = nn.RNN(
                input_size=input_size,
                hidden_size=hidden_size,
                num_layers=1,
                batch_first=True,
            )	
            for p in self.rnn.parameters():#正态分布的权值初始化
                nn.init.normal_(p, mean=0.0, std=0.001)
            self.linear = nn.Linear(hidden_size, output_size)
    	def forward(self,x,h0):
        	out, ht = self.rnn(x, h0)	   #out:[batch_sz,seq,hidden_sz]
           	out = out.view(-1, hidden_size)#out:[seq,hidden_size](b=1)
           	out = self.linear(out)		   #out:[seq,output_size]
           	out = out.unsqueeze(dim=0)	   #out:[1,seq,output_size]
           	return out, ht
    
  • 由于我们在对RNN进行反向传播求梯度的时候,最后的求导项里面有一个WhhkW_{hh}^{k}Whhk​,会导致梯度爆炸或梯度消失。

    # 解决梯度爆炸
    for p in net.parameters():
        torch.nn.utils.clip_grad_norm_(p, 10)	# 保证梯度的绝对值小于10 
    # 解决梯度离散:LSTM
    
  • LSTM:Long Short-Term Memory(结构如下图)

    Pytorch之RNN实战
    有三个门:分别是遗忘门—fff,输入门—iii,输出门—ooo。对于输入输出变量:ct1c_{t-1}ct−1​是输入的memory(新增的,为了解决梯度离散增强记忆能力),xtx_txt​是输入, ht1h_{t-1}ht−1​是前一个时间单元的输出,ctc_{t}ct​是传到下一个时间单元的memory。

    • 遗忘门:ft=σ(Wf×[ht1,xt]+bf)f_t = \sigma(W_f \times [h_{t-1},x_{t}]+b_f)ft​=σ(Wf​×[ht−1​,xt​]+bf​)

    • 输入门:it=σ(Wi×[ht1,xt]+bi)i_t = \sigma(W_i \times [h_{t-1},x_{t}]+b_i)it​=σ(Wi​×[ht−1​,xt​]+bi​)

    • 输出门:ot=σ(Wo×[ht1,xt]+bo)o_t = \sigma(W_o \times [h_{t-1},x_{t}]+b_o)ot​=σ(Wo​×[ht−1​,xt​]+bo​)

    • 过滤输入:ct~=tanh(Wc×[ht1,xt]+bc)\widetilde{c_t} = tanh(W_c \times [h_{t-1},x_{t}]+b_c)ct​​=tanh(Wc​×[ht−1​,xt​]+bc​),得到的结果是过滤后的输入

    那么,新的memory就等于 “遗忘门作用后保留的之前的memory” + “输入门作用后保留的新增的过滤后的输入”, 即 ct=ft×ct1+it×ct~c_t = f_t \times c_{t-1} + i_t \times \widetilde{c_t}ct​=ft​×ct−1​+it​×ct​​。而新的输出(h)等于输出门作用后保留的"经tanh处理过的新的memory",即ht=ot×tanh(ct)h_t = o_t \times tanh(c_t)ht​=ot​×tanh(ct​)。

  • LSTM layer

    # initial
    nn.LSTM(input_size,hidden_size, num_layers = 1)
    # forward
    #   x : [seq, batchsz, input]    out : [seq, batchsz, hidden]
    # h/c : [layer, batchsz, hidden]
    out, (ht, ct) = lstm(x, [h0, c0])
    
    # silimar to LSTMcell
    cell = nn.LSTMcell(~)
    for xt in x:
        h, c = cell(xt, [h, c])
    

情感分类问题实战

  • 例如淘宝,对好评差评分类。模型如下。分别对每个词embedding后送入RNN,对所有输出综合出情感类别。

    Pytorch之RNN实战

  • 加载数据集(很重要的包__torchtext__)

    from torchtext import data, datasets
    # data.Field() : 默认在空格上拆分字符串,token设置为spacy表示英语分词
    #				 用来定义字段的处理方法				
    TEXT = data.Field(tokenize='spacy')
    # LabelField是Field的子类,专门用于处理标签
    LABEL = data.LabelField(dtype=torch.float)
    # 加载IMDB电影评论数据集
    train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)
    
  • 使用Glove词向量模型构建语料库,并将处理后的数据进行batch操作。BucketIterator的作用是按相似长度分为若干批,每一批进行对应长度的补齐操作。

    TEXT.build_vocab(train_data,max_size=10000,vectors='glove.6B.100d')
    LABEL.build_vocab(train_data)
    train_iterator, test_iterator = data.BucketIterator.splits(
        (train_data, test_data),
        batch_size = batchsz,
        device=device
    )
    
  • 网络结构

    class LSTM_Net(nn.Module):  
        def __init__(self, vocab_size, embedding_dim, hidden_dim):
            super(LSTM_Net, self).__init__()      
            # [0-10001] => [100] [vb -> embedding]
            self.embedding = nn.Embedding(vocab_size, embedding_dim)
            # [100] => [256] [embedding -> hidden]
            self.lstm = nn.LSTM(embedding_dim, hidden_dim, num_layers=2, 
                               bidirectional=True, dropout=0.5)
            # [256*2] => [1]	 
            self.fc = nn.Linear(hidden_dim*2, 1)
            self.dropout = nn.Dropout(0.5)
        def forward(self, x):
            # [seq, batchsz, 1(字符串)] => [seq, batchsz, 100]
            embedding = self.dropout(self.embedding(x))
            # output: [seq, batchsz, hidden*2] 因为是双层
            # hidden/cell: [layers, batchsz, hidden]
            # hidden 是每个时间戳的输出
            output, (hidden, cell) = self.lstm(embedding)
            # [layers*2, batchsz, hidden] => [batchsz, hidden*2]
            # torch.cat():把前面两个torch按维度1拼接起来
            hidden = torch.cat([hidden[-2], hidden[-1]], dim=1)
            # [batchsz, hidden*2] => [b, 1]
            hidden = self.dropout(hidden)
            out = self.fc(hidden)        
            return out
    
  • Embedding初始化

    rnn = LSTM_Net(len(TEXT.vocab), 100, 256)
    pretrained_embedding = TEXT.vocab.vectors 	# 指定初始权重(由glove得到)
    rnn.embedding.weight.data.copy_(pretrained_embedding)# 将初始化权重导入
    
  • 定义优化器及损失函数

    optimizer = optim.Adam(rnn.parameters(), lr=1e-3)
    criteon = nn.BCEWithLogitsLoss()	# 二分类交叉熵损失函数
    
  • 训练与测试

    def binary_acc(preds, y):
        preds = torch.round(torch.sigmoid(preds))
        correct = torch.eq(preds, y).float()
        acc = correct.sum() / len(correct)
        return acc
    
    def train(rnn, iterator, optimizer, criteon):
        avg_acc = []
        lstm.train()
        for i, batch in enumerate(iterator): 
            # [seq, b] => [b, 1] => [b]
            pred = lstm(batch.text).squeeze(1)
            loss = criteon(pred, batch.label)
            acc = binary_acc(pred, batch.label).item()
            avg_acc.append(acc)
            
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
        avg_acc = np.array(avg_acc).mean()
        print('train acc:', avg_acc)
        
        
    def eval(rnn, iterator, criteon):    
        avg_acc = []
        lstm.eval()
        with torch.no_grad():
            for batch in iterator:
                # [b, 1] => [b]
                pred = lstm(batch.text).squeeze(1)
                loss = criteon(pred, batch.label)
                acc = binary_acc(pred, batch.label).item()
                avg_acc.append(acc)       
        avg_acc = np.array(avg_acc).mean()   
        print('test acc:', avg_acc)
    
Pytorch之RNN实战Pytorch之RNN实战 Hero13146688 发布了11 篇原创文章 · 获赞 5 · 访问量 551 私信 关注
上一篇:过拟合、欠拟合及其解决方案;梯度消失、梯度爆炸;循环神经网络进阶


下一篇:哈夫曼树(最优二叉树)、哈夫曼编码