睿智的seq2seq模型4——往英文到法文的翻译里加上注意力机制
学习前言
既然学了注意力机制,也学了文献翻译,那不如,联通一下吧?
什么是注意力机制
假设我们要翻译一句话:
打电脑游戏。
也就是play computer game。
如果不引入注意力机制,那么我们从Encoder获得语义编码c之后,这个语义编码在Decoder中传递,其内容就和Encoder无关了。
但是事实上我们希望在翻译
打电脑游戏
中的
打
的时候,我们更注意
打->play
的转换,此时我们希望Decoder更加注意Encoder从
打
中提取出来的特征。
这就是注意力机制的概念,它的本意是
让神经网络模型在做特定的事的时候可以注意到它需要注意的地方。
由于神经网络是一堆数字的传递,每个事物的特征也是由一堆数字组成的,比如
打
字的特征也是一堆数字,
电脑
的特征也是一堆数字,
游戏
的特征也是一堆数字,语义编码c就是这么多特征的组合。
那么如何使得
神经网络模型对某个内容进行注意呢
?其实就是将改变不同内容的权重,当我们需要神经网络注意到
打
的时候,我们只需要提高
打
字的特征的权重就可以了。
假设函数 f 可以用于提取特征,函数 g 可以实现解码。那么如果我们要神经网络注意到
打
,可以通过如下方式进行。
C
p
l
a
y
=
g
(
0.8
∗
f
(
打
)
,
0.1
∗
f
(
电
脑
)
,
0.1
∗
f
(
游
戏
)
)
C_{play} = g(0.8*f(打),0.1*f(电脑),0.1*f(游戏))
C
p
l
a
y
=
g
(
0
.
8
∗
f
(
打
)
,
0
.
1
∗
f
(
电
脑
)
,
0
.
1
∗
f
(
游
戏
)
)
如何将注意力机制应用到翻译中
如上述所示,想要将注意力机制应用到翻译中,其
核心重点就是将输入的字符和输出的字符建立映射。
还是以
打电脑游戏
为例,此时我们的输入可以分为三个部分,分别是
打、电脑、游戏
。
输出是三个部分,分别是
play、computer、game
。
我们需要分别在翻译这三个部分的时候,
打、电脑、游戏
对翻译的贡献。
用图片来演示就是这样。
利用LSTM可以
获得打、电脑、游戏的特征
,分别命名为特征1、特征2、特征3。
特征1
代表了打的特征、
特征2
代表了
电脑
的特征、
特征3
代表了
游戏
的特征。
当我们需要翻译出play的时候,我们需要使得特征1的权重较大,特征2、特征3的权重较小。
当我们需要翻译出computer的时候,我们需要使得特征2的权重较大,特征1、特征3的权重较小。
当我们需要翻译出game的时候,我们需要使得特征3的权重较大,特征1、特征2的权重较小。
实际上就是通过调整上述图像中,特征1、2、3到STEP1、2、3的权值,从而实现注意力机制。
英文翻译到法文的思路
1、对英文进行特征提取
将英文按顺序输入到LSTM中,LSTM会对英文进行特征提取。
我们知道LSTM每一个STEP都会有一个输出,因此我们将
每一个STEP的输出作为当前输入的特征值。
每一个STEP的输入都有一个对应的特征值。
实现代码为(这里我将LSTM函数换成了CuDNNLSTM函数,因为我装了TensorflowGPU,会运算的更快,二者除了运算速度的差别,没有什么其它的差别。):
encoder_inputs = Input(shape=(None, num_encoder_tokens))
x_encoder, _, _ = CuDNNLSTM(latent_dim,return_sequences=True, return_state=True)(encoder_inputs)
x_encoder = Dropout(0.5)(x_encoder)
x_encoder, state_h, state_c = CuDNNLSTM(latent_dim,return_sequences=True, return_state=True)(encoder_inputs)
x_encoder = Dropout(0.5)(x_encoder)
2、将提取到的特征传入到decoder
通过第一步,我们可以获得每个STEP的特征,按照顺序传入到decoder中。
3、将”\t”作为起始符预测第一个字母
当我们将所有特征传入到decoder中,我们还需要计算
每个特征对不同的输出的贡献权重。
当我们把
将”\t”作为起始符预测第一个字母时,我们需要计算每个特征对这个输出的贡献权重。
我们采用点乘的方式进行贡献权重的计算。
然后利用特征和贡献权重计算出进入到decoder中的特征,并与decoder利用自身输入得到的特征进行结合,实现预测。
在本例子中,求出了
四个特征对这个输出的贡献权重,从而计算出进入到decoder中的特征
,然后将”\t”传入到LSTM中
获得自身输入得到的特征
,将两个特征结合后,进入到新的一个LSTM中,再经过
全连接层进行预测。
4、逐个字母向后传递进行预测
接下来就是继续求取
四个特征对这个输出的贡献权重,从而计算出进入到decoder中的特征
。
然后将
上一轮求出的字符
传入到LSTM中
获得自身输入得到的特征
,将两个特征结合后,进入到新的一个LSTM中,再经过
全连接层进行预测。
以此类推。
以此类推,当出现”\n”的时候,表示结尾了!
神经网络部分实现代码如下:
encoder_inputs = Input(shape=(None, num_encoder_tokens))
x_encoder, _, _ = CuDNNLSTM(latent_dim,return_sequences=True, return_state=True)(encoder_inputs)
x_encoder = Dropout(0.5)(x_encoder)
x_encoder, state_h, state_c = CuDNNLSTM(latent_dim,return_sequences=True, return_state=True)(encoder_inputs)
x_encoder = Dropout(0.5)(x_encoder)
decoder_inputs = Input(shape=(None, num_decoder_tokens))
x_decoder = CuDNNLSTM(latent_dim,return_sequences=True)(decoder_inputs)
x_decoder = Dropout(0.5)(x_decoder)
# Attention
attention = Dot(axes=[2, 2])([x_decoder, x_encoder])
attention = Activation('softmax')(attention)
context = Dot(axes=[2, 1])([attention, x_encoder])
decoder_combined_context = Concatenate(axis=-1)([context, x_decoder])
x_decoder = CuDNNLSTM(int(latent_dim/2),return_sequences=True)(decoder_combined_context)
x_decoder = Dropout(0.5)(x_decoder)
# Output
decoder_dense = Dense(num_decoder_tokens, activation='softmax')
decoder_outputs = decoder_dense(x_decoder)
model = Model([encoder_inputs, decoder_inputs], decoder_outputs)
全部代码实现
训练集在这里下载:
http://www.manythings.org/anki/fra-eng.zip
英文到法文的翻译可通过如下的代码实现。
from __future__ import print_function
from keras.models import Model
from keras.layers import Input, CuDNNLSTM, Dense,TimeDistributed
from keras.layers import Convolution1D, Dot, Activation, Concatenate, Dropout
from keras.models import Model
import numpy as np
import keras.backend as K
def get_dataset(data_path, num_samples):
input_texts = []
target_texts = []
input_characters = set()
target_characters = set()
with open(data_path, 'r', encoding='utf-8') as f:
lines = f.read().split('\n')
for line in lines[: min(num_samples, len(lines) - 1)]:
input_text, target_text, _ = line.split('\t')
# 用tab作用序列的开始,用\n作为序列的结束
target_text = '\t' + target_text + '\n'
input_texts.append(input_text)
target_texts.append(target_text)
for char in input_text:
if char not in input_characters:
input_characters.add(char)
for char in target_text:
if char not in target_characters:
target_characters.add(char)
return input_texts,target_texts,input_characters,target_characters
#------------------------------------------#
# init初始化部分
#------------------------------------------#
# 每一次输入64个batch
batch_size = 64
# 训练一百个世代
epochs = 100
# 256维特征向量
latent_dim = 128
# 一共10000个样本
num_samples = 10000
# 读取数据集
data_path = 'fra.txt'
# 获取数据集
# 其中input_texts为输入的英文字符串
# target_texts为对应的法文字符串
# input_characters用到的所有输入字符,如a,b,c,d,e,……,.,!等
# target_characters用到的所有输出字符
input_texts,target_texts,input_characters,target_characters = get_dataset(data_path, num_samples)
# 对字符进行排序
input_characters = sorted(list(input_characters))
target_characters = sorted(list(target_characters))
# 计算共用到了什么字符
num_encoder_tokens = len(input_characters)
num_decoder_tokens = len(target_characters)
# 计算出最长的序列是多长
max_encoder_seq_length = max([len(txt) for txt in input_texts])
max_decoder_seq_length = max([len(txt) for txt in target_texts])
print('一共有多少训练样本:', len(input_texts))
print('多少个英文字母:', num_encoder_tokens)
print('多少个法文字母:', num_decoder_tokens)
print('最大英文序列:', max_encoder_seq_length)
print('最大法文序列:', max_decoder_seq_length)
# 建立字母到数字的映射
input_token_index = dict(
[(char, i) for i, char in enumerate(input_characters)])
target_token_index = dict(
[(char, i) for i, char in enumerate(target_characters)])
#---------------------------------------------------------------------------#
#--------------------------------------#
# 改变数据集的格式
#--------------------------------------#
encoder_input_data = np.zeros(
(len(input_texts), max_encoder_seq_length, num_encoder_tokens),
dtype='float32')
decoder_input_data = np.zeros(
(len(input_texts), max_decoder_seq_length, num_decoder_tokens),
dtype='float32')
decoder_target_data = np.zeros(
(len(input_texts), max_decoder_seq_length, num_decoder_tokens),
dtype='float32')
for i, (input_text, target_text) in enumerate(zip(input_texts, target_texts)):
# 为末尾加上" "空格
for t, char in enumerate(input_text):
encoder_input_data[i, t, input_token_index[char]] = 1.
encoder_input_data[i, t + 1:, input_token_index[' ']] = 1.
# 相当于前一个内容的识别结果,作为输入,传入到解码网络中
for t, char in enumerate(target_text):
decoder_input_data[i, t, target_token_index[char]] = 1.
if t > 0:
# decoder_target_data不包括第一个tab
decoder_target_data[i, t - 1, target_token_index[char]] = 1.
decoder_input_data[i, t + 1:, target_token_index[' ']] = 1.
decoder_target_data[i, t:, target_token_index[' ']] = 1.
#---------------------------------------------------------------------------#
encoder_inputs = Input(shape=(None, num_encoder_tokens))
x_encoder, _, _ = CuDNNLSTM(latent_dim,return_sequences=True, return_state=True)(encoder_inputs)
x_encoder = Dropout(0.5)(x_encoder)
x_encoder, state_h, state_c = CuDNNLSTM(latent_dim,return_sequences=True, return_state=True)(encoder_inputs)
x_encoder = Dropout(0.5)(x_encoder)
decoder_inputs = Input(shape=(None, num_decoder_tokens))
x_decoder = CuDNNLSTM(latent_dim,return_sequences=True)(decoder_inputs)
x_decoder = Dropout(0.5)(x_decoder)
# Attention
attention = Dot(axes=[2, 2])([x_decoder, x_encoder])
attention = Activation('softmax')(attention)
context = Dot(axes=[2, 1])([attention, x_encoder])
decoder_combined_context = Concatenate(axis=-1)([context, x_decoder])
x_decoder = CuDNNLSTM(int(latent_dim/2),return_sequences=True)(decoder_combined_context)
x_decoder = Dropout(0.5)(x_decoder)
# Output
decoder_dense = Dense(num_decoder_tokens, activation='softmax')
decoder_outputs = decoder_dense(x_decoder)
model = Model([encoder_inputs, decoder_inputs], decoder_outputs)
model.summary()
# Run training
model.compile(optimizer='adam', 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)
# Save model
model.save('cnn_s2s.h5')
# 求字符到序号的映射
reverse_input_char_index = dict(
(i, char) for char, i in input_token_index.items())
reverse_target_char_index = dict(
(i, char) for char, i in target_token_index.items())
index = np.random.randint(0,9999,100)
in_encoder = encoder_input_data[index]
in_decoder = np.zeros(
(len(in_encoder), max_decoder_seq_length, num_decoder_tokens),
dtype='float32')
in_decoder[:, 0, target_token_index["\t"]] = 1
for i in range(max_decoder_seq_length - 1):
predict = model.predict([in_encoder, in_decoder])
predict = predict.argmax(axis=-1)
predict_ = predict[:, i].ravel().tolist()
for j, x in enumerate(predict_):
in_decoder[j, i + 1, x] = 1
for seq_index in range(100):
output_seq = predict[seq_index, :].ravel().tolist()
decoded_sentence = ""
for x in output_seq:
if reverse_target_char_index[x] == "\n":
break
else:
decoded_sentence+=reverse_target_char_index[x]
print('-')
print('Input sentence:', input_texts[index[seq_index]])
print('Decoded sentence:', decoded_sentence)
实现效果:
Input sentence: It looks good.
Decoded sentence: Ça a m'esprisé.
-
Input sentence: You're young.
Decoded sentence: Vous êtes malins.
-
Input sentence: How weird!
Decoded sentence: Comment allez !
-
Input sentence: I want to live.
Decoded sentence: Je veux un la.