在前面的文章中,我们介绍了怎么用Python自己实现一个简单的神经网络,来实现MNIST的手写数字集。这能帮我们更好的理解神经网络是怎样工作的,但是毕竟在实际应用中,自己实现容易出错,而且如果我们要实现比较复杂的结构比如说卷积神经网络,从底层实现就不太合适了。如果用Tensorflow的话,更加的方便快捷,合理高效
我们之前实现了用Python做一个简单的神经网络,但是这是有问题的,问题主要存在于这几个方面
- 全连接的结构不合适:对于一个32 * 32 * 3大小的图片,单单一个神经元就要有32 * 32 * 3个连接的权重,那么对于更多的神经元,更多的层数,更大的图片,这个计算量的增加是无法接受的
- 全连接的神经网络没有利用到图片的一些特点
在Convoluntional Layer当中,一个神经元只连接一个小区域内的像素点,这个区域就叫做这个神经元的receptive field,这个区域的大小就叫做filter size。这个地方需要注意的一点是,receptive field在图像的前两维里面是只取一个小区域,但是在第三个维度上,也就是深度上,是接收所有的数据的
输出向量的深度也是我们关心的一个量,这里深度就代表着这个维度上有多少个神经元。每一个深度上所有的神经元当然都是卷积同一个receptive field的,但是它们连接的权重也都不一样,它们提取的特征也不一样,有的是提取边特征,有的是提取颜色特征,这些看着同一个receptive field的神经元,我们叫作一个depth column(或者一个fibre)
我们还关心卷积的步长(step),也就是每当我做完一次卷积之后,我在每个维度上将我的窗口移动多大的距离。
如果我们输入层的大小是W,窗口的大小是F,步长的大小是S,Padding的大小是P,那么我们得到的输出层的大小是
Size_{output}=\frac{W-F+2P}{S} + 1
这个式子也很好理解,输入的大小减去窗口的大小,就是可以移动一个步长,然后再加一。处理的时候还要考虑padding添加的大小
这里值得注意的地方有两点
1. 输出层的一个neuron,看的区域,是receptive的大小,乘以输入层的所有深度,这么大,比如说输入的量是X,某一个神经元的权重W看的区域就是X[:5,:5,:],其中W[:,:,0]对应深度的第一层,W[:,:,1]对应深度的第二层
2. 输出层有很多层,每一层的权重系数都是一样的,比如说在depth_column[0]上,对于不同平面上的区域,权重都是一样的
也就是说,每一个神经元连接的输入层的量X和连接的权重之间,都是elementwise的运算
现在我们举个例子
如果我们有一个输入图片,大小是227 * 227 * 3,我们卷积的窗口大小是 11 * 11 * 3,步长是4,没有padding,设置输出层的层数是96,现在我们来简单进行一下计算
每一组输入的大小,是 11 * 11 * 3 = 363
在这个窗口的大小下,我们得到的数据的组数是
( 227 - 11 ) / 4 + 1 = 55,就是55 * 55 = 3025组
输入的矩阵X是3025 * 363
对于每一个receptive field当中的值,我们都需要给他一个权重,这个权重是每层共享的,总共有96层,所以我们的权重矩阵W1的大小是 363 * 96的
OutputLayer = np.matmul( X, W1 )
我们得到的输出矩阵就是3025 * 96的,我们需要把这个矩阵重新reshape成55 * 55 * 96的,然后输入下一个卷积层进行计算
1. 准备数据集
准备数据集的工作基本上和之前是一样的,我们把训练的60000组数据和训练用的10000组数据按每个图片成一个一维的向量(1,28*28),在进行计算的时候我们再把它展开
2. 搭建前向计算的网络
这个我们工作最重要的部分,如何对输入的向量进行正向计算
我们首先需要构建两个初始化权重矩阵的函数
def GetWeights(self,shape):
weights = tf.truncated_normal( shape, stddev= 0.1 )
return tf.Variable( weights )
def GetBias(self,shape):
bias = tf.constant( 0.1, shape = shape )
return tf.Variable( bias )
第一个函数,生成一个给定shape形状的符合正态分布的权重矩阵
第二个函数,生成一个初始值为0.1给定形状的矩阵
下一步的工作就是搭建神经网络
卷积神经网络的简单介绍在这篇文章中,有兴趣的同学可以看一下
这个网络的结构是这样的
INPUT-> CONV1 ->RELU -> MAXPOOL ->CONV2 -> RELU -> MAXPOOL -> FULL_CONNECTED1 -> FULL_CONNECTED2 ->OUTPUT
基本的卷积,非线性单元,池化层的代码是这样的
首先我们来声明权重系数
W1 = self.get_weights( [ 5,5,1,32 ] )
b1 = self.get_bias( [ 32 ] )
这里[5,5,1,32]的意思是,receptive field的大小是5 * 5,输入层的深度是1,输出层的深度是32(关于每一层的深度的意思,详见这篇文章)
h1 = tf.nn.relu(
tf.nn.conv2d( self.input, W1, strides= [ 1,1,1,1 ], padding= 'SAME' )
+ b1)
然后我们通过这个函数来计算卷积,和非线性单元,通过取stride=1和padding=’SAME’,可以保证输入和输出的大小是一样的
然后进行一个池化的操作
p1 = tf.nn.max_pool( h1, [ 1,2,2,1 ],strides= [1,2,2,1],padding= 'SAME' )
通过设定池化的filter size = 2, stride = 2,使得输出的图片大小长宽各是输入的一半
这就是一层卷积,然后我们再进行一次卷积,然后就进行全连接操作,全连接就是指,我们把输入reshape成一维向量,然后像普通的神经网络那样进行矩阵乘
reshape的方法有一点点特别
p2_flatten = tf.reshape( p2, [ -1, 7 * 7 * 64 ] )
这里-1的意思是,我们把每7 * 7 * 64个值组成一个向量,但是我们不关心组成了多少组向量(其实设计网络的时候我们也不知道,因为这跟每次输入的数据有关),我们只对每一维的向量做相应的操作
在两个卷积层之后我们添加两个全连接层,就可以得到每个分类的分数了
3. 计算误差
误差的计算仍然用和之间神经网络一样的计算Softmax的算法,不过这次我们直接用Tensorfow的softmax的函数就可以了,非常的方便
self.output = tf.nn.softmax( tf.matmul( conv1, W_fc2 ) + b_fc2 )
cross_entropy = -tf.reduce_sum( self.label * tf.log(self.output))
这里需要说明一下,self.output是之前通过卷积网络计算的每个分类的分数,再通过softmax函数计算得到的值,也就是我们之前说的
L_{i} = -\log(\frac{e^{f_{y_{i}}}}{\sum_{j}e^{f_{j}}})
但是我们得到的是所有分类的值,所以这里通过one_hot的标签来进行选择,并且对所有样本的值求和,来计算交叉熵,cross_entropy,来得到Loss,正确样本的得分越高,Loss也就越小
4. 调用optimizer进行训练
我们现在要对Loss进行梯度下降,调整参数,使得Loss越来越小,我们可以使用优化器,就像这样
self.optimizer = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
在优化器当中我们可以指定使用怎样的梯度下降方式,来优化哪个量
5. 进行predict
在进行训练之后,我们希望能进行在测试集上进行预测,测试集和训练集是完全分开的,这样得到的预测结果才比较具有代表性
batch = mnist.train.next_batch(50)
prediction_scores = self.output.eval( feed_dict = { self.input_layer : batch[0] } )
我们首先从训练集当中取一定大小的batch,然后将这个batch提供给input_layer,做输入,得到我们的output层,也就是通过这个神经网络预测的输入样本属于每个类的评分
prediction = tf.argmax(prediction_scores,1)
对于每个样本,我们认为评分最大的那个分类就是它的预测分类
correct_prediction = tf.equal( prediction, tf.argmax(batch[1],1) )
我们将这个分类和真是的分类标签进行比对,得到一个1*M的向量,每一个分量表示有没有正确分类,是True或者False
但是我们要计算分类的准确率,需要把这个向量转成float型,然后计算均值
accuracy = tf.reduce_mean( tf.cast( correct_prediction, "float") )
print 'accuracy: ', accuracy.eval()
至此,所有部分都已经介绍完了,下面给出完整的代码,有兴趣的同学可以自己运行一下
import numpy as np
import tensorflow as tf
import pickle
import struct
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("Mnist_data/", one_hot=True)
class Data:
def __init__(self):
self.K = 10
self.N = 60000
self.M = 10000
self.BATCHSIZE = 100
self.reg_factor = 1e-3
self.stepsize = 1e-2
self.train_img_list = np.zeros(( self.N, 28*28 ))
self.train_label_list = np.zeros((self.N, 1))
self.test_img_list = np.zeros(( self.M, 28*28 ))
self.test_label_list = np.zeros((self.M, 1))
self.sess = tf.InteractiveSession()
self.loss_list = []
self.init_network()
self.sess.run(tf.initialize_all_variables())
self.train_data = np.append( self.train_img_list, self.train_label_list, axis = 1 )
def GetOneHot(self, transfer_list):
const_zero = np.zeros( [ transfer_list.shape[0], 10 ] )
for i in range( transfer_list.shape[0] ):
const_zero[ i ][ int(transfer_list[ i ]) ] = 1
return const_zero
def get_weights(self,shape):
initial = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(initial)
def get_bias(self, shape):
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial)
def init_network(self):
self.label = tf.placeholder( "float", [None, self.K] )
self.input_layer = tf.placeholder( "float", [ None, 28*28 ] )
self.input = tf.reshape( self.input_layer, ( -1, 28, 28, 1 ) )
W1 = self.get_weights( [ 5,5,1,32 ] )
b1 = self.get_bias( [ 32 ] )
W2 = self.get_weights( [ 5,5,32,64 ] )
b2 = self.get_bias( [ 64 ] )
W_fc1 = self.get_weights( [ 7 * 7 * 64, 1024 ] )
b_fc1 = self.get_bias( [ 1024 ] )
W_fc2 = self.get_weights( [ 1024, 10 ] )
b_fc2 = self.get_bias([ 10 ] )
h1 = tf.nn.relu(
tf.nn.conv2d( self.input, W1, strides= [ 1,1,1,1 ], padding= 'SAME' )
+ b1)
p1 = tf.nn.max_pool( h1, [ 1,2,2,1 ],strides= [1,2,2,1],padding= 'SAME' )
h2 = tf.nn.relu(
tf.nn.conv2d( p1, W2, strides = [ 1,1,1,1 ], padding = 'SAME')
+ b2
)
p2 = tf.nn.max_pool( h2, [1,2,2,1], strides=[1,2,2,1], padding= 'SAME' )
p2_flatten = tf.reshape( p2, [ -1, 7 * 7 * 64 ] )
conv1 = tf.nn.relu(tf.matmul( p2_flatten, W_fc1 ) + b_fc1)
self.output = tf.nn.softmax( tf.matmul( conv1, W_fc2 ) + b_fc2 )
cross_entropy = -tf.reduce_sum( self.label * tf.log(self.output))
self.optimizer = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
def predict(self):
batch = mnist.train.next_batch(50)
prediction_scores = self.output.eval( feed_dict = { self.input_layer : batch[0] } )
prediction = tf.argmax(prediction_scores,1)
correct_prediction = tf.equal( prediction, tf.argmax(batch[1],1) )
accuracy = tf.reduce_mean( tf.cast( correct_prediction, "float") )
print 'accuracy: ', accuracy.eval()
def train(self):
for i in range(1000):
batch = mnist.train.next_batch(50)
self.optimizer.run(
feed_dict = {
self.input_layer:batch[0],self.label:batch[1]
}
)
print i
self.predict()
return
def main():
data = Data()
data.train()
data.predict()
if __name__ == '__main__':
main()
参考资料
http://wiki.jikexueyuan.com/project/tensorflow-zh/tutorials/mnist_pros.html