henryzhou Make robot converse with human naturally

chat-bot

2019-03-16
Henryzhou

聊天机器人

​ 聊天机器人的应用场景十分丰富,包括用户服务或者在线自动回复。以往的聊天机器人是基于自动的检索系统,根据用户的输入自动选取最合适的回答。这种机制在需要多领域知识或领域知识快速迭代的应用场景就显得捉襟见肘。使用深度学习的方法,只要提供语料库,机器就能够从中学习到对话的能力。

  • 项目重点:
    • 对cornell Movie-Dialogs corpus语料库进行预处理,分割成成对的对话问答
    • 应用Luong attention mechanism构建seq2seq模型
    • 用mini-batch的数据训练encoder和decoder
    • 使用greedy-search方法生成回答
    • 与聊天机器人交流
  1. 语料库主要使用两部分:movie_lines.txt、movie_conversations.txt
  • movie_lines包含一下信息:
    • LineID:唯一确定当前sentence在整个语料库中的位置
    • userID:在某电影中角色的编号
    • movieID:电影的编号
    • user_name:电影中角色的名字
    • text: 角色台词
  • movie_conversations包含一下信息:
    • character1ID:对话中角色1的编号
    • character2ID:对话中角色2的编号
    • movieID:电影的编号
    • utterranceIDs:对话所在的行编号
  1. 解析文本

    1. 将语料库movie_lines.txt中每行解析并保存在字典中,key为['lineID', 'characterID', 'movieID', 'characher', 'text']
    2. 将语料库movie_lines.txt中每行解析并保存在字典中,key为['character1ID', 'character2ID', 'movieID', 'utterranceIDs','lines'],lines是根据uterranceIDs从第一步得到的字典中获取的台词。
    3. 从第二步得到的conversations字典中生成问答对:pa_pairs,内容为[inputLine, targetLine]的列表。这里就得到了可以用于训练的原始问答对话qa_pairs。将qa_pairs写入datafile.csv文件,每行为一对问答。
  2. 创建一个Voc词汇类,功能包括:

    1. 往词汇表中添加词add_word(), 给词汇分配index并且计算词汇出现的次数
    2. 添加句子中出现的所有词汇(使用add_word()的功能)
    3. 去除不常出现的词汇(根据词频和词汇出现次数的阀值)trim(),只保留出现次数大于给定阀值的词汇,筛选结束后重新生成一次词汇表。
  3. 在将文本用于创建Voc词汇表之前,对文本进行编码转化、标准化和过滤

    1. unicode2Ascii(),在需要比较字符串的程序中使用字符的多种表示会产生问题。 为了修正这个问题,你可以使用unicodedata模块先将文本标准化:normalize() 第一个参数指定字符串标准化的方式。 NFC表示字符应该是整体组成(比如可能的话就使用单一编码),而NFD表示字符应该分解为多个组合字符表示。
    2. normalizeString():在unicode2Ascii()基础上,将所有字符转化为小写(如果有的话),去除基本标点符号和字母以外的所有字符。
    3. filterPairs():为了帮助模型收敛,剔除长度超过一定长度的句子
    4. 使用经过上面三个预处理步骤的文本,初始化一个Voc词汇类,voc词汇类包含了word2index、word2count、index2word和num_words,为了进一步帮助模型收敛,使用voc的trim()函数剔除一些非常见的词汇,重新生成一次词汇表,并且剔除包含非常见词汇的句子,得到新的qa_pairs。
  4. 将文本pa_pair向量化,并通过pad方法成(max_length, batch_size)的形状

    1. 之所以要设置成(max_length, batch_size)的形状,是因为每一个batch中句子的长度不是对齐的,而训练模型网络需要固定输入输出以及隐藏层的形状。
    2. inputVar():先将qa_pair中的question句子batch转化成indexes的batch,使用itertools.zip(*l, fillvalue='PAD')进行空白部分的填充。返回填充后的index_batch和batch中个句子的长度lengths
    3. outputVar:对qa_pair中的answer句子进行填充,过程和inputVar大致相同,不同点在于返回mask用于记录answer中非填充部分的位置,用于后面的损失函数的计算。
  5. 聊天机器人的核心:seq2seq

    1. seq2seq模型包含两个模块:encoder和decoder。encoder将不同长度的输入压缩成一个高维的上下文张量,这个上下文张量包含着从所有输入语句中学习到的语义信息。decoder可以根据模块的输入和隐藏状态(hidden-state)生成预测的输出。
    2. encoder使用了双向的GRU,从输入语句的双向学习隐藏的信息,在双向GRU层学习输入的表示之前,我们在输入和GRU层之间夹一层Embedding层。Embedding层可以将输入重新表示到一个新的空间中,好处时可以使得模型学习到一部分语义上的信息,比如语义相近的词可能被映射到相似的Embedding空间中。
      • 经过第5步后,pa_pair变成了使用padding的方式进行长度对齐的文本,为了方便将训练数据输入到LSTM模型进行训练,同时为了保证模型训练的精度,应该同时告诉LSTM相关padding的情况,此时,pytorch中的pack_padded_sequence就有了用武之地,pack_padded_sequence按序列长度的长短排序,长的在前,短的在后。pad_packed_sequence的作用时将index_batch重新打包成包含0填充的序列。 当使用双向RNN的时候, 必须要使用 pack_padded_sequence !! .否则的话, pytorch 是无法获得 序列的长度, 这样也无法正确的计算双向 RNN/GRU/LSTM的结果.
      • Encoder部分的计算图如下所示:
        • 将词的index转化为词嵌入embedding
        • 对input进行pack压缩后feed给RNN模块
        • 向前传播GRU层(embedding层->GRU)
        • 对ouput重新填充0
        • 对双向GRU输出求和
        • 返回输出和隐藏状态
    3. Decoder通过隐藏状态和上下文张量对输入响应进而生成一个一个词的输出,直到生成EOS结束符。只使用seq2seq模型可能对于长句子,对整个句子做编码会出现信息丢失的问题。注意力机制使得decoder只关注输入句子的特定部分而不是整个句子。
      • 注意力权重由decoder的hidden state和dcoder input共同计算得到。注意力权重和encoder的output乘积即可得到加持注意力后新的的encoder output
      • global attention基于attention改进得到,我们的模型使用的就是这个Luong attention。与基本的attention的不同点在于:
        1. global attention考虑了所有encoder的hidden state,而不是只关注当前step的encoder的hidden state
        2. global attention利用decoder当前step的hidden state计算attention weights,而不是前一step的hidden state
        3. global attention给出了计算attention energies或者得分函数的方法,包括dot、general、concat三种。$\overline{h}_s=all \ encoder states$, $h_t=current \ target \ decoder \ state$
        4. 我们单独设置一个Attn子层,计算注意力权重
      • decoder层结构:embedding->dropout->GRU->Attn->concat(Linear)->out(Linear)->softmax
        1. 获得当前输入层词汇的embedding
        2. 按照decoder层结构向前传播单向GRU
        3. 基于第2步计算得到的GRU层的output计算attention weights
        4. 将attention weigth应用到encoder的outout上得到新的encoder output
        5. 合并当前GRU输出和应用attention weight后的新的encoder output得到concateed_output
        6. 将concated_output通过两层全连接层,得到softmax概率output
        7. 返回output和hidden
  6. 定义训练步骤

    1. 遮掩损失:我们处理的是batch后的填充了0值的序列,但在计算损失的时候不能也将填充的部分也加入到损失的计算过程中。根据mask只计算非填充部分的交叉熵。
    2. 定义单次训练的过程,这个过程我们使用了两个技巧来提高训练的效率。teacher forcing 也就是按照teacher_forcing_ratio设置的概率将本次训练的target代替decoder的输出(guess)作为decoder下一次的输入。gradient clipping可以将反向传播中计算得到的梯度限制到一个固定的区间内,可以解决梯度爆炸的问题。单次训练的计算图如下:
      1. 将整个batch在encoder中向前传播
      2. 将decoder的input初始化为开始符SOS_token,将decoder的hidden state初始化为encoder最后的hidden state
      3. 每次一步地向decoder传播batch sequence
      4. 如果使用teacher forcing的话:将当前step的target设置为decoder的下一次输入,否则将当前step的output作为下一次decoder 的输入
      5. 计算并且累计loss
      6. 进行反向传播
      7. 进行梯度截断
      8. 更新encoder和decoder的模型参数,5-8四步就是为了进行梯度截断对优化器的工作过程的拆解。
    3. 定义n_iteration此迭代的训练过程。这个函数大部分的功能依赖于单次的训练。我们将模型保存为tar包,保存的参数包括encoder和decoder的state_dicts, optimizer的state_dicts,loss和当前的iteration。本函数的计算图如下:
      1. 从qa_pair中选取出batch_size大小的qa对组成batch,进而制作iteration大小的batches
      2. 进行单次训练,每次训练完都打印当前iter的loss,每500次iter保存一次模型参数。
      3. 加入loadFilename值有效则从文件中恢复模型,并从当前的iter开始训练。
  7. 定义输入的evaluate函数,对输入的句子生成回答

    1. 定义一个greedySearcher函数,根据输入的input_seq、lengths、max_lengths生成最后的回答序列,greedySearch的计算图如下:
      1. 将input_seq通过encoder模型
      2. 将encoder的最后step的hideen layer作为decoder的第一个hidden input
      3. 将decoder的首次输入初始化为SOS_token
      4. 初始化一个tensor用来追加decoder得到的单词
      5. 每次得到一个decoder的输出:
        • 在decoder中向前传播一次
        • 获得可能性最高的下一个输出的token
        • 记录token和score
        • 将当前的token作为decoder的下一输入
      6. 返回decoder预测的单词token集合和scores
    2. 对于输入的句子我们可以将其看作是batch_size=1的测试数据,计算lengths,最后使用greedySearcher()生成回答。
  8. 整个训练过程整合

    1. 超参数设置:

      model_name attn_model hidden_size encoder_n_layers decoder_n_layers
      cb_model dot 500 2 2
      dropout batch_size checkpoint_iter    
      0.1 64 400    
    2. 初始化embedding层:nn.Embedding(voc.num_words, hidden_size)

    3. 初始化encoder = EncoderRNN(hidden_size, embedding, encoder_n_layers, dropout)

    4. 初始化decoder = LuongAttnDecoderRNN(attn_model, embedding, hidden_size, , voc.num_words, decoder_n_layers, dropout)

    5. 训练模型

      1. 设置训练的超参数
      clip teacher_forcing_ratio decoder_learning_ratio n_iteration print_every
      50 1.0 5.0 4000 1
      save_every        
      500        
      1. 将encoder和decoder设置为训练模式
      2. 设定encoder和decoder的优化器为adam
      3. 调用trainIters函数进行训练
    6. 预测

      1. 将encoder和decoder设定为预测模型
      2. 使用GreedySearchDecoder生成一个输入框,对输入进行预测

下一篇 文摘

Comments

Content