1.詞嵌入
nn.Embedding(num_embeddings, embedding_dim, padding_idx=None, max_norm=None, norm_type=2.0, scale_grad_by_freq=False, sparse=False, _weight=None)
其為一個簡單的存儲固定大小的詞典的嵌入向量的查找表,意思是說,給一個編號,嵌入層就能返回這個編號對應的嵌入向量(嵌入向量反映了各個編號代表的符號之間的語義關系)
輸入為一個編號列表,輸出為對應的符號嵌入向量列表。
- num_embeddings(int):詞典的大小尺寸,比如總共出現5000個詞,那就輸入5000。此時index為(0-4999);
- embedding_dim (int): 嵌入向量的維度,即用多少維來表示一個符號;
- padding_idx(int,可選):比如,輸入長度為100,但是每次的句子長度並不一樣,后面就需要用統一的數字填充,而這里就是指定這個數字;
- max_norm(float,可選):最大范數,如果嵌入向量的范數超過了這個界限,就要進行再歸一化;
- norm_type (float, 可選):指定利用什么范數計算,並用於對比max_norm,默認為2范數;
- scale_grad_by_freq (boolean, 可選):根據單詞在mini-batch中出現的頻率,對梯度進行放縮,默認為False;
- sparse (bool, 可選):若為True,則與權重矩陣相關的梯度轉變為稀疏張量;
1 import torch 2 from torch import nn 3 4 word_to_idx = {'hello':0, 'world':1} #給單詞編索引號 5 lookup_tensor = torch.tensor([word_to_idx['hello']], dtype=torch.long) #得到目標單詞索引 6 7 embeds = nn.Embedding(2,5) 8 hello_embed = embeds(lookup_tensor) #傳入單詞的index,返回對應的嵌入向量 9 print(hello_embed)
tensor([[ 0.0254, 1.2267, -0.9689, -0.1929, -0.2187]],grad_fn=<EmbeddingBackward>)
Tip:上面nn.Embedding的那張表是沒有初始化的,得到的嵌入向量是隨機生成的,初始化一般采用現成的編碼方式,把word2vec或者GloVe下載下來,數據內容填充進表。
下面直接使用GloVe方式進行查表操作(提前pip install pytorch-nlp),運行時需要漫長的等待下載。
1 from torchnlp.word_to_vector import GloVe 2 vectors = GloVe() 3 4 print(vectors['hello'])
2.nn.RNN
nn.RNN的數據處理如下圖所示。每次向網絡中輸入batch個樣本,每個時刻處理的是該時刻的batch個樣本,因此xt的shape為[batch,feature_len]的Tensor。例如,輸入3句話,每句話10個單詞,每個單詞用100維的向量表示,那么seq_len=10,batch=3,feature_len=100。
隱藏記憶單元h的shape是二維的[batch,hidden_len],其中hidden_len是一個可以自定的超參數,如可以取為20,表示每個樣本用20長度的向量記錄。
x相當於seq_len個xt
2.1nn.RNN(input_size, hidden_size, num_layers=1, nonlinearity=tanh, bias=True, batch_first=False, dropout=0, bidirectional=False)
參數:
- input_size:輸入特征的維度, 一般rnn中輸入的是詞向量,那么 input_size 就等於一個詞向量的維度,即feature_len;
- hidden_size:隱藏層神經元個數,或者也叫輸出的維度(因為rnn輸出為各個時間步上的隱藏狀態);
- num_layers:網絡的層數;
- nonlinearity:激活函數;
- bias:是否使用偏置;
- batch_first:輸入數據的形式,默認是 False,就是這樣形式,(seq(num_step), batch, input_dim),也就是將序列長度放在第一位,batch 放在第二位;
- dropout:是否應用dropout, 默認不使用,如若使用將其設置成一個0-1的數字即可;
- birdirectional:是否使用雙向的 rnn,默認是 False;
1 from torch import nn 2 3 rnn = nn.RNN(100, 10) #詞向量維度100維,輸出維度10 4 print(rnn._parameters.keys()) #odict_keys(['weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0']) 5 6 print(rnn.weight_hh_l0.shape, rnn.weight_ih_l0.shape) #torch.Size([10, 10]) torch.Size([10, 100]) 7 print(rnn.bias_hh_l0.shape, rnn.bias_ih_l0.shape) #torch.Size([10]) torch.Size([10])
Tip:bias只取hidden_len,等到作加法時會廣播到所有的batch上。
2.2out,ht=forward(x,h0)
- x:[seq_len, batch, feature_len] 它是一次性將所有時刻特征喂入的,不需要每次喂入當前時刻的xt;
- h0/ht:[num_layers, batch, hidden_len] h0是第一個時間戳所有層的記憶單元的Tensor(理解成每一層中每個句子的隱藏輸出);
- out:[seq_len, batch, hidden_len] out是每一個時刻上空間上最后一層的輸出;
1 #單層RNN 2 import torch 3 from torch import nn 4 5 rnn = nn.RNN(100, 20, 1) #feature_len=100, hidden_len=20, 層數=1 6 x = torch.randn(10, 3, 100) #單詞數量(seq_len=10),句子數量(batch=3),每個特征100維度(feature_len=100) 7 out, h = rnn(x, torch.zeros(1, 3, 20)) #傳入RNN處理, 另外傳入h_0, shape是<層數, batch, hidden_len=20> 8 9 print(out.shape) #torch.Size([10, 3, 20]) 10 print(h.shape) #torch.Size([1, 3, 20])
2.3使用nn.RNN構建多層循環網絡
相比2.1處的代碼,只要改變層數即可。
1 from torch import nn 2 3 rnn = nn.RNN(100, 20, 2) #詞向量維度100維,輸出維度20,層數為2 4 print(rnn._parameters.keys()) #odict_keys(['weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0', 'weight_ih_l1', 'weight_hh_l1', 'bias_ih_l1', 'bias_hh_l1']) 5 6 print(rnn.weight_hh_l0.shape, rnn.weight_ih_l0.shape) #torch.Size([20, 20]) torch.Size([20, 100]) 7 print(rnn.weight_hh_l1.shape, rnn.weight_ih_l1.shape) #torch.Size([20, 20]) torch.Size([20, 20])
Tip:從l1層開始接受的輸入都是下面層的輸出,即接受的輸入特征數不再是feature_len而是hidden_len,所以這里參數weight_ih_l1的shape應是[hidden_len,hidden_len]
out,ht=forward(x,h0)
1 import torch 2 from torch import nn 3 4 rnn = nn.RNN(100, 20, 4) #feature_len=100, hidden_len=20, 層數=4 5 x = torch.randn(10, 3, 100) #單詞數量(seq_len=10),句子數量(batch=3),每個特征100維度(feature_len=100) 6 out, h = rnn(x, torch.zeros(4, 3, 20)) #傳入RNN處理, 另外傳入h_0, shape是<層數, batch, hidden_len=20> 7 8 print(out.shape) #torch.Size([10, 3, 20]) 9 print(h.shape) #torch.Size([4, 3, 20])
3.nn.RNNCell
nn.RNN是一次性將所有時刻特征喂入的,nn.RNNCell將序列上的每個時刻分開來處理。
舉例:如果要處理3個句子,每個句子10個單詞,每個單詞用100維的嵌入向量表示,那么nn.RNN傳入的Tensor的shape是[10,3,100],nn.RNNCell傳入的Tensor的shape是[3,100],將此計算單元運行10次。
3.1nn.RNNCell()
初始化方法和上面一樣。
3.2ht=forward(xt, ht-1)
- xt:[batch, feature_len]表示當前時刻的輸入;
- ht-1:[num layers, batch, hidden_len]前一個時刻的單元輸出,ht是下一時刻的單元輸入;
- out:[seq_len, batch, hidden_len] out是所有時間點最后一層的輸出,上面的一條線,層級別(區別ht是最后一個時間所有memory的狀態,右側的一條線,時間戳級別);
1 import torch 2 from torch import nn 3 4 #單層RNN 5 cell1 = nn.RNNCell(100, 20) 6 h1 = torch.zeros(3, 20) 7 x = torch.randn(10, 3, 100) 8 for xt in x: #xt.shape=[3, 100] 9 h1 = cell1(xt, h1) 10 print(h1.shape) #torch.Size([3, 20]) 11 12 13 #多層RNN 14 cell1 = nn.RNNCell(100, 30) 15 cell2 = nn.RNNCell(30, 20) 16 17 h1 = torch.zeros(3, 30) 18 h2 = torch.zeros(3, 20) 19 x = torch.randn(10, 3, 100) 20 for xt in x: 21 h1 = cell1(xt, h1) 22 h2 = cell2(h1, h2) 23 print(h1.shape) #torch.Size([3, 30]) 24 print(h2.shape) #torch.Size([3, 20])