tensorflow入门项目(二):手写数字识别之CNN模型(详解)

前言

在做实战项目时,最好有一定的基础。个人觉得吴恩达的视频讲解的很好,可以看一下打个基础。
 
 

内容

 
 
附上代码以及详细解析,也可跳过讲解,直接复制代码。代码里包含全部讲解!

 import tensorflow as tf import numpy as np from
tensorflow.examples.tutorials.mnist import input_data mnist =
input_data.read_data_sets('E:/program/tensorflow_learning/example/mnist_program/mnist',one_hot=True)

导入数据
 

print(type(mnist))
print('train image shape输入数据:',mnist.train.images.shape,'trian label shape:', mnist.train.labels.shape)#(55000, 784),(55000, 10)
print('val image shape:', mnist.validation.images.shape)#(5000, 784)
print('test image shape:', mnist.test.images.shape)#(10000, 784)

查看数据类型
 

config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.InteractiveSession(config=config)

设置tensorflow对GPU使用按需分配
 

def weight_variable(shape):
    init = tf.truncated_normal(shape,stddev=0.1)
    return tf.Variable(init)

使用正态分布初始化权值。
 
tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
shape表示一维的张量,也是输出的张量。
mean是正态分布的均值
stddev是标准差
从截断的正态分布中输出随机值。 生成的值服从具有指定平均值和标准偏差的正态分布,如果生成的值大于平均值2个标准偏差的值则丢弃重新选择。
在tf.truncated_normal中如果init的取值在区间(μ-2σ,μ+2σ)之外则重新进行选择。这样保证了生成的值都在均值附近
 
tf.Variable(initializer,name)
参数initializer是初始化参数,
name是可自定义的变量名称
 

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

tf.constant(value,dtype=None,shape=None,name=‘Const’,verify_shape=False)
创建一个常量tensor,按照给出value来赋值,可以用shape来指定其形状。value可以是一个数,也可以是一个list。 如果是一个数,那么这个常量中所有值的按该数来赋值。
如果是list,那么len(value)一定要小于等于shape展开后的长度。赋值时,先将value中的值逐个存入。如果len(value)小于shape展开后的长度,则全部存入value的最后一个值。
详情指路:https://blog.csdn.net/csdn_jiayu/article/details/82155224

 

#定义卷积层
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:指定需要做卷积的输入图像,它要求是一个Tensor,具有[batch,in_height,in_width,in_channels]这样的形状(shape),具体含义是"训练时一个batch的图片数量,图片高度,图片宽度,图片通道数",注意这是一个四维的Tensor,要求类型为float32或者float64.

filter:相当于CNN中的卷积核,它要求是一个Tensor,具有[filter_height,filter_width,in_channels,out_channels]这样的shape,
具体含义是"卷积核的高度,卷积核的宽度,图像通道数,滤波器个数",要求类型与参数input相同。有一个地方需要注意,第三维in_channels,就是参数input中的第四维

strides:卷积时在图像每一维的步长,这是一个一维的向量,长度为4,与输入input对应,一般值为[1,x,x,1],x取步长。

padding:定义元素边框与元素内容之间的空间。string类型的量,只能是"SAME"和“VALID”其中之一,这个值决定了不同的卷积方式。"SAME"和“VALID”的不同详情指路 https://blog.csdn.net/wuzqChom/article/details/74785643

use_cudnn_on_gpu:bool类型,是否使用cudnn加速,默认是True.

name:指定名字

 

def max_pooling(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)
tf.nn.avg_pooll(input,ksize,strides,padding,name=None)

input:需要池化的输入,一般池化层接在卷积层后面,所以输入通常是feature map,依然是[batch,height,width,channels]这样的shape。

ksize:池化窗口的大小,取一个思维向量,一般是[1,height,width,1],因为我们不想在batch和channels上做池化,所以这两个维度设为1.

strides:和卷积参数含义类似,窗口在每一个维度上滑动的步长,一般也是[1,stride,stride,1]。

padding:和卷积参数含义一样,也是"VALID"或者"SAME"。

该函数返回一个Tensor。类型不变,shape仍然是[batch,height,width,channels]这种形式。
池化层一般都是接在卷积层后面,用于减少参数矩阵的维度,从而减小最后全连接层的参数数目

 

x_ = tf.placeholder(tf.float32,[None,784])
y_ = tf.placeholder(tf.float32,[None,10])

 
在 MNIST 数据集中的每张图片由 28 x 28 个像素点构成, 每个像素点用一个灰度值表示.
在这里, 我们将 28 x 28 的像素展开为一个一维的行向量, 这些行向量就是图片数组里的行(每行 784 个值, 或者说每行就是代表了一张图片).
mnist 函数返回的第二个数组(labels) 包含了相应的目标变量, 也就是手写数字的类标签(整数 0-9).None的取值代表了batch的大小,数值不固定。
 

