使用Pytorch從零開始構建LSTM

長短期記憶(LSTM)網絡已被廣泛用于解決各種順序任務。讓我們了解這些網絡如何工作以及如何實施它們。

就像我們一樣,循環神經網絡(RNN)也可能很健忘。這種與短期記憶的斗爭導致 RNN 在大多數任務中失去有效性。不過,不用擔心,長短期記憶網絡 (LSTM) 具有出色的記憶力,可以記住普通 RNN 無法記住的信息!

LSTM 是 RNN 的一種特殊變體,因此掌握 RNN 相關的概念將極大地幫助您理解本文中的 LSTM。我在上一篇文章中介紹了 RNN 的機制。

RNN 快速回顧

RNN 以順序方式處理輸入,其中在計算當前步驟的輸出時考慮先前輸入的上下文。這允許神經網絡在不同的時間步長上攜帶信息,而不是保持所有輸入彼此獨立。
在這里插入圖片描述
然而,困擾典型 RNN 的一個重大缺點是梯度消失/爆炸問題。在訓練過程中通過 RNN 反向傳播時會出現此問題,特別是對于具有更深層的網絡。由于鏈式法則,梯度在反向傳播過程中必須經過連續的矩陣乘法,導致梯度要么呈指數收縮(消失),要么呈指數爆炸(爆炸)。梯度太小會阻礙權重的更新和學習,而梯度太大會導致模型不穩定。

由于這些問題,RNN 無法處理較長的序列并保持長期依賴性,從而使它們遭受“短期記憶”的困擾。

什么是 LSTM

雖然 LSTM 是一種 RNN,其功能與傳統 RNN 類似,但它的門控機制使其與眾不同。該功能解決了 RNN 的“短期記憶”問題。
在這里插入圖片描述
從圖中我們可以看出,差異主要在于 LSTM 保存長期記憶的能力。這在大多數自然語言處理 (NLP) 或時間序列和順序任務中尤其重要。例如,假設我們有一個網絡根據給我們的一些輸入生成文本。文章開頭提到作者有一只“名叫克里夫的狗”。在其他幾個沒有提到寵物或狗的句子之后,作者再次提到了他的寵物,模型必須生成下一個單詞“但是,克里夫,我的寵物____”。由于單詞 pet 出現在空白之前,RNN 可以推斷出下一個單詞可能是可以作為寵物飼養的動物。
hello
然而,由于短期記憶的原因,典型的 RNN 只能使用最后幾句話中出現的文本中的上下文信息——這根本沒有用處。RNN 不知道寵物可能是什么動物,因為文本開頭的相關信息已經丟失。

另一方面,LSTM 可以保留作者有一只寵物狗的早期信息,這將有助于模型在生成文本時根據大量上下文信息選擇“狗” 。較早的時間步長。
在這里插入圖片描述

LSTM 的內部工作原理

LSTM 的秘密在于每個 LSTM 單元內的門控機制。在普通 RNN 單元中,某個時間步的輸入和前一個時間步的隱藏狀態通過tanh激活函數來獲得新的隱藏狀態和輸出。
在這里插入圖片描述
另一方面,LSTM 的結構稍微復雜一些。在每個時間步,LSTM 單元都會接收 3 條不同的信息:當前輸入數據、前一個單元的短期記憶(類似于 RNN 中的隱藏狀態)以及最后的長期記憶。
短期記憶通常稱為隱藏狀態,長期記憶通常稱為細胞狀態。

然后,在將長期和短期信息傳遞到下一個單元之前,單元使用門來調節每個時間步要保留或丟棄的信息。

這些門可以看作是水過濾器。理想情況下,這些門的作用應該是選擇性地去除任何不相關的信息,類似于水過濾器如何防止雜質通過。同時,只有水和有益的營養物質才能通過這些過濾器,就像大門只保留有用的信息一樣。當然,這些門需要經過訓練才能準確過濾有用的內容和無用的內容。
在這里插入圖片描述

這些門稱為輸入門、遺忘門和輸出門。這些門的名稱有多種變體。然而,這些門的計算和工作原理大部分是相同的。
在這里插入圖片描述
讓我們一一了解這些門的機制。

輸入門

輸入門決定哪些新信息將存儲在長期記憶中。它僅適用于當前輸入的信息和上一時間步的短期記憶。因此,它必須從這些變量中過濾掉無用的信息。
在這里插入圖片描述

