RNN Encoder-Decoder
Encoder-Decoder模型介绍
RNN Encoder-Decoder模型是一个sequence to sequence模型。该模型将输入序列映射到一个输出序列,而输入序列的长度和输出序列的长度可以是变化的。这种模型最常见的任务是机器翻译。从一种语言的句子(sequence)翻译成另一种语言的句子(sequence)。RNN Encoder-Decoder模型的想法很简单:
(1)一个编码器Encoder处理输入序列
X
=
(
x
(
1
)
,
.
.
.
,
x
(
n
x
)
)
\mathrm{X}=(\mathrm{x}^{(1)},...,\mathrm{x}^{(n_x)})
X=(x(1),...,x(nx)),将它们表示成一个固定长度的向量C,通常是Encoder(是一个RNN)最后一个时间步的输出;
(2)一个解码器Decoder以C作为输入,产生一个输出序列
Y
=
(
y
(
1
)
,
.
.
.
,
y
(
n
y
)
)
\mathrm{Y}=(\mathrm{y}^{(1)},...,\mathrm{y}^{(n_y)})
Y=(y(1),...,y(ny))。
n
x
n_x
nx和
n
y
n_y
ny是变化的。这是一个sequence-to-sequence结构,两个RNN联合训练,以最大化
log
P
(
y
(
1
)
,
.
.
.
,
y
(
n
y
)
∣
x
(
1
)
,
.
.
.
,
x
(
n
x
)
)
\log\mathrm{P}(\mathrm{y}^{(1)},...,\mathrm{y}^{(n_{y})}|\mathrm{x}^{(1)},...,\mathrm{x}^{(n_{x})})
logP(y(1),...,y(ny)∣x(1),...,x(nx))。
(3)(x,y)是所有的训练集中的序列对。当然Encoder-Decoder结构也有其他形式,例如有的翻译模型,就是Encoder的状态输出,传递给Decoder。
该模型最初是在EMNLP2014上的论文《Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation》里提出。我们下面就以该论文中的RNN Encoder-Decoder模型为主(该论文还涉及到的其他部分就不介绍了)。注:该文有EMNLP版和预印版。预印版的附录部分对模型具体的实施做了详细的介绍。此处参见的是预印版。
论文链接:https://arxiv.org/pdf/1406.1078
一个典型的RNN Encoder-Decoder例子如图所示。

设输入是一个词的序列
X
=
(
x
1
,
x
2
,
.
.
.
,
x
N
)
\mathrm{X=(x_1,x_2,...,x_N)}
X=(x1,x2,...,xN),输出序列是
Y
=
(
y
1
,
y
2
,
.
.
.
,
y
M
)
\mathrm{Y=(y_{1},y_{2},...,y_{M})}
Y=(y1,y2,...,yM)。输入序列和输出序列的长度可以是变化的。
Encoder
编码器Encoder是一个RNN。

RNN隐层的状态按照这个公式计算:
h
t
=
f
(
h
t
−
1
,
x
t
)
\mathrm{h}_t=f(\mathrm{h}_{t-1},\mathrm{x}_t)
ht=f(ht−1,xt)
是
f
f
f一个非线性激活函数。
f
f
f即可以是一个简单的elemente-wise logistic sigmoid函数,也可以很复杂,如一个LSTM unit。最后一个时间步的输出是一个向量c。它是对整个输入序列做的一个总结(summary)得到的结果,即编码结果。
原文自己设计了cell,如图所示。

