近日學習LSTM結構,已有很多博客對LSTM結構進行說明,但某些細節仍然存在模糊情況,為此本文將進行補充與說明,可分以下內容:
一.RNN原理簡介與LSTM原理闡釋。
一般來說,RNN的輸入和輸出都是一個序列,分別記為和
,同時
的取值不僅與
有關還與序列中更早的輸入有關(序列中的第t個元素我們叫做序列在time_step=t時的取值)。
注:seqin={x1,x2,...,x3}可假設指代有順序的句子序列長度,如 I Like code ,其中x1=I,x2=Like,x3=code;以此類推seqout指代我們想輸出結果。
更直觀的理解可看下圖:
LSTM是一種特殊的RNN,主要通過三個門控邏輯實現(遺忘、輸入、輸出)。它的提出就是為了解決長序列訓練過程中的梯度消失和梯度爆炸問題。
下圖是一個LSTM結構示意圖,如Xt指代Like單詞:
以上可看出輸出為yt,ct和ht
求解Ct公式
求解ht
σ函數表示sigmoid函數
更詳細解釋如下:
其中, ,
,
是由拼接向量乘以權重矩陣之后,再通過一個
激活函數轉換成0到1之間的數值,來作為一種門控狀態。而
則是將結果通過一個
激活函數將轉換成-1到1之間的值(這里使用
是因為這里是將其做為輸入數據,而不是門控信號)。
與普通RNN類似,輸出 往往最終也是通過
變化得到。
二.LSTM代碼如下:
注:主要調用nn.LSTM
''' 本程序實現了對單詞詞性的判斷,輸入一句話,輸出該句話中每個單詞的詞性。 ''' import torch import torch.nn.functional as F from torch import nn, optim from tqdm import tqdm training_data = [("The dog ate the apple".split(), ["DET", "NN", "V", "DET", "NN"]), ("Everybody read that book".split(), ["NN", "V", "DET", "NN"])] def build_data(training_data): # 構建數據集 # 數據轉換方法 word_to_idx = {} tag_to_idx = {} for context, tag in training_data: for word in context: if word not in word_to_idx: word_to_idx[word] = len(word_to_idx) for label in tag: if label not in tag_to_idx: tag_to_idx[label] = len(tag_to_idx) idx_to_tag = {tag_to_idx[tag]: tag for tag in tag_to_idx} return word_to_idx,tag_to_idx,idx_to_tag class LSTMTagger(nn.Module): def __init__(self, n_word, n_dim, n_hidden, n_tag): super(LSTMTagger, self).__init__() self.word_embedding = nn.Embedding(n_word, n_dim) self.lstm = nn.LSTM(n_dim, n_hidden, batch_first=True) # nn.lstm()接受的數據輸入是(序列長度,batch,輸入維數), # 這和我們cnn輸入的方式不太一致,所以使用batch_first=True,把輸入變成(batch,序列長度,輸入維度),本程序的序列長度指的是一句話的單詞數目 # 同時,batch_first=True會改變輸出的維度順序。<br data-filtered="filtered"> self.linear1 = nn.Linear(n_hidden, n_tag) def forward(self, x): # x是word_list,即單詞的索引列表,size為len(x) x = self.word_embedding(x) # embedding之后,x的size為(len(x),n_dim) x = x.unsqueeze(0) # unsqueeze之后,x的size為(1,len(x),n_dim),1在下一行程序的lstm中被當做是batchsize,len(x)被當做序列長度 x, _ = self.lstm(x) # lstm的隱藏層輸出,x的size為(1,len(x),n_hidden),因為定義lstm網絡時用了batch_first=True,所以1在第一維,如果batch_first=False,則len(x)會在第一維 x = x.squeeze(0) # squeeze之后,x的size為(len(x),n_hidden),在下一行的linear層中,len(x)被當做是batchsize x = self.linear1(x) # linear層之后,x的size為(len(x),n_tag) y = F.log_softmax(x, dim=1) # 對第1維先進行softmax計算,然后log一下。y的size為(len(x),n_tag)。 return y word_to_idx, tag_to_idx, idx_to_tag=build_data(training_data) def main(): # 用於訓練 model = LSTMTagger(len(word_to_idx), 100, 128, len(tag_to_idx)) # 模型初始化 if torch.cuda.is_available(): model = model.cuda() criterion = nn.NLLLoss() optimizer = optim.SGD(model.parameters(), lr=1e-2) for epoch in tqdm(range(200)): running_loss = 0 for data in training_data: sentence, tags = data word_list = [word_to_idx[word] for word in sentence] # word_list是word索引列表 word_list = torch.LongTensor(word_list) tag_list = [tag_to_idx[tag] for tag in tags] # tag_list是tag索引列表 tag_list = torch.LongTensor(tag_list) if torch.cuda.is_available(): word_list = word_list.cuda() tag_list = tag_list.cuda() # forward out = model(word_list) loss = criterion(out, tag_list) running_loss += loss.data.cpu().numpy() # backward optimizer.zero_grad() loss.backward() optimizer.step() print('Epoch: {:<3d} | Loss: {:6.4f}'.format(epoch, running_loss / len(data))) # 模型測試 test_sentence = "Everybody ate the apple" print('\n The test sentence is:\n', test_sentence) test_sentence = test_sentence.split() test_list = [word_to_idx[word] for word in test_sentence] test_list = torch.LongTensor(test_list) if torch.cuda.is_available(): test_list = test_list.cuda() out = model(test_list) _, predict_idx = torch.max(out, 1) # 1表示找行的最大值。 predict_idx是詞性索引,是一個size為([len(test_sentence)]的張量 predict_tag = [idx_to_tag[idx] for idx in list(predict_idx.cpu().numpy())] print('The predict tags are:', predict_tag) if __name__ == '__main__': main()
結果如下:
借鑒內容如下:
https://zhuanlan.zhihu.com/p/32085405
https://zhuanlan.zhihu.com/p/128098497