睿智的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(游戏))

如何将注意力机制应用到翻译中

如上述所示,想要将注意力机制应用到翻译中,其核心重点就是将输入的字符和输出的字符建立映射。

还是以打电脑游戏为例,此时我们的输入可以分为三个部分,分别是打、电脑、游戏

扫描二维码关注公众号,回复: 8917894 查看本文章

输出是三个部分,分别是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)(x_decoder)
x_decoder = Dropout(0.5)(x_decoder)
# Output
decoder_dense = Dense(num_decoder_tokens, activation='softmax')
decoder_outputs = decoder_dense(decoder_combined_context)

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)(x_decoder)
x_decoder = Dropout(0.5)(x_decoder)
# Output
decoder_dense = Dense(num_decoder_tokens, activation='softmax')
decoder_outputs = decoder_dense(decoder_combined_context)

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.
发布了167 篇原创文章 · 获赞 112 · 访问量 24万+

猜你喜欢

转载自blog.csdn.net/weixin_44791964/article/details/104016227