從數學上講,這是使用2 層來實現的。第一層可以看作是過濾器,它選擇哪些信息可以通過它以及哪些信息被丟棄。為了創建這一層,我們將短期記憶和當前輸入傳遞給sigmoid函數。sigmoid函數會將值轉換為0到1之間,0表示部分信息不重要,而1表示該信息將被使用。這有助于決定要保留和使用的值,以及要丟棄的值。當該層通過反向傳播進行訓練時,sigmoid函數中的權重將被更新,以便它學會只讓有用的特征通過,同時丟棄不太重要的特征。
i 1 = σ ( W i 1 ? ( H t ? 1 , x t ) + b i a s i 1 ) i_1 = \sigma(W_{i_1} \cdot (H_{t-1}, x_t) + bias_{i_1}) i1?=σ(Wi1???(Ht?1?,xt?)+biasi1??)
第二層也采用短期記憶和當前輸入,并將其傳遞給激活函數(通常是 t a n h tanh tanh函數)來調節網絡。
i 2 = t a n h ( W i 2 ? ( H t ? 1 , x t ) + b i a s i 2 ) i_2 = tanh(W_{i_2} \cdot (H_{t-1}, x_t) + bias_{i_2}) i2?=tanh(Wi2???(Ht?1?,xt?)+biasi2??)
然后將這兩層的輸出相乘,最終結果表示要保存在長期記憶中并用作輸出的信息。
i i n p u t = i 1 ? i 2 i_{input} = i_1 * i_2 iinput?=i1??i2?

遺忘門

遺忘門決定應保留或丟棄長期記憶中的哪些信息。這是通過將傳入的長期記憶乘以當前輸入和傳入的短期記憶生成的遺忘向量來完成的。
在這里插入圖片描述
就像輸入門中的第一層一樣,遺忘向量也是一個選擇性過濾層。為了獲得遺忘向量,短期記憶和當前輸入通過sigmoid函數傳遞,類似于上面輸入門中的第一層,但具有不同的權重。該向量將由 0 和 1 組成,并將與長期記憶相乘以選擇要保留長期記憶的哪些部分。
f = σ ( W f o r g e t ? ( H t ? 1 , x t ) + b i a s f o r g e t ) f = \sigma(W_{forget} \cdot (H_{t-1}, x_t) + bias_{forget}) f=σ(Wforget??(Ht?1?,xt?)+biasforget?)
輸入門和遺忘門的輸出將進行逐點加法,以給出新版本的長期記憶,并將其傳遞到下一個單元。這個新的長期記憶也將用于最后一個門,即輸出門。
C t = C t ? 1 ? f + i i n p u t C_t = C_{t-1} * f + i_{input} Ct?=Ct?1??f+iinput?

輸出門

輸出門將采用當前輸入、先前的短期記憶和新計算的長期記憶來產生新的短期記憶/隱藏狀態 ,該狀態將在下一個時間步驟中傳遞到單元。當前時間步的輸出也可以從這個隱藏狀態中得出。
在這里插入圖片描述
首先,之前的短期記憶和當前輸入將再次以不同的權重傳遞到 sigmoid 函數(是的,這是我們第三次這樣做),以創建第三個也是最后一個過濾器。然后,我們將新的長期記憶通過激活 t a n h tanh tanh函數。這兩個過程的輸出將相乘以產生新的短期記憶。
O 1 = σ ( W o u t p u t 1 ? ( H t ? 1 , x t ) + b i a s o u t p u t 1 ) O 2 = t a n h ( W o u t p u t 2 ? C t + b i a s o u t p u t 2 ) H t , O t = O 1 ? O 2 O_1 = \sigma (W_{output_1} \cdot (H_{t-1}, x_t) + bias_{output_1})\\ O_2 = tanh(W_{output_2} \cdot C_t + bias_{output_2})\\ H_t, O_t = O_1 * O_2 O1?=σ(Woutput1???(Ht?1?,xt?)+biasoutput1??)O2?=tanh(Woutput2???Ct?+biasoutput2??)Ht?,Ot?=O1??O2?
這些門產生的短期和長期記憶將被轉移到下一個單元,以重復該過程。每個時間步的輸出可以從短期記憶中獲得,也稱為隱藏狀態。

這就是典型 LSTM 結構的全部機制。沒那么難吧?

代碼實現

對 LSTM 有了必要的理論了解后,讓我們開始在代碼中實現它。今天我們將使用 PyTorch 庫。

在我們進入具有完整數據集的項目之前,讓我們通過可視化輸出來看看 PyTorch LSTM 層在實踐中的實際工作原理。我們不需要實例化模型來查看該層如何工作。您可以使用下面的按鈕在 FloydHub 上運行此程序LSTM_starter.ipynb。(這部分不需要在 GPU 上運行)

import torch
import torch.nn as nn

