使用Pytorch從零開始構建RNN

在這篇文章中,我們將了解 RNN(即循環神經網絡),并嘗試通過 PyTorch 從頭開始??實現其中的部分內容。是的,這并不完全是從頭開始,因為我們仍然依賴 PyTorch autograd 來計算梯度并實現反向傳播,但我仍然認為我們也可以從這個實現中收集到有價值的見解。

有關 RNN 的簡要介紹性概述,我建議您查看上一篇文章,其中我們不僅探討了 RNN 是什么及其工作原理,還探討了如何使用 Keras 實現 RNN 模型。這次,我們將使用 PyTorch,但采取更實際的方法從頭開始構建一個簡單的 RNN。

完全免責聲明,這篇文章很大程度上改編自PyTorch 教程這個 PyTorch 教程。我修改并改變了預處理和訓練中涉及的一些步驟。我仍然建議您將其作為補充材料查看。考慮到這一點,讓我們開始吧。

數據準備

任務是建立一個簡單的分類模型,可以根據名字正確確定一個人的國籍。更簡單地說,我們希望能夠分辨出特定名稱的來源。

下載

我們將使用 PyTorch 教程中的一些標記數據。我們只需輸入即可下載

!curl -O https://download.pytorch.org/tutorial/data.zip; unzip data.zip

此命令將下載文件并將其解壓到當前目錄中,文件夾名稱為data.

現在我們已經下載了所需的數據,讓我們更詳細地看一下數據。首先,這是我們需要的依賴項。

import os
import random
from string import ascii_lettersimport torch
from torch import nn
import torch.nn.functional as F
from unidecode import unidecode_ = torch.manual_seed(42)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

我們首先指定一個目錄,然后嘗試打印出其中的所有標簽。然后我們可以構建一個字典,將語言映射到數字標簽。

data_dir = "./data/names"lang2label = {file_name.split(".")[0]: torch.tensor([i], dtype=torch.long)for i, file_name in enumerate(os.listdir(data_dir))
}

我們看到一共有18種語言。我將每個標簽包裝為張量,以便我們可以在訓練期間直接使用它們。

lang2label
{'Czech': tensor([0]),'German': tensor([1]),'Arabic': tensor([2]),'Japanese': tensor([3]),'Chinese': tensor([4]),'Vietnamese': tensor([5]),'Russian': tensor([6]),'French': tensor([7]),'Irish': tensor([8]),'English': tensor([9]),'Spanish': tensor([10]),'Greek': tensor([11]),'Italian': tensor([12]),'Portuguese': tensor([13]),'Scottish': tensor([14]),'Dutch': tensor([15]),'Korean': tensor([16]),'Polish': tensor([17])}

讓我們將語言數量存儲在某個變量中,以便稍后在模型聲明中使用它,特別是當我們指定最終輸出層的大小時。

num_langs = len(lang2label)

預處理

現在,讓我們對名稱進行預處理。我們首先要用來unidecode標準化所有名稱并刪除任何銳利符號或類似符號。例如,

unidecode("?lusàrski")
'Slusarski'

一旦我們有了解碼后的字符串,我們就需要將其轉換為張量,以便模型可以處理它。這可以首先通過構建映射來完成char2idx,如下所示。

char2idx = {letter: i for i, letter in enumerate(ascii_letters + " .,:;-'")}
num_letters = len(char2idx); num_letters
59

我們看到我們的字符詞匯表中共有 59 個標記。這包括空格和標點符號,例如 .,:;-' . This also means that each name will now be expressed as a tensor of size (num_char, 59) ; in other words, each character will be a tensor of size (59,)。我們現在可以構建一個完成此任務的函數,如下所示:

def name2tensor(name):tensor = torch.zeros(len(name), 1, num_letters)for i, char in enumerate(name):tensor[i][0][char2idx[char]] = 1return tensor

如果你仔細閱讀代碼,你會發現輸出張量的大小是(num_char, 1, 59),這與上面的解釋不同。嗯,這個額外維度的原因是我們在本例中使用的批量大小為 1。在 PyTorch 中,RNN 層期望輸入張量的大小為(seq_len, batch_size, input_size)。由于每個名稱都有不同的長度,因此為了簡單起見,我們不會對輸入進行批處理,而只是將每個輸入用作單個批處理。有關更詳細的討論,請查看此論壇討論。

name2tensor()讓我們使用虛擬輸入快速驗證函數的輸出。

name2tensor("abc")
tensor([[[1., 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., 1., 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., 1., 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.]]])

數據集創建

