吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(一):机器翻译

Part 1: 机器翻译

欢迎来到本周第一个作业。你将建立一个将人类可读日期(“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

1 将人类可读日期翻译为机器可读日期

你将创建的模型可用于从一种语言翻译为另一种语言,如从英语翻译为印地安语。 但是,语言翻译需要大量的数据集,并且通常需要几天的GPU训练。 在不使用海量数据的情况下,为了让你有机会尝试使用这些模型,我们使用更简单的“日期转换”任务。

网络以各种可能格式(例如“1958年8月29日”,“03/30/1968”,“1987年6月24日”)写成的日期作为输入,并将它们转换成标准化的机器可读的日期(例如“1958 -08-29“,”1968-03-30“,”1987-06-24“),让网络学习以通用机器可读格式YYYY-MM-DD输出日期。

1.1 数据集

我们将在一个包含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')]

其中

  • dataset:一个二元组列表(人类可读日期,机器可读日期)
  • human_vocab: 人类可读日期中使用到的所有字符到字典索引值的映射
  • machine_vocab: 机器可读日期中使用到的所有字符到字典索引值的映射(与human_vocab的索引没必要完全一致)
  • inv_machine_vocab: machine_vocab 的翻转映射,从索引映射到字符

预处理数据并将原始文本数据映射到索引。 我们还将使用 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)

目前已有:

  • X: 经过处理的训练集中人类可读日期,其中每个字符都替换为其在human_vocab中映射到的索引。 每个日期用特殊字符进一步填充为 Tx T x 长度。 X.shape =(m, Tx T x
  • Y: 经过处理的训练集中机器可读日期,其中每个字符都替换为其在machine_vocab中映射到的索引。 你应该有Y.shape =(m,Ty)。
  • Xoh:X的one-hot向量,Xoh.shape = (m,Tx,len(human_vocab))
  • Yoh:Y的one-hot向量,Yoh.shape = (m,Tx,len(machine_vocab))。

这里,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.]]

2 采用注意力机制的机器翻译

如果你需要将一段法文翻译成英文,你不会阅读整段然后关闭书本再翻译。 即使在翻译过程中,您也会阅读/重读,并专注于你正在翻译的部分。

注意力机制告诉神经翻译模型什么时候应该关注什么。

2.1 注意力机制

在这一部分,你将会实现注意力机制,图中展示了注意力机制的工作原理。上图是注意力机制的模型,下图是每步注意力变量 α<t,t> α < t , t ′ > 的计算,该变量将用于计算每步输出的 context<t> c o n t e x t < t > , 其中 (t = 1, … , Ty T y )

图一

吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(一):机器翻译_第1张图片

下面是关于模型的一些属性说明:

  • 模型中有两个单独的LSTM。
    • 图片底部的是双向LSTM,处在关注机制之前,将其称为pre-attention Bi-LSTM。
    • 图片顶部的LSTM出现在关注机制之后,将其称为post-attention LSTM。
    • pre-attention Bi-LSTM经过 Tx T x 个时间步。
    • post-attention LSTM经历 Ty T y 个时间步。
  • post-attention LSTM将 st s ⟨ t ⟩ ct c ⟨ t ⟩ 从一个时间步传给下一个时间步。
    • 在视频课程中,我们仅使用了基本的RNN作为post-attention 序列模型,因此RNN输出激活状态为 st s ⟨ t ⟩
    • 这里我们使用LSTM,因此LSTM具有输出激活状态 st s ⟨ t ⟩ 和隐藏单元状态 ct c ⟨ t ⟩
    • 然而,与先前的文本生成示例(例如第1周的恐龙)不同,在该模型中,在时间t的post-activation LSTM不会将具体生成的 yt1 y ⟨ t − 1 ⟩ 作为输入; 而只是 st s ⟨ t ⟩ ct c ⟨ t ⟩ 作为输入。
    • 我们以这种方式设计了模型,因为(与邻近字符高度相关的语言生成不同),在YYYY-MM-DD日期中,前一个字符与下一个字符之间的依赖性不强。
  • 我们使用 a<t> a < t > 来表示pre-attention Bi-LSTM的前向激活函数和反向激活函数。

a<t>=[a<t>,a⃖ <t>] a < t > = [ a → < t > , a ← < t > ]

图二

吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(一):机器翻译_第2张图片

  • 使用RepeatVector节点复制 Tx T x s<t1> s < t − 1 > 的值,然后连接 s<t1> s < t − 1 > a<t> a < t > 来计算 e<t,t> e < t , t ′ > , 然后 e<t,t> e < t , t ′ > 通过softmax来计算 αtt α ⟨ t , t ′ ⟩

