Pytorch学习记录-torchtext和Pytorch的实例( 使用神经网络训练Seq2Seq代码)
Pytorch學(xué)習(xí)記錄-torchtext和Pytorch的實例1
0. PyTorch Seq2Seq項目介紹
1. 使用神經(jīng)網(wǎng)絡(luò)訓(xùn)練Seq2Seq
1.1 簡介,對論文中公式的解讀
1.2 數(shù)據(jù)預(yù)處理
我們將在PyTorch中編寫模型并使用TorchText幫助我們完成所需的所有預(yù)處理。我們還將使用spaCy來協(xié)助數(shù)據(jù)的標(biāo)記化。
# 引入相關(guān)庫
import torch
import torch.nn as nn
import torch.optim as optim
from torchtext.datasets import TranslationDataset, Multi30k
from torchtext.data import Field, BucketIterator
import spacy
import random
import math
import time
SEED=1234
random.seed(SEED)
torch.manual_seed(SEED)
# 訓(xùn)練模型個人的基本要求是deterministic/reproducible,或者說是可重復(fù)性。也就是說在隨機(jī)種子固定的情況下,每次訓(xùn)練出來的模型要一樣。之前遇到了兩次不可重復(fù)的情況。第一次是訓(xùn)練CNN的時候,發(fā)現(xiàn)每次跑出來小數(shù)點后幾位會有不一樣。epoch越多,誤差就越多
# 確定性卷積:(相當(dāng)于把所有操作的seed=0,以便重現(xiàn),會變慢)
torch.backends.cudnn.deterministic=True
加載spacy的英、德庫,我只能說大陸的網(wǎng)太慢了,德文包11mb我下了2個小時……
spacy_de=spacy.load('de')
spacy_en=spacy.load('en')
創(chuàng)建分詞方法,這些可以傳遞給TorchText并將句子作為字符串接收并將句子作為標(biāo)記列表返回。
在論文中,他們發(fā)現(xiàn)扭轉(zhuǎn)輸入順序是有益的,他們認(rèn)為“在數(shù)據(jù)中引入了許多短期依賴關(guān)系,使優(yōu)化問題變得更加容易”。在德文(輸入端)進(jìn)行了扭轉(zhuǎn)。
def tokenize_de(text):return [tok.text for tok in spacy_de.tokenizer(text)][::-1]
def tokenize_en(text):return [tok.text for tok in spacy_en.tokenizer(text)]# 這里增加了<sos>和<eos>
SRC=Field(tokenize=tokenize_de,init_token='<sos>',eos_token='<eos>',lower=True
)
TRG=Field(tokenize=tokenize_en,init_token='<sos>',eos_token='<eos>',lower=True
)
接下來使用英文和德文的平行語料庫Multi30k dataset,使用這個語料庫加載成為訓(xùn)練、驗證、測試數(shù)據(jù)。下載地址見評論。
exts指定使用哪種語言作為源和目標(biāo)(源首先),字段指定用于源和目標(biāo)的字段。
train_data, valid_data, test_data=Multi30k.splits(exts=('.de','.en'),fields=(SRC,TRG))
可以對下載的數(shù)據(jù)集進(jìn)行驗證,前面的exts和fields做的是加標(biāo)簽,同時對數(shù)據(jù)集進(jìn)行切分。下面對切分結(jié)果進(jìn)行驗證,可以看到,train_data的例子中,src是德文輸入,trg是英文輸出。
接下來,我們將為源語言和目標(biāo)語言構(gòu)建詞匯表。詞匯表用于將每個唯一令牌與索引(整數(shù))相關(guān)聯(lián),這用于為每個令牌構(gòu)建一個熱門編碼(除了索引所代表的位置之外的所有零的向量,即1)。源語言和目標(biāo)語言的詞匯表是截然不同的。
使用min_freq參數(shù),我們只允許至少出現(xiàn)2次的標(biāo)記出現(xiàn)在我們的詞匯表中。僅出現(xiàn)一次令牌轉(zhuǎn)換成<UNK>(未知)令牌。
要注意的是,詞匯表只能從訓(xùn)練集而不是驗證/測試集構(gòu)建。這可以防止“信息泄漏”進(jìn)入您的模型,為您提供人為夸大的驗證/測試分?jǐn)?shù)。
print(f"Number of training examples: {len(train_data.examples)}")
print(f"Number of validation examples: {len(valid_data.examples)}")
print(f"Number of testing examples: {len(test_data.examples)}")
print(vars(train_data.examples[1]))
Number of training examples: 29000
Number of validation examples: 1014
Number of testing examples: 1000
{'src': ['.', 'antriebsradsystem', 'ein', 'bedienen', 'schutzhelmen', 'mit', 'm?nner', 'mehrere'], 'trg': ['several', 'men', 'in', 'hard', 'hats', 'are', 'operating', 'a', 'giant', 'pulley', 'system', '.']}
SRC.build_vocab(train_data,min_freq=2)
TRG.build_vocab(train_data,min_freq=2)
print(f"Unique tokens in source (de) vocabulary: {len(SRC.vocab)}")
print(f"Unique tokens in target (en) vocabulary: {len(TRG.vocab)}")
Unique tokens in source (de) vocabulary: 7855
Unique tokens in target (en) vocabulary: 5893
最后一步是迭代器,使用BucketIterator處理。
我們還需要定義一個torch.device。這用于告訴TorchText將張量放在GPU上。我們使用torch.cuda.is_available()函數(shù),如果在我們的計算機(jī)上檢測到GPU,它將返回True。我們將此設(shè)備傳遞給迭代器。
當(dāng)我們使用迭代器獲得一批示例時,我們需要確保所有源句子都填充到相同的長度,與目標(biāo)句子相同。幸運的是,TorchText迭代器為我們處理這個問題。我們使用BucketIterator而不是標(biāo)準(zhǔn)迭代器,因為它以這樣的方式創(chuàng)建批處理,以便最小化源句和目標(biāo)句子中的填充量。
device=torch.device('cpu')
print(device)
BATCH_SIZE=128
train_iterator, valid_iterator, test_iterator = BucketIterator.splits((train_data, valid_data, test_data), batch_size = BATCH_SIZE, device = -1)
cpuThe `device` argument should be set by using `torch.device` or passing a string as an argument. This behavior will be deprecated soon and currently defaults to cpu.
The `device` argument should be set by using `torch.device` or passing a string as an argument. This behavior will be deprecated soon and currently defaults to cpu.
The `device` argument should be set by using `torch.device` or passing a string as an argument. This behavior will be deprecated soon and currently defaults to cpu.
1.3 構(gòu)建Seq2Seq模型
將Seq2Seq模型分為三部分:Encoder、Decoder、Seq2Seq,每個模塊之間使用接口進(jìn)行操作
1.3.1 Encoder
encoder是一個2層的LSTM(原論文為4層),對于一個多層的RNN,輸入句子X在底層,這一層的輸出作為上層的輸入。因此,使用上標(biāo)來表示每一層。下面公式表示的第一層和第二層的隱藏狀態(tài)
轉(zhuǎn)存失敗重新上傳取消
轉(zhuǎn)存失敗重新上傳取消
使用多層RNN同樣也意味著需要賦予一個初始的隱藏狀態(tài)轉(zhuǎn)存失敗重新上傳取消,并且每層要輸出對應(yīng)的上下文向量轉(zhuǎn)存失敗重新上傳取消。
我們需要知道的是,LSTM是一種RNN,它不是僅僅處于隱藏狀態(tài)并且每個時間步返回一個新的隱藏狀態(tài),而是每次接收并返回一個單元狀態(tài)轉(zhuǎn)存失敗重新上傳取消。
轉(zhuǎn)存失敗重新上傳取消
我們的上下文向量現(xiàn)在將是最終隱藏狀態(tài)和最終單元狀態(tài),即轉(zhuǎn)存失敗重新上傳取消。將我們的多層方程擴(kuò)展到LSTM,我們得到下面的公式。
轉(zhuǎn)存失敗重新上傳取消
請注意,我們只將第一層的隱藏狀態(tài)作為輸入傳遞給第二層,而不是單元狀態(tài)。
image.png?
下面重點來了,encoder有哪些參數(shù)
- input_dim輸入encoder的one-hot向量維度,這個和輸入詞匯大小一致
- emb_dim嵌入層的維度,這一層將one-hot向量轉(zhuǎn)為密度向量
- hid_dim隱藏層和cell狀態(tài)維度
- n_layersRNN的層數(shù)
- dropout是要使用的丟失量。這是一個防止過度擬合的正則化參數(shù)。
教程中不討論嵌入層。在單詞之前還有一個步驟 - 單詞的索引 - 被傳遞到RNN,其中單詞被轉(zhuǎn)換為向量。
嵌入層使用nn.Embedding,帶有nn.LSTM的LSTM和帶有nn.Dropout的dropout層創(chuàng)建。
需要注意的一點是LSTM的dropout參數(shù)是在多層RNN的層之間應(yīng)用多少丟失,即在層轉(zhuǎn)存失敗重新上傳取消輸出的隱藏狀態(tài)和用于輸入的相同隱藏狀態(tài)之間。 layer 轉(zhuǎn)存失敗重新上傳取消。
在forward方法中,我們傳入源句子轉(zhuǎn)存失敗重新上傳取消,使用嵌入層將其轉(zhuǎn)換為密集向量,然后應(yīng)用dropout。然后將這些嵌入傳遞到RNN。當(dāng)我們將整個序列傳遞給RNN時,它會自動為整個序列重復(fù)計算隱藏狀態(tài)!您可能會注意到我們沒有將初始隱藏或單元狀態(tài)傳遞給RNN。這是因為,如文檔中所述,如果沒有將隱藏/單元狀態(tài)傳遞給RNN,它將自動創(chuàng)建初始隱藏/單元狀態(tài)作為全零的張量。
RNN返回:輸出(每個時間步的頂層隱藏狀態(tài)),隱藏(每個層的最終隱藏狀態(tài),轉(zhuǎn)存失敗重新上傳取消,堆疊在彼此之上)和單元格(每個層的最終單元狀態(tài)) ,轉(zhuǎn)存失敗重新上傳取消,疊加在彼此之上)。
由于我們只需要最終隱藏和單元格狀態(tài)(以制作我們的上下文向量),因此只返回隱藏和單元格。
每個張量的大小在代碼中留作注釋。在此實現(xiàn)中,n_directions將始終為1,但請注意,雙向RNN(在教程3中介紹)將具有n_directions為2。
class Encoder(nn.Module):def __init__(self, input_dim, emb_dim, hid_dim, n_layers, dropout):super(Encoder,self).__init__()self.input_dim=input_dimself.emb_dim=emb_dimself.hid_dim=hid_dimself.n_layers=n_layersself.dropout=dropoutself.embedding=nn.Embedding(input_dim,emb_dim)self.rnn=nn.LSTM(emb_dim,hid_dim,n_layers,dropout=dropout)self.dropout=nn.Dropout(dropout)def forward(self, src):embedded=self.dropout(self.embedding(src))outputs, (hidden,cell)=self.rnn(embedded)return hidden ,cell
1.3.2 Decoder
Decoder同樣也是一個兩層的LSTM。
image.pngDecoder只執(zhí)行一個解碼步驟。第一層將從前一個時間步,接收隱藏和單元狀態(tài),并通過將當(dāng)前的token 喂給LSTM,進(jìn)一步產(chǎn)生一個新的隱藏和單元狀態(tài)。后續(xù)層將使用下面層中的隱藏狀態(tài),,以及來自其圖層的先前隱藏和單元狀態(tài),。這提供了與編碼器中的方程非常相似的方程。
另外,Decoder的初始隱藏和單元狀態(tài)是我們的上下文向量,它們是來自同一層的Encoder的最終隱藏和單元狀態(tài)
接下來將隱藏狀態(tài)傳遞給Linear層,預(yù)測目標(biāo)序列下一個標(biāo)記應(yīng)該是什么。
Decoder的參數(shù)和Encoder類似,其中output_dim是將要輸入到Decoder的one-hot向量。
在forward方法中,獲取到了輸入token、上一層的隱藏狀態(tài)和單元狀態(tài)。解壓之后加入句子長度維度。接下來與Encoder類似,傳入嵌入層并使用dropout,然后將這批嵌入式令牌傳遞到具有先前隱藏和單元狀態(tài)的RNN。這產(chǎn)生了一個輸出(來自RNN頂層的隱藏狀態(tài)),一個新的隱藏狀態(tài)(每個層一個,堆疊在彼此之上)和一個新的單元狀態(tài)(每層也有一個,堆疊在彼此的頂部) )。然后我們通過線性層傳遞輸出(在除去句子長度維度之后)以接收我們的預(yù)測。然后我們返回預(yù)測,新的隱藏狀態(tài)和新的單元狀態(tài)。
?
class Decoder(nn.Module):def __init__(self, output_dim, emb_dim, hid_dim, n_layers, dropout):super(Decoder,self).__init__()self.emb_dim=emb_dimself.hid_dim=hid_dimself.output_dim=output_dimself.n_layers=n_layersself.dropout=dropoutself.embedding=nn.Embedding(output_dim,emb_dim)self.rnn=nn.LSTM(emb_dim,hid_dim,n_layers,dropout=dropout)self.out=nn.Linear(hid_dim,output_dim)self.dropout=nn.Dropout(dropout)def forward(self, input,hidden,cell):input=input.unsqueeze(0)embedded=self.dropout(self.embedding(input))output, (hidden,cell)=self.rnn(embedded,(hidden,cell))prediction=self.out(output.squeeze(0))return prediction,hidden ,cell
1.3.3 Seq2Seq
最后一部分的實現(xiàn),seq2seq。
- 接收輸入/源句子
- 使用Encoder生成上下文向量
- 使用Decoder生成預(yù)測輸出/目標(biāo)句子
再看一下整體的模型 image.png
確定Encoder和Decoder每一層的數(shù)目、隱藏層和單元維度相同。
我們在forward方法中做的第一件事是創(chuàng)建一個輸出張量,它將存儲我們所有的預(yù)測,轉(zhuǎn)存失敗重新上傳取消。
然后,我們將輸入/源語句轉(zhuǎn)存失敗重新上傳取消 / src輸入編碼器,并接收最終的隱藏和單元狀態(tài)。
解碼器的第一個輸入是序列的開始(<sos>)令牌。由于我們的trg張量已經(jīng)附加了<sos>標(biāo)記(當(dāng)我們在TRG字段中定義init_token時一直回來),我們通過切入它來得到轉(zhuǎn)存失敗重新上傳取消。我們知道我們的目標(biāo)句子應(yīng)該是多長時間(max_len),所以我們循環(huán)多次。在循環(huán)的每次迭代期間,我們: - 將輸入,先前隱藏和前一個單元狀態(tài)(轉(zhuǎn)存失敗重新上傳取消)傳遞給Decoder。
- 接收預(yù)測,來自Decoder下一個隱藏狀態(tài)和下一個單元狀態(tài)(轉(zhuǎn)存失敗重新上傳取消)
- 將我們的預(yù)測,轉(zhuǎn)存失敗重新上傳取消 /輸出放在我們的預(yù)測張量中,轉(zhuǎn)存失敗重新上傳取消 / outputs
- 決定我們是否要“教師強(qiáng)制”。
- 如果我們這樣做,下一個輸入是序列中的groundtruth下一個標(biāo)記,轉(zhuǎn)存失敗重新上傳取消 / trg [t]
- 如果我們不要,下一個輸入是序列中預(yù)測的下一個標(biāo)記,轉(zhuǎn)存失敗重新上傳取消 / top1
class Seq2Seq(nn.Module):def __init__(self, encoder, decoder, device):super(Seq2Seq,self).__init__()self.encoder = encoderself.decoder = decoderself.device = deviceassert encoder.hid_dim == decoder.hid_dim, \"Hidden dimensions of encoder and decoder must be equal!"assert encoder.n_layers == decoder.n_layers, \"Encoder and decoder must have equal number of layers!"def forward(self, src,trg,teacher_forcing_ratio=0.5):# src = [src sent len, batch size]# trg = [trg sent len, batch size]# teacher_forcing_ratio是使用教師強(qiáng)制的概率# 例如。如果teacher_forcing_ratio是0.75,我們75%的時間使用groundtruth輸入batch_size=trg.shape[1]max_len=trg.shape[0]trg_vocab_size=self.decoder.output_dimoutputs = torch.zeros(max_len, batch_size, trg_vocab_size).to(self.device)hidden, cell=self.encoder(src)input=trg[0,:]for t in range(1, max_len):output, hidden, cell = self.decoder(input, hidden, cell)outputs[t] = outputteacher_force = random.random() < teacher_forcing_ratiotop1 = output.max(1)[1]input = (trg[t] if teacher_force else top1)return outputs
1.4 訓(xùn)練模型
首先,我們將初始化我們的模型。如前所述,輸入和輸出維度由詞匯表的大小定義。編碼器和解碼器的嵌入尺寸和丟失可以不同,但是層數(shù)和隱藏/單元狀態(tài)的大小必須相同。
然后我們定義編碼器,解碼器,然后定義我們放置在設(shè)備上的Seq2Seq模型。
INPUT_DIM = len(SRC.vocab)
OUTPUT_DIM = len(TRG.vocab)
ENC_EMB_DIM = 256
DEC_EMB_DIM = 256
HID_DIM = 512
N_LAYERS = 2
ENC_DROPOUT = 0.5
DEC_DROPOUT = 0.5
enc=Encoder(INPUT_DIM,ENC_EMB_DIM,HID_DIM,N_LAYERS,ENC_DROPOUT)
dec=Decoder(OUTPUT_DIM,DEC_EMB_DIM,HID_DIM,N_LAYERS,DEC_DROPOUT)
model=Seq2Seq(enc,dec,device)
def init_weights(m):for name, param in m.named_parameters():nn.init.uniform_(param.data, -0.08, 0.08)model.apply(init_weights)
Seq2Seq((encoder): Encoder((embedding): Embedding(7855, 256)(rnn): LSTM(256, 512, num_layers=2, dropout=0.5)(dropout): Dropout(p=0.5))(decoder): Decoder((embedding): Embedding(5893, 256)(rnn): LSTM(256, 512, num_layers=2, dropout=0.5)(out): Linear(in_features=512, out_features=5893, bias=True)(dropout): Dropout(p=0.5))
)
def count_parameters(model):return sum(p.numel() for p in model.parameters() if p.requires_grad)print(f'The model has {count_parameters(model):,} trainable parameters')
The model has 13,899,013 trainable parameters
optimizer = optim.Adam(model.parameters())
PAD_IDX = TRG.vocab.stoi['<pad>']
criterion = nn.CrossEntropyLoss(ignore_index = PAD_IDX)
def train(model, iterator, optimizer, criterion, clip):model.train()epoch_loss = 0for i, batch in enumerate(iterator):src = batch.srctrg = batch.trgoptimizer.zero_grad()output = model(src, trg)#trg = [trg sent len, batch size]#output = [trg sent len, batch size, output dim]output = output[1:].view(-1, output.shape[-1])trg = trg[1:].view(-1)#trg = [(trg sent len - 1) * batch size]#output = [(trg sent len - 1) * batch size, output dim]loss = criterion(output, trg)print(loss.item())loss.backward()torch.nn.utils.clip_grad_norm_(model.parameters(), clip)optimizer.step()epoch_loss += loss.item()return epoch_loss / len(iterator)
def evaluate(model, iterator, criterion):model.eval()epoch_loss = 0with torch.no_grad():for i, batch in enumerate(iterator):src = batch.srctrg = batch.trgoutput = model(src, trg, 0) #turn off teacher forcing#trg = [trg sent len, batch size]#output = [trg sent len, batch size, output dim]output = output[1:].view(-1, output.shape[-1])trg = trg[1:].view(-1)#trg = [(trg sent len - 1) * batch size]#output = [(trg sent len - 1) * batch size, output dim]loss = criterion(output, trg)epoch_loss += loss.item()return epoch_loss / len(iterator)
def epoch_time(start_time, end_time):elapsed_time = end_time - start_timeelapsed_mins = int(elapsed_time / 60)elapsed_secs = int(elapsed_time - (elapsed_mins * 60))return elapsed_mins, elapsed_secs
N_EPOCHS = 2
CLIP = 1best_valid_loss = float('inf')for epoch in range(N_EPOCHS):start_time = time.time()train_loss = train(model, train_iterator, optimizer, criterion, CLIP)
# valid_loss = evaluate(model, valid_iterator, criterion)end_time = time.time()epoch_mins, epoch_secs = epoch_time(start_time, end_time)# if valid_loss < best_valid_loss:
# best_valid_loss = valid_loss
# torch.save(model.state_dict(), 'tut1-model.pt')print(f'Epoch: {epoch+1:02} | Time: {epoch_mins}m {epoch_secs}s')print(f'\tTrain Loss: {train_loss:.3f} | Train PPL: {math.exp(train_loss):7.3f}')
# print(f'\t Val. Loss: {valid_loss:.3f} | Val. PPL: {math.exp(valid_loss):7.3f}')
8.671906471252441
8.567961692810059
8.38569450378418
7.892151832580566
7.042192459106445
6.31839656829834
6.088204383850098
5.77440881729126
5.662734508514404
5.574016571044922
。。。
在這里我做了處理,因為顯存的問題,被迫把數(shù)據(jù)和模型都放在cpu上跑了,速度奇慢,所以我把評價和模型保存部分注釋掉了。看來要盡快搞定基礎(chǔ)的,然后選一個云平臺了……
作者:我的昵稱違規(guī)了
鏈接:https://www.jianshu.com/p/dbf00b590c70
來源:簡書
簡書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處。
總結(jié)
以上是生活随笔為你收集整理的Pytorch学习记录-torchtext和Pytorch的实例( 使用神经网络训练Seq2Seq代码)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UTF-8与UTF-8 BOM
- 下一篇: 结巴分词原理介绍