今天完成Tensorflow的第二个项目学习与实战——“作诗机器人”,也就是能够自动生成古诗,并且可以生成藏头诗,感觉比较有意思。
其基本原理就是我上一篇博客中的“基于RNN的语言模型”,这个项目相当于是对这个模型的实战。
参考博客:http://blog.topspeedsnail.com/archives/10542
首先介绍实验平台和数据。
实验平台:
tensorflow 0.12
python 3.5
数据集可以从上面提供的链接博客中下载,数据集长这个样子:
代码及我个人理解的注释:
import collections
import numpy as np
import tensorflow as tf
#数据集,文件应放在项目目录下
poetry_file='poetry.txt'
poetrys=[]
#with-open结构打开文件,可以自动关闭文件
#比较一下读表格csv文件时用到的pandas.resd_csv(),文本文件用open()
with open(poetry_file,'r',encoding='utf-8') as f:
# f是一个可以迭代的对象 支持for line in f
for line in f:
try:
# strip() 方法用于移除字符串头尾指定的字符(默认为空格)
#通过“:”来从一行中分开了题目和内容,通过观察数据集这里似乎有点问题
title,content=line.strip().split(':')
#替换掉空格
content=content.replace(' ','')
#如果有特殊字符的话 不参与训练
if '_' in content or '(' in content or '(' in content or '《' in content or '[' in content:
continue
#对content的长度进行了限制 <5似乎好理解 79哪里来的??
if len(content)<5 or len(content)>79:
continue
#content是个字符串,这个操作是为啥目前没懂
content='['+content+']'
#添加到训练集中,注意这时候每个数据是string类型,两边有[]包围
poetrys.append(content)
except Exception as e:
#pass就是什么也不做,只是为了防止语法错误
pass
#s通过上面的代码,将数据集放在了poetrys这个列表中,而且只是放了content
#sorted(iterable[, cmp[, key[, reverse]]])
#key后面的lambda是一个匿名函数 参数为line 这个Line是从poetrys中取出的 line的值len(line)就是比较的元素 默认升序
poetrys = sorted(poetrys,key=lambda line:len(line))
print('唐诗总数: ',len(poetrys))
all_words=[]
for poetry in poetrys:
#poetry是一个string类型,可迭代对象
#z这是一个列表相加的操作,相当于两个列表合并
#比如:a = [1,2,3],b = [4,5,6],c = a+b,c的结果:[1,2,3,4,5,6]
all_words+=[word for word in poetry]
#所以这个all_words就是把所有的word弄成一块了
#Counter类:为hashable对象计数,是字典的子类。引入自2.7。
#Counter类的目的是用来跟踪值出现的次数。它是一个无序的容器类型,以字典的键值对形式存储,其中元素作为key,其计数作为value。
counter = collections.Counter(all_words)
# item()方法,转为(elem, cnt)格式的列表
#所以x就表示一个(elem, cnt),x[1]也就是cnt的值了,加上负号后,这样原本默认的升序排序变成了降序
#排序结果是一个(elem, cnt)格式的列表,且按字出现的频率由高到低
count_pairs=sorted(counter.items(),key=lambda x:-x[1])
#zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
#利用 * 号操作符,可以将元组解压为列表
#所以words是一个[[]]格式的容器,内层[]是[elem,cnt],且所有的[]按字出现频率由高到低排序
#第二个返回值没看懂
words,_=zip(*count_pairs)
#依旧没看懂
#暂时认为words变成了一个字的列表
words=words[:len(words)]+(' ',)
#range返回一个从1到len(words)的列表
#通过zip函数打包成[(word1,1),(word2,2)...]形式
#通过dict函数变成了字典{word1:1,word2:2..},这里的1,2其实表明了word出现的频率排名
word_num_map=dict(zip(words,range(len(words))))
#get返回指定键的值,也就是word的数字ID值,默认返回words大小,
#该匿名函数的参数为word,返回指定word在words中的位置
to_num=lambda word:word_num_map.get(word,len(words))
#map()函数接收两个参数,一个是函数,一个是序列,map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回。
poetrys_vector=[list(map(to_num,poetry)) for poetry in poetrys]
#这样得到的结果就是得到了一个[[]]二维列表,里面一维是每个poetry形成的list
#实现了文字的向量化
#通常训练模型都是分batch的
batch_size=64
#块数
n_chunk=len(poetrys_vector)//batch_size
x_batches=[]
y_batches=[]
for i in range(n_chunk):
start_index=i*batch_size
end_index=start_index+batch_size
#取出batches训练集
batches=poetrys_vector[start_index:end_index]
#lengh为最长的poetry的长度
length=max(map(len,batches))
#返回给定要求(形状、数据类型)填充的矩阵
#用空格所对应的num填充,所以明白了之前的代码为什么words=words[:len(words)]+(' ',)
#空格对应的Num应该是len(words),也就是字的个数
xdata=np.full((batch_size,length),word_num_map[' '],np.int32)
for row in range(batch_size):
#batches[row]是一个列表,记录了batches中第row个诗句对应的向量如[33,285,675,...]
xdata[row,:len(batches[row])]=batches[row]
ydata=np.copy(xdata)
ydata[:,:-1]=xdata[:,1:]
"""
xdata ydata
[6,2,4,6,9] [2,4,6,9,9]
[1,4,2,8,5] [4,2,8,5,5]
"""
#为什么这么做,去理解语言模型的原理
x_batches.append(xdata)
y_batches.append(ydata)
#通过上面的代码,x_batches变成了一个二维列表,里面的列表元素就是一个batch大小的训练数据,上面所有的代码就是数据的预处理过程
#定义RNN网络
#定义占位符作为数据输入
input_data=tf.placeholder(tf.int32,[batch_size,None])
output_targets=tf.placeholder(tf.int32,[batch_size,None])
#参数:模型类型、RNN隐藏层神经元个数,RNN隐藏层层数,默认是2层隐藏层的128个神经元的网络
def neural_network(model='lstm',rnn_size=128,num_layers=2):
#cell-fun为类
if model=='rnn':
cell_fun=tf.nn.rnn_cell.BasicRNNCell
elif model=='gru':
cell_fun=tf.nn.rnn_cell.GRUCell
elif model=='lstm':
cell_fun=tf.nn.rnn_cell.BasicLSTMCell
#调用类的构造器,cell为类的实例
#创建rnn层,run_size个神经元
#官方建议state_is_tuple=True,此时,输入和输出的states为c(cell状态)和h(输出)的二元组,还记得LSTM的内部结构吗
#输入、输出、cell状态的维度相同,都是 batch_size * num_units,
#建议直接读源代码
cell =cell_fun(rnn_size,state_is_tuple=True)
# MultiRNNCell也是一个类
# 参数cells: list of RNNCells that will be composed in this order.,是一个列表,这里写作[cell]*num_layers
# *操作符去构建list中的重复元素,[cell]*num_layers生成的是[cell,cell]
cell = tf.nn.rnn_cell.MultiRNNCell([cell] * num_layers, state_is_tuple=True)
# cell为多层的RNN
# 状态tensor初始化为0
initial_state = cell.zero_state(batch_size, tf.float32)
with tf.variable_scope('rnnlm'):
#定义隐藏层与输出层连接权重
#run_size也就是隐藏层神经元个数(输出个数),连接len(words)+1个输出层神经元,此处为何+1,应该是给其他单词留着
softmax_w=tf.get_variable("softmax_w",[rnn_size,len(words)+1])
softmax_b=tf.get_variable("softmax_b",[len(words)+1])
with tf.device("/cpu:0"):
#embedding技术:X所属空间的单词映射为到Y空间的多维向量
#word embedding,就是找到一个映射或者函数,生成在一个新的空间上的表达
#所有的embedding都是随机初始化,然后在训练过程中不断更新embedding矩阵的值。
#因为我们input embedding后的结果就直接输入给了第一层cell,所以我们就确定下来embedding matrix shape=[vocab_size, hidden_units_size]
embedding = tf.get_variable("embedding",[len(words)+1,rnn_size])
#此处得到的inputs是embedding矩阵
inputs=tf.nn.embedding_lookup(embedding,input_data)
#TensorFlow提供了一个tf.nn.dynamic_rnn函数,使用该函数就相当于调用了n次call函数。即通过{h0,x1, x2, …., xn}直接得{h1,h2…,hn}。
outputs,last_state=tf.nn.dynamic_rnn(cell,inputs,initial_state=initial_state,scope='rnnlm')
#得到的outputs就是time_steps步里所有的输出。它的形状为(batch_size, time_steps, cell.output_size)。state是最后一步的隐状态,它的形状为(batch_size, cell.state_size)。
output=tf.reshape(outputs,[-1,rnn_size])
#output大小为batch_size*run_size
#softmax_w大小为run_size*(len(words)+1)
#logits大小为 batch_size*(len(words)+1)
logits=tf.matmul(output,softmax_w)+softmax_b
#probs就是我们预测下一个词是什么的概率分布
probs=tf.nn.softmax(logits)
return logits,last_state,probs,cell,initial_state
#训练网络
def train_neural_network():
logits, last_state,probs, _, _ = neural_network()
#前面定义过output_targets=tf.placeholder(tf.int32,[batch_size,None])
targets = tf.reshape(output_targets, [-1])
"""
tf.nn.seq2seq.sequence_loss_by_example
这个函数用于计算所有examples(假设一句话有n个单词,一个单词及单词所对应的label就是一个example,所有example就是一句话中所有单词)的加权交叉熵损失,
logits参数是一个2D Tensor构成的列表对象,每一个2D Tensor的尺寸为[batch_size x num_decoder_symbols],
函数的返回值是一个1D float类型的Tensor,尺寸为batch_size,其中的每一个元素代表当前输入序列example的交叉熵。
进一步理解:
logits 的shape = [batch_size, vocab_size], vocab_size是(分类)类别的个数
targets 的shape = [batch_size]
sequence_loss_by_example的做法是,针对logits中的batch_size个examples中的每一个example,
对所有vocab_size个预测结果,得出预测值最大的那个类别,与target中的值相比较计算Loss值
因此返回值就是一个batch_size大小的float类型的tensor,代表了交叉熵损失
"""
#??????????????????????????
loss = tf.nn.seq2seq.sequence_loss_by_example([logits], [targets], [tf.ones_like(targets, dtype=tf.float32)],
len(words))
#计算损失均值
cost = tf.reduce_mean(loss)
learning_rate = tf.Variable(0.0, trainable=False)
#返回所有已定义的可以训练的变量列表
tvars = tf.trainable_variables()
#gradients是求梯度的函数
"""
Gradient Clipping的引入是为了处理gradient explosion或者gradients vanishing的问题。当在一次迭代中权重的更新过于迅猛的话,很容易导致loss divergence。Gradient Clipping的直观作用就是让权重的更新限制在一个合适的范围。
具体的细节是
1.在solver中先设置一个clip_gradient
2.在前向传播与反向传播之后,我们会得到每个权重的梯度diff,这时不像通常那样直接使用这些梯度进行权重更新,而是先求所有权重梯度的平方和sumsq_diff,如果sumsq_diff > clip_gradient,则求缩放因子scale_factor = clip_gradient / sumsq_diff。这个scale_factor在(0,1)之间。如果权重梯度的平方和sumsq_diff越大,那缩放因子将越小。
3.最后将所有的权重梯度乘以这个缩放因子,这时得到的梯度才是最后的梯度信息。
这样就保证了在一次迭代更新中,所有权重的梯度的平方和在一个设定范围以内,这个范围就是clip_gradient.
tf.clip_by_global_norm
tf.clip_by_global_norm(t_list, clip_norm, use_norm=None, name=None)
通过权重梯度的总和的比率来截取多个张量的值。
t_list 是梯度张量, clip_norm 是截取的比率, 这个函数返回截取过的梯度张量和一个所有张量的全局范数。
t_list[i] 的更新公式如下:
t_list[i] * clip_norm / max(global_norm, clip_norm)
其中global_norm = sqrt(sum([l2norm(t)**2 for t in t_list]))
global_norm 是所有梯度的平方和,如果 clip_norm > global_norm ,就不进行截取
"""
grads, _ = tf.clip_by_global_norm(tf.gradients(cost, tvars), 5)
optimizer = tf.train.AdamOptimizer(learning_rate)
train_op = optimizer.apply_gradients(zip(grads, tvars))
with tf.Session() as sess:
#版本0.12
sess.run(tf.global_variables_initializer())
saver = tf.train.Saver(tf.all_variables())
for epoch in range(10):
#tf.assign(A, new_number): 这个函数的功能主要是把A的值变为new_number
#在这里用于动态调整学习率
sess.run(tf.assign(learning_rate, 0.002 * (0.97 ** epoch)))
n = 0
for batche in range(n_chunk):
train_loss, _, _ = sess.run([cost, last_state, train_op],
feed_dict={input_data: x_batches[n], output_targets: y_batches[n]})
n += 1
print(epoch, batche, train_loss)
if epoch % 5 == 0:
saver.save(sess, './poetry.module', global_step=epoch)
train_neural_network()
下面是使用模型生成古诗:
看了好久的代码,查了很多资料,不得不佩服原作者对Python、RNN、Tensorflow的理解和掌握程度。
下面是我个人的分析:
代码分为4个模块:
数据预处理——定义网络结构——训练网络并保存网络——使用网络生成结果
1.数据预处理部分
代码:
poetry_file = 'poetry.txt'
# 诗集
poetrys = []
with open(poetry_file, "r", encoding='utf-8', ) as f:
for line in f:
try:
title, content = line.strip().split(':')
content = content.replace(' ', '')
if '_' in content or '(' in content or '(' in content or '《' in content or '[' in content:
continue
if len(content) < 5 or len(content) > 79:
continue
content = '[' + content + ']'
poetrys.append(content)
except Exception as e:
pass
# 按诗的字数排序
poetrys = sorted(poetrys, key=lambda line: len(line))
print('唐诗总数: ', len(poetrys))
# 统计每个字出现次数
all_words = []
for poetry in poetrys:
all_words += [word for word in poetry]
counter = collections.Counter(all_words)
count_pairs = sorted(counter.items(), key=lambda x: -x[1])
words, _ = zip(*count_pairs)
# 取前多少个常用字
words = words[:len(words)] + (' ',)
# 每个字映射为一个数字ID
word_num_map = dict(zip(words, range(len(words))))
# 把诗转换为向量形式,参考TensorFlow练习1
to_num = lambda word: word_num_map.get(word, len(words))
poetrys_vector = [list(map(to_num, poetry)) for poetry in poetrys]
# [[314, 3199, 367, 1556, 26, 179, 680, 0, 3199, 41, 506, 40, 151, 4, 98, 1],
# [339, 3, 133, 31, 302, 653, 512, 0, 37, 148, 294, 25, 54, 833, 3, 1, 965, 1315, 377, 1700, 562, 21, 37, 0, 2, 1253, 21, 36, 264, 877, 809, 1]
# ....]
# 每次取64首诗进行训练
batch_size = 64
n_chunk = len(poetrys_vector) // batch_size
x_batches = []
y_batches = []
for i in range(n_chunk):
start_index = i * batch_size
end_index = start_index + batch_size
batches = poetrys_vector[start_index:end_index]
length = max(map(len, batches))
xdata = np.full((batch_size, length), word_num_map[' '], np.int32)
for row in range(batch_size):
xdata[row, :len(batches[row])] = batches[row]
ydata = np.copy(xdata)
ydata[:, :-1] = xdata[:, 1:]
"""
xdata ydata
[6,2,4,6,9] [2,4,6,9,9]
[1,4,2,8,5] [4,2,8,5,5]
"""
x_batches.append(xdata)
y_batches.append(ydata)
首先我们这里分析一下构建“字典”,也就是word_num_map的过程,在第一次读代码的时候是不清楚这个过程的,查资料也没有查到,于是想起来自己还有手,动手做实验试试:
python的交互式编程派上用场:
实验中,首先构造了一个all_words,这个地方不难理解,最终得到了word_num_map。
其实我觉得words可以set(all_words),也就是把这个列表转换成一个集合,然后在集合操作,不用像上面那么麻烦。
zip的功能就可以理解为打包或者拆包,包就是元组。利用*可以实现拆包。
接着,根据构造了一个poetrys列表,定义to_num函数后,结果如图,成功将poetrys向量化,每个数字都是其字在word_num_map中映射得到的。
总结一下,文字向量化是我们的第一步,向量化的基本思路就是利用现有数据构造一个word_num_map,也就是“字典”,然后利用这个“字典”把我们的训练数据文字向量化。
然后就是定义batch,定义batch是训练模型经常用到的地方。
下面的试验是xdata和ydata,这里注意结合语言模型的原理理解xdata和ydata(也就是label),
最终完成了x_batch和y_batch的构建,x_batch、y_batch的形状应该是:
[[[],[],[]],
[[],[],[]],
[[],[],[]]]
这样的,就是把所有的数据都分了batch,然后每个batch里面的元素就是一个向量化后的文字。
另外仔细想想这个地方,就是用
word_num_map[' ']
填充的地方:
假设这个填充值为100,某个xdata=[1,8,3,14,21,100,100,100,100],也就是后面4个是填充的,然后其对应的ydata也就是ydata=[8,3,14,21,100,100,100,100,100]。
1-8,8-3,3-14,14-21都没有问题
21-100....,也就是在告诉模型,如果这首诗遇到了21,其后面有一定概率输出100,也就是对应空格,这个地方可以在使用模型的时候留心处理一下。
由此准备好了训练数据了,一切都很不错。
总结一下,在写数据处理模块代码的时候,一定要搞清楚提供的数据是什么样的,自己的模型接收的数据是什么样的,一般情况下都是分batch的向量数据。思考怎么样从原始数据得到这个数据,需要通过哪些步骤(构造字典、向量化、填充、分batch),然后又需要定义哪些数据结构,最后完成从原始数据结构转化为需要的数据结构。基本功就是熟练掌握python数据类型和相关的操作函数的使用。
2.定义模型部分
数据处理部分不管是什么框架都差不多,差别在于模型定义和训练部分。
本项目使用2层LSTM网络,每层128个节点。
建议跟原作者一样,定义一个函数:
def neural_network(model='lstm',rnn_size=128,num_layers=2): .....
return logits,last_state,probs,cell,initial_state
通过该函数可以调整模型结构,改变网络层数或者节点个数或类型。
数据和标签的输入通常都定义为占位符:
input_data=tf.placeholder(tf.int32,[batch_size,None]) output_targets=tf.placeholder(tf.int32,[batch_size,None])
下面我们比较一下CNN和RNN模型定义的过程。
CNN:
CNN通常用于图像处理分类,输入的数据是一个归一化后的像素值矩阵,比如在手写数字识别项目中,
我们的输入是一个[batch_size,28,28,1],表示单通道,28*28大小的灰度二维数组。
经过不断地“卷积层-池化层-规范层”处理,最后连接一个全连接层和dropout层,输出层激活函数为softmax,输出是一个概率分布向量,将该向量与one-dot化后的标签向量比较,计算交叉熵,通过随即梯度下降优化损失得到参数。
相比RNN而言,CNN真挺简单的,基本上会写一个以后,后面的就知道该怎么写了,因为都差不多。
RNN:
# 定义RNN
def neural_network(model='lstm', rnn_size=128, num_layers=2):
if model == 'rnn':
cell_fun = tf.nn.rnn_cell.BasicRNNCell
elif model == 'gru':
cell_fun = tf.nn.rnn_cell.GRUCell
elif model == 'lstm':
cell_fun = tf.nn.rnn_cell.BasicLSTMCell
cell = cell_fun(rnn_size, state_is_tuple=True)
cell = tf.nn.rnn_cell.MultiRNNCell([cell] * num_layers, state_is_tuple=True)
initial_state = cell.zero_state(batch_size, tf.float32)
with tf.variable_scope('rnnlm'):
softmax_w = tf.get_variable("softmax_w", [rnn_size, len(words) + 1])
softmax_b = tf.get_variable("softmax_b", [len(words) + 1])
with tf.device("/cpu:0"):
embedding = tf.get_variable("embedding", [len(words) + 1, rnn_size])
inputs = tf.nn.embedding_lookup(embedding, input_data)
outputs, last_state = tf.nn.dynamic_rnn(cell, inputs, initial_state=initial_state, scope='rnnlm')
output = tf.reshape(outputs, [-1, rnn_size])
logits = tf.matmul(output, softmax_w) + softmax_b
probs = tf.nn.softmax(logits)
return logits, last_state, probs, cell, initial_state
1.定义RNN层cell
2.用0作为初始化状态
3.对输入embedding处理
4.调用tf.nn.dynamic_rnn获得outputs和last_state
output直接可以与输出层相连作为输出层的输入,last_state将来使用模型的时候会用到,作为下一个预测值的输入state。
所谓定义模型,就是定义网络的结构,比如节点类型、层数、节点个数,权重矩阵、偏置向量、激活函数、初始化等等,以前面定义的Input作为输出产生输出。
3.训练模型部分
# 训练
def train_neural_network():
logits, last_state, _, _, _ = neural_network()
targets = tf.reshape(output_targets, [-1])
loss = tf.nn.seq2seq.sequence_loss_by_example([logits], [targets], [tf.ones_like(targets, dtype=tf.float32)],
len(words))
cost = tf.reduce_mean(loss)
learning_rate = tf.Variable(0.0, trainable=False)
tvars = tf.trainable_variables()
grads, _ = tf.clip_by_global_norm(tf.gradients(cost, tvars), 5)
optimizer = tf.train.AdamOptimizer(learning_rate)
train_op = optimizer.apply_gradients(zip(grads, tvars))
with tf.Session() as sess:
sess.run(tf.initialize_all_variables())
saver = tf.train.Saver(tf.all_variables())
for epoch in range(50):
sess.run(tf.assign(learning_rate, 0.002 * (0.97 ** epoch)))
n = 0
for batche in range(n_chunk):
train_loss, _, _ = sess.run([cost, last_state, train_op],
feed_dict={input_data: x_batches[n], output_targets: y_batches[n]})
n += 1
print(epoch, batche, train_loss)
if epoch % 7 == 0:
saver.save(sess, './poetry.module', global_step=epoch)
模型的训练一般都要用到session,要定义损失计算的op、优化训练的op、训练次数epoch,然后不要忘了save自己训练的模型。
4.使用模型部分
模型的使用模块,原作者定义在了另一个python文件:
代码:
import collections
import numpy as np
import tensorflow as tf
# -------------------------------数据预处理---------------------------#
poetry_file = 'poetry.txt'
# 诗集
poetrys = []
with open(poetry_file, "r", encoding='utf-8', ) as f:
for line in f:
try:
title, content = line.strip().split(':')
content = content.replace(' ', '')
if '_' in content or '(' in content or '(' in content or '《' in content or '[' in content:
continue
if len(content) < 5 or len(content) > 79:
continue
content = '[' + content + ']'
poetrys.append(content)
except Exception as e:
pass
# 按诗的字数排序
poetrys = sorted(poetrys, key=lambda line: len(line))
print('唐诗总数: ', len(poetrys))
# 统计每个字出现次数
all_words = []
for poetry in poetrys:
all_words += [word for word in poetry]
counter = collections.Counter(all_words)
count_pairs = sorted(counter.items(), key=lambda x: -x[1])
words, _ = zip(*count_pairs)
# 取前多少个常用字
words = words[:len(words)] + (' ',)
# 每个字映射为一个数字ID
word_num_map = dict(zip(words, range(len(words))))
# 把诗转换为向量形式,参考TensorFlow练习1
to_num = lambda word: word_num_map.get(word, len(words))
poetrys_vector = [list(map(to_num, poetry)) for poetry in poetrys]
# [[314, 3199, 367, 1556, 26, 179, 680, 0, 3199, 41, 506, 40, 151, 4, 98, 1],
# [339, 3, 133, 31, 302, 653, 512, 0, 37, 148, 294, 25, 54, 833, 3, 1, 965, 1315, 377, 1700, 562, 21, 37, 0, 2, 1253, 21, 36, 264, 877, 809, 1]
# ....]
batch_size = 1
n_chunk = len(poetrys_vector) // batch_size
x_batches = []
y_batches = []
for i in range(n_chunk):
start_index = i * batch_size
end_index = start_index + batch_size
batches = poetrys_vector[start_index:end_index]
length = max(map(len, batches))
xdata = np.full((batch_size, length), word_num_map[' '], np.int32)
for row in range(batch_size):
xdata[row, :len(batches[row])] = batches[row]
ydata = np.copy(xdata)
ydata[:, :-1] = xdata[:, 1:]
"""
xdata ydata
[6,2,4,6,9] [2,4,6,9,9]
[1,4,2,8,5] [4,2,8,5,5]
"""
x_batches.append(xdata)
y_batches.append(ydata)
# ---------------------------------------RNN--------------------------------------#
input_data = tf.placeholder(tf.int32, [batch_size, None])
output_targets = tf.placeholder(tf.int32, [batch_size, None])
# 定义RNN
def neural_network(model='lstm', rnn_size=128, num_layers=2):
if model == 'rnn':
cell_fun = tf.nn.rnn_cell.BasicRNNCell
elif model == 'gru':
cell_fun = tf.nn.rnn_cell.GRUCell
elif model == 'lstm':
cell_fun = tf.nn.rnn_cell.BasicLSTMCell
cell = cell_fun(rnn_size, state_is_tuple=True)
cell = tf.nn.rnn_cell.MultiRNNCell([cell] * num_layers, state_is_tuple=True)
initial_state = cell.zero_state(batch_size, tf.float32)
with tf.variable_scope('rnnlm'):
softmax_w = tf.get_variable("softmax_w", [rnn_size, len(words) + 1])
softmax_b = tf.get_variable("softmax_b", [len(words) + 1])
with tf.device("/cpu:0"):
embedding = tf.get_variable("embedding", [len(words) + 1, rnn_size])
inputs = tf.nn.embedding_lookup(embedding, input_data)
outputs, last_state = tf.nn.dynamic_rnn(cell, inputs, initial_state=initial_state, scope='rnnlm')
output = tf.reshape(outputs, [-1, rnn_size])
logits = tf.matmul(output, softmax_w) + softmax_b
probs = tf.nn.softmax(logits)
return logits, last_state, probs, cell, initial_state
# -------------------------------生成古诗---------------------------------#
# 使用训练完成的模型
def gen_poetry():
def to_word(weights):
t = np.cumsum(weights)
s = np.sum(weights)
sample = int(np.searchsorted(t, np.random.rand(1) * s))
if(sample>len(words)):
sample=len(words)-1
return words[sample]
_, last_state, probs, cell, initial_state = neural_network()
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
saver = tf.train.Saver(tf.all_variables())
saver.restore(sess, './poetry.module-5')
state_ = sess.run(cell.zero_state(1, tf.float32))
x = np.array([list(map(word_num_map.get, '['))])
[probs_, state_] = sess.run([probs, last_state], feed_dict={input_data: x, initial_state: state_})
word = to_word(probs_)
# word = words[np.argmax(probs_)]
poem = ''
while word != ']':
poem += word
x = np.zeros((1, 1))
x[0, 0] = word_num_map[word]
[probs_, state_] = sess.run([probs, last_state], feed_dict={input_data: x, initial_state: state_})
word = to_word(probs_)
# word = words[np.argmax(probs_)]
return poem
print(gen_poetry())
因为要用到字典啊等等以前定义的数据结构或者函数,因此大部分代码有所重复,而且必须一致。
给出我的个人理解:
def gen_poetry():
#这个函数是用来将预测值转换成单词输出的
def to_word(weights):
#求累加和
#比如一个列表是这样[1,2,3,4,5] 返回是这样[1,3,6,10,15]
t = np.cumsum(weights)
# 求和: >>> np.sum([0.5, 1.5]) 2.0
s = np.sum(weights)
#searchsorted 二分法查找a在b中的位置,返回位置索引值
sample = int(np.searchsorted(t, np.random.rand(1) * s))
return words[sample]
#调用训练好的网络得到word
_, last_state, probs, cell, initial_state = neural_network()
with tf.Session() as sess:
sess.run(tf.initialize_all_variables())
saver = tf.train.Saver(tf.all_variables())
saver.restore(sess, './poetry.module-10')
#初始状态为0
state_ = sess.run(cell.zero_state(1, tf.float32))
x = np.array([list(map(word_num_map.get, '['))])
#保留state,既是本次训练最后的状态,也是下一次输入时的初始状态
[probs_, state_] = sess.run([probs, last_state], feed_dict={input_data: x, initial_state: state_})
word = to_word(probs_)
# word = words[np.argmax(probs_)]
poem = ''
while word != ']':
poem += word
x = np.zeros((1, 1))
#x为word在字典中的索引位置,这样的话顺序找下去就可以了
x[0, 0] = word_num_map[word]
[probs_, state_] = sess.run([probs, last_state], feed_dict={input_data: x, initial_state: state_})
word = to_word(probs_)
# word = words[np.argmax(probs_)]
return poem
print(gen_poetry())
基本流程就是,定义一个由数字转化为字的函数to_word
注意这里,while word!=']',我们知道在处理数据的时候,一方面是把句子两边加上了'['']',另一方面对x_batch进行了填充,这里的‘]’恰恰是对应了那个填充的数据,也就是说,其他的数据是不会输出填充数据的。这里也是设计的巧妙之处。
另外留意一下RNN中的state传递的思想。
最后的结果运行很慢= =不过确实运行成功过。
最后致谢原作者,并且表达一下敬佩。
本文写了两天,总共就学着做了两个项目,对tensorflow和python还很不熟练,需要多多练习和学习,尤其是打算再学几个后准备自己开发项目锻炼一下,敬请期待嘻嘻。
心得体会:
1.学着看源代码分析函数的功能和使用方法,还有一定要学着看官方的文档
2.对于网上没有的信息,要自己动手做试验
3.努力提高从设计到编码的动手能力
最后附上我最近的心得:
当一切的前提工作都做好的时候,成功就会变成一件很自然的事情。