tf.placeholder(dtype, shape=None, name=None)
TensorFlow中的占位符,用于feed_dict传入外部数据。
这里None是随便传入几个样本,784是每一个样本有784列,这是确定的(因为用来训练的数据集为[55000,784]),每行代表一张图,用None代表你可以指定传入多少图。
下面的y也是这样,10代表10个类别
dtype:数据类型。
shape:数据的维度。默认为None,表示没有限制
name:名称
返回类型:Tensor
 

x = tf.reshape(x_,shape=[-1,28,28,1])

 
将x_变为一个4d向量,其第2、第3维对应图片的高、宽,最后一维代表图片的颜色通道数(因为是灰度图所以这里的通道数为1,如果是rgb彩色图,则为3)。-1代表自动计算剩下的维度。在这里-1就是指含有28x28x1向量的个数,具体多少个要看x_的输入,然后计算。详情指路:https://blog.csdn.net/qq_41424519/article/details/81778984
 

w_conv1 = weight_variable([5,5,1,32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x,w_conv1)+b_conv1)
h_pool1 = max_pooling(h_conv1)

 
搭建第一层网络:它由一个卷积接一个max pooling完成。卷积在每个5x5的patch中算出32个特征。卷积的权重张量形状是[5, 5, 1, 32],前两个维度是patch的大小,接着是输入的通道数目,最后是输出的通道数目。 而对于每一个输出通道都有一个对应的偏置量b。32个过滤器。
 
卷积层:x输入为[-1,28,28,1],经过32个过滤器,共享权重矩阵为1x5x5的卷积,且滑动步长为[1,1,1,1],最后输出为[-1,28,28,32],因为长和宽上的滑动步长为1,1,且padding=‘SAME’,所以原图像的高宽不变为28,28.又经过32个过滤器,通道数变为32.
 
池化层:输入为:[-1,28,28,32],滑动步长为[1,2,2,1],长和宽上的步长为2,所以输出图像的长和宽减半,batch和channels都是1,不变。所以输出图像为[-1,14,14,32]
 
激活函数tf.nn.relu()
函数的目的是,将输入小于0的值赋值为0,输入大于0的值不变
 

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_pooling(h_conv2)

 
第二层网络的搭建,64个过滤器,共享权重矩阵为32x5x5
 
卷积层:x输入为[-1,14,14,32],经过64个过滤器,共享权重矩阵为3255的卷积,且滑动步长为[1,1,1,1],最后输出为[-1,14,14,64],因为长和宽上的滑动步长为1,1,padding=‘SAME’,所以原图像的高宽不变为28,28.且经过64个过滤器,通道数变为64.
池化层:输入为:[-1,14,14,64],滑动步长为[1,2,2,1],长和宽上的步长为2,所以输出图像的长和宽减半,batch和channels都是1,不变。所以输出图像为[-1,7,7,64]
 

h_pool2_falt = tf.reshape(h_pool2,[-1,7*7*64])
w_h = weight_variable([7*7*64,1024])
b_h = bias_variable([1024])
hidden = tf.nn.relu(tf.matmul(h_pool2_falt,w_h) + b_h)

 
全连接层
现在,图片尺寸减小到7x7,我们加入一个有1024个神经元的全连接层,用于处理整个图片。我们把池化层输出的张量reshape成一些向量,乘上权重矩阵,加上偏置,然后对其使用ReLU。
 
h_pool2_falt的形状是[-1,7764] ,w_h的形状为[7764,1024],两者矩阵相乘之后的形状为[-1,1024],这里-1的含义和前面相同
最终我们将会把每张图片提纯成一个[1,1024]向量,最终实现的转换为:
[batch,7,7,64]⇒[batch,1024] (batch代表个数)
 
tf.matmul(a, b, transpose_a=False, transpose_b=False, adjoint_a=False, adjoint_b=False, a_is_sparse=False, b_is_sparse=False, name=None)
a: 一个类型为 float16, float32, float64, int32, complex64, complex128 且张量秩 > 1 的张量。
b: 一个类型跟张量a相同的张量。
其他的参数没用上就不一一介绍了
返回值: 一个跟张量a和张量b类型一样的张量且最内部矩阵是a和b中的相应矩阵的乘积。
 
卷积取的是局部特征,全连接就是把以前的局部特征重新通过权值矩阵组装成完整的图。因为用到了所有的局部特征,所以叫全连接。
https://blog.csdn.net/m0_37407756/article/details/80904580
 

keep_prob = tf.placeholder(tf.float32)
hidden_drop = tf.nn.dropout(hidden,keep_prob)

 
tf.nn.dropout的作用为:
加入弃权,把部分神经元输出置为0,为了减少过拟合,使得输入矩阵,hidden中的元素随机变为0,概率大概为输入参数keep_prob.其余的元素的值变为element/keep_prob,变为 0的输出相当于我们所丢弃掉的结点,这样在每次迭代完之后,都会更新我们的卷积神经网络,使得新的一部分结点在新的迭代中将会被 drop 掉,由此就实现了 dropout 过程。
 

