RNN 直观理解
- 一个非常棒的RNN入门Anyone Can learn To Code LSTM-RNN in Python(Part 1: RNN)
- 基于此文章,本文给出我自己的一些愚见
- 基于此文章,给出其中代码的TensorFlow的实现版本。完整代码请看这里
RNN的结构
如果从网上搜索关于RNN的结构图,大概可以下面的结构图
第一次看到这样的图,我是懵逼的,这货怎么有两种形态? 先说结论:
- 左侧是RNN的原始结构, 右侧是RNN在时间上展开的结果
- RNN的结构,本质上和全连接网络相同
为什么可以根据时间维度展开,这主要是因为RNN的的输入是具有时间序列的。这一点是和全连接网络最大的不同,输入决定了RNN的结构
假设RNN的输入是一句话,这句话中有多个单词,那么RNN需要forward多次,如下图
- 橙色部分是上一个时刻的隐层的值,可以直观的理解为“记忆”
- 当前时刻的输出与当前时刻的输入还有记忆有关。
- RNN对一个样本需要做多次forward,这一点与全连接网络不一样,全连接网络对一个样本只做一次forward。
就将RNN看成是全连接网络吧
将RNN看成是全连接网络就能很好的理解它。
- 将上面gif图中的隐层中的每一个神经元看成是LSTM单元,就得到了基于LSTM的RNN
- RNN的输入、输出都和全连接网络一模一样
- RNN只是一个需要做好多次forward的全连接网络
一个RNN的简单例子
- 基于TensorFlow,搭建一个RNN,教会神经网络进行二进制加法。参考Anyone Can learn To Code LSTM-RNN in Python(Part 1: RNN)
import tensorflow as tf
import numpy as np
# 一个字典,隐射一个数字到其二进制的表示
# 例如 int2binary[3] = [0,0,0,0,0,0,1,1]
int2binary = {}
# 最多8位二进制
binary_dim = 8
# 在8位情况下,最大数为2^8 = 256
largest_number = pow(2,binary_dim)
# 将[0,256)所有数表示成二进制
binary = np.unpackbits(
np.array([range(largest_number)],dtype=np.uint8).T,axis=1)
# 建立字典
for i in range(largest_number):
int2binary[i] = binary[i]
def binary_generation(numbers, reverse = False):
'''
返回numbers中所有数的二进制表达,
例如 numbers = [3, 2, 1]
返回 :[[0,0,0,0,0,0,1,1],
[0,0,0,0,0,0,1,0],
[0,0,0,0,0,0,0,1]'
如果 reverse = True, 二进制表达式前后颠倒,
这么做是为训练方便,因为训练的输入顺序是从低位开始的
numbers : 一组数字
reverse : 是否将其二进制表示进行前后翻转
'''
binary_x = np.array([ int2binary[num] for num in numbers], dtype=np.uint8)
if reverse:
binary_x = np.fliplr(binary_x)
return binary_x
def batch_generation(batch_size, largest_number):
'''
生成batch_size大小的数据,用于训练或者验证
batch_x 大小为[batch_size, biniary_dim, 2]
batch_y 大小为[batch_size, biniray_dim]
'''
# 随机生成batch_size个数
n1 = np.random.randint(0, largest_number//2, batch_size)
n2 = np.random.randint(0, largest_number//2, batch_size)
# 计算加法结果
add = n1 + n2
# int to binary
binary_n1 = binary_generation(n1, True)
binary_n2 = binary_generation(n2, True)
batch_y = binary_generation(add, True)
# 堆叠,因为网络的输入是2个二进制
batch_x = np.dstack((binary_n1, binary_n2))
return batch_x, batch_y, n1, n2, add
def binary2int(binary_array):
'''
将一个二进制数组转为整数
'''
out = 0
for index, x in enumerate(reversed(binary_array)):
out += x*pow(2, index)
return out
设置参数
batch_size = 64
# LSTM的个数,就是隐层中神经元的数量
lstm_size = 20
# 隐层的层数
lstm_layers =2
定义输入输出
# 输入,[None, binary_dim, 2],
# None表示batch_size, binary_dim表示输入序列的长度,2表示每个时刻有两个输入
x = tf.placeholder(tf.float32, [None, binary_dim, 2], name='input_x')
# 输出
y_ = tf.placeholder(tf.float32, [None, binary_dim], name='input_y')
# dropout 参数
keep_prob = tf.placeholder(tf.float32, name='keep_prob')
建立模型
# 搭建LSTM层(看成隐层)
# 有lstm_size个单元
lstm = tf.contrib.rnn.BasicLSTMCell(lstm_size)
# dropout
drop = tf.contrib.rnn.DropoutWrapper(lstm, output_keep_prob=keep_prob)
# 一层不够,就多来几层
def lstm_cell():
return tf.contrib.rnn.BasicLSTMCell(lstm_size)
cell = tf.contrib.rnn.MultiRNNCell([ lstm_cell() for _ in range(lstm_layers)])
# 初始状态,可以理解为初始记忆
initial_state = cell.zero_state(batch_size, tf.float32)
# 进行forward,得到隐层的输出
# outputs 大小为[batch_size, lstm_size*binary_dim]
outputs, final_state = tf.nn.dynamic_rnn(cell, x, initial_state=initial_state)
# 建立输出层
weights = tf.Variable(tf.truncated_normal([lstm_size, 1], stddev=0.01))
bias = tf.zeros([1])
# [batch_size, lstm_size*binary_dim] ==> [batch_size*binary_dim, lstm_size]
outputs = tf.reshape(outputs, [-1, lstm_size])
# 得到输出, logits大小为[batch_size*binary_dim, 1]
logits = tf.sigmoid(tf.matmul(outputs, weights))
# [batch_size*binary_dim, 1] ==> [batch_size, binary_dim]
predictions = tf.reshape(logits, [-1, binary_dim])
损失函数和优化方法
cost = tf.losses.mean_squared_error(y_, predictions)
optimizer = tf.train.AdamOptimizer().minimize(cost)
- 1
- 2
训练
steps = 2000
with tf.Session() as sess:
tf.global_variables_initializer().run()
iteration = 1
for i in range(steps):
# 获取训练数据
input_x, input_y,_,_,_ = batch_generation(batch_size, largest_number)
_, loss = sess.run([optimizer, cost], feed_dict={x:input_x, y_:input_y, keep_prob:0.5})
if iteration % 1000 == 0:
print('Iter:{}, Loss:{}'.format(iteration, loss))
iteration += 1
# 训练结束,进行测试
val_x, val_y, n1, n2, add = batch_generation(batch_size, largest_number)
result = sess.run(predictions, feed_dict={x:val_x, y_:val_y, keep_prob:1.0})
# 左右翻转二进制数组。因为输出的结果是低位在前,而正常的表达是高位在前,因此进行翻转
result = np.fliplr(np.round(result))
result = result.astype(np.int32)
for b_x, b_p, a, b, add in zip(np.fliplr(val_x), result, n1, n2, add):
print('{}:{}'.format(b_x[:,0], a))
print('{}:{}'.format(b_x[:,1], b))
print('{}:{}\n'.format(b_p, binary2int(b_p)))
Iter:1000, Loss:0.012912601232528687
Iter:2000, Loss:0.000789149955380708
[0 1 0 1 0 0 1 0]:82
[0 0 1 1 1 1 0 0]:60
[1 0 0 0 1 1 1 0]:142
[0 1 1 1 1 1 0 0]:124
[0 1 1 0 0 0 1 0]:98
[1 1 0 1 1 1 1 0]:222
[0 0 0 1 1 0 1 0]:26
[0 0 0 0 0 1 0 1]:5
[0 0 0 1 1 1 1 1]:31
[0 0 0 1 0 0 1 1]:19
[0 0 1 0 1 0 0 1]:41
[0 0 1 1 1 1 0 0]:60
[0 1 0 1 1 0 1 1]:91
[0 1 0 0 0 0 0 0]:64
[1 0 0 1 1 0 1 1]:155
[0 1 0 1 0 1 0 0]:84
[0 0 0 0 1 1 0 1]:13
[0 1 1 0 0 0 0 1]:97
[0 1 0 0 1 0 1 1]:75
[0 0 1 0 0 0 0 1]:33
[0 1 1 0 1 1 0 0]:108
[0 0 1 1 0 1 0 0]:52
[0 1 1 1 0 0 1 0]:114
[1 0 1 0 0 1 1 0]:166
[0 1 1 1 0 0 0 0]:112
[0 0 1 0 1 1 0 1]:45
[1 0 0 1 1 1 0 1]:157
[0 0 1 1 0 1 0 1]:53
[0 1 0 0 0 0 0 1]:65
[0 1 1 1 0 1 1 0]:118
[0 0 0 0 1 0 1 1]:11
[0 1 1 0 0 1 0 1]:101
[0 1 1 1 0 0 0 0]:112
[0 0 0 0 1 1 0 0]:12
[0 0 1 0 0 1 0 0]:36
[0 0 1 1 0 0 0 0]:48
[0 0 0 0 1 0 0 1]:9
[0 1 1 0 0 0 0 1]:97
[0 1 1 0 1 0 1 0]:106
[0 0 0 1 1 1 1 0]:30
[0 1 0 0 0 0 0 1]:65
[0 1 0 1 1 1 1 1]:95
[0 0 0 1 0 0 1 1]:19
[0 1 0 0 0 0 0 1]:65
[0 1 0 1 0 1 0 0]:84
[0 1 1 0 1 1 0 0]:108
[0 0 1 0 0 0 0 1]:33
[1 0 0 0 1 1 0 1]:141
[0 0 1 1 1 1 1 1]:63
[0 0 1 0 0 0 0 1]:33
[0 1 1 0 0 0 0 0]:96
[0 0 0 1 0 1 1 1]:23
[0 0 0 1 1 1 0 1]:29
[0 0 1 1 0 1 0 0]:52
[0 0 1 1 1 1 0 0]:60
[0 1 0 0 1 0 0 1]:73
[1 0 0 0 0 1 0 1]:133
[0 1 0 0 1 1 0 0]:76
[0 1 1 1 0 1 1 1]:119
[1 1 0 0 0 0 1 1]:195
[0 1 1 0 1 1 1 1]:111
[0 0 0 1 1 0 0 1]:25
[1 0 0 0 1 0 0 0]:136
[0 0 1 1 0 0 0 1]:49
[0 1 1 0 1 0 0 0]:104
[1 0 0 1 1 0 0 1]:153
[0 1 1 1 1 1 0 0]:124
[0 1 1 0 1 0 1 1]:107
[1 1 1 0 0 1 1 1]:231
[0 0 1 1 1 0 1 1]:59
[0 0 0 1 1 0 1 1]:27
[0 1 0 1 0 1 1 0]:86
[0 1 1 0 0 0 0 0]:96
[0 0 0 1 1 1 0 1]:29
[0 1 1 1 1 1 0 1]:125
[0 0 1 1 1 0 1 0]:58
[0 1 0 0 0 1 0 0]:68
[0 1 1 1 1 1 1 0]:126
[0 0 1 1 1 1 1 0]:62
[0 1 1 0 0 1 1 1]:103
[1 0 1 0 0 1 0 1]:165
[0 1 1 1 1 0 0 0]:120
[0 0 1 0 1 1 1 0]:46
[1 0 1 0 0 1 1 0]:166
[0 0 0 0 1 1 0 1]:13
[0 0 1 1 0 0 0 0]:48
[0 0 1 1 1 1 0 1]:61
[0 0 1 0 0 1 0 0]:36
[0 0 1 1 0 0 0 1]:49
[0 1 0 1 0 1 0 1]:85
.......
.......
[0 0 1 0 0 1 1 0]:38
[0 1 0 0 1 1 0 1]:77
[0 1 1 1 0 0 1 1]:115
[0 1 0 1 1 1 1 0]:94
[0 0 1 1 1 1 1 1]:63
[1 0 0 1 1 1 0 1]:157
[0 0 0 0 0 0 1 0]:2
[0 1 0 1 0 0 0 0]:80
[0 1 0 1 0 0 1 0]:82
总结
本文向大家介绍了RNN的结构,以及从全连接网络的角度出去,去理解RNN,得到结论有:
- RNN的结构与全连接网络基本一致
- RNN具有时间展开的特点,这是由其输入决定的
- 全连接网络对一个样本做一次forward,RNN对一个样本做多次forward
同时,基于TensorFlow给出一个简单的RNN网络,从实验结果看,该网络经过训练,以及能够正确进行二进制加法
原文:https://blog.csdn.net/weiwei9363/article/details/78902455