卷积神经网络CNN
CNN是一种至少包含一层卷积层的神经网络模型,卷积层的目的是将卷积核应用到图像张量的所有点上,并通过将卷积核在输入张量上滑动生成经过滤波处理的张量.简单的CNN架构通常会包含卷积层,池化层,非线性变换层,全连接层.通过这些层网络会被填充大量的信息,因此模型便可以进行复杂的模式匹配.
本文主要是通过TF1.5这个工具,展示CNN中各个层对图像信息的加工过程.
主要内容:
- 张量在TF中的表示TensorFlow基础知识
- 卷积层
- 激活函数
- 池化层
import tensorflow as tf
import matplotlib.pyplot as plt
# 图像经过解码后是一张量的形式表示
# 定义一个包含两个张量[图像]的常量
image_batch = tf.constant([
[
[[0, 255, 0], [0, 255, 0], [0, 255, 0]],
[[0, 255, 0], [0, 255, 0], [0, 255, 0]]
],
[
[[0, 0, 255], [0, 0, 255], [0, 0, 255]],
[[0, 0, 255], [0, 0, 255], [0, 0, 255]]
]
])
image_batch.get_shape()
重绘图像
卷积层
卷积是CNN的重要组成,通常也是网络的第一层,下面主要介绍卷积层对图像的一些操作
# 在TensorFlow中卷积运算通过tf.nn.conv2d()完成.同时TF还提供其特定的卷积运算.
# 定义一个张量,简单实现卷积运算
tensor_1 = tf.constant([
[
[[0.0],[1.0]],
[[2.0],[3.0]]
],
])
# 卷积核
kernel_1 = tf.constant([
[
[[1.0, 2.0]]
]
])
# 张量的shape
tensor_1.get_shape()
# TensorShape[张量数量,张量高度,张量宽度,通道数]
TensorShape([Dimension(1), Dimension(2), Dimension(2), Dimension(1)])
# kernel_1
kernel_1.get_shape()
TensorShape([Dimension(1), Dimension(1), Dimension(1), Dimension(2)])
# 卷积后,得到一个新张量(特征图)
conv2d_1 = tf.nn.conv2d(tensor_1, kernel_1 ,strides=[1, 1, 1, 1],
padding='SAME')
sess.run(conv2d_1)
array([
[
[[0.0, 0.0], [1.0, 2.0]],
[[2.0, 4.0], [3.0, 6.0]]
]
])
conv2d_1.get_shape()
TensorShape([Dimension(1), Dimension(2), Dimension(2), Dimension(2)])
sess.run(tensor_1)[0][1][1],sess.run(conv2d_1)[0][1][1]
(array([3.], dtype=float32), array([3., 6.], dtype=float32))
# tensor_2 [1x5x5x1]
tensor_2 = tf.constant([
[
[[2.0], [1.0], [0.0], [2.0], [3.0]],
[[9.0], [5.0], [4.0], [2.0], [0.0]],
[[2.0], [3.0], [4.0], [5.0], [6.0]],
[[1.0], [2.0], [3.0], [1.0], [0.0]],
[[0.0], [4.0], [4.0], [2.0], [8.0]],
],
])
# 卷积核[3x3x1x1]
kernel_2 = tf.constant([
[
[[-1.0]], [[0.0]], [[1.0]]
],
[
[[-1.0]], [[0.0]], [[1.0]]
],
[
[[-1.0]], [[0.0]], [[1.0]]
]
])
tensor_2.get_shape()
TensorShape([Dimension(1), Dimension(5), Dimension(5), Dimension(1)])
kernel_2.get_shape()
TensorShape([Dimension(3), Dimension(3), Dimension(1), Dimension(1)])
# 卷积,strides,padding(下面会介绍)
# padding = 'VALID',输入会比输入尺寸小
conv2d_2 = tf.nn.conv2d(tensor_2, kernel_2, strides =[1,1,1,1],
padding='VALID')
sess.run(conv2d_2)
array([
[
[[-5.],[ 0.],[ 1.]],
[[-1.],[-2.],[-5.]],
[[ 8.],[-1.],[ 3.]]
],
], dtype=float32)
在卷积过程中会遇到这种情况:卷积核未到达图像的边界时,下一步的移动会越过图像的边界.
针对这种情况,tensorflow提供的措施是对超出图像边界的部分进行填充,即边界填充.
SAME:当卷积核超出图像边界时,会进行0填充,卷积的输出与输入相同
VALID:考虑卷积核的尺寸,尽量不越过图像边界.卷积输出小于输入
# 边缘检测卷积核
kernel_3 = tf.constant([
[
[[-1., 0., 0.], [0., -1., 0.], [0., 0., -1.]],
[[-1., 0., 0.], [0., -1., 0.], [0., 0., -1.]],
[[-1., 0., 0.], [0., -1., 0.], [0., 0., -1.]]
],
[
[[-1., 0., 0.], [0., -1., 0.], [0., 0., -1.]],
[[ 8., 0., 0.], [0., 8., 0.], [0., 0., 8.]],
[[-1., 0., 0.], [0., -1., 0.], [0., 0., -1.]]
],
[
[[-1., 0., 0.], [0., -1., 0.], [0., 0., -1.]],
[[-1., 0., 0.], [0., -1., 0.], [0., 0., -1.]],
[[-1., 0., 0.], [0., -1., 0.], [0., 0., -1.]]
]
])
# 卷积前图像
plt.imshow(sess.run(image))
plt.show()
sess.run(image).shape
(393, 700, 3)
# uint8转化为浮点型
image = tf.to_float(image)
# 转化为一个四维张量
conv2d_3 = tf.nn.conv2d(tf.reshape(image,[1,393,700,3]), kernel_3,
[1,1,1,1], padding='SAME')
conv2d_3.get_shape()
TensorShape([Dimension(1), Dimension(393), Dimension(700), Dimension(3)])
# tf.minimum()和tf.nn.relu()是将卷积后的像素值保持在RGB颜色值的合法范围内[0,255]
conved_image = tf.minimum(tf.nn.relu(conv2d_3), 255)
plt.imshow(sess.run(conved_image).reshape(393,700,3))
plt.show()
# 锐化卷积核
kernel_4 = tf.constant([
[
[[ 0., 0., 0.], [0., 0., 0.], [0., 0., 0.]],
[[-1., 0., 0.], [0., -1., 0.], [0., 0., -1.]],
[[ 0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]
],
[
[[-1., 0., 0.], [0., -1., 0.], [0., 0., -1.]],
[[ 5., 0., 0.], [0., 5., 0.], [0., 0., 5.]],
[[-1., 0., 0.], [0., -1., 0.], [0., 0., -1.]]
],
[
[[ 0., 0., 0.], [0., 0., 0.], [0., 0., 0.]],
[[-1., 0., 0.], [0., -1., 0.], [0., 0., -1.]],
[[ 0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]
]
])
# 卷积
conv2d_4 = tf.nn.conv2d(tf.reshape(image,[1,393,700,3]), kernel_4,
[1,1,1,1], padding='SAME')
sharpen_image = tf.minimum(tf.nn.relu(conv2d_4), 255)
# 通过索引访问第一个张量,就不用对图像张量进行resize操作了
plt.imshow(sess.run(sharpen_image)[0])
plt.show()
features = tf.to_float(tf.range(-20,20))
# Relu
plt.plot(sess.run(features), sess.run(tf.nn.relu(features)))
plt.grid(True)
plt.title('Relu')
plt.show()
# sigmoid
plt.plot(sess.run(features), sess.run(tf.sigmoid(features)))
plt.grid(True)
plt.title("Sigmoid")
plt.show()
# tanh
plt.plot(sess.run(features), sess.run(tf.tanh(features)))
plt.title("Tanh")
plt.grid(True)
plt.show()
# drop
plt.scatter(sess.run(features), sess.run(tf.nn.dropout(features, keep_prob=0.5)))
plt.title("Dropout")
plt.grid(True)
plt.show()
# 输入张量
input_tensor = tf.constant([
[
[[1.0], [1.0], [2.0], [4.0]],
[[5.0], [6.0], [7.0], [8.0]],
[[3.0], [2.0], [1.0], [0.0]],
[[1.0], [2.0], [3.0], [4.0]]
]
])
kernel_pool = [1, 2, 2, 1]
strides = [1, 2, 2, 1] # 跟卷积作用相同
max_pool = tf.nn.max_pool(input_tensor, kernel_pool, strides, 'VALID')
sess.run(max_pool)
array([
[
[[6.],[8.]],
[[3.],[4.]]
]
], dtype=float32)
# 平均池化
avg_pool = tf.nn.avg_pool(input_tensor, kernel_pool, strides, 'VALID')
sess.run(avg_pool)
array([
[
[[3.25],[5.25]],
[[2.],[2.]]
]
], dtype=float32)
# 下面分别对这副图像进行最大和平均池化
image_file2 = tf.gfile.FastGFile('123.jpg','rb').read()
image_2 = tf.image.decode_jpeg(image_file2)
plt.imshow(sess.run(image_2))
plt.show()
max_pool_image = tf.nn.max_pool(tf.reshape(image_2,[1,873,1070,3]),
[1,6,6,1],[1,5,5,1],'VALID')
plt.imshow(sess.run(max_pool_image)[0])
plt.show()
max_pool_image
<tf.Tensor 'MaxPool_8:0' shape=(1, 174, 214, 3) dtype=uint8>
# 平均池化层输入张量为浮点型数据
avg_pool_image = tf.nn.avg_pool(tf.to_float(tf.reshape(image_2,[1,873,1070,3])),
[1,1,2,1],[1,1,1,1],'VALID')
plt.imshow(sess.run(avg_pool_image)[0])
plt.show()
# 平均池化层输入张量为浮点型数据
avg_pool_image_1 = tf.nn.avg_pool(tf.to_float(tf.reshape(image_2,[1,873,1070,3])),
[1,2,2,1],[1,2,2,1],'VALID')
plt.imshow(sess.run(avg_pool_image_1)[0])
plt.show()