IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 人工智能 -> Pytorch实战_Seq2seq模型 -> 正文阅读

[人工智能]Pytorch实战_Seq2seq模型

1. Sequence-to-Sequence 简介

大多数常见的 sequence-to-sequence (seq2seq) model 为 encoder-decoder model,主要由两个部分组成,分别是 EncoderDecoder,而这两个部分大多数是由 recurrent neural network (RNN) 实现。

Encoder 是将一连串的输入,如文字、影片、声音讯号等,编码为单个向量,这个向量可以想像为整个输入的抽象表示,包含了整个输入的资讯。
Decoder 是將 Encoder 输出的向量进行逐步解码,一次输出一个结果,直到将最终的目标全部输出为止,每次输出会影响下一个输出,一般会在开始输入 < BOS > 来表示开始解码,会在结尾出输出 < EOS > 来表示解码结束。

在这里插入图片描述

2. 任务介绍

  • 英文翻译为中文
    • 输入: 一句英文 (e.g. tom is a student .)
    • 输出: 中文翻译 (e.g. 汤姆 是 个 学生 。)

3. 实现过程

首先要做的是下载资料,主要是用来下载本次任务需要的数据集

!gdown --id '1r4px0i-NcrnXy1-tkBsIwvYwbWnxAhcg' --output data.tar.gz
!tar -zxvf data.tar.gz
!mkdir ckpt
!ls

之后导入需要用到的包(如果nltk包没有下载的话,可使用第一段代码进行下载)

!pip3 install --user nltk
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torch.utils.data as data
import torch.utils.data.sampler as sampler
import torchvision
from torchvision import datasets, transforms

import numpy as np
import sys
import os
import random
import json

device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 判斷是用 CPU 還是 GPU 執行運算

需要注意的是,不同的句子往往有着不同的长度,这无疑给训练带来了不小的麻烦(因为 RNN 的输入维度要进行相应的改变)。为了解决这个麻烦,我们使用 <pad> 长度较短的句子进行填充。因此这里定义一个长度转换的类

import numpy as np

class LabelTransform(object):
  def __init__(self, size, pad):
    self.size = size
    self.pad = pad

  def __call__(self, label):
    label = np.pad(label, (0, (self.size - label.shape[0])), mode='constant', constant_values=self.pad)
    return label

下一步就是数据的准备了,我们定义一个Dataset。

  • Data (出自manythings 的 cmn-eng):

    • 训练资料:18000句
    • 验证资料: 500句
    • 测试资料: 2636句
  • 资料预处理:

    • 英文:
      • 用 subword-nmt 套件将word转为subword
      • 建立字典:取出标签中出现频率高于预定阈值的subword
    • 中文:
      • 用 jieba 将中文句子进行断句
      • 建立字典:取出标签中出现频率高于预定阈值的词
    • 特殊字元: < PAD >, < BOS >, < EOS >, < UNK >
      • < PAD > :无意义,将句子拓展到相同长度
      • < BOS > :Begin of sentence, 开始字元
      • < EOS > :End of sentence, 结尾字元
      • < UNK > :单字沒有出现在字典里的字
    • 将字典里出现的 subword (词) 用一个整数表示,分为英文和中文的字典,方便之后转化为 one-hot vector
import re
import json