現在我們需要構建包含所有預處理步驟的數據集。讓我們將所有解碼和轉換的張量收集在一個列表中,并附上標簽。可以從文件名輕松獲取標簽,例如german.txt.

tensor_names = []
target_langs = []for file in os.listdir(data_dir):with open(os.path.join(data_dir, file)) as f:lang = file.split(".")[0]names = [unidecode(line.rstrip()) for line in f]for name in names:try:tensor_names.append(name2tensor(name))target_langs.append(lang2label[lang])except KeyError:pass

我們可以將其包裝在 PyTorchDataset類中,但為了簡單起見,我們只使用一個好的舊for循環將這些數據輸入到我們的模型中。由于我們處理的是普通列表,因此我們可以輕松地使用sklearn’strain_test_split()將訓練數據與測試數據分開。

from sklearn.model_selection import train_test_splittrain_idx, test_idx = train_test_split(range(len(target_langs)), test_size=0.1, shuffle=True, stratify=target_langs
)train_dataset = [(tensor_names[i], target_langs[i])for i in train_idx
]test_dataset = [(tensor_names[i], target_langs[i])for i in test_idx
]

讓我們看看我們有多少訓練和測試數據。請注意,我們使用的 test_size為 0.1。

print(f"Train: {len(train_dataset)}")
print(f"Test: {len(test_dataset)}")
Train: 18063
Test: 2007

模型

我們將構建兩個模型:一個簡單的 RNN(將從頭開始構建)和一個使用 PyTorch 層的基于 GRU 的模型。

簡單循環神經網絡

現在我們可以構建我們的模型了。這是一個非常簡單的 RNN,它采用單個字符張量表示作為輸入,并產生一些預測和隱藏狀態,可在下一次迭代中使用。請注意,它只是一些在隱藏狀態計算期間應用了 sigmoid 非線性的全連接層。

class MyRNN(nn.Module):def __init__(self, input_size, hidden_size, output_size):super(MyRNN, self).__init__()self.hidden_size = hidden_sizeself.in2hidden = nn.Linear(input_size + hidden_size, hidden_size)self.in2output = nn.Linear(input_size + hidden_size, output_size)def forward(self, x, hidden_state):combined = torch.cat((x, hidden_state), 1)hidden = torch.sigmoid(self.in2hidden(combined))output = self.in2output(combined)return output, hiddendef init_hidden(self):return nn.init.kaiming_uniform_(torch.empty(1, self.hidden_size))

我們init_hidden()在每個新批次開始時都會打電話。為了更容易訓練和學習,我決定使用kaiming_uniform_()來初始化這些隱藏狀態。

我們現在可以構建模型并開始訓練它。

hidden_size = 256
learning_rate = 0.001model = MyRNN(num_letters, hidden_size, num_langs)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

我意識到訓練這個模型非常不穩定,正如你所看到的,損失上下跳躍了很多。盡管如此,我不想為難我的 13 英寸 MacBook Pro,所以我決定在兩個epochs停止。

num_epochs = 2
print_interval = 3000for epoch in range(num_epochs):random.shuffle(train_dataset)for i, (name, label) in enumerate(train_dataset):hidden_state = model.init_hidden()for char in name:output, hidden_state = model(char, hidden_state)loss = criterion(output, label)optimizer.zero_grad()loss.backward()nn.utils.clip_grad_norm_(model.parameters(), 1)optimizer.step()if (i + 1) % print_interval == 0:print(f"Epoch [{epoch + 1}/{num_epochs}], "f"Step [{i + 1}/{len(train_dataset)}], "f"Loss: {loss.item():.4f}")
Epoch [1/2], Step [3000/18063], Loss: 0.0390
Epoch [1/2], Step [6000/18063], Loss: 1.0368
Epoch [1/2], Step [9000/18063], Loss: 0.6718
Epoch [1/2], Step [12000/18063], Loss: 0.0003
Epoch [1/2], Step [15000/18063], Loss: 1.0658
Epoch [1/2], Step [18000/18063], Loss: 1.0021
Epoch [2/2], Step [3000/18063], Loss: 0.0021
Epoch [2/2], Step [6000/18063], Loss: 0.0131
Epoch [2/2], Step [9000/18063], Loss: 0.3842
Epoch [2/2], Step [12000/18063], Loss: 0.0002
Epoch [2/2], Step [15000/18063], Loss: 2.5420
Epoch [2/2], Step [18000/18063], Loss: 0.0172