w_o = weight_variable([1024,10])
b_o = bias_variable([10])
output = tf.nn.softmax(tf.matmul(hidden_drop,w_o) + b_o)

 
hidden_drop形状为[-1,1024],w_o 形状为[1024,10],所以矩阵乘法得出最后的out_put的形状为[-1,10],它是一个行数未知列数为10矩阵
 
f.nn.softmax(logits,axis=None,name=None,dim=None)
logits:一个非空的Tensor。必须是下列类型之一:half, float32,float64
axis:将在其上执行维度softmax。默认值为-1,表示最后一个维度
name:操作的名称(可选)
dim:axis的已弃用的别名
返回:一个Tensor,与logits具有相同的类型和shape
 
通过Softmax回归,将logistic的预测二分类的概率的问题推广到了n分类的概率的问题
softmax的输出向量是概率,该样本属于各个类的概率,这里是十个类别0-9,每一列的数值代表该类别的概率。输出的向量的每个值的大小范围为0到1。
当一个样本经过softmax层并输出一个向量,会取这个向量中值最大的那个数的index作为这个样本的预测标签,
下文中的**tf.argmax(pred , 1)**就是取最大值操作
 

cost = tf.reduce_mean(-tf.reduce_sum(y_*tf.log(output),axis=1))

 
交叉熵评估代价,设置对数似然损失函数
 
tf.log函数,这个函数完成了对张量所有元素依次求对数的功能
 
乘以y后,得到了一个n×m的二维矩阵,其中n为一个batch中样例的数量,m为分类的类别数量根据交叉熵公式,(可以理解为[-1,10])
应该将每行中的m个结果相加得到的所有样例的交叉熵,然后在取平均值,得到一个batch的平均交叉熵。
 
tf.reduce_sum(input_tensor,axis=None,keepdims=None,name=None,reduction_indices=None,keep_dims=None)
input_tensor:待求和的tensor;
reduction_indices:在以前版本中用来指定轴,已弃用;
有两个取值分别为0和1,通常用reduction_indices=[0]或reduction_indices=[1]来传递参数。
从上图可以看出,当等于0时,是纵向对矩阵求和,原来矩阵有几列就得到几个值;
相似地,当等于1时,是横向对矩阵求和;当省略参数时,默认对矩阵所有元素进行求和。
reduce_sum应该理解为压缩求和,用于降维,不懂指路:https://blog.csdn.net/lxg0807/article/details/74625861
 
tf.reduce_mean(input_tensor, axis=None, keep_dims=False, name=None, reduction_indices=None)
input_tensor: 输入的待降维的tensor
axis: 指定的轴,如果不指定,则计算所有元素的均值
keep_dims:是否降维度,默认False。设置为True,输出的结果保持输入tensor的形状,设置为False,输出结果会降低维度
name: 操作的名称
reduction_indices:在以前版本中用来指定轴,已弃用
tf.reduce_mean()函数用于计算张量tensor沿着指定的数轴(tensor的某一维度)上的平均值,
主要用作降维或者计算tensor(图像)的平均值。
指路:https://blog.csdn.net/qq_42847843/article/details/103515505
 

train = tf.train.AdamOptimizer(0.0001).minimize(cost)

 
0.0001是学习率
AdamOptimizer是TensorFlow中实现Adam算法的优化器。Adam即Adaptive Moment Estimation(自适应矩估计),
是一个寻找全局最优点的优化算法,引入了二次梯度校正。Adam 算法相对于其它种类算法有一定的优越性,是比较常用的算法之一
 
minimize(loss,global_step=None, var_list=None,gate_gradients=GATE_OP,
aggregation_method=None,colocate_gradients_with_ops=False,name=None,
grad_loss=None)

主要的两个参数:
loss:构造优化的损失函数,类型Tensor
global_step:通常于学习率变化一起使用,可选变量,在变量更新后增加1。
minimize() 函数处理了梯度计算和参数更新两个操作
 

correct = tf.equal(tf.argmax(output,1),tf.argmax(y_,1))

 
预测类别是否与真实类别相等
equal(x, y, name=None)
判断x, y 是不是相等,它的判断方法不是整体判断,而是逐个元素进行判断,如果相等就是True,不相等,就是False。
 
tf.argmax(input, axis=None, name=None, dimension=None)
此函数是对矩阵按行或列计算最大值,输出最大值的下标,也就是概率最大的类别
input:输入Tensor
axis:0表示按列,1表示按行
name:名称
dimension:和axis功能一样,默认axis取值优先。新加的字段
返回:Tensor 一般是行或列的最大值下标向量

 