就像其他類型的層一樣,我們可以實例化 LSTM 層并為其提供必要的參數。可以在此處找到已接受參數的完整文檔。在此示例中,我們將僅定義輸入維度、隱藏維度和層數。

  • 輸入維度- 表示每個時間步輸入的大小,例如維度 5 的輸入將如下所示 [1, 3, 8, 2, 3]
  • 隱藏維度- 表示每個時間步隱藏狀態和細胞狀態的大小,例如,如果隱藏維度為 3,則隱藏狀態和細胞狀態都將具有 [3, 5, 4] 的形狀
  • 層數 - 彼此堆疊的 LSTM 層數
input_dim = 5
hidden_dim = 10
n_layers = 1lstm_layer = nn.LSTM(input_dim, hidden_dim, n_layers, batch_first=True)

讓我們創建一些虛擬數據來查看該層如何接收輸入。由于我們的輸入維度是5,我們必須創建一個形狀為 ( 1, 1, 5 ) 的張量,它表示(批量大小、序列長度、輸入維度)。

此外,我們必須初始化 LSTM 的隱藏狀態和單元狀態,因為這是第一個單元。隱藏狀態和單元狀態存儲在格式為 ( hidden_??state , cell_state ) 的元組中。

batch_size = 1
seq_len = 1inp = torch.randn(batch_size, seq_len, input_dim)
hidden_state = torch.randn(n_layers, batch_size, hidden_dim)
cell_state = torch.randn(n_layers, batch_size, hidden_dim)
hidden = (hidden_state, cell_state)
[Out]:
Input shape: (1, 1, 5)
Hidden shape: ((1, 1, 10), (1, 1, 10))

接下來,我們將提供輸入和隱藏狀態,看看我們會從中得到什么。

out, hidden = lstm_layer(inp, hidden)
print("Output shape: ", out.shape)
print("Hidden: ", hidden)
[Out]: Output shape: torch.size([1, 1, 10])Hidden: (tensor([[[ 0.1749,  0.0099, -0.3004,  0.2846, -0.2262, -0.5257,  0.2925, -0.1894,  0.1166, -0.1197]]], grad_fn=<StackBackward>), tensor([[[ 0.4167,  0.0385, -0.4982,  0.6955, -0.9213, -1.0072,  0.4426,-0.3691,  0.2020, -0.2242]]], grad_fn=<StackBackward>))

在上面的過程中,我們看到了 LSTM 單元如何在每個時間步處理輸入和隱藏狀態。然而,在大多數情況下,我們將以大序列處理輸入數據。LSTM 還可以接收可變長度的序列并在每個時間步產生輸出。這次我們嘗試改變序列長度。

seq_len = 3
inp = torch.randn(batch_size, seq_len, input_dim)
out, hidden = lstm_layer(inp, hidden)
print(out.shape)
[Out]: torch.Size([1, 3, 10])

這次,輸出的第二維是 3,表明 LSTM 給出了 3 個輸出。這對應于我們輸入序列的長度。對于我們需要在每個時間步(多對多)輸出的用例,例如文本生成,每個時間步的輸出可以直接從第二維提取并輸入到完全連接的層中。對于文本分類任務(多對一),例如情感分析,可以將最后的輸出輸入到分類器中。

在這里插入圖片描述

# Obtaining the last output
out = out.squeeze()[-1, :]
print(out.shape)
[Out]: torch.Size([10])

項目:亞馬遜評論情緒分析

對于此項目,我們將使用 Amazon 客戶評論數據集,該數據集可以在Kaggle上找到。該數據集總共包含 400 萬條評論,每條評論都標記為正面或負面情緒, 可以在此處找到 GitHub 存儲庫的鏈接。

我們實施此項目時的目標是創建一個 LSTM 模型,能夠準確分類和區分評論的情緒。為此,我們必須從一些數據預處理、定義和訓練模型開始,然后評估模型。

我們的實現流程如下所示。
在這里插入圖片描述
我們在此實現中僅使用 100 萬條評論來加快速度,但是,如果您有時間和計算能力,請隨意使用整個數據集自行運行它。

對于我們的數據預處理步驟,我們將使用regex、Numpy和NLTK(自然語言工具包)庫來實現一些簡單的 NLP 輔助函數。由于數據以bz2格式壓縮,因此我們將使用 Python bz2模塊來讀取數據。

import bz2
from collections import Counter
import re
import nltk
import numpy as np
nltk.download('punkt')train_file = bz2.BZ2File('../input/amazon_reviews/train.ft.txt.bz2')
test_file = bz2.BZ2File('../input/amazon_reviews/test.ft.txt.bz2')train_file = train_file.readlines()
test_file = test_file.readlines()

Number of training reviews: 3600000
Number of test reviews: 400000

該數據集總共包含 400 萬條評論,其中 360 萬條用于訓練,40 萬條用于測試。我們將僅使用 800k 進行訓練,200k 進行測試——這仍然是大量數據。

