[codes] Writing Editing Networks Source Code Analysis

Basic Information

作者:Qingyun Wang

论文:Paper Abstract Writing through Editing Mechanism (ACL)

源码:https://github.com/EagleW/Writing-editing-Network

Data Preprocessing

1. Load Data

  1. 抽取出headline 和 abstract
  2. 分词(自己写了一个分词,当然也可以用NLTK等工具包)
  3. 构造corpus:列表(all sample)> 子列表(a sample: headline+abstract)> 子子列表 (tokens + sentence)

2. Construct Vocabulary

  1. 根据训练数据构造词表;按照词频排序,删去低频词,将vocabulary大小设置为vocab_size
  2. 在单词、标点的基础上,增加<pad>,<unk>,<eos>,<bos>等几个标志位
  3. 将<pad>放在字典的第一位

3. word2id,id2word

  1. 将corpus中的token映射成id(未进行padding)
  2. 在abstract的前后增加了两个标志位<eos>,<bos>,(当teacher forcing training时)作为解码输入相当于进行了shift,而标题不需要添加<eos>,<bos>
  3. 按照编码输入的长度对进行corpus进行排序(按照headline从长到短对corpus进行排序)
  4. 获得max_x_len,max_y_len

Model Construction

1. Embedding

  1. 随机初始化embedding matrix,传入nn.Embedding两个参数:vocab_size,embed_dim
  2. Embedding定义好之后,encoder for title,encoder for draft,decoder共用。即headline,draft,abstract共用同一个编码

2. Encoder for Title

  1. single-layer bidirectional GRU
  2. 因为编码输入是按照title顺序排序好的,所以输入的时候可以利用nn.utils.rnn.pack_padded_sequence进行编码,得到了结果再利用nn.utils.rnn.pad_packed_sequence补出padding位。如此可以使结果更加准确,排除了padding位对双向编码的影响
  3. input_sorted_by_length ==> pack ==> encode ==> pad ==> encoder_output

3. Encoder for Draft

  1. single-layer bidirectional GRU
  2. 这个encoder是对解码器输出的draft进行编码,输入的序列虽然在形状上都是 [batch_size, max_y_len],但是其中的有效序列元素个数参差不齐,此时再进行排序,编码,恢复batch中sample原来的位置就比较麻烦
  3. 所以就不用nn.utils.rnn.pack_padded_sequencenn.utils.rnn.pad_packed_sequence两个函数了,编码结果会受到padding位的影响,但是无伤大雅

4. Decoder for All-pass Decoding

  1. single-layer unidirectional GRU
  2. 每个pass的解码,用的都是同一个decoder
  3. 包含两种attention:
    1. 对于上一pass中decoder hidden states的attention
    2. 当前pass的decoder hidden states,对于encoder hidden states的attention

5. Complete Model

包含以上提到的两种encoder,decoder,以及word probability layer

6. Instantiate Model

# 为模型设置可见GPU
torch.cuda.set_device(0)

# 打印GPU信息
if torch.cuda.is_available():
    print("congratulations! {} GPU(s) can be used!".format(torch.cuda.device_count()))
    print("currently, you are using GPU" + str(torch.cuda.current_device()), 
          "named", torch.cuda.get_device_name(torch.cuda.current_device()))

    # 利用CUDA进行加速
    model = model.cuda()
    criterion = nn.CrossEntropyLoss(ignore_index=0).cuda()
    
else:
    print("sadly, CUDA is not available!")

输出:

Congratulations! 1 GPU(s) can be used!
Currently, you are using GPU0 named "GeForce GTX 1050 Ti"

Training Process

  1. 进入train_epoch函数

    1. 进入train_batch函数

      1. 设置 previously generated draft 为 None

      2. input [batch_size, max_x_len], target [batch_size, max_y_len]

      3. 对输入进行编码,[batch_size, max_x_len]

      4. 进入multi-pass decoding循环

        1. 如果previously generated draft 为 None,说明是第一次进行解码,只进行decoder - encoder端的attention

          1. 解码得到隐含向量
          2. 映射到vocabulary size,得到概率分布
          3. 采样得到解码输出(greedy search)
          4. 作为previously generated draft
        2. 如果previously generated draft 不为 None,说明之前已经进行过解码了,所以要有decoder-encoder之间的attention,还要对生成的draft的隐含状态进行attend

        3. 该implementation中对每一pass的解码结果,即draft,都与ground truth进行了交叉熵的Loss计算,反向传播,更新参数(训练一个batch,反传多次,希望草稿也尽量接近目标序列)

    2. 记录batch的loss,在一个epoch完结之后打印average epoch loss

    3. 每个epoch中没有进行validation

  2. epoch循环结束之后保存一组参数

  3. 如果当前epoch的平均loss大于前一epoch的平均loss的话,需要停止训练防止过拟合,不过此处模型是利用Training set上的loss来计算的

Inference Phase

  1. 载入训练好的模型参数:

    model.load_state_dict(torch.load(args.save))
    
    # 在GPU1上训练的模型,当前torch可见的GPU序号为0时不能够被正藏载入,需要进行映射
    model.load_state_dict(torch.load(args.save, map_location='cuda:0'))
    
  2. 进入batch循环,执行predict函数

    1. 输入:article ids,input length,num_pass
    2. 进入n_pass循环
      1. 对title进行编码,得到编码状态
      2. 解码(不是teacher forcing),进入max_y_len循环:
        1. 每次对一个batch的第i个字符进行解码
        2. 用encoder final hidden state作为decoder initial hidden state
        3. 根据current decoder state 与 encoder states 进行 attention计算
        4. 根据current decoder state 与 draft embedding 进行 attention计算
        5. 将attention vectors拼接起来通过一个全连接层,作为解码输入
        6. 将解码输出映射为单词表上的概率分布,进行采样获得当前解码时间步概率最大的单词(将UNK对应的概率人为设置为0,方法是把softmax之前的logits中UNK所对应的项改为-inf)此处进行的是greedy search,而不是beam search
        7. 得到[batch_size, 1]个symbols,保存在一个列表中;当循环结束后,将列表进行stack,得到[batch_size, max_y_len]
    3. 记录解码结果

TO DO

  1. 每个epoch结束进行一次validation,当validation的平均loss大于之前时刻的loss,停止训练。

  2. 添加mixed objective function

  3. 将encoder放入循环外,对于一个batch只编码一次

  4. 将每一pass的decoder 换做不一样的。甚至可以一个是Transformer,一个是RNN(进行两次beam search)。这个实现中,前面几个pass的解码都是greedy search

  5. 搞清楚Transformer的解码原理,理清楚beam searcher应该输入什么维度Tensor,方便进行植入

  6. 原文中model类的forward函数里是:title encoder,draft encoder(条件),decoder。其中draft encoder使用不使用,需要根据是否有draft输入来确定。model的forward函数循环执行pass数次。

    但是完全可以执行model的forward函数一次,但是forward函数里写清楚步骤:进行一次title encoding,再进入pass数循环,上一pass的output draft,可以拿来用做当前pass的输入(如果previously generated draft = None),那么一定是第一次解码。这样就不用进行多次对title编码,效率会高一些

原文地址:https://www.cnblogs.com/lauspectrum/p/11256794.html