accuracy = tf.reduce_mean(tf.cast(correct,tf.float32))

 
求准确率
 
cast(x,dtype,name=None)
将x的数据格式转化成dtype数据类型

 

training_accuracy_list = []
test_accuracy_list = []
training_cost_list=[]
test_cost_list = []

#使用会话执行图
sess.run(tf.global_variables_initializer())#初始化变量

#开始迭代 使用Adam优化的随机梯度下降法
for i in range(5000):
    # 一个epoch需要迭代次数计算公式:测试集长度 / batch_size
    x_batch,y_batch = mnist.train.next_batch(batch_size = 64)
    #开始迭代,喂入数据
    train.run(feed_dict={x_:x_batch,y_:y_batch,keep_prob:1.0})
    if (i+1)%200 == 0:
        training_accuracy,training_cost = sess.run([accuracy,cost],feed_dict={x_:x_batch,y_:y_batch,keep_prob:1.0})
        training_accuracy_list.append(training_accuracy)
        training_cost_list.append(training_cost)
        print('Step{0}:Training set accuracy {1},cost {2}'.format(i+1,training_accuracy,training_cost))

 
mnist.train.next_batch是专门用于由tensorflow提供的MNIST教程的函数。 它的工作原理是在开始时将训练图像和标签对随机化,并在每次调用该函数时选择每个随后的batch_size张图像。一旦到达末尾,图像标签对将再次随机分配,并重复该过程。仅在使用所有可用对后,才重新组合和重复整个数据集。
 

for i in range(200):
    x_batch,y_batch = mnist.test.next_batch(batch_size = 50)
    test_accuracy,test_cost = sess.run([accuracy,cost],feed_dict={x_:x_batch,y_:y_batch,keep_prob:1.0})
    test_accuracy_list.append(test_accuracy)
    test_cost_list.append(test_cost)
    if (i+1)%200==0:
        print('Step{0}:Training set accuracy {1},cost {2}'.format(i + 1, test_accuracy, test_cost))
print('Test accuracy:',np.mean(test_accuracy_list))

 
全部训练完成后做测试 分成200次,一次测试50个样本
输出测试机准确率 如果一次性全部做测试,内容不够用会出现OOM错误。所以测试时选取比较小的mini_batch来测试
 
后面就比较容易理解了,我就一次性贴上来了。
 

'''
图像操作
'''
import matplotlib.pyplot as plt
# 取一张图片测试
img = mnist.train.images[2]
label = mnist.train.labels[2]

print('图像对应的标签{0}'.format(np.argmax(label)))

plt.figure()
#子图1
plt.subplot(1,2,1)
plt.imshow(img.reshape(28,28)) #显示的是热度图片
plt.axis('off')   #不显示坐标

#子图2
plt.subplot(1,2,2)
plt.imshow(img.reshape(28,28),cmap='gray')    #显示灰度图片
plt.axis('off')

plt.show()

'''
显示卷积和池化层结果
'''
plt.figure(figsize=(1.0*8,1.6*4))
plt.subplots_adjust(bottom=0,left=.01,right=.99,top=.90,hspace=.35)
'''
subplots_adjust(self, left=None, bottom=None, right=None, top=None, wspace=None, hspace=None)
left, right, bottom, top:子图所在区域的边界。
当值大于1.0的时候子图会超出figure的边界从而显示不全;值不大于1.0的时候,子图会自动分布在一个矩形区域(下图灰色部分)。
要保证left < right, bottom < top,否则会报错。
wspace, hspace:子图之间的横向和纵向间距
'''
#显示第一个卷积层之后的结果  (1,28,28,32)
conv1 = h_conv1.eval(feed_dict={x_:img.reshape([-1,784]),y_:label.reshape([-1,10]),keep_prob:1.0})
print('conv1 shape',conv1.shape)

for i in range(32):
    show_image = conv1[:,:,:,1]
    show_image.shape = [28,28]
    plt.subplot(4,8,i+1)
    plt.imshow(show_image,cmap='gray')
    plt.axis('off')
plt.show()


plt.figure(figsize=(1.2*8,2.0*4))
plt.subplots_adjust(bottom=0,left=.01,right=.99,top=.90,hspace=.35)
#显示第一个池化层之后的结果  (1,14,14,32)
pool1 = h_pool1.eval(feed_dict={x_:img.reshape([-1,784]),y_:label.reshape([-1,10]),keep_prob:1.0})
print('pool1 shape',pool1.shape)

for i in range(32):
    show_image = pool1[:,:,:,1]
    show_image.shape = [14,14]
    plt.subplot(4,8,i+1)
    plt.imshow(show_image,cmap='gray')
    plt.axis('off')
plt.show()

 
 

最后,附上完整的代码和全部的解析

