PyTorch-18 使用Torchtext進(jìn)行語(yǔ)言翻譯(德語(yǔ)到英語(yǔ))

要查看圖文版教程跨晴,請(qǐng)移步到:http://studyai.com/pytorch-1.4/beginner/torchtext_translation_tutorial.html

本教程介紹如何使用 torchtext 提供的幾個(gè)便利的類(lèi)來(lái)預(yù)處理包含英語(yǔ)和德語(yǔ)句子的知名數(shù)據(jù)集中的數(shù)據(jù), 并使用它來(lái)訓(xùn)練一個(gè)帶有注意力機(jī)制的序列到序列的模型,將德語(yǔ)句子翻譯成英語(yǔ)著蟹。

本教程是基于PyTorch社區(qū)成員 Ben Trevett 創(chuàng)作的 這個(gè)教程

在本教程結(jié)束時(shí),你將能夠:

使用以下 torchtext 提供的類(lèi)將句子預(yù)處理為NLP建模的常用格式:
        TranslationDataset
        Field
        BucketIterator

Field 和 TranslationDataset

torchtext 有用于創(chuàng)建數(shù)據(jù)集的實(shí)用程序,這些數(shù)據(jù)集可以很容易地迭代以創(chuàng)建語(yǔ)言翻譯模型违寿。 一個(gè)關(guān)鍵類(lèi)是 Field, 它指定每個(gè)句子的預(yù)處理方式湃交, 另一個(gè)是 TranslationDataset ; torchtext 有幾個(gè)這樣的數(shù)據(jù)集藤巢;在本教程中搞莺,我們將使用 Multi30k dataset , 它包含大約30000個(gè)英語(yǔ)和德語(yǔ)句子(平均長(zhǎng)度約13個(gè)單詞)掂咒。

請(qǐng)注意: 本教程的詞語(yǔ)切分標(biāo)記(tokenization)需要 Spacy 才沧。 我們使用Spacy是因?yàn)樗谟⒄Z(yǔ)以外的語(yǔ)言中為詞語(yǔ)切分標(biāo)記提供了強(qiáng)大的支持。 torchtext 提供了一個(gè)基本的英語(yǔ)標(biāo)記器绍刮,并支持其他英語(yǔ)標(biāo)記器 (例如 Moses )温圆, 但對(duì)于語(yǔ)言翻譯-則需要多種語(yǔ)言-而Spacy是最好的選擇。

要運(yùn)行此教程录淡,首先要使用 pip 或 conda 安裝 spacy 捌木。 接著, 下載 English 和 German 原始數(shù)據(jù)的 Spacy tokenizers:

python -m spacy download en
python -m spacy download de

安裝好 Spacy 之后, 下面的代碼將基于 Field 中定義的標(biāo)記器(tokenizer)標(biāo)記 TranslationDataset 中的每個(gè)句子

from torchtext.datasets import Multi30k
from torchtext.data import Field, BucketIterator

SRC = Field(tokenize = "spacy",
            tokenizer_language="de",
            init_token = '<sos>',
            eos_token = '<eos>',
            lower = True)

TRG = Field(tokenize = "spacy",
            tokenizer_language="en",
            init_token = '<sos>',
            eos_token = '<eos>',
            lower = True)

train_data, valid_data, test_data = Multi30k.splits(exts = ('.de', '.en'),
                                                    fields = (SRC, TRG))

現(xiàn)在我們已經(jīng)定義了 train_data ,我們可以看到 torchtext 的 Field 的一個(gè)非常有用的功能: build_vocab 方法現(xiàn)在允許我們創(chuàng)建與每種語(yǔ)言相關(guān)聯(lián)的詞匯表(vocabulary)

SRC.build_vocab(train_data, min_freq = 2)
TRG.build_vocab(train_data, min_freq = 2)

一旦運(yùn)行上面這些代碼行后嫉戚, SRC.vocab.stoi 將是一個(gè)字典刨裆,其中詞匯表中的標(biāo)記為鍵,其相應(yīng)索引為值彬檀; SRC.vocab.itos 將是同一個(gè)字典帆啃,只是其中的鍵和值被交換了。 在本教程中窍帝,我們不會(huì)廣泛地使用這個(gè)事實(shí)努潘,但是在您將遇到的其他NLP任務(wù)中,這可能會(huì)很有用坤学。

