<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      大模型基礎補全計劃(五)---seq2seq實例與測試(編碼器、解碼器架構)

      PS:要轉載請注明出處,本人版權所有。

      PS: 這個只是基于《我自己》的理解,

      如果和你的原則及想法相沖突,請諒解,勿噴。

      環境說明

      ??無

      前言


      ??????本文是這個系列第五篇,它們是:

      ??本文,我們先簡單介紹一下編碼器-解碼器架構,然后介紹一個基于這種架構的機翻模型seq2seq的簡單實例。





      編碼器-解碼器(encoder-decoder)架構


      ???前面的文章中我們的模型示例都是根據已有的文字序列,續寫N個字。在自然語言處理中,還有有一類需求也是比較經典,那就是機器翻譯。

      ???對于機器翻譯來說,其核心就是將一種語言翻譯為另外一種語言,換句話說就是一種序列數據到另外一種序列數據。從這里來看,出現了兩種序列數據,那么必然的很容易想到類似兩個RNN的獨立網絡來處理這種任務,基于這種情況,有人提出了編碼器-解碼器架構,下圖是這種架構的示意圖。

      rep_img

      ??從示意圖可知,這種架構的核心就是處理輸入序列,得到中間狀態,將中間狀態傳給解碼器,解碼器負責生成輸出序列。對于翻譯任務來說,輸入序列就是原文,輸出序列就是譯文。

      ??這里說起來還是概念性的,我們下面從一個經典的編碼器、解碼器結構的模型來實際 演示一下翻譯需求的模型是什么樣子的。





      基于 seq2seq 的 英文翻譯中文 的實例


      ??



      英文中文翻譯數據集

      ?? 首先數據集下載地址是http://www.manythings.org/anki/ 中的cmn-eng.zip 文件,其內部的數據集格式大概如下:

          I try.	我試試。	CC-BY 2.0 (France) Attribution: tatoeba.org #20776 (CK) & #8870261 (will66)
          I won!	我贏了。	CC-BY 2.0 (France) Attribution: tatoeba.org #2005192 (CK) & #5102367 (mirrorvan)
          Oh no!	不會吧。	CC-BY 2.0 (France) Attribution: tatoeba.org #1299275 (CK) & #5092475 (mirrorvan)
          Cheers!	乾杯!	CC-BY 2.0 (France) Attribution: tatoeba.org #487006 (human600) & #765577 (Martha)
          Got it?	知道了沒有?	CC-BY 2.0 (France) Attribution: tatoeba.org #455353 (CM) & #455357 (GlossaMatik)
          Got it?	懂了嗎?	CC-BY 2.0 (France) Attribution: tatoeba.org #455353 (CM) & #2032276 (ydcok)
          Got it?	你懂了嗎?	CC-BY 2.0 (France) Attribution: tatoeba.org #455353 (CM) & #7768205 (jiangche)
          He ran.	他跑了。	CC-BY 2.0 (France) Attribution: tatoeba.org #672229 (CK) & #5092389 (mirrorvan)
      

      ?? 由于我的卡(3060 12G)有點拉庫,為了效率,因此整個數據集只用前面2千條即可。





      文本預處理
      # dataset.py
      import collections
      import torch
      from torch.utils import data
      
      
      # 下面返回的數據是:
      # [['Hi.', '嗨。'], ['Hi.', '你好。'], ['Run.', '你用跑的。'], ['Stop!', '住手!'], ['Wait!', '等等!'], ... ...]
      def read_data():
          with open('cmn-eng/cmn.txt', 'r',
                   encoding='utf-8') as f:
              lines = f.readlines()
          
          return [line.split("	")[:2] for line in lines]
          
      
      # 輸出是:
      # [['Hi.'], ['Hi.'], ['Run.'], ['Stop!'], ['Wait!']]
      # [['嗨', '。'], ['你', '好', '。'], ['你', '用', '跑', '的', '。'], ['住', '手', '!'], ['等', '等', '!']]
      # ['Hi.', 'Hi.', 'Run.', 'Stop!', 'Wait!']
      # ['嗨。', '你好。', '你用跑的。', '住手!', '等等!']
      def tokenize(lines, token='char'):  #@save
          """將文本行拆分為單詞或字符詞元"""
          source_tokenize, target_tokenize = [], []
          source_line, target_line = [], []
          print(f'dataset len = {len(lines)}')
          for line in lines:
      
              s = line[0]
              t = line[1]
              source_line.append(s)
              target_line.append(t)
              source_tokenize.append(s.split(' '))
              target_tokenize.append([word for word in t])
      
          return source_tokenize, target_tokenize, source_line, target_line
      
      # 詞元的類型是字符串,而模型需要的輸入是數字,因此這種類型不方便模型使用。 現在,讓我們構建一個字典,
      # 通常也叫做詞表(vocabulary), 用來將字符串類型的詞元映射到從開始的數字索引中。
      def count_corpus(tokens):  #@save
          """統計詞元的頻率"""
          # 這里的tokens是1D列表或2D列表
          if len(tokens) == 0 or isinstance(tokens[0], list):
              # 將詞元列表展平成一個列表
              tokens = [token for line in tokens for token in line]
          return collections.Counter(tokens)
      
      # 返回類似{'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1}的一個字典
      class Vocab:
          """文本詞表"""
          def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):
              if tokens is None:
                  tokens = []
              if reserved_tokens is None:
                  reserved_tokens = []
              # 按出現頻率排序
              # 對于Counter("hello world"),結果如下
              # Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
              counter = count_corpus(tokens)
              self._token_freqs = sorted(counter.items(), key=lambda x: x[1],
                                         reverse=True)
              # 未知詞元的索引為0
              self.idx_to_token = ['<unk>'] + reserved_tokens
              self.token_to_idx = {token: idx
                                   for idx, token in enumerate(self.idx_to_token)}
              for token, freq in self._token_freqs:
                  if freq < min_freq:
                      break
                  if token not in self.token_to_idx:
                      self.idx_to_token.append(token)
                      self.token_to_idx[token] = len(self.idx_to_token) - 1
      
          def __len__(self):
              return len(self.idx_to_token)
      
          def __getitem__(self, tokens):
              if not isinstance(tokens, (list, tuple)):
                  return self.token_to_idx.get(tokens, self.unk)
              return [self.__getitem__(token) for token in tokens]
      
          def to_tokens(self, indices):
              if not isinstance(indices, (list, tuple)):
                  return self.idx_to_token[indices]
              return [self.idx_to_token[index] for index in indices]
      
          @property
          def unk(self):  # 未知詞元的索引為0
              return 0
      
          @property
          def token_freqs(self):
              return self._token_freqs    
          
      
      
      
      def truncate_pad(line, num_steps, padding_token):
          """截斷或填充文本序列"""
          if len(line) > num_steps:
              return line[:num_steps]  # 截斷
          return line + [padding_token] * (num_steps - len(line))  # 填充
      
      
      def build_array(lines, vocab, num_steps):
          """將機器翻譯的文本序列轉換成小批量"""
          lines = [vocab[l] for l in lines] # 每行的token轉換為其id
          lines = [l + [vocab['<eos>']] for l in lines] # 每行的token后加上eos的id
          array = torch.tensor([truncate_pad(
              l, num_steps, vocab['<pad>']) for l in lines])
          valid_len = (array != vocab['<pad>']).type(torch.int32).sum(1)
          return array, valid_len
      
      def load_array(data_arrays, batch_size, is_train=True):
          """構造一個PyTorch數據迭代器
      
          Defined in :numref:`sec_linear_concise`"""
          dataset = data.TensorDataset(*data_arrays)
          return data.DataLoader(dataset, batch_size, shuffle=is_train)
      
      def load_data(batch_size, num_steps, num_examples=600):
          """返回翻譯數據集的迭代器和詞表"""
      
          text = read_data()
      
          source, target, src_line, tgt_line = tokenize(text)
          # 返回類似{'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1}的一個字典
          src_vocab = Vocab(source, min_freq=0,
                                reserved_tokens=['<pad>', '<bos>', '<eos>'])
          tgt_vocab = Vocab(target, min_freq=0,
                                reserved_tokens=['<pad>', '<bos>', '<eos>'])
          # 首先把每行的詞轉換為了其對應的id,然后給每一行的末尾添加token <eos>, 然后根據num_steps,如果line長度不足,補<pad>,如果長度超出,截斷
          # 一種類型的輸出是:
          # [
          #     [line0-char0-id, line0-char1-id, line0-char2-id, ...., eos-id],
          #     [line1-char0-id, line1-char1-id, line1-char2-id, ...., eos-id], 注意,最后的末尾可能沒有eos
          #     .....
          # ]
          src_array, src_valid_len = build_array(source, src_vocab, num_steps)
          tgt_array, tgt_valid_len = build_array(target, tgt_vocab, num_steps)
      
          data_arrays = (src_array, src_valid_len, tgt_array, tgt_valid_len)
          data_iter = load_array(data_arrays, batch_size)
          return data_iter, src_vocab, tgt_vocab, src_line, tgt_line
      

      ??上面代碼做了如下事情:

      • 根據數據集的格式,讀取每一行,只提前每行前面2個字符串。
      • 然后我們對每一行進行文字切割,得到了一個二維列表,列表中的每一行又被分割為一個個中文文字和一個個英文的詞,也就得到了一個個token。(特別注意,站在當前的時刻,這里的token和現在主流的大語言模型的token概念是一樣的,但是不是一樣的實現。)
      • 由于模型不能直接處理文字,我們需要將文字轉換為數字,那么直接的做法就是將一個個token編號即可,這個時候我們得到了詞表(vocabulary)。
      • 然后我們根據我們得到的詞表,對原始數據集進行數字化,得到一個列表,列表中每個元素就是一個個token對應的索引。
      • 最后得到:基于pytorch的DataLoader、原文詞表、譯文詞表、原文文字列表、譯文文字列表

      ??此外,在這里出現了幾個在后面的大語言模型中也會出現的詞:BOS/EOS。這兩個分別代表一次對話的起始、結尾,這里直接記住就行。



      搭建seq2seq訓練框架

      ??首先引用一些包和一些輔助class

      import os
      import random
      import torch
      import math
      from torch import nn
      from torch.nn import functional as F
      import numpy as np
      import time
      import visdom
      import collections
      
      import dataset
      class Accumulator:
          """在n個變量上累加"""
          def __init__(self, n):
              """Defined in :numref:`sec_softmax_scratch`"""
              self.data = [0.0] * n
      
          def add(self, *args):
              self.data = [a + float(b) for a, b in zip(self.data, args)]
      
          def reset(self):
              self.data = [0.0] * len(self.data)
      
          def __getitem__(self, idx):
              return self.data[idx]
          
      class Timer:
          """記錄多次運行時間"""
          def __init__(self):
              """Defined in :numref:`subsec_linear_model`"""
              self.times = []
              self.start()
      
          def start(self):
              """啟動計時器"""
              self.tik = time.time()
      
          def stop(self):
              """停止計時器并將時間記錄在列表中"""
              self.times.append(time.time() - self.tik)
              return self.times[-1]
      
          def avg(self):
              """返回平均時間"""
              return sum(self.times) / len(self.times)
      
          def sum(self):
              """返回時間總和"""
              return sum(self.times)
      
          def cumsum(self):
              """返回累計時間"""
              return np.array(self.times).cumsum().tolist()
          
      

      ??然后我們根據編碼器、解碼器架構,設計seq2seq的網絡主干

      class Encoder(nn.Module):
          """編碼器-解碼器架構的基本編碼器接口"""
          def __init__(self, **kwargs):
              # 調用父類nn.Module的構造函數,確保正確初始化
              super(Encoder, self).__init__(**kwargs)
      
          def forward(self, X, *args):
              # 拋出未實現錯誤,意味著該方法需要在子類中具體實現
              raise NotImplementedError
      
      class Decoder(nn.Module):
          """編碼器-解碼器架構的基本解碼器接口
      
          Defined in :numref:`sec_encoder-decoder`"""
          def __init__(self, **kwargs):
              # 調用父類nn.Module的構造函數,確保正確初始化
              super(Decoder, self).__init__(**kwargs)
      
          def init_state(self, enc_outputs, *args):
              # 拋出未實現錯誤,意味著該方法需要在子類中具體實現
              raise NotImplementedError
      
          def forward(self, X, state):
              # 拋出未實現錯誤,意味著該方法需要在子類中具體實現
              raise NotImplementedError
      
      class EncoderDecoder(nn.Module):
          """編碼器-解碼器架構的基類
      
          Defined in :numref:`sec_encoder-decoder`"""
          def __init__(self, encoder, decoder, **kwargs):
              # 調用父類nn.Module的構造函數,確保正確初始化
              super(EncoderDecoder, self).__init__(**kwargs)
              # 將傳入的編碼器實例賦值給類的屬性
              self.encoder = encoder
              # 將傳入的解碼器實例賦值給類的屬性
              self.decoder = decoder
      
          def forward(self, enc_X, dec_X, *args):
              # 調用編碼器的前向傳播方法,處理輸入的編碼器輸入數據enc_X
              enc_outputs = self.encoder(enc_X, *args)
              # 調用解碼器的init_state方法,根據編碼器的輸出初始化解碼器的狀態
              dec_state = self.decoder.init_state(enc_outputs, *args)
              # 調用解碼器的前向傳播方法,處理輸入的解碼器輸入數據dec_X和初始化后的狀態
              return self.decoder(dec_X, dec_state)
          
      #@save
      class Seq2SeqEncoder(Encoder):
          """用于序列到序列學習的循環神經網絡編碼器"""
          def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                       dropout=0, **kwargs):
              super(Seq2SeqEncoder, self).__init__(**kwargs)
              # 嵌入層
              self.embedding = nn.Embedding(vocab_size, embed_size)
              self.rnn = nn.GRU(embed_size, num_hiddens, num_layers,
                                dropout=dropout)
              # self.lstm = nn.LSTM(embed_size, num_hiddens, num_layers)
      
          def forward(self, X, *args):
              # 輸入X.shape = (batch_size,num_steps)
              # 輸出'X'的形狀:(batch_size,num_steps,embed_size)
              X = self.embedding(X)
              # 在循環神經網絡模型中,第一個軸對應于時間步
              X = X.permute(1, 0, 2)
              # 如果未提及狀態,則默認為0
              output, state = self.rnn(X)
              # output : 這個返回值是所有時間步的隱藏狀態序列
              # output的形狀:(num_steps,batch_size,num_hiddens)
              # hn (hidden) : 這是每一層rnn的最后一個時間步的隱藏狀態
              # state的形狀:(num_layers,batch_size,num_hiddens)
              return output, state
      
      class Seq2SeqDecoder(Decoder):
          """用于序列到序列學習的循環神經網絡解碼器"""
          def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                       dropout=0, **kwargs):
              super(Seq2SeqDecoder, self).__init__(**kwargs)
              self.embedding = nn.Embedding(vocab_size, embed_size)
              self.rnn = nn.GRU(embed_size + num_hiddens, num_hiddens, num_layers,
                                dropout=dropout)
              self.dense = nn.Linear(num_hiddens, vocab_size)
      
          def init_state(self, enc_outputs, *args):
              return enc_outputs[1]
      
          def forward(self, X, state):
              # 輸出'X'的形狀:(batch_size,num_steps,embed_size)
              X = self.embedding(X).permute(1, 0, 2)
              # 廣播context,使其具有與X相同的num_steps
              context = state[-1].repeat(X.shape[0], 1, 1)
              X_and_context = torch.cat((X, context), 2)
              output, state = self.rnn(X_and_context, state)
              output = self.dense(output).permute(1, 0, 2)
              # output的形狀:(batch_size,num_steps,vocab_size)
              # state的形狀:(num_layers,batch_size,num_hiddens)
              return output, state
      

      ??我們結合上面的架構圖對比著看,首先聲明一下decoder/encoder的接口類:

      • 聲明了Encoder(nn.Module),Encoder(nn.Module)其輸入是原文,輸出是中間狀態。
      • 聲明了Decoder(nn.Module),Decoder(nn.Module)的輸入是BOS和中間狀態,輸出是譯文。
      • 聲明了EncoderDecoder(nn.Module)類,串聯Encoder(nn.Module)/Decoder(nn.Module)進行運行。

      ??然后聲明實際的Seq2SeqEncoder部分:

      • 聲明了Seq2SeqEncoder(Encoder),其是seq2seq編碼器部分的實際定義,其輸入是一串原文,然后經過了nn.Embedding,將輸入的token序列轉換為token-embedding,然后送入nn.GRU,得到了兩個值:最后一層rnn的所有時間步的隱藏狀態output(shape=num_steps,batch_size,num_hiddens),所有層rnn的最后一個時間步的隱藏狀態h_n(shape=num_layers,batch_size,num_hiddens)
      • 從Seq2SeqEncoder(Encoder)上面的分析可知:rnn的輸出output代表的是每一個時間步,當前序列的總結信息,h_n encoder的隱藏態參數。

      ??最后聲明實際的Seq2SeqDecoder部分:

      • 聲明了Seq2SeqDecoder(Decoder),輸入是:一個是bos,一個是Seq2SeqEncoder(Encoder)輸出的隱藏態state(output,h_n)。首先將bos轉換為embedding向量,然后將h_n的最后一個數據(也就是原文的總結,rnn最后一層最后一個時間步的隱藏態)和embedding組合在一起(注意:這里已經將原文的語義已經和bos輸入混合在一起了),和Seq2SeqEncoder(Encoder) state作為的隱藏狀態初始值,一起傳入rnn,然后經過nn.Linear的映射,得到了decoder的輸出。
      • 從Seq2SeqDecoder(Decoder)的分析可知,經過了nn.Linear映射之后,我們將decoder層的rnn的output轉換為詞表大小的一個向量,這個向量我們可以看做下一個字的分數Logits(注意:這個概念在后續大語言模型中,有比較大的作用)。

      ??這里,nn.RNN等pytorch層的輸出,可以結合下面這個圖來理解(圖來自于參考文獻相關鏈接):

      rep_img

      ?? 下面給出的就是訓練、預測部分的代碼:

      def try_gpu(i=0):
          """如果存在,則返回gpu(i),否則返回cpu()
      
          Defined in :numref:`sec_use_gpu`"""
          if torch.cuda.device_count() >= i + 1:
              return torch.device(f'cuda:{i}')
          return torch.device('cpu')
      
      def sequence_mask(X, valid_len, value=0):
          """在序列中屏蔽不相關的項"""
          maxlen = X.size(1)
          mask = torch.arange((maxlen), dtype=torch.float32,
                              device=X.device)[None, :] < valid_len[:, None]
          X[~mask] = value
          return X
      
      class MaskedSoftmaxCELoss(nn.CrossEntropyLoss):
          """帶遮蔽的softmax交叉熵損失函數"""
          # pred的形狀:(batch_size,num_steps,vocab_size)
          # label的形狀:(batch_size,num_steps)
          # valid_len的形狀:(batch_size,)
          def forward(self, pred, label, valid_len):
              weights = torch.ones_like(label)
              weights = sequence_mask(weights, valid_len)
              self.reduction='none'
              unweighted_loss = super(MaskedSoftmaxCELoss, self).forward(
                  pred.permute(0, 2, 1), label)
              weighted_loss = (unweighted_loss * weights).mean(dim=1)
              return weighted_loss
          
      def grad_clipping(net, theta):  #@save
          """裁剪梯度"""
          if isinstance(net, nn.Module):
              params = [p for p in net.parameters() if p.requires_grad]
          else:
              params = net.params
          norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))
          if norm > theta:
              for param in params:
                  param.grad[:] *= theta / norm
      
      def train_seq2seq(net, data_iter, lr, num_epochs, tgt_vocab, device):
          """訓練序列到序列模型"""
          def xavier_init_weights(m):
              if type(m) == nn.Linear:
                  nn.init.xavier_uniform_(m.weight)
              if type(m) == nn.GRU:
                  for param in m._flat_weights_names:
                      if "weight" in param:
                          nn.init.xavier_uniform_(m._parameters[param])
      
          net.apply(xavier_init_weights)
          net.to(device)
          optimizer = torch.optim.Adam(net.parameters(), lr=lr)
          loss = MaskedSoftmaxCELoss()
          net.train()
          vis = visdom.Visdom(env=u'test1', server="http://127.0.0.1", port=8097)
          animator = vis
          for epoch in range(num_epochs):
              timer = Timer()
              metric = Accumulator(2)  # 訓練損失總和,詞元數量
              for batch in data_iter:
                  #清零(reset)優化器中的梯度緩存
                  optimizer.zero_grad()
                  # x.shape = [batch_size, num_steps]
                  X, X_valid_len, Y, Y_valid_len = [x.to(device) for x in batch]
                  # bos.shape = batch_size 個 bos-id
                  bos = torch.tensor([tgt_vocab['<bos>']] * Y.shape[0],
                                device=device).reshape(-1, 1)
                  # dec_input.shape = (batch_size, num_steps)
                  # 解碼器的輸入通常由序列的起始標志 bos 和目標序列(去掉末尾的部分 Y[:, :-1])組成。
                  dec_input = torch.cat([bos, Y[:, :-1]], 1)  # 強制教學
                  # Y_hat的形狀:(batch_size,num_steps,vocab_size)
                  Y_hat, _ = net(X, dec_input, X_valid_len)
                  l = loss(Y_hat, Y, Y_valid_len)
                  l.sum().backward()      # 損失函數的標量進行“反向傳播”
                  grad_clipping(net, 1)
                  num_tokens = Y_valid_len.sum()
                  optimizer.step()
                  with torch.no_grad():
                      metric.add(l.sum(), num_tokens)
      
              if (epoch + 1) % 10 == 0:
                  # print(predict('你是?'))
                  # print(epoch)
                  # animator.add(epoch + 1, )
      
                  if epoch == 9:
                      # 清空圖表:使用空數組來替換現有內容
                      vis.line(X=np.array([0]), Y=np.array([0]), win='train_ch8', update='replace')
                  # _loss_val = l
                  # _loss_val = _loss_val.cpu().sum().detach().numpy()
                  vis.line(
                      X=np.array([epoch + 1]),
                      Y=[ metric[0] / metric[1]],
                      win='train_ch8',
                      update='append',
                      opts={
                          'title': 'train_ch8',
                          'xlabel': 'epoch',
                          'ylabel': 'loss',
                          'linecolor': np.array([[0, 0, 255]]),  # 藍色線條
                      }
                  )
          print(f'loss {metric[0] / metric[1]:.3f}, {metric[1] / timer.stop():.1f} '
              f'tokens/sec on {str(device)}')
          torch.save(net.cpu().state_dict(), 'model_h.pt')  # [[6]]
          torch.save(net.cpu(), 'model.pt')  # [[6]]
      
      def predict_seq2seq(net, src_sentence, src_vocab, tgt_vocab, num_steps,
                          device, save_attention_weights=False):
          """序列到序列模型的預測"""
          # 在預測時將net設置為評估模式
          net.eval()
          src_tokens = src_vocab[src_sentence.lower().split(' ')] + [
              src_vocab['<eos>']]
          enc_valid_len = torch.tensor([len(src_tokens)], device=device)
          src_tokens = dataset.truncate_pad(src_tokens, num_steps, src_vocab['<pad>'])
          # 添加批量軸
          enc_X = torch.unsqueeze(
              torch.tensor(src_tokens, dtype=torch.long, device=device), dim=0)
          enc_outputs = net.encoder(enc_X, enc_valid_len)
          dec_state = net.decoder.init_state(enc_outputs, enc_valid_len)
          # 添加批量軸
          dec_X = torch.unsqueeze(torch.tensor(
              [tgt_vocab['<bos>']], dtype=torch.long, device=device), dim=0)
          output_seq, attention_weight_seq = [], []
          for _ in range(num_steps):
              Y, dec_state = net.decoder(dec_X, dec_state)
              # 我們使用具有預測最高可能性的詞元,作為解碼器在下一時間步的輸入
              dec_X = Y.argmax(dim=2)
              pred = dec_X.squeeze(dim=0).type(torch.int32).item()
              # 保存注意力權重(稍后討論)
              if save_attention_weights:
                  attention_weight_seq.append(net.decoder.attention_weights)
              # 一旦序列結束詞元被預測,輸出序列的生成就完成了
              if pred == tgt_vocab['<eos>']:
                  break
              output_seq.append(pred)
          return ' '.join(tgt_vocab.to_tokens(output_seq)), attention_weight_seq
      
      
      def bleu(pred_seq, label_seq, k):  #@save
          """計算BLEU"""
          pred_tokens, label_tokens = pred_seq.split(' '), [i for i in label_seq]
          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
      

      ??這里首先要介紹一下其損失函數,核心兩個:

      • 通過交叉熵計算真實分布、預測分布的差異性,差異性越小,意味著我們的模型越好
      • 由于我們是序列模型,可能涉及pad項,這些pad項的位置是無意義的,但是對模型有影響,我們需要再loss中剔除掉這種無意義的位置,我們用mask來屏蔽。

      ??然后訓練過程的核心就是:從數據集中獲取 訓練數據、驗證數據,通過訓練數據得到預測數據,預測數據和驗證數據進行loss計算,然后進行反向傳播,找到loss最小化的方向,然后最小化loss,模型就會越來越好。

      ??然后就是介紹預測部分的內容:先將原文輸入到seq的encoder,然后將bos序列 + seq的encoder的隱藏態傳給seq的decoder,就可以得到下一個字的輸出,直到我們遇到eos,預測結束。

      ??我們雖然預測完畢了,得到了原文對應的譯文,但是我們需要一種方法來評估我們翻譯的是不是正確,這里用的方法是bleu,它的作用就是評估輸出序列與目標序列的精確度。

      ??最后,我們開始訓練過程,注意,下面的例子是先進行訓練,然后保存pt模型,然后加載模型進行預測推理。

      
      if __name__ == '__main__':
          embed_size, num_hiddens, num_layers, dropout = 32, 32, 2, 0.1
          batch_size, num_steps = 64, 10
          lr, num_epochs, device = 0.005, 2000, try_gpu()
          # train_iter 每個迭代輸出:(batch_size, num_steps)
          train_iter, src_vocab, tgt_vocab, source, target = dataset.load_data(batch_size, num_steps)
          encoder = Seq2SeqEncoder(len(src_vocab), embed_size, num_hiddens, num_layers,
                              dropout)
          decoder = Seq2SeqDecoder(len(tgt_vocab), embed_size, num_hiddens, num_layers,
                                  dropout)
          net = EncoderDecoder(encoder, decoder)
      
          is_train = False
          if is_train:
              train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)
      
          else:
              state_dict = torch.load('model_h.pt')
              net.load_state_dict(state_dict)
              net.to(device)
              C = 0
              C1 = 0
              for i in range(2000):
                  # print(source[i])
                  # print(target[i])
                  translation, attention_weight_seq = predict_seq2seq(
                      net, source[i], src_vocab, tgt_vocab, num_steps, device)
                  
                  score = bleu(translation, target[i], k=2)
                  if score > 0.0:
                      C = C + 1
                      if score > 0.8:
                          C1 = C1 + 1
                      print(f'{source[i]} => {translation}, bleu {score:.3f}')
      
              print(f'Counter(bleu > 0) = {C}')
              print(f'Valid-Counter(bleu > 0.8) = {C1}')
      
      rep_img
      rep_img

      ??從上面的圖可以看到,這個模型有一定的翻譯效果。

      ??此外,我這里計算了非零的bleu以及大于0.8的bleu的個數,這個個數勉強可以評估,我們對現在這個seq2seq模型優化的效果,為后面的文章提前做一些準備工作。





      后記


      ??本文出現了bos/eos/logits等一些概念的應用,這些應用在大語言模型中也有體現。

      ??此外,我們從當前的模型結構也可以知道,當前并沒有解決輸入序列過長時,序列前面部分信息可能丟失,序列中的重點信息沒有動態突出的問題。

      參考文獻




      打賞、訂閱、收藏、丟香蕉、硬幣,請關注公眾號(攻城獅的搬磚之路)
      qrc_img

      PS: 請尊重原創,不喜勿噴。

      PS: 要轉載請注明出處,本人版權所有。

      PS: 有問題請留言,看到后我會第一時間回復。

      posted on 2025-10-19 09:57  SkyOnSky  閱讀(49)  評論(0)    收藏  舉報

      導航

      主站蜘蛛池模板: 污网站在线观看视频| 亚洲国产美女精品久久久| 株洲市| 国产精品人妻久久ai换脸| 国产精品国产三级国快看| 韩国午夜理伦三级| 狠狠色综合久久狠狠色综合| 丝袜美腿亚洲综合第一页| 亚洲AV成人无码久久精品| 中文字幕无线码免费人妻| 国产成人免费午夜在线观看| 亚洲熟妇自偷自拍另类| 日本黄漫动漫在线观看视频| 精品亚洲国产成人av在线| 偷拍精品一区二区三区| 欧美一本大道香蕉综合视频| av天堂久久天堂av| 中文日韩在线一区二区| 精品国产迷系列在线观看| 伊人久久精品久久亚洲一区| 国产精品福利自产拍久久| 国产一区二区一卡二卡| 国产毛片子一区二区三区| 午夜精品视频在线看| 国产成人a在线观看视频| 九九热视频在线免费观看| a片在线免费观看| 国产成人精品无码播放| 无码伊人66久久大杳蕉网站谷歌| 91精品一区二区蜜桃| 大地资源免费视频观看| 亚洲一区二区三区小蜜桃| 国产成人一区二区视频免费| 人与禽交av在线播放| 中文文精品字幕一区二区| 国产亚洲精品久久久久婷婷瑜伽| 人人狠狠综合久久亚洲爱咲| gogogo高清在线观看视频中文| 国产精品成人一区二区不卡| 国产精品成人午夜福利| 亚洲av色香蕉一二三区|