import tensorflow as tf
import numpy as np
# 导入数据
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets('E:/program/tensorflow_learning/example/mnist_program/mnist',one_hot=True)
# 查看数据类型
print(type(mnist))
print('train image shape输入数据:',mnist.train.images.shape,'trian label shape:', mnist.train.labels.shape)#(55000, 784),(55000, 10)
print('val image shape:', mnist.validation.images.shape)#(5000, 784)
print('test image shape:', mnist.test.images.shape)#(10000, 784)

#设置tensorflow对GPU使用按需分配
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.InteractiveSession(config=config)

def weight_variable(shape):
    # 使用正态分布初始化权值
    init = tf.truncated_normal(shape,stddev=0.1)
    '''
    tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None) 
    shape表示一维的张量,也是输出的张量。
    mean是正态分布的均值
    stddev是标准差
    从截断的正态分布中输出随机值。 生成的值服从具有指定平均值和标准偏差的正态分布,如果生成的值大于平均值2个标准偏差的值则丢弃重新选择。
    在tf.truncated_normal中如果init的取值在区间(μ-2σ,μ+2σ)之外则重新进行选择。这样保证了生成的值都在均值附近
    '''
    return tf.Variable(init)
'''
tf.Variable(initializer,name),
参数initializer是初始化参数,
name是可自定义的变量名称
'''

def bias_variable(shape):
    init = tf.constant(0.1,shape=shape)
    '''
    tf.constant(value,dtype=None,shape=None,name='Const',verify_shape=False)
    创建一个常量tensor,按照给出value来赋值,可以用shape来指定其形状。value可以是一个数,也可以是一个list。 如果是一个数,那么这个常量中所有值的按该数来赋值。 
    如果是list,那么len(value)一定要小于等于shape展开后的长度。赋值时,先将value中的值逐个存入。如果len(value)小于shape展开后的长度,则全部存入value的最后一个值。
    详情指路:https://blog.csdn.net/csdn_jiayu/article/details/82155224
    '''
    return tf.Variable(init)

# 定义卷积层
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:指定需要做卷积的输入图像,它要求是一个Tensor,具有[batch,in_height,in_width,in_channels]这样的形状(shape),
具体含义是"训练时一个batch的图片数量,图片高度,图片宽度,图片通道数",注意这是一个四维的Tensor,要求类型为float32或者float64.
filter:相当于CNN中的卷积核,它要求是一个Tensor,具有[filter_height,filter_width,in_channels,out_channels]这样的shape,
具体含义是"卷积核的高度,卷积核的宽度,图像通道数,滤波器个数",要求类型与参数input相同。有一个地方需要注意,第三维in_channels,就是参数input中的第四维
strides:卷积时在图像每一维的步长,这是一个一维的向量,长度为4,与输入input对应,一般值为[1,x,x,1],x取步长。
padding:定义元素边框与元素内容之间的空间。string类型的量,只能是"SAME"和“VALID”其中之一,这个值决定了不同的卷积方式。"SAME"和“VALID”的不同详情指路 :https://blog.csdn.net/wuzqChom/article/details/74785643
use_cudnn_on_gpu:bool类型,是否使用cudnn加速,默认是True.
name:指定名字
'''
def max_pooling(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)
tf.nn.avg_pooll(input,ksize,strides,padding,name=None)
input:需要池化的输入,一般池化层接在卷积层后面,所以输入通常是feature map,依然是[batch,height,width,channels]这样的shape。
ksize:池化窗口的大小,取一个思维向量,一般是[1,height,width,1],因为我们不想在batch和channels上做池化,所以这两个维度设为1.
strides:和卷积参数含义类似,窗口在每一个维度上滑动的步长,一般也是[1,stride,stride,1]。
padding:和卷积参数含义一样,也是"VALID"或者"SAME"。
该函数返回一个Tensor。类型不变,shape仍然是[batch,height,width,channels]这种形式。
池化层一般都是接在卷积层后面,用于减少参数矩阵的维度,从而减小最后全连接层的参数数目
'''

'''
在 MNIST 数据集中的每张图片由 28 x 28 个像素点构成, 每个像素点用一个灰度值表示.
在这里, 我们将 28 x 28 的像素展开为一个一维的行向量, 这些行向量就是图片数组里的行(每行 784 个值, 或者说每行就是代表了一张图片).
mnist 函数返回的第二个数组(labels) 包含了相应的目标变量, 也就是手写数字的类标签(整数 0-9).None的取值代表了batch的大小,数值不固定。
'''
x_ = tf.placeholder(tf.float32,[None,784])
y_ = tf.placeholder(tf.float32,[None,10])
'''
tf.placeholder(dtype, shape=None, name=None)
TensorFlow中的占位符,用于feed_dict传入外部数据。
这里None是随便传入几个样本,784是每一个样本有784列,这是确定的(因为用来训练的数据集为[55000,784]),每行代表一张图,用None代表你可以指定传入多少图。
下面的y也是这样,10代表10个类别
dtype:数据类型。
shape:数据的维度。默认为None,表示没有限制
name:名称
返回类型:Tensor
'''

