天天看點

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進行反向傳播求梯度的時候,最後的求導項裡面有一個 W h h k W_{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實戰
    有三個門:分别是遺忘門— f f f,輸入門— i i i,輸出門— o o o。對于輸入輸出變量: c t − 1 c_{t-1} ct−1​是輸入的memory(新增的,為了解決梯度離散增強記憶能力), x t x_t xt​是輸入, h t − 1 h_{t-1} ht−1​是前一個時間單元的輸出, c t c_{t} ct​是傳到下一個時間單元的memory。
    • 遺忘門: f t = σ ( W f × [ h t − 1 , x t ] + b f ) f_t = \sigma(W_f \times [h_{t-1},x_{t}]+b_f) ft​=σ(Wf​×[ht−1​,xt​]+bf​)
    • 輸入門: i t = σ ( W i × [ h t − 1 , x t ] + b i ) i_t = \sigma(W_i \times [h_{t-1},x_{t}]+b_i) it​=σ(Wi​×[ht−1​,xt​]+bi​)
    • 輸出門: o t = σ ( W o × [ h t − 1 , x t ] + b o ) o_t = \sigma(W_o \times [h_{t-1},x_{t}]+b_o) ot​=σ(Wo​×[ht−1​,xt​]+bo​)
    • 過濾輸入: c t ~ = t a n h ( W c × [ h t − 1 , x t ] + b c ) \widetilde{c_t} = tanh(W_c \times [h_{t-1},x_{t}]+b_c) ct​

      ​=tanh(Wc​×[ht−1​,xt​]+bc​),得到的結果是過濾後的輸入

    那麼,新的memory就等于 “遺忘門作用後保留的之前的memory” + “輸入門作用後保留的新增的過濾後的輸入”, 即 c t = f t × c t − 1 + i t × c t ~ c_t = f_t \times c_{t-1} + i_t \times \widetilde{c_t} ct​=ft​×ct−1​+it​×ct​

    ​。而新的輸出(h)等于輸出門作用後保留的"經tanh處理過的新的memory",即 h t = o t × t a n h ( c t ) 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)
               

繼續閱讀