
本文使用 GRU 单元建立 RNN 网络,使用唐诗三百首进行训练,使用模型生成唐诗。
GRU RNN 网络能够克服简单RNN网络的一些问题,如梯度消失,梯度很难从深层传递到浅层,导致浅层的参数更新非常缓慢,学习速度很慢,还导致深层浅层学习不均衡。
GRU,LSTM 使用更新门,遗忘门,来解决长距离的依赖关系,GRU相比LSTM参数更少。
RNN 网络的还有缺点就是无法采用并行计算,必须在上一个时间步的基础上计算下一个时间步。
# 读取文本
file = "tangshi300.txt"
with open(file,'r',encoding='utf-8') as f:
text = f.read()
print(len(text))
print(text[:180])输出:
29405 # 文本长度
唐诗300首 1-50
010杜甫:佳人
绝代有佳人,幽居在空谷。
自云良家子,零落依草木。
关中昔丧乱,兄弟遭杀戮。
官高何足论,不得收骨肉。
世情恶衰歇,万事随转烛。
夫婿轻薄儿,新人美如玉。
合昏尚知时,鸳鸯不独宿。
但见新人笑,那闻旧人哭!
在山泉水清,出山泉水浊。
侍婢卖珠回,牵萝补茅屋。
摘花不插发,采柏动盈掬。
天寒翠袖薄,日暮倚修竹。# 创建字符序号索引
words = sorted(list(set(text)))
print("字和符号数量:{}".format(len(words)))
word_idx = {w : i for (i, w) in enumerate(words)}
idx_word = {i : w for (i, w) in enumerate(words)}输出:
字和符号数量:2590# 根据文本,创建序列
sample_maxlen = 40 # 样本句子长度
sentences = []
next_word = []
for i in range(len(text)-sample_maxlen):
sentences.append(text[i : i+sample_maxlen]) # 滑窗取出样本句子
next_word.append(text[i+sample_maxlen]) # 句子末尾的下一个字
print("样本数量:{}".format(len(sentences)))样本数量:29365# 将文本序列转化成数字序列(矩阵), 实际上就是一个one_hot 编码
import numpy as np
X = np.zeros((len(sentences), sample_maxlen, len(words)), dtype=np.bool)
# 样本数 1个样本字个数 一个字的OH编码长度
y = np.zeros((len(sentences), len(words)), dtype=np.bool)
# 样本数 一个字的OH编码长度
for i in range(len(sentences)):
for t, w in enumerate(sentences[i]):
X[i, t, word_idx[w]] = 1 # 把 i 样本 t 字符 对应的 OH 编码位置 写为 1
y[i, word_idx[next_word[i]]] = 1# 建模
from keras.models import Sequential
from keras.layers import GRU, Dense
from keras.optimizers import Adam
model = Sequential()
model.add(GRU(units=128,input_shape=(sample_maxlen, len(words)))) # GRU 层
model.add(Dense(units=len(words), activation='softmax')) # FC 层 多分类 softmaxoptimizer = Adam(learning_rate=0.001) # adam 优化器
model.compile(optimizer=optimizer,
loss='categorical_crossentropy',
metrics=['accuracy']) # 配置模型
history = model.fit(X, y, batch_size=128, epochs=500) # 训练
model.save("tangshi_generator_model.h5") # 保存模型import pandas as pd
import matplotlib.pyplot as plt
pd.DataFrame(history.history).plot(figsize=(8, 5)) # 绘制训练曲线
plt.grid(True)
plt.gca().set_ylim(0, 1) # set the vertical range to [0-1]
plt.show()
模型在 100 个 epochs 时已基本上完全拟合了训练数据
def sampling(preds, temperature=1.0):
preds = np.asarray(preds).astype('float64')
preds = np.log(preds)/temperature # 我的理解是概率平滑
exp_preds = np.exp(preds)
preds = exp_preds/np.sum(exp_preds)
probs = np.random.multinomial(1, preds, 1)
# 多项式分布,做n次试验,按照preds的概率分布(和=1),取出size组结果,如下
# >>> np.random.multinomial(20, [1/6.]*6, size=1)
# array([[4, 1, 7, 5, 2, 1]]) # random
return np.argmax(probs) # 返回概率最大的idxfrom keras.models import load_model
import random
model = load_model("tangshi_generator_model.h5")
def generate_tangshi(model, generate_len=200):
start_idx = random.randint(0, len(text)-sample_maxlen-1) # 随机开始位置
generated = ""
sentence = text[start_idx : start_idx + sample_maxlen] # 开始的句子
generated += sentence
print("随机选取的开始句子为:{}".format(generated))
for i in range(generate_len): # 后续要生成的句子长度
x_pred = np.zeros((1, sample_maxlen, len(words)))
for t, w in enumerate(sentence):
x_pred[0, t, word_idx[w]] = 1 # 当前句子的 OH 编码
preds = model.predict(x_pred)[0] # predict 返回 (1,2590)一个样本 2590个类预测值
next_idx = sampling(preds, 1) # 采样出来下一个最有可能的词的idx
next_w = idx_word[next_idx] # 取出这个词
generated += next_w # 加到句子中
sentence = sentence[1:] + next_w # 句子窗口后移一个位置,作为下次预测的输入
return generated
generate_tangshi(model, 100)输出:

模型完全记住了后续的诗句。


with open('test.txt','r',encoding='utf-8') as f:
test_text = f.read()
def generate_tangshi_test(model, generate_len=60):
generated = ""
sentence = test_text[0 : sample_maxlen]
generated += sentence
print("测试文本开始句子为:{}".format(generated))
for i in range(generate_len):
x_pred = np.zeros((1, sample_maxlen, len(words)))
for t, w in enumerate(sentence):
x_pred[0, t, word_idx[w]] = 1
preds = model.predict(x_pred)[0]
next_idx = sampling(preds, 1)
next_w = idx_word[next_idx]
generated += next_w
sentence = sentence[1:] + next_w
return generated
generate_tangshi_test(model, 100)输出:

输出的诗句有部分一整句都是训练集里的,有的则不是,反正没有多少诗的美感,哈哈!