卷积神经网络最主要在图像识别领域有很重要的应用,属于深度前馈神经网络范畴,
卷积神经网络一般结构为输入层–卷积层–池化层–全连接层–输出层。
一般步骤
1.定义超参数
2.定义卷积层和全连接权重参数初始化函数
3.导入训练数据
4.用占位符表示输入数据和标签
5.搭建网络模型(卷积层,非线性化relu,池化层,全连接层)
6.定义损失(交叉熵损失+参数的正则化损失)
7.启动队列,开始训练
8.验证模型
输入数据一般为图像,一般代表的是一张图片的像素矩阵,根据通道数的不同,图片像素矩阵有着不同的深度值,黑白图片只有一个通道,就深度为1。而彩色RGB图片有三个通道,就有深度为3。
可以使用随机变换的方法来人为的增加数据集。详见tensorflow图像常用变换方法
读取图像并进行预处理,做为后续评估和训练的输入;
卷积层进行卷积操作,用卷积核将像素矩阵映射为特征矩阵,减少特征数量,常用的卷积核为 3 ∗ 3 3*3 3∗3或者 5 ∗ 5 5*5 5∗5。卷积操作是一个特殊的线性操作,需要进行去线性化,通常加入整流线性单元(ReLu)去线性化。所以一般大型神经网络,我们将包含卷积操作,非线性变化操作,池化操作等三个操作称为一段卷积层,大型神经网络是由多段卷积层堆积而成。
一个卷积核只能提取某一个特征,多个卷积核就能提取多个丰富的特征,tensorflow中称卷积核为过滤器,过滤器的深度为卷积核的数量。
卷积运算有三个重要特性
代码实现
tf.conv2d(input,filter,strids,padding,us_cudnn_on_gpu_,data_format,name)
参数
代码实例
import tensorflow as tf
import numpy as np
#使用numpy工具初始化一个名为M的数组,形状为2x3,数据类型为float32
#并使用numpy的reshape()函数调整输入的格式
#注意,M不会被TensorFlow识别为张量
M = np.array([[[2],[1],[2],[-1]],[[0],[-1],[3],[0]],
[[2],[1],[-1],[4]],[[-2],[0],[-3],[4]]],dtype="float32").reshape(1, 4, 4, 1)
#通过get_variable()函数创建过滤器的权重变量,上面介绍了卷积层
#这里声明的参数变量是一个四维矩阵,前面两个维度代表了过滤器的尺寸,
#第三个维度表示当前层的深度,第四个维度表示过滤器的深度。
filter_weight = tf.get_variable("weights",[2, 2, 1, 1],initializer = tf.constant_initializer([[-1, 4],[2, 1]]))
#通过get_variable()函数创建过滤器的偏置项,代码中[1]表示过滤器的深度。
#等于神经网络下一层的深度。
biases = tf.get_variable("biase", [1], initializer = tf.constant_initializer(1))
x = tf.placeholder('float32', [1,None, None,1])
#conv2d()函数实现了卷积层前向传播的算法。
#这个函数的第一个参数为当前层的输入矩阵,注意这个矩阵应该是一个四维矩阵,
#代表第一个维度的参数对应一个输入batch。如果在输入层,input[0, , , ]表示第一张图片,
#input[1, , , ]表示第二张图片,等等。函数第二个参数是卷积层的权重,第三个参数为
#不同维度上的步长。虽然第三个参数提供的是一个长度为4 的数组,
#但是第一个和最后一个数字要求一定是1,这是因为卷积层的步长只对矩阵的长和宽有效。
#最后一个参数是填充(padding的方法,有SAME或VALID 两种选择,
#其中SAME 表示添加全0填充,VALID表示不添加。
#函数原型conv2d(input,filter,strids,padding,us_cudnn_on_gpu_,data_format,name)
conv = tf.nn.conv2d(x, filter_weight, strides=[1, 1, 1, 1], padding="SAME")
#bias_add()函数具有给每一个节点加上偏置项点功能。这里不能直接使用加法的原因是
#矩阵上不同位置上的节点都需要加上同样的偏置项。因为过滤器深度为1,
#故偏置项只有一个数,结果为3x4的矩阵中每一个值都要加上这个偏置项。
#原型bias_add(value,bias,data_format,name)
add_bias = tf.nn.bias_add(conv, biases)
init_op=tf.global_variables_initializer()
with tf.Session() as sess:
init_op.run()
M_conv=sess.run(add_bias, feed_dict={x: M})
#输出结果并不是一个张量,而是数组
print("M after convolution: \n", M_conv)
池化层能在卷积操作之后对结果进行宽度和高度方向的进一步处理(统计聚合操作),同时能够减少参数,常用池化操作有最大池化(计算区域内的最大值作为该区域的值),平均值池化(计算区域内的平均值作为该区域的值),使用池化函数原因,某一特征会与相邻的特征非常相似,所以保留一个即可。池化核的大小就是池化操作的矩形区域的大小。
tf.nn.max_pool(value,ksize,strides,padding,data_format,name)
参数(和卷积参数类似)
代码实例
import tensorflow as tf
import numpy as np
M = np.array([[[-2],[2],[0],[3]],
[[1],[2],[-1],[2]],
[[0],[-1],[1],[0]]],dtype="float32").reshape(1, 3, 4, 1)
filter_weight = tf.get_variable("weights",[2, 2, 1, 1],initializer = tf.constant_initializer([[2, 0],[-1, 1]]))
biases = tf.get_variable('biases', [1], initializer = tf.constant_initializer(1))
x = tf.placeholder('float32', [1, None, None, 1])
conv = tf.nn.conv2d(x, filter_weight, strides=[1, 1, 1, 1], padding="SAME")
add_bias = tf.nn.bias_add(conv, biases)
#max_pool()函数实现了最大池化层的前向传播过程
#原型为max_pool(value,strides,padding,data_format,name)
#参数value为输入数据,strides为提供了步长信息,padding提供了是否使用全0填充。
pool = tf.nn.max_pool(add_bias, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")##
with tf.Session() as sess:
tf.global_variables_initializer().run()
M_conv = sess.run(add_bias, feed_dict={x: M})
M_pool = sess.run(pool, feed_dict={x: M})
print(" after average pooled: \n", M_pool)
print(" after conv: \n", M_conv)
'''输出内容
after average pooled:
[[[[7.]
[5.]]
[[1.]
[3.]]]]
'''
通过卷积层和池化层之后,提取到了图像最基本的特征,为了完成分类任务,还需要添加几个全连接层。都需要定义一个权重初始化函数来方便定义参数,其中 weight_variable为卷积核参数定义函数,bias_variable为偏置定义函数。
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)
可以在每层输出之后,定义一个概率,以一定的概率随机使每层的一部分神经元响应为0,来防止过拟合。测试时概率设置为1,训练时概率为0到1之间值。
dropout介绍
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
#h_fc1为具体某层的输出
可以得到输入样本所属类别的概率分布情况,并进行分类,一般用softmax层作最后输出。
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)
损失函数常用交叉熵(分类)和RMSE(回归)。
训练模型方法常用以下几种方法。
tf.train.GradientDescentOptimizer
tf.train.AdagradOptimizer
tf.train.MomentumOptimizer
tf.train.AdamOptimizer
tf.train.FtrlOptimizer
tf.train.RMSPropOptimizer
#以mnist分类为例
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv), reduction_indices=[1]))
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, tf.float32))
以mnist为例,每隔100步输出精度,输出测试精度
tf.global_variables_initializer().run()
#初始化
for i in range(20000):
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("step %d, training accuracy %g"%(i, train_accuracy))
train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
print("test accuracy %g"%accuracy.eval(feed_dict={
x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
常用一个函数来包含卷积核参数初始化,卷积操作,非线性Relu操作,池化操作,LRN局部响应归一化操作,方便后续调用。
函数模板:
with tf.variable_scope('conv1') as scope:
kernel = _variable_with_weight_decay('weights',
shape=[5, 5, 3, 64],
stddev=5e-2,
wd=0.0)
conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME')
biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.0))
pre_activation = tf.nn.bias_add(conv, biases)
conv1 = tf.nn.relu(pre_activation, name=scope.name)
_activation_summary(conv1)
pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
padding='SAME', name='pool1')
norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
name='norm1')
详见github主页