说到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)
的确起到了编码的效果哦!
机器翻译的代码因为逻辑要复杂的多,小编会放在下一篇文章中,记得点赞哦!
欢迎关注我的公众号“数据科学杂谈”,原创技术文章第一时间推送。
扫码下图关注我们不会让你失望!