class EN2CNDataset(data.Dataset):
  def __init__(self, root, max_output_len, set_name):
    self.root = root

    self.word2int_cn, self.int2word_cn = self.get_dictionary('cn')
    self.word2int_en, self.int2word_en = self.get_dictionary('en')

    # 载入资料
    self.data = []
    with open(os.path.join(self.root, f'{set_name}.txt'), "r") as f:
      for line in f:
        self.data.append(line)
    print (f'{set_name} dataset size: {len(self.data)}')

    self.cn_vocab_size = len(self.word2int_cn)
    self.en_vocab_size = len(self.word2int_en)
    self.transform = LabelTransform(max_output_len, self.word2int_en['<PAD>'])

  def get_dictionary(self, language):
    # 载入字典
    with open(os.path.join(self.root, f'word2int_{language}.json'), "r") as f:
      word2int = json.load(f)
    with open(os.path.join(self.root, f'int2word_{language}.json'), "r") as f:
      int2word = json.load(f)
    return word2int, int2word

  def __len__(self):
    return len(self.data)

  def __getitem__(self, Index):
    # 先将中英文词分开
    sentences = self.data[Index]
    sentences = re.split('[\t\n]', sentences)
    sentences = list(filter(None, sentences))
    #print (sentences)
    assert len(sentences) == 2

    # 特殊字元
    BOS = self.word2int_en['<BOS>']
    EOS = self.word2int_en['<EOS>']
    UNK = self.word2int_en['<UNK>']

    # 在开头添加 <BOS>,在结尾添加 <EOS> ,不在字典的 subword (词) 用 <UNK> 取代
    en, cn = [BOS], [BOS]
    # 将句子拆解为 subword 并转为整数
    sentence = re.split(' ', sentences[0])
    sentence = list(filter(None, sentence))
    #print (f'en: {sentence}')
    for word in sentence:
      en.append(self.word2int_en.get(word, UNK))
    en.append(EOS)

    # 将句子拆解为 subword 并转为整数
    # e.g. < BOS >, we, are, friends, < EOS > --> 1, 28, 29, 205, 2
    sentence = re.split(' ', sentences[1])
    sentence = list(filter(None, sentence))
    #print (f'cn: {sentence}')
    for word in sentence:
      cn.append(self.word2int_cn.get(word, UNK))
    cn.append(EOS)

    en, cn = np.asarray(en), np.asarray(cn)

    # 用 <PAD> 將将句子拓展到相同长度
    en, cn = self.transform(en), self.transform(cn)
    en, cn = torch.LongTensor(en), torch.LongTensor(cn)

    return en, cn

接下来就是构建自己的模型

Encoder

  • seq2seq模型的编码器为RNN。对于每个输入,Encoder 会输出一个向量一个隐藏层状态(hidden state),并将隐藏层状态用于下一个输入,换句话说,Encoder 会逐步读入输入序列。
  • 参数:
    • en_vocab_size 是英文字典的大小,也就是英文的 subword 的个数
    • emb_dim 是 embedding 的维度,主要将 one-hot vector 的单词向量压缩到指定的维度,可以使用预先训练好的 word embedding,如 Glove 和 word2vector
    • hid_dim 是 RNN 输出和隐藏状态的维度
    • n_layers 是 RNN 要叠多少层
    • dropout 是决定有多少的机率将某某个节点变为 0,主要是为了防止 overfitting ,一般来说是在训练集使用,测试集不使用
  • Encoder 的输入和输出:
    • 輸入:
      • 英文的整数序列 e.g. 1, 28, 29, 205, 2
    • 輸出:
      • outputs: 最上层 RNN 全部的输出,可以用 Attention 再进行处理
      • hidden: 每层最后的隐藏状态,将传输到后面的 Decoder 进行解码
class Encoder(nn.Module):
  def __init__(self, en_vocab_size, emb_dim, hid_dim, n_layers, dropout):
    super().__init__()
    self.embedding = nn.Embedding(en_vocab_size, emb_dim)
    self.hid_dim = hid_dim
    self.n_layers = n_layers
    self.rnn = nn.GRU(emb_dim, hid_dim, n_layers, dropout=dropout, batch_first=True, bidirectional=True)
    self.dropout = nn.Dropout(dropout)

  def forward(self, input):
    # input = [batch size, sequence len, vocab size]
    embedding = self.embedding(input)
    outputs, hidden = self.rnn(self.dropout(embedding))
    # outputs = [batch size, sequence len, hid dim * directions]
    # hidden =  [num_layers * directions, batch size  , hid dim]
    # outputs 是最上层RNN的输出
        
    return outputs, hidden

Decoder

  • Decoder 是另一个 RNN,在最简单的 seq2seq decoder 中,仅使用 Encoder 对每一层最后的隐藏状态来进行解码,而这最好的的隐藏状态有些被称为 “content vector”,因为可以想象它对整个前文序列进行了编码, 此 “content vector” 用作 Decoder初始隐藏状态, 而 Encoder 的输出通常用于 Attention Mechanism 产生相应的 Attention。

  • 参数

    • en_vocab_size 是英文字典的大小,也就是英文的 subword 的个数
    • emb_dim 是 embedding 的维度,主要将 one-hot vector 的单词向量压缩到指定的维度,可以使用预先训练好的 word embedding,如 Glove 和 word2vector
    • hid_dim 是 RNN 输出和隐藏状态的维度
    • output_dim 是最终输出的维度,一般来说是将 hid_dim 转到 one-hot vector 的单词向量
    • n_layers 是 RNN 要叠多少层
    • dropout 是决定有多少的机率将某某个节点变为 0,主要是为了防止 overfitting ,一般来说是在训练集使用,测试集不使用
    • isatt 是来決定是否使用 Attention Mechanism
  • Decoder 的输入和输出:

    • 输入:
      • 前一次解码出來的单词的整数表示
    • 輸出:
      • hidden: 根据输入和前一次的隐藏转态,现在的隐藏转态的更新的结果
      • output: 每个字有多少概率是这次解码的结果
