漫谈四种神经网络序列解码模型【附示例代码】

以下为漫谈,即瞎聊,利用通俗的语言来谈谈神经网络模型中4种序列解码模型,主要是从整体概念和思路上进行通俗解释帮助理解。预警,以下可能为了偷懒就不贴公式了,一些细节也被略过了,感兴趣的可以直接去阅读原文[1][2][3]。

[1] Sequence to Sequence Learning with Neural Networks
[2] Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation
[3] Neural Machine Translation by Jointly Learning to Align and Translate

利用神经网络进行序列编码的模型主要为RNN,目前比较火的一些变种模型有LSTM和GRU,只是cell单元不同而已。以下统统用RNN来代表。

编码模型比较简单,如下图所示,输入文本{X1-X6}经过循环迭代编码,在每个时刻得到当前时刻的一个隐层状态,最后序列结束后进行特征融合得到句子的表示。注意,一种比较常用的方式是将编码模型最后一个时刻的隐层状态做为整个序列的编码表示,但是实际应用中这种效果并不太好,因而我们的图例中直接采用了整个序列隐层编码进行求和平均的方式得到序列的编码向量。

早期的一些任务主要是做一些主题分类、情感检测等等分类任务,那么在编码向量上面添加一个softmax就可以解决问题。但是对于机器翻译和语音识别等问题则需要进行序列化解码。

注意到,编码时RNN每个时刻除了自己上一时刻的隐层状态编码外,还有当前时刻的输入字符,而解码时则没有这种输入。那么,一种比较直接的方式是把编码端得到的编码向量做为解码模型的每时刻输入特征。如下图所示:

简单直观而且解码模型和编码模型并没有任何区别,然而学者感觉该模型并不优雅,那么接下来我们就来介绍一些精巧点的吧。

=============此处开始转入瞎聊模式==============

我们用考试作弊来做为一个通俗的例子来解释一下模型。

首先我们假设输入文本是所学课本,编码端则是对课本的理解所整理的课堂笔记。解码端的隐层神经网络则是我们的大脑,而每一时刻的输出则是考试时要写在卷子上的答案。在上面最简单的解码模型中,可以考虑成是考试时一边写答案一边翻看课堂笔记。如果这是一般作弊学生的做法,学霸则不需要翻书,他们有一个强大的大脑神经网络,可以记住自己的课堂笔记。解码时只需要回顾一下自己前面写过什么,然后依次认真的把答案写在答卷上,就是下面这种模型了[1]:

还有很多学弱,他们不只需要作弊,而且翻看笔记的时候还需要回顾自己上一时刻写在答卷上的答案(学弱嘛,简直弱到连自己上一时刻写在答卷上的文字都记不住了),就是下面的答题模式了[2]:

然而学渣渣也是存在的,他们不只需要作弊,不只需要回顾自己上一时刻卸载答卷上的答案,还需要老师在课本上画出重点才能整理出自己的课题笔记(这就是一种注意力机制Attention,记笔记的时候一定要根据考题画出重点啊!),真的很照顾渣渣了,他们的答题模式如下[3]:

可见,除了学霸以外,其他人都作弊了,在答题的时候翻看课堂笔记(很多文献中叫这种解码模型结构为peek(偷看),是不是很像在作弊?),而且学渣渣还去找过老师给画过重点,有了清楚的重点之后就不用翻书偷看了,瞄一眼就可以了,文献中叫glimpse(一瞥),是不是很像?

如果我们将他们的大脑网络设定为同样结构的话(将他们的IQ强制保持一致),肯定是作弊的同学得分最高了,学霸模式好吃亏啊。我们来简单做一个模型测试。

测试数据:

输入序列文本 = ['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']
目标序列文本 = ['one two three four five'
, 'six seven eight nine ten'
, 'eleven twelve thirteen fourteen fifteen'
, 'sixteen seventeen eighteen nineteen twenty'
, 'twenty_one twenty_two twenty_three twenty_four twenty_five']