BucketIterator

我們將要使用 torchtext 的最后一個(gè)特別的功能是 BucketIterator, 因?yàn)樗邮芤粋€(gè) TranslationDataset 作為其第一個(gè)參數(shù)疯坤,所以非常簡(jiǎn)單易用。 就像文檔中描述的那樣: 定義一個(gè)迭代器深浮,用于將相似長(zhǎng)度的樣本組織到同一個(gè)batch中压怠。 在為每個(gè)新的回合(new epoch)生產(chǎn)新的隨機(jī)batch時(shí),最小化所需的填充量(padding)飞苇。

import torch

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)

這些迭代器(iterators)可以被調(diào)用菌瘫,就像調(diào)用 DataLoader 的迭代器一樣; 下面, 在 函數(shù) train 和 evaluate 中, 可以簡(jiǎn)單地使用以下方式來(lái)調(diào)用:

for i, batch in enumerate(iterator):

每一個(gè) batch 都有 src 和 trg 屬性:

src = batch.src
trg = batch.trg

定義 nn.Module 和 Optimizer

從 torchtext 的角度來(lái)看:隨著數(shù)據(jù)集的構(gòu)建和迭代器(iterator)的定義, 本教程的其余部分只是將我們的模型定義為 nn.Module 以及創(chuàng)建一個(gè) Optimizer布卡,然后對(duì)其進(jìn)行訓(xùn)練雨让。

具體來(lái)說(shuō),我們的模型遵循了 這里 描述的架構(gòu)(您可以在 這里 找到一個(gè)更具注釋性的版本)忿等。

注意: 這個(gè)模型只是一個(gè)可用于語(yǔ)言翻譯的示例模型栖忠;我們選擇它是因?yàn)樗窃撊蝿?wù)的一個(gè)標(biāo)準(zhǔn)模型, 而不是因?yàn)樗潜粡?qiáng)烈推薦用于翻譯任務(wù)的模型。正如您可能知道的娃闲,最先進(jìn)的模型當(dāng)前基于Transformers虚汛; 您可以在 這里 看到PyTorch實(shí)現(xiàn) Transformer layers 的能力; 特別是皇帮,下面模型中使用的 “attention” 不同于Transformer模型中的多頭自我注意(multi-headed self-attention)卷哩。

import random
from typing import Tuple

import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch import Tensor


class Encoder(nn.Module):
    def __init__(self,
                 input_dim: int,
                 emb_dim: int,
                 enc_hid_dim: int,
                 dec_hid_dim: int,
                 dropout: float):
        super().__init__()

        self.input_dim = input_dim
        self.emb_dim = emb_dim
        self.enc_hid_dim = enc_hid_dim
        self.dec_hid_dim = dec_hid_dim
        self.dropout = dropout

        self.embedding = nn.Embedding(input_dim, emb_dim)

        self.rnn = nn.GRU(emb_dim, enc_hid_dim, bidirectional = True)

        self.fc = nn.Linear(enc_hid_dim * 2, dec_hid_dim)

        self.dropout = nn.Dropout(dropout)

    def forward(self,
                src: Tensor) -> Tuple[Tensor]:

        embedded = self.dropout(self.embedding(src))

        outputs, hidden = self.rnn(embedded)

        hidden = torch.tanh(self.fc(torch.cat((hidden[-2,:,:], hidden[-1,:,:]), dim = 1)))

        return outputs, hidden


class Attention(nn.Module):
    def __init__(self,
                 enc_hid_dim: int,
                 dec_hid_dim: int,
                 attn_dim: int):
        super().__init__()

        self.enc_hid_dim = enc_hid_dim
        self.dec_hid_dim = dec_hid_dim

        self.attn_in = (enc_hid_dim * 2) + dec_hid_dim

        self.attn = nn.Linear(self.attn_in, attn_dim)

    def forward(self,
                decoder_hidden: Tensor,
                encoder_outputs: Tensor) -> Tensor:

        src_len = encoder_outputs.shape[0]

        repeated_decoder_hidden = decoder_hidden.unsqueeze(1).repeat(1, src_len, 1)

        encoder_outputs = encoder_outputs.permute(1, 0, 2)

        energy = torch.tanh(self.attn(torch.cat((
            repeated_decoder_hidden,
            encoder_outputs),
            dim = 2)))

        attention = torch.sum(energy, dim=2)

        return F.softmax(attention, dim=1)


