Pytorch學(xué)習(xí)記錄- 訓(xùn)練GRU Seq2Seq(論文再讀)

對(duì)Pytorch的Seq2Seq這6篇論文進(jìn)行精讀,第二篇,Cho, K., et al., Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation. 2014.
發(fā)表于2014年,全文鏈接

摘要

很牛逼的一個(gè)神經(jīng)網(wǎng)絡(luò),基于RNN的Seq2Seq,用于處理符號(hào)。使用這個(gè)encoder-decoder計(jì)算的短語對(duì)條件概率作為現(xiàn)有對(duì)數(shù)線性模型中的附加特征,通過實(shí)驗(yàn)發(fā)現(xiàn)使用這樣特征的SMT成績得到提升。

1. 介紹

SMT越來越用到神經(jīng)網(wǎng)絡(luò),用作傳統(tǒng)的基于短語的SMT系統(tǒng)的一部分。
基于RNN的encdoer-decoder,由兩個(gè)遞歸神經(jīng)網(wǎng)絡(luò)(RNN)組成,其充當(dāng)編碼器和解碼器對(duì)。編碼器將可變長度源序列映射到固定長度矢量,并且解碼器將矢量表示映射回可變長度目標(biāo)序列。聯(lián)合訓(xùn)練兩個(gè)網(wǎng)絡(luò)以最大化給定源序列的目標(biāo)序列的條件概率。此外,我們建議使用復(fù)雜的隱藏單元,以提高內(nèi)存容量和訓(xùn)練的便利性。
處理英文-法文,專門訓(xùn)練用來翻譯短語,發(fā)現(xiàn)這個(gè)模型能夠很好地捕捉短語表中的語言規(guī)律。基于RNN的encoder-decoder能夠?qū)W習(xí)短語中的連續(xù)空間表示,而這個(gè)短語保留了語義和語法結(jié)構(gòu),也就是說,RNN能夠較好地學(xué)習(xí)文本中的語義和語法結(jié)構(gòu)。(也就是現(xiàn)在普遍的認(rèn)知,RNN能夠處理較長的文本(雖然在這篇論文里面仍只是處理短語結(jié)構(gòu)))

2. RNN encoder-decoder

2.1 初步:RNN

在這里說了什么是RNN,略過

2.2 RNN encoder-decoder

同樣也是encoder處理短語,生成向量,使用隱藏狀態(tài)保存整個(gè)輸入的sequence。decoder使用encoder生成的隱藏狀態(tài)h_{<t>}訓(xùn)練生成輸出結(jié)果y_t。但是與傳統(tǒng)的RNN不同,h_{<t>}y_t也以輸入序列y_{t-1}的匯總c作為條件。
decoder在t時(shí)間的隱藏狀態(tài)計(jì)算公式
h_{<t>}=f(h_{<t>},y_{t-1},c)
而下一個(gè)短語的條件分布,使用softmax計(jì)算
P(y_t|y_{t-1},...,y_1,c)=g(h_{<t>},y_{t-1},c)
最終encoder-decoder兩個(gè)部分整合,訓(xùn)練得到最大的條件log-likelihood,其中θ是模型參數(shù),每一個(gè)(x_n,y_n)就是一個(gè)輸入輸出對(duì)
max_θ \frac{1}{N}\Sigma_{n=1}^Nlogp_{θ}{y_n|x_n}

2.3 能夠自適應(yīng)記憶和遺忘的隱藏狀態(tài)

新的模型就有新的隱藏狀態(tài),之前LSTM的隱藏狀態(tài)太過簡單,這里使用GRU,幫助RNN記住長期信息。
由于每個(gè)隱藏單元具有單獨(dú)的重置和更新門,每個(gè)隱藏單元將學(xué)習(xí)捕獲不同時(shí)間尺度上的依賴性。學(xué)習(xí)捕獲短期依賴關(guān)系的那些單元將傾向于重置經(jīng)?;顒?dòng)的門,但那些捕獲長期依賴關(guān)系的那些將具有最活躍的更新門。
總之,換成GRU。

3. 基于統(tǒng)計(jì)的機(jī)器翻譯

在實(shí)踐中,大多數(shù)SMT系統(tǒng)將log p(f|e)建模為具有附加特征和相應(yīng)權(quán)重的對(duì)數(shù)線性模型
log p(f|e)=\Sigma_{n=1}^N w_nf_n(f,e)+logZ(e)

3.1 使用RNN encoder-decoder評(píng)分短語對(duì)