num_train = 800000  # We're training on the first 800,000 reviews in the dataset
num_test = 200000  # Using 200,000 reviews from test settrain_file = [x.decode('utf-8') for x in train_file[:num_train]]
test_file = [x.decode('utf-8') for x in test_file[:num_test]]

句子的格式如下:

__label__2 Stunning even for the non-gamer: This soundtrack was beautiful! It paints the scenery in your mind so well I would recommend it even to people who hate vid. game music! I have played the game Chrono Cross but out of all of the games I have ever played it has the best music! It backs away from crude keyboarding and takes a fresher step with great guitars and soulful orchestras. It would impress anyone who cares to listen! _

我們必須從句子中提取標簽。數據是格式__label__1/2 ,因此我們可以輕松地相應地分割它。積極情緒標簽存儲為 1,消極情緒標簽存儲為 0。

我們還將所有URL更改為標準,<url>因為在大多數情況下,確切的URL與情緒無關。

# Extracting labels from sentences
train_labels = [0 if x.split(' ')[0] == '__label__1' else 1 for x in train_file]
train_sentences = [x.split(' ', 1)[1][:-1].lower() for x in train_file]test_labels = [0 if x.split(' ')[0] == '__label__1' else 1 for x in test_file]
test_sentences = [x.split(' ', 1)[1][:-1].lower() for x in test_file]# Some simple cleaning of data
for i in range(len(train_sentences)):train_sentences[i] = re.sub('\d','0',train_sentences[i])for i in range(len(test_sentences)):test_sentences[i] = re.sub('\d','0',test_sentences[i])# Modify URLs to <url>
for i in range(len(train_sentences)):if 'www.' in train_sentences[i] or 'http:' in train_sentences[i] or 'https:' in train_sentences[i] or '.com' in train_sentences[i]:train_sentences[i] = re.sub(r"([^ ]+(?<=\.[a-z]{3}))", "<url>", train_sentences[i])for i in range(len(test_sentences)):if 'www.' in test_sentences[i] or 'http:' in test_sentences[i] or 'https:' in test_sentences[i] or '.com' in test_sentences[i]:test_sentences[i] = re.sub(r"([^ ]+(?<=\.[a-z]{3}))", "<url>", test_sentences[i])

快速清理數據后,我們將對句子進行標記化,這是標準的 NLP 任務。

標記化是將句子分割成單個標記的任務,這些標記可以是單詞或標點符號等。

有許多 NLP 庫可以做到這一點,例如spaCy或Scikit-learn,但我們將在這里使用NLTK,因為它具有更快的分詞器之一。
然后,這些單詞將被存儲在字典中,將單詞映射到其出現次數。這些詞將成為我們的詞匯。

words = Counter()  # Dictionary that will map a word to the number of times it appeared in all the training sentences
for i, sentence in enumerate(train_sentences):# The sentences will be stored as a list of words/tokenstrain_sentences[i] = []for word in nltk.word_tokenize(sentence):  # Tokenizing the wordswords.update([word.lower()])  # Converting all the words to lowercasetrain_sentences[i].append(word)if i%20000 == 0:print(str((i*100)/num_train) + "% done")
print("100% done")
  • 為了刪除可能不存在的拼寫錯誤和單詞,我們將從詞匯表中刪除僅出現一次的所有單詞。
  • 為了解決未知單詞和填充問題,我們還必須將它們添加到我們的詞匯表中。然后,詞匯表中的每個單詞將被分配一個整數索引,然后映射到該整數。
# Removing the words that only appear once
words = {k:v for k,v in words.items() if v>1}
# Sorting the words according to the number of appearances, with the most common word being first
words = sorted(words, key=words.get, reverse=True)
# Adding padding and unknown to our vocabulary so that they will be assigned an index
words = ['_PAD','_UNK'] + words
# Dictionaries to store the word to index mappings and vice versa
word2idx = {o:i for i,o in enumerate(words)}
idx2word = {i:o for i,o in enumerate(words)}

通過映射,我們將句子中的單詞轉換為其相應的索引。

for i, sentence in enumerate(train_sentences):# Looking up the mapping dictionary and assigning the index to the respective wordstrain_sentences[i] = [word2idx[word] if word in word2idx else 0 for word in sentence]for i, sentence in enumerate(test_sentences):# For test sentences, we have to tokenize the sentences as welltest_sentences[i] = [word2idx[word.lower()] if word.lower() in word2idx else 0 for word in nltk.word_tokenize(sentence)]

在最后的預處理步驟中,我們將用 0 填充句子并縮短冗長的句子,以便可以批量訓練數據以加快速度。