class Decoder(nn.Module):
  def __init__(self, cn_vocab_size, emb_dim, hid_dim, n_layers, dropout, isatt):
    super().__init__()
    self.cn_vocab_size = cn_vocab_size
    self.hid_dim = hid_dim * 2
    self.n_layers = n_layers
    self.embedding = nn.Embedding(cn_vocab_size, config.emb_dim)
    self.isatt = isatt
    self.attention = Attention(hid_dim)
    # 如果使用 Attention Mechanism 會使得輸入維度變化,請在這裡修改
    # e.g. Attention 接在輸入後面會使得維度變化,所以輸入維度改為
    # self.input_dim = emb_dim + hid_dim * 2 if isatt else emb_dim
    self.input_dim = emb_dim
    self.rnn = nn.GRU(self.input_dim, self.hid_dim, self.n_layers, dropout = dropout, batch_first=True)
    self.embedding2vocab1 = nn.Linear(self.hid_dim, self.hid_dim * 2)
    self.embedding2vocab2 = nn.Linear(self.hid_dim * 2, self.hid_dim * 4)
    self.embedding2vocab3 = nn.Linear(self.hid_dim * 4, self.cn_vocab_size)
    self.dropout = nn.Dropout(dropout)

  def forward(self, input, hidden, encoder_outputs):
    # input = [batch size, vocab size]
    # hidden = [batch size, n layers * directions, hid dim]
    # Decoder 只會是單向,所以 directions=1
    input = input.unsqueeze(1)
    embedded = self.dropout(self.embedding(input))
    # embedded = [batch size, 1, emb dim]
    if self.isatt:
      attn = self.attention(encoder_outputs, hidden)
      # TODO: 在這裡決定如何使用 Attention,e.g. 相加 或是 接在後面, 請注意維度變化
    output, hidden = self.rnn(embedded, hidden)
    # output = [batch size, 1, hid dim]
    # hidden = [num_layers, batch size, hid dim]

    # 將 RNN 的輸出轉為每個詞出現的機率
    output = self.embedding2vocab1(output.squeeze(1))
    output = self.embedding2vocab2(output)
    prediction = self.embedding2vocab3(output)
    # prediction = [batch size, vocab size]
    return prediction, hidden

Attention

  • 当输入过长时,或是单独靠 “content vector” 无法获取整个输入的意思时,用 Attention Mechanism 来提供 Decoder 更多的资讯

  • 主要是根据现在 Decoder hidden state ,去计算在 Encoder outputs 中,那些与其有较高的关系,根据关系的数值来决定传给 Decoder 哪些额外的资讯

  • 常见 Attention 的操作是用 Neural Network / Dot Product 来计算 Decoder hidden stateEncoder outputs 之间的关系,再对所有算出來的数值做 softmax ,最后根据过完 softmax 的值对 Encoder outputsweight sum

  • 李宏毅老师的课程在此处并没有给出具体的代码,需要大家自己补充。大家可以参考这篇文章 Seq2Seq (Attention) 的 PyTorch 实现 或者B站的视频 PyTorch35——基于注意力机制的Seq2Seq的PyTorch实现示例

class Attention(nn.Module):
  def __init__(self, hid_dim):
    super(Attention, self).__init__()
    self.hid_dim = hid_dim
  
  def forward(self, encoder_outputs, decoder_hidden):
    # encoder_outputs = [batch size, sequence len, hid dim * directions]
    # decoder_hidden = [num_layers, batch size, hid dim]
    # 一般來說是取最後一層的 hidden state 來做 attention
    ########
    # TODO #
    ########
    attention=None
    
    return attention

Seq2seq模型

  • EncoderDecoder 组成
  • 接收输入并传给 Encoder
  • Encoder 的输出传给 Decoder
  • 不断地将 Decoder 的输出传回 Decoder ,进行解码
  • 当解码完成,将 Decoder 的输出传回