# 将x_变为一个4d向量,其第2、第3维对应图片的高、宽,最后一维代表图片的颜色通道数
# (因为是灰度图所以这里的通道数为1,如果是rgb彩色图,则为3)。-1代表自动计算剩下的维度。在这里-1就是指
# 含有28x28x1向量的个数,具体多少个要看x_的输入,然后计算。详情指路:https://blog.csdn.net/qq_41424519/article/details/81778984
x = tf.reshape(x_,shape=[-1,28,28,1])


# 搭建第一层网络:它由一个卷积接一个max pooling完成。卷积在每个5x5的patch中算出
# 32个特征。卷积的权重张量形状是[5, 5, 1, 32],前两个维度是patch的大小,接着是输入的通道数目,
# 最后是输出的通道数目。 而对于每一个输出通道都有一个对应的偏置量b。
# 32个过滤器,
w_conv1 = weight_variable([5,5,1,32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x,w_conv1)+b_conv1)
h_pool1 = max_pooling(h_conv1)
'''
卷积层:x输入为[-1,28,28,1],经过32个过滤器,共享权重矩阵为1*5*5的卷积,且滑动步长为[1,1,1,1],最后输出为[-1,28,28,32],因为长和宽上的滑动步长为1,1
padding='SAME',所以原图像的高宽不变为28,28.且经过32个过滤器,通道数变为32.
池化层:输入为:[-1,28,28,32],滑动步长为[1,2,2,1],长和宽上的步长为2,所以输出图像的长和宽减半,batch和channels都是1,不变。所以输出图像为[-1,14,14,32]

激活函数tf.nn.relu()
函数的目的是,将输入小于0的值赋值为0,输入大于0的值不变
'''

# 第二层网络的搭建,64个过滤器,共享权重矩阵为32*5*5
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_pooling(h_conv2)
'''
卷积层:x输入为[-1,14,14,32],经过64个过滤器,共享权重矩阵为32*5*5的卷积,且滑动步长为[1,1,1,1],最后输出为[-1,14,14,64],因为长和宽上的滑动步长为1,1
padding='SAME',所以原图像的高宽不变为28,28.且经过64个过滤器,通道数变为64.
池化层:输入为:[-1,14,14,64],滑动步长为[1,2,2,1],长和宽上的步长为2,所以输出图像的长和宽减半,batch和channels都是1,不变。所以输出图像为[-1,7,7,64]
'''

# 全连接层
# 现在,图片尺寸减小到7x7,我们加入一个有1024个神经元的全连接层,用于处理整个图片。我们把池化层输
# 出的张量reshape成一些向量,乘上权重矩阵,加上偏置,然后对其使用ReLU。
h_pool2_falt = tf.reshape(h_pool2,[-1,7*7*64])
w_h = weight_variable([7*7*64,1024])
b_h = bias_variable([1024])
hidden = tf.nn.relu(tf.matmul(h_pool2_falt,w_h) + b_h)
'''
h_pool2_falt的形状是[-1,7*7*64] ,w_h的形状为[7*7*64,1024],两者矩阵相乘之后的形状为[-1,1024],这里-1的含义和前面相同
最终我们将会把每张图片提纯成一个[1,1024]向量,最终实现的转换为:
[batch,7,7,64]⇒[batch,1024] (batch代表个数)

tf.matmul(a, b, transpose_a=False, transpose_b=False, adjoint_a=False, adjoint_b=False, a_is_sparse=False, b_is_sparse=False, name=None) 
a: 一个类型为 float16, float32, float64, int32, complex64, complex128 且张量秩 > 1 的张量。
b: 一个类型跟张量a相同的张量。
其他的参数没用上就不一一介绍了
返回值: 一个跟张量a和张量b类型一样的张量且最内部矩阵是a和b中的相应矩阵的乘积。

卷积取的是局部特征,全连接就是把以前的局部特征重新通过权值矩阵组装成完整的图。
因为用到了所有的局部特征,所以叫全连接。
https://blog.csdn.net/m0_37407756/article/details/80904580
'''

keep_prob = tf.placeholder(tf.float32)
hidden_drop = tf.nn.dropout(hidden,keep_prob)
'''
tf.nn.dropout的作用为:
加入弃权,把部分神经元输出置为0,为了减少过拟合
使得输入矩阵,hidden中的元素随机变为0,概率大概为输入参数keep_prob
其余的元素的值变为element/keep_prob,变为 0的输出相当于我们所丢弃掉的结点,
这样在每次迭代完之后,都会更新我们的卷积神经网络,使得新的一部分结点在新的迭代中将会被 drop 掉,由此就实现了 dropout 过程。
'''