# Defining a function that either shortens sentences or pads sentences with 0 to a fixed length
def pad_input(sentences, seq_len):features = np.zeros((len(sentences), seq_len),dtype=int)for ii, review in enumerate(sentences):if len(review) != 0:features[ii, -len(review):] = np.array(review)[:seq_len]return featuresseq_len = 200  # The length that the sentences will be padded/shortened totrain_sentences = pad_input(train_sentences, seq_len)
test_sentences = pad_input(test_sentences, seq_len)# Converting our labels into numpy arrays
train_labels = np.array(train_labels)
test_labels = np.array(test_labels)

填充的句子看起來像這樣,其中 0 代表填充:

array([    0,     0,     0,     0,     0,     0,     0,     0,     0,0,     0,     0,     0,     0,     0,     0,     0,     0,0,     0,     0,     0,     0,     0,     0,     0,     0,0,     0,     0,     0,     0,     0,     0,     0,     0,0,     0,     0,     0,     0,     0,     0,     0,     0,0,     0,     0,     0,     0,     0,     0,     0,     0,0,     0,     0,     0,     0,     0,     0,     0,     0,0,     0,     0,     0,     0,     0,     0,     0,     0,0,     0,    44,   125,    13,    28,  1701,  5144,    60,31,    10,     3,    44,  2052,    10,    84,  2131,     2,5,    27,  1336,     8,    11,   125,    17,   153,     6,5,   146,   103,     9,     2,    64,     5,   117,    14,7,    42,  1680,     9,   194,    56,   230,   107,     2,7,   128,  1680,    52, 31073,    41,  3243,    14,     3,3674,     2,    11,   125,    52, 10669,   156,     2,  1103,29,     0,     0,     6,   917,    52,  1366,     2,    31,10,   156,    23,  2071,  3574,     2,    11,    12,     7,2954,  9926,   125,    14,    28,    21,     2,   180,    95,132,   147,     9,   220,    12,    52,   718,    56,     2,2339,     5,   272,    11,     4,    72,   695,   562,     4,722,     4,   425,     4,   163,     4,  1491,     4,  1132,1829,   520,    31,   169,    34,    77,    18,    16,  1107,69,    33])

我們的數據集已經分為訓練數據和測試數據。然而,我們在訓練過程中仍然需要一組數據進行驗證。因此,我們將測試數據分成兩半,分為驗證集和測試集。可以在此處找到數據集拆分的詳細說明。

split_frac = 0.5 # 50% validation, 50% test
split_id = int(split_frac * len(test_sentences))
val_sentences, test_sentences = test_sentences[:split_id], test_sentences[split_id:]
val_labels, test_labels = test_labels[:split_id], test_labels[split_id:]

接下來,我們將開始使用 PyTorch 庫。我們首先從句子和標簽定義數據集,然后將它們加載到數據加載器中。我們將批量大小設置為 256。這可以根據您的需要進行調整。

import torch
from torch.utils.data import TensorDataset, DataLoader
import torch.nn as nntrain_data = TensorDataset(torch.from_numpy(train_sentences), torch.from_numpy(train_labels))
val_data = TensorDataset(torch.from_numpy(val_sentences), torch.from_numpy(val_labels))
test_data = TensorDataset(torch.from_numpy(test_sentences), torch.from_numpy(test_labels))batch_size = 400train_loader = DataLoader(train_data, shuffle=True, batch_size=batch_size)
val_loader = DataLoader(val_data, shuffle=True, batch_size=batch_size)
test_loader = DataLoader(test_data, shuffle=True, batch_size=batch_size)

我們還可以檢查是否有 GPU 可以將訓練時間加快很多倍。如果您使用帶有 GPU 的 FloydHub 來運行此代碼,訓練時間將顯著減少。

# torch.cuda.is_available() checks and returns a Boolean True if a GPU is available, else it'll return False
is_cuda = torch.cuda.is_available()# If we have a GPU available, we'll set our device to GPU. We'll use this device variable later in our code.
if is_cuda:device = torch.device("cuda")
else:device = torch.device("cpu")

此時,我們將定義模型的架構。在此階段,我們可以創建具有深層或大量相互堆疊的 LSTM 層的神經網絡。然而,像下面這樣的簡單模型,只有一個 LSTM 和一個全連接層,效果很好,并且需要的訓練時間要少得多。在將句子輸入 LSTM 層之前,我們將在第一層訓練我們自己的詞嵌入。
在這里插入圖片描述
最后一層是一個全連接層,具有 sigmoid 函數,用于對評論是否具有積極/消極情緒進行分類。

