- 全部代码:点击这里查看
- 关于
Tensorflow
实现一个简单的二元序列的例子可以点击这里查看 - 关于
RNN
和LSTM
的基础可以查看这里 - 这篇博客主要包含以下内容
- 训练一个
RNN
模型逐字符生成文本数据(最后的部分) - 使用
Tensorflow
的scan
函数实现dynamic_rnn
动态创建的效果 - 使用
multiple RNN
创建多层的RNN
- 实现
Dropout
和Layer Normalization
的功能
- 训练一个
一、模型说明和数据处理
1、模型说明
- 我们要使用
RNN
学习一个语言模型(language model
)去生成字符序列 githbub
上有别人实现好的Torch
中的实现:https://github.com/karpathy/char-rnnTensorflow
中的实现:https://github.com/sherjilozair/char-rnn-tensorflow
- 接下来我们来看如何实现
2、数据处理
- 数据集使用莎士比亚的一段文集,点击这里查看, 实际也可以使用别的
- 大小写字符视为不同的字符
- 下载并读取数据
|
|
处理字符数据,转换为数字
- 使用
set
去重,得到所有的唯一字符 - 然后一个字符对应一个数字(使用字典)
- 然后遍历原始数据,得到所有字符对应的数字12345678'''处理字符数据,转换为数字'''vocab = set(raw_data) # 使用set去重,这里就是去除重复的字母(大小写是区分的)vocab_size = len(vocab)idx_to_vocab = dict(enumerate(vocab)) # 这里将set转为了字典,每个字符对应了一个数字0,1,2,3..........(vocab_size-1)vocab_to_idx = dict(zip(idx_to_vocab.values(), idx_to_vocab.keys())) # 这里将字典的(key, value)转换成(value, key)data = [vocab_to_idx[c] for c in raw_data] # 处理raw_data, 根据字符,得到对应的value,就是数字del raw_data
- 使用
生成
batch
数据Tensorflow models
给出的PTB模型:https://github.com/tensorflow/models/tree/master/tutorials/rnn/ptb
|
|
- ptb_iterator函数实现:
- 返回数据
X,Y
的shape=[batch_size, num_steps]
1234567891011121314151617181920212223242526272829303132def ptb_iterator_oldversion(raw_data, batch_size, num_steps):"""Iterate on the raw PTB data.This generates batch_size pointers into the raw PTB data, and allowsminibatch iteration along these pointers.Args:raw_data: one of the raw data outputs from ptb_raw_data.batch_size: int, the batch size.num_steps: int, the number of unrolls.Yields:Pairs of the batched data, each a matrix of shape [batch_size, num_steps].The second element of the tuple is the same data time-shifted to theright by one.Raises:ValueError: if batch_size or num_steps are too high."""raw_data = np.array(raw_data, dtype=np.int32)data_len = len(raw_data)batch_len = data_len // batch_sizedata = np.zeros([batch_size, batch_len], dtype=np.int32)for i in range(batch_size):data[i] = raw_data[batch_len * i:batch_len * (i + 1)]epoch_size = (batch_len - 1) // num_stepsif epoch_size == 0:raise ValueError("epoch_size == 0, decrease batch_size or num_steps")for i in range(epoch_size):x = data[:, i*num_steps:(i+1)*num_steps]y = data[:, i*num_steps+1:(i+1)*num_steps+1]yield (x, y)
- 返回数据
- ptb_iterator函数实现:
二、使用tf.scan
函数和dynamic_rnn
1、为什么使用tf.scan
和dynamic_rnn
- 之前我们实现的第一个例子中没有用
dynamic_rnn
的部分是将输入的三维数据[batch_size,num_steps, state_size]
按num_steps
维度进行拆分,然后每计算一步都存到list
列表中,如下图
- 这种构建方式很耗时,在我们例子中没有体现出来,但是如果我们要学习的步数很大(
num_steps
,也可以说要学习的依赖关系很长),如果再使用深层的RNN,这种就不合适了 为了方便比较和
dynamic_rnn
的运行耗时,下面还是给出使用list
2、使用
list
的方式(static_rnn
)构建计算图
- 我这里
tensorflow
的版本是1.2.0
,与1.0
些许不一样 - 和之前的例子差不多,这里不再累述12345678910111213141516171819202122232425262728293031323334353637383940414243444546'''使用list的方式'''def build_basic_rnn_graph_with_list(state_size = state_size,num_classes = num_classes,batch_size = batch_size,num_steps = num_steps,num_layers = 3,learning_rate = learning_rate):reset_graph()x = tf.placeholder(tf.int32, [batch_size, num_steps], name='x')y = tf.placeholder(tf.int32, [batch_size, num_steps], name='y')x_one_hot = tf.one_hot(x, num_classes) # (batch_size, num_steps, num_classes)'''这里按第二维拆开num_steps*(batch_size, num_classes)'''rnn_inputs = [tf.squeeze(i,squeeze_dims=[1]) for i in tf.split(x_one_hot, num_steps, 1)]cell = tf.nn.rnn_cell.BasicRNNCell(state_size)init_state = cell.zero_state(batch_size, tf.float32)'''使用static_rnn方式'''rnn_outputs, final_state = tf.contrib.rnn.static_rnn(cell=cell, inputs=rnn_inputs,initial_state=init_state)#rnn_outputs, final_state = tf.nn.rnn(cell, rnn_inputs, initial_state=init_state) # tensorflow 1.0的方式with tf.variable_scope('softmax'):W = tf.get_variable('W', [state_size, num_classes])b = tf.get_variable('b', [num_classes], initializer=tf.constant_initializer(0.0))logits = [tf.matmul(rnn_output, W) + b for rnn_output in rnn_outputs]y_as_list = [tf.squeeze(i, squeeze_dims=[1]) for i in tf.split(y, num_steps, 1)]#loss_weights = [tf.ones([batch_size]) for i in range(num_steps)]losses = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y_as_list,logits=logits)#losses = tf.nn.seq2seq.sequence_loss_by_example(logits, y_as_list, loss_weights) # tensorflow 1.0的方式total_loss = tf.reduce_mean(losses)train_step = tf.train.AdamOptimizer(learning_rate).minimize(total_loss)return dict(x = x,y = y,init_state = init_state,final_state = final_state,total_loss = total_loss,train_step = train_step)
- 我这里
训练神经网络函数
- 和之前例子类似123456789101112131415161718192021222324252627'''训练rnn网络的函数'''def train_rnn(g, num_epochs, num_steps=num_steps, batch_size=batch_size, verbose=True, save=False):tf.set_random_seed(2345)with tf.Session() as sess:sess.run(tf.initialize_all_variables())training_losses = []for idx, epoch in enumerate(gen_epochs(num_epochs, num_steps, batch_size)):training_loss = 0steps = 0training_state = Nonefor X, Y in epoch:steps += 1feed_dict = {g['x']: X, g['y']: Y}if training_state is not None:feed_dict[g['init_state']] = training_statetraining_loss_, training_state, _ = sess.run([g['total_loss'],g['final_state'],g['train_step']],feed_dict=feed_dict)training_loss += training_loss_if verbose:print('epoch: {0}的平均损失值:{1}'.format(idx, training_loss/steps))training_losses.append(training_loss/steps)if isinstance(save, str):g['saver'].save(sess, save)return training_losses
- 和之前例子类似
调用执行:
123456start_time = time.time()g = build_basic_rnn_graph_with_list()print("构建图耗时", time.time()-start_time)start_time = time.time()train_rnn(g, 3)print("训练耗时:", time.time()-start_time)运行结果
- 构建计算图耗时:
113.43532419204712
3
个epoch
运行耗时:1234epoch: 0的平均损失值:3.6314958388777985epoch: 1的平均损失值:3.287133811534136epoch: 2的平均损失值:3.250853428895446训练耗时: 84.2816972732544
- 构建计算图耗时:
可以看出在构建图的时候非常耗时,这里仅仅一层的cell
3、dynamic_rnn
的使用
- 之前在我们第一个例子中实际已经使用过了,这里使用
MultiRNNCell
实现多层cell,具体下面再讲 - 构建模型:
tf.nn.embedding_lookup(params, ids)
函数是在params
中查找ids
的表示, 和在matrix
中用array
索引类似, 这里是在二维embeddings中找二维的ids,ids
每一行中的一个数对应embeddings
中的一行,所以最后是[batch_size, num_steps, state_size]
,关于具体的输出可以查看这里- 这里我认为就是某个字母的表示,之前上面我们的
statci_rnn
就是one-hot
来表示的
|
|
- 调用执行即可
|
|
- 运行结果(注意这是3层的LSTM):
- 构建计算图耗时
7.616888523101807
,相比第一种static_rnn
很快 - 训练耗时(这是3层的LSTM,所以还是挺慢的):1234epoch: 0的平均损失值:3.604653576324726epoch: 1的平均损失值:3.3202743626188957epoch: 2的平均损失值:3.3155322650383257训练耗时: 303.5468375682831
- 构建计算图耗时
4、tf.scan
实现的方式
- 如果你不了解
tf.scan
,建议看下官方API, 还是有点复杂的。- 或者Youtube上有个介绍,点击这里查看
scan
是个高阶函数,一般的计算方式是:给定一个序列$[x_0, x_1,…..,x_n]$和初试状态$y_{-1}$,根据$y_t = f(x_t, y_{t-1})$ 计算得到最终序列$[y_0, y_1,……,y_n]$- 构建计算图
tf.transpose(rnn_inputs, [1,0,2])
是将rnn_inputs
的第一个和第二个维度调换,即变成[num_steps,batch_size, state_size]
, 在dynamic_rnn
函数有个time_major参数,就是指定num_steps
是否在第一个维度上,默认是false
的,即不在第一维tf.scan
会将elems
按照第一维拆开,所以一次就是一个step
的数据(和我们static_rnn
的例子类似)- 参数a的结构和initializer的结构一致,所以
a[1]
就是对应的state
,cell
需要传入x
和state
计算 - 每次迭代
cell
返回的是一个rnn_output, shape=(batch_size,state_size)
和对应的state
,num_steps
之后的rnn_outputs
的shape
就是(num_steps, batch_size, state_size)
,state
同理 - 每次输入的
x
都会得到的state-->(final_states)
,我们只要的最后的final_state
|
|
- 运行结果
- 构建计算图耗时:
8.685610055923462
(比dynamic_rnn
稍微慢一点) - 训练耗时(和
dynamic_rnn
耗时差不多)
- 构建计算图耗时:
- 使用
scan
的方式只比dynamic_rnn
慢一点点,但是对我们来说更加灵活和清楚执行的过程。也方便我们修改代码(比如从state
的t-2
时刻跳过一个时刻直接到t
)
|
|
三、关于多层RNN
1、结构
LSTM
中包含两个state
,一个是c
记忆单元(memory cell
),另外一个是h
隐藏状态(hidden state
), 在Tensorflow
中是以tuple
元组的形式,所以才有上面构建dynamic_rnn
时的参数state_is_tuple
的参数,这种方式执行更快- 多层的结构如下图
- 我们可以将其包装起来, 看起来像一个
cell
一样
2、代码
Tensorflow
中的实现就是使用tf.nn.rnn_cell.MultiRNNCell
- 声明一个
cell
MultiRNNCell
中传入[cell]*num_layers
就可以了- 注意如果是
LSTM
,定义参数state_is_tuple=True
123cell = tf.nn.rnn_cell.LSTMCell(num_units=state_size, state_is_tuple=True)cell = tf.nn.rnn_cell.MultiRNNCell(cells=[cell]*num_layers, state_is_tuple=True)init_state = cell.zero_state(batch_size, dtype=tf.float32)
- 声明一个
四、Dropout操作
- 应用在一层
cell
的输入和输出,不应用在循环的部分
1、一层的cell
static_rnn
中实现- 声明
placeholder
:keep_prob = tf.placeholder(tf.float32, name='keep_prob')
- 输入:
rnn_inputs = [tf.nn.dropout(rnn_input, keep_prob) for rnn_input in rnn_inputs]
- 输出:
rnn_outputs = [tf.nn.dropout(rnn_output, keep_prob) for rnn_output in rnn_outputs]
feed_dict
中加入即可:feed_dict = {g['x']: X, g['y']: Y, g['keep_prob']: keep_prob}
- 声明
dynamic_rnn
或者scan
中实现- 直接添加即可,其余类似:
rnn_inputs = tf.nn.dropout(rnn_inputed, keep_prob)
- 直接添加即可,其余类似:
2、多层cell
- 我们之前说使用
MultiRNNCell
将多层cell
看作一个cell
, 那么怎么实现对每层cell
使用dropout
呢 - 可以使用
tf.nn.rnn_cell.DropoutWrapper
来实现 - 方式一:
cell = tf.nn.rnn_cell.DropoutWrapper(cell, input_keep_prob=input_keep_prob, output_keep_prob=output_drop_prob)
- 如果同时使用了
input_keep_prob
和output_keep_prob
都是0.9
, 那么层之间的drop_out=0.9*0.9=0.81
- 如果同时使用了
- 方式二: 对于
basic cell
只使用一个input_keep_prob
或者output_keep_prob
,对MultiRNNCell
也使用一个input_keep_prob
或者output_keep_prob
|
|
五、层标准化 (Layer Normalization
)
1、说明
Layer Normalization
是受Batch Normalization
的启发而来,针对于RNN,可以查看相关论文Batch Normalization
主要针对于传统的深度神经网络和CNN,关于Batch Normalization
的操作和推导可以看我之前的博客- 可以加快训练的速度,得到更好的结果等
2、代码
- 找到
LSTMCell
的源码拷贝一份修改即可 layer normalization
函数- 传入的
tensor
是二维的,对其进行batch normalization
操作 tf.nn.moment
是计算tensor
的mean value
和variance value
- 然后对其进行缩放(
scale
)和平移(shift
)123456789101112131415'''layer normalization'''def ln(tensor, scope=None, epsilon=1e-5):assert(len(tensor.get_shape()) == 2)m, v = tf.nn.moments(tensor, [1], keep_dims=True)if not isinstance(scope, str):scope = ''with tf.variable_scope(scope+'layer_norm'):scale = tf.get_variable(name='scale',shape=[tensor.get_shape()[1]],initializer=tf.constant_initializer(1))shift = tf.get_variable('shift',[tensor.get_shape()[1]],initializer=tf.constant_initializer(0))LN_initial = (tensor - m) / tf.sqrt(v + epsilon)return LN_initial*scale + shift
- 传入的
LSTMCell
中的call
方法i,j,f,o
调用layer normalization
操作_linear
函数中的bias
设为False
, 因为BN
会加上shift
123456789'''这里bias设置为false, 因为bn会加上shift'''lstm_matrix = _linear([inputs, m_prev], 4 * self._num_units, bias=False)i, j, f, o = array_ops.split(value=lstm_matrix, num_or_size_splits=4, axis=1)'''执行ln'''i = ln(i, scope = 'i/')j = ln(j, scope = 'j/')f = ln(f, scope = 'f/')o = ln(o, scope = 'o/')
构建计算图
- 可以选择
RNN GRU LSTM
Dropout
Layer Normalization
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960'''最终的整合模型,- 普通RNN,GRU,LSTM- dropout- BN'''from LayerNormalizedLSTMCell import LayerNormalizedLSTMCell # 导入layer normalization的LSTMCell 文件def build_final_graph(cell_type = None,state_size = state_size,num_classes = num_classes,batch_size = batch_size,num_steps = num_steps,num_layers = 3,build_with_dropout = False,learning_rate = learning_rate):reset_graph()x = tf.placeholder(tf.int32, [batch_size, num_steps], name='x')y = tf.placeholder(tf.int32, [batch_size, num_steps], name='y')keep_prob = tf.placeholder(tf.float32, name='keep_prob')embeddings = tf.get_variable('embedding_matrix', [num_classes, state_size])rnn_inputs = tf.nn.embedding_lookup(embeddings, x)if cell_type == 'GRU':cell = tf.nn.rnn_cell.GRUCell(state_size)elif cell_type == 'LSTM':cell = tf.nn.rnn_cell.LSTMCell(state_size, state_is_tuple=True)elif cell_type == 'LN_LSTM':cell = LayerNormalizedLSTMCell(state_size) # 自己修改的代码,导入对应的文件else:cell = tf.nn.rnn_cell.BasicRNNCell(state_size)if build_with_dropout:cell = tf.nn.rnn_cell.DropoutWrapper(cell, input_keep_prob=keep_prob)init_state = cell.zero_state(batch_size, tf.float32)'''dynamic_rnn'''rnn_outputs, final_state = tf.nn.dynamic_rnn(cell, rnn_inputs, initial_state=init_state)with tf.variable_scope('softmax'):W = tf.get_variable('W', [state_size, num_classes])b = tf.get_variable('b', [num_classes], initializer=tf.constant_initializer(0.0))rnn_outputs = tf.reshape(rnn_outputs, [-1, state_size])y_reshaped = tf.reshape(y, [-1])logits = tf.matmul(rnn_outputs, W) + bpredictions = tf.nn.softmax(logits)total_loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=y_reshaped))train_step = tf.train.AdamOptimizer(learning_rate).minimize(total_loss)return dict(x = x,y = y,keep_prob = keep_prob,init_state = init_state,final_state = final_state,total_loss = total_loss,train_step = train_step,preds = predictions,saver = tf.train.Saver())
- 可以选择
六、生成文本
1、说明
- 训练完成之后将计算图保存到本地磁盘,下次直接读取就可以了
- 我们给出第一个字符,
RNN
接着一个个生成字符,每次都是根据前一个字符- 所以
num_steps=1
,batch_size=1
(可以想象生成prediction
的shape
是(1, num_classes)
中选择一个概率,–>num_steps=1
)
- 所以
2、代码
- 构建图(直接传入参数即可):
g = build_final_graph(cell_type='LN_LSTM', num_steps=1, batch_size=1)
生成文本
- 读取训练好的文件
- 得到给出的第一个字符对应的数字
- 循环遍历要生成多少个字符, 每次循环生成一个字符123456789101112131415161718192021222324252627'''生成文本'''def generate_characters(g, checkpoint, num_chars, prompt='A', pick_top_chars=None):with tf.Session() as sess:sess.run(tf.global_variables_initializer())g['saver'].restore(sess, checkpoint) # 读取文件state = Nonecurrent_char = vocab_to_idx[prompt] # 得到给出的字母对应的数字chars = [current_char]for i in range(num_chars): # 总共生成多少数字if state is not None: # 第一次state为None,因为计算图中定义了刚开始为0feed_dict={g['x']: [[current_char]], g['init_state']: state} # 传入当前字符else:feed_dict={g['x']: [[current_char]]}preds, state = sess.run([g['preds'],g['final_state']], feed_dict) # 得到预测结果(概率)preds的shape就是(1,num_classes)if pick_top_chars is not None: # 如果设置了概率较大的前多少个p = np.squeeze(preds)p[np.argsort(p)[:-pick_top_chars]] = 0 # 其余的置为0p = p / np.sum(p) # 因为下面np.random.choice函数p的概率和要求是1,处理一下current_char = np.random.choice(vocab_size, 1, p=p)[0] # 根据概率选择一个else:current_char = np.random.choice(vocab_size, 1, p=np.squeeze(preds))[0]chars.append(current_char)chars = map(lambda x: idx_to_vocab[x], chars)result = "".join(chars)print(result)return result
结果
- 由于训练耗时很长,这里使用
LSTM
训练了30
个epoch
,结果如下 - 可以自己调整参数,可能会得到更好的结果123456789101112131415161718192021222324252627282930313233ANKO: HFOFMFRone s the statlighte thithe thit.BODEN --I I's a tomir.I'tshis and on ar tald the theand this he sile be cares hat s ond tho fo hour he singe sime shind and somante tat ond treang tatsing of the an the to to fook.. Ir ard the with ane she stale..ANTE --KINEShow the ard and a beat the weringe be thing or.Bo hith tho he melan to the mute steres.The singer stis ard stis.BACE CANKONS CORESard the sids ing tho the the sackes tom theINWe stoe shit a dome thorate seomser hith.Thatthow oundTANTONT. SEAT THONTITE SERTI 1 23SHe the mathe a tomonerind is ingit ofres treacentit. Sher stard on this the tor an the candin he whor he sath heres andstha dortour tit thas stand. I'd and or a
- 由于训练耗时很长,这里使用
#2017/06/25 运行结果更新
- 更换了一个大点的数据集,点击查看,使用了
layer normalized
的LSTM
模型 - 参数设置:
num_steps=80
batch_size=50
state_size=512
num_classes = vocab_size
learning_rate = 5e-4
30
个epochs
- 在实验室电脑跑了一晚上,结果是不是好一点了1234567891011121314151617181920212223242526272829303132333435AKTIN: Yousa hand it have to turn you, sir.I have. I've got to here hard on myplay as a space state, and why hehappened. What we alwaws whothis?JOCASTAND :PADMYou, sir!A battle. An arm of the ship is still.THE WINDEN'S CORUSHan's laser guns at the forest fire. The crowd spots his blackfolkwark and sees the bedroom and twists and sees Leiawho is shaking. A huge creature has a long time,hold her hand and his timmed, that we see the saulyand. Thecrowd ruised by the staircase.EXT. MAZ' CASTLE RUINS - DAYRey and Wicket and CAMERA is heard. Here as so they helfthis tonight, he spins and sit in a startled bright.LUKE(into propecy)The defenstity! Thank you.LUKEI'm afraid to have a lossing live,or help. We're
- 更换了一个大点的数据集,点击查看,使用了
Reference
- https://r2rt.com/recurrent-neural-networks-in-tensorflow-ii.html
- https://karpathy.github.io/2015/05/21/rnn-effectiveness/
- http://jmlr.org/proceedings/papers/v37/ioffe15.pdf
tensorflow scan
:
- 本文链接: http://lawlite.me/2017/06/21/RNN-LSTM循环神经网络-03Tensorflow进阶实现/
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议 。转载请注明出处!