現在我們可以測試我們的模型。我們可以查看其他指標,但準確性是迄今為止最簡單的,所以我們就這樣吧。

num_correct = 0
num_samples = len(test_dataset)model.eval()with torch.no_grad():for name, label in test_dataset:hidden_state = model.init_hidden()for char in name:output, hidden_state = model(char, hidden_state)_, pred = torch.max(output, dim=1)num_correct += bool(pred == label)print(f"Accuracy: {num_correct / num_samples * 100:.4f}%")
Accuracy: 72.2471%

該模型的準確率高達 72%。這非常糟糕,但考慮到模型非常簡單,而且我們只訓練了兩個 epoch 的模型,我們可以放松下來,享受短暫的快樂,因為知道簡單的 RNN 模型至少能夠學到一些東西。

讓我們通過一些具體示例來看看我們的模型表現如何。下面是一個接受字符串作為輸入并輸出解碼預測的函數。

label2lang = {label.item(): lang for lang, label in lang2label.items()}def myrnn_predict(name):model.eval()tensor_name = name2tensor(name)with torch.no_grad():hidden_state = model.init_hidden()for char in tensor_name:output, hidden_state = model(char, hidden_state)_, pred = torch.max(output, dim=1)model.train()    return label2lang[pred.item()]

我不知道這些名字是否真的在訓練或測試集中;這些只是我想出的一些隨機名稱,我認為這些名稱相當合理。瞧,結果是有希望的。

myrnn_predict("Mike")
'English'
myrnn_predict("Qin")
'Chinese'
myrnn_predict("Slaveya")
'Russian'

該模型似乎已將所有名稱分類為正確的類別!

PyTorch GRU

這很酷,我可能可以停在這里,但我想看看這個自定義模型與使用 PyTorch 層的模型相比如何。對于我們簡單的 RNN 來說,GRU 可能不太公平,但讓我們看看它的表現如何。

class GRUModel(nn.Module):def __init__(self, num_layers, hidden_size):super(GRUModel, self).__init__()self.num_layers = num_layersself.hidden_size = hidden_sizeself.gru = nn.GRU(input_size=num_letters, hidden_size=hidden_size, num_layers=num_layers,)self.fc = nn.Linear(hidden_size, num_langs)def forward(self, x):hidden_state = self.init_hidden()output, hidden_state = self.gru(x, hidden_state)output = self.fc(output[-1])return outputdef init_hidden(self):return torch.zeros(self.num_layers, 1, self.hidden_size).to(device)

讓我們聲明模型和與之配套的優化器。請注意,我們使用的是兩層 GRU,它已經比我們當前的 RNN 實現多了一層。

model = GRUModel(num_layers=2, hidden_size=hidden_size)
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for epoch in range(num_epochs):random.shuffle(train_dataset)for i, (name, label) in enumerate(train_dataset):output = model(name)loss = criterion(output, label)optimizer.zero_grad()loss.backward()optimizer.step()if (i + 1) % print_interval == 0:print(f"Epoch [{epoch + 1}/{num_epochs}], "f"Step [{i + 1}/{len(train_dataset)}], "f"Loss: {loss.item():.4f}")
Epoch [1/2], Step [3000/18063], Loss: 1.8497
Epoch [1/2], Step [6000/18063], Loss: 0.4908
Epoch [1/2], Step [9000/18063], Loss: 1.0299
Epoch [1/2], Step [12000/18063], Loss: 0.0855
Epoch [1/2], Step [15000/18063], Loss: 0.0053
Epoch [1/2], Step [18000/18063], Loss: 2.6417
Epoch [2/2], Step [3000/18063], Loss: 0.0004
Epoch [2/2], Step [6000/18063], Loss: 0.0008
Epoch [2/2], Step [9000/18063], Loss: 0.1446
Epoch [2/2], Step [12000/18063], Loss: 0.2125
Epoch [2/2], Step [15000/18063], Loss: 3.7883
Epoch [2/2], Step [18000/18063], Loss: 0.4862

訓練一開始看起來比較穩定,但我們確實在第二個時期結束時看到了奇怪的跳躍。部分原因是我沒有對此 GRU 模型使用梯度裁剪,并且應用裁剪后我們可能會看到更好的結果。

讓我們看看這個模型的準確性。

num_correct = 0model.eval()with torch.no_grad():for name, label in test_dataset:output = model(name)_, pred = torch.max(output, dim=1)num_correct += bool(pred == label)print(f"Accuracy: {num_correct / num_samples * 100:.4f}%")
Accuracy: 81.4150%

