前言

最近深度学习是比较火的,在theano和tensorflow之间做了个抉择,还是觉得google维护的项目靠谱点。而从另一个角度,theano是个更强大的数学工具,所以对于我们这些门外汉来说,可能更希望直接上手网络的构建和预测,从这个角度来说,TensorFlow兴许更适合。

我在coding中建立了个开源项目,在此处分享自己学习TensorFlow的过程。如果有人有兴趣一起学习,原因在此项目中讨论或者提交PR。

项目地址:https://coding.net/u/zealseeker/p/deep-learning-with-coding/git

在这里先推荐几个教程(在项目主页中也有):

深度学习基础概念 (中文)

卷积神经网络介绍: http://www.36dsj.com/archives/24006
受限波尔兹曼机: http://blog.sciencenet.cn/blog-110554-876316.html
防止过拟合方法:http://blog.csdn.net/u012162613/article/details/44261657
Dropout : http://www.w2bc.com/Article/73328
tensorflow教程

tensorfly: http://tensorfly.cn/index.html
极客学院: http://wiki.jikexueyuan.com/project/tensorflow-zh/

然后介绍下自己对TensorFlow构建神经网络的基本理解:

神经网络在TensorFlow中的概念介绍

占位符(网络输入/输出层定义)

神经网络的输入层与输出层是由每个样本决定的,所以它与变量不同,属于”已知数据”,在TensorFlow中用tf.placeholder来创建,参数分别是类型与shape,shape[0]是样本数量,如果未知的话,可以使用None(参考softmax.py),shape[1]是输入/输出层的节点数量。在mnist数据中输入层是784个像素,输出层是10个可能的结果。 用y_表示y的期望值。

1
2
x = tf.placeholder('float',[None,784])
y_ = tf.placeholder('float',[None,10])

变量(层之间的联系)

神经网络,节点与节点之间连接有两个变量,W和b
假设l层共m个节点,l+1层 n个节点则
W即输入与输出之间的权重,所以是m * n 的矩阵
b是输出的偏量共n个,所以会写:

1
2
W_l = tf.Variable(tf.zeros([m,n]),name='可以给变量取名')
b_l = tf.Variable(tf.zeros([n]))

tf.zeros(shape) 显然是得到一个全为0的矩阵或向量
除了全为0作为初始化,shape对应变量规格,比如[m,n]或者偏量则用[n]。当然还有其他的初始化方法,比如
1
2
3
tf.truncated_normal(shape, stddev=0.1) # 截断正态分布
tf.random_normal(shape,stddev=0.1) # 正太分布
tf.constant(0.1, shape=shape) # 常数

由于中间层有许多,每两层之间都会有shape不同的W与b变量,若初始化方法都一样,则可以将他们写进一个函数里方便调用(参考cnn_mnist.py)
1
2
3
4
5
6
7
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(initial)

def bias_variable(shape):
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial)

这样只需要调用W_l = weight_variable([m,n]), b_l = bias_variable([n]) 即可轻松完成这两个变量的定义。

激活函数(中间层/输出层的输入与输出)

TensorFlow内置许多激活函数,详细请参见http://tensorfly.cn/tfdoc/api_docs/python/nn.html
这里介绍几个常用的:
ReLU: tf.nn.relu(features, name=None)
Sigmod: tf.sigmoid(features, name=None)
Softplus: tf.nn.softplus(features, name=None)
这里的features一般是中间层的输入值,它通常等于上一个层的输出值(x):
features = tf.matmul(x,W)+b
对于输出层,双分类模型使用Sigmod即可,多分类模型则可以用softmax: tf.nn.softmax(features,name=None)其结果则是预测出的y值。

损失函数

损失函数一般用交叉熵表示,它等于-y_ * log(y) 并进行累加,累加在TensorFlow中使用tf.redunce_sum()表示,用它定义后,TensorFlow会自动在训练完所有样本过程中把交叉熵加和在一起。

1
cross_entropy = -tf.reduce_sum(y_*tf.log(y))

训练

训练的过程就是通过训练集的数据寻找最优参数的过程。一般采用最陡梯度下降法(Gradient descent algorithm)或者随机梯度下降法(Stochastic gradient descent)进行最优化(使得损失函数最小)。
这两个都属于梯度下降法(其他方法可参考这里),两者的区别是,前者每步都计算训练集中所有的样本扩散到输出端并得到的损失函数的和寻找梯度进行移动,而随机梯度下降法则是每次计算一个batch(比如50个样本),从中找到一个梯度并向前移动。显然当数据集很多(比如50000个)时,使用随机梯度下降法的单步计算时间要明显大于最陡梯度下降法。但由于50个样本不一定能完全代替所有样本,所以得到的梯度并不是全局最陡的,所以相对前者,其梯度的可靠性不如前者。综合考虑的话,数据集多可以用SGD,而少的话则用GD足以。
无论怎样,先要定义训练使用的最优化方法:

1
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

不难发现,tf.train.GradientDescentOptimizer 定义了一种最优化方法,0.01是学习率(学习率越大,相当于每一步走的越远,精度就越低)而 minimize 则定义了其方向是朝向最小化损失函数 cross_entropy。所以称这个过程叫 train_step ,执行一次代表走一次。接下来就要进入不停地优化过程:
随机梯度下降法如下表示:
1
2
3
for i in range(1000):
batch = mnist.train.next_batch(50)
train_step.run(feed_dict={x: batch[0], y_: batch[1]})

其中batch就是下一批样本的样本,这里mnist库已经事先提供了计算下一批样本的方法, 可直接调用。可以去看看mnist库是如何做到的(点击这里看源码
feed_dict就是学习过程中读取的数据,x,y_ 对应之前申明的占位符,即特征x以及其真实的分类y_
如果不想用随机梯度下降法,则将x,y_分别用所有额数据赋值即可,笔者并没有尝试过,这样训练速度是难以想象的。
1
train_step.run(feed_dict={x: mnist.train.images, y_: mnist.train.labels}

最后,在这里分享一个我碰到的问题,具体解释已经放在了这里:https://coding.net/u/zealseeker/p/deep-learning-with-coding/git/blob/master/tensorflow/softmax_SG.md

简单介绍就是:如果一个batch里训练太多的数据(比如1000个mnist)会导致准确率下降到0.098且不再上升。 目前还没想通具体的原因,欢迎大牛指出,欢迎一同学习的新人一起学习探讨!