<废话框>这次要说的是循环神经网络(Recurrent Neural Networks,RNN)。传统的神经网络模型中,是从输入层到隐含层再到输出层,层与层之间是全连接的,每层之间的节点是无连接的。他们都只能单独的取处理一个个的输入,前一个输入和后一个输入是完全没有关系的。但是,某些任务需要能够更好的处理序列的信息,即前面的输入和后面的输入是有关系的。但是普通的神经网络对于很多问题却无能无力。例如,你要预测句子的下一个单词是什么,一般需要用到前面的单词,因为一个句子中前后单词并不是独立的,而RNN就是擅长处理序列数据......</废话框>
(上皆废话,阁下但略无妨)
在这篇文章,你可以了解到如何使用Tensorflow框架搭建RNN,此文章不会出现RNN内部原理的内容,如果是打算来研究RNN的而不是使用的小伙伴就可以节省时间离开这篇陋文了,关于原理的话先上个维基百科意思一下,其实知乎上也有很多把这个讲得很通俗易懂的文章,大家可以去搜一搜
万事先导包,当个调包侠真舒服(可能饿死):
import
numpy和tensorflow就不说了,导入pylab主要是为了可视化,numpy的话一般装了python就自动安装了,其它两个库如果有没安装的可以用pip安装一下
然后定义一些全局变量,方便后续代码编写:
data_size = 100
timesteps = 16
hidden_size = 20
batch_size = data_size-timesteps+1
这次是想用一个简单的RNN来拟合预测一个sin函数,反正每次都是很简单的东西,入门教程这种东西,一复杂读者一般看着就会略懵,比如讲个RNN,一上来就RNN结合NLP,凉凉,总要考虑有些还没有任何NLP基础的人吧,但是!sin函数大家肯定是知道的。
data_size如其变量名,就是输入数据的多少了嘛
因为sin函数可以自己随意整,不是实际世界,想要多少就多少
timesteps 也就是时间步长,可以理解为LSTM的个数,如下图,timesteps的值就为4
hidden_size 即隐藏层的大小,就像普通BP神经网络隐藏层有多少个神经元一样
batch_size 一次训练使用数据的多少,这里为了方便,就一次把所有数据扔进去了,即data_size-timesteps+1,为什么是这么个数值呢?你想想看,预测一个值需要前面timesteps个隐藏状态作为基础,所以这里将数据处理成data_size-timesteps+1组数据了
数据生成及预处理的代码如下:
def get_data(raw_data,timesteps):
x = [];y = []
for i in range(len(raw_data)-timesteps+1):
x.append(raw_data[i:i+timesteps])
y.append(raw_data[i+timesteps-1])
return np.array(x),np.array(y)
raw_x = np.linspace(-10,10,data_size)
raw_data = np.sin(raw_x)
noise = np.random.normal(0,0.02,raw_data.shape) # 不加点噪声都不敢说话
x_data,y_data = get_data(raw_data+noise,timesteps)
get_data函数什么意思呢?举个栗子:
>>> get_data([1,2,3,4,5,6],3)
(array([[1, 2, 3],
[2, 3, 4],
[3, 4, 5],
[4, 5, 6]]),
array([3, 4, 5, 6]))
可以看出:
- x:[1,2,3]->y:[3]
- x:[2,3,4]->y:[4]
- x:[3,4,5]->y:[5]
- x:[4,5,6]->y:[6]
x的最后一个数值等于y,且刚好得到data_size-timesteps+1比数据,以此作为RNN的输入
来个图帮助理解下:
而其余几个生成sin函数的数据的代码就不多阐述了
重点来了,RNN的代码:
先附个全的:
class RNN():
x = tf.placeholder(shape=[None,timesteps,1],dtype=tf.float32)
y = tf.placeholder(shape=[None,1],dtype=tf.float32)
W = tf.Variable(tf.random_normal([hidden_size,1]))
b = tf.Variable(tf.constant(0.1, shape=[1]))
cell = tf.nn.rnn_cell.BasicRNNCell(num_units=hidden_size)
output,h = tf.nn.dynamic_rnn(cell,x,dtype=tf.float32)
prediction = tf.matmul(output[:,-1], W) + b
loss = tf.losses.mean_squared_error(y, prediction)
trainstep = tf.train.AdamOptimizer(1e-3).minimize(loss)
再分开:
x = tf.placeholder(shape=[None,timesteps,1],dtype=tf.float32)
y = tf.placeholder(shape=[None,1],dtype=tf.float32)
这两个应该很熟悉,不熟悉的话请回顾一下Tensorflow基础,这里要注意的是,要把输入的数据reshape成[batchsize, timesteps, 数据的维度]这种格式
W
权重W和偏置b的定义,这个像全连接神经网络一样,多少隐藏层多大W定义多大啦。
cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=hidden_size)
output,h = tf.nn.dynamic_rnn(cell,x,dtype=tf.float32)
cell是什么,就是单个的神经元啦,第一句代码就是使用BasicRNNCell得到hidden_size个Cell啦。然后我们再回到这个图:
t时刻的隐藏状态是由t-1时刻的隐藏状态和t时刻的输入决定的,即
所以上面这个图是这样得到所有的h的:
timesteps为4则需要分四步进行
而欲计算
h0 = cell.zero_state(batch_size, np.float32)
output1, h1 = cell.call(inputs, h0)
output2, h2 = cell.call(inputs, h1)
...
output4, h4 = cell.call(inputs, h3)
而实际我们只关心组后的
翻上去不容易,我再贴一下这句代码:
output,h = tf.nn.dynamic_rnn(cell,x,dtype=tf.float32)
根据您Cell用的不同,返回的两个值会有所出入
如果是BasicRNNCell的话,那么Cell调用call函数返回值output和h是一样的,连内存地址都一样的那种一样,因为在Tensor flow源码中,这个函数返回的两个值是同一个引用。如果是一次性计算完的话(也就是使用dynamic_rnn函数的话),返回的output是
如果是BasicLSTMCell的话(也就是我正用的这个),那么Cell调用call函数返回值output等于h[1],返回的h是一个元组,h[0]等于通过激励函数计算过的h[1]。同理如果是一次性计算完的话,output[:,-1,:]等于h[1]
然后就是计算预测值,loss和优化器了:
prediction = tf.matmul(output[:,-1], W) + b
loss = tf.losses.mean_squared_error(y, prediction)
trainstep = tf.train.AdamOptimizer(1e-3).minimize(loss)
都是老套路了不解释了,不懂参考Tensorflow基础
然后继续套路式代码:
也不解释了,总不可能说一次Tensorflow就要解释一次Session是什么嘛,希望理解2333
rnet = RNN()
sess = tf.Session()
sess.run(tf.global_variables_initializer())
为了方便实时看到训练的过程,使用Matplotlib可视化训练过程:
pl.figure(figsize=(10,4))
pl.ion()
raw_x = raw_x[timesteps-1:]
raw_x是为了画图用的,至于为什么是timesteps-1?读者自己思考吧
最后一步,训练!
for step in range(100):
_,yp = sess.run([rnet.trainstep,rnet.prediction],feed_dict={rnet.x:x_data[:,:,None],rnet.y:y_data[:,None]})
print('The {}th train step'.format(step))
pl.cla()
pl.plot(raw_x,yp,color='r')
pl.scatter(raw_x,y_data,color='b')
pl.grid()
pl.pause(0.01)
训练个100次差不多,sin函数多简单呀,久之无益。
循环体内:
- 第一行:训练并计算预测值
- 第二行:在控制台打印,告诉我们训练到第几步了
- 其余行:训练可视化(Matplotlib的知识,相信读者都会 理直气壮.jpg)
最终训练出的模型大概长这样吧
是长红色(shai)的那样,不是蓝色的那样,蓝色的是原始数据
其余要说的:
- 此次使用的网络是单层的RNN(毕竟是个sin函数,不能太过分是吧?搞复杂了overfitting也不好处理是吧),如果您想搭建多层的话:
将这一句代码:
cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=hidden_size)
替换成如下即可:
def get_cells(): return tf.nn.rnn_cell.BasicRNNCell(num_units=hidden_size)
cell = tf.nn.rnn_cell.MultiRNNCell([get_cells() for _ in range(layers_size)])
- 模型的保存
这篇文章里面我并没有写模型保存的部分,所以我来给你讲道理,我们写个代码都追求什么模块化呀,封装什么的,我觉得写文章亦是如此(其实是为懒找借口)。模型的保存和恢复我已经有文章写过了,相当于对那个知识进行封装了,所以以后写直接调用那篇文章就行了~~~
所以!!!如果想保存模型的小伙伴,Call文章Tensorflow模型保存
最后附上完整代码:(方便您复制呐)
import numpy as np
import tensorflow as tf
import pylab as pl
data_size = 100
timesteps = 16
hidden_size = 20
batch_size = data_size-timesteps+1
def get_data(raw_data,timesteps):
x = [];y = []
for i in range(len(raw_data)-timesteps+1):
x.append(raw_data[i:i+timesteps])
y.append(raw_data[i+timesteps-1])
return np.array(x),np.array(y)
raw_x = np.linspace(-10,10,data_size)
raw_data = np.sin(raw_x)
noise = np.random.normal(0,0.02,raw_data.shape) # 不加点噪声都不敢说话
x_data,y_data = get_data(raw_data+noise,timesteps)
class RNN():
x = tf.placeholder(shape=[None,timesteps,1],dtype=tf.float32)
y = tf.placeholder(shape=[None,1],dtype=tf.float32)
W = tf.Variable(tf.random_normal([hidden_size,1]))
b = tf.Variable(tf.constant(0.1, shape=[1]))
cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=hidden_size)
output,h = tf.nn.dynamic_rnn(cell,x,dtype=tf.float32)
prediction = tf.matmul(output[:,-1], W) + b
loss = tf.losses.mean_squared_error(y, prediction)
trainstep = tf.train.AdamOptimizer(1e-3).minimize(loss)
rnet = RNN()
sess = tf.Session()
sess.run(tf.global_variables_initializer())
pl.figure(figsize=(10,4))
pl.ion()
raw_x = raw_x[timesteps-1:]
for step in range(100):
_,yp = sess.run([rnet.trainstep,rnet.prediction],feed_dict={rnet.x:x_data[:,:,None],rnet.y:y_data[:,None]})
print('The {}th train step'.format(step))
pl.cla()
pl.plot(raw_x,yp,color='r')
pl.scatter(raw_x,y_data,color='b')
pl.grid()
pl.pause(0.01)