我們得到的這個模型的準確率約為 80%。這比我們的簡單 RNN 模型要好,這在某種程度上是預料之中的,因為它有一個附加層并且使用了更復雜的 RNN 單元模型。

讓我們看看這個模型如何預測給定的一些原始名稱字符串。

def pytorch_predict(name):model.eval()tensor_name = name2tensor(name)with torch.no_grad():output = model(tensor_name)_, pred = torch.max(output, dim=1)model.train()return label2lang[pred.item()]
pytorch_predict("Jake")
'English'
pytorch_predict("Qin")
'Chinese'
pytorch_predict("Fernando")
'Spanish'
pytorch_predict("Demirkan")
'Russian'

最后一個很有趣,因為這是我一位土耳其好朋友的名字。該模型顯然無法告訴我們這個名字是土耳其語,因為它沒有看到任何標記為土耳其語的數據點,但它告訴我們這個名字可能屬于它所訓練的 18 個標簽中的哪個國籍。這顯然是錯誤的,但在某些方面也許相差并不遠。例如,至少它沒有說日語。對于該模型來說,這也不是完全公平的游戲,因為有許多名字可能被描述為跨國的:也許有一個俄羅斯人的名字叫 Demirkan。

結論

通過實現這個 RNN,我學到了很多關于 RNN 的知識。誠然,它很簡單,并且與 PyTorch 基于層的方法有所不同,因為它需要我們手動循環每個字符,但它的低級性質迫使我更多地思考張量維度以及具有張量維度的目的。隱藏狀態和輸出之間的劃分。這也很好地提醒了我們 RNN 是如何難以訓練的。

在接下來的文章中,我們將研究序列到序列模型,簡稱 seq2seq。自從聽說 seq2seq 以來,我就對將一種數據形式轉換為另一種數據形式的力量著迷。盡管由于本地機器的限制,這些模型無法在 CPU 上進行實際訓練,但我認為實現它們本身將是一個令人興奮的挑戰。

本博文譯自Jake Tae的博文。

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

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

相關文章

Apache訪問控制

服務器相關的訪問控制 Options指令 Options指令是Apache服務器配置文件中的一個重要指令,它可以用于控制特定目錄啟用哪些服務器特性。Options指令可以在Apache服務器的核心配置、虛擬主機配置、特定目錄配置以及.htaccess文件中使用。 以下是一些常用的服務器特性選項: N…

Django(九、cookie與session)

文章目錄 一、cookie與session的介紹HTTP四大特性 cookiesession Django操作cookie三板斧基于cookie的登錄功能 一、cookie與session的介紹 在講之前我們先來回憶一下HTTP的四大特性 HTTP四大特性 1.基于請求響應 2.基于TIC、IP作用于應用層上的協議 3.無狀態 保存…

二叉查找(排序)樹你需要了解一下

簡介 二叉排序樹(Binary Sort Tree),又稱二叉查找樹(Binary Search Tree),亦稱二叉搜索樹,是一種重要的數據結構。 它有以下特性: 若左子樹不空,則左子樹上所有結點的…

目標檢測YOLO系列從入門到精通技術詳解100篇-【圖像處理】目標檢測

目錄 幾個高頻面試題目 如何在超大分辨率的圖片中檢測目標? 1當超大分辨率圖像邂逅目標檢測任務 2You Only Look Twice

邊緣計算多角色智能計量插座 x 資產顯示標簽:實現資產追蹤與能耗管理的無縫結合

越來越多智慧園區、智慧工廠、智慧醫院、智慧商業、智慧倉儲物流等企業商家對精細化、多元化智能生態應用場景的提升,順應國家節能減排、環保的時代潮流,設計一款基于融合以太網/WiFi/藍牙智能控制的智能多角色插座應運而生,賦予智能插座以遙…

大數據學習(23)-hive on mapreduce對比hive on spark

&&大數據學習&& 🔥系列專欄: 👑哲學語錄: 承認自己的無知,乃是開啟智慧的大門 💖如果覺得博主的文章還不錯的話,請點贊👍收藏??留言📝支持一下博主哦&#x1f91…

uniapp實現表單彈窗