我们将在下面解释如何在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<t1> s < t − 1 > ),函数 one_step_attention() 可以计算出注意力权重 ([α<t,1>,α<t,2>,...,α<t,Tx>]) ( [ α < t , 1 > , α < t , 2 > , . . . , α < t , T x > ] )
然后输出上下文向量(见图二)

context<t>=t=0Txα<t,t>a<t> c o n t e x t < t > = ∑ t ′ = 0 T x α < t , t ′ > a < t ′ >

注意这里我们使用 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 >

练习:实现 one_step_attention()

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() 的输出。

练习:实现 model()
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次这些层来生成输出了,它们的参数将不会重新初始化。

  1. 将输入传给一个 Bidirectional LSTM
  2. 循环 for t = 0,…, Ty T y -1:
    1. [α<t,1>,α<t,2>,...,α<t,Tx>] [ α < t , 1 > , α < t , 2 > , . . . , α < t , T x > ] s<t1> s < t − 1 > 上执行 one_step_attention() 得到上下文向量 context<t> c o n t e x t < t >
    2. context<t> c o n t e x t < t > 输入post-attention LSTM。记得使用initial_state= [previous hidden state, previous cell state]传入前面的隐藏状态 s<t1> s < t − 1 > 和LSTM的单元状态 c<t1> c < t − 1 > 。得到新的隐藏状态 s<t> s < t > 和新的单元状态 c<t> c < t >
    3. s<t> s < t > 传给softmax层,得到输出
    4. 将输出添加到输出列表中进行保存
  3. 创建你的Keras模型实例,模型有三个输入(“inputs”, s<0> s < 0 > and c<0> c < 0 > ),输出一个列表”output”
# 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 ###

最后一步是定义模型的输入和输出:

  • 你已经有了包含所有训练样本的(m=10000, Tx T x =30)的X
  • 需要创建 s0,c0 来为 post_activation_LSTM_cell 进行0值初始化
  • model() 的输出为11个(m, Ty)元素的列表。所以:outputs[i][0], …, outputs[i][Ty] 表示第i的样本(X[i])的标签字符。更一般的说,outputs[i][j] 表示第i个训练样本的第j个字母的标签字符。
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的例子。

吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(一):机器翻译_第3张图片

你可以导入并使用我们已经训练了很长时间的模型。

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

您也可以您自己的示例进行测试。下一部分将更直观的认识到注意机制正在做什么。例如,当生成一个特定的输出字符时,网络正在关注哪些输入部分。

3 注意力可视化 (可选)

由于输出的长度固定为10,因此可以使用10个不同的softmax单位执行此任务以生成输出的10个字符。注意力模型的一个优点是输出的每个部分(比如说月份)都知道它只需要依赖一小部分输入(输入的月份中的字符)。 我们可以看到输出的哪一部分正在查看输入的哪一部分。

考虑将“2018年5月9日星期六”翻译为“2018-05-09”的任务。 如果我们将计算出的 αt,t α ⟨ t , t ′ ⟩ 可视化,我们可以得到:

吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(一):机器翻译_第4张图片

注意输出如何忽略输入的“星期六”部分。没有一个输出时间步对“星期六”部分有很多的注意力。 我们还看到,9被翻译为09,“May”被正确翻译成05,输出将注意力放在产生该输出所需的输入部分。 年份将大多的注意力都放在输入的“18”上以产生“2018”。

3.1 得到网络的激活函数

现在我们将网络中的激活函数进行可视化。我们将举一个例子贯穿整个网络,然后可视化 α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)

吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(一):机器翻译_第5张图片

在生成的图上,您可以观察预测输出每个字符时的注意力权重值。 检查此图并检查网络注意力在哪里才对你有意义。

在这个日期转换应用中,你可以观察到大多数的注意力都帮助预测年,相对较少的注意力在日期和月份上。

谨记

  • 机器翻译模型可用于从一个序列映射到另一个序列。 不仅用于翻译人类语言(如法语 - >英语),还用于日期格式翻译等任务。
  • 注意机制允许网络在产生输出的特定部分时专注于输入的最相关部分。
  • 使用注意机制的网络可以从长度为 Tx T x 的输入转换为长度为 Ty T y 的输出,其中 Tx T x Ty T y 可以不同。
  • 您可以将注意力权重 αtt α ⟨ t , t ′ ⟩ 可视化,以便在生成每个输出时查看网络正在关注的内容。

恭喜你完成了这个作业!现在你可以实现一个注意力模型并使用它学习从一个序列到另一个序列的复杂映射了。

你可能感兴趣的:(机器学习,深度学习,吴恩达)