该系列的Jupyter源码以及Char-RNN的模型权重&训练历史已放到附件以供下载:
使用RNN生成莎士比亚文本(二)训练Char-RNN模型
该章的模型请尽量采用GPU运算,如果使用CPU可能一天也算不完
在第(一)节,我们建立了数据集,完成了最难的一部分,然后就可以着手搭建模型训练了。
模型构架
目标:基于前100个字符预测第101个字符
构架:可以使用2个GRU蹭的RNN,每个GRU层有128个单元,输入和隐藏状态的dropout率均为20%。放弃设定dropout率是因为能加快GPU的运算速度
输出:因为有39种字符,所以必须有39个单元,每个单元代表为某个字符的概率。
激活函数:每个时间步长内,输出概率总和为1,所以可以将softmax激活函数应用于Dense层的输出。
其他:选择“sparse_categorical_crossen tropy”损失和“adam”优化器。
创建和训练Char-RNN模型的代码如下:
下面的代码请在算力强的计算机上运行,本人实验室电脑4路1080显卡,跑了9个多小时。

import sys
import sklearn
import tensorflow as tf
from tensorflow import keras
import numpy as np
import os
# 确保该程序能够稳定输出
np.random.seed(42)
tf.random.set_seed(42)
# 下载数据集
shakespeare_url = "https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt"
filepath = keras.utils.get_file("shakespeare.txt", shakespeare_url)
with open(filepath) as f:
shakespeare_text = f.read()
# 加载分词器
tokenizer = keras.preprocessing.text.Tokenizer(char_level=True)
tokenizer.fit_on_texts(shakespeare_text)
max_id = len(tokenizer.word_index) #字符种类数
dataset_size = tokenizer.document_count #字符总数
#对文本进行编码
[encoded] = np.array(tokenizer.texts_to_sequences([shakespeare_text])) - 1
#90%作为训练集
train_size = dataset_size * 90 // 100
dataset = tf.data.Dataset.from_tensor_slices(encoded[:train_size])
#以长度101分窗
n_steps = 100
window_length = n_steps + 1 # t窗口长度为101,前移一个字符
dataset = dataset.window(window_length, shift=1, drop_remainder=True)
# drop_remainder=True 舍弃最后100个窗口不足101个字符的情况
# 展平数据集
dataset = dataset.flat_map(lambda window: window.batch(window_length))
#数据集乱序
np.random.seed(42)
tf.random.set_seed(42)
batch_size = 32
dataset = dataset.shuffle(10000).batch(batch_size)
dataset = dataset.map(lambda windows: (windows[:, :-1], windows[:, 1:]))
# 进行独热编码
dataset = dataset.map(
lambda X_batch, Y_batch: (tf.one_hot(X_batch, depth=max_id), Y_batch))
# 搭建模型
model = keras.models.Sequential([
keras.layers.GRU(128, return_sequences=True, input_shape=[None, max_id],
#dropout=0.2, recurrent_dropout=0.2),把这行注释掉是为了加快模型训练速度
dropout=0.2),
keras.layers.GRU(128, return_sequences=True,
#dropout=0.2, recurrent_dropout=0.2),注释原因同上
dropout=0.2),
keras.layers.TimeDistributed(keras.layers.Dense(max_id,
activation="softmax"))
])
# 编译模型
model.compile(loss="sparse_categorical_crossentropy", optimizer="adam")
#开始训练
history = model.fit(dataset, epochs=10)
# 保存模型权重
model.save_weights('Char-RNN.tf')
# 保存训练历史
import pickle
with open('Char-RNN', 'wb') as file_pi:
pickle.dump(history.history, file_pi)
搭建并加载模型(在外部机器训练或直接导入我附件里的数据的话需要这一步,如果上一步在本机训练,则跳过这一步)
# 搭建模型
model = keras.models.Sequential([
keras.layers.GRU(128, return_sequences=True, input_shape=[None, max_id],
#dropout=0.2, recurrent_dropout=0.2),
dropout=0.2),
keras.layers.GRU(128, return_sequences=True,
#dropout=0.2, recurrent_dropout=0.2),
dropout=0.2),
keras.layers.TimeDistributed(keras.layers.Dense(max_id,
activation="softmax"))
])
# 导入权重
model.load_weights("./Char-RNN/Char-RNN.tf")
# 编译模型
model.compile(loss="sparse_categorical_crossentropy", optimizer="adam")
# 加载训练历史
import pickle
with open('./Char-RNN/Char-RNN', 'rb') as f:
history = pickle.load(f)
编写函数,将输入的字符转换为独热编码
def preprocess(texts):
X = np.array(tokenizer.texts_to_sequences(texts)) - 1
return tf.one_hot(X, max_id)
模型使用:输入字符串,模型预测下一个字符
输入How are yo作为例子:
X_new = preprocess(["I'm painfu"])
Y_pred = np.argmax(model(X_new), axis=-1)
tokenizer.sequences_to_texts(Y_pred + 1)[0][-1] # 1st sentence, last char
输出:
'u'
预测成功!
Comments | NOTHING