uni.showModal({title: 刪除賬戶,confirmColor:#3A3A3A,cancelColor:#999999,confirmText:確定,editable:true,//顯示content:請輸入“delete”刪除賬戶,success: function (res) {console.log(res)if(res.confirm){if(res.contentdelete){console.log(123123123213)uni.setSto…

PCIE鏈路訓練-狀態跳轉1

A:12ms超時,或者再任何lane上檢測到Electrical Idle Exit; B: 1.發送“receiver detection”之后沒有一個lane的接收邏輯被rx檢測到 2.不滿足條件c,比如兩次detection出現差別; C:發送端在沒…

凸優化基礎與應用

諸神緘默不語-個人CSDN博文目錄 文章目錄 1. 線性規劃用SciPy求解 2. 二次規劃3. 半定規劃4. 錐規劃 凸優化是數學優化的一個重要分支,廣泛應用于各種工程和科學領域。它的核心特征在于優化問題的目標函數和約束條件是凸的,這使得找到全局最優解變得可行…

Ps:背景橡皮擦工具摳圖實例

背景橡皮擦工具 Background Eraser Tool由于是一個破壞性的工具(直接刪除像素)而少被人使用。 其實,它不僅是一個功能強大的摳圖工具,也是可以轉換為非破壞性運用的。 原圖(注:圖片來自網絡) 效…

微軟離Altman越近,離OpenAI就越遠!

大數據產業創新服務媒體 ——聚焦數據 改變商業 在OpenAI這場連續劇中(之所以說是連續劇,這個事情肯定沒完,后面肯定還會出續集),讓我倍感意外的是,Altman剛跟OpenAI分手,“離婚手續”都還沒辦…

使用Pytorch從零開始構建WGAN

引言 在考慮生成對抗網絡的文獻時,Wasserstein GAN 因其與傳統 GAN 相比的訓練穩定性而成為關鍵概念之一。在本文中,我將介紹基于梯度懲罰的 WGAN 的概念。文章的結構安排如下: WGAN 背后的直覺;GAN 和 WGAN 的比較;…

selenium新版使用find_element/find_elements函數鎖定元素(替換原有find_element_by_xx)

css選擇器請參考:網絡爬蟲之css選擇器 原來的find_element_by_xx都被修改為find_element(返回匹配到的第一個元素)或find_elements(返回全部的匹配元素) from selenium.webdriver.common.by import By示例程序 選擇…

【Q3——30min】

1、介紹一下數據庫的三大范式 第一范式(1NF):屬性不可分割,即每個屬性都是不可分割的原子項。(實體的屬性即表中的列) 第二范式(2NF):滿足第一范式;且不存在部分依賴,即非主屬性必須完全依賴于主屬性。(主屬性即主鍵&a…

minio集群部署(k8s內)

一、前言 minio的部署有幾種方式,分別是單節點單磁盤,單節點多磁盤,多節點多磁盤三種方式,本次部署使用多節點多磁盤的方式進行部署,minio集群多節點部署最低要求需要4個節點,集群擴容時也是要求擴容的節點…

2、數倉理論概述與相關概念

1、問:數據倉庫 建設過程中 經常會遇到那些問題? 模型(邏輯)重復建設 數據不一致性 維度不一致:命名、維度屬性值、維度定義 指標不一致:命名、計算口徑 數據不規范(字段命名、表名、分層、主題命名規范) 2、OneData數據建設核心方…

python爬蟲HMAC加密案例:某企業信息查詢網站

聲明: 該文章為學習使用,嚴禁用于商業用途和非法用途,違者后果自負,由此產生的一切后果均與作者無關 一、找出需要加密的參數 js運行 atob(‘aHR0cHM6Ly93d3cucWNjLmNvbS93ZWIvc2VhcmNoP2tleT0lRTQlQjglODclRTglQkUlQkUlRTklOUI…

飛槳——總結PPOCRLabel中遇到的坑

操作系統:win10 python環境:python3.9 paddleocr項目版本:2.7 1.報錯:ModuleNotFoundError: No module named Polygon(已解決) 已解決所以沒有復現報錯內容 嘗試方法一:直接使用pip命令安裝&…

oracle rac 19.3安裝補丁19.19

使用opatchauto apply DIR來進行安裝 1.升級之前先備份一下GRID_HOME和ORACLE_HOME 2.現在新的opatch安裝不需要先停止集群和數據庫,在升級過程中,他會自動關閉和啟動集群 3.先將OPatch(P6880880)包拷貝到$GRID_HOME和$ORACLE_HOM…

【Web安全】sqlmap的使用筆記及示例

【Web安全】sqlmap的使用筆記 文章目錄 【Web安全】sqlmap的使用筆記1. 目標2. 脫庫2.1. 脫庫(補充) 3. 其他3.1. 其他(補充) 4. 繞過腳本tamper講解 1. 目標 操作作用必要示例-u指定URL,檢測注入點sqlmap -u http://…