w_o = weight_variable([1024,10])
b_o = bias_variable([10])
output = tf.nn.softmax(tf.matmul(hidden_drop,w_o) + b_o)
'''
hidden_drop形状为[-1,1024],w_o 形状为[1024,10],所以矩阵乘法得出最后的out_put的形状为[-1,10],它是一个行数未知列数为10矩阵

f.nn.softmax(logits,axis=None,name=None,dim=None)
logits:一个非空的Tensor。必须是下列类型之一:half, float32,float64
axis:将在其上执行维度softmax。默认值为-1,表示最后一个维度
name:操作的名称(可选)
dim:axis的已弃用的别名
返回:一个Tensor,与logits具有相同的类型和shape
通过Softmax回归,将logistic的预测二分类的概率的问题推广到了n分类的概率的问题
softmax的输出向量是概率,该样本属于各个类的概率,这里是十个类别0-9,每一列的数值代表该类别的概率。输出的向量的每个值的大小范围为0到1。
当一个样本经过softmax层并输出一个向量,会取这个向量中值最大的那个数的index作为这个样本的预测标签,
下文中的tf.argmax(pred , 1)就是取最大值操作
'''
# # 交叉熵评估代价,设置对数似然损失函数
cost = tf.reduce_mean(-tf.reduce_sum(y_*tf.log(output),axis=1))
'''
tf.log函数,这个函数完成了对张量所有元素依次求对数的功能

乘以y后,得到了一个n×m的二维矩阵,其中n为一个batch中样例的数量,m为分类的类别数量根据交叉熵公式,(可以理解为[-1,10])
应该将每行中的m个结果相加得到的所有样例的交叉熵,然后在取平均值,得到一个batch的平均交叉熵。

tf.reduce_sum(input_tensor,axis=None,keepdims=None,name=None,reduction_indices=None,keep_dims=None)
input_tensor:待求和的tensor;
reduction_indices:在以前版本中用来指定轴,已弃用;
有两个取值分别为0和1,通常用reduction_indices=[0]或reduction_indices=[1]来传递参数。
从上图可以看出,当等于0时,是纵向对矩阵求和,原来矩阵有几列就得到几个值;
相似地,当等于1时,是横向对矩阵求和;当省略参数时,默认对矩阵所有元素进行求和。
reduce_sum应该理解为压缩求和,用于降维,不懂指路:https://blog.csdn.net/lxg0807/article/details/74625861

tf.reduce_mean(input_tensor, axis=None, keep_dims=False, name=None, reduction_indices=None)
input_tensor: 输入的待降维的tensor
axis: 指定的轴,如果不指定,则计算所有元素的均值
keep_dims:是否降维度,默认False。设置为True,输出的结果保持输入tensor的形状,设置为False,输出结果会降低维度
name: 操作的名称
reduction_indices:在以前版本中用来指定轴,已弃用
tf.reduce_mean()函数用于计算张量tensor沿着指定的数轴(tensor的某一维度)上的平均值,
主要用作降维或者计算tensor(图像)的平均值。
指路:https://blog.csdn.net/qq_42847843/article/details/103515505
'''
train = tf.train.AdamOptimizer(0.0001).minimize(cost)
'''
0.0001是学习率
AdamOptimizer是TensorFlow中实现Adam算法的优化器。Adam即Adaptive Moment Estimation(自适应矩估计),
是一个寻找全局最优点的优化算法,引入了二次梯度校正。Adam 算法相对于其它种类算法有一定的优越性,是比较常用的算法之一

minimize(loss,global_step=None, var_list=None,gate_gradients=GATE_OP,
aggregation_method=None,colocate_gradients_with_ops=False,name=None,
grad_loss=None)
主要的两个参数:
loss:构造优化的损失函数,类型Tensor
global_step:通常于学习率变化一起使用,可选变量,在变量更新后增加1。
minimize() 函数处理了梯度计算和参数更新两个操作
'''
correct = tf.equal(tf.argmax(output,1),tf.argmax(y_,1))
'''
预测类别是否与真实类别相等
equal(x, y, name=None)
判断x, y 是不是相等,它的判断方法不是整体判断,而是逐个元素进行判断,如果相等就是True,不相等,就是False。

tf.argmax(input, axis=None, name=None, dimension=None)
    此函数是对矩阵按行或列计算最大值,输出最大值的下标,也就是概率最大的类别
    input:输入Tensor
    axis:0表示按列,1表示按行
    name:名称
    dimension:和axis功能一样,默认axis取值优先。新加的字段
    返回:Tensor 一般是行或列的最大值下标向量
'''
#求准确率
accuracy = tf.reduce_mean(tf.cast(correct,tf.float32))
'''
   cast(x,dtype,name=None)将x的数据格式转化成dtype数据类型
'''
training_accuracy_list = []
test_accuracy_list = []
training_cost_list=[]
test_cost_list = []