3.2 相關(guān)方法,神經(jīng)網(wǎng)絡(luò)機(jī)器翻譯

列舉了一系列相關(guān)類似研究,都是使用神經(jīng)網(wǎng)絡(luò)構(gòu)建機(jī)器翻譯系統(tǒng)。

4. 實(shí)驗(yàn)

4.1 數(shù)據(jù)和基線

在WMT'14翻譯任務(wù)的框架內(nèi),可以利用大量資源建立英語/法語SMT系統(tǒng)。雙語語料庫包括Europarl(61M字),新聞評(píng)論(5.5M),UN(421M)和兩個(gè)分別為90M和780M字的爬蟲語料庫。最后兩個(gè)語料庫很嘈雜。
實(shí)驗(yàn)里使用的語料庫很大,是一個(gè)新聞?wù)Z料庫含有712M字。

應(yīng)該關(guān)注給定任務(wù)的最相關(guān)數(shù)據(jù)子集。論文從一個(gè)超過2G字中選取了一個(gè)418M字子集中用于語言建模,并從850M字中選擇348M的子集用于訓(xùn)練RNN encoder-decoder。我們使用測試集newstest2012和2013進(jìn)行數(shù)據(jù)選擇和使用MERT進(jìn)行重量調(diào)整,并使用newstest2014作為我們的測試集。每組有超過7萬個(gè)單詞和一個(gè)參考翻譯。
為了訓(xùn)練神經(jīng)網(wǎng)絡(luò),將源和目標(biāo)詞匯限制為英語和法語最常見的15,000個(gè)單詞。這涵蓋了大約93%的數(shù)據(jù)集。所有詞匯表外的單詞都被映射到一個(gè)特殊的令牌([UNK])。

可以發(fā)現(xiàn)在教程中的復(fù)現(xiàn)很粗糙,2014年的論文中使用了超過2G的數(shù)據(jù)集,現(xiàn)在的運(yùn)算量應(yīng)該更大……

下面讓我們來實(shí)現(xiàn)一下。

5. 模型實(shí)現(xiàn)

GRU Seq2Seq結(jié)構(gòu)圖.png

前一個(gè)模型的一個(gè)缺點(diǎn)是Decoder試圖將大量信息塞入隱藏狀態(tài)。在解碼時(shí),隱藏狀態(tài)將需要包含關(guān)于整個(gè)源序列的信息,以及到目前為止已經(jīng)解碼的所有token。通過減輕一些信息壓縮,我們可以創(chuàng)建一個(gè)更好的模型!
同時(shí)這里將使用LSTM的進(jìn)化版GRU。
另外從原論文來看,似乎這個(gè)模型復(fù)現(xiàn)是一個(gè)很粗糙的,使用的數(shù)據(jù)依舊是Multi30k。

Multi30K共有30K個(gè)圖片,每個(gè)圖片對(duì)應(yīng)的描述有兩大類,(i)每個(gè)圖片的英語描述和英語描述的德語翻譯,(ii)五個(gè)獨(dú)立的英語描述和德語描述(不是翻譯)。正因?yàn)樗?dú)立的收集不同語言對(duì)圖片的描述,因此可以更好地適用于有噪聲的多模態(tài)內(nèi)容。

這里我想試一下能不能使用WMT'14的數(shù)據(jù)集訓(xùn)練模型。不過還是先用Multi30k來實(shí)現(xiàn)。

5.1 導(dǎo)入庫和數(shù)據(jù)預(yù)處理

和之前的操作一致,導(dǎo)入必須庫,并對(duì)數(shù)據(jù)集進(jìn)行預(yù)處理

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)
torch.backends.cudnn.deterministic=True

spacy_de=spacy.load('de')
spacy_en=spacy.load('en')

def tokenize_de(text):
    return [tok.text for tok in spacy_de.tokenizer(text)]

def tokenize_en(text):
    return [tok.text for tok in spacy_en.tokenizer(text)]

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)

train_data,valid_data,test_data=Multi30k.splits(exts=('.de','.en'),fields=(SRC,TRG))
print(vars(train_data.examples[11]))
{'src': ['vier', 'typen', ',', 'von', 'denen', 'drei', 'hüte', 'tragen', 'und', 'einer', 'nicht', ',', 'springen', 'oben', 'in', 'einem', 'treppenhaus', '.'], 'trg': ['four', 'guys', 'three', 'wearing', 'hats', 'one', 'not', 'are', 'jumping', 'at', 'the', 'top', 'of', 'a', 'staircase', '.']}
SRC.build_vocab(train_data, min_freq=2)
TRG.build_vocab(train_data, min_freq=2)
device=torch.device('cuda' if torch.cuda.is_available() else 'cpu')
BATCH_SIZE=128
train_iterator, valid_iterator, test_iterator=BucketIterator.splits(
    (train_data,valid_data,test_data),
    batch_size=BATCH_SIZE,
    device=device
)

