欢迎来到本周第一个作业。你将建立一个将人类可读日期(“2009年6月25日”)转换为机器可读日期(“2009-06-25”)的神经机器翻译(NMT)模型。 你将使用注意力机制来执行此操作,这是模型序列中最尖端的一个序列。
from keras.layers import Bidirectional, Concatenate, Permute, Dot, Input, LSTM, Multiply
from keras.layers import RepeatVector, Dense, Activation, Lambda
from keras.optimizers import Adam
from keras.utils import to_categorical
from keras.models import load_model, Model
import keras.backend as K
import numpy as np
from faker import Faker
import random
from tqdm import tqdm
from babel.dates import format_date
from nmt_utils import *
import matplotlib.pyplot as plt
%matplotlib inline
你将创建的模型可用于从一种语言翻译为另一种语言,如从英语翻译为印地安语。 但是,语言翻译需要大量的数据集,并且通常需要几天的GPU训练。 在不使用海量数据的情况下,为了让你有机会尝试使用这些模型,我们使用更简单的“日期转换”任务。
网络以各种可能格式(例如“1958年8月29日”,“03/30/1968”,“1987年6月24日”)写成的日期作为输入,并将它们转换成标准化的机器可读的日期(例如“1958 -08-29“,”1968-03-30“,”1987-06-24“),让网络学习以通用机器可读格式YYYY-MM-DD输出日期。
我们将在一个包含10000个可读日期的数据集及其等效的标准化机器可读日期上训练模型。 执行以下命令加载数据并查看一些示例。
m = 10000
dataset, human_vocab, machine_vocab, inv_machine_vocab = load_dataset(m)
dataset[:10]
# [('9 may 1998', '1998-05-09'),
# ('10.09.70', '1970-09-10'),
# ('4/28/90', '1990-04-28'),
# ('thursday january 26 1995', '1995-01-26'),
# ('monday march 7 1983', '1983-03-07'),
# ('sunday may 22 1988', '1988-05-22'),
# ('tuesday july 8 2008', '2008-07-08'),
# ('08 sep 1999', '1999-09-08'),
# ('1 jan 1981', '1981-01-01'),
# ('monday may 22 1995', '1995-05-22')]
其中
预处理数据并将原始文本数据映射到索引。 我们还将使用 Tx=30 T x = 30 (假设它是人类可读日期的最大长度;如果得到更长的输入,将不得不截断它)并且 Ty=10 T y = 10 (因为“YYYY-MM-DD”是10个字符)
Tx = 30
Ty = 10
X, Y, Xoh, Yoh = preprocess_data(dataset, human_vocab, machine_vocab, Tx, Ty)
print("X.shape:", X.shape)
print("Y.shape:", Y.shape)
print("Xoh.shape:", Xoh.shape)
print("Yoh.shape:", Yoh.shape)
# X.shape: (10000, 30)
# Y.shape: (10000, 10)
# Xoh.shape: (10000, 30, 37)
# Yoh.shape: (10000, 10, 11)
目前已有:
这里,len(machine_vocab)= 11,因为有11个字符(’ - ‘以及0-9)。
查看预处理数据
index = 0
print("Source date:", dataset[index][0])
print("Target date:", dataset[index][1])
print()
print("Source after preprocessing (indices):", X[index])
print("Target after preprocessing (indices):", Y[index])
print()
print("Source after preprocessing (one-hot):", Xoh[index])
print("Target after preprocessing (one-hot):", Yoh[index])
# Source date: 9 may 1998
# Target date: 1998-05-09
#
# Source after preprocessing (indices): [12 0 24 13 34 0 4 12 12 11 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36
# 36 36 36 36 36]
# Target after preprocessing (indices): [ 2 10 10 9 0 1 6 0 1 10]
#
# Source after preprocessing (one-hot): [[ 0. 0. 0. ..., 0. 0. 0.]
# [ 1. 0. 0. ..., 0. 0. 0.]
# [ 0. 0. 0. ..., 0. 0. 0.]
# ...,
# [ 0. 0. 0. ..., 0. 0. 1.]
# [ 0. 0. 0. ..., 0. 0. 1.]
# [ 0. 0. 0. ..., 0. 0. 1.]]
# Target after preprocessing (one-hot): [[ 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
# [ 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
# [ 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
# [ 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
# [ 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
# [ 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]
如果你需要将一段法文翻译成英文,你不会阅读整段然后关闭书本再翻译。 即使在翻译过程中,您也会阅读/重读,并专注于你正在翻译的部分。
注意力机制告诉神经翻译模型什么时候应该关注什么。
在这一部分,你将会实现注意力机制,图中展示了注意力机制的工作原理。上图是注意力机制的模型,下图是每步注意力变量 α<t,t′> α < t , t ′ > 的计算,该变量将用于计算每步输出的 context<t> c o n t e x t < t > , 其中 (t = 1, … , Ty T y )
下面是关于模型的一些属性说明:
我们将在下面解释如何在Keras中使用RepeatVector和Concatenation。
下面实现模型,首先需要实现one_step_attention() 和 model()。
1 one_step_attention(): 在第t个时间步,利用Bi-LSTM的所有隐藏状态 ([a<1>,a<2>,...,a<Tx>]) ( [ a < 1 > , a < 2 > , . . . , a < T x > ] ) 和第二个LSTM的之前的隐藏状态( s<t−1> s < t − 1 > ),函数 one_step_attention() 可以计算出注意力权重 ([α<t,1>,α<t,2>,...,α<t,Tx>]) ( [ α < t , 1 > , α < t , 2 > , . . . , α < t , T x > ] )
然后输出上下文向量(见图二)
注意这里我们使用 context<t> c o n t e x t < t > 来表示注意力,在视频课程中我们使用的是 c<t> c < t > ;这里使用 context<t> c o n t e x t < t > 是为了避免与post-attention LSTM的内存单元变量混淆,它有时候会使用 c<t> c < t > 。
2 model(): 实现了完整的模型。首先执行Bi-LSTM得到 [a<1>,a<2>,...,a<Tx>] [ a < 1 > , a < 2 > , . . . , a < T x > ] ,
然后在for循环中执行 Ty T y 次one_step_attention()。
每次迭代都是利用 c<t> c < t > 计算下一个LSTM 并通过softmax层产生预测值 ŷ <t> y ^ < t > 。
model() 将会在for循环中执行Ty次one_step_attention(),重要的是 Ty T y 的每个副本都具有相同的权重,换言之, Ty T y 的所有时间步共享权重。
下面是如何在Keras中利用共享权重进行实现层
1. 定义层对象(例如全局变量)
2. 前向传播时调用这些对象
我们已经将你需要的层对象都定义为全局变量了。执行下面代码进行创建,可以查询Keras文档来理解这些层: RepeatVector(), Concatenate(), Dense(), Activation(), Dot()
# Defined shared layers as global variables
repeator = RepeatVector(Tx)
concatenator = Concatenate(axis=-1)
densor1 = Dense(10, activation = "tanh")
densor2 = Dense(1, activation = "relu")
activator = Activation(softmax, name='attention_weights') # We are using a custom softmax(axis = 1) loaded in this notebook
dotor = Dot(axes = 1)
现在可以用这些层实现 one_step_attention() 了。为了在各层传播Keras 的对象X,调用layer(X) (或者layer([X, Y])如果需要多个输入的话),densor(X) 将通过上面定义的Denser(1)层进行传播。
# GRADED FUNCTION: one_step_attention
def one_step_attention(a, s_prev):
"""
Performs one step of attention: Outputs a context vector computed as a dot product of the attention weights
"alphas" and the hidden states "a" of the Bi-LSTM.
Arguments:
a -- hidden state output of the Bi-LSTM, numpy-array of shape (m, Tx, 2*n_a)
s_prev -- previous hidden state of the (post-attention) LSTM, numpy-array of shape (m, n_s)
Returns:
context -- context vector, input of the next (post-attetion) LSTM cell
"""
### START CODE HERE ###
# Use repeator to repeat s_prev to be of shape (m, Tx, n_s) so that you can concatenate it with all hidden states "a" (≈ 1 line)
s_prev = repeator(s_prev)
# Use concatenator to concatenate a and s_prev on the last axis (≈ 1 line)
concat = concatenator([a, s_prev])
# Use densor1 to propagate concat through a small fully-connected neural network to compute the "intermediate energies" variable e. (≈1 lines)
e = densor1(concat)
# Use densor2 to propagate e through a small fully-connected neural network to compute the "energies" variable energies. (≈1 lines)
energies = densor2(e)
# Use "activator" on "energies" to compute the attention weights "alphas" (≈ 1 line)
alphas = activator(energies)
# Use dotor together with "alphas" and "a" to compute the context vector to be given to the next (post-attention) LSTM-cell (≈ 1 line)
context = dotor([alphas, a])
### END CODE HERE ###
return context
完成 model() 代码后再来检查 one_step_attention() 的输出。
n_a = 32
n_s = 64
post_activation_LSTM_cell = LSTM(n_s, return_state = True)
output_layer = Dense(len(machine_vocab), activation=softmax)
现在你可以在for循环中使用Ty次这些层来生成输出了,它们的参数将不会重新初始化。
# GRADED FUNCTION: model
def model(Tx, Ty, n_a, n_s, human_vocab_size, machine_vocab_size):
"""
Arguments:
Tx -- length of the input sequence
Ty -- length of the output sequence
n_a -- hidden state size of the Bi-LSTM
n_s -- hidden state size of the post-attention LSTM
human_vocab_size -- size of the python dictionary "human_vocab"
machine_vocab_size -- size of the python dictionary "machine_vocab"
Returns:
model -- Keras model instance
"""
# Define the inputs of your model with a shape (Tx,)
# Define s0 and c0, initial hidden state for the decoder LSTM of shape (n_s,)
X = Input(shape=(Tx, human_vocab_size))
s0 = Input(shape=(n_s,), name='s0')
c0 = Input(shape=(n_s,), name='c0')
s = s0
c = c0
# Initialize empty list of outputs
outputs = []
### START CODE HERE ###
# Step 1: Define your pre-attention Bi-LSTM. Remember to use return_sequences=True. (≈ 1 line)
a = Bidirectional(LSTM(n_a, return_sequences=True))(X)
# Step 2: Iterate for Ty steps
for t in range(Ty):
# Step 2.A: Perform one step of the attention mechanism to get back the context vector at step t (≈ 1 line)
context = one_step_attention(a, s)
# Step 2.B: Apply the post-attention LSTM cell to the "context" vector.
# Don't forget to pass: initial_state = [hidden state, cell state] (≈ 1 line)
s, _, c = post_activation_LSTM_cell(context, initial_state = [s, c])
# Step 2.C: Apply Dense layer to the hidden state output of the post-attention LSTM (≈ 1 line)
out = output_layer(s)
# Step 2.D: Append "out" to the "outputs" list (≈ 1 line)
outputs.append(out)
# Step 3: Create model instance taking three inputs and returning the list of outputs. (≈ 1 line)
model = Model(inputs=[X,s0,c0],outputs=outputs)
### END CODE HERE ###
return model
执行下面代码创建模型
model = model(Tx, Ty, n_a, n_s, len(human_vocab), len(machine_vocab))
执行如下代码查看总结概况是否与期待输出一致。
model.summary()
期待的输出
key | value |
---|---|
Total params: | 52,960 |
Trainable params: | 52,960 |
Non-trainable params: | 0 |
bidirectional_1’s output shape | (None, 30, 64) |
repeat_vector_1’s output shape | (None, 30, 64) |
concatenate_1’s output shape | (None, 30, 128) |
attention_weights’s output shape | (None, 30, 1) |
dot_1’s output shape | (None, 1, 64) |
dense_3’s output shape | (None, 11) |
和往常一样,在Keras中创建模型之后,需要编译模型。定义损失函数,优化器和指标。使用 categorical_crossentropy 损失,Adam优化器(learning rate = 0.005, β1=0.9, β2=0.999, decay = 0.01) 和 [‘accuracy’]指标来编译模型。
### START CODE HERE ### (≈2 lines)
opt = Adam(lr=0.0005, beta_1=0.9, beta_2=0.999, decay=0.01)
model.compile(loss = 'categorical_crossentropy',optimizer=opt, metrics = ['accuracy'])
### END CODE HERE ###
最后一步是定义模型的输入和输出:
s0 = np.zeros((m, n_s))
c0 = np.zeros((m, n_s))
outputs = list(Yoh.swapaxes(0,1))
现在执行模型一个epoch。
model.fit([Xoh, s0, c0], outputs, epochs=1, batch_size=100)
# Epoch 1/1
# 10000/10000 [==============================] - 41s - loss: 22.1165 - dense_3_loss_1: 2.3099 - dense_3_loss_2: 2.2119 - dense_3_loss_3: 2.3548 - dense_3_loss_4: 2.5886 - dense_3_loss_5: 1.7306 - dense_3_loss_6: 1.8726 - dense_3_loss_7: 2.6256 - dense_3_loss_8: 1.5889 - dense_3_loss_9: 2.0589 - dense_3_loss_10: 2.7747 - dense_3_acc_1: 0.0027 - dense_3_acc_2: 0.1505 - dense_3_acc_3: 0.0492 - dense_3_acc_4: 0.0064 - dense_3_acc_5: 0.9331 - dense_3_acc_6: 0.0184 - dense_3_acc_7: 0.0073 - dense_3_acc_8: 0.9440 - dense_3_acc_9: 0.0071 - dense_3_acc_10: 0.0039
dense_2_acc_8: 0.89 表示你正在预测输出的第7个字符,在当前batch中拥有89%的准确率。
训练时你可以看到每10个位置输出一次,下表是一个batch=2的例子。
你可以导入并使用我们已经训练了很长时间的模型。
model.load_weights('models/model.h5')
现在看看在新的样本上的结果:
EXAMPLES = ['3 May 1979', '5 April 09', '21th of August 2016', 'Tue 10 Jul 2007', 'Saturday May 9 2018', 'March 3 2001', 'March 3rd 2001', '1 March 2001']
for example in EXAMPLES:
source = string_to_int(example, Tx, human_vocab)
source = np.array(list(map(lambda x: to_categorical(x, num_classes=len(human_vocab)), source))).swapaxes(0,1)
prediction = model.predict([source, s0, c0])
prediction = np.argmax(prediction, axis = -1)
output = [inv_machine_vocab[int(i)] for i in prediction]
print("source:", example)
print("output:", ''.join(output))
# source: 3 May 1979
# output: 1979-05-03
# source: 5 April 09
# output: 2009-05-05
# source: 21th of August 2016
# output: 2016-08-21
# source: Tue 10 Jul 2007
# output: 2007-07-10
# source: Saturday May 9 2018
# output: 2018-05-09
# source: March 3 2001
# output: 2001-03-03
# source: March 3rd 2001
# output: 2001-03-03
# source: 1 March 2001
# output: 2001-03-01
您也可以您自己的示例进行测试。下一部分将更直观的认识到注意机制正在做什么。例如,当生成一个特定的输出字符时,网络正在关注哪些输入部分。
由于输出的长度固定为10,因此可以使用10个不同的softmax单位执行此任务以生成输出的10个字符。注意力模型的一个优点是输出的每个部分(比如说月份)都知道它只需要依赖一小部分输入(输入的月份中的字符)。 我们可以看到输出的哪一部分正在查看输入的哪一部分。
考虑将“2018年5月9日星期六”翻译为“2018-05-09”的任务。 如果我们将计算出的 α⟨t,t′⟩ α ⟨ t , t ′ ⟩ 可视化,我们可以得到:
注意输出如何忽略输入的“星期六”部分。没有一个输出时间步对“星期六”部分有很多的注意力。 我们还看到,9被翻译为09,“May”被正确翻译成05,输出将注意力放在产生该输出所需的输入部分。 年份将大多的注意力都放在输入的“18”上以产生“2018”。
现在我们将网络中的激活函数进行可视化。我们将举一个例子贯穿整个网络,然后可视化 α⟨t,t′⟩ α ⟨ t , t ′ ⟩ 。
为了找出注意力的位置,我们先打印一下总结概况。
model.summary()
浏览 model.summary() 的输出。可以看到在每个时间步dot_2计算上下文向量之前 attention_weights 层输出了(m, 30, 1)的 α α 。让我们获取这一层的激活函数。
attention_map = plot_attention_map(model, human_vocab, inv_machine_vocab, "Tuesday 09 Oct 1993", num = 7, n_s = 64)
在生成的图上,您可以观察预测输出每个字符时的注意力权重值。 检查此图并检查网络注意力在哪里才对你有意义。
在这个日期转换应用中,你可以观察到大多数的注意力都帮助预测年,相对较少的注意力在日期和月份上。
恭喜你完成了这个作业!现在你可以实现一个注意力模型并使用它学习从一个序列到另一个序列的复杂映射了。