机器学习/深度学习个人进阶日志-基于Tensorflow的“作诗机器人”完整版

今天完成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.努力提高从设计到编码的动手能力

最后附上我最近的心得:

当一切的前提工作都做好的时候,成功就会变成一件很自然的事情。

猜你喜欢

转载自blog.csdn.net/qq_33837704/article/details/79528871