吴恩达深度学习专项课程的所有实验均采用iPython Notebooks实现,不熟悉的朋友可以提前使用一下Notebooks。
目录
1.实验综述
2.加载必要的包
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
#!pip install faker #安装faker
from faker import Faker
import random
from tqdm import tqdm
from babel.dates import format_date
from nmt_utils import * #./nmt_utils.py
import matplotlib.pyplot as plt
%matplotlib inline
3.把人类可读日期转换为机器可读日期
- 数据集
我们将在一个包含10000个人类可读日期及其对应的标准机器可读日期的数据集上训练我们的模型。接下里加载数据集并打印一些样本。
m = 10000
dataset,human_vocab,machine_vocab,inv_machine_vocab = load_dataset(m)
print(dataset[:10]) #打印前10个样本
print(human_vocab)
print(inv_machine_vocab)
print(machine_vocab)
查看头文件中的load_dataset()及其相关函数:
# 定义生成日期数据的格式
FORMATS = ['short',
'medium',
'long',
'full',
'full',
'full',
'full',
'full',
'full',
'full',
'full',
'full',
'full',
'd MMM YYY',
'd MMMM YYY',
'dd MMM YYY',
'd MMM, YYY',
'd MMMM, YYY',
'dd, MMM YYY',
'd MM YY',
'd MMMM YYY',
'MMMM d YYY',
'MMMM d, YYY',
'dd.MM.YY']
# 如果想生成其他语言的日期 可以修改这里
LOCALES = ['en_US']
def load_date():
"""
加载一些假的日期
:returns: 元组,包括人类可读日期,机器可读日期和数据对象。
"""
dt = fake.date_object()
try:
human_readable = format_date(dt, format=random.choice(FORMATS), locale='en_US') # locale=random.choice(LOCALES)) 生成一些人类可读的随机格式的日期
human_readable = human_readable.lower() #日期小写化
human_readable = human_readable.replace(',','') #将日期中的,替换为''
machine_readable = dt.isoformat() #生成对应的机器可读日期
except AttributeError as e:
return None, None, None
return human_readable, machine_readable, dt
def load_dataset(m):
"""
加载包含m个样本和词的数据集
m: 生成样本的数量
"""
human_vocab = set() #集合 人类可读日期
machine_vocab = set() #集合 机器可读日期
dataset = [] #数据集
Tx = 30
for i in tqdm(range(m)): #生成m对数据
h, m, _ = load_date() #得到一对人类可读日期和机器可读日期
if h is not None:
dataset.append((h, m)) #添加到数据集列表中
human_vocab.update(tuple(h))#将人类可读日期转换为元组 加到人类可读日期集合中
machine_vocab.update(tuple(m))#将机器可读日期转换为元组 加到机器可读日期集合中
human = dict(zip(sorted(human_vocab) + ['<unk>', '<pad>'],
list(range(len(human_vocab) + 2))))
inv_machine = dict(enumerate(sorted(machine_vocab)))
machine = {v:k for k,v in inv_machine.items()}
return dataset, human, machine, inv_machine
接下来对数据进行预处理并把原始文本数据映射为索引值。我们设置Tx=30(假设人类可读日期的最大长度为30,如果有一个更长的输入则进行截断),Ty=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)
查看头文件中的preprocess_data()及其相关函数:
def preprocess_data(dataset, human_vocab, machine_vocab, Tx, Ty):
X, Y = zip(*dataset) #得到人类可读日期和机器可读日期
X = np.array([string_to_int(i, Tx, human_vocab) for i in X]) #把每个人类可读日期转换为一个整数列表 返回一个2维数组
Y = [string_to_int(t, Ty, machine_vocab) for t in Y]#把每个机器可读日期转换为一个整数列表 返回一个2维数组
#把整数索引装换为one-hot形式 返回一个三维数组
Xoh = np.array(list(map(lambda x: to_categorical(x, num_classes=len(human_vocab)), X)))
Yoh = np.array(list(map(lambda x: to_categorical(x, num_classes=len(machine_vocab)), Y)))
return X, np.array(Y), Xoh, Yoh
def string_to_int(string, length, vocab):
"""
把所有字符串转换为一个整数列表
该整数列表中的整数代表字符串中的各个字符在词典中的位置。
Arguments:
string -- 输入字符串, e.g. 'Wed 10 Jul 2007'
length -- 输入的最大长度,将采用的时间步骤数, 决定输出是否被截断或填充
vocab --词典,字符到整数索引的映射字典
Returns:
rep -- 返回的整数列表 (or '<unk>') (size = length) 表示字符串中的字符在词典中的位置
"""
string = string.lower()#转换为标准小写
string = string.replace(',','') #将,替换为''
if len(string) > length: #如果输入字符串长度大于length则截断 只取前length个字符
string = string[:length]
rep = list(map(lambda x: vocab.get(x, '<unk>'), string))
if len(string) < length:#如果输入字符串长度小于length则填充
rep += [vocab['<pad>']] * (length - len(string))
#print (rep)
return rep
接下来看一下预处理完后的一些训练样本。可以所以改变index
值。
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])
4.神经机器翻译(with Attention)
- Attention机制
#定义共享参数层为全局变量
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)
# GRADED FUNCTION: one_step_attention
def one_step_attention(a, s_prev):
"""
执行一步attention: 通过把attention权重"alphas"和Bi-LSTM的隐藏状态'a'点乘
输出一个context vector。
Arguments:
a -- Bi-LSTM输出的隐藏状态, 数组大小 (m, Tx, 2*n_a)
s_prev -- (post-attention) LSTM之前的隐藏状态, 数组大小 (m, n_s)
Returns:
context -- context vector, 下一个 (post-attetion) LSTM 单元的输入
"""
# 使用 repeator 对 s_prev 进行复制 形状变为(m, Tx, n_s) 使得可以把它和所有的隐藏状态 "a"拼接
s_prev = repeator(s_prev)
# 使用concatenator 把 a 和 s_prev 在最后一个维度上进行拼接
concat = concatenator([a,s_prev])
# 使用 densor1 将 concat 前向传播通过一个小的全联接神经网络计算变量e.
e = densor1(concat)
# 使用 densor2 将 e 前向传播通过一个小的全联接神经网络来计算变量energies.
energies = densor2(e)
# 对 "energies" 使用 "activator" 计算attention权重 "alphas"
alphas = activator(energies)
#使用 dotor 把 "alphas" 和 "a" 结合计算context vector 传给下一个(post-attention) LSTM-cell
context = dotor([alphas,a])
return context
实验:实现model()
函数,我们首先定义model()中共享参数的层为全局变量。
n_a = 32 #Bi-LSTM隐藏单元数
n_s = 64 #post-attention LSTM隐藏单元数
post_activation_LSTM_cell = LSTM(n_s,return_state=True)
output_layer = Dense(len(machine_vocab),activation=softmax)
# GRADED FUNCTION: model
def model(Tx, Ty, n_a, n_s, human_vocab_size, machine_vocab_size):
"""
Arguments:
Tx -- 输入序列长度
Ty -- 输出序列长度
n_a -- Bi-LSTM隐藏单元数
n_s -- post-attention LSTM隐藏单元数
human_vocab_size -- 字典 "human_vocab"的大小
machine_vocab_size -- 自带呢"machine_vocab"的大小
Returns:
model -- Keras 模型实例
"""
# 定义模型输入 大小(Tx,human_vocab_size,None)
# 定义s0 和 c0, 初始化decoder LSTM 的隐藏状态 大小(n_s,None)
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
# 初始化输出列表
outputs = []
# Step 1: 定义pre-attention Bi-LSTM. return_sequences=True.
a = Bidirectional(LSTM(n_a,return_sequences=True),name='bidirectional_1')(X)
# Step 2: 迭代Ty次
for t in range(Ty):
# Step 2.A: 执行一步attention机制 得到时间步骤t上的context vector
context = one_step_attention(a,s)
# Step 2.B: 把 "context" vector作为post-attention LSTM cell 的输入
# 不要忘记传递: initial_state = [hidden state, cell state]
s, _, c = post_activation_LSTM_cell(context,initial_state=[s,c])
# Step 2.C: 对post-attention LSTM输出的隐藏状态经过一个Dense层(输出层使用softmax激活函数)
out = output_layer(s)
# Step 2.D: 把out添加到"outputs" 列表中 (≈ 1 line)
outputs.append(out)
# Step 3:创建模型实例 有三个输入 返回一个outputs列表
model = Model(inputs=[X,s0,c0],outputs=outputs)
return model
- 创建模型
#创建模型
model = model(Tx,Ty,n_a,n_s,len(human_vocab),len(machine_vocab))
- 模型摘要
model.summary()
- 编译模型
opt = Adam(lr=0.005,beta_1=0.9,beta_2=0.999,decay=0.01)
model.compile(loss='categorical_crossentropy',optimizer=opt,metrics=['accuracy'])
- 定义模型输入输出,训练模型
s0 = np.zeros((m, n_s))
c0 = np.zeros((m, n_s))
outputs = list(Yoh.swapaxes(0,1))
'''
print(Yoh.shape)
print(Yoh.swapaxes(0,1).shape)
print(Yoh.swapaxes(0,1))
print(list(Yoh.swapaxes(0,1)))
'''
model.fit([Xoh, s0, c0], outputs, epochs=50, batch_size=100)
- 保存模型
model.save('models/model.h5') #把训练好的模型权重保存为.h5文件
- 读取模型
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)))
source = source[np.newaxis,:,:] #[1,30,37] 添加一个新轴 长度为1 变成三维
print(source.shape)
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))
5.可视化Attention
model.summary()
attention_map = plot_attention_map(model, human_vocab, inv_machine_vocab, "Tuesday 09 Oct 1993", num = 7, n_s = 64)
查看plot_attention_map()函数:
def plot_attention_map(model, input_vocabulary, inv_output_vocabulary, text, n_s = 128, num = 6, Tx = 30, Ty = 10):
"""
Plot the attention map.
"""
attention_map = np.zeros((10, 30))
Ty, Tx = attention_map.shape
s0 = np.zeros((1, n_s))
c0 = np.zeros((1, n_s))
layer = model.layers[num]
encoded = np.array(string_to_int(text, Tx, input_vocabulary)).reshape((1, 30))
encoded = np.array(list(map(lambda x: to_categorical(x, num_classes=len(input_vocabulary)), encoded)))
f = K.function(model.inputs, [layer.get_output_at(t) for t in range(Ty)])
r = f([encoded, s0, c0])
for t in range(Ty):
for t_prime in range(Tx):
attention_map[t][t_prime] = r[t][0,t_prime,0]
# Normalize attention map
# row_max = attention_map.max(axis=1)
# attention_map = attention_map / row_max[:, None]
prediction = model.predict([encoded, s0, c0])
predicted_text = []
for i in range(len(prediction)):
predicted_text.append(int(np.argmax(prediction[i], axis=1)))
predicted_text = list(predicted_text)
predicted_text = int_to_string(predicted_text, inv_output_vocabulary)
text_ = list(text)
# get the lengths of the string
input_length = len(text)
output_length = Ty
# Plot the attention_map
plt.clf()
f = plt.figure(figsize=(8, 8.5))
ax = f.add_subplot(1, 1, 1)
# add image
i = ax.imshow(attention_map, interpolation='nearest', cmap='Blues')
# add colorbar
cbaxes = f.add_axes([0.2, 0, 0.6, 0.03])
cbar = f.colorbar(i, cax=cbaxes, orientation='horizontal')
cbar.ax.set_xlabel('Alpha value (Probability output of the "softmax")', labelpad=2)
# add labels
ax.set_yticks(range(output_length))
ax.set_yticklabels(predicted_text[:output_length])
ax.set_xticks(range(input_length))
ax.set_xticklabels(text_[:input_length], rotation=45)
ax.set_xlabel('Input Sequence')
ax.set_ylabel('Output Sequence')
# add grid and legend
ax.grid()
#f.show()
return attention_map
6.总结