<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # 10.12 機器翻譯 機器翻譯是指將一段文本從一種語言自動翻譯到另一種語言。因為一段文本序列在不同語言中的長度不一定相同,所以我們使用機器翻譯為例來介紹編碼器—解碼器和注意力機制的應用。 ## 10.12.1 讀取和預處理數據 我們先定義一些特殊符號。其中“&lt;pad&gt;”(padding)符號用來添加在較短序列后,直到每個序列等長,而“&lt;bos&gt;”和“&lt;eos&gt;”符號分別表示序列的開始和結束。 ``` python import collections import os import io import math import torch from torch import nn import torch.nn.functional as F import torchtext.vocab as Vocab import torch.utils.data as Data import sys sys.path.append("..") import d2lzh_pytorch as d2l PAD, BOS, EOS = '<pad>', '<bos>', '<eos>' os.environ["CUDA_VISIBLE_DEVICES"] = "0" device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') ``` 接著定義兩個輔助函數對后面讀取的數據進行預處理。 ``` python # 將一個序列中所有的詞記錄在all_tokens中以便之后構造詞典,然后在該序列后面添加PAD直到序列 # 長度變為max_seq_len,然后將序列保存在all_seqs中 def process_one_seq(seq_tokens, all_tokens, all_seqs, max_seq_len): all_tokens.extend(seq_tokens) seq_tokens += [EOS] + [PAD] * (max_seq_len - len(seq_tokens) - 1) all_seqs.append(seq_tokens) # 使用所有的詞來構造詞典。并將所有序列中的詞變換為詞索引后構造Tensor def build_data(all_tokens, all_seqs): vocab = Vocab.Vocab(collections.Counter(all_tokens), specials=[PAD, BOS, EOS]) indices = [[vocab.stoi[w] for w in seq] for seq in all_seqs] return vocab, torch.tensor(indices) ``` 為了演示方便,我們在這里使用一個很小的法語—英語數據集。在這個數據集里,每一行是一對法語句子和它對應的英語句子,中間使用`'\t'`隔開。在讀取數據時,我們在句末附上“&lt;eos&gt;”符號,并可能通過添加“&lt;pad&gt;”符號使每個序列的長度均為`max_seq_len`。我們為法語詞和英語詞分別創建詞典。法語詞的索引和英語詞的索引相互獨立。 ``` python def read_data(max_seq_len): # in和out分別是input和output的縮寫 in_tokens, out_tokens, in_seqs, out_seqs = [], [], [], [] with io.open('../../data/fr-en-small.txt') as f: lines = f.readlines() for line in lines: in_seq, out_seq = line.rstrip().split('\t') in_seq_tokens, out_seq_tokens = in_seq.split(' '), out_seq.split(' ') if max(len(in_seq_tokens), len(out_seq_tokens)) > max_seq_len - 1: continue # 如果加上EOS后長于max_seq_len,則忽略掉此樣本 process_one_seq(in_seq_tokens, in_tokens, in_seqs, max_seq_len) process_one_seq(out_seq_tokens, out_tokens, out_seqs, max_seq_len) in_vocab, in_data = build_data(in_tokens, in_seqs) out_vocab, out_data = build_data(out_tokens, out_seqs) return in_vocab, out_vocab, Data.TensorDataset(in_data, out_data) ``` 將序列的最大長度設成7,然后查看讀取到的第一個樣本。該樣本分別包含法語詞索引序列和英語詞索引序列。 ``` python max_seq_len = 7 in_vocab, out_vocab, dataset = read_data(max_seq_len) dataset[0] ``` 輸出: ``` (tensor([ 5, 4, 45, 3, 2, 0, 0]), tensor([ 8, 4, 27, 3, 2, 0, 0])) ``` ## 10.12.2 含注意力機制的編碼器—解碼器 我們將使用含注意力機制的編碼器—解碼器來將一段簡短的法語翻譯成英語。下面我們來介紹模型的實現。 ### 10.12.2.1 編碼器 在編碼器中,我們將輸入語言的詞索引通過詞嵌入層得到詞的表征,然后輸入到一個多層門控循環單元中。正如我們在6.5節(循環神經網絡的簡潔實現)中提到的,PyTorch的`nn.GRU`實例在前向計算后也會分別返回輸出和最終時間步的多層隱藏狀態。其中的輸出指的是最后一層的隱藏層在各個時間步的隱藏狀態,并不涉及輸出層計算。注意力機制將這些輸出作為鍵項和值項。 ``` python class Encoder(nn.Module): def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, drop_prob=0, **kwargs): super(Encoder, self).__init__(**kwargs) self.embedding = nn.Embedding(vocab_size, embed_size) self.rnn = nn.GRU(embed_size, num_hiddens, num_layers, dropout=drop_prob) def forward(self, inputs, state): # 輸入形狀是(批量大小, 時間步數)。將輸出互換樣本維和時間步維 embedding = self.embedding(inputs.long()).permute(1, 0, 2) # (seq_len, batch, input_size) return self.rnn(embedding, state) def begin_state(self): return None # 隱藏態初始化為None時PyTorch會自動初始化為0 ``` 下面我們來創建一個批量大小為4、時間步數為7的小批量序列輸入。設門控循環單元的隱藏層個數為2,隱藏單元個數為16。編碼器對該輸入執行前向計算后返回的輸出形狀為(時間步數, 批量大小, 隱藏單元個數)。門控循環單元在最終時間步的多層隱藏狀態的形狀為(隱藏層個數, 批量大小, 隱藏單元個數)。對于門控循環單元來說,`state`就是一個元素,即隱藏狀態;如果使用長短期記憶,`state`是一個元組,包含兩個元素即隱藏狀態和記憶細胞。 ``` python encoder = Encoder(vocab_size=10, embed_size=8, num_hiddens=16, num_layers=2) output, state = encoder(torch.zeros((4, 7)), encoder.begin_state()) output.shape, state.shape # GRU的state是h, 而LSTM的是一個元組(h, c) ``` 輸出: ``` (torch.Size([7, 4, 16]), torch.Size([2, 4, 16])) ``` ### 10.12.2.2 注意力機制 我們將實現10.11節(注意力機制)中定義的函數`$ a $`:將輸入連結后通過含單隱藏層的多層感知機變換。其中隱藏層的輸入是解碼器的隱藏狀態與編碼器在所有時間步上隱藏狀態的一一連結,且使用tanh函數作為激活函數。輸出層的輸出個數為1。兩個`Linear`實例均不使用偏差。其中函數`$ a $`定義里向量`$ \boldsymbol{v} $`的長度是一個超參數,即`attention_size`。 ``` python def attention_model(input_size, attention_size): model = nn.Sequential(nn.Linear(input_size, attention_size, bias=False), nn.Tanh(), nn.Linear(attention_size, 1, bias=False)) return model ``` 注意力機制的輸入包括查詢項、鍵項和值項。設編碼器和解碼器的隱藏單元個數相同。這里的查詢項為解碼器在上一時間步的隱藏狀態,形狀為(批量大小, 隱藏單元個數);鍵項和值項均為編碼器在所有時間步的隱藏狀態,形狀為(時間步數, 批量大小, 隱藏單元個數)。注意力機制返回當前時間步的背景變量,形狀為(批量大小, 隱藏單元個數)。 ``` python def attention_forward(model, enc_states, dec_state): """ enc_states: (時間步數, 批量大小, 隱藏單元個數) dec_state: (批量大小, 隱藏單元個數) """ # 將解碼器隱藏狀態廣播到和編碼器隱藏狀態形狀相同后進行連結 dec_states = dec_state.unsqueeze(dim=0).expand_as(enc_states) enc_and_dec_states = torch.cat((enc_states, dec_states), dim=2) e = model(enc_and_dec_states) # 形狀為(時間步數, 批量大小, 1) alpha = F.softmax(e, dim=0) # 在時間步維度做softmax運算 return (alpha * enc_states).sum(dim=0) # 返回背景變量 ``` 在下面的例子中,編碼器的時間步數為10,批量大小為4,編碼器和解碼器的隱藏單元個數均為8。注意力機制返回一個小批量的背景向量,每個背景向量的長度等于編碼器的隱藏單元個數。因此輸出的形狀為(4, 8)。 ``` python seq_len, batch_size, num_hiddens = 10, 4, 8 model = attention_model(2*num_hiddens, 10) enc_states = torch.zeros((seq_len, batch_size, num_hiddens)) dec_state = torch.zeros((batch_size, num_hiddens)) attention_forward(model, enc_states, dec_state).shape # torch.Size([4, 8]) ``` ### 10.12.2.3 含注意力機制的解碼器 我們直接將編碼器在最終時間步的隱藏狀態作為解碼器的初始隱藏狀態。這要求編碼器和解碼器的循環神經網絡使用相同的隱藏層個數和隱藏單元個數。 在解碼器的前向計算中,我們先通過剛剛介紹的注意力機制計算得到當前時間步的背景向量。由于解碼器的輸入來自輸出語言的詞索引,我們將輸入通過詞嵌入層得到表征,然后和背景向量在特征維連結。我們將連結后的結果與上一時間步的隱藏狀態通過門控循環單元計算出當前時間步的輸出與隱藏狀態。最后,我們將輸出通過全連接層變換為有關各個輸出詞的預測,形狀為(批量大小, 輸出詞典大小)。 ``` python class Decoder(nn.Module): def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, attention_size, drop_prob=0): super(Decoder, self).__init__() self.embedding = nn.Embedding(vocab_size, embed_size) self.attention = attention_model(2*num_hiddens, attention_size) # GRU的輸入包含attention輸出的c和實際輸入, 所以尺寸是 2*embed_size self.rnn = nn.GRU(2*embed_size, num_hiddens, num_layers, dropout=drop_prob) self.out = nn.Linear(num_hiddens, vocab_size) def forward(self, cur_input, state, enc_states): """ cur_input shape: (batch, ) state shape: (num_layers, batch, num_hiddens) """ # 使用注意力機制計算背景向量 c = attention_forward(self.attention, enc_states, state[-1]) # 將嵌入后的輸入和背景向量在特征維連結 input_and_c = torch.cat((self.embedding(cur_input), c), dim=1) # (批量大小, 2*embed_size) # 為輸入和背景向量的連結增加時間步維,時間步個數為1 output, state = self.rnn(input_and_c.unsqueeze(0), state) # 移除時間步維,輸出形狀為(批量大小, 輸出詞典大小) output = self.out(output).squeeze(dim=0) return output, state def begin_state(self, enc_state): # 直接將編碼器最終時間步的隱藏狀態作為解碼器的初始隱藏狀態 return enc_state ``` ## 10.12.3 訓練模型 我們先實現`batch_loss`函數計算一個小批量的損失。解碼器在最初時間步的輸入是特殊字符`BOS`。之后,解碼器在某時間步的輸入為樣本輸出序列在上一時間步的詞,即強制教學。此外,同10.3節(word2vec的實現)中的實現一樣,我們在這里也使用掩碼變量避免填充項對損失函數計算的影響。 ``` python def batch_loss(encoder, decoder, X, Y, loss): batch_size = X.shape[0] enc_state = encoder.begin_state() enc_outputs, enc_state = encoder(X, enc_state) # 初始化解碼器的隱藏狀態 dec_state = decoder.begin_state(enc_state) # 解碼器在最初時間步的輸入是BOS dec_input = torch.tensor([out_vocab.stoi[BOS]] * batch_size) # 我們將使用掩碼變量mask來忽略掉標簽為填充項PAD的損失 mask, num_not_pad_tokens = torch.ones(batch_size,), 0 l = torch.tensor([0.0]) for y in Y.permute(1,0): # Y shape: (batch, seq_len) dec_output, dec_state = decoder(dec_input, dec_state, enc_outputs) l = l + (mask * loss(dec_output, y)).sum() dec_input = y # 使用強制教學 num_not_pad_tokens += mask.sum().item() # 將PAD對應位置的掩碼設成0, 原文這里是 y != out_vocab.stoi[EOS], 感覺有誤 mask = mask * (y != out_vocab.stoi[PAD]).float() return l / num_not_pad_tokens ``` 在訓練函數中,我們需要同時迭代編碼器和解碼器的模型參數。 ``` python def train(encoder, decoder, dataset, lr, batch_size, num_epochs): enc_optimizer = torch.optim.Adam(encoder.parameters(), lr=lr) dec_optimizer = torch.optim.Adam(decoder.parameters(), lr=lr) loss = nn.CrossEntropyLoss(reduction='none') data_iter = Data.DataLoader(dataset, batch_size, shuffle=True) for epoch in range(num_epochs): l_sum = 0.0 for X, Y in data_iter: enc_optimizer.zero_grad() dec_optimizer.zero_grad() l = batch_loss(encoder, decoder, X, Y, loss) l.backward() enc_optimizer.step() dec_optimizer.step() l_sum += l.item() if (epoch + 1) % 10 == 0: print("epoch %d, loss %.3f" % (epoch + 1, l_sum / len(data_iter))) ``` 接下來,創建模型實例并設置超參數。然后,我們就可以訓練模型了。 ``` python embed_size, num_hiddens, num_layers = 64, 64, 2 attention_size, drop_prob, lr, batch_size, num_epochs = 10, 0.5, 0.01, 2, 50 encoder = Encoder(len(in_vocab), embed_size, num_hiddens, num_layers, drop_prob) decoder = Decoder(len(out_vocab), embed_size, num_hiddens, num_layers, attention_size, drop_prob) train(encoder, decoder, dataset, lr, batch_size, num_epochs) ``` 輸出: ``` epoch 10, loss 0.441 epoch 20, loss 0.183 epoch 30, loss 0.100 epoch 40, loss 0.046 epoch 50, loss 0.025 ``` ## 10.12.4 預測不定長的序列 在10.10節(束搜索)中我們介紹了3種方法來生成解碼器在每個時間步的輸出。這里我們實現最簡單的貪婪搜索。 ``` python def translate(encoder, decoder, input_seq, max_seq_len): in_tokens = input_seq.split(' ') in_tokens += [EOS] + [PAD] * (max_seq_len - len(in_tokens) - 1) enc_input = torch.tensor([[in_vocab.stoi[tk] for tk in in_tokens]]) # batch=1 enc_state = encoder.begin_state() enc_output, enc_state = encoder(enc_input, enc_state) dec_input = torch.tensor([out_vocab.stoi[BOS]]) dec_state = decoder.begin_state(enc_state) output_tokens = [] for _ in range(max_seq_len): dec_output, dec_state = decoder(dec_input, dec_state, enc_output) pred = dec_output.argmax(dim=1) pred_token = out_vocab.itos[int(pred.item())] if pred_token == EOS: # 當任一時間步搜索出EOS時,輸出序列即完成 break else: output_tokens.append(pred_token) dec_input = pred return output_tokens ``` 簡單測試一下模型。輸入法語句子“ils regardent.”,翻譯后的英語句子應該是“they are watching.”。 ``` python input_seq = 'ils regardent .' translate(encoder, decoder, input_seq, max_seq_len) ``` 輸出: ``` ['they', 'are', 'watching', '.'] ``` ## 10.12.5 評價翻譯結果 評價機器翻譯結果通常使用BLEU(Bilingual Evaluation Understudy)[1]。對于模型預測序列中任意的子序列,BLEU考察這個子序列是否出現在標簽序列中。 具體來說,設詞數為$n$的子序列的精度為`$ p_n $`。它是預測序列與標簽序列匹配詞數為`$ n $`的子序列的數量與預測序列中詞數為`$ n $`的子序列的數量之比。舉個例子,假設標簽序列為`$ A $`、`$ B $`、`$ C $`、`$ D $` 、`$ E $`、`$ F $`,預測序列為`$ A $` 、`$ B $`、`$ B $`、`$ C $`、`$ D $`,那么`$ p_1 = 4/5,\ p_2 = 3/4,\ p_3 = 1/3,\ p_4 = 0 $`。設`$ len_{\text{label}} $`和`$ len_{\text{pred}} $`分別為標簽序列和預測序列的詞數,那么,BLEU的定義為 ```[tex] \exp\left(\min\left(0, 1 - \frac{len_{\text{label}}}{len_{\text{pred}}}\right)\right) \prod_{n=1}^k p_n^{1/2^n}, ``` 其中`$ k $`是我們希望匹配的子序列的最大詞數。可以看到當預測序列和標簽序列完全一致時,BLEU為1。 因為匹配較長子序列比匹配較短子序列更難,BLEU對匹配較長子序列的精度賦予了更大權重。例如,當`$ p_n $`固定在0.5時,隨著`$ n $`的增大,`$ 0.5^{1/2} \approx 0.7, 0.5^{1/4} \approx 0.84, 0.5^{1/8} \approx 0.92, 0.5^{1/16} \approx 0.96 $`。另外,模型預測較短序列往往會得到較高`$ p_n $`值。因此,上式中連乘項前面的系數是為了懲罰較短的輸出而設的。舉個例子,當`$ k=2 $`時,假設標簽序列為`$ A $`、`$ B $`、`$ C $`、`$ D $`、`$ E $`、`$ F $`,而預測序列為`$ A $`、`$ B $`。雖然`$ p_1 = p_2 = 1 $`,但懲罰系數`$ \exp(1-6/2) \approx 0.14 $`,因此BLEU也接近0.14。 下面來實現BLEU的計算。 ``` python def bleu(pred_tokens, label_tokens, k): len_pred, len_label = len(pred_tokens), len(label_tokens) score = math.exp(min(0, 1 - len_label / len_pred)) for n in range(1, k + 1): num_matches, label_subs = 0, collections.defaultdict(int) for i in range(len_label - n + 1): label_subs[''.join(label_tokens[i: i + n])] += 1 for i in range(len_pred - n + 1): if label_subs[''.join(pred_tokens[i: i + n])] > 0: num_matches += 1 label_subs[''.join(pred_tokens[i: i + n])] -= 1 score *= math.pow(num_matches / (len_pred - n + 1), math.pow(0.5, n)) return score ``` 接下來,定義一個輔助打印函數。 ``` python def score(input_seq, label_seq, k): pred_tokens = translate(encoder, decoder, input_seq, max_seq_len) label_tokens = label_seq.split(' ') print('bleu %.3f, predict: %s' % (bleu(pred_tokens, label_tokens, k), ' '.join(pred_tokens))) ``` 預測正確則分數為1。 ``` python score('ils regardent .', 'they are watching .', k=2) ``` 輸出: ``` bleu 1.000, predict: they are watching . ``` 測試一個不在訓練集中的樣本。 ``` python score('ils sont canadiens .', 'they are canadian .', k=2) ``` 輸出: ``` bleu 0.658, predict: they are russian . ``` ## 小結 * 可以將編碼器—解碼器和注意力機制應用于機器翻譯中。 * BLEU可以用來評價翻譯結果。 ## 參考文獻 [1] Papineni, K., Roukos, S., Ward, T., & Zhu, W. J. (2002, July). BLEU: a method for automatic evaluation of machine translation. In Proceedings of the 40th annual meeting on association for computational linguistics (pp. 311-318). Association for Computational Linguistics. [2] WMT. http://www.statmt.org/wmt14/translation-task.html [3] Tatoeba Project. http://www.manythings.org/anki/ ----------- > 注:本節除代碼外與原書基本相同,[原書傳送門](https://zh.d2l.ai/chapter_natural-language-processing/machine-translation.html)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看