Pytorch學習記錄-torchtext和Pytorch的實例1
0. PyTorch Seq2Seq項目介紹
1. 使用神經網絡訓練Seq2Seq
1.1 簡介,對論文中公式的解讀
1.2 數據預處理
我們將在PyTorch中編寫模型並使用TorchText幫助我們完成所需的所有預處理。我們還將使用spaCy來協助數據的標記化。
# 引入相關庫 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) # 訓練模型個人的基本要求是deterministic/reproducible,或者說是可重復性。也就是說在隨機種子固定的情況下,每次訓練出來的模型要一樣。之前遇到了兩次不可重復的情況。第一次是訓練CNN的時候,發現每次跑出來小數點后幾位會有不一樣。epoch越多,誤差就越多 # 確定性卷積:(相當於把所有操作的seed=0,以便重現,會變慢) torch.backends.cudnn.deterministic=True
加載spacy的英、德庫,我只能說大陸的網太慢了,德文包11mb我下了2個小時……
spacy_de=spacy.load('de') spacy_en=spacy.load('en')
創建分詞方法,這些可以傳遞給TorchText並將句子作為字符串接收並將句子作為標記列表返回。
在論文中,他們發現扭轉輸入順序是有益的,他們認為“在數據中引入了許多短期依賴關系,使優化問題變得更加容易”。在德文(輸入端)進行了扭轉。
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,使用這個語料庫加載成為訓練、驗證、測試數據。下載地址見評論。
exts指定使用哪種語言作為源和目標(源首先),字段指定用於源和目標的字段。
train_data, valid_data, test_data=Multi30k.splits(exts=('.de','.en'),fields=(SRC,TRG))
可以對下載的數據集進行驗證,前面的exts和fields做的是加標簽,同時對數據集進行切分。下面對切分結果進行驗證,可以看到,train_data的例子中,src是德文輸入,trg是英文輸出。
接下來,我們將為源語言和目標語言構建詞匯表。詞匯表用於將每個唯一令牌與索引(整數)相關聯,這用於為每個令牌構建一個熱門編碼(除了索引所代表的位置之外的所有零的向量,即1)。源語言和目標語言的詞匯表是截然不同的。
使用min_freq參數,我們只允許至少出現2次的標記出現在我們的詞匯表中。僅出現一次令牌轉換成<UNK>(未知)令牌。
要注意的是,詞匯表只能從訓練集而不是驗證/測試集構建。這可以防止“信息泄漏”進入您的模型,為您提供人為誇大的驗證/測試分數。
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()函數,如果在我們的計算機上檢測到GPU,它將返回True。我們將此設備傳遞給迭代器。
當我們使用迭代器獲得一批示例時,我們需要確保所有源句子都填充到相同的長度,與目標句子相同。幸運的是,TorchText迭代器為我們處理這個問題。我們使用BucketIterator而不是標准迭代器,因為它以這樣的方式創建批處理,以便最小化源句和目標句子中的填充量。
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)
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. 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 構建Seq2Seq模型
將Seq2Seq模型分為三部分:Encoder、Decoder、Seq2Seq,每個模塊之間使用接口進行操作
1.3.1 Encoder
encoder是一個2層的LSTM(原論文為4層),對於一個多層的RNN,輸入句子X在底層,這一層的輸出作為上層的輸入。因此,使用上標來表示每一層。下面公式表示的第一層和第二層的隱藏狀態
使用多層RNN同樣也意味着需要賦予一個初始的隱藏狀態,並且每層要輸出對應的上下文向量
。
我們需要知道的是,LSTM是一種RNN,它不是僅僅處於隱藏狀態並且每個時間步返回一個新的隱藏狀態,而是每次接收並返回一個單元狀態。
我們的上下文向量現在將是最終隱藏狀態和最終單元狀態,即。將我們的多層方程擴展到LSTM,我們得到下面的公式。
請注意,我們只將第一層的隱藏狀態作為輸入傳遞給第二層,而不是單元狀態。

下面重點來了,encoder有哪些參數
- input_dim輸入encoder的one-hot向量維度,這個和輸入詞匯大小一致
- emb_dim嵌入層的維度,這一層將one-hot向量轉為密度向量
- hid_dim隱藏層和cell狀態維度
- n_layersRNN的層數
- dropout是要使用的丟失量。這是一個防止過度擬合的正則化參數。
教程中不討論嵌入層。在單詞之前還有一個步驟 - 單詞的索引 - 被傳遞到RNN,其中單詞被轉換為向量。
嵌入層使用nn.Embedding,帶有nn.LSTM的LSTM和帶有nn.Dropout的dropout層創建。
需要注意的一點是LSTM的dropout參數是在多層RNN的層之間應用多少丟失,即在層輸出的隱藏狀態和用於輸入的相同隱藏狀態之間。 layer
。
在forward方法中,我們傳入源句子,使用嵌入層將其轉換為密集向量,然后應用dropout。然后將這些嵌入傳遞到RNN。當我們將整個序列傳遞給RNN時,它會自動為整個序列重復計算隱藏狀態!您可能會注意到我們沒有將初始隱藏或單元狀態傳遞給RNN。這是因為,如文檔中所述,如果沒有將隱藏/單元狀態傳遞給RNN,它將自動創建初始隱藏/單元狀態作為全零的張量。
RNN返回:輸出(每個時間步的頂層隱藏狀態),隱藏(每個層的最終隱藏狀態,,堆疊在彼此之上)和單元格(每個層的最終單元狀態) ,
,疊加在彼此之上)。
由於我們只需要最終隱藏和單元格狀態(以制作我們的上下文向量),因此只返回隱藏和單元格。
每個張量的大小在代碼中留作注釋。在此實現中,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_dim self.emb_dim=emb_dim self.hid_dim=hid_dim self.n_layers=n_layers self.dropout=dropout self.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。

