卷积层 (Convolutional Layer) 是卷积神经网络 (Convolutional Neural Networks, CNN) 的核心组件,本篇介绍卷积层的工作原理。
全连接层与卷积层
对于全连接层,每个神经元与上一层的所有神经元相连。例如,下图展示了一个 的单通道图片信号输入 (灰度图):
如果使用基于全连接层的模型,我们需要为每个输入信号分配一个权重,即建模一个神经元需要 个权重,并生成一个输出信号。 如果一层包含 个神经元,则需要 个权重,并生成 个输出信号。
在 CNN 的卷积层中,我们通过以下方式建模一个卷积层的神经元:如下图所示, 的红框代表该神经元的感受野 (Receptive Field)。将红框内对应位置的权重相乘并相加,得到一个数值。然后,让红框依次向右和向下滑动,并按相同方式计算,得到一个新的输出矩阵。这样,我们只需 个权值,即可生成一个输出信号。
卷积层的数学表示
详细来说,假设
那么对于红框所示的位置,输出信号为
然而, 的范围显然不足以覆盖整个图像,因此我们采用滑动窗口的方法。 使用相同的参数 ,将红框在图像中从左到右滑动,进行逐行扫描,每滑动到一个位置就计算一个值。 例如,当红框向右移动一个单位时,
因此,与一般神经元只能输出一个值不同,卷积层神经元可以输出一个 的矩阵。
TensorFlow 实践
下面,我们使用 TensorFlow 验证上图的计算结果。
import numpy as np
import tensorflow as tf
from tensorflow.keras import models, layers
W = np.array([[
[0, 0, -1],
[0, 1, 0],
[-2, 0, 2]
]], dtype=np.float32)
b = np.array([1], dtype=np.float32)
model = models.Sequential([
layers.Conv2D(
filters=1,
kernel_size=[3, 3],
kernel_initializer=tf.constant_initializer(W),
bias_initializer=tf.constant_initializer(b))
])
image = np.array([
[0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 1, 2, 1, 0],
[0, 0, 2, 2, 0, 1, 0],
[0, 1, 1, 0, 2, 1, 0],
[0, 0, 2, 1, 1, 0, 0],
[0, 2, 1, 1, 2, 0, 0],
[0, 0, 0, 0, 0, 0, 0]
], dtype=np.float32)
image = np.expand_dims(image, axis=0)
image = np.expand_dims(image, axis=-1)
output = model(image)
print(tf.squeeze(output))
程序运行结果为
tf.Tensor(
[[ 6. 5. -2. 1. 2.]
[ 3. 0. 3. 2. -2.]
[ 4. 2. -1. 0. 0.]
[ 2. 1. 2. -1. -3.]
[ 1. 1. 1. 3. 1.]], shape=(5, 5), dtype=float32)
多通道卷积
以上假设图片都只有一个通道 (例如灰度图片),但如果图像是彩色的 (例如有 RGB 三个通道) 该怎么办呢?此时,我们可以为每个通道准备一个 的权值矩阵,即一共有 个权值。对于每个通道,均使用自己的权值矩阵进行处理,输出时将多个通道所输出的值进行加和,再加上偏置项,共 28 个权值。下面通过代码验证一下。
model = models.Sequential([
layers.Conv2D(
input_shape=(7, 7, 3),
filters=1,
kernel_size=[3, 3])
])
model.summary()
程序运行结果为
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param#
=================================================================
conv2d (Conv2D) (None, 5, 5, 1) 28
=================================================================
Total params: 28
Trainable params: 28
Non-trainable params: 0
_________________________________________________________________
从上述输出可以看出,每次卷积后,结果图像的四周都会比原始图像少一圈。我们可以通过设置 padding 策略来解决这个问题。当 padding 参数设为 same 时,系统会用 0 补齐周围缺失的部分,从而使输出矩阵的大小与输入一致。
model = models.Sequential([
layers.Conv2D(
input_shape=(7, 7, 3),
padding='same',
filters=1,
kernel_size=[3, 3])
])
model.summary()
程序运行结果为
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param#
=================================================================
conv2d (Conv2D) (None, 7, 7, 1) 28
=================================================================
Total params: 28
Trainable params: 28
Non-trainable params: 0
_________________________________________________________________
此外,还可以通过 strides 参数设置步长 (默认为 1)。
model = models.Sequential([
layers.Conv2D(
input_shape=(7, 7, 3),
strides=2,
filters=1,
kernel_size=[3, 3])
])
model.summary()
程序运行结果为
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param#
=================================================================
conv2d (Conv2D) (None, 3, 3, 1) 28
=================================================================
Total params: 28
Trainable params: 28
Non-trainable params: 0
_________________________________________________________________