此处摘录一个简单的CNN实例。
例子利用Minist数据集,利用两个卷积层(+两个池化层)和全连接层实现了手写数字照片的识别。
原图reshape为28*28的照片输入。
第一层:卷积层。32个5*5的卷积核,输入为28*28(*1)的照片,输出为28*28*32的照片,也就是把一张照片弄成大小不变的32张照片,1->32实际上是厚度,也就是通道数变了;
第二层:池化层。2*2的池化区域,上下步数为1,输入为28*28的32张照片,输出为14*14的32张照片。即28*28*32->14*14*32;
第三层:卷积层。64个5*5的卷积核,输入为14*14*32的照片,输出为14*14*64的照片。经过第一层把1张照片卷成32张,进一步卷成64张;
第四层:池化层。2*2的池化区域,上下步数为1,输入为14*14的64张照片,输出为7*7的64张照片。即14*14*64->7*7*64;
第五层:全连接层。输入为7*7*64的照片,输出为1024的一维向量。先将照片扁平化为7*7*64长度的一维向量,再拿这个一维向量去映射全连接层。并考虑过拟合,加入drropout层。上面五层激活函数都为relu。
第六层:全连接层。输入为1024一维向量,输出为10的一维向量,进行分类,激活函数为softmax,即多情况分类。此时输出的10长度的一维向量就是预测结果。
本文遇到的函数都在代码里的注释进行了详细说明。
提一下关于神经网络的实现方法和计算方法。总的来说就是每层和每层之间都有权值weight和偏置bias这两个参数,经过训练得出这些参数就可以。实现方法就是矩阵乘法,所以可以看到文中的计算都是h/x_image*W+b,即图像矩阵(不管是经过卷积的图像还是池化后的图像)乘以权值矩阵再加偏执矩阵,这些参数前后传递并优化和改正,最终得到这个这些很多的参数,这些参数就构成了神经网络。有了这些参数后,预测的时候输进去一个照片,经过reshape和一系列计算后,就得出了一个作为预测结果的10长度的一维向量,这就是预测的结果。
本质是一个张量,经过一系列矩阵运算后,得到了一个一维向量,一维向量里面最大的那个数就是对应位置的预测结果。
代码:
# 导入包
from __future__ import print_function
import tensorflow as tf
# 导入minist数据集,若没有则需要下载,此处可能出现多个错误,如tensorflow包里面没有examples,则需要自行下载,移入tensorflow目录下
from tensorflow.examples.tutorials.mnist import input_data
# 将数据集的数据进行one_hot编码为向量,one_hot编码又称为一位有效编码,采用N位表示N个状态
# 说明:mnist数据集不能直接使用,需要通过input_data模块进行初始化,否则会报mnist is not defined
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
# 首先整个来说网络的结构为:image层->conv层->池化层->conv层->池化层->全连接层->全连接层->分类器
# 计算准确度
def compute_accuracy(v_xs, v_ys):
global prediction
y_pre = sess.run(prediction, feed_dict={xs: v_xs, keep_prob:1})
correct_prediction = tf.equal(tf.argmax(y_pre, 1), tf.argmax(v_ys, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
result = sess.run(accuracy, feed_dict={xs: v_xs, ys: v_ys, keep_prob: 1})
return result
# 对权值weight和偏置bias进行初始化,生成随机数进行初始化
# tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
# truncated_normal函数:从截断的正态分布中输出随机值,shape表示生成张量的维度,mean是均值,stddev是标准差
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1) # 生成shape形状张量的随机数,标准差为0.1
return tf.Variable(initial)
# tf.constant函数:创建常量
# tf.constant(value,dtype=None,shape=None,name='Const',verify_shape=False)
# 第一个参数必须,可以是数字或者列表,后边四个参数可以不要,dtype数据类型,shape表示张量的形状
# 第四个参数name可以是任何内容,主要是字符串就行
# 第五个参数verify_shape默认为False,如果修改为True的话表示检查value的形状与shape是否相符,如果不符会报错
def bias_variable(shape):
initial = tf.constant(0.1, shape=shape) # 意义为给shape形状的张量都填写值0.1
return tf.Variable(initial)
# 定义卷积层,x为图片参数,W为权重weight参数
def conv2d(x, W):
# 直接调用函数tf.nn.conv2d
# strides为步长,四个参数,strides=[1,x_move,y_move,1]为步长
# padding:填充像素,两种形式:valid抽取的都在里面的原数据,抽取得到的照片变小,same有一部分在外面填充的是0,抽取得到图片大小不变
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
# 池化层,tf.nn.max_pool(value, ksize, strides, padding, name=None),
# 第一个参数是输入,也就是照片x,第二个池化窗口的大小,[1, height, width, 1],第三个窗口在每个维度上的滑动步长[1, stride,stride, 1]
# 返回值依然是一个张量,类型不变,shape仍然是[batch, height, width, channels]这种形式
# shape中的channel,即通道,一般的RGB图片的channel为3(红绿蓝),还有一种情况是channel为每个卷积层中卷积核的数量
def max_poll_2x2(x):
return tf.nn.max_pool(x, ksize=[1,2,2,1], strides=[1,2,2,1], padding='SAME')
# tf.placeholder(dtype,shape=None,name=None)
# dtype:数据类型,shape:数据形状。默认是None,就是一维值,也可以是多维(比如[2,3], [None, 3]表示列是3,行不定)
# placeholder()函数是在神经网络构建graph的时候在模型中的占位,此时并没有把要输入的数据传入模型,它只会分配必要的内存
xs = tf.placeholder(tf.float32, [None, 784])/255.
ys = tf.placeholder(tf.float32, [None, 10])
keep_prob = tf.placeholder(tf.float32) # 待会用到一维的dropout7
# 将照片xs进行reshape,变为28*28的照片,reshape=[batch, height, width, channels],此时batch为所有样本照片,最后channel为1,即黑白照片
x_image = tf.reshape(xs, [-1, 28, 28, 1])
## conv1:定义第一个卷积层
# 此处输入为28*28的照片,经过第一层卷积核为5*5的32个卷积核,每个照片得到28*28的32张照片,经过池化,得到14*14*32
# 首先利用上面的函数初始化定义权值weight和偏置bias,实际意义为32个卷积核,所以第一层卷积过后得到32张图片,bias参数自然为32个,weight参数为5*5*32个
W_conv1 = weight_variable([5,5,1,32]) # patch 5*5,代表卷积核大小,in size 1,out size 32,把高度为1(即黑白照channel为1,以前是3,reshape为1)即5*5*1的映射为高度32即1*1*32,
b_conv1 = bias_variable([32]) # 偏置定义为32个长度
# 利用定义好的卷积函数conv2d函数定义卷积层,本质为weight*照片+bias,最后加了一个激活函数relu进行非线性变化
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) # 输出为28*28*32
h_poll1 = max_poll_2x2(h_conv1) # 进行2*2的max池化,输出为14*14*32
## conv2:定义第二个卷积层
# 卷积核大小不变,但个数变为64个,将以前每张照片映射成的32张又映射一次变为64张
W_conv2 = weight_variable([5,5,32,64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_poll1, W_conv2) + b_conv2) # 输入为14*14*32,输出为14*14*64
h_poll2 = max_poll_2x2(h_conv2) # 输出为7*7*64
# 全连接层
# 输入为两层卷积过后的照片,即7*7*64,输出为1024的一维向量
# 关于下面两个函数:tf.matmul(a,b,transpose_a=False,transpose_b=False),将矩阵a,b相乘,生成a*b,后面若为true,则转置
# tf.nn.dropout(x,keep_prob,noise_shape=None,seed=None,name=None)
# x为输入的张量,keep_prob为float类型,值表示每个元素被保留下来的概率,设置神经元被选中的概率,
# noise_shape : 一个1维的int32张量,代表了随机产生“保留/丢弃”标志的shape。
# seed : 整形变量,随机数种子
# name:指定该操作的名字
W_fc1 = weight_variable([7*7*64, 1024])
b_fc1 = bias_variable([1024])
h_poll2_flat = tf.reshape(h_poll2, [-1, 7*7*64]) # 将卷积过后的照片转化为一维:[n_samples,7,7,64]->>[n_samples,7*7*64],作为全连接层的参数
h_fc1 = tf.nn.relu(tf.matmul(h_poll2_flat, W_fc1) + b_fc1) # 全连接层的参数和计算方法
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) # 考虑过拟合,加入dropout
# 第二个全连接层,将1024映射成10个数字,输出结果,采用的是softmax激活函数进行分类(多物体类别分类用softmax)
## fc2 layer ##
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
prediction = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
# 此时prediction为预测得出的1*10维张量
# the error between prediction and real data
# 预测和真实数据之间的误差
# 此处tf.reduce_sum是求和操作,求出预测值(即一个10长度的一维向量)各元素之和,reduction_indices=[1]指定轴是第二个,因为ys是n_samples*10
# 此处将真实值ys与预测值的log乘积求和取平均作为损失
# 此为二次代价函数
cross_entropy = tf.reduce_mean(-tf.reduce_sum(ys * tf.log(prediction),
reduction_indices=[1])) # loss
# tf.train.AdamOptimizer函数是adam优化算法,是一个寻找全局最优点的优化算法,引入了二次方梯度较正
# tf.train.AdamOptimizer.__init__(learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-08, use_locking=False, name='Adam')
# learning_rate为学习速率,beta1为一阶矩估计的指数衰减率,beta2为二阶矩估计的指数衰减率
# 此处minimize包含两个步骤:第一计算loss的梯度,第二用计算得到的梯度来更新对用的变量(即权重),即梯度下降法
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
# 以上的都为函数模型,接下来保存此模型,进行运行
# 保存模型函数,准备接下来运行
sess = tf.Session()
init = tf.global_variables_initializer() # 初始化参数,因为tf.Variable环境下tf建立的变量是没有初始化的,用此函数进行初始化
sess.run(init) # 运行模型,运行完之后可以sess.close()
# 建立session,在会话中,运行模型的时候通过feed_dict()函数向占位符喂入数据。
# 每50步输出准确率
for i in range(1000):
batch_xs, batch_ys = mnist.train.next_batch(100)
sess.run(train_step, feed_dict={xs: batch_xs, ys: batch_ys, keep_prob: 0.5})
if i % 50 == 0:
print(compute_accuracy(
mnist.test.images[:1000], mnist.test.labels[:1000])) # 传入的参数分别为图片参数和label,输出准确率
此代码涉及到的几个函数:
(1)tf.reduce_mean():用于计算张量tensor沿着指定的数轴(tensor的某一维度)上的的平均值,主要用作降维或者计算tensor(图像)的平均值。
reduce_mean(input_tensor,
axis=None,
keep_dims=False,
name=None,
reduction_indices=None)
——第一个参数input_tensor: 输入的待降维的tensor;
——第二个参数axis: 指定的轴,如果不指定,则计算所有元素的均值;
——第三个参数keep_dims:是否降维度,设置为True,输出的结果保持输入tensor的形状,设置为False,输出结果会降低维度;
——第四个参数name: 操作的名称;
——第五个参数 reduction_indices:在以前版本中用来指定轴,已弃用。
举例:
import tensorflow.compat.v1 as tf
# 此处因为tensorflow2.0不向下兼容,所以改为这个包
tf.disable_eager_execution() #可以用于从TensorFlow 1.x到2.x的复杂迁移项目的程序开头
x = [[1, 2, 3],
[1, 2, 3]]
xx = tf.cast(x, tf.float32)
mean_all = tf.reduce_mean(xx, keepdims=False)
mean_0 = tf.reduce_mean(xx, axis=0, keepdims=False)
mean_1 = tf.reduce_mean(xx, axis=1, keepdims=False)
with tf.Session() as sess:
m_a, m_0, m_1 = sess.run([mean_all, mean_0, mean_1])
print(m_a) # output: 2.0
print(m_0) # output: [ 1. 2. 3.]
print(m_1) # output: [ 2. 2.]
(2)tf.reduce_sum():用于计算张量tensor沿着某一维度的和,可以在求和后降维。
tf.reduce_sum(
input_tensor,
axis=None,
keepdims=None,
name=None,
reduction_indices=None,
keep_dims=None)
- 类似的方法还有:
- tf.reduce_mean():计算tensor指定轴方向上的所有元素的累加和;
- tf.reduce_max():计算tensor指定轴方向上的各个元素的最大值;
- tf.reduce_all():计算tensor指定轴方向上的各个元素的逻辑和(and运算);
- tf.reduce_any():计算tensor指定轴方向上的各个元素的逻辑或(or运算);
(3)tf.Session():session是tensorflow为了控制和输出文件的执行的语句。运行session.run()可以得到你想要的执行结果,或者是你要运算的部分。
同时为了解决tf版本不兼容的问题,使用命令tf.compat.v1.Session()可以调用tf1.0中的方法。
同时使用语句tf.compat.v1.disable_eager_execution()。
import tensorflow as tf
tf.compat.v1.disable_eager_execution()
matrix1 = tf.constant([[3,3]])
matrix2 = tf.constant([[2],
[2]])
product = tf.matmul(matrix1,matrix2)
因为 product 不是直接计算的步骤, 所以我们会要使用 Session 来激活 product 并得到计算结果. 有两种形式使用会话控制 Session 。
# method 1
sess = tf.compat.v1.Session()
result = sess.run(product)
print(result)
sess.close()
# method 2
with tf.compat.v1.Session() as sess:
result2 = sess.run(product)
print(result2)