恐龙岛--构建字符级语言模型
本文是基于吴恩达《深度学习》第五周第一课后练习题所做。
0.背景简介
本练习题中,达叔提出了一个有趣的问题,让我们在现有的已知恐龙名称的基础上创造新的恐龙 ,而且如果所创造的新品恐龙和其外貌不相称的话就会被消灭。主要思路是利用现有的恐龙数据集,构建字符级的语言模型来产生新的名字,算法会学习不同的名字特征,然后产生新的名字。

通过本练习可以实现如下4个目标:
(1)如何使用RNN存储可处理的文本数据;
(2)如何通过在每一时间步采集预测值并将其传递到下一个RNN单元,以实现数据同步;
(3)如何构建一个字符级的文本生成器来构建RNN;
(4)裁剪梯度值的重要性。
导入所需的第三方库,其中所用辅助程序可点击此处下载。
import numpy as np
from utils import *
import random
1.问题描述
1.1数据集及预处理
从文件中读取恐龙名字数据集,创建一个统一字符列表,计算数据集和字典的大小。
data = open('datasets\dinos.txt').read()
data = data.lower()
chars = list(set(data))
data_size, vocab_size = len(data), len(chars)
print("There are %d total characters and %d unique characters in your data."%(data_size, vocab_size))
There are 19909 total characters and 27 unique characters in your data.
从打印的输出我们可以看到统一字符一个是27个,这其中包括a-z共26个字符和“\n”,“\n”可以看做是句子结束符号“EOS”。接下来我们创建两个字典,使统一字符和指标index对于,以便我们能够将softmax层输出的概率分布与字符对应起来。
char_to_ix = {ch : i for i,ch in enumerate(sorted(chars))}
ix_to_char = {i : ch for i,ch in enumerate(sorted(chars))}
print(ix_to_char)
{0: '\n', 1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g', 8: 'h', 9: 'i', 10: 'j', 11: 'k', 12: 'l', 13: 'm', 14: 'n', 15: 'o', 16: 'p', 17: 'q', 18: 'r', 19: 's', 20: 't', 21: 'u', 22: 'v', 23: 'w', 24: 'x', 25: 'y', 26: 'z'}
1.2模型概览
我们将要建立模型结构如下:
(1)初始化参数;
(2)运行优化循环:
- 前向传播计算loss
- 反向传播计算loss的梯度
- 截断梯度以避免梯度爆炸
- 使用梯度下降更新参数
(3)返回学习到的参数;

在RNN的每一个时间步中,模型会根据上一步的字符推测下一步的字符是什么,然后将预测的是y<t> _hat作为下一步的输入。
2.构建2个重要模块
本小节主要构建截断梯度模块和采样模块。
2.1在优化循环中截取梯度
我们下面将编写一个clip函数实现截取梯度的功能,实现clip的方法很多,在此我们此采用一种简单的按元素处理的方案,对于梯度向量的元素,将其裁剪成[-N, N]的区间范围,假设N=10,当有元素大于10时将这个元素设置成10;同理,当某元素小于-10时将其设置成-10。截取之后的梯度趋势将如下图所示。

def clip(gradients, maxValue):
dWaa, dWax, dWya, db, dby = gradients['dWaa'], gradients['dWax'], gradients['dWya'], \
gradients['db'], gradients['dby']
for gradiet in [dWaa, dWax, dWya, db, dby]:
np.clip(gradiet, -maxValue, maxValue, out=gradiet)
gradiets = {"dWaa": dWaa, "dWax": dWax, "dWya": dWya, "db": db, "dby": dby}
return gradiets
np.random.seed(3)
dWax = np.random.randn(5,3)*10
dWaa = np.random.randn(5,5)*10
dWya = np.random.randn(2,5)*10
db = np.random.randn(5,1)*10
dby = np.random.randn(2,1)*10
gradients = {"dWax": dWax, "dWaa": dWaa, "dWya": dWya, "db": db, "dby": dby}
gradients = clip(gradients, 10)
print("gradients[\"dWaa\"][1][2] =", gradients["dWaa"][1][2])
print("gradients[\"dWax\"][3][1] =", gradients["dWax"][3][1])
print("gradients[\"dWya\"][1][2] =", gradients["dWya"][1][2])
print("gradients[\"db\"][4] =", gradients["db"][4])
print("gradients[\"dby\"][1] =", gradients["dby"][1])
gradients["dWaa"][1][2] = 10.0
gradients["dWax"][3][1] = -10.0
gradients["dWya"][1][2] = 0.2971381536101662
gradients["db"][4] = [10.]
gradients["dby"][1] = [8.45833407]
2.2 采样
假设我们的模型已经训练完成,现在讲产生下一个文本,流程如下图所示。

提示:本函数中所用的公式如下:

def sample(parameters, char_to_ix, seed):
Waa, Wax, Wya, by, b = parameters['Waa'], parameters['Wax'], parameters['Wya'],\
parameters['by'], parameters['b']
vocab_size = by.shape[0]
n_a = Waa.shape[1]
x = np.zeros((vocab_size, 1))
a_prev = np.zeros((n_a, 1))
indices = []
idx = -1
counter = 0
newline_character = char_to_ix['\n']
while (idx != newline_character and counter != 50):
a = np.tanh(np.dot(Wax, x) + np.dot(Waa, a_prev) + b)
z = np.dot(Wya, a) + by
y = softmax(z)
np.random.seed(counter + seed)
idx = np.random.choice(range(len(y)), p = y.ravel())
indices.append(idx)
x = np.zeros((vocab_size, 1))
x[idx] = 1
a_prev = a
seed += 1
counter += 1
if (counter == 50):
indices.append(char_to_ix['\n'])
return indices
np.random.seed(2)
_, n_a = 20, 100
Wax, Waa, Wya = np.random.randn(n_a, vocab_size), np.random.randn(n_a, n_a), np.random.randn(vocab_size, n_a)
b, by = np.random.randn(n_a, 1), np.random.randn(vocab_size, 1)
parameters = {"Wax": Wax, "Waa": Waa, "Wya": Wya, "b": b, "by": by}
indices = sample(parameters, char_to_ix, 0)
print("Sampling:")
print("list of sampled indices:", indices)
print("list of sampled characters:", [ix_to_char[i] for i in indices])
Sampling:
list of sampled indices: [12, 17, 24, 14, 13, 9, 10, 22, 24, 6, 13, 11, 12, 6, 21, 15, 21, 14, 3, 2, 1, 21, 18, 24, 7, 25, 6, 25, 18, 10, 16, 2, 3, 8, 15, 12, 11, 7, 1, 12, 10, 2, 7, 7, 11, 17, 24, 1, 13, 0, 0]
list of sampled characters: ['l', 'q', 'x', 'n', 'm', 'i', 'j', 'v', 'x', 'f', 'm', 'k', 'l', 'f', 'u', 'o', 'u', 'n', 'c', 'b', 'a', 'u', 'r', 'x', 'g', 'y', 'f', 'y', 'r', 'j', 'p', 'b', 'c', 'h', 'o', 'l', 'k', 'g', 'a', 'l', 'j', 'b', 'g', 'g', 'k', 'q', 'x', 'a', 'm', '\n', '\n']
3.建立语言模型
3.1梯度下降
在此,我们将实现随机梯度下降,每次从训练集中读取一个样本。
函数中用到了utils_character_level_LM辅助程序中的3个函数:rnn_forward, rnn_backward, update_parameters,代码如下:
def rnn_forward(X, Y, a0, parameters, vocab_size = 27):
# Initialize x, a and y_hat as empty dictionaries
x, a, y_hat = {}, {}, {}
a[-1] = np.copy(a0)
# initialize your loss to 0
loss = 0
for t in range(len(X)):
# Set x[t] to be the one-hot vector representation of the t'th character in X.
# if X[t] == None, we just have x[t]=0. This is used to set the input for the first timestep to the zero vector.
x[t] = np.zeros((vocab_size,1))
if (X[t] != None):
x[t][X[t]] = 1
# Run one step forward of the RNN
a[t], y_hat[t] = rnn_step_forward(parameters, a[t-1], x[t])
# Update the loss by substracting the cross-entropy term of this time-step from it.
loss -= np.log(y_hat[t][Y[t],0])
cache = (y_hat, a, x)
return loss, cache
def rnn_backward(X, Y, parameters, cache):
# Initialize gradients as an empty dictionary
gradients = {}
# Retrieve from cache and parameters
(y_hat, a, x) = cache
Waa, Wax, Wya, by, b = parameters['Waa'], parameters['Wax'], parameters['Wya'], parameters['by'], parameters['b']
# each one should be initialized to zeros of the same dimension as its corresponding parameter
gradients['dWax'], gradients['dWaa'], gradients['dWya'] = np.zeros_like(Wax), np.zeros_like(Waa), np.zeros_like(Wya)
gradients['db'], gradients['dby'] = np.zeros_like(b), np.zeros_like(by)
gradients['da_next'] = np.zeros_like(a[0])
### START CODE HERE ###
# Backpropagate through time
for t in reversed(range(len(X))):
dy = np.copy(y_hat[t])
dy[Y[t]] -= 1
gradients = rnn_step_backward(dy, gradients, parameters, x[t], a[t], a[t-1])
### END CODE HERE ###
return gradients, a
def update_parameters(parameters, gradients, lr):
parameters['Wax'] += -lr * gradients['dWax']
parameters['Waa'] += -lr * gradients['dWaa']
parameters['Wya'] += -lr * gradients['dWya']
parameters['b'] += -lr * gradients['db']
parameters['by'] += -lr * gradients['dby']
return parameters
def optimize(X, Y, a_prev, parameters, learning_rate = 0.01):
loss, cache = rnn_forward(X, Y, a_prev, parameters)
gradients, a = rnn_backward(X, Y, parameters, cache)
gradients = clip(gradients, 5)
parameters = update_parameters(parameters, gradients, learning_rate)
return loss, gradients, a[len(X)-1]
np.random.seed(1)
vocab_size, n_a = 27, 100
a_prev = np.random.randn(n_a, 1)
Wax, Waa, Wya = np.random.randn(n_a, vocab_size), np.random.randn(n_a, n_a), np.random.randn(vocab_size, n_a)
b, by = np.random.randn(n_a, 1), np.random.randn(vocab_size, 1)
parameters = {"Wax": Wax, "Waa": Waa, "Wya": Wya, "b": b, "by": by}
X = [12,3,5,11,22,3]
Y = [4,14,11,22,25, 26]
loss, gradients, a_last = optimize(X, Y, a_prev, parameters, learning_rate = 0.01)
print("Loss =", loss)
print("gradients[\"dWaa\"][1][2] =", gradients["dWaa"][1][2])
print("np.argmax(gradients[\"dWax\"]) =", np.argmax(gradients["dWax"]))
print("gradients[\"dWya\"][1][2] =", gradients["dWya"][1][2])
print("gradients[\"db\"][4] =", gradients["db"][4])
print("gradients[\"dby\"][1] =", gradients["dby"][1])
print("a_last[4] =", a_last[4])
Loss = 126.50397572165382
gradients["dWaa"][1][2] = 0.1947093153471637
np.argmax(gradients["dWax"]) = 93
gradients["dWya"][1][2] = -0.007773876032002977
gradients["db"][4] = [-0.06809825]
gradients["dby"][1] = [0.01538192]
a_last[4] = [-1.]
3.2训练模型
对于给定的恐龙名字数据集,我们将其每一行数据当做一个训练样本。每隔100步随机梯度下降,随机采集10个名字来观察算法运行如何。在使用随机梯度下降之前需要将数据集打乱。
def model(data, ix_to_char, char_to_ix, num_iterations = 35000, n_a =50, dino_names = 7, vocab_size = 27):
n_x, n_y = vocab_size, vocab_size
parameters = initialize_parameters(n_a, n_x, n_y)
loss = get_initial_loss(vocab_size, dino_names)
with open("datasets\dinos.txt") as f:
examples = f.readlines()
examples = [x.lower().strip() for x in examples]
np.random.seed(0)
np.random.shuffle(examples)
a_prev = np.zeros((n_a, 1))
for j in range(num_iterations):
index = j % len(examples)
X = [None] + [char_to_ix[ch] for ch in examples[index]]
Y = X[1:] + [char_to_ix['\n']]
curr_loss, gradients, a_prev = optimize(X, Y, a_prev, parameters, learning_rate = 0.01)
loss = smooth(loss, curr_loss)
if j % 2000 == 0:
print("Iteration: %d, Loss: %f"%(j, loss) + '\n')
seed = 0
for name in range(dino_names):
sampled_indices = sample(parameters, char_to_ix, seed)
print_sample(sampled_indices, ix_to_char)
seed += 1
print('\n')
return parameters
parameters = model(data, ix_to_char, char_to_ix)
Iteration: 0, Loss: 23.087336
Nkzxwtdmfqoeyhsqwasjkjvu
Kneb
Kzxwtdmfqoeyhsqwasjkjvu
Neb
Zxwtdmfqoeyhsqwasjkjvu
Eb
Xwtdmfqoeyhsqwasjkjvu
Iteration: 2000, Loss: 27.884160
Liusskeomnolxeros
Hmdaairus
Hytroligoraurus
Lecalosapaus
Xusicikoraurus
Abalpsamantisaurus
Tpraneronxeros
Iteration: 4000, Loss: 25.901815
Mivrosaurus
Inee
Ivtroplisaurus
Mbaaisaurus
Wusichisaurus
Cabaselachus
Toraperlethosdarenitochusthiamamumamaon
Iteration: 6000, Loss: 24.608779
Onwusceomosaurus
Lieeaerosaurus
Lxussaurus
Oma
Xusteonosaurus
Eeahosaurus
Toreonosaurus
Iteration: 8000, Loss: 24.070350
Onxusichepriuon
Kilabersaurus
Lutrodon
Omaaerosaurus
Xutrcheps
Edaksoje
Trodiktonus
Iteration: 10000, Loss: 23.844446
Onyusaurus
Klecalosaurus
Lustodon
Ola
Xusodonia
Eeaeosaurus
Troceosaurus
Iteration: 12000, Loss: 23.291971
Onyxosaurus
Kica
Lustrepiosaurus
Olaagrraiansaurus
Yuspangosaurus
Eealosaurus
Trognesaurus
Iteration: 14000, Loss: 23.382338
Meutromodromurus
Inda
Iutroinatorsaurus
Maca
Yusteratoptititan
Ca
Troclosaurus
Iteration: 16000, Loss: 23.282946
Mdyusaurus
Indaacosaupisaurus
Justolong
Maca
Yuspandosaurus
Cabaspadantes
Trodon
Iteration: 18000, Loss: 22.850813
Phyusaurus
Meja
Mystoosaurus
Pegamosaurus
Yusmaphosaurus
Eiahosaurus
Trolonosaurus
Iteration: 20000, Loss: 22.929326
Nlyusaurus
Logbalosaurus
Lvuslangosaurus
Necalosaurus
Ytrrangosaurus
Ekairus
Troenesaurus
Iteration: 22000, Loss: 22.760728
Onvusaroglolonoshareimus
Llecaerona
Myrrocephoeurus
Pedacosaurus
Ytrodonosaurus
Eiadosaurus
Trodonosaurus
Iteration: 24000, Loss: 22.577208
Meustrhe
Inca
Jurrollesaurus
Medaislleamlis
Yusaurus
Cabasigaansaurus
Trocerator
Iteration: 26000, Loss: 22.616434
Onyxosaurus
Lelealosaurus
Lustolialneusaurns
Olaahus
Yusichesaurus
Ehairuodan
Trochenomusaurus
Iteration: 28000, Loss: 22.524690
Meutosaurus
Kracafosaurus
Lvtosaurus
Necaisaurus
Yusiadgosaurus
Eiadosaurus
Trocephicrisaurus
Iteration: 30000, Loss: 22.719788
Peutroknethithosaurus
Llacalosaurus
Lutroenator
Pacalosaurus
Yusolonosaurus
Edacosaurus
Trocheshathystatepiscaptiuiman
Iteration: 32000, Loss: 22.232960
Meustrathus
Kojcanosaurus
Lustrasaurus
Macalosaurus
Yusiandon
Eeainor
Usianatorsaurus
Iteration: 34000, Loss: 22.318447
Olusaurus
Klacaesaurus
Lusnsaurus
Ola
Ytosaurus
Egaisaurus
Trhangosaurus
运行35000代之后,我们得到了七个新种恐龙的名字。这些名字看起来和霸王龙(tyrannosaurus)一样酷。
在本次编程作业中,吴恩达老师还提供了一个小练习“像莎士比亚一样写诗”,该例子使用LSTM单元来完成的。在此将代码复制如下,有兴趣的朋友可以研究一下。
from __future__ import print_function
from keras.callbacks import LambdaCallback
from keras.models import Model, load_model, Sequential
from keras.layers import Dense, Activation, Dropout, Input, Masking
from keras.layers import LSTM
from keras.utils.data_utils import get_file
from keras.preprocessing.sequence import pad_sequences
from utils_shakespeare import *
import sys
import io
print_callback = LambdaCallback(on_epoch_end = on_epoch_end)
model.fit(x, y, batch_size = 128, epochs=1, callbacks=[print_callback])
generate_output()
其中辅助程序utils_shakespeare如下:
from __future__ import print_function
from keras.callbacks import LambdaCallback
from keras.models import Model, load_model, Sequential
from keras.layers import Dense, Activation, Dropout, Input, Masking
from keras.layers import LSTM
from keras.utils.data_utils import get_file
from keras.preprocessing.sequence import pad_sequences
import numpy as np
import random
import sys
import io
def build_data(text, Tx = 40, stride = 3):
"""
Create a training set by scanning a window of size Tx over the text corpus, with stride 3.
Arguments:
text -- string, corpus of Shakespearian poem
Tx -- sequence length, number of time-steps (or characters) in one training example
stride -- how much the window shifts itself while scanning
Returns:
X -- list of training examples
Y -- list of training labels
"""
X = []
Y = []
### START CODE HERE ### (≈ 3 lines)
for i in range(0, len(text) - Tx, stride):
X.append(text[i: i + Tx])
Y.append(text[i + Tx])
### END CODE HERE ###
print('number of training examples:', len(X))
return X, Y
def vectorization(X, Y, n_x, char_indices, Tx = 40):
"""
Convert X and Y (lists) into arrays to be given to a recurrent neural network.
Arguments:
X --
Y --
Tx -- integer, sequence length
Returns:
x -- array of shape (m, Tx, len(chars))
y -- array of shape (m, len(chars))
"""
m = len(X)
x = np.zeros((m, Tx, n_x), dtype=np.bool)
y = np.zeros((m, n_x), dtype=np.bool)
for i, sentence in enumerate(X):
for t, char in enumerate(sentence):
x[i, t, char_indices[char]] = 1
y[i, char_indices[Y[i]]] = 1
return x, y
def sample(preds, temperature=1.0):
# helper function to sample an index from a probability array
preds = np.asarray(preds).astype('float64')
preds = np.log(preds) / temperature
exp_preds = np.exp(preds)
preds = exp_preds / np.sum(exp_preds)
probas = np.random.multinomial(1, preds, 1)
out = np.random.choice(range(len(chars)), p = probas.ravel())
return out
#return np.argmax(probas)
def on_epoch_end(epoch, logs):
# Function invoked at end of each epoch. Prints generated text.
None
#start_index = random.randint(0, len(text) - Tx - 1)
#generated = ''
#sentence = text[start_index: start_index + Tx]
#sentence = '0'*Tx
#usr_input = input("Write the beginning of your poem, the Shakespearian machine will complete it.")
# zero pad the sentence to Tx characters.
#sentence = ('{0:0>' + str(Tx) + '}').format(usr_input).lower()
#generated += sentence
#
#sys.stdout.write(usr_input)
#for i in range(400):
"""
#x_pred = np.zeros((1, Tx, len(chars)))
for t, char in enumerate(sentence):
if char != '0':
x_pred[0, t, char_indices[char]] = 1.
preds = model.predict(x_pred, verbose=0)[0]
next_index = sample(preds, temperature = 1.0)
next_char = indices_char[next_index]
generated += next_char
sentence = sentence[1:] + next_char
sys.stdout.write(next_char)
sys.stdout.flush()
if next_char == '\n':
continue
# Stop at the end of a line (4 lines)
print()
"""
print("Loading text data...")
text = io.open('datasets\shakespeare.txt', encoding='utf-8').read().lower()
#print('corpus length:', len(text))
Tx = 40
chars = sorted(list(set(text)))
char_indices = dict((c, i) for i, c in enumerate(chars))
indices_char = dict((i, c) for i, c in enumerate(chars))
#print('number of unique characters in the corpus:', len(chars))
print("Creating training set...")
X, Y = build_data(text, Tx, stride = 3)
print("Vectorizing training set...")
x, y = vectorization(X, Y, n_x = len(chars), char_indices = char_indices)
print("Loading model...")
model = load_model('model_data/model_shakespeare_kiank_350_epoch.h5')
def generate_output():
generated = ''
#sentence = text[start_index: start_index + Tx]
#sentence = '0'*Tx
usr_input = input("Write the beginning of your poem, the Shakespeare machine will complete it. Your input is: ")
# zero pad the sentence to Tx characters.
sentence = ('{0:0>' + str(Tx) + '}').format(usr_input).lower()
generated += usr_input
sys.stdout.write("\n\nHere is your poem: \n\n")
sys.stdout.write(usr_input)
for i in range(400):
x_pred = np.zeros((1, Tx, len(chars)))
for t, char in enumerate(sentence):
if char != '0':
x_pred[0, t, char_indices[char]] = 1.
preds = model.predict(x_pred, verbose=0)[0]
next_index = sample(preds, temperature = 1.0)
next_char = indices_char[next_index]
generated += next_char
sentence = sentence[1:] + next_char
sys.stdout.write(next_char)
sys.stdout.flush()
if next_char == '\n':
continue
更多推荐



所有评论(0)