给出具体的计算如下。喂入Encoder的是一个词的序列,每个词是一个词向量embedding(该文的实验部分使用了维度为500的词向量),用
e
(
x
i
)
∈
R
500
\mathrm{e}(\mathrm{x}_i)\in\mathrm{R}^{500}
e(xi)∈R500表示。一个时间步的隐层包含1000个hidden unit(即隐层的输出是一个长度为1000维的向量)。时间步t的计算:
h t = z h t − 1 + ( 1 − z ) h ~ t \mathrm{h}_t=\mathbf{z}\mathrm{h}_{t-1}+(1-\mathbf{z})\tilde{\mathrm{h}}_t ht=zht−1+(1−z)h~t
其中:
h ~ t = tanh ( W e ( x t ) + U ( r ⊙ h t − 1 ) ) z = σ ( W z e ( x t ) + U z h t − 1 ) r = σ ( W r e ( x t ) + U r h t − 1 ) \tilde{\mathrm{h}}_{t}=\tanh\left(\boldsymbol{W}e(x_t)+\boldsymbol{U}(\boldsymbol{r}\odot\mathrm{h}_{t-1})\right)\\\mathbf{z}=\sigma(\boldsymbol{W}_\mathbf{z}e(x_t)+\boldsymbol{U}_\mathbf{z}\mathrm{h}_{t-1})\\\boldsymbol{r}=\sigma(\boldsymbol{W}_re(x_t)+\boldsymbol{U}_r\mathrm{h}_{t-1}) h~t=tanh(We(xt)+U(r⊙ht−1))z=σ(Wze(xt)+Uzht−1)r=σ(Wre(xt)+Urht−1)
e ( x t ) e(x_t) e(xt)是词对应的词向量。 σ \text{σ} σ是一个logistic sigmoid激活函数。 ⋅ ◯ \textcircled{\cdot} ⋅◯是一个逐个元素相乘运算符。计算时初始隐状态设为0。编码器的输出是:
c = tanh ( V h N ) c=\tanh{(Vh_N)} c=tanh(VhN)
矩阵W, U, V都是需要学习的参数。
Decoder
解码器Decoder也是一个RNN,它用于产生输出序列。Decoder RNN的隐状态是:
h ′ t = f ( h ′ t − 1 , y t − 1 , c ) \mathrm{h'}_t=f(\mathrm{h'}_{t-1},\mathrm{y}_{t-1},\mathrm{c}) h′t=f(h′t−1,yt−1,c)
符号’ 用于表示和Encoder RNN中的参数有区分。Decoder输出序列的第t个元素(例如一个词)的产生是一个条件分布
P ( y t ∣ y t − 1 , y t − 2 , . . . , y 1 , c ) = g ( h t ′ , y t − 1 , c ) \mathrm{P}(\mathrm{y}_t|\mathrm{y}_{t-1},\mathrm{y}_{t-2},...,\mathrm{y}_1,\mathrm{c})=\mathrm{g}(\mathrm{h}_t^{\prime},\mathrm{y}_{t-1},\mathrm{c}) P(yt∣yt−1,yt−2,...,y1,c)=g(ht′,yt−1,c)
激活函数 g g g必须能产生概率值。例如,可以是softmax。原文采用的Decoder计算过程如下:
Decoder RNN的初始状态是:
h 0 = tanh ( V ′ c ) \mathrm{h}_0=\tanh{(V^{\prime}c)} h0=tanh(V′c)
在时间步t的输出的计算如下:
h ′ t = z ′ h ′ t − 1 + ( 1 − z ′ ) h ′ ~ t \mathrm{h'}_t=\mathbf{z'h'}_{t-1}+(1-\mathbf{z'})\tilde{\mathrm{h'}}_t h′t=z′h′t−1+(1−z′)h′~t
其中
h ′ ~ t = tanh ( W ′ e ( y t − 1 ) + r ′ ⊙ ( U ′ h t − 1 ′ + C c ) ) z ′ = σ ( W z ′ e ( y t − 1 ) + U z ′ h t − 1 ′ + C z c ) r ′ = σ ( W r ′ e ( y t − 1 ) + U r ′ h t − 1 ′ + C r c ) \begin{aligned}\tilde{\mathrm{h'}}_{t}&=\tanh\left(\mathbf{W}^{'}e(\mathbf{y}_{t-1})+\boldsymbol{r}^{'}\odot(\mathbf{U}^{'}\mathbf{h}_{t-1}^{'}+\boldsymbol{C}c)\right)\\\mathbf{z}^{\prime}&=\sigma\left(\mathbf{W}_z^{\prime}e(\mathbf{y}_{t-1})+\mathbf{U}_z^{\prime}\mathbf{h}_{t-1}^{\prime}+\boldsymbol{C}_z\boldsymbol{c}\right)\\\boldsymbol{r}^{\prime}&=\sigma\left(\mathbf{W}_r^{\prime}e(\mathbf{y}_{t-1})+\mathbf{U}_r^{\prime}\mathbf{h}_{t-1}^{\prime}+\boldsymbol{C}_r\boldsymbol{c}\right)\end{aligned} h′~tz′r′=tanh(W′e(yt−1)+r′⊙(U′ht−1′+Cc))=σ(Wz′e(yt−1)+Uz′ht−1′+Czc)=σ(Wr′e(yt−1)+Ur′ht−1′+Crc)
e
(
y
t
−
1
)
\mathrm{e(y_{t-1})}
e(yt−1)表示是t-1步预测的输出的词的词向量。
e
(
y
0
)
\mathrm{e}(\mathrm{y}_0)
e(y0)是一个全部值为0的向量。
在每个时间步,decoder计算产生词j的概率:
P ( y t , j = 1 ∣ y t − 1 , y t − 2 , . . . , y 1 , X ) = exp ( G j s t ) ∑ j ′ = 1 K exp ( G j ′ s t ) \mathrm{P}(\mathrm{y}_{t,j}=1|\mathrm{y}_{t-1},\mathrm{y}_{t-2},...,\mathrm{y}_1,\boldsymbol{X})=\frac{\exp\left(\boldsymbol{G}_j\boldsymbol{s}_t\right)}{\sum_{j^{\prime}=1}^K\exp\left(\boldsymbol{G}_j^{\prime}\boldsymbol{s}_t\right)} P(yt,j=1∣yt−1,yt−2,...,y1,X)=∑j′=1Kexp(Gj′st)exp(Gjst)
X表示词汇表, G ∈ R K × d G\in R^{K\times d} G∈RK×d是权重矩阵,K是词汇表的大小,d是向量s的维度。 G j \mathbf{G}_j Gj是权重矩阵的一行。 s t s_t st的计算如下:
s t = max { s t , 2 i − 1 ′ , s t , 2 i ′ } s_t=\max\{s_{t,2i-1}^{'},s_{t,2i}^{'}\} st=max{st,2i−1′,st,2i′}
这个操作叫max-out,就像是pooling操作,输出的 s t s_t st的向量长度是 s t ′ {s_t}^{'} st′的一半。
其中:
s t ′ = O h h t ′ + O y e ( y t − 1 ) + O c c s_t^{\prime}=O_hh_t^{\prime}+O_ye(y_{t-1})+O_cc st′=Ohht′+Oye(yt−1)+Occ
可以看到,Decoder在计算输出序列的一个词时,采用了解码器当前时间步的隐状态 h t ′ h^{\prime}_t ht′,序列上一个词 y t − 1 \mathrm{y}_{t-1} yt−1的表示向量和Encoder输出的context c c c。
实例:实现一个翻译模型
建立一个字符级的英-中翻译模型。
要得到一个翻译模型,它包含两个阶段:一个是训练阶段;一个是推断阶段。第一阶段建立的模型结构与第二阶段的不完全一致。而是推断阶段使用第一阶段训练好的模型组件,搭建一个可以进行翻译的模型。下图是一个训练阶段的模型结构。

这是一个字符级的LSTM Encoder-Decoder模型。Encoder是一个LSTM模型,对输入数据(英文字符加符号)进行编码。Decoder是一个基于LSTM的语言模型,根据输入预测下一个中文字符或符号。实例的翻译模型很简单,只为说明怎样使用Encoder-Decoder模型。
实现程序包括:数据集准备、模型的训练和推断(inference)三个部分。
1. 数据集准备
网站http://www.manythings.org/anki/提供了许多语言翻译成英语的预料库。比如,法语-英语、中文-英语。下面的模型都可以适用。本例子中,使用一个英-中数据集。
数据集被处理成三个部分:Encoder的输入encoder_inputs,Decoder的输入decoder_inputs和Decoder的输出decoder_target。
Encoder的输入数据将英文文本按照字符序列来处理。每个字符用one-hot编码。因此encoder_inputs是一个3d的tensor,它的shape是[样本数,最长序列长度,英文字符表大小]。每个英文样本按照最长样本字符长度用空格符进行补齐。
Decoder的输入是将中文文本按照字符序列来处理。同样每个字符用one-hot编码。在输入的中文文本补充一个开始字符’\t’和一个结束符’\n’,同样每个中文文样本按照最长样本字符长度用空格符进行补齐。
Decoder是一个语言模型,因此Decoder的输出的目标数据是Decoder输入数据的左移一位。见上图。
2. 建立模型和训练模型
模型包括Encoder和Decoder。其中Encoder就是一个LSTM模型,我们只需获取最后一个时间步的输出。Decoder在训练阶段是按照一个语言模型来训练。即输入是一个字符的序列,目标数据是错开一位的输入数据。例如,输入是‘hello’,目标数据是‘ello<结束符>’。
建立模型和训练模型的程序如下,
encoder_inputs = Input(shape=(None, num_encoder_tokens))
encoder = LSTM(latent_dim, return_state=True)
encoder_outputs, state_h, state_c = encoder(encoder_inputs)
encoder_states = [state_h, state_c]
decoder_inputs = Input(shape=(None, num_decoder_tokens))
decoder_lstm = LSTM(latent_dim, return_sequences=True, return_state=True)
decoder_outputs, _, _ = decoder_lstm(decoder_inputs,
initial_state=encoder_states)
decoder_dense = Dense(num_decoder_tokens, activation='softmax')
decoder_outputs = decoder_dense(decoder_outputs)
model = Model([encoder_inputs, decoder_inputs], decoder_outputs)
model.compile(optimizer='rmsprop', loss='categorical_crossentropy',
metrics=['accuracy'])
model.fit([encoder_input_data, decoder_input_data], decoder_target_data,
batch_size=batch_size,
epochs=epochs,
validation_split=0.2)
model.save('s2s.h5')
Encoder模型的输出encoder_outputs后面没有使用。而它的两个状态encoder_states = [state_h, state_c],作为了decoder的初始状态。训练好的模型会被保存到磁盘。
3.推断(翻译过程)
进行推断的工作原理如下:

(1)LSTM每个时间步产生一个状态c和一个输出h。我们说,基于LSTM的encoder将输入序列编码成两个向量h和 c。
(2)在Decoder, 接收Encoder传递的向量h和c作为自己的状态向量的初始值。
(3)喂入Decoder的输入层一个开始符,本文我们用的是字符’\t’。Decoder将预测一个输出字符。
(4)将预测的字符再喂入Decoder的输入层,再产生一个输出字符。重复这个过程,直到产生了结束符,本文我们用的是’\n’,或者达到了设定的序列长度。
推断的核心是先搭建新的模型
encoder_model = Model(encoder_inputs, encoder_states)
decoder_state_input_h = Input(shape=(latent_dim,))
decoder_state_input_c = Input(shape=(latent_dim,))
decoder_states_inputs = [decoder_state_input_h, decoder_state_input_c]
decoder_outputs, state_h, state_c = decoder_lstm(
decoder_inputs, initial_state=decoder_states_inputs)
decoder_states = [state_h, state_c]
decoder_outputs = decoder_dense(decoder_outputs)
decoder_model = Model(
[decoder_inputs] + decoder_states_inputs,
[decoder_outputs] + decoder_states)
这段程序非常有意思的是,它利用前面的模型的构件,搭建新的模型。这些构件已经训练好了。新的模型encoder_model = Model(encoder_inputs, encoder_states)
就是前面的:
encoder_inputs = Input(shape=(None, num_encoder_tokens))
encoder = LSTM(latent_dim, return_state=True)
encoder_outputs, state_h, state_c = encoder(encoder_inputs)
encoder_states = [state_h, state_c]
而新的模型decoder_model用到了前面定义的层decoder_dense和decoder_lstm。模型的参数不变,但喂入模型的状态数据不同。
进行推断是一个函数decode_sequence,如下。此处把模型简化了,一次喂入decoder的只有一个one-hot编码了的字符,它的tensor的shape=(1,1,字符集大小)。得到的输出包括,一个预测的字符和状态值。再下一趟的预测时,把预测的字符和状态值作为了模型的输入。即一个字符一个字符进行预测,然后拼接成翻译的句子。
def decode_sequence(input_seq):
states_value = encoder_model.predict(input_seq)
target_seq = np.zeros((1, 1, num_decoder_tokens))
target_seq[0, 0, target_token_index['\t']] = 1. # one-hot编码起始字符’\t’
stop_condition = False
decoded_sentence = ''
while not stop_condition:
output_tokens, h, c = decoder_model.predict(
[target_seq] + states_value)
sampled_token_index = np.argmax(output_tokens[0, -1, :])
sampled_char = reverse_target_char_index[sampled_token_index]
decoded_sentence += sampled_char
if (sampled_char == '\n' or
len(decoded_sentence) > max_decoder_seq_length):
stop_condition = True
target_seq = np.zeros((1, 1, num_decoder_tokens))
target_seq[0, 0, sampled_token_index] = 1. #one-hot编码上一个预测的字符,作为下一个时间步的输入
states_value = [h, c]
return decoded_sentence
使用decode_sequence函数进行测试的程序如下:
for seq_index in range(100):
input_seq = encoder_input_data[seq_index: seq_index + 1]
decoded_sentence = decode_sequence(input_seq)
print('-')
print('Input sentence:', input_texts[seq_index])
print('Decoded sentence:', decoded_sentence)
下面是列出的一些翻译结果:
Input sentence: Try it.
Decoded sentence: 试试吧。
Input sentence: We try.
Decoded sentence: 我们来试试。
Input sentence: Why me?
Decoded sentence: 为什么是我?
Input sentence: Ask Tom.
Decoded sentence: 去问汤姆。
Input sentence: Awesome!
Decoded sentence: 好棒!
可以看到有些翻译的可以,有些还是不准。模型还是有很大的改善的空间。
1万+



被折叠的 条评论
为什么被折叠?



