★tensorflow中“卷积”的深度解析
两点结论:
(1)单个独立卷积核的通道数=输入图像的通道数(输入图像决定);
(2)独立卷积核的个数=输出图像的通道数(用户决定)。
注意:
在描述卷积核时,“卷积核通道数”这个概念必须搞清楚,是指“单个独立卷积核的通道数”,还是指独立卷积核的个数(输出通道数)。
例:
假如输入图像的规模为32×32×128,即输入通道数为128;
那么根据前面(1)可知单个卷积核的通道数也是128,即卷积核尺寸应该为:n×n×128(n通常为3、5、7等)。
假如用256个这样的卷积核对输入进行卷积,则最后得到的输出图像尺寸为(根据前面(2)):(32-n+1)×(32-n+1)×256,即输出图像有256个通道(一个卷积核得到一个输出结果,也就是一个通道,相当于把原来只有128层的图像,扩展到了256层)。
可见,本次所有卷积核尺寸(或者说卷积核的总参数个数)是:n×n×128×256
需要注意的是,很多时候在描述卷积核时,通常会忽略单个卷积核的通道数,而保留了卷积核个数,如上面的总卷积核简写为:n×n×256,省略了128,这么写是有道理的,因为这4个参数中128这个参数是由输入图像的通道数决定的,而其他三个参数则是由用户决定的。
因此在解读卷积核尺寸时,必须搞清楚,第三个参数是输入通道数还是卷积核个数。判断标准就是前面的(1)和(2)两点结论。
1. 关于通道(channels)
图片通道channel:通常jpg等彩色图片由R、G、B三个图层构成,每个图层实际上就是一个二维矩阵,也就是所谓的一个通道,因此一个彩色图片通常有3个通道,而一个黑白图片就只有一个通道。
tensorfllow中的“输入通道in_channels”:输入图像的通道数。
tensorfllow中的“输出通道out_channels”:输出图像的通道数。
最初输入的图片样本的 channels ,取决于图片类型,比如RGB;
卷积操作完成后输出的 out_channels ,取决于卷积核的数量。此时的 out_channels 也会作为下一次卷积时的卷积核的 in_channels;
卷积核中的 in_channels ,就是上一次卷积的 out_channels ,如果是第一次做卷积,就是输入本图片的 channels 。
2. 函数解析
tf.nn.conv2d()是TensorFlow里面实现卷积的函数,其调用方式如下:
result = tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)
-
input:待卷积的一张或一组图片,是一个4维Tensor,规定其shape=[batch, in_height, in_width, in_channels],(读入图片后通常需要进行一次数组转化,满足tensorflow的格式要求后才能进行卷积计算)
batch=图片张数
in_height=单张图片高度
in_width=单张图片宽度
in_channels=单张图片通道数(黑白图片为1,彩色图片为3,如有必要也可以任意指定)
例:对一张32×32的黑白图片进行卷积,必须将图片转化为input=[1,32,32,1]格式;对2张32×32的彩色图片进行卷积,必须将图片转化为input=[2,32,32,3]。 -
filter:卷积核(组),是一个4维Tensor,规定其shape=[filter_height, filter_width, in_channels, out_channels]
filter_height=单个卷积核高度
filter_width=单个卷积核宽度
★★in_channels(实际上就是input的in_channels,两者必须相等,否则报错),用这个参数来确定“单个卷积核的通道数”
★★out_channels(输出通道数),实际上就是“卷积核个数”,该参数由用户决定
记住:对于某个卷积层,无论输入图像有多少个通道,输出图像的通道数是由卷积核个数决定的! -
strides:卷积移动步长,是一个有四个元素的1维Tensor,strides=[b,h,w,c],
b:决定对哪些图片进行卷积,默认为1,即对所有图片卷积;
h:卷积核在每张图片高度方向的移动步长;
w:卷积核在每张图片宽度方向的移动步长;
c:决定对每张图片的哪些通道进行卷积,默认为1,即对所有通道进行卷积;
通常都是strides=[1,h,w,1],对所有图片的所有通道卷积 - padding:决定图像的边缘扩展,是string类型的量,只能是"SAME","VALID"其中之一(详见https://www.imooc.com/article/details/id/29525)。
- use_cudnn_on_gpu:bool类型,是否使用cudnn加速,默认为true。
-
返回结果result:返回一个Tensor,就是我们常说的feature map,其shape=[batch, height, width, out_channels]
batch=图片张数=input参数的batch
height:卷积后每个输出的高度,由图像高度、卷积核高度、strides、padding共同决定
width:卷积后每个输出的宽度,由图像宽度、卷积核宽度、strides、padding共同决定
out_channels=filter的out_channels。
3. tensorflow图像卷积过程
下图展示了对一张3通道jpeg图片进行两次连续卷积操作的过程:
cgx-tensorflow二维卷积过程解析.jpg
- 上图中,读入图片img的in_channels通常是一个固定值1(黑白图片)或者3(彩色图片)。直接读入的图片数据还不能直接喂入tensorflow进行卷积计算,必须将其reshape成tensorflow要求的格式input=[batch, in_height, in_width, in_channels]。需要注意的是,input的in_channels可以与img的in_channels设置成不相等,用户可自定义,他只用于指定将几个二维数组(一个二维数组即是一个通道)作为一个整体进行卷积。但实际中我们通常将input的in_channels设为与img的相同,因为我们通常都对一整张图片进行卷积操作,而不是只对一张图片的一个或几个通道进行卷积计算。
- 卷积核filter的in_channels必须和input的in_channels相同,否则出错,tensorflow为一张图片(或一组二维数组)中的每个通道(图层)都单独设置一个二维卷积核(tensorflow规定一张图的几个通道不能共用一个二维卷积核,必须是一个通道对应一个二维卷积核),因此对于3通道图片,必须要有三个二维卷积核(如上图),但这三个二维卷积核可以相同。通常我们将与input的in_channels对应个数的“二维卷积核组”称为一个独立的“卷积核”或“三维卷积核”(如上图中一个紫色三维矩阵就是一个独立的卷积核)。
- filter(多维卷积核)中的out_channels参数实际上就是“独立多维卷积核的个数”,其中具体是多少维,由input的in_channels决定,而“个数”纯粹是由用户决定,与input或输入图片没有任何关系。如上图第一次卷积所示,卷积核个数为2(用户决定),每个卷积核由3个(input决定)二维卷积核(二维数组)组成。“卷积核个数”决定了用多少个独立的卷积核对一张图片进行卷积,每一个核最终都会产生一个独立的卷积结果(二维数组),也就是所谓的特征图(feature map),卷积核越多,特征图自然也越多,上图中就只有2个特征图,因为我们只用了2个三维卷积核。用不同卷积核对一张图片进行卷积,实际上就相当于用不同的方式来提取图片中不同的特征,一般卷积核越多,卷积核的差异性越大,得到的特征结果也越多。
- 从上图中可以看出,对一个多通道的图片,tensorflow会为每一个通道(图层)都分配一个二维卷积核,最后把所有通道的卷积结果“对应求和”作为该图片的一个完整卷积结果,这个结果就是这个图片的一个特征图(feature map)。
- 从上图中还能看出 tensorflow中无论一张图片有多少个通道,对一个完整的卷积核而言,最终就只能得到一个二维的feature map,其原因在于,tensorflow最终会把所有通道的卷积结果在对应位置进行求和,从而将多维矩阵压缩成一个二维矩阵。
- 上图中第一次卷积的结果是,将一张3通道图片通过2个3维卷积核变成了一个2通道的最终卷积结果。
- 第一次卷积结束后,会把一张图的所有特征图作为一个整体进行下次一卷积计算,而不会将这些特征图分开。
- 前一次卷积的结果是下一次卷积的输入,因此下一次卷积的输入通道数in_channels是由上一次卷积的“卷积核个数”决定的(用户决定)。
4. 一般卷积网络解读示例(根据前面知识)
卷积网络结构解读.png 卷积网络结构解读2.png5. Conv2D()如何定义一个卷积层?
图片1.jpg6. tensorflow图像卷积实例
import tensorflow as tf
import matplotlib.pyplot as plt
sess = tf.InteractiveSession()
# 二维卷积核大小(多个二维卷积核组成一个三维卷积核)
kernel_size = 7
# 卷积步长
stride_size = 3
# 输出通道数(三维卷积核个数,或输出特征图个数)
out_channels = 4
# 读入图像文件
image_value = tf.read_file('./cgx.jpg')
# 图像解码
img = tf.image.decode_jpeg(image_value, channels=3)
# print(img.eval().shape) #原始图片维度
img_hight = img.eval().shape[0] #得到图像的高度维
img_width = img.eval().shape[1] #得到图像的宽度维
img_inchannels = img.eval().shape[2] #对于jpg图片为3
# 显示图片
plt.figure(1)
plt.imshow(img.eval()) #完整图像
# plt.imshow(img.eval()[:,:,0]) #G层
# plt.imshow(img.eval()[:,:,1]) #B层
# plt.imshow(img.eval()[:,:,2]) #R层
plt.show()
# # 格式转换,转化成浮点数便于计算
img = tf.to_float(img, name='ToFloat')
# 将图片转化成适合tensorflow卷积的格式
# 第一个参数1是输入图片张数,最后一个3个RGB3个维度
batch_shape = (1,img_width,img_hight,img_inchannels)
input_img = tf.reshape(img,batch_shape)
# 生成卷积核
filter = tf.Variable(tf.random_normal([kernel_size,kernel_size,img_inchannels,out_channels]))
# 卷积步长定义
strides_shape =[ 1,stride_size,stride_size,1]
# 调用conv2d()函数计算二维卷积
my_conv2d = tf.nn.conv2d(input_img, filter, strides_shape, padding='VALID')
##############################################################################################
#执行会话
# 调用sess初始化变量
sess.run(tf.global_variables_initializer())
#调用sess.run()执行卷积操作
feature_maps = sess.run(my_conv2d) #numpy.ndarray数组,shape=[原始图片张数,卷积后的高,卷积后的宽,特征图张数]
print('原始图像维度:[原始图片张数,图片高,图片宽,图片通道数]={}'.format(input_img.shape))
print('卷积图像维度:[原始图片张数,特征图高,特征图宽,特征图张数]={}'.format(feature_maps.shape))
# 展示每所有特征图
for i in range(feature_maps.shape[3]):
print("第{}个特征图:".format(i+1))
plt.imshow(feature_maps[0,:,:,i],cmap='gray') #第一个特征图
plt.show()
# 关闭会话
sess.close()
输出结果:
卷积结果.png
5. 参考链接
https://www.cnblogs.com/welhzh/p/6607581.html
https://www.jianshu.com/p/abb7d9b82e2a
https://www.imooc.com/article/details/id/29525
https://cloud.tencent.com/developer/article/1341522
https://www.cnblogs.com/billux/p/9072173.html