当前位置:   article > 正文

PyTorch 使用Torchtext进行语言翻译(德语到英语)_from torchtext.datasets import multi30k

from torchtext.datasets import multi30k
要查看图文版教程,请移步到:http://studyai.com/pytorch-1.4/beginner/torchtext_translation_tutorial.html

本教程介绍如何使用 torchtext 提供的几个便利的类来预处理包含英语和德语句子的知名数据集中的数据, 并使用它来训练一个带有注意力机制的序列到序列的模型,将德语句子翻译成英语。

本教程是基于PyTorch社区成员 Ben Trevett 创作的 这个教程

在本教程结束时,你将能够:

使用以下 torchtext 提供的类将句子预处理为NLP建模的常用格式:
        TranslationDataset
        Field
        BucketIterator
  • 1
  • 2
  • 3

Field 和 TranslationDataset

torchtext 有用于创建数据集的实用程序,这些数据集可以很容易地迭代以创建语言翻译模型。 一个关键类是 Field, 它指定每个句子的预处理方式, 另一个是 TranslationDataset ; torchtext 有几个这样的数据集;在本教程中,我们将使用 Multi30k dataset , 它包含大约30000个英语和德语句子(平均长度约13个单词)。

请注意: 本教程的词语切分标记(tokenization)需要 Spacy 。 我们使用Spacy是因为它在英语以外的语言中为词语切分标记提供了强大的支持。 torchtext 提供了一个基本的英语标记器,并支持其他英语标记器 (例如 Moses ), 但对于语言翻译-则需要多种语言-而Spacy是最好的选择。

要运行此教程,首先要使用 pip 或 conda 安装 spacy 。 接着, 下载 English 和 German 原始数据的 Spacy tokenizers:

python -m spacy download en
python -m spacy download de
  • 1

安装好 Spacy 之后, 下面的代码将基于 Field 中定义的标记器(tokenizer)标记 TranslationDataset 中的每个句子

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))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

现在我们已经定义了 train_data ,我们可以看到 torchtext 的 Field 的一个非常有用的功能: build_vocab 方法现在允许我们创建与每种语言相关联的词汇表(vocabulary)

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

一旦运行上面这些代码行后, SRC.vocab.stoi 将是一个字典,其中词汇表中的标记为键,其相应索引为值; SRC.vocab.itos 将是同一个字典,只是其中的键和值被交换了。 在本教程中,我们不会广泛地使用这个事实,但是在您将遇到的其他NLP任务中,这可能会很有用。

BucketIterator

我们将要使用 torchtext 的最后一个特别的功能是 BucketIterator, 因为它接受一个 TranslationDataset 作为其第一个参数,所以非常简单易用。 就像文档中描述的那样: 定义一个迭代器,用于将相似长度的样本组织到同一个batch中。 在为每个新的回合(new epoch)生产新的随机batch时,最小化所需的填充量(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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这些迭代器(iterators)可以被调用,就像调用 DataLoader 的迭代器一样; 下面, 在 函数 train 和 evaluate 中, 可以简单地使用以下方式来调用:

for i, batch in enumerate(iterator):

    每一个 batch 都有 src 和 trg 属性:

    src = batch.src
    trg = batch.trg
    • 1

    定义 nn.Module 和 Optimizer

    从 torchtext 的角度来看:随着数据集的构建和迭代器(iterator)的定义, 本教程的其余部分只是将我们的模型定义为 nn.Module 以及创建一个 Optimizer,然后对其进行训练。

    具体来说,我们的模型遵循了 这里 描述的架构(您可以在 这里 找到一个更具注释性的版本)。

    注意: 这个模型只是一个可用于语言翻译的示例模型;我们选择它是因为它是该任务的一个标准模型, 而不是因为它是被强烈推荐用于翻译任务的模型。正如您可能知道的,最先进的模型当前基于Transformers; 您可以在 这里 看到PyTorch实现 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')
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229

    注意:在对语言翻译模型的性能进行评分时,我们必须告诉 nn.CrossEntropyLoss 函数忽略目标只是填充的索引 (ignore the indices where the target is simply padding.)。

    PAD_IDX = TRG.vocab.stoi['<pad>']
    
    criterion = nn.CrossEntropyLoss(ignore_index=PAD_IDX)
    • 1
    • 2

    最后,我们可以训练和评估这个模型:

    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} |')
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

    下一步

    使用 torchtext 继续学习 Ben Trevett 的教程的剩余部分—- 在这里
    请继续关注使用其他 torchtext 功能特性以及 nn.Transformer 的教程,以便学习通过预测下一个单词进行语言建模!
    • 1
    声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/372524
    推荐阅读
    相关标签
      

    闽ICP备14008679号