tensorflow代码全解析 -3- seq2seq 自动生成文本
2017-04-26 15:02
645 查看
模型概述
序列建模seq2seq,给定一个序列A,模型生产另一个序列B,然后模型再由序列B生成C,以此一直持续下去。基本工作流程如下:
序列A中的每一个单词通过word_embedding操作以后,作为input进seq2seq入模型,模型生成同样维度的序列A_out
训练的时候,模型的输出序列A_out与序列B之间的交叉熵作为模型的目标函数,采用clip控制过的梯度进行收敛;
生成的时候,首先给定一个种子序列作为模型输入,初始化模型,让模型可以自循环,并生产指定长度的序列
模型数据流
第一部分:生成数据
数据源 JayLyrics.txtdataGenerator.py
定义参数
log_dir = './logs' seq_length = 20 # 每一个序列的长度 batch_size = 32 #每一个batch的长度 datafiles = 'JayLyrics.txt'
读取文本,进行词频统计,将文本与数字进行一一对应,方便进行处理
with open(datafiles, encoding='utf-8') as f: data = f.read() total_len = len(data) words = list(set(data)) words.sort() vocab_size = len(words) char2id_dict = {w: i for i, w in enumerate(words)} id2char_dict = {i: w for i, w in enumerate(words)}
进行这一步之后可以得到
将上面的数据对应表进行存储,注意tsv与csv格式不同,是以tab进行分隔的
_pointer = 0 def char2id( c): return char2id_dict[c] def id2char(id): return id2char_dict[id] metadata = 'metadata2.tsv' def save_metadata(file): with open(file, 'w') as f: f.write('id\tchar\n') for i in range(vocab_size): c = id2char(i) f.write('{}\t{}\n'.format(i, c)) save_metadata(metadata)
下面进行关键的将原始文本转换为输入数据序列和输入标签
我们可以看到,文本数据与训练数据集是如何进行转换的。
def next_batch(): _pointer = 0 x_batches = [] y_batches = [] for i in range(batch_size): if _pointer + seq_length + 1 >= total_len: _pointer = 0 bx = data[_pointer: _pointer + seq_length] by = data[_pointer + 1: _pointer + seq_length + 1] _pointer += seq_length # update pointer position # convert to idss bx = [char2id(c) for c in bx] by = [char2id(c) for c in by] x_batches.append(bx) y_batches.append(by) return x_batches, y_batches
data[_pointer: _pointer + seq_length] Out[22]: '作词:黄俊郎 \n作曲:周杰伦\n编曲:黄雨' # 以上是 bx data[_pointer +1: _pointer + seq_length + 1] Out[23]: '词:黄俊郎 \n作曲:周杰伦\n编曲:黄雨勛' # 以上是 by
再将上面的数据转化为数字
eg = next_batch() # 一个batch 就这样生成了 #应注意到输入数据是一个32*20维的数据矩阵 #输入标签同样是一个32*20维的数据矩阵
[char2id(c) for c in bx]
第二部分 构建模型逻辑
构建seq2seq模型
3层LSTMCell 每一层100个神经元state_size = 100 num_layers = 3 #定义神经网络 cell = rnn_cell.BasicLSTMCell(state_size) cell = rnn_cell.MultiRNNCell([cell] * num_layers) # 神经元的初始状态,将神经元设置为全零状态 initial_state = cell.zero_state( batch_size, tf.float32)
将输入数据传递的seq2seq模型中
采用tf.nn.dynamic_rnninputs: The RNN inputs.
Tensor of shape: [batch_size, max_time, …]
dynamic_rnn 返回
outputs: The RNN output
Tensor.
If time_major == False (default), this will be a
Tensorshaped:
[batch_size, max_time, cell.output_size].
state: The final state. If
cell.state_sizeis an int, this
will be shaped
[batch_size, cell.state_size].
with tf.variable_scope('rnnlm'): with tf.device("/cpu:0"): embedding = tf.get_variable( c71d 'embedding', [vocab_size, state_size]) inputs = tf.nn.embedding_lookup(embedding, input_data) # 将输入数据传递的seq2seq模型中 # 采用tf.nn.dynamic_rnn # inputs: The RNN inputs. # Tensor of shape: [batch_size, max_time, ...] outputs, last_state = tf.nn.dynamic_rnn(cell, inputs, initial_state=initial_state)
embedding input 的详细解释 下面是看到的比较好的解释
引用自http://blog.csdn.net/mydear_11000/article/details/52414342
我们预处理了数据之后得到的是一个二维array,每个位置的元素表示这个word在vocabulary中的index。但是传入graph的数据不能讲word用index来表示,这样词和词之间的关系就没法刻画了。我们需要将word用dense vector表示,这也就是广为人知的word embedding。paper中并没有使用预训练的word embedding,所有的embedding都是随机初始化,然后在训练过程中不断更新embedding矩阵的值。
123
with tf.device("/cpu:0"): embedding = tf.get_variable("embedding", vocab_size, state_size]) inputs = tf.nn.embedding_lookup(embedding, self._input_data)
首先要明确几点:
既然我们要在训练过程中不断更新embedding矩阵,那么embedding必须是tf.Variable并且trainable=True(default)
目前tensorflow对于lookup embedding的操作只能再cpu上进行
embedding矩阵的大小是多少:每个word都需要有对应的embedding vector,总共就是vocab_size那么多个embedding,每个word embed成多少维的vector呢?因为我们input embedding后的结果就直接输入给了第一层cell,刚才我们知道cell的hidden units size,因此这个embedding dim要和hidden units size对应上(这也才能和内部的各种门的W和b完美相乘)。因此,我们就确定下来
embedding matrix shape=[vocab_size, hidden_units_size]
最后生成真正的inputs节点,也就是从embedding_lookup之后得到的结果,这个tensor的shape=[batch_size, num_stemps, size]
第三部分 构建模型损失函数和收敛方法
先定义参数w(100*2636)和b(2636)再将output 展开成[-1*state_size]维即是[-1*100]
再用(output*w)+b获得输出序列logits
下面便可以使用sequence_loss_by_example将模型输出logits和训练标签targets计算序列交叉熵
with tf.name_scope('model'): with tf.variable_scope('rnnlm'): w = tf.get_variable( 'softmax_w', [state_size, vocab_size]) b = tf.get_variable('softmax_b', [vocab_size]) with tf.name_scope('loss'): output = tf.reshape(outputs, [-1, state_size]) logits = tf.matmul(output, w) + b probs = tf.nn.softmax(logits) last_state = last_state targets = tf.reshape(target_data, [-1]) # 将targets 展平 维度从(32*20)转化为640 # pass '[-1]' to flatten 't' #reshape(t, [-1]) ==> [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6] loss = seq2seq.sequence_loss_by_example([logits], [targets], [tf.ones_like(targets, dtype=tf.float32)]) cost = tf.reduce_sum(loss) / batch_size #下面采用scalar 是用来在tensorboard 显示数据的 tf.summary.scalar('loss', cost)
第四部分 优化器设定
首先 定义lr 是不可训练的关键地方 clip_by_global_norm的作用
设置梯度的最大范数
Gradient Clipping的方法,控制梯度的最大范数。
可以防止梯度爆炸的问题,如果梯度不加限制,则可能因为迭代中梯度过大导致训练难以收敛。
optimizer.apply_gradients则将前面clip过的梯度应用到所有可以训练的tvars上
with tf.name_scope('optimize'): lr = tf.placeholder(tf.float32, []) tf.summary.scalar('learning_rate', lr) optimizer = tf.train.AdamOptimizer(lr) #获取全部可以训练的参数tvars tvars = tf.trainable_variables() # 提前计算梯度 grads = tf.gradients(cost, tvars) #显示在tensorboard for g in grads: tf.summary.histogram(g.name, g) # 由它们的范数之和之比求多个张量的值 grads, _ = tf.clip_by_global_norm(grads, grad_clip) # 将前面clip过的梯度应用到可训练的参数上 train_op = optimizer.apply_gradients(zip(grads, tvars)) merged_op = tf.summary.merge_all()
第五部分 训练
启动模型,设定tensorboard,导入数据,记录数据with tf.Session() as sess: #启动模型 sess.run(tf.global_variables_initializer()) saver = tf.train.Saver() writer = tf.summary.FileWriter(log_dir, sess.graph) # 设定tensorboard # Add embedding tensorboard visualization. Need tensorflow version # >= 0.12.0RC0 config = projector.ProjectorConfig() embed = config.embeddings.add() embed.tensor_name = 'rnnlm/embedding:0' embed.metadata_path = metadata projector.visualize_embeddings(writer, config) # 导入数据 max_iter = n_epoch * \ (data.total_len // seq_length) // batch_size for i in range(max_iter): learning_rate = learning_rate * \ (decay_rate ** (i // decay_steps)) x_batch, y_batch = data.next_batch() feed_dict = {model.input_data: x_batch, model.target_data: y_batch, model.lr: learning_rate} train_loss, summary, _, _ = sess.run([model.cost, model.merged_op, model.last_state, model.train_op], feed_dict) # 记录数据 if i % 10 == 0: writer.add_summary(summary, global_step=i) print('Step:{}/{}, training_loss:{:4f}'.format(i, max_iter, train_loss)) if i % 2000 == 0 or (i + 1) == max_iter: saver.save(sess, os.path.join( log_dir, 'lyrics_model.ckpt'), global_step=i)
第五部分 生成文本
首先从保存的模型中取出参数,初始化模型再设定种子
将种子进行处理
初始化模型
按照设定的生成数量生成文本
#首先从保存的模型中取出参数,初始化模型 saver = tf.train.Saver() with tf.Session() as sess: ckpt = tf.train.latest_checkpoint(args.log_dir) print(ckpt) saver.restore(sess, ckpt) # 再设定种子 # initial phrase to warm RNN prime = u'你要离开我知道很简单' state = sess.run(cell.zero_state(1, tf.float32)) # 将种子进行处理 # 初始化模型 for word in prime[:-1]: x = np.zeros((1, 1)) x[0, 0] = char2id(word) feed = {input_data: x, initial_state: state} state = sess.run(last_state, feed) # 按照设定的生成数量生成文本 word = prime[-1] lyrics = prime for i in range(args.gen_num): x = np.zeros([1, 1]) x[0, 0] = char2id(word) feed_dict = {input_data: x, initial_state: state} probs, state = sess.run([probs, last_state], feed_dict) p = probs[0] word = id2char(np.argmax(p)) print(word, end='') sys.stdout.flush() time.sleep(0.05) lyrics += word return lyrics
代码还是很复杂很复杂的,看了好几天,还是有些不明白的,
有些只能等以后再慢慢专研,现在主干是抓住了。
相关文章推荐
- Runtime 解析字典自动生成属性代码 及 字典转模型方式一 KVC/Runtime
- 一段程序代码 --- 实现解析字典,自动转换生成属性。
- 自制工具:CSV代码生成器:自动生成CSV文件对应的C++实体类和字段类型解析代码
- visual paradigm 自动解析代码生成 UML图
- XML布局文件在Activity自动生成代码(二)SAX 解析
- UE4中类自动生成代码解析
- TensorFlow 自动文本摘要生成模型,2016
- XML形式的配置文件解析框架以及自动代码生成(一)
- 自制工具:CSV代码生成器:自动生成CSV文件对应的C++实体类和字段类型解析代码
- access如何用代码在“默认值”里实现自动编号?单据流水号生成问题。
- 代码自动生成操作
- 如何高效地使用XCodeFactory自动生成的数据层代码(二)
- 简单代码的自动生成
- C#分析数据库结构,使用XSL模板自动生成代码
- 如何实现自动生成在线播放代码?
- 代码自动生成.
- 代码自动生成工具的补充
- 利用xml自动生成j2ee代码
- 一个VS.net自动生成代码引发的问题
- Hibernate中代码自动生成小结