卷积神经网络 (Convolutional Neural Network, CNN): 是一种前馈神经网络,它的人工神经元可以响应一部分覆盖范围内的周围单元,对于大型图像处理有出色表现。

1 原理

要学习卷积神经网络,当然首先得学习朴素的神经网络:https://io.zouht.com/109.html

接下来当然得了解卷积:https://io.zouht.com/175.html

我们知道,朴素的神经网络是通过误差逆传播方法进行参数学习,整个过程无需人工干预参数,是系统自学习的。

我们也知道,卷积是一种特征提取的方式,通过构造特定卷积核,这个卷积核就能完成特定的事情例如对数据特征进行提取。

那么能不能将上述二者结合,形成一个能够自学习构造卷积核,让这个卷积核能够捕获数据中某种特征,最后通过提取出的特征实现特定功能呢?这便是卷积神经网络!

通常来说,卷积神经网络只是包含了几层”卷积层“,卷积层提取特征后,会输入“全连接层”(即感知机层),让全连接层进一步拟合。

2 前向传播

对于一个(二维)卷积层,它有两种参数:

  • $n\times n$ 的卷积核参数:$\boldsymbol{W}_{n\times n}$
  • 偏置:$b$

它的输入和输入都是矩阵:

  • 输入 $p\times q$ 大小的矩阵:$\boldsymbol{G}_{p\times q}$
  • 输出 $(p-2)\times(q-2)$ 大小的矩阵:$\boldsymbol{H}_{(p-2)\times (q-2)}$

前向传播过程其实就是进行卷积:

$$ \boldsymbol{H}_{(p-2)\times (q-2)}=\boldsymbol{G}_{p\times q}*\boldsymbol{W}_{n\times n}+b $$

注 1:卷积运算常用 $*$​ 来表示,请注意它不是乘法。
注 2:该偏置是实数,加偏置的意义是矩阵每个位置分别加偏置。

不过需要注意的是,在朴素的神经网络中我们一般一层的输入输出都是向量,但是(二维)卷积输入输出都是矩阵。

因此,在卷积层和全连接层之间,需要插入一个展平层,前向传播时负责把矩阵拉平成向量,反向传播时负责把梯度从向量压回矩阵。

3 反向传播

神经网络的核心是反向传播,而反向传播的核心就是求导。

3.1 对 $\boldsymbol{W}$​ 求偏导

我们要求的是:

$$ \frac{\partial E}{\partial \boldsymbol{W}} $$

首先考虑卷积核的参数之一:

$$ \frac{\partial E}{\partial w_{ij}}=\sum_{p=0}^{n-1}\sum_{q=0}^{n-1}\frac{\partial E}{\partial h_{pq}}\frac{\partial h_{pq}}{\partial w_{ij}} $$

我们把卷积展开来写:

$$ h_{ij}=\sum_{\alpha=0}^{n-1}\sum_{\beta=0}^{n-1}g_{i+\alpha-\frac{n-1}{2},j+\beta-\frac{n-1}{2}}\cdot w_{\alpha\beta}+b $$

那么:

$$ \frac{\partial h_{pq}}{\partial w_{ij}}=\frac{\partial \sum_{\alpha=0}^{n-1}\sum_{\beta=0}^{n-1}g_{p+\alpha-\frac{n-1}{2},q+\beta-\frac{n-1}{2}}\cdot w_{\alpha\beta}+b}{\partial w_{ij}}=g_{p+i-\frac{n-1}{2},q+j-\frac{n-1}{2}} $$

那么:

$$ \frac{\partial E}{\partial w_{ij}}=\sum_{p=0}^{n-1}\sum_{q=0}^{n-1}\frac{\partial E}{\partial h_{pq}}\frac{\partial h_{pq}}{\partial w_{ij}}=\sum_{p=0}^{n-1}\sum_{q=0}^{n-1}g_{p+i-\frac{n-1}{2},q+j-\frac{n-1}{2}}\frac{\partial E}{\partial h_{pq}} $$

我们仔细观察上面这个式子,是不是可以把它看作卷积的一部分?我们把它组合成卷积:

$$ \frac{\partial E}{\partial \boldsymbol{W}}=\boldsymbol{G}*\frac{\partial E}{\partial \boldsymbol{H}} $$

3.2 对 $b$ 求偏导

我们仍然从卷积的一部分开始:

$$ h_{ij}=\sum_{\alpha=0}^{n-1}\sum_{\beta=0}^{n-1}g_{i+\alpha-\frac{n-1}{2},j+\beta-\frac{n-1}{2}}\cdot w_{\alpha\beta}+b $$

那么:

$$ \frac{\partial h_{ij}}{\partial b} = 1 $$

那么:

$$ \frac{\partial E}{\partial d}=\sum_{i,j}\frac{\partial E}{\partial h_{ij}}\frac{\partial h_{ij}}{\partial d}=\sum_{i,j}\frac{\partial E}{\partial h_{ij}} $$

3.3 对 $\boldsymbol{G}$ 求偏导

上面两步完成了本层的参数梯度计算,但是我们还得为计算前一层的梯度,要不然就没法继续往前传了。

我们要求的是:

$$ \frac{\partial E}{\partial \boldsymbol{G}} $$

首先考虑输入矩阵其中一项:

$$ \frac{\partial E}{\partial g_{ij}}=\sum_{p=0}^{n-1}\sum_{q=0}^{n-1}\frac{\partial E}{\partial h_{pq}}\frac{\partial h_{pq}}{\partial g_{ij}} $$

由于:

$$ h_{ij}=\sum_{\alpha=0}^{n-1}\sum_{\beta=0}^{n-1}g_{i+\alpha-\frac{n-1}{2},j+\beta-\frac{n-1}{2}}\cdot w_{\alpha\beta}+b $$

那么:

$$ \frac{\partial h_{pq}}{\partial g_{ij}}=\frac{\partial \sum_{\alpha=0}^{n-1}\sum_{\beta=0}^{n-1}g_{p+\alpha-\frac{n-1}{2},q+\beta-\frac{n-1}{2}}\cdot w_{\alpha\beta}+b}{\partial g_{ij}}=w_{i-p+\frac{n-1}{2},j-q+\frac{n-1}{2}} $$

那么:

$$ \frac{\partial E}{\partial g_{ij}}=\sum_{p=0}^{n-1}\sum_{q=0}^{n-1}w_{i-p+\frac{n-1}{2},j-q+\frac{n-1}{2}}\frac{\partial E}{\partial h_{pq}} $$

和 3.1 中操作一样,我们可以把它看作卷积:

$$ \frac{\partial E}{\partial \boldsymbol{G}}=\boldsymbol{W}*\frac{\partial E}{\partial \boldsymbol{H}} $$

4 代码实现

以下是我使用 C++ 编写的神经网络的源代码,其中实现了卷积层,可以和上文的公式对照学习:

https://github.com/ChrisKimZHT/Neural-Net-cpp

实际上,3.1 ~ 3.3 推导出的三个梯度,代码表示就如下:

Matrix ConvolutionalLayer::backward(const Matrix &d_output, double learning_rate) {
    Matrix d_input = d_output.convolution(weights.transpose(), false);
    Matrix d_filters = input.convolution(d_output.transpose(), true);
    double d_bias = d_output.sum();

    weights -= d_filters * learning_rate;
    bias -= d_bias * learning_rate;

    return d_input;
}
文章目录