#使用会话执行图
sess.run(tf.global_variables_initializer())#初始化变量

#开始迭代 使用Adam优化的随机梯度下降法
for i in range(5000):
    # 一个epoch需要迭代次数计算公式:测试集长度 / batch_size
    x_batch,y_batch = mnist.train.next_batch(batch_size = 64)
    '''
               mnist.train.next_batch是专门用于由tensorflow提供的MNIST教程的函数。
               它的工作原理是在开始时将训练图像和标签对随机化,并在每次调用该函数时选择每个随后的batch_size张图像。
               一旦到达末尾,图像标签对将再次随机分配,并重复该过程。仅在使用所有可用对后,才重新组合和重复整个数据集。
    '''
    #开始迭代,喂入数据
    train.run(feed_dict={x_:x_batch,y_:y_batch,keep_prob:1.0})
    if (i+1)%200 == 0:
        training_accuracy,training_cost = sess.run([accuracy,cost],feed_dict={x_:x_batch,y_:y_batch,keep_prob:1.0})
        training_accuracy_list.append(training_accuracy)
        training_cost_list.append(training_cost)
        print('Step{0}:Training set accuracy {1},cost {2}'.format(i+1,training_accuracy,training_cost))

#全部训练完成后做测试  分成200次,一次测试50个样本
#输出测试机准确率   如果一次性全部做测试,内容不够用会出现OOM错误。所以测试时选取比较小的mini_batch来测试
#test_accuracy = accuracy.eval(feed_dict={x_:mnist.test.images,y_:mnist.test.labels})
for i in range(200):
    x_batch,y_batch = mnist.test.next_batch(batch_size = 50)
    test_accuracy,test_cost = sess.run([accuracy,cost],feed_dict={x_:x_batch,y_:y_batch,keep_prob:1.0})
    test_accuracy_list.append(test_accuracy)
    test_cost_list.append(test_cost)
    if (i+1)%200==0:
        print('Step{0}:Training set accuracy {1},cost {2}'.format(i + 1, test_accuracy, test_cost))
print('Test accuracy:',np.mean(test_accuracy_list))



'''
图像操作
'''
import matplotlib.pyplot as plt
# 取一张图片测试
img = mnist.train.images[2]
label = mnist.train.labels[2]

print('图像对应的标签{0}'.format(np.argmax(label)))

plt.figure()
#子图1
plt.subplot(1,2,1)
plt.imshow(img.reshape(28,28)) #显示的是热度图片
plt.axis('off')   #不显示坐标

#子图2
plt.subplot(1,2,2)
plt.imshow(img.reshape(28,28),cmap='gray')    #显示灰度图片
plt.axis('off')

plt.show()

'''
显示卷积和池化层结果
'''
plt.figure(figsize=(1.0*8,1.6*4))
plt.subplots_adjust(bottom=0,left=.01,right=.99,top=.90,hspace=.35)
'''
subplots_adjust(self, left=None, bottom=None, right=None, top=None, wspace=None, hspace=None)
left, right, bottom, top:子图所在区域的边界。
当值大于1.0的时候子图会超出figure的边界从而显示不全;值不大于1.0的时候,子图会自动分布在一个矩形区域(下图灰色部分)。
要保证left < right, bottom < top,否则会报错。
wspace, hspace:子图之间的横向和纵向间距
'''
#显示第一个卷积层之后的结果  (1,28,28,32)
conv1 = h_conv1.eval(feed_dict={x_:img.reshape([-1,784]),y_:label.reshape([-1,10]),keep_prob:1.0})
print('conv1 shape',conv1.shape)

for i in range(32):
    show_image = conv1[:,:,:,1]
    show_image.shape = [28,28]
    plt.subplot(4,8,i+1)
    plt.imshow(show_image,cmap='gray')
    plt.axis('off')
plt.show()


plt.figure(figsize=(1.2*8,2.0*4))
plt.subplots_adjust(bottom=0,left=.01,right=.99,top=.90,hspace=.35)
#显示第一个池化层之后的结果  (1,14,14,32)
pool1 = h_pool1.eval(feed_dict={x_:img.reshape([-1,784]),y_:label.reshape([-1,10]),keep_prob:1.0})
print('pool1 shape',pool1.shape)

for i in range(32):
    show_image = pool1[:,:,:,1]
    show_image.shape = [14,14]
    plt.subplot(4,8,i+1)
    plt.imshow(show_image,cmap='gray')
    plt.axis('off')
plt.show()

 
 

如有错误,欢迎指出,大家一起进步。

tensorflow入门项目(二):手写数字识别之CNN模型(详解)_第1张图片

你可能感兴趣的:(深度学习)