CNN中的channels 该如何理解?
首先,是 tensorflow 中给出的,对于输入样本中 channels 的含义。一般的RGB图片,channels 数量是 3 (红、绿、蓝);而monochrome图片,channels 数量是 1 。
如下图,假设现有一个为 6×6×3 的图片样本,使用 3×3×3 的卷积核(filter)进行卷积操作。此时输入图片的 channels 为 3 ,而卷积核中的 in_channels 与 需要进行卷积操作的数据的 channels 一致(这里就是图片样本,为3)。
接下来,进行卷积操作,卷积核中的27个数字与分别与样本对应相乘后,再进行求和,得到第一个结果。依次进行,最终得到 4×4 的结果。
上面步骤完成后,由于只有一个卷积核,所以最终得到的结果为 4×4×1 , out_channels 为 1 。
在实际应用中,都会使用多个卷积核。这里如果再加一个卷积核,就会得到 4×4×2 的结果。
tf.nn.conv2d
tf.nn.conv2d
是TensorFlow里面实现卷积的函数,参考文档对它的介绍并不是很详细,实际上这是搭建卷积神经网络比较核心的一个方法,非常重要
tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)
除去name参数用以指定该操作的name,与方法有关的一共五个参数:
input
:指需要做卷积的输入图像,它要求是一个Tensor,具有[batch, in_height, in_width, in_channels]这样的shape,具体含义是[训练时一个batch的图片数量, 图片高度, 图片宽度, 图像通道数],注意这是一个4维的Tensor,要求类型为float32和float64其中之一
filter
:相当于CNN中的卷积核,它要求是一个Tensor,具有[filter_height, filter_width, in_channels, out_channels]这样的shape,具体含义是[卷积核的高度,卷积核的宽度,图像通道数,卷积核个数],要求类型与参数input相同,有一个地方需要注意,第三维in_channels,就是参数input的第四维
strides
:卷积时在图像每一维的步长,这是一个一维的向量,长度4
padding
:string类型的量,只能是”SAME”,”VALID”其中之一,这个值决定了不同的卷积方式(后面会介绍)
use_cudnn_on_gpu
: bool类型,是否使用cudnn加速,默认为true
结果返回一个Tensor,这个输出,就是我们常说的feature map
那么TensorFlow的卷积具体是怎样实现的呢,用一些例子去解释它:
1.考虑一种最简单的情况,现在有一张3×3单通道的图像(对应的shape:[1,3,3,1]),用一个1×1的卷积核(对应的shape:[1,1,1,1])去做卷积,最后会得到一张3×3的feature map
2.增加图片的通道数,使用一张3×3五通道的图像(对应的shape:[1,3,3,5]),用一个1×1的卷积核(对应的shape:[1,1,1,1])去做卷积,仍然是一张3×3的feature map,这就相当于每一个像素点,卷积核都与该像素点的每一个通道做点积
input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([1,1,5,1]))
op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')
3.把卷积核扩大,现在用3×3的卷积核做卷积,最后的输出是一个值,相当于情况2的feature map所有像素点的值求和
input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')
4.使用更大的图片将情况2的图片扩大到5×5,仍然是3×3的卷积核,令步长为1,输出3×3的feature map
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')
注意我们可以把这种情况看成情况2和情况3的中间状态,卷积核以步长1滑动遍历全图,以下x表示的位置,表示卷积核停留的位置,每停留一个,输出feature map的一个像素
.....
.xxx.
.xxx.
.xxx.
.....
5.上面我们一直令参数padding的值为‘VALID’,当其为‘SAME’时,表示卷积核可以停留在图像边缘,如下,输出5×5的feature map
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
6.如果卷积核有多个
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')
此时输出7张5×5的feature map
7.步长不为1的情况,文档里说了对于图片,因为只有两维,通常strides取[1,stride,stride,1]
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')
此时,输出7张3×3的feature map
x.x.x
.....
x.x.x
.....
x.x.x
8.如果batch值不为1,同时输入10张图
input = tf.Variable(tf.random_normal([10,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')
每张图,都有7张3×3的feature map,输出的shape就是[10,3,3,7]
最后,把程序总结一下:
import tensorflow as tf
#case 2
input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([1,1,5,1]))
op2 = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')
#case 3
input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op3 = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')
#case 4
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op4 = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')
#case 5
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op5 = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')
#case 6
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op6 = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')
#case 7
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op7 = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')
#case 8
input = tf.Variable(tf.random_normal([10,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op8 = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')
init = tf.initialize_all_variables()
with tf.Session() as sess:
sess.run(init)
print("case 2")
print(sess.run(op2))
print("case 3")
print(sess.run(op3))
print("case 4")
print(sess.run(op4))
print("case 5")
print(sess.run(op5))
print("case 6")
print(sess.run(op6))
print("case 7")
print(sess.run(op7))
print("case 8")
print(sess.run(op8))
补充
1.2 tf.nn.conv2d函数介绍
官网说明文档:https://www.tensorflow.org/api_docs/python/tf/nn/conv2d
函数原型:
conv2d(input,
filter,
strides,
padding,
use_cudnn_on_gpu=None,
data_format=None,
name=None
)
函数参数说明:
input
:是需要做卷积的输入图像,要求是一个Tensor,具有[batch, in_height, in_width, in_channels]这样的shape,具体含义是[训练时一个batch的图片数量, 图片高度, 图片宽度, 图像通道数],注意这是一个4维的Tensor,要求类型为float32和float64其中之一。
filter
:相当于CNN中的卷积滤波器,要求是一个Tensor,具有[filter_height, filter_width, in_channels, out_channels]这样的shape,具体含义是[卷积核的高度,卷积核的宽度,图像通道数,卷积核个数],要求类型与参数input相同。注意,第三维in_channels,就是参数input的第四维。
strides
:卷积时在图像每一维滑动的步长,这是一个一维的向量,长度4,一般是[1,x,x,1]的形式。
padding
:string类型的量,取“SAME”或“VALID”,其中SAME表示需要在输入input高和宽维度的周围添加像素点,卷积率抱起会停留在原图边缘,保证得到的新图与原图的shape相同;VALID则表示不需要填充。
use_cudnn_on_gpu
:可选,bool类型,是否使用cudnn加速,默认为true。
data_format
:可选,string类型,取“NHWC”或“NCHW”,表示输入数据input的存储形式,“NHWC” the d表示[batch, height, width, channels],NCHW”表示[batch, channels, height, width]。
name
:可选,表示操作的名字。
函数返回值:一个tensor,与input类型一致,维度顺序取决于data_format。
1.3 代码解释
1、操作一:
input:使用一张3×3、5通道的图像(对应的shape:[1,3,3,5])。filter:用一个1×1的卷积核(对应的shape:[1,1,5,1])。
padding:VALID。
strides:[1,1,1,1]。
返回tensor:是一张3×3、单通道的feature map(对应的shape:[1,3,3,1])。
这就相当于每一个像素点,卷积核都与该像素点的每一个通道做点积。
input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([1,1,5,1]))
op1 = tf.nn.conv2d(input, filter, strides=[1,1,1,1], padding='VALID')
2、操作二:
input:使用一张3×3、5通道的图像(对应的shape:[1,3,3,5])。filter:用一个3×3的卷积核(对应的shape:[3,3,5,1])。
padding:VALID。
strides:[1,1,1,1]。
返回tensor:是一张1×1、单通道的feature map(对应的shape:[1,1,1,1])。
input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op2 = tf.nn.conv2d(input, filter, strides=[1,1,1,1], padding='VALID')
3、操作三:
input:使用一张5×5、5通道的图像(对应的shape:[1,5,5,5])。filter:用一个3×3的卷积核(对应的shape:[3,3,5,1])。
padding:VALID。
strides:[1,1,1,1]。
返回tensor:是一张3×3、单通道的feature map(对应的shape:[1,3,3,1])。
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op3 = tf.nn.conv2d(input, filter, strides=[1,1,1,1], padding='VALID')
4、操作四:
input:使用一张5×5、5通道的图像(对应的shape:[1,5,5,5])。filter:用一个3×3的卷积核(对应的shape:[3,3,5,1])。
padding:SAME。
strides:[1,1,1,1]。
返回tensor:是一张5×5、单通道的feature map(对应的shape:[1,5,5,1])。
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op4 = tf.nn.conv2d(input, filter, strides=[1,1,1,1], padding='SAME')
5、操作五:
input:使用一张5×5、5通道的图像(对应的shape:[1,5,5,5])。filter:用7个3×3的卷积核(对应的shape:[3,3,5,7])。
padding:SAME。
strides:[1,1,1,1]。
返回tensor:是一张5×5、7通道的feature map(对应的shape:[1,5,5,7])。
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op5 = tf.nn.conv2d(input, filter, strides=[1,1,1,1], padding='SAME')
6、操作六:
input:使用一张5×5、5通道的图像(对应的shape:[1,5,5,5])。filter:用7个3×3的卷积核(对应的shape:[3,3,5,7])。
padding:SAME。
strides:[1,2,2,1]。
返回tensor:是一张3×3、7通道的feature map(对应的shape:[1,3,3,7])。
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op6 = tf.nn.conv2d(input, filter, strides=[1,2,2,1], padding='SAME')
7、操作七:
input:使用10张5×5、5通道的图像(对应的shape:[10,5,5,5])。
filter:用7个3×3的卷积核(对应的shape:[3,3,5,7])。
padding:SAME。
strides:[1,2,2,1]。
返回tensor:是10张3×3、7通道的feature map(对应的shape:[10,3,3,7])。
input = tf.Variable(tf.random_normal([10,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op7 = tf.nn.conv2d(input, filter, strides=[1,2,2,1], padding='SAME')
全部代码:
import tensorflow as tf
input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([1,1,5,1]))
op1 = tf.nn.conv2d(input, filter, strides=[1,1,1,1], padding='VALID')
input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op2 = tf.nn.conv2d(input, filter, strides=[1,1,1,1], padding='VALID')
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op3 = tf.nn.conv2d(input, filter, strides=[1,1,1,1], padding='VALID')
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op4 = tf.nn.conv2d(input, filter, strides=[1,1,1,1], padding='SAME')
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op5 = tf.nn.conv2d(input, filter, strides=[1,1,1,1], padding='SAME')
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op6 = tf.nn.conv2d(input, filter, strides=[1,2,2,1], padding='SAME')
input = tf.Variable(tf.random_normal([10,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op7 = tf.nn.conv2d(input, filter, strides=[1,2,2,1], padding='SAME')
init = tf.initialize_all_variables()
with tf.Session() as sess:
sess.run(init)
print "images:"
print sess.run(input)
print "filter:"
print sess.run(filter)
print "after conv1:"
print sess.run(op1)
print "after conv2:"
print sess.run(op2)
print "after conv3:"
print sess.run(op3)
print "after conv4:"
print sess.run(op4)
print "after conv5:"
print sess.run(op5)
print "after conv6:"
print sess.run(op6)
print "after conv7:"
print sess.run(op7)