设定一些参数如下:
-
(‘Vocab size:’, 51, ‘unique words’)
(‘Input max length:’, 5, ‘words’)
(‘Target max length:’, 5, ‘words’)
(‘Dimension of hidden vectors:’, 20)
(‘Number of training stories:’, 5)
(‘Number of test stories:’, 5)
-

观察训练过程:


其中,第一种解码模型为 普通作弊,第二种解码模型为 学霸模式,第三种解码模型为 学弱作弊,第四种解码模型为 学渣作弊。

可以看到在IQ值(解码模型的神经网络结构)相同的情况下,学渣作弊模式答题(训练收敛速度)更快,而学霸模式答题最慢。

文章[1]中已经提到过,想通过学霸模式达到一个好的性能需要模型隐层有4000个节点(学霸的IQ果然是高的,有一颗强大的大脑网络)。

可以想想,在课本内容很多很多时,学霸也会累的,而且学弱们你们确定课上能听懂吗?学渣就会笑啦,因而老师给他们画重点了!!!!

哈哈,娱乐而已。

好,学术一点,博主关注:对话与问答,信息检索,深度学习等相关。欢迎学术讨论。

博文出处:http://jacoxu.com/?p=1852

附录:

本博文中测试的示例代码见【Github地址】:

Code   ViewCopyPrint
·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. # -*- encoding:utf-8 -*-   
  2. “”"
  3.     测试Encoder-Decoder 2016/03/22  
  4. “”"  
  5. from keras.models import Sequential   
  6. from keras.layers.recurrent import LSTM   
  7. from keras.layers.embeddings import Embedding   
  8. from keras.layers.core import RepeatVector, TimeDistributedDense, Activation   
  9. from seq2seq.layers.decoders import LSTMDecoder, LSTMDecoder2, AttentionDecoder   
  10. import time  
  11. import numpy as np   
  12. import re  
  13.   
  14. __author__ = ’http://jacoxu.com’   
  15.   
  16.   
  17. def pad_sequences(sequences, maxlen=None, dtype=’int32′,   
  18.                   padding=’pre’, truncating=’pre’, value=0.):   
  19.     ”’Pads each sequence to the same length:
  20.     the length of the longest sequence.  
  21.  
  22.     If maxlen is provided, any sequence longer  
  23.     than maxlen is truncated to maxlen.  
  24.     Truncation happens off either the beginning (default) or  
  25.     the end of the sequence.  
  26.  
  27.     Supports post-padding and pre-padding (default).  
  28.  
  29.     # Arguments  
  30.         sequences: list of lists where each element is a sequence  
  31.         maxlen: int, maximum length  
  32.         dtype: type to cast the resulting sequence.  
  33.         padding: ’pre’ or ’post’, pad either before or after each sequence.  
  34.         truncating: ’pre’ or ’post’, remove values from sequences larger than  
  35.             maxlen either in the beginning or in the end of the sequence  
  36.         value: float, value to pad the sequences to the desired value.  
  37.  
  38.     # Returns  
  39.         x: numpy array with dimensions (number_of_sequences, maxlen)  
  40.     ”’  
  41.     lengths = [len(s) for s in sequences]   
  42.   
  43.     nb_samples = len(sequences)   
  44.     if maxlen is None:   
  45.         maxlen = np.max(lengths)   
  46.   
  47.     # take the sample shape from the first non empty sequence   
  48.     # checking for consistency in the main loop below.   
  49.     sample_shape = tuple()   
  50.     for s in sequences:   
  51.         if len(s) > 0:   
  52.             sample_shape = np.asarray(s).shape[1:]   
  53.             break  
  54.   
  55.     x = (np.ones((nb_samples, maxlen) + sample_shape) * value).astype(dtype)   
  56.     for idx, s in enumerate(sequences):   
  57.         if len(s) == 0:   
  58.             continue  # empty list was found   
  59.         if truncating == ’pre’:   
  60.             trunc = s[-maxlen:]   
  61.         elif truncating == ’post’:   
  62.             trunc = s[:maxlen]   
  63.         else:   
  64.             raise ValueError(‘Truncating type ”%s” not understood’ % truncating)   
  65.   
  66.         # check `trunc` has expected shape   
  67.         trunc = np.asarray(trunc, dtype=dtype)   
  68.         if trunc.shape[1:] != sample_shape:   
  69.             raise ValueError(‘Shape of sample %s of sequence at position %s is different from expected shape %s’ %   
  70.                              (trunc.shape[1:], idx, sample_shape))   
  71.   
  72.         if padding == ’post’:   
  73.             x[idx, :len(trunc)] = trunc   
  74.         elif padding == ’pre’:   
  75.             x[idx, -len(trunc):] = trunc   
  76.         else:   
  77.             raise ValueError(‘Padding type ”%s” not understood’ % padding)   
  78.     return x   
  79.   
  80.   
  81. def vectorize_stories(input_list, tar_list, word_idx, input_maxlen, tar_maxlen, vocab_size):   
  82.     x_set = []   
  83.     Y = np.zeros((len(tar_list), tar_maxlen, vocab_size), dtype=np.bool)   
  84.     for _sent in input_list:   
  85.         x = [word_idx[w] for w in _sent]   
  86.         x_set.append(x)   
  87.     for s_index, tar_tmp in enumerate(tar_list):   
  88.         for t_index, token in enumerate(tar_tmp):   
  89.             Y[s_index, t_index, word_idx[token]] = 1   
  90.   
  91.     return pad_sequences(x_set, maxlen=input_maxlen), Y   
  92.   
  93.   
  94. def tokenize(sent):   
  95.     ”’Return the tokens of a sentence including punctuation.
  96.  
  97.     >>> tokenize(‘Bob dropped the apple. Where is the apple?’)  
  98.     ['Bob', 'dropped', 'the', 'apple', '.', 'Where', 'is', 'the', 'apple', '?']  
  99.     ”’  
  100.     return [x.strip() for x in re.split('(\W+)?', sent) if x.strip()]   
  101.   
  102.   
  103. def main():   
  104.     input_text = ['1 2 3 4 5'   
  105.                   , '6 7 8 9 10'   
  106.                   , '11 12 13 14 15'   
  107.                   , '16 17 18 19 20'   
  108.                   , '21 22 23 24 25']   
  109.     tar_text = ['one two three four five'   
  110.                 , 'six seven eight nine ten'   
  111.                 , 'eleven twelve thirteen fourteen fifteen'   
  112.                 , 'sixteen seventeen eighteen nineteen twenty'   
  113.                 , 'twenty_one twenty_two twenty_three twenty_four twenty_five']   
  114.   
  115.     input_list = []   
  116.     tar_list = []   
  117.   
  118.     for tmp_input in input_text:   
  119.         input_list.append(tokenize(tmp_input))   
  120.     for tmp_tar in tar_text:   
  121.         tar_list.append(tokenize(tmp_tar))   
  122.   
  123.     vocab = sorted(reduce(lambda x, y: x | y, (set(tmp_list) for tmp_list in input_list + tar_list)))   
  124.     # Reserve 0 for masking via pad_sequences   
  125.     vocab_size = len(vocab) + 1  # keras进行embedding的时候必须进行len(vocab)+1   
  126.     input_maxlen = max(map(len, (x for x in input_list)))   
  127.     tar_maxlen = max(map(len, (x for x in tar_list)))   
  128.     output_dim = vocab_size   
  129.     hidden_dim = 20   
  130.   
  131.     print(‘-’)   
  132.     print(‘Vocab size:’, vocab_size, ’unique words’)   
  133.     print(‘Input max length:’, input_maxlen, ’words’)   
  134.     print(‘Target max length:’, tar_maxlen, ’words’)   
  135.     print(‘Dimension of hidden vectors:’, hidden_dim)   
  136.     print(‘Number of training stories:’, len(input_list))   
  137.     print(‘Number of test stories:’, len(input_list))   
  138.     print(‘-’)   
  139.     print(‘Vectorizing the word sequences…’)   
  140.     word_to_idx = dict((c, i + 1) for i, c in enumerate(vocab))  # 编码时需要将字符映射成数字index   
  141.     idx_to_word = dict((i + 1, c) for i, c in enumerate(vocab))  # 解码时需要将数字index映射成字符   
  142.     inputs_train, tars_train = vectorize_stories(input_list, tar_list, word_to_idx, input_maxlen, tar_maxlen, vocab_size)   
  143.   
  144.     decoder_mode = 1  # 0 最简单模式,1 [1]向后模式,2 [2] Peek模式,3 [3]Attention模式   
  145.     if decoder_mode == 3:   
  146.         encoder_top_layer = LSTM(hidden_dim, return_sequences=True)   
  147.     else:   
  148.         encoder_top_layer = LSTM(hidden_dim)   
  149.   
  150.     if decoder_mode == 0:   
  151.         decoder_top_layer = LSTM(hidden_dim, return_sequences=True)   
  152.         decoder_top_layer.get_weights()   
  153.     elif decoder_mode == 1:   
  154.         decoder_top_layer = LSTMDecoder(hidden_dim=hidden_dim, output_dim=hidden_dim   
  155.                                         , output_length=tar_maxlen, state_input=False, return_sequences=True)   
  156.     elif decoder_mode == 2:   
  157.         decoder_top_layer = LSTMDecoder2(hidden_dim=hidden_dim, output_dim=hidden_dim   
  158.                                          , output_length=tar_maxlen, state_input=False, return_sequences=True)   
  159.     elif decoder_mode == 3:   
  160.         decoder_top_layer = AttentionDecoder(hidden_dim=hidden_dim, output_dim=hidden_dim   
  161.                                              , output_length=tar_maxlen, state_input=False, return_sequences=True)   
  162.   
  163.     en_de_model = Sequential()   
  164.     en_de_model.add(Embedding(input_dim=vocab_size,   
  165.                               output_dim=hidden_dim,   
  166.                               input_length=input_maxlen))   
  167.     en_de_model.add(encoder_top_layer)   
  168.     if decoder_mode == 0:   
  169.         en_de_model.add(RepeatVector(tar_maxlen))   
  170.     en_de_model.add(decoder_top_layer)   
  171.   
  172.     en_de_model.add(TimeDistributedDense(output_dim))   
  173.     en_de_model.add(Activation(‘softmax’))   
  174.     print(‘Compiling…’)   
  175.     time_start = time.time()   
  176.     en_de_model.compile(loss=’categorical_crossentropy’, optimizer=’rmsprop’)   
  177.     time_end = time.time()   
  178.     print(‘Compiled, cost time:%fsecond!’ % (time_end - time_start))   
  179.     for iter_num in range(5000):   
  180.         en_de_model.fit(inputs_train, tars_train, batch_size=3, nb_epoch=1, show_accuracy=True)   
  181.         out_predicts = en_de_model.predict(inputs_train)   
  182.         for i_idx, out_predict in enumerate(out_predicts):   
  183.             predict_sequence = []   
  184.             for predict_vector in out_predict:   
  185.                 next_index = np.argmax(predict_vector)   
  186.                 next_token = idx_to_word[next_index]   
  187.                 predict_sequence.append(next_token)   
  188.             print(‘Target output:’, tar_text[i_idx])   
  189.             print(‘Predict output:’, predict_sequence)   
  190.   
  191.         print(‘Current iter_num is:%d’ % iter_num)   
  192.   
  193. if __name__ == ’__main__‘:   
  194.     main()   