5.2 構(gòu)建模型

數(shù)據(jù)集和之前是一樣的,接下來構(gòu)建模型,這次使用的是GRU作為神經(jīng)網(wǎng)絡(luò)單元,結(jié)構(gòu)上有一定區(qū)別,但是在論文中沒有提及,論文對(duì)模型的描述更加概括。

5.2.1 參數(shù)設(shè)定

這里的GRU是一個(gè)單層,所以不需要n_layers參數(shù)。

INPUT_DIM=len(SRC.vocab)
OUTPUT_DIM=len(TRG.vocab)
ENC_EMB_DIM=256
DEC_EMB_DIM=256
HID_DIM=512
ENC_DROPOUT=0.5
DEC_DROPOUT=0.5

和上一個(gè)模型類似,三個(gè)部分,encoder、decoder和seq2seq

5.2.2 Seq2Seq

我把里面預(yù)生成的張量outputs打印了出來。

for i ,batch in enumerate(train_iterator):
    if i <1:
        print(i)
        src=batch.src
        trg=batch.trg
        print(type(src))
        print(src.shape)
        print(src)
        print(src.shape[0])
        print(src.shape[1])
        max_len=trg.shape[0]
        batch_size=trg.shape[1]
        trg_vocab_size=len(TRG.vocab)
        print(max_len)
        print(batch_size)
        print(trg_vocab_size)
        
        outputs=torch.zeros(max_len,batch_size,trg_vocab_size)
        print(outputs.shape)
        print(outputs)
    else: break
0
<class 'torch.Tensor'>
torch.Size([26, 128])
tensor([[  2,   2,   2,  ...,   2,   2,   2],
        [  8, 241,   5,  ...,   5,   5,   5],
        [168, 163,   0,  ...,  26, 550,  66],
        ...,
        [  1,   1,   1,  ...,   1,   1,   1],
        [  1,   1,   1,  ...,   1,   1,   1],
        [  1,   1,   1,  ...,   1,   1,   1]], device='cuda:0')
26
128
23
128
5893
torch.Size([23, 128, 5893])
tensor([[[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., 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., 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.]]])
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!"
    def forward(self, src, trg, teacher_forcing_ratio=0.5):
        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)
        context=self.encoder(src)
        hidden=context
        input=trg[0,:]
        
        for t in range(1,max_len):
            output, hidden=self.decoder(input,hidden,context)
            outputs[t]=output
            teacher_force=random.random()<teacher_forcing_ratio
            top1=output.max(1)[1]
            input=(trg[t] if teacher_forcing_ratio else top1)
        return outputs

5.2.3 Encoder

encoder和之前的很類似,因?yàn)槭褂肎RU不會(huì)輸出每個(gè)單元的狀態(tài),因此在返回中只是輸出了隱藏層的狀態(tài)。

class Encoder(nn.Module):
    def __init__(self, input_dim, emb_dim, hid_dim,dropout):
        super(Encoder,self).__init__()
        self.input_dim=input_dim
        self.emb_dim=emb_dim
        self.hid_dim=hid_dim
        self.dropout=dropout
        
        self.embedding=nn.Embedding(input_dim,emb_dim)
        self.rnn=nn.GRU(emb_dim, hid_dim)
        self.dropout=nn.Dropout(dropout)
        
    def forward(self, src):
        embedded=self.dropout(self.embedding(src))
        outputs, hidden=self.rnn(embedded)
        return hidden

5.2.4 Decoder

decoder因?yàn)橐彩褂肎RU,減少了信息壓縮,同時(shí)GRU獲取了目標(biāo)token y_t,上一個(gè)時(shí)間的隱藏狀態(tài)s_{t-1},上下文向量z。但是在這里要注意的是輸入的初始隱藏狀態(tài)s_0其實(shí)就是上下文向量,就是說,實(shí)際輸入的是兩個(gè)相同的上下文向量。

  • 在實(shí)現(xiàn)的時(shí)候,通過將y_tz串聯(lián)傳入GRU,所以輸入的維度應(yīng)該是emb_dim+ hid_dim
  • linear層輸入的是 y_t, s_tz串聯(lián),而隱藏狀態(tài)和上下文向量都是h維度相同,所以輸入的維度是emb_dim+hid_dim*2
  • forward現(xiàn)在需要一個(gè)上下文參數(shù)。在forward過程中,我們將y_tz連接成emb_con,然后輸入GRU,我們將y_t,s_tz連接在一起作為輸出,然后通過線性層提供它以接收我們的預(yù)測, \hat{Y} _ {T + 1}。
