1.LSTM模型參數說明
class torch.nn.LSTM(*args, **kwargs)
參數列表
- input_size:x的特征維度
- hidden_size:隱藏層的特征維度
- num_layers:lstm隱層的層數,默認為1
- bias:False則bih=0和bhh=0. 默認為True
- batch_first:True則輸入輸出的數據格式為 (batch, seq, feature)
- dropout:除最后一層,每一層的輸出都進行dropout,默認為: 0
- bidirectional:True則為雙向lstm默認為False
- 輸入:input, (h0, c0)
- 輸出:output, (hn,cn)
輸入數據格式:
input(seq_len, batch, input_size)
h0(num_layers * num_directions, batch, hidden_size)
c0(num_layers * num_directions, batch, hidden_size)
輸出數據格式:
output(seq_len, batch, hidden_size * num_directions)
hn(num_layers * num_directions, batch, hidden_size)
cn(num_layers * num_directions, batch, hidden_size)
Pytorch里的LSTM單元接受的輸入都必須是3維的張量(Tensors).每一維代表的意思不能弄錯。
第一維體現的是序列(sequence)結構,也就是序列的個數,用文章來說,就是每個句子的長度,因為是喂給網絡模型,一般都設定為確定的長度,也就是我們喂給LSTM神經元的每個句子的長度,當然,如果是其他的帶有帶有序列形式的數據,則表示一個明確分割單位長度,
例如是如果是股票數據內,這表示特定時間單位內,有多少條數據。這個參數也就是明確這個層中有多少個確定的單元來處理輸入的數據。
第二維度體現的是batch_size,也就是一次性喂給網絡多少條句子,或者股票數據中的,一次性喂給模型多少是個時間單位的數據,具體到每個時刻,也就是一次性喂給特定時刻處理的單元的單詞數或者該時刻應該喂給的股票數據的條數
第三位體現的是輸入的元素(elements of input),也就是,每個具體的單詞用多少維向量來表示,或者股票數據中 每一個具體的時刻的采集多少具體的值,比如最低價,最高價,均價,5日均價,10均價,等等
H0-Hn是什么意思呢?就是每個時刻中間神經元應該保存的這一時刻的根據輸入和上一課的時候的中間狀態值應該產生的本時刻的狀態值,
這個數據單元是起的作用就是記錄這一時刻之前考慮到所有之前輸入的狀態值,形狀應該是和特定時刻的輸出一致
c0-cn就是開關,決定每個神經元的隱藏狀態值是否會影響的下一時刻的神經元的處理,形狀應該和h0-hn一致。
當然如果是雙向,和多隱藏層還應該考慮方向和隱藏層的層數。
注:上式中的 q后面跟一個單詞,表示該單詞的一定維度的向量表示,該維度即是LSTM接受的張量中的第三個維度。
記住這里存在一個尺寸為1的第二維度。此外,如果你希望一次在網絡中走完整個序列,你可以將第一個維度的尺寸也設為1。
下面我簡單看一下例子:
import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim torch.manual_seed(1) lstm = nn.LSTM(3, 3) # 輸入單詞用一個維度為3的向量表示, 隱藏層的一個維度3,僅有一層的神經元, #記住就是神經元,這個時候神經層的詳細結構還沒確定,僅僅是說這個網絡可以接受[seq_len,batch_size,3]的數據輸入 print(lstm.all_weights) inputs = [torch.randn(1, 3) for _ in range(5)] # 構造一個由5個單單詞組成的句子 構造出來的形狀是 [5,1,3]也就是明確告訴網絡結構我一個句子由5個單詞組成, #每個單詞由一個1X3的向量組成,就是這個樣子[1,2,3] #同時確定了網絡結構,每個批次只輸入一個句子,其中第二維的batch_size很容易迷惑人 #對整個這層來說,是一個批次輸入多少個句子,具體但每個神經元,就是一次性喂給神經元多少個單詞。 print('Inputs:',inputs) # 初始化隱藏狀態 hidden = (torch.randn(1, 1, 3), torch.randn(1, 1, 3)) print('Hidden:',hidden) for i in inputs: # 將序列的元素逐個輸入到LSTM,這里的View是把輸入放到第三維,看起來有點古怪, #回頭看看上面的關於LSTM輸入的描述,這是固定的格式,以后無論你什么形式的數據, #都必須放到這個維度。就是在原Tensor的基礎之上增加一個序列維和MiniBatch維, #這里可能還會有迷惑,前面的1是什么意思啊,就是一次把這個輸入處理完, #在輸入的過程中不會輸出中間結果,這里注意輸入的數據的形狀一定要和LSTM定義的輸入形狀一致。 # 經過每步操作,hidden 的值包含了隱藏狀態的信息 out, hidden = lstm(i.view(1, 1, -1), hidden) print('out1:',out) print('hidden2:',hidden) # 另外, 我們還可以一次對整個序列進行訓練. LSTM 返回的第一個值表示所有時刻的隱狀態值, # 第二個值表示最近的隱狀態值 (因此下面的 "out"的最后一個值和 "hidden" 的值是一樣的). # 之所以這樣設計, 是為了通過 "out" 的值來獲取所有的隱狀態值, 而用 "hidden" 的值來 # 進行序列的反向傳播運算, 具體方式就是將它作為參數傳入后面的 LSTM 網絡. # 增加額外的第二個維度 inputs = torch.cat(inputs).view(len(inputs), 1, -1) hidden = (torch.randn(1, 1, 3), torch.randn(1, 1, 3)) # clean out hidden state out, hidden = lstm(inputs, hidden) print('out2',out) print('hidden3',hidden) 運行輸出: out2 tensor([[[-0.0187, 0.1713, -0.2944]], [[-0.3521, 0.1026, -0.2971]], [[-0.3191, 0.0781, -0.1957]], [[-0.1634, 0.0941, -0.1637]], [[-0.3368, 0.0959, -0.0538]]]) hidden3 (tensor([[[-0.3368, 0.0959, -0.0538]]]), tensor([[[-0.9825, 0.4715, -0.0633]]]))
2.入門小案例
接下來我們要做一件事,
就是訓練網絡幫我我們標注詞性,當然實際的自然語言處理我們有很多成功的算法,但是應對新詞總會有點麻煩,我們想啊,既然神經網絡可以幫我們做了很多神奇的事,那么我們可不可以訓練一個網路模型來幫我我們自動的標注詞性呢,顯然這個思路靠譜,使用神經網絡的套路:
- 准備訓練數據,這一步最是頭大的,最好的辦法就是找各大機構提供的標准的標注庫,實在找不到,自己處理,國內外很多的分詞標准庫和工具可以用,jieba分詞標注是一個不錯的選擇,使用起來也簡單。
- 讀取數據文件
- 分詞
- 把詞語和標注分別放在兩個數組里面
- 構建詞匯表、構建標注表
- 把分詞結果轉換成對應詞匯表和標簽表中的序號。
- 構建網絡模型,這里使用Word2Vec預處理一下輸入文本
- 訓練網絡
- 分析結果
import jieba.posseg import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim # import sys import gensim torch.manual_seed(2) # sys.stdout = open('1.log', 'a') sent='明天是榮耀運營十周年紀念日。' \ '榮耀從兩周年紀念日開始,' \ '在每年的紀念日這天凌晨零點會開放一個新區。' \ '第十版賬號卡的銷售從三個月前就已經開始。' \ '在老區玩的不順心的老玩家、准備進入榮耀的新手,都已經准備好了新區賬號對這個日子翹首以盼。' \ '陳果坐到了葉修旁邊的機器,隨手登錄了她的逐煙霞。' \ '其他九大區的玩家人氣並沒有因為第十區的新開而降低多少,' \ '越老的區越是如此,實在是因為榮耀的一個賬號想經營起來並不容易。' \ '陳果的逐煙霞用了五年時間才在普通玩家中算是翹楚,哪舍得輕易拋棄。' \ '更何況到最后大家都會沖着十大區的共同地圖神之領域去。' words=jieba.posseg.cut(sent,HMM=True) #分詞 processword=[] tagword=[] for w in words: processword.append(w.word) tagword.append(w.flag) #詞語和對應的詞性做一一對應 texts=[(processword,tagword)] #使用gensim構建本例的詞匯表 id2word=gensim.corpora.Dictionary([texts[0][0]]) #每個詞分配一個獨特的ID word2id=id2word.token2id #使用gensim構建本例的詞性表 id2tag=gensim.corpora.Dictionary([texts[0][1]]) #為每個詞性分配ID tag2id=id2tag.token2id def sen2id(inputs): return [word2id[word] for word in inputs] def tags2id(inputs): return [tag2id[word] for word in inputs] #根據詞匯表把文本輸入轉換成對應的詞匯表的序號張量 def formart_input(inputs): return torch.tensor(sen2id(inputs),dtype=torch.long) #根據詞性表把文本標注輸入轉換成對應的詞匯標注的張量 def formart_tag(inputs): return torch.tensor(tags2id(inputs),dtype=torch.long) #定義網絡結構 class LSTMTagger(torch.nn.Module): def __init__(self,embedding_dim,hidden_dim,voacb_size,target_size): super(LSTMTagger,self).__init__() self.embedding_dim=embedding_dim self.hidden_dim=hidden_dim self.voacb_size=voacb_size self.target_size=target_size # 使用Word2Vec預處理一下輸入文本 self.embedding=nn.Embedding(self.voacb_size,self.embedding_dim) # LSTM 以 word_embeddings 作為輸入, 輸出維度為 hidden_dim 的隱狀態值 self.lstm=nn.LSTM(self.embedding_dim,self.hidden_dim) ## 線性層將隱狀態空間映射到標注空間 self.out2tag=nn.Linear(self.hidden_dim,self.target_size) self.hidden = self.init_hidden() def init_hidden(self): # 開始時刻, 沒有隱狀態 # 關於維度設置的詳情,請參考 Pytorch 文檔 # 各個維度的含義是 (Seguence, minibatch_size, hidden_dim) return (torch.zeros(1, 1, self.hidden_dim), torch.zeros(1, 1, self.hidden_dim)) def forward(self,inputs): # 預處理文本轉成稠密向量 embeds=self.embedding((inputs)) #根據文本的稠密向量訓練網絡 out,self.hidden=self.lstm(embeds.view(len(inputs),1,-1),self.hidden) #做出預測 tag_space=self.out2tag(out.view(len(inputs),-1)) tags=F.log_softmax(tag_space,dim=1) return tags model=LSTMTagger(10,10,len(word2id),len(tag2id)) loss_function=nn.NLLLoss() optimizer=optim.SGD(model.parameters(),lr=0.1) #看看隨機初始化網絡的分析結果 with torch.no_grad(): input_s=formart_input(texts[0][0]) print(input_s) print(processword) tag_s=model(input_s) for i in range(tag_s.shape[0]): print(tag_s[i]) # print(tag_s) for epoch in range(300): # 再說明下, 實際情況下你不會訓練300個周期, 此例中我們只是構造了一些假數據 for p ,t in texts: # Step 1. 請記住 Pytorch 會累加梯度 # 每次訓練前需要清空梯度值 model.zero_grad() # 此外還需要清空 LSTM 的隱狀態 # 將其從上個實例的歷史中分離出來 # 重新初始化隱藏層數據,避免受之前運行代碼的干擾,如果不重新初始化,會有報錯。 model.hidden = model.init_hidden() # Step 2. 准備網絡輸入, 將其變為詞索引的Tensor 類型數據 sentence_in=formart_input(p) tags_in=formart_tag(t) # Step 3. 前向傳播 tag_s=model(sentence_in) # Step 4. 計算損失和梯度值, 通過調用 optimizer.step() 來更新梯度 loss=loss_function(tag_s,tags_in) loss.backward() print('Loss:',loss.item()) optimizer.step() #看看訓練后的結果 with torch.no_grad(): input_s=formart_input(texts[0][0]) tag_s=model(input_s) for i in range(tag_s.shape[0]): print(tag_s[i])
參考文獻:https://zhuanlan.zhihu.com/p/41261640