漫谈四种神经网络序列解码模型【附示例代码】》上有 38 条评论

  1. 您好,我最近也在用KERAS,做LSTM的时候看到的例子一直是EMBEDDING,所以输入就是定长的,怎么才能实现输入不定长呢?谢谢您

    • 您好,以文本为例,词的特征维度一般是定长的,词的个数可能不定长。在输入端比较好处理,用keras的话,输入特征用0进行补位,然后在Embedding Layer中的参数mask_zero=False 改为True就可以了。如果是输出端词的个数不定长,那么需要预测一个进行批量预测后进行裁剪。祝好!

      • 谢谢您的回复,我理解您的意思是这样子,比如我输入输出的词数都不一样,我用100维的word2vec向量表示一个词,那如果输入最长的是10个词,输入三个词的时候就是300维,补上700维,到1000维。 输出的时候 全都输出1000维的向量,再去0,是这个意思吗?

        • 你好,差不多是这个意思,先输出再裁剪,虽然不太优雅,但效率高。另,输出长度不一定要和输入长度一致,输出的最大长度可以单独定。

  2. 您好,非常感谢您分享的代码,我在用您的模型更换数据的时候,用的中文新闻,在输出target output 和predict output的时候,输出的并不是中文,而是一堆编码形式的类似于{‘Target output:’, ‘\xeb\xaf\xb8\xec\x93\xb0\xeb\xb9\x84\xec\x8b\x9c, \xea\xb2\xbd\xec\xa3\xbc\xeb\x8c\x80\xed\x9a\x8c\xec\x97\x90 \xeb\x9e\x9c\xec\x84\x9c \xec\x97\x90\xeb\xb3\xbc\xeb\xa3\xa8\xec\x85\x98 \xec\xa0\x9c\xea\xb3\xb5′)
    (‘Predict output:’, “[‘quot’, ‘quot’, ‘quot’, ‘quot’, ‘quot’, ‘quot’, ‘quot’, ‘quot’, ‘quot’, ‘quot’, ‘quot’, ‘quot’, ‘quot’, ‘quot’, ‘qu} 这种形式的数据,是因为什么啊。我尝试改变编码或者运用decode(),encode(),但是都解决不了问题。。。。请您赐教

    • 您好,可以尝试把代码187和188行中
      print(‘Target output:’, tar_text[i_idx])
      print(‘Predict output:’, predict_sequence)
      中的括号去掉,如
      print ‘Target output:’, tar_text[i_idx]
      print ‘Predict output:’, predict_sequence

  3. 您好,请问一下,如果encoder和decoder我不想只用一层,而分别想用两个隐层,应该怎么叠加啊,我看simpleseq2seq是有一个参数是depth,但是LSTMdecoder,Attentiondecoder好像没有depth这个参数啊,谢谢

  4. 不好意思贸然打扰了,我现在在做这一块的研究,想更进一步请教些问题,请问可以发邮件给你或者有什么其他联系方式吗?

  5. 您好,我想用于时间序列识别,输入是一总共10个月每隔五分钟的采样电压数据,想先对规定长度数据聚类,比如一天,一周,然后输入一段长度数据来识别,感觉和单词有相似之处,请问这方面可行么?

    • 您好,该示例代码其实是基于Keras的写的,Keras既可支持Theano,也可支持Tensorflow,所以配置一下Keras应该可以在tensorflow上跑,祝好!

    • 您好,不确定keras是否向下兼容,一些接口可能会重新定义并命名,直接使用应该会报错,可根据错误信息修改下应该比较容易地在keras1.0版本上跑起来。

    • LSTM的参数return_sequences=False则只返回(Batch, Hidden_dim)的向量,直接采用Dense全连接层即可,但是如果LSTM的return_sequences=True,则返回(Batch, Time_step, Hidden_dim)的向量,多了time_step的维度,那么一个(Hidden_dim, output_dim)的Dense层需要在Time维度上都计算一遍,那么就用到了TimeDistributed.

  6. Pingback 引用通告: –>>>深度学习 | _梦境

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>