Decoder只執行一個解碼步驟。第一層將從前一個時間步,接收隱藏和單元狀態,並通過將當前的token 喂給LSTM,進一步產生一個新的隱藏和單元狀態。后續層將使用下面層中的隱藏狀態,,以及來自其圖層的先前隱藏和單元狀態,。這提供了與編碼器中的方程非常相似的方程。
另外,Decoder的初始隱藏和單元狀態是我們的上下文向量,它們是來自同一層的Encoder的最終隱藏和單元狀態
接下來將隱藏狀態傳遞給Linear層,預測目標序列下一個標記應該是什么。
Decoder的參數和Encoder類似,其中output_dim是將要輸入到Decoder的one-hot向量。
在forward方法中,獲取到了輸入token、上一層的隱藏狀態和單元狀態。解壓之后加入句子長度維度。接下來與Encoder類似,傳入嵌入層並使用dropout,然后將這批嵌入式令牌傳遞到具有先前隱藏和單元狀態的RNN。這產生了一個輸出(來自RNN頂層的隱藏狀態),一個新的隱藏狀態(每個層一個,堆疊在彼此之上)和一個新的單元狀態(每層也有一個,堆疊在彼此的頂部) )。然后我們通過線性層傳遞輸出(在除去句子長度維度之后)以接收我們的預測。然后我們返回預測,新的隱藏狀態和新的單元狀態。
class Decoder(nn.Module): def __init__(self, output_dim, emb_dim, hid_dim, n_layers, dropout): super(Decoder,self).__init__() self.emb_dim=emb_dim self.hid_dim=hid_dim self.output_dim=output_dim self.n_layers=n_layers self.dropout=dropout self.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
最后一部分的實現,seq2seq。
- 接收輸入/源句子
- 使用Encoder生成上下文向量
- 使用Decoder生成預測輸出/目標句子
再看一下整體的模型
image.png
確定Encoder和Decoder每一層的數目、隱藏層和單元維度相同。
我們在forward方法中做的第一件事是創建一個輸出張量,它將存儲我們所有的預測,。
然后,我們將輸入/源語句/ src輸入編碼器,並接收最終的隱藏和單元狀態。
解碼器的第一個輸入是序列的開始(<sos>)令牌。由於我們的trg張量已經附加了<sos>標記(當我們在TRG字段中定義init_token時一直回來),我們通過切入它來得到。我們知道我們的目標句子應該是多長時間(max_len),所以我們循環多次。在循環的每次迭代期間,我們:
- 將輸入,先前隱藏和前一個單元狀態(
)傳遞給Decoder。
- 接收預測,來自Decoder下一個隱藏狀態和下一個單元狀態(
)
- 將我們的預測,
/輸出放在我們的預測張量中,
/ outputs
- 決定我們是否要“教師強制”。
- 如果我們這樣做,下一個輸入是序列中的groundtruth下一個標記,
/ trg [t]
- 如果我們不要,下一個輸入是序列中預測的下一個標記,
/ top1
- 如果我們這樣做,下一個輸入是序列中的groundtruth下一個標記,
class Seq2Seq(nn.Module): def __init__(self, encoder, decoder, device): super(Seq2Seq,self).__init__() self.encoder = encoder self.decoder = decoder self.device = device assert 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是使用教師強制的概率 # 例如。如果teacher_forcing_ratio是0.75,我們75%的時間使用groundtruth輸入 batch_size=trg.shape[1] max_len=trg.shape[0] trg_vocab_size=self.decoder.output_dim outputs = 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] = output teacher_force = random.random() < teacher_forcing_ratio top1 = output.max(1)[1] input = (trg[t] if teacher_force else top1) return outputs
1.4 訓練模型
首先,我們將初始化我們的模型。如前所述,輸入和輸出維度由詞匯表的大小定義。編碼器和解碼器的嵌入尺寸和丟失可以不同,但是層數和隱藏/單元狀態的大小必須相同。
然后我們定義編碼器,解碼器,然后定義我們放置在設備上的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 = 0 for i, batch in enumerate(iterator): src = batch.src trg = batch.trg optimizer.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 = 0 with torch.no_grad(): for i, batch in enumerate(iterator): src = batch.src trg = batch.trg output = 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_time elapsed_mins = int(elapsed_time / 60) elapsed_secs = int(elapsed_time - (elapsed_mins * 60)) return elapsed_mins, elapsed_secs
N_EPOCHS = 2 CLIP = 1 best_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
。。。
在這里我做了處理,因為顯存的問題,被迫把數據和模型都放在cpu上跑了,速度奇慢,所以我把評價和模型保存部分注釋掉了。看來要盡快搞定基礎的,然后選一個雲平台了……
作者:我的昵稱違規了
鏈接:https://www.jianshu.com/p/dbf00b590c70
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。