从零开始学Python自然语言处理(30)—— Encoder-Decoder实战演练

浏览: 3497

说到Encoder-Decoder框架,就算没用过也都听说过。这玩意儿在经典的机器翻译模型里经常出现。而且,该框架灵活多变,适用于多个场景,比如文本摘要、问答系统等。只要是这种输入一个序列,再输出一个序列的场景,都可以用到Encoder-Decoder框架。所以学习甚至是学会这个框架是很有必要都。

浅谈Encoder-Decoder

Encoder-Decoder,即编码器-解码器,粗的来讲,就是你输入模型一个序列,模型对其进行编码,然后再对这个编码进行一个解码,最后再返回给你一个序列。那举个形象的栗子,就拿人脑来比喻一下子:一个翻译官,他听到英语之后(序列输入到大脑),大脑先要理解这段英语的意思(看作是对英语语义的一种编码),大脑理解了这个意思之后,再用中文将其表达出来(看作是对这个编码的一种解码)。
意思就是这么个意思。先对一个序列进行编码,可以看作是对特征的提取和抽象;再对这个编码好的特征进行解码,转换为另一种序列。编码器可以使用的模型包括RNN、CNN,甚至是多层感知器,只要是能对输入序列进行编码的模型都可以看作是编码器;解码器同样可以使用RNN、CNN等来进行解码。
大体上知道了它是怎么工作的之后,我们来介绍Encoder-Decoder这一经典框架的经典深度学习模型——用循环神经网络来实现。

基于LSTM的Encoder-Decoder

循环神经网络可以很好地处理时序特征,自然语言其实也相当于一种时序数据。如下图所示,Encoder端的自然语言一个词一个词的输入到LSTM中,最终会生成一个中间变量,即语义编码C,这玩意儿因为是包含了之前所有词的信息,可以看作是它已经蕴含了输入序列的大致语义信息。Decoder端再对这个语义编码C进行解码,转换为新的序列。

但其实上面这张图很容易误导大家对循环神经网络的认知。真正的循环网络rnn长的样子如下图左侧部分——每次输入一个step(在自然语言中,就是一个词向量),然后模型生成一个输出,即图中的W和o(W和o其实是相同的玩意儿,如果每一步的输出作为下一个时间步的输入,就是图上的W,如果直接将其输出,就是o,只是为了区分其功能而已)。然后下一个时间步的输入就是xi和上一步的输出Wi-1。而为了看起来方便,许多教学 博客喜欢将rnn展开,就是下图右侧的形式。每个重复的部分叫做cell,看起来它们好像是不同的cell,但实际上它们只是同一个cell在不同时刻的样子。

LSTM是rnn的一种,其基本结构和上图类似。好了,在了解了循环神经网络的基本结构之后,就不难理解这个语义编码C了。这个玩意儿是最后一个词经过模型之后的输出,即最后一个o。当然了,针对语义编码C的操作可以有许多种,除了是最后一个o之外,也可以是所有o的一个平均,即avg(o0, o1, ..., on)。
接下来,就需要对语义编码C进行解码。我们还使用LSTM进行解码。有细心的小伙伴可能会提问,LSTM的输入需要是时序数据,即有多个时间步,现在你只有一个语义编码C,就相当于只有一个step,和LSTM的输入要求不匹配啊。没戳!你说的对,这里我们就需要trick一下了,在keras里,有一个函数是RepeatVector,它的作用就是将输入重复n次。重复n次之后,就有了时序,虽然每一步都是一样的,但我们至少满足了对输入的要求。然后将其输入到Decoder端的LSTM,就能够输出序列了。

机器翻译

当然,上述这么做其实是很糙的,而且在做机器翻译的时候,显然不符合常理。机器翻译时,这个语义编码C是作为每一个时间步的中间向量,跟随每一个词同时输入的。什么意思呢?比如模型已经对英文“I love China!”进行了编码得到语义编码C,然后在解码时,<S>+C解码出中文“我”,然后“我”+C解码出“爱”,再然后“爱”+C解码出“中”,再再然后“中”+C解码出“国”,再再再然后“国”+C解码出“!”,最后,“!”+C解码出<E>,解码过程结束。<S><E>是特殊标记,用于告诉模型什么时候开始解码,什么时候结束解码。(当然也可以C只作为第LSTM的初始中间变量,剩下的时间步的中间输入还是上一步的输出)

自编码器

Encoder-Decoder的特性,决定了他可以做特征提取器模型。当模型的输入和输出相同时,就是传说中的自编码器了,即输入模型为x,输出还是x。有的同学会问,这特么是干嘛呢?闲得慌了?
你还没看出其中的奥妙!x输入Encoder之后,得到中间向量C,而解码其需要根据C再输出x,这说明了什么?说明C包含了x的信息啊,是不是可以将C看作是x的低维表示呢?而且,x是时序特征,一般是不能直接放入树模型计算的,直观来讲,x长这样:[[···],[···],[···]],而C是长这样的[···],就可以作为对x的特征提取,输入到一般的机器学习模型里了。同时,输入和输出长得一模一样,就不需要标签了是不是?所以自编码器也是一种无监督模型。

代码

这里就只提供一下自编码器的代码了(因为容易)。

    from keras.models import Model
    import numpy as np
    inputs = Input(shape=(3,4)) #假设输入是有3个时间步,每一步的特征长度为4
    Encoder = LSTM(3)(inputs) #将其进行编码,编码长度为3
    repeat = RepeatVector(3)(Encoder) #将这个编码重复三次,得到一个时间步为3,每一步的特征为3的序列
    Decoder = LSTM(4,return_sequences=True)(repeat) #输入到解码器中,注意这个return_sequences参数,默认是false,即只返回最后一步的编码,为true的话,就返回每一步的输出,即返回的也是一个序列。
    train_model = Model(inputs, Decoder) #这个model是用来训练模型
    true_model = Model(inputs, Encoder) #而这个model则是真正拿来用的model,请读者自己观察两者的区别
    train_model.compile(loss='mse', optimizer='adam')train_model.summary()

    简单玩具级别的模型,弄个小破例子验证一下。

      x = np.array([[[0,0,0,1],[1,0,0,0],[0,1,0,0]]])  #只有一个样本train_model.fit(x,x)  #输入输出都是x

      训练好之后,用 true_model来输出一下:

        true_model.predict(x)

        输出为:

          array([[-0.05461767, -0.11485334,  0.04993425]], dtype=float32)

          的确起到了编码的效果哦!

          机器翻译的代码因为逻辑要复杂的多,小编会放在下一篇文章中,记得点赞哦!

          欢迎关注我的公众号“数据科学杂谈”,原创技术文章第一时间推送。

          扫码下图关注我们不会让你失望!

          image.png

          推荐 0
          本文由 ID王大伟 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
          转载、引用前需联系作者,并署名作者且注明文章出处。
          本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

          0 个评论

          要回复文章请先登录注册