class Decoder(nn.Module):
    def __init__(self,
                 output_dim: int,
                 emb_dim: int,
                 enc_hid_dim: int,
                 dec_hid_dim: int,
                 dropout: int,
                 attention: nn.Module):
        super().__init__()

        self.emb_dim = emb_dim
        self.enc_hid_dim = enc_hid_dim
        self.dec_hid_dim = dec_hid_dim
        self.output_dim = output_dim
        self.dropout = dropout
        self.attention = attention

        self.embedding = nn.Embedding(output_dim, emb_dim)

        self.rnn = nn.GRU((enc_hid_dim * 2) + emb_dim, dec_hid_dim)

        self.out = nn.Linear(self.attention.attn_in + emb_dim, output_dim)

        self.dropout = nn.Dropout(dropout)


    def _weighted_encoder_rep(self,
                              decoder_hidden: Tensor,
                              encoder_outputs: Tensor) -> Tensor:

        a = self.attention(decoder_hidden, encoder_outputs)

        a = a.unsqueeze(1)

        encoder_outputs = encoder_outputs.permute(1, 0, 2)

        weighted_encoder_rep = torch.bmm(a, encoder_outputs)

        weighted_encoder_rep = weighted_encoder_rep.permute(1, 0, 2)

        return weighted_encoder_rep


    def forward(self,
                input: Tensor,
                decoder_hidden: Tensor,
                encoder_outputs: Tensor) -> Tuple[Tensor]:

        input = input.unsqueeze(0)

        embedded = self.dropout(self.embedding(input))

        weighted_encoder_rep = self._weighted_encoder_rep(decoder_hidden,
                                                          encoder_outputs)

        rnn_input = torch.cat((embedded, weighted_encoder_rep), dim = 2)

        output, decoder_hidden = self.rnn(rnn_input, decoder_hidden.unsqueeze(0))

        embedded = embedded.squeeze(0)
        output = output.squeeze(0)
        weighted_encoder_rep = weighted_encoder_rep.squeeze(0)

        output = self.out(torch.cat((output,
                                     weighted_encoder_rep,
                                     embedded), dim = 1))

        return output, decoder_hidden.squeeze(0)


class Seq2Seq(nn.Module):
    def __init__(self,
                 encoder: nn.Module,
                 decoder: nn.Module,
                 device: torch.device):
        super().__init__()

        self.encoder = encoder
        self.decoder = decoder
        self.device = device

    def forward(self,
                src: Tensor,
                trg: Tensor,
                teacher_forcing_ratio: float = 0.5) -> Tensor:

        batch_size = src.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)

        encoder_outputs, hidden = self.encoder(src)

        # first input to the decoder is the <sos> token
        output = trg[0,:]

        for t in range(1, max_len):
            output, hidden = self.decoder(output, hidden, encoder_outputs)
            outputs[t] = output
            teacher_force = random.random() < teacher_forcing_ratio
            top1 = output.max(1)[1]
            output = (trg[t] if teacher_force else top1)

        return outputs


INPUT_DIM = len(SRC.vocab)
OUTPUT_DIM = len(TRG.vocab)
# ENC_EMB_DIM = 256
# DEC_EMB_DIM = 256
# ENC_HID_DIM = 512
# DEC_HID_DIM = 512
# ATTN_DIM = 64
# ENC_DROPOUT = 0.5
# DEC_DROPOUT = 0.5

ENC_EMB_DIM = 32
DEC_EMB_DIM = 32
ENC_HID_DIM = 64
DEC_HID_DIM = 64
ATTN_DIM = 8
ENC_DROPOUT = 0.5
DEC_DROPOUT = 0.5

enc = Encoder(INPUT_DIM, ENC_EMB_DIM, ENC_HID_DIM, DEC_HID_DIM, ENC_DROPOUT)

attn = Attention(ENC_HID_DIM, DEC_HID_DIM, ATTN_DIM)

dec = Decoder(OUTPUT_DIM, DEC_EMB_DIM, ENC_HID_DIM, DEC_HID_DIM, DEC_DROPOUT, attn)

