今天完成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.努力提高从设计到编码的动手能力
最后附上我最近的心得:
当一切的前提工作都做好的时候,成功就会变成一件很自然的事情。