class Decoder(nn.Module):
    def __init__(self, output_dim, emb_dim, hid_dim, dropout):
        super(Decoder, self).__init__()
        self.output_dim=output_dim
        self.emb_dim=emb_dim
        self.hid_dim=hid_dim
        self.dropout=dropout
        
        self.embedding=nn.Embedding(output_dim,emb_dim)
        self.rnn=nn.GRU(emb_dim+hid_dim, hid_dim)
        self.out=nn.Linear(emb_dim+hid_dim*2, output_dim)
        self.dropout=nn.Dropout(dropout)
    def forward(self, input, hidden, context):
        input=input.unsqueeze(0)
        embedded=self.dropout(self.embedding(input))
        emb_con=torch.cat((embedded,context),dim=2)
        output,hidden=self.rnn(emb_con,hidden)
        output=torch.cat((embedded.squeeze(0),hidden.squeeze(0),context.squeeze(0)),dim=1)
        
        prediction=self.out(output)
        return prediction, hidden
enc = Encoder(INPUT_DIM, ENC_EMB_DIM, HID_DIM, ENC_DROPOUT)
dec = Decoder(OUTPUT_DIM, DEC_EMB_DIM, HID_DIM, DEC_DROPOUT)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# device = torch.device('cpu')
model=Seq2Seq(enc,dec,device).to(device)
def init_weights(m):
    for name,param in m.named_parameters():
        nn.init.normal_(param.data,mean=0,std=0.01)
        
model.apply(init_weights)
Seq2Seq(
  (encoder): Encoder(
    (embedding): Embedding(7855, 256)
    (rnn): GRU(256, 512)
    (dropout): Dropout(p=0.5)
  )
  (decoder): Decoder(
    (embedding): Embedding(5893, 256)
    (rnn): GRU(768, 512)
    (out): Linear(in_features=1280, 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 14,220,293 trainable parameters
optimizer=optim.Adam(model.parameters())
PAD_IDX=TRG.vocab.stoi['<pad>']
criterion=nn.CrossEntropyLoss(ignore_index=PAD_IDX)
# 構(gòu)建訓(xùn)練循環(huán)和驗(yàn)證循環(huán)
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 = 10
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(), 'tut2-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}')
Epoch: 01 | Time: 1m 14s
    Train Loss: 4.430 | Train PPL:  83.960
     Val. Loss: 7.065 |  Val. PPL: 1169.895
Epoch: 02 | Time: 1m 14s
    Train Loss: 3.577 | Train PPL:  35.783
     Val. Loss: 6.738 |  Val. PPL: 843.836
Epoch: 03 | Time: 1m 14s
    Train Loss: 3.146 | Train PPL:  23.237
     Val. Loss: 6.359 |  Val. PPL: 577.889
Epoch: 04 | Time: 1m 14s
    Train Loss: 2.742 | Train PPL:  15.519
     Val. Loss: 5.904 |  Val. PPL: 366.446
Epoch: 05 | Time: 1m 14s
    Train Loss: 2.393 | Train PPL:  10.949
     Val. Loss: 5.749 |  Val. PPL: 313.895
Epoch: 06 | Time: 1m 14s
    Train Loss: 2.096 | Train PPL:   8.136
     Val. Loss: 5.654 |  Val. PPL: 285.460
Epoch: 07 | Time: 1m 14s
    Train Loss: 1.836 | Train PPL:   6.272
     Val. Loss: 5.626 |  Val. PPL: 277.580
Epoch: 08 | Time: 1m 14s
    Train Loss: 1.609 | Train PPL:   4.996
     Val. Loss: 5.626 |  Val. PPL: 277.538
Epoch: 09 | Time: 1m 14s
    Train Loss: 1.414 | Train PPL:   4.113
     Val. Loss: 5.707 |  Val. PPL: 301.039
Epoch: 10 | Time: 1m 14s
    Train Loss: 1.246 | Train PPL:   3.475
     Val. Loss: 5.727 |  Val. PPL: 307.084
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容