model = Seq2Seq(enc, dec, device).to(device)


def init_weights(m: nn.Module):
    for name, param in m.named_parameters():
        if 'weight' in name:
            nn.init.normal_(param.data, mean=0, std=0.01)
        else:
            nn.init.constant_(param.data, 0)


model.apply(init_weights)

optimizer = optim.Adam(model.parameters())


def count_parameters(model: nn.Module):
    return sum(p.numel() for p in model.parameters() if p.requires_grad)


print(f'The model has {count_parameters(model):,} trainable parameters')

注意:在對(duì)語(yǔ)言翻譯模型的性能進(jìn)行評(píng)分時(shí),我們必須告訴 nn.CrossEntropyLoss 函數(shù)忽略目標(biāo)只是填充的索引 (ignore the indices where the target is simply padding.)属拾。

PAD_IDX = TRG.vocab.stoi['<pad>']

criterion = nn.CrossEntropyLoss(ignore_index=PAD_IDX)

最后将谊,我們可以訓(xùn)練和評(píng)估這個(gè)模型:

import math
import time


def train(model: nn.Module,
          iterator: BucketIterator,
          optimizer: optim.Optimizer,
          criterion: nn.Module,
          clip: float):

    model.train()

    epoch_loss = 0

    for _, batch in enumerate(iterator):

        src = batch.src
        trg = batch.trg

        optimizer.zero_grad()

        output = model(src, trg)

        output = output[1:].view(-1, output.shape[-1])
        trg = trg[1:].view(-1)

        loss = criterion(output, trg)

        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: nn.Module,
             iterator: BucketIterator,
             criterion: nn.Module):

    model.eval()

    epoch_loss = 0

    with torch.no_grad():

        for _, batch in enumerate(iterator):

            src = batch.src
            trg = batch.trg

            output = model(src, trg, 0) #turn off teacher forcing

            output = output[1:].view(-1, output.shape[-1])
            trg = trg[1:].view(-1)

            loss = criterion(output, trg)

            epoch_loss += loss.item()

    return epoch_loss / len(iterator)


def epoch_time(start_time: int,
               end_time: int):
    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)

    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}')

test_loss = evaluate(model, test_iterator, criterion)

print(f'| Test Loss: {test_loss:.3f} | Test PPL: {math.exp(test_loss):7.3f} |')

下一步

使用 torchtext 繼續(xù)學(xué)習(xí) Ben Trevett 的教程的剩余部分—- 在這里
請(qǐng)繼續(xù)關(guān)注使用其他 torchtext 功能特性以及 nn.Transformer 的教程,以便學(xué)習(xí)通過(guò)預(yù)測(cè)下一個(gè)單詞進(jìn)行語(yǔ)言建模渐白!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末尊浓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子纯衍,更是在濱河造成了極大的恐慌栋齿,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件襟诸,死亡現(xiàn)場(chǎng)離奇詭異瓦堵,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)歌亲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)菇用,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人陷揪,你說(shuō)我怎么就攤上這事惋鸥。” “怎么了悍缠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵卦绣,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我飞蚓,道長(zhǎng)滤港,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任玷坠,我火速辦了婚禮蜗搔,結(jié)果婚禮上劲藐,老公的妹妹穿的比我還像新娘八堡。我一直安慰自己,他們只是感情好聘芜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布兄渺。 她就那樣靜靜地躺著,像睡著了一般汰现。 火紅的嫁衣襯著肌膚如雪挂谍。 梳的紋絲不亂的頭發(fā)上叔壤,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音口叙,去河邊找鬼炼绘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛妄田,可吹牛的內(nèi)容都是我干的俺亮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼疟呐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼脚曾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起启具,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤本讥,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后鲁冯,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拷沸,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年晓褪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了堵漱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涣仿,死狀恐怖勤庐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情好港,我是刑警寧澤愉镰,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站钧汹,受9級(jí)特大地震影響丈探,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拔莱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一碗降、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧塘秦,春花似錦讼渊、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春挨稿,著一層夾襖步出監(jiān)牢的瞬間仇轻,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工奶甘, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留篷店,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓臭家,卻偏偏與公主長(zhǎng)得像船庇,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子侣监,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容