zoukankan      html  css  js  c++  java
  • [实现] 利用 Seq2Seq 预测句子后续字词 (Pytorch)2

    最近有个任务:利用 RNN 进行句子补全,即给定一个不完整的句子,预测其后续的字词。
    本文使用了 Seq2Seq 模型,输入为 5 个中文字词,输出为 1 个中文字词。
    目录

    关于RNN

    语料预处理

    搭建数据集

    搭建模型

    训练模型

    测试模型

    保存/加载模型

    1.关于RNN

    自被提出以来,循环神经网络(Recurrent Neural Networks,RNN) 在 NLP 领域取得了巨大的成功与广泛的应用,也由此催生出了许多新的变体与网络结构。由于网上有众多资料,在此我也只做简单的讲解了。
    首先,讲讲 RNN cell 及其变体:
    (1) vallina RNN cell

    不同于常见的神经网络结构,RNN 的输入为时序输入,每一时刻的输入对神经元的隐状态产生影响,从而影响后续所有时刻的输出。
    其中,隐藏层的公式如下所示:
    Ot=g(V∗St),St=f(U∗Xt+W∗St−1) O_{t} = g(V* S_t), S_t = f(U*X_t+W*S_{t-1})
    O
    t

    =g(V∗S
    t

    ),S
    t

    =f(U∗X
    t

    +W∗S
    t−1

    )


    (2) LSTM cell

    LSTM(Long short-term memory,长短期记忆)极大程度的解决了长序列训练过程中的梯度消失和梯度爆炸问题。


    (3) GRU cell
    GRU(Gate Recurrent Unit)与 LSTM 一样,也极大程度的解决了长序列训练过程中的梯度消失和梯度爆炸问题。但是,与 LSTM 相比,GRU 所需要的计算资源更小,往往工程实现时更倾向于使用 GRU。


    接着,讲讲网络结构:

    (1) 常见结构:


    (2) Bi-directional RNN


    (3) Deep Bi-directional RNN


    (4) Seq2Seq


    (5) Attention


    参考资料:

    循环神经网络(RNN, Recurrent Neural Networks)介绍
    人人都能看懂的LSTM
    人人都能看懂的GRU
    从Encoder到Decoder实现Seq2Seq模型
    Attention学习笔记
    2.语料预处理

    由于这次使用的语料为中文语料,自然需要对其进行分词,并构造词典。
    首先,收集所用的句子,利用 jieba 库,对每个句子进行分词,并将所得结果加入 word_set 中。
    接着,对 word_set 中的所有字词构建统计词典。

    代码:

    import os
    import json
    import jieba
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt

    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    import torch.optim as optim
    from torch.autograd import Variable

    import torchvision
    import torchvision.datasets as datasets
    import torchvision.transforms as transforms

    from torch.utils.data import DataLoader,Dataset

    # Set Hyper Parameters
    LR = 0.005
    EPOCH = 100
    BATCH_SIZE = 1
    Sentence_Num = 100
    Embedding_Dim = None

    # Bulid Vocab

    sentence_set = [] # 收集所用到的文本句子
    for index in range(Sentence_Num):
    with open('../../Corpus/CAIL2018/'+str(index)+'.txt','r',encoding='UTF-8') as f:
    sentence_set.append(f.read().replace(' ', '').replace(' ', '').replace(',', ' ').replace('。', ' ').replace(':', ' ').replace(' ', ' ').lower())

    word_set = set() # 利用jieba库进行中文分词
    for sentence in sentence_set:
    words = jieba.lcut(sentence)
    word_set.update(words)

    word_to_ix = {'SOS':0, 'EOS':1, 'UNK':2} # 'SOS': start of sentencex
    ix_to_word = {0:'SOS', 1:'EOS', 2:'UNK'} # 'EOS': end of sentence
    # 'UNK': unknown token
    for word in word_set: # 构建词典,注意:word_to_ix用于对字词进行编号,ix_to_word用于将模型的输出转化为字词
    if word not in word_to_ix:
    word_to_ix[word] = len(word_to_ix)
    ix_to_word[len(ix_to_word)] = word

    Embedding_Dim = len(word_to_ix)

    with open('./Vocab.txt','w',encoding='UTF-8') as f: # 保存词典
    for vocab in word_to_ix.items():
    f.write(vocab[0]+' '+str(vocab[1])+' ')
    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
    参考资料:

    [Pytorch] - No.2 Pytorch 实现RNN语言模型
    GitHub - fxsjy/jieba: 结巴中文分词
    3.搭建数据集

    由于所使用的中文文本并无数据集格式,故我们需要自己制作数据集。
    注意,代码中的 bulid_one_hot 并非生成 one-hot 向量。这是因为模型中使用了 nn.Embedding() ,它会初始一个矩阵,相当于我们模型再训练过程中,顺便训练了一个 word embedding matrix。
    至于如何使用该函数进行 word embedding ,大家可以查阅本小节的参考资料。

    代码:

    # Bulid Dataset

    def bulid_one_hot(word,word_dict):
    if word in word_dict:
    return torch.LongTensor([word_dict[word]])
    return torch.LongTensor([word_dict['UNK']])


    class MyDataset(Dataset):
    def __init__(self, words, labels, transform=None, target_transform=None):
    self.words = words
    self.labels = labels
    self.transform = transform
    self.target_transform = target_transform

    def __getitem__(self, index):
    words, labels = self.words[index], self.labels[index]
    if self.transform is not None:
    words = [self.transform(word) for word in words]
    if self.target_transform is not None:
    labels = self.target_transform(labels)
    return words, labels

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

    train_words, train_labels, test_words, test_labels = [], [], [], []

    for i in range(int(0.9*Sentence_Num)):
    sentence = sentence_set[i]
    words = jieba.lcut(sentence)
    words.insert(0,'SOS')
    words.append('EOS')
    words = [bulid_one_hot(word,word_to_ix) for word in words]

    for j in range(0,len(words),6):
    if j+6 >= len(words):
    break
    train_words.append(words[j:j+5])
    train_labels.append(words[j+5])

    for i in range(int(0.9*Sentence_Num),Sentence_Num):
    sentence = sentence_set[i]
    words = jieba.lcut(sentence)
    words.insert(0,'SOS')
    words.append('EOS')
    words = [bulid_one_hot(word,word_to_ix) for word in words]

    for j in range(0,len(words),6):
    if j+6 >= len(words):
    break
    test_words.append(words[j:j+5])
    test_labels.append(words[j+5])

    trans, target_trans = None, None # transforms.ToTensor(), transforms.ToTensor()

    train_set = MyDataset(train_words, train_labels, trans, target_trans)
    train_loader = DataLoader(dataset=train_set, batch_size=BATCH_SIZE)

    test_set = MyDataset(test_words, test_labels, trans, target_trans)
    test_loader = DataLoader(dataset=test_set, batch_size=BATCH_SIZE)
    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
    参考资料:

    torch.nn.Embedding
    10分钟快速入门PyTorch (7)
    Pytorch数据读取(Dataset, DataLoader, DataLoaderIter)
    pytorch学习笔记(六):自定义Datasets
    Pytorch中正确设计并加载数据集方法
    4.搭建模型

    采用 GRU 结构构建 Seq2Seq 模型,其中,loss function 为 nn.CrossEntropyLoss(), optimizer 为 optim.SGD()。
    注意,pytorch 中采用 nn.CrossEntropyLoss(),对输入与输出有格式要求,请查阅本小节的参考资料。

    代码:

    # Bulid Seq2Seq Model

    class Encoder(nn.Module):
    def __init__(self, input_size, hidden_size):
    super(Encoder, self).__init__()
    self.hidden_size = hidden_size
    self.embedding = nn.Embedding(input_size, hidden_size) # 将one-hot向量embedding为词向量
    self.gru = nn.GRU(hidden_size, hidden_size) # GRU的hidden layer的size与词向量的size一样,并非必须

    def forward(self, input, hidden):
    embedded = self.embedding(input).view(1, 1, -1) # RNN的输入格式为 (seq_len, batch, input_size)
    output = embedded
    output, hidden = self.gru(output, hidden)
    return output, hidden

    def initHidden(self):
    return torch.zeros(1, 1, self.hidden_size) # 初始化Encoder的隐状态


    class Decoder(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
    super(Decoder, self).__init__()
    self.hidden_size = hidden_size
    self.embedding = nn.Embedding(input_size, hidden_size)
    self.gru = nn.GRU(hidden_size, hidden_size)
    self.out = nn.Linear(hidden_size, output_size)

    def forward(self, input, hidden):
    output = self.embedding(input).view(1, 1, -1)
    output = F.relu(output)
    output, hidden = self.gru(output, hidden)
    output = self.out(output[0])
    return output, hidden

    def initHidden(self):
    return torch.zeros(1, 1, self.hidden_size)


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

    def forward(self, inputs):
    encoder_hidden = self.encoder.initHidden()
    if torch.cuda.is_available():
    encoder_hidden = encoder_hidden.cuda()

    # encode
    for word in inputs:
    encoder_out, encoder_hidden = self.encoder(word, encoder_hidden)

    # decode
    decoder_hidden = encoder_hidden
    pred, decoder_hidden = self.decoder(inputs[-1], decoder_hidden)

    return pred


    encoder = Encoder(Embedding_Dim,1000)
    decoder = Decoder(Embedding_Dim,1000,Embedding_Dim)

    if torch.cuda.is_available():
    encoder = encoder.cuda()
    decoder = decoder.cuda()

    seq2seq = Seq2Seq(encoder,decoder)

    if torch.cuda.is_available():
    seq2seq = seq2seq.cuda()

    # Bulid loss function and optimizer

    loss_func = nn.CrossEntropyLoss()
    #encoder_optimizer = optim.SGD(encoder.parameters(), lr=LR, momentum=0.9)
    #decoder_optimizer = optim.SGD(decoder.parameters(), lr=LR, momentum=0.9)
    seq2seq_optimizer = optim.SGD(seq2seq.parameters(), lr=LR, momentum=0.9)
    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
    参考资料:

    深度解析Pytorch Seq2Seq Toturials(2)
    spro/practical-pytorch
    搞懂Transformer结构,看这篇PyTorch实现就够了!
    PyTorch(总)——PyTorch遇到令人迷人的BUG与记录
    RuntimeError: multi-target not supported (newbie)
    pytorch error: multi-target not supported in CrossEntropyLoss()
    5.训练模型

    代码:

    # Train Seq2Seq Model

    for epoch in range(EPOCH):
    loss_sum = 0
    for step, (inputs, labels) in enumerate(train_loader):
    # encoder_hidden = encoder.initHidden()
    label = torch.LongTensor((1,))
    label[0] = int(labels.data.numpy()[0])

    if torch.cuda.is_available():
    inputs = [word.cuda() for word in inputs]
    label = label.cuda()
    # encoder_hidden = encoder_hidden.cuda()

    # forward
    pred = seq2seq(inputs)
    loss = loss_func(pred,label)

    # backward
    seq2seq_optimizer.zero_grad()
    loss.backward()
    seq2seq_optimizer.step()

    '''
    for word in inputs:
    encoder_out, encoder_hidden = encoder(word, encoder_hidden)
    decoder_hidden = encoder_hidden
    decoder_out, decoder_hidden = decoder(inputs[-1], decoder_hidden)

    loss = loss_func(decoder_out,label)

    #backward
    encoder_optimizer.zero_grad()
    decoder_optimizer.zero_grad()
    loss.backward()
    encoder_optimizer.step()
    decoder_optimizer.step()
    '''

    loss_sum+= loss.data[0]
    print('Epoch: %2d train loss: %.4f' % (epoch, loss_sum))
    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
    结果:


    6.测试模型

    代码:

    # Test Seq2Seq Model

    for step, (inputs, labels) in enumerate(test_loader):
    # encoder_hidden = encoder.initHidden()
    label = torch.LongTensor((1,))
    label[0] = int(labels.data.numpy()[0])

    if torch.cuda.is_available():
    inputs = [word.cuda() for word in inputs]
    label = label.cuda()
    # encoder_hidden = encoder_hidden.cuda()

    decoder_output = seq2seq(inputs)

    '''
    # forward
    for word in inputs:
    encoder_out, encoder_hidden = encoder(word, encoder_hidden)
    decoder_hidden = encoder_hidden
    decoder_out, decoder_hidden = decoder(inputs[-1], decoder_hidden)
    '''

    # output
    ans = ''
    pred = ''

    for word in inputs:
    ix = word.cpu().data.numpy()[0][0]
    ans+=ix_to_word[ix]
    pred+=ix_to_word[ix]

    ans+=ix_to_word[int(labels.data.numpy()[0])]
    pred+=ix_to_word[np.argmax(decoder_output.cpu().data.numpy())]

    print('Answer: %s' % ans)
    print('Prediction: %s' % pred)
    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
    结果:


    上述结果是全部结果中效果不错的,可以观察到:虽然模型无法完全预测后续字词,但是能依照句子的前部分继续生成意思完整的句子。
    不过,整体来看模型效果较差,我认为有以下几个原因:

    所用文本数量少,仅用了100个句子进行训练。
    构造的词库小,词典中仅有3000+字词,其中包括许多无意义的字词。
    未对超参数进行微调。
    7.保存/加载模型

    往往大家保存和加载模型都是用的最简单的方法:torch.save(model,path),torch.load(path)。
    这样的方法不仅将模型的参数保存了下来,还将模型的结构保存了下来。

    有时我们只需要保存模型的参数,我们可以采用这样的方法:torch.save(model.state_dict(),path),torch.load_state_dict(torch.load(path))。

    当然,还有许多复杂的方法可以选择,大家可以查阅参考资料进一步了解。

    代码:

    # Save Seq2Seq Model


    '''
    torch.save(encoder.state_dict(),'../../Model/Seq2Seq/encoder_params.pkl')
    torch.save(decoder.state_dict(),'../../Model/Seq2Seq/decoder_params.pkl')

    torch.save(encoder,'../../Model/Seq2Seq/encoder.pkl')
    torch.save(decoder,'../../Model/Seq2Seq/decoder.pkl')
    '''

    torch.save(seq2seq.state_dict(),'../../Model/Seq2Seq/seq2seq_params.pkl')
    torch.save(seq2seq,'../../Model/Seq2Seq/seq2seq.pkl')

    # Load Seq2Seq Model

    # encoder.load_state_dict(torch.load('../../Model/Seq2Seq/encoder_params.pkl'))
    # decoder.load_state_dict(torch.load('../../Model/Seq2Seq/decoder_params.pkl'))
    seq2seq = torch.load('../../Model/Seq2Seq/seq2seq.pkl')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    参考资料:

    Pytorch 保存模型与加载模型
    如果你看到了这篇文章的最后,并且觉得有帮助的话,麻烦你花几秒钟时间点个赞,或者受累在评论中指出我的错误。谢谢!

    作者信息:
    知乎:没头脑
    LeetCode:Tao Pu
    CSDN:Code_Mart
    Github:Bojack-want-drink
    ---------------------
    作者:Code_Mart
    来源:CSDN
    原文:https://blog.csdn.net/Code_Mart/article/details/86978470
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    CI(CodeIgniter)框架介绍
    yii框架
    Jenkins简单使用介绍
    UNITY_INITIALIZE_OUTPUT宏
    UNITY地图寻路及服务器解决方案
    bloom
    Rigidbody中 Angular Drag (角阻力):
    Unity5 Shader Stripping 导致 LightMap 全部丢失的解决方法
    移动端播放视频文件
    UNITY5 为什么Inspector视图中脚本前面的勾选框没了
  • 原文地址:https://www.cnblogs.com/jfdwd/p/11075794.html
Copyright © 2011-2022 走看看