无论在nlp还是cv领域,cnn都是非常好用的工具,它擅长提取局部特征。
import tensorflow as tf
tf.enable_eager_execution()
picture = tf.reshape([0.0, 0, 0, 0, 0, 1, 3, 0, 0, 2, 2, 0, 0, 0, 0, 0], [1, 4, 4, 1])
w = tf.reshape([1, 2, 0.5, 1], [2, 2, 1, 1])
out = tf.nn.conv2d(picture, w, [1, 1, 1, 1], padding="VALID")
print(tf.squeeze(picture))
print(tf.squeeze(w))
print(tf.squeeze(out))
# 下面是输出结果 ============================
tf.Tensor(
[[0. 0. 0. 0.]
[0. 1. 3. 0.]
[0. 2. 2. 0.]
[0. 0. 0. 0.]], shape=(4, 4), dtype=float32)
tf.Tensor(
[[1. 2. ]
[0.5 1. ]], shape=(2, 2), dtype=float32)
tf.Tensor(
[[ 1. 3.5 1.5]
[ 4. 10. 4. ]
[ 4. 6. 2. ]], shape=(3, 3), dtype=float32)
在这个例子中,输入一张单通道(channel)图片picture,它的shape是4*4。
我希望用一个2*2的卷积核w做卷积,看看卷积结果是什么样的。
输出结果可以看到,原始图像、卷积核以及卷积结果。这个结果,与我们心算的结果是一致的。
picture = tf.reshape([0.0, 0, 0, 0, 0, 1, 3, 0, 0, 2, 2, 0, 0, 0, 0, 0], [1, 4, 4, 1])
w1 = tf.reshape([1, 2, 0.5, 1], [2, 2, 1, 1])
w2 = tf.reshape([1.0, 1, 1, 1], [2, 2, 1, 1])
w = tf.concat([w1, w2], axis=-1)
out = tf.nn.conv2d(picture, w, [1, 1, 1, 1], padding="VALID")
print(tf.squeeze(picture))
print(tf.squeeze(tf.transpose(w, [3, 0, 1, 2])))
out = tf.transpose(out, [0, 3, 1, 2])
print(tf.squeeze(out))
# 下面是输出结果 ============================
tf.Tensor(
[[0. 0. 0. 0.]
[0. 1. 3. 0.]
[0. 2. 2. 0.]
[0. 0. 0. 0.]], shape=(4, 4), dtype=float32)
tf.Tensor(
[[[1. 2. ]
[0.5 1. ]]
[[1. 1. ]
[1. 1. ]]], shape=(2, 2, 2), dtype=float32)
tf.Tensor(
[[[ 1. 3.5 1.5]
[ 4. 10. 4. ]
[ 4. 6. 2. ]]
[[ 1. 4. 3. ]
[ 3. 8. 5. ]
[ 2. 4. 2. ]]], shape=(2, 3, 3), dtype=float32)
稍加改变,输出通道数(channel)由1改为2,因此需要2个卷积核w1与w2。
合并两个卷积核w1与w2,得到w,其shape为[2*2*1*2],分别代表卷积核长2、宽2、输入通道数1、输出通道数2。
注意,这里是为了方便演示卷积计算。实际应用的时候,我们跳过分别定义w1与w2的过程,直接初始化w。
根据输出结果可以看出,2个卷积核在原始图像处理后,分别生成2张新的图像。其中,输出channel1,是与上一例子的结果完全一致的。
总结来说,有多少个输出通道channel,就会生成多少个新的图片。
channel1 = tf.reshape([0.0, 0, 0, 0, 0, 1, 3, 0, 0, 2, 2, 0, 0, 0, 0, 0], [1, 4, 4, 1])
channel2 = tf.reshape([0.0, 0, 0, 0, 0, 1, 3, 0, 0, 2, 2, 0, 0, 0, 0, 0], [1, 4, 4, 1])
picture = tf.concat([channel1, channel2], axis=-1)
w1 = tf.reshape([1, 2, 0.5, 1], [2, 2, 1, 1])
w2 = tf.reshape([1.0, 1, 1, 1], [2, 2, 1, 1])
w = tf.concat([w1, w2], axis=-2)
out = tf.nn.conv2d(picture, w, [1, 1, 1, 1], padding="VALID")
print(tf.squeeze(tf.transpose(picture, [3,0,1,2])))
print(tf.squeeze(tf.transpose(w, [3, 0, 1, 2])))
out = tf.transpose(out, [0, 3, 1, 2])
print(tf.squeeze(out))
# 下面是输出结果 ============================
tf.Tensor(
[[[0. 0. 0. 0.]
[0. 1. 3. 0.]
[0. 2. 2. 0.]
[0. 0. 0. 0.]]
[[0. 0. 0. 0.]
[0. 1. 3. 0.]
[0. 2. 2. 0.]
[0. 0. 0. 0.]]], shape=(2, 4, 4), dtype=float32)
tf.Tensor(
[[[1. 1. ]
[2. 1. ]]
[[0.5 1. ]
[1. 1. ]]], shape=(2, 2, 2), dtype=float32)
tf.Tensor(
[[ 2. 7.5 4.5]
[ 7. 18. 9. ]
[ 6. 10. 4. ]], shape=(3, 3), dtype=float32)
输入channel加一,既输入的图片具有2通道(channel),由channel1 与channel2 组成。实际中,RGB图是3通道。
卷积核需要由w1与w2组成,这是因为w1与w2分别在channel1与channel2上完成卷积。
通过w1 channel1卷积 与 w2 channel2卷积后,他们的结果再相加,得到最终结果。
从输出结果上看到本例的输出,正是上一例中输出的2通道输出的结果之和。
总结来说,输入图片有n个输入channel,一个卷积核需要有n个size*size的矩阵组合才能与之对应,在n个channel分别卷积后,再把结果相加,得到一个输出channel。
由以上可知卷积核维数[height, width, in_channels, output_channels]的意义了。
picture = tf.reshape([0.0, 0, 0, 0, 0, 1, 3, 0, 0, 2, 2, 0, 0, 0, 0, 0], [1, 4, 4, 1])
w = tf.reshape([1, 2, 0.5, 1], [2, 2, 1, 1])
point = tf.reshape([1.0], [1, 1, 1, 1])
out = tf.nn.separable_conv2d(picture, w, point, strides=[1, 1, 1, 1], padding="VALID")
print(tf.squeeze(picture))
print(tf.squeeze(w))
print(tf.squeeze(out))
# 下面是输出结果 ============================
tf.Tensor(
[[0. 0. 0. 0.]
[0. 1. 3. 0.]
[0. 2. 2. 0.]
[0. 0. 0. 0.]], shape=(4, 4), dtype=float32)
tf.Tensor(
[[1. 2. ]
[0.5 1. ]], shape=(2, 2), dtype=float32)
tf.Tensor(
[[ 1. 3.5 1.5]
[ 4. 10. 4. ]
[ 4. 6. 2. ]], shape=(3, 3), dtype=float32)
参考 https://blog.csdn.net/tintinetmilou/article/details/81607721
一张[4 * 4 * 3]的图片,用十个卷积核处理,参数量达到 2 * 2 * 3 * 10 = 120
改用depthwise separable convolution,参数量只需 2 * 2 * 3 + 3 * 10 = 42
可见,depthwise separable convolution可以大幅减少参数数量。