class Seq2Seq(nn.Module):
  def __init__(self, encoder, decoder, device):
    super().__init__()
    self.encoder = encoder
    self.decoder = decoder
    self.device = device
    assert encoder.n_layers == decoder.n_layers, \
            "Encoder and decoder must have equal number of layers!"
            
  def forward(self, input, target, teacher_forcing_ratio):
    # input  = [batch size, input len, vocab size]
    # target = [batch size, target len, vocab size]
    # teacher_forcing_ratio 是有多少機率使用正確答案來訓練
    batch_size = target.shape[0]
    target_len = target.shape[1]
    vocab_size = self.decoder.cn_vocab_size

    # 準備一個儲存空間來儲存輸出
    outputs = torch.zeros(batch_size, target_len, vocab_size).to(self.device)
    # 將輸入放入 Encoder
    encoder_outputs, hidden = self.encoder(input)
    # Encoder 最後的隱藏層(hidden state) 用來初始化 Decoder
    # encoder_outputs 主要是使用在 Attention
    # 因為 Encoder 是雙向的RNN,所以需要將同一層兩個方向的 hidden state 接在一起
    # hidden =  [num_layers * directions, batch size  , hid dim]  --> [num_layers, directions, batch size  , hid dim]
    hidden = hidden.view(self.encoder.n_layers, 2, batch_size, -1)
    hidden = torch.cat((hidden[:, -2, :, :], hidden[:, -1, :, :]), dim=2)
    # 取的 <BOS> token
    input = target[:, 0]
    preds = []
    for t in range(1, target_len):
      output, hidden = self.decoder(input, hidden, encoder_outputs)
      outputs[:, t] = output
      # 決定是否用正確答案來做訓練
      teacher_force = random.random() <= teacher_forcing_ratio
      # 取出機率最大的單詞
      top1 = output.argmax(1)
      # 如果是 teacher force 則用正解訓練,反之用自己預測的單詞做預測
      input = target[:, t] if teacher_force and t < target_len else top1
      preds.append(top1.unsqueeze(1))
    preds = torch.cat(preds, 1)
    return outputs, preds

  def inference(self, input, target):
    ########
    # TODO #
    ########
    # 在這裡實施 Beam Search
    # 此函式的 batch size = 1  
    # input  = [batch size, input len, vocab size]
    # target = [batch size, target len, vocab size]
    batch_size = input.shape[0]
    input_len = input.shape[1]        # 取得最大字數
    vocab_size = self.decoder.cn_vocab_size

    # 準備一個儲存空間來儲存輸出
    outputs = torch.zeros(batch_size, input_len, vocab_size).to(self.device)
    # 將輸入放入 Encoder
    encoder_outputs, hidden = self.encoder(input)
    # Encoder 最後的隱藏層(hidden state) 用來初始化 Decoder
    # encoder_outputs 主要是使用在 Attention
    # 因為 Encoder 是雙向的RNN,所以需要將同一層兩個方向的 hidden state 接在一起
    # hidden =  [num_layers * directions, batch size  , hid dim]  --> [num_layers, directions, batch size  , hid dim]
    hidden = hidden.view(self.encoder.n_layers, 2, batch_size, -1)
    hidden = torch.cat((hidden[:, -2, :, :], hidden[:, -1, :, :]), dim=2)
    # 取的 <BOS> token
    input = target[:, 0]
    preds = []
    for t in range(1, input_len):
      output, hidden = self.decoder(input, hidden, encoder_outputs)
      # 將預測結果存起來
      outputs[:, t] = output
      # 取出機率最大的單詞
      top1 = output.argmax(1)
      input = top1
      preds.append(top1.unsqueeze(1))
    preds = torch.cat(preds, 1)
    return outputs, preds

  人工智能 最新文章
2022吴恩达机器学习课程——第二课(神经网
第十五章 规则学习
FixMatch: Simplifying Semi-Supervised Le
数据挖掘Java——Kmeans算法的实现
大脑皮层的分割方法
【翻译】GPT-3是如何工作的
论文笔记:TEACHTEXT: CrossModal Generaliz
python从零学(六)
详解Python 3.x 导入(import)
【答读者问27】backtrader不支持最新版本的
上一篇文章      下一篇文章      查看所有文章
加:2022-01-28 11:54:55  更:2022-01-28 11:56:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 10:56:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码