您的位置:首页 > 理论基础 > 计算机网络

RNN循环神经网络的直观理解:基于TensorFlow的简单RNN例子

2017-12-26 15:08 956 查看

RNN循环神经网络的直观理解:基于TensorFlow的简单RNN例子

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)


训练

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网络,从实验结果看,该网络经过训练,以及能够正确进行二进制加法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息