这篇文章主要讲的是个人这几周来在ubuntu系统上学习tensorflow的所得所思,由于水平不足,如有缺陷与错误,望各位看官不吝赐教!谢谢!(本文中如有侵权处,望联系作者,速删)
step 1 .安装tensorflow(不需外网):有pip,anaconda等多种方式(本人用的是anaconda),可自行百度。
step 2. 从MNIST手写识别开始: 我是从tensorflow中文社区上的MNIST手写数字识别教程开始学习tensorflow的:
tensorflow中文社区“MNIST入门”篇介绍了基本的代码,此处不再赘述。
以下先附上本次要说的“卷积神经网络识别版”代码,在代码后会细致讲解:
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data',one_hot=True)
#此处可设置会话(tensorflow的术语,在下面解释)的类型:交互式:sess = tf.InteractiveSession()用于命令行,此处不用也可
sess = tf.InteractiveSession()
#初始化参数
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) #生成常量矩阵,shape即为矩阵的shape
return tf.Variable(initial)
#卷积:
def conv2d(x,W):
return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding ='SAME')
#池化
def max_pool_2x2(x):
return tf.nn.max_pool(x,ksize =[1,2,2,1],strides=[1,2,2,1],padding='SAME')
#第一层隐藏层:
W_conv1 =weight_variable([5,5,1,32])
b_conv1=bias_variable([32])
x_image = tf.reshape(x,[-1,28,28,1])
#h 是激活后得到的特征
h_conv1 =tf.nn.relu(conv2d(x_image,W_conv1)+b_conv1)
h_pool1=max_pool_2x2(h_conv1)
#第二层隐藏层:
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
#全连接层1:
W_fc1= weight_variable([7*7*64,1024])
b_fc1 = bias_variable([1024])
h_pool2_flat=tf.reshape(h_pool2,[-1,7*7*64])
h_fc1 =tf.nn.relu(tf.matmul(h_pool2_flat,W_fc1)+b_fc1)
#Dropout处理
keep_prob=tf.placeholder('float')
h_fc1_drop=tf.nn.dropout(h_fc1,keep_prob)
#全连接层2:
W_fc2 =weight_variable([1024,10])
b_fc2=bias_variable([10])
#输出结果
y_conv =tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2)+b_fc2)
#计算交叉熵
cross_entropy = - tf.reduce_sum(y_ * tf.log(y_conv))
#设置训练方法、优化器及其他
train_step =tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv,1),tf.argmax(y_,1))
accuracy=tf.reduce_mean(tf.cast(correct_prediction,'float'))
#开始训练:
sess.run(tf.global_variables_initializer())
#储存
saver =tf.train.Saver(max_to_keep=1)
for i in range(10000):
batch = mnist.train.next_batch(50)
if i%100==0:
train_accuracy =accuracy.eval(feed_dict ={x:batch[0],y_:batch[1],keep_prob:1.0})
print('第%d步:准确度为%g'%(i,train_accuracy))
saver.save(sess,'/home/richard/my_CNN.ckpt',global_step=i)#储存模型
train_step.run(feed_dict ={x:batch[0],y_:batch[1],keep_prob:0.5})
#打印结果:
print('综合准确度为%g'%accuracy.eval(feed_dict ={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1.0}))
1>>>
from tensorflow.examples.tutorials.mnist import input_data
这句用于导入文件input.data.py,用它自动从网上下载并解压MNIST手写数据集(含训练集与验证集)及标签集的(显然,每一个手写数字图片都对应着一个标签(label)说明它是什么数字),然后生成了名为“MNIST_data”的文件(打开里面都是后缀为 .gz 的压缩包)
mnist = input_data.read_data_sets('MNIST_data',one_hot=True)
用read_data_sets导入数据,对one-hot=True我的理解的是这相当于打开了 [one-hot]这个模式的开关
2>>>
sess = tf.InteractiveSession()
会话:tensorflow术语。大家都知道tensorflow采用“计算图(graph)”的方式表示深度学习程序的执行过程,它是神经网络的一种直观表示方式。我理解的是:tensorflow将神经网络中出现的多种计算放到一起构成graph,在“会话”(Session)中执行。
3>>>
#占位符用于输入数据及得到目标数据
x = tf.placeholder("float",[None,784])
y_ = tf.placeholder("float",[None,10])
这两句定义占位符(placeholder),占位符是用于装输入数据、输出数据的空壳。
x: 用于输入要识别的图片,此处的图片为28*28像素。[None,784]是一个二维张量(tensor),第一位表示第几张图片,None意为任意。784=28*28,是图片的二维平面。如第42张图片的第600个像素点就是[42,600]。
x = tf.placeholder("float",[None,28,28,1])
而这样是另外一种写法。
y_ :用于接收label值,即图片识别的正确答案。[None,10]:二维张量,None意同上,第二位之所以是10,因为识别结果是0~9。
简言之,占位符就是先把位子留出来,用于装数据。
4>>>我们需要先对W与b做预处理。
#初始化参数
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) #生成常量矩阵,shape即为矩阵的shape
return tf.Variable(initial)
大家知道如果我们使用SGD(随机梯度下降),需要随机选取梯度下降开始点。我们这里使用的是Adam,也是一样需要随机初始化。所以对w 进行正态分布初始化。
initial=tf.truncated_normal(shape,stddev=0.1)
truncated_normal(shape,mean=0,stddev)即正态分布,stddev为方差,此处设为0.1,控制赋值的范围在均值(默认为0)附近0.1大小范围内。
initial = tf.constant(0.1, shape=shape)
对bias:先生成一个常量矩阵即可。由于之后我们使用了Relu这个激活函数,为了防止出现负数,对b初始化为常量矩阵(值全为0.1)
5>>>定义卷积函数
def conv2d(x,W):
return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding ='SAME')
卷积这个概念源于数学,被广泛应用于图像处理方面。它用于从图像中提取特征,涉及到矩阵运算。
>>>tf.nn.conv2d(input, filter , strides , padding , use_cudnn_on_gpu=None,name=None)
是卷积神经网络核心函数之一,
input 为要做卷积的输入的图像,为一个4维tensor,有参数【batch,height,width,channels】,类型为float32或float64。batch为一次训练的图片数(样本),channels为色彩通道(如RGB三通道,此处MNIST单通道)。这里x即为input。
filter为卷积核:是4维tensor,有参数【filter_height,filter_width,in_channels, out_channels】,指【卷积核的高度,宽度,输入通道数,输出通道数】构建卷积核时需定义输入通道,并由此定下输出通道。卷积核大小又称为“感受野”。
strides为步长,有四个参数,表示卷积时在各维度上一步跨多大。strides参数分别为【batch,height,width,channels】一般strides【0】=strides【3】=1,因为第一位表示在batch(样本)维度上的步长,最后一位表示在色彩通道(channels)维度上的步长,即不跳过任何一个颜色。
padding是卷积的模式,有“SAME”(在图像边缘处自动补充0像素点以使卷积核得以卷积边缘)与“VALID”(在图像边缘处不自动补0像素点,不卷积边缘)两种。当卷积核大于1且不进行边界扩充,输出尺寸将相应缩小,当卷积核以标准方式SAME进行边界扩充,则输出图像数据的空间尺寸将与输入图像相等。
此处x即为input,W为卷积核。
关于卷积核与提取特征有必要讲的:
卷积的用途就是 提取图像的特征,比如一副手写数字8的图片,为什么人类看了一下子认出来是8?因为我们看得多了,知道8是长什么样子,也就是我们很了解8的特征。但机器不知道,所以我们要训练它学习数字8的特征:上下都有圆圈、中间窄等等。
tf.nn.conv2d(x,W,strides=[1,1,1,1],padding ='SAME')
此处W ,也就是我们的权重参数(一个张量,或者说,一个矩阵),就是我们的卷积核。卷积核又名滤波器,它就是用来提取特征的,但一开始它不是一个优秀的特征提取器。我们要通过不断的让神经网络“看”图片,让卷积核自己不断摸索着提取一些特征,再通过比较它的答案与正确答案(标签)的差异(用loss function 衡量),不断重复训练,在这个过程中更新参数(w和b)直至它成为一个优秀的特征提取器。
6>>>最大池化(别听池化名字取得高大上,本质就是采样。可以对输入的图像数据进行压缩,保留主要特征,增强鲁棒性)
def max_pool_2x2(x):
return tf.nn.max_pool(x,ksize =[1,2,2,1],strides=[1,2,2,1],padding='SAME')
tf.nn.max_pool(input, ksize, strides, padding,name =None)重要的池化函数
ksize:滤波器的大小,即池化窗口的大小,此处池化窗口为2*2大小。
7>>>
#改变输入的图像x
x_image = tf.reshape(x,[-1,28,28,1])
此处让输入的图像数据变为(resize) 28*28*1的矩阵形式(1为通道数)
8>>>
#第一层隐藏层:
W_conv1 =weight_variable([5,5,1,32])
b_conv1=bias_variable([32])
#h 是激活后得到的特征
h_conv1 =tf.nn.relu(conv2d(x_image,W_conv1)+b_conv1)
h_pool1=max_pool_2x2(h_conv1)
对第一层隐藏层的W,b进行初始化。对图像进行特征提取后加上bias再用Relu激活,之后池化。
有人可能要问:
W_conv1 =weight_variable([5,5,1,32])
为啥这里的shape是一个[5,5,1,32]的张量?
5,5,1表示第一层的卷积核(filter)是5*5*1大小的(卷积核通道数要和input一致,否则无法计算卷积)。32是输出的通道数,也就是这一层有32个5*5*1大小的卷积核。(注意这里的卷积核个数32是自定义的,非计算得出)做了第一层的卷积后我们会得到32个28*28*1的特征(feature map)(因为是SAME卷积,所以每一个filter做完卷积后输出28*28的feature map,和原图一致),这已经有了32*5*5=800个权重参数。
再将每一张都加上bias,再激活,作为输入数据输入下一隐藏层。
9>>>
#第二层隐藏层:
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
同上。第二层有64个卷积核,这次我们得到64个feature map,有1600个权重参数。
10>>>全连接层(fully connected layers ,FC)
为什么要有全连接层?
全连接层:用于分类。如果说之前的卷积与池化是对输入的原始数据提取特征,那么全连接层就是在这些特征之上对原图进行分类。
(有的神经网络设计时将全连接层替换为全局平均池化(global average pooling,GAP),根据不同的情况两种方式有不同的准确性,而且GAP减少了参数的数量。此处不对其作展开)
#全连接层1:
W_fc1= weight_variable([7*7*64,1024])
b_fc1 = bias_variable([1024])
h_pool2_flat=tf.reshape(h_pool2,[-1,7*7*64])
h_fc1 =tf.nn.relu(tf.matmul(h_pool2_flat,W_fc1)+b_fc1)
W_fc1= weight_variable([7*7*64,1024])
对【7*7*64,1024】的解释:
11>>>
#Dropout处理
keep_prob=tf.placeholder('float')
h_fc1_drop=tf.nn.dropout(h_fc1,keep_prob)
keep_prob是人为设定的每个神经元被drop的概率。
12>>>
#全连接层2:
W_fc2 =weight_variable([1024,10])
b_fc2=bias_variable([10])
#输出结果
y_conv =tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2)+b_fc2)
对【1024,10】的解释:将h_fc1_drop映射到{0,1,2,3,4,5,6,7,8,9}上。
最后y_conv是1*10的tensor,每一位为对应数字的概率值(用softmax函数映射到【0,1】区间)
13>>>
#计算交叉熵
cross_entropy = - tf.reduce_sum(y_ * tf.log(y_conv))
#设置训练方法、优化器及其他
train_step =tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv,1),tf.argmax(y_,1))
accuracy=tf.reduce_mean(tf.cast(correct_prediction,'float'))
我们的目的是最小化交叉熵(cross entropy)
怎么计算accuracy呢?
correct_prediction = tf.equal(tf.argmax(y_conv,1),tf.argmax(y_,1))
这个式子里tf.argmax(y_conx,1)函数输出张量y_conx10个数中概率值最大者,也就是我们的预测值。再用tf.equal函数判断我们模型的预测值y与标准值y_是否一致,一致就输出True,否则False
accuracy=tf.reduce_mean(tf.cast(correct_prediction,'float'))
再将bool值转换为float,再求均值,再输出这一批样本识别的正确率(accuracy of batch)
14>>>
#储存
saver =tf.train.Saver(max_to_keep=1)
使用saver类,目的是在接下来的过程中储存模型。
max_to_keep=1指只保留最后一次的。可以改为3,5等。如改为0或None,则为保留所有。
会在路径下生成后缀为 .meta, .index.data-00x(00x是可变的),以及一个checkpoint文件。
15>>>
#开始训练:
sess.run(tf.global_variables_initializer())
for i in range(10000):
batch = mnist.train.next_batch(50)
if i%100==0:
train_accuracy =accuracy.eval(feed_dict ={x:batch[0],y_:batch[1],keep_prob:1.0})
print('第%d步:准确度为%g'%(i,train_accuracy))
saver.save(sess,'/home/richard/my_CNN.ckpt',global_step=i)#储存模型
train_step.run(feed_dict ={x:batch[0],y_:batch[1],keep_prob:0.5})
#打印结果:
print('测试集准确度为%g'%accuracy.eval(feed_dict ={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1.0}))
训练10000次,每次取50个图片,用feed_dict填充数据。
accuracy.eval()
Tensor.eval(feed_dict=None, session=None):这里eval() 其实就是tf.Tensor的Session.run() 的另外一种写法。
但要注意,eval()只能用于tf.Tensor类对象,也就是有输出的Operation。对于没有输出的Operation, 可以用.run()或者Session.run()。Session.run()没有对输出限制。
最后用测试集进行测试,输出结果。
16>>>输出的东西
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
#这一段是提示你安装xx可以加快速度,没有也可以运行。
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE3 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.1 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX2 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use FMA instructions, but these are available on your machine and could speed up CPU computations.
第0步:准确度为0.08
第100步:准确度为0.82
第200步:准确度为0.92
第300步:准确度为0.82
第400步:准确度为0.96
第500步:准确度为0.92
第600步:准确度为1
第700步:准确度为0.96
第800步:准确度为0.92
第900步:准确度为1
第1000步:准确度为0.96
第1100步:准确度为0.98
第1200步:准确度为0.98
第1300步:准确度为0.98
......