class SentimentNet(nn.Module):def __init__(self, vocab_size, output_size, embedding_dim, hidden_dim, n_layers, drop_prob=0.5):super(SentimentNet, self).__init__()self.output_size = output_sizeself.n_layers = n_layersself.hidden_dim = hidden_dimself.embedding = nn.Embedding(vocab_size, embedding_dim)self.lstm = nn.LSTM(embedding_dim, hidden_dim, n_layers, dropout=drop_prob, batch_first=True)self.dropout = nn.Dropout(drop_prob)self.fc = nn.Linear(hidden_dim, output_size)self.sigmoid = nn.Sigmoid()def forward(self, x, hidden):batch_size = x.size(0)x = x.long()embeds = self.embedding(x)lstm_out, hidden = self.lstm(embeds, hidden)lstm_out = lstm_out.contiguous().view(-1, self.hidden_dim)out = self.dropout(lstm_out)out = self.fc(out)out = self.sigmoid(out)out = out.view(batch_size, -1)out = out[:,-1]return out, hiddendef init_hidden(self, batch_size):weight = next(self.parameters()).datahidden = (weight.new(self.n_layers, batch_size, self.hidden_dim).zero_().to(device),weight.new(self.n_layers, batch_size, self.hidden_dim).zero_().to(device))return hidden

請注意,我們實際上可以加載預先訓練的詞嵌入,例如GloVe或fastText,這可以提高模型的準確性并減少訓練時間。

這樣,我們就可以在定義參數后實例化我們的模型。輸出維度僅為 1,因為它只需要輸出 1 或 0。還定義了學習率、損失函數和優化器。

vocab_size = len(word2idx) + 1
output_size = 1
embedding_dim = 400
hidden_dim = 512
n_layers = 2model = SentimentNet(vocab_size, output_size, embedding_dim, hidden_dim, n_layers)
model.to(device)lr=0.005
criterion = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)

最后,我們可以開始訓練模型。每 1000 個步驟,我們將根據驗證數據集檢查模型的輸出,如果模型的表現比前一次更好,則保存模型。

state_dict是 PyTorch 中模型的權重,可以在單獨的時間或腳本中加載到具有相同架構的模型中。

epochs = 2
counter = 0
print_every = 1000
clip = 5
valid_loss_min = np.Infmodel.train()
for i in range(epochs):h = model.init_hidden(batch_size)for inputs, labels in train_loader:counter += 1h = tuple([e.data for e in h])inputs, labels = inputs.to(device), labels.to(device)model.zero_grad()output, h = model(inputs, h)loss = criterion(output.squeeze(), labels.float())loss.backward()nn.utils.clip_grad_norm_(model.parameters(), clip)optimizer.step()if counter%print_every == 0:val_h = model.init_hidden(batch_size)val_losses = []model.eval()for inp, lab in val_loader:val_h = tuple([each.data for each in val_h])inp, lab = inp.to(device), lab.to(device)out, val_h = model(inp, val_h)val_loss = criterion(out.squeeze(), lab.float())val_losses.append(val_loss.item())model.train()print("Epoch: {}/{}...".format(i+1, epochs),"Step: {}...".format(counter),"Loss: {:.6f}...".format(loss.item()),"Val Loss: {:.6f}".format(np.mean(val_losses)))if np.mean(val_losses) <= valid_loss_min:torch.save(model.state_dict(), './state_dict.pt')print('Validation loss decreased ({:.6f} --> {:.6f}).  Saving model ...'.format(valid_loss_min,np.mean(val_losses)))valid_loss_min = np.mean(val_losses)

完成訓練后,是時候在以前從未見過的數據集(我們的測試數據集)上測試我們的模型了。我們首先從驗證損失最低的點加載模型權重。

我們可以計算模型的準確性,看看我們的模型的預測有多準確。

# Loading the best model
model.load_state_dict(torch.load('./state_dict.pt'))test_losses = []
num_correct = 0
h = model.init_hidden(batch_size)model.eval()
for inputs, labels in test_loader:h = tuple([each.data for each in h])inputs, labels = inputs.to(device), labels.to(device)output, h = model(inputs, h)test_loss = criterion(output.squeeze(), labels.float())test_losses.append(test_loss.item())pred = torch.round(output.squeeze())  # Rounds the output to 0/1correct_tensor = pred.eq(labels.float().view_as(pred))correct = np.squeeze(correct_tensor.cpu().numpy())num_correct += np.sum(correct)print("Test loss: {:.3f}".format(np.mean(test_losses)))
test_acc = num_correct/len(test_loader.dataset)
print("Test accuracy: {:.3f}%".format(test_acc*100))
[Out]: Test loss: 0.161Test accuracy: 93.906%

通過這個簡單的 LSTM 模型,我們成功實現了93.8%的準確率!這顯示了 LSTM 在處理此類順序任務方面的有效性。

這個結果是通過幾個簡單的層實現的,并且沒有任何超參數調整。可以進行許多其他改進來提高模型的有效性,并且您可以自由地嘗試通過實施這些改進來超越此準確性!

一些改進建議如下:

  • 運行超參數搜索來優化您的配置。可以在此處找到技術指南
  • 增加模型復雜性
  • 添加更多層/使用雙向 LSTM 使用預先訓練的詞嵌入,例如GloVe嵌入

超越 LSTM

多年來,LSTM 在 NLP 任務方面一直是最先進的。然而,基于注意力的模型和Transformer 的最新進展產生了更好的結果。隨著 Google 的 BERT 和 OpenAI 的 GPT 等預訓練 Transformer 模型的發布,LSTM 的使用量一直在下降。盡管如此,理解 RNN 和 LSTM 背后的概念肯定還是有用的,誰知道,也許有一天 LSTM 會卷土重來呢?

本博文譯自Gabriel Loye的博客。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/167760.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/167760.shtml
英文地址,請注明出處:http://en.pswp.cn/news/167760.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

發送一個網絡數據包的過程解析

在 ip_queue_xmit 中&#xff0c;也即 IP 層的發送函數里面&#xff0c;有三部分邏輯。第一部分&#xff0c;選取路由&#xff0c;也即我要發送這個包應該從哪個網卡出去。 這件事情主要由 ip_route_output_ports 函數完成。接下來的調用鏈為&#xff1a;ip_route_output_port…

改進YOLOv8 | YOLOv5系列:RFAConv續作,即插即用具有任意采樣形狀和任意數目參數的卷積核AKCOnv

RFAConv續作,構建具有任意采樣形狀的卷積AKConv 一、論文yolov5加入的方式論文 源代碼 一、論文 基于卷積運算的神經網絡在深度學習領域取得了顯著的成果,但標準卷積運算存在兩個固有缺陷:一方面,卷積運算被限制在一個局部窗口,不能從其他位置捕獲信息,并且其采樣形狀是…

Matlab進階繪圖第33期—雙曲面圖

在《Matlab論文插圖繪制模板第56期—曲面圖&#xff08;Surf&#xff09;》中&#xff0c;我分享過曲面圖的繪制模板。 然而&#xff0c;有的時候&#xff0c;需要在一張圖上繪制兩個及以上的曲面圖&#xff0c;且每個曲面圖使用不同的配色方案。 在Matlab中&#xff0c;一張…

C++基礎入門(超詳細)

話不多說&#xff0c;序言搞起來&#xff1a; 自從開始學老師布置的任務后&#xff0c;目前還是OpenCV&#xff0c;哈~哈。我就莫名問老師&#xff1a;“以后編程是用C還是python&#xff1f;”&#xff0c;果然還是太年輕&#xff0c;老師說&#xff1a;“兩們都要精通”。唉&…

set和map + multiset和multimap(使用+封裝(RBTree))

set和map 前言一、使用1. set(1)、模板參數列表(2)、常見構造(3)、find和count(4)、insert和erase(5)、iterator(6)、lower_bound和upper_bound 2. multiset3. map(1)、模板參數列表(2)、構造(3)、modifiers和operations(4)、operator[] 4. multimap 二、封裝RBTree迭代器原理R…

9.輸出國際象棋盤【2023.11.24】

1.問題描述 要求輸出國際象棋棋盤。 2.解決思路 國際象棋棋盤由64個黑白相間的格子組成&#xff0c;分為8行*8列。用i控制行&#xff0c;j控制列&#xff0c;根據ij的和的變化來控制輸出黑方格還是白方格。 3.代碼實現 #include<stdio.h> int main(){for(int i0;i&…

各操作系統之間的關系

請移步知乎&#xff1a; 操作系統UNIX、WINDOWS、LINUX、MC OS的聯系與區別 - 知乎 (zhihu.com) 移動端的android操作系統就人盡皆知啦&#xff0c;基于linux內核。 完畢。 適用領域&#xff1a; windows,macos:主要面向個人計算機市場 Linux、Windows Server:隨著互聯網的…

基于廣義正態分布算法優化概率神經網絡PNN的分類預測 - 附代碼

基于廣義正態分布算法優化概率神經網絡PNN的分類預測 - 附代碼 文章目錄 基于廣義正態分布算法優化概率神經網絡PNN的分類預測 - 附代碼1.PNN網絡概述2.變壓器故障診街系統相關背景2.1 模型建立 3.基于廣義正態分布優化的PNN網絡5.測試結果6.參考文獻7.Matlab代碼 摘要&#xf…

網絡安全—自學

1.網絡安全是什么 網絡安全可以基于攻擊和防御視角來分類&#xff0c;我們經常聽到的 “紅隊”、“滲透測試” 等就是研究攻擊技術&#xff0c;而“藍隊”、“安全運營”、“安全運維”則研究防御技術。 2.網絡安全市場 一、是市場需求量高&#xff1b; 二、則是發展相對成熟…

深度學習之基于Pytorch照片圖像轉漫畫風格網絡系統

歡迎大家點贊、收藏、關注、評論啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代碼。 文章目錄 一項目簡介 二、功能三、系統四. 總結 一項目簡介 以下是一個基本的設計介紹&#xff1a; 數據準備&#xff1a;收集足夠的真實照片和漫畫圖像&#xff0c;用于訓練模…

typora中的快捷鍵shift enter 和 enter的交換

1 問題&#xff1a; 我最近在用 typora 進行寫作&#xff0c;但是在合格 typora 的 markdown 編輯器很奇怪&#xff0c;它的一個回車符是兩次換行&#xff0c;而用 shfit ent 找了半天都不知道怎么解決的這個問題&#xff0c;然后我就去了這個 typora 在 github 開源的問題倉庫…

hive 報錯return code 40000 from org.apache.hadoop.hive.ql.exec.MoveTask解決思路

參考學習 https://github.com/apache/hive/blob/2b57dd27ad61e552f93817ac69313066af6562d9/ql/src/java/org/apache/hadoop/hive/ql/ErrorMsg.java#L47 為啥學習error code 開發過程中遇到以下錯誤&#xff0c;大家覺得應該怎么辦&#xff1f;從哪方面入手呢&#xff1f; 1.百…

解決在Windows10或Windows11下無權限修改hosts文件

解決在Windows10或Windows11下無權限修改hosts文件&#xff0c;無法寫入內容 1、首先在開始菜單中找到這個 2、接著輸入&#xff1a; C:\Windows\System32\drivers\etc3、再次輸入以下命令行&#xff1a;notepad hosts &#xff0c;并回車&#xff1a; notepad hosts 4、然后…

DataFunSummit:2023年現代數據棧技術峰會-核心PPT資料下載

一、峰會簡介 現代數據棧&#xff08;Modern Data Stack&#xff09;是一種集合了多種技術和工具的軟件基礎設施&#xff0c;旨在更好地管理和處理數據&#xff0c;并為企業提供數據驅動的洞察和決策。包含以下幾個組件&#xff1a;數據采集、數據處理、數據存儲、數據查詢和分…

區塊鏈技術與應用 【全國職業院校技能大賽國賽題目解析】第四套區塊鏈應用后端開發

第四套區塊鏈應用后端開發 環境 : ubuntu20 fisco : 2.8.0 springboot 2.1.1 fisco-java-sdk: 2.7.2 maven 3.8.8 前言 這套后端樣題,只涉及調用fisco的系統接口,不涉及此食品溯源項目的業務接口,所以我就直接生成一個springboot項目進行完成此題目。 請提前準備好一…

Docker的項目資源參考

Docker的項目資源包括以下內容&#xff1a; Docker官方網站&#xff1a;https://www.docker.com/ Docker Hub&#xff1a;https://hub.docker.com/ Docker文檔&#xff1a;https://docs.docker.com/ Docker GitHub倉庫&#xff1a;https://github.com/docker Docker官方博客…

Unity中Shader的Standard材質解析(二)

文章目錄 前言一、我們對 Standard 的 PBR 的 GI 進行解析1、我們先創建一個PBR的.cginc文件&#xff0c;用于整理用到的函數2、然后在Standard的Shader中引用該cginc文件 二、依次整理函數到該cginc文件中我們來看一下PBR中GI的鏡面反射做了些什么 二、最終代碼.cginc代碼&…

OpenGL 繪制旋轉球(Qt)

文章目錄 一、簡介二、實現代碼三、實現效果一、簡介 這里其實就是指三個互相垂直的三個圓形,正好之前已經完成了圓形平面的繪制,那么這里就需要對之前的圓形進行一些改造,使得它們可以以任意一種姿態在OpenGL中進行繪制(添加變換矩陣)。 這里同樣對其進行封裝,具體內容如…

【教學類-06-07】20231124 (55格版)X-X之間的加法、減法、加減混合題

背景需求 在大四班里&#xff0c;預測試55格“5以內、10以內、20以內的加法題、減法題、加減混合題”的“實用性”。 由于只打印一份20以內加法減法混合題。 “這套20以內的加減法最難”&#xff0c;我詢問誰會做&#xff08;摸底幼兒的水平&#xff09; 有兩位男孩舉手想挑…

joplin筆記同步 到騰訊云S3

創建存儲桶 打開騰訊云的存儲桶列表&#xff0c;點擊“創建存儲桶”&#xff0c;輸入名稱&#xff0c;選擇地域&#xff08;建議選擇離自己較近的地域以降低訪問時延&#xff09;和訪問權限&#xff08;建議選擇“私有讀寫”&#xff09;。 s3 存儲桶&#xff1a; 存儲桶的名稱…