這篇文章主要介紹Pytorch中常用的幾個迴圈神經網路模型,包括RNN,LSTM,GRU,以及其他相關知識點。
nn.Embedding
在使用各種NLP模型之前,需要將單詞進行向量化,其中,pytorch自帶一個Embedding層,用來實現單詞的編碼。Embedding層 隨機初始化了一個查詢表,他可以將一個詞轉換成一個詞向量。需要注意的是,Embedding層輸入的是一個tensor long 型別,表示讀取第多少個tensor,等於token的數量。
import torch.nn as nn
embeds = nn.Embedding(2,5)
word_to_ix = {"one":0,"hot":1}
token_id = torch.tensor([word_to_ix["one"]],dtype=torch.long)
one_embed = embeds(token_id)
RNN
計算公式如下:
Pytorch中的RNN
nn.RNN(
input_size,
hidden_size,
num_layers,
nonlinearity,
bias,
batch_first,
dropout,
bidirectional
)
input_size: 輸入維度
hidden_size:隱層維度,在RNN中也是輸出的維度
num_layers :RNN的層數,預設為1
nonlinearity :非線性函式,預設為'tanh' , 也可以設定為'relu'
bias : 偏置引數,預設為True
batch_first : 輸入向量第一維度是batch 還是 seq(序列長度), 預設為False
dropout :預設為0,設定是否在RNN層的末尾增加dropout操作
bidirectional : 是否為雙向RNN,預設為False
RNN 中的輸入與輸出
out 等於所有step 中h 的輸出的拼接
x : ( batch, seq , input_size) 當btach_first = True
h0 / ht : (num_layers, batch, hidden_size)
out : (batch, seq, hidden_size)
out , ht = RNN(x, h0)
舉個例子,假設我們輸入為 (3,10,100),表示3個batch,長度為10,每個單詞表徵為100維度。設定hidden_size 為20,這個時候,h的尺寸為[1,3,20]表示1層rnn,3個batch,20是隱層,然後out是[3,10,20].
rnn = nn.RNN(input_size =100,hidden_size = 20,batch_first=True)
x = torch.randn(3,10,100)
out, h = rnn(x,torch.zeros(1,3,20))
print(out.shape)
print(h.shape)
# [3,10,20]
# [1,3,20 ]
也可以直接檢視RNN中的引數的尺寸
rnn = nn.RNN(100,20)
# rnn的引數名為:
# odict_keys(['weight_ih_l0','weight_hh_l0','bias_ih_l0','bias_hh_l0'])
print(rnn.weight_hh_l0.shape)
# [20,20]
多層RNN要注意的是要這是layer的雙向設定,如何layer設定是雙向的,那麼 計算方式如下:
x : ( batch, seq , input_size) 當btach_first = True
h0 / ht : (num_layers2, batch, hidden_size) #只有這裡有區別,num_layers2
out : (batch, seq, hidden_size)
RNNCell
RNNCell 計算公式和RNN一樣,不過RNNCell每次執行只執行一步,因此,RNNCell的尺寸計算方式也會有所區別
RNNCell(
input_size,
hidden_size,
bias=True,
nonlinearity='tanh',
device=None,
dtype=None
)
輸入輸出尺寸:
xt : [batch, wordvec]
ht_1 / ht : [batch, hidden_size]
舉個例子:
cell1 = nn.RNNCell(100,20)
xt = torch.randn(3,100)
h1 = cell1(xt, torch.randn(3,20))
print(h1.shape)
# [3,20]
LSTM
LSTM 的計算公式和結構圖如下所示:包含四個門,輸入門 i 控制多少資訊可以輸入,遺忘門f作用在先前的記憶c^{t-1} 上,作用是過濾一部分之前的記憶,輸出門作用在新的記憶c^{t} 上,得到最終的輸出h
Pytorch 中的LSTM
在Pytorch中LSTM的使用跟RNN一樣,區別是LSTM中的output有兩個,一個是c一個是h,這裡的c和h的尺寸大小是一樣的。
nn.LSTM(
input_size,
hidden_size,
num_layers,
bias,
batch_first,
dropout,
bidirectional
)
input_size: 輸入維度
hidden_size:隱層維度,在RNN中也是輸出的維度
num_layers :RNN的層數,預設為1
nonlinearity :非線性函式,預設為'tanh' , 也可以設定為'relu'
bias : 偏置引數,預設為True
batch_first : 輸入向量第一維度是batch 還是 seq(序列長度), 預設為False
dropout :預設為0,設定是否在RNN層的末尾增加dropout操作
bidirectional : 是否為雙向RNN,預設為False
用法示例:
>>> rnn = nn.LSTM(10, 20, 2)
>>> input = torch.randn(5, 3, 10)
>>> h0 = torch.randn(2, 3, 20)
>>> c0 = torch.randn(2, 3, 20)
>>> output, (hn, cn) = rnn(input, (h0, c0))
LSTM 中的輸入與輸出
與RNN不一樣的是,LSTM 中多了一個記憶引數c, 所以輸入輸出都多了c, 這裡的c 和 h 的尺寸一模一樣。
output, (hn, cn) = rnn(input, (h0, c0))
x : ( batch, seq , input_size) 當btach_first = True
h / c : (num_layers, batch, hidden_size)
output : (batch, seq, hidden_size) # h的拼接
一個例子:
lstm = nn.LSTM(input_size = 100,hidden_size=20,num_layer=4,batch_first=True)
x = torch.randn(3,10,100)
out, (h,c) = lstm(x)
print(out.shape,h.shape,c.shape)
# [3,10,20] [4,3,20] [4,3,20]
LSTMCell
同樣的LSTM也有Cell版本,和上述RNNCell原理一樣,都是隻執行一個時間步
nn.LSTMCell(
input_size,
hidden_size,
bias=True,
device=None,
dtype=None
)
尺寸計算:
xt : [batch, wordvec]
h / c : [batch, hidden_size]
舉個例子:
cell = nn.LSTMCell(100,20)
xt = torch.randn(3,100)
h = torch.zeros(3,20)
c = torch.zeros(3,20)
h_out, c_out = cell(xt, [h,c])
print(h_out.shape)
# [3,20]
通過LSTMCell 構建多層LSTM
cell1 = nn.LSTMCell(100,30)
cell2 = nn.LSTMCell(30,20)
h1 = torch.zeros(3,30)
c1 = torch.zeros(3,30)
h2 = torch.zeros(3,30)
c2 = torch.zeros(3,30)
for xt in x:
h1,c1 = cell1(xt,[h1,c1])
h2,c2 = cell2(h1,[h2,c2])
print(h2.shape,c2.shape)
# [3,20] [3,20]
GRU
GRU的工作原理如下圖所示,其中,重置門rt 用於過濾一部分過去的資訊,更新門zt 用來控制多少保留多少舊資訊和輸出多少新資訊
Pytorch 中的GRU
這裡GRU的引數跟RNN的引數基本一樣,區別是GRU中不能設定啟用函式。
nn.GRU(
input_size,
hidden_size,
num_layers,
bias,
batch_first,
dropout,
bidirectional
)
input_size: 輸入維度
hidden_size:隱層維度,在RNN中也是輸出的維度
num_layers :RNN的層數,預設為1
bias : 偏置引數,預設為True
batch_first : 輸入向量第一維度是batch 還是 seq(序列長度), 預設為False
dropout :預設為0,設定是否在RNN層的末尾增加dropout操作
bidirectional : 是否為雙向RNN,預設為False
序列模型取樣方法
針對一段長文字,如果要使用迴圈神經網路去訓練,必須要做取樣,因為迴圈神經網路無法做長序列學習,這裡常用的取樣方式有兩種。
- 隨機取樣
隨機的選取一段樣本用來訓練,這個時候,每一個batch中的尾巴和下一個batch 的頭部是沒有任何關聯的,所以需要訓練的時候,每一個batch後,需要重新初始化一下隱層,此外還需要對隱層進行detach,中斷計算圖。
- 相鄰取樣
令相鄰的兩個隨機小批量在原始序列上的位置相毗鄰, 這樣由於相鄰的batch是連線起來的,所以這個時候,只需要在第一個batch前初始化一個隱藏狀態,無需在每個batch結束後初始化。另外,由於模型串聯使得梯度計算開銷增大,所以在這裡依然還是要在每個batch 結束後進行detach操作,中斷計算圖。
完整示例
一個lstm網路示例,注意這裡使用Glove來初始化Embedding權重,然後取LSTM最後一層的輸出,喂入到線性層中。
class BLSTM(nn.Module):
def __init__(self,USE_GLOVE,pretrained_emb,token_size,batch_size,device):
super(BLSTM, self).__init__()
self.embedding = nn.Embedding(
num_embeddings=token_size,
embedding_dim=300
)
self.device = device
# Loading the GloVe embedding weights
if USE_GLOVE:
self.embedding.weight.data.copy_(torch.from_numpy(pretrained_emb))
self.text_lstm = nn.LSTM(300,150,num_layers=1,batch_first=True) #b,n,150
self.linear = nn.Sequential(
nn.Linear(150,125),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(125,32),
nn.ReLU(),
nn.Dropout(0.4),
nn.Linear(32,1)
)
def forward(self,text_feat):
text_emb = self.embedding(text_feat)
first_lstm_out = []
init_h = torch.full((1,text_feat.shape[0],150),0).float().to(self.device)
init_c = torch.full((1,text_feat.shape[0],150),0).float().to(self.device)
out,_ = self.text_lstm(text_emb,[init_h,init_c])
out_pre = out[:,-1,:] # get last representation
output = self.linear(out)
return output