CNN

Lenet5

MNIST

要说 Lenet ,那我们就不得不说说 MNIST 数据集。这个手写数字的数据集是非常常用的数据集之一了。在这个数据及上的手写数字识别的比赛,发展到目前已经算是基本解决了。但是发展的历程还是值得一看的。我们就看一下 LeCun 他的工作的历程吧。 LeCun 在这个数据集上尝试了很多的方法,从 1-layer neural network , K-nearest neighbors + Distance method 再到 PCA , SVM , Multi-layers neural network 最后提出了 Convolutional neural network 。 是一个很值得思考的过程,但是在每一分项中,他都没有达到最好的的成绩,有点前人栽树,后人乘凉的味道。

具体的数据集我就不再这里介绍了,感兴趣的可以去 LeCun 的主页上可以找到相关的信息。 LeCun 也是在这个模型上,完成了商用的首写数字识别系统,具体的可以参考 LeCun 98年发表的那个 Gradient-based learning applied to document recognition ,不过那篇文章是个 Sequential handwriting number recognition ,所以是在卷积神经网络前面接了一个 HMM 作为序列的分割和拼接。我们并不关注前端的 HMM ,我们这里只关注我们感兴趣的 CNN 。

下面的章节我们主要参考的是这里的教程,还有 Jake Bouvrie 的“ Notes on Convolutional Neural Networks
”。

motivation

卷积神经网络和之前的神经网络最大的区别,应该是有三点:
motivation

卷积神经网络特征

1.Receptive field:局部感受野,主要描述的是,输出不再是空间无关的了。每一个输出是有一定的空间意义的,然后根据 Hubel,Wiesel 对人脑的链接的研究,提出每一个输出只和输入的相对应的空间位置的一个邻域内部的输入有关的。这个观点非常适合具有局部特性的数据的处理,例如图像数据。

2.Shared weights:共享权值,这个说的是每个输出所对应的权值是相同的,但是每一个空间位置是由多个输出(多个 Kernels )组成的。这个我们直观来理解就是,用一个边缘检测的例子,我们用不同方向(对应多个核)的滤波器进行全图(对应共享权值)的滤波,然后综合各个滤波的结果得到我们想要的边缘。

3.Pooling:池化,关于池化,是有很多种形式的,最主要的形式就是 MAX pooling ,就是在一个邻域里面取最大值。因为实在一个邻域里面取最大值,所以起到了的降维的作用。这个同时还会得到一个更好的效果,那就是 invariance 。

Lenet

说了这么多,让我们来看一个实例吧:
framework

网络结构

我们先来看一下这个网络的结构吧。这是一个7层的神经网络,输入的是一个32x32 的灰度图, S 表示的是下采样层, C 表示的是卷积层, F 表示的是全连接层。根据上图,我们可以很清楚的看到,第一层是一个卷积层,其包含6 个5x5的卷积核,参数个数156,也就是说这里的输出有6 个28x28 的特征图。第二层是一个下采样层,这里的下采样策略都是 MEAN pooling ,采样窗的大小都是2x2,参数个数是12个(一个特征图对应两个参数),所以这里的输出就变成了6 个14x14 的特征图。第三层是一个卷积层,但是这里涉及到一个问题,那就是上一层有6 个特征图,而这一层,我们有16 个5x5 的卷积核,也就是说我们应该得到16 个特征图,那我们怎么建立这6 个输入和16 个输出的关系呢。显然,我们是不想输出的每一个特征图都和之前的特征图链接的(我觉得这里可能是想保持空间特异性的,当然也不排除只是为了计算复杂性)。那 LeCun 是怎么定义这之间的链接的呢?

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0 X X X X X X X X X X
1 X X X X X X X X X X
2 X X X X X X X X X X
3 X X X X X X X X X X
4 X X X X X X X X X X
5 X X X X X X X X X X

现在我们搞清楚了第二层和第三层是怎么连接的,这里的参数个数是6x(3x25+1)+9x(4x25+1)+6x25+1)=1516,第三层的输出是16 个10x10 的特征图,下面我们接着看第四层。第四层是一个下采样层,采样策略和采样窗的大小都与之前的采样层是一样,所以输出的结果是16 个5x5 的特征图。然后第五层还是一个卷积层(这里也可以看做是一个全连接层,当且仅当输入的特征图的大小和卷积核一样大的时候。)但是按照 LeCun 关于参数的计算,这里的参数是120x(16x25+1)= 48120,也就是说这里没有任何的权值共享,每一个输出对应的所有输入的参数都不一样这样才能保证参数是48120。第六层也是全连接层,输入是120,输出时84,所以参数是84x (120+1)=10164,最后一层就是输出了,这里的实处是10,也就是说这里训练的参数是(84+1)x10=850.

好了,这样整个结构就基本整明白了。下面我们开始介绍这个卷积神将网络的训练。

Forward pass

为了方便,我们用$u^l$表示第$l$层的输出,最后输出层的输出,我们使用$t$ 表示,这里我们使用的激活函数
$$f(\cdot)=sigmoid(\cdot)$$
我们用$\star$ 代表卷积。好了那我们来看一下卷积层和下采样层,全连接层都是怎么计算的。
对于卷积层,我们的计算过程是:
$$u_i^l=W_i^l \star x^{l-1}+b_i^l,~~~i\in~|F.map|$$
然后输出是:
$$x_i^l=f(u_i^l)$$
但是中间的卷积层是由多个输入特征图的,所以之前的公式应该变成:
$$u_i^l=\sum_{j \in M_i} x_j^{l-1}*k_{ji}^l +b_i^l$$
这里的$M_i$表示的是所选取的输入特征图。

对于下采样层我们的计算公式是:

$$u_i^l=\beta_i^l down(x_i^{l-1})+b_i^l,~~~i\in~|F.map|$$

对于全连接层,就和之前的神经网络是一样的:
$$ u^l=W^l \cdot x^{l-1}+b^l $$

然后我们使用的损失函数是:
\begin{equation}\label{6}
E^N=\frac{1}{2}\sum_{n=1}^{N}\sum_{k=1}^{c}(t_k^n-y_k^n)^2
\end{equation}

这个就是前向的过程了,下面我们重点关注反向传播的过程。

Backpropagation pass

让我们先回顾一下原来的人工神经网络里面的反向传播算法是什么样子的。

首先我们定义了一个灵敏度(也就是对偏置的导数):
\begin{equation}\label{7}
\frac{\partial E}{\partial b}=\frac{\partial E}{\partial u} \frac{\partial u}{\partial b}=\delta
\end{equation}

然后我们还能给出本层的灵敏度和上一层的灵敏度之间的关系(这个对于编程是很有用的):
\begin{equation}\label{8}
\delta^l=(W^{l+1})^T\delta^{l+1} \circ f’(u^l)
\end{equation}

然后这些都知道了,由于是反向传播,我们从最上层,开始的,所以只要知道最上层的灵敏度:
\begin{equation}\label{9}
\delta^L=f’(u^L) \circ (y^n-t^n)
\end{equation}

这就相当于,我们知道了每一层偏置的导数,然后我们就很容易得到关于$w$的导数:
\begin{equation}\label{19}
\frac{\partial E}{\partial W^t}=x^{l-1}(\delta ^l)^T
\end{equation}

这样我们就可以使用梯度下降法进行反向传播的求解了。但是这个是不能直接应用到卷积神经网络的,主要是因为这里有卷积层的卷积操作和下采样层的下采样函数,这个都是不是那么容易求导的,我们要作相应的改动才可以。

BP for CNN

反向传播,那我们就从全链接开始吧。全连接层,和之前的反向传播算法的求解过程是一样的。所以就不在这里一一赘述了。下面,我们主要讲一下,卷积层和下采样层是怎么求解的吧。

(1)卷积层:

先让我们回去一下之前的卷积层的操作:
\begin{equation}\label{10}
x_j^l=f\left ( \sum_{i \in M_j} x_i^{l-1}*k_{ij}^l +b_j^l\right )
\end{equation}

由于上一层是下采样层,所以上一层的灵敏度因子是不够的。所以我们不能直接的使用之前的灵敏度之间的关系来获取这一层的灵敏度。这里要引入一个上采样的过程,也就是把上一层的一个灵敏度扩充为2*2 的灵敏度,具体的函数是:
\begin{equation}\label{11}
up(x)=x \otimes 1_{n \times n}
\end{equation}

这里的$\otimes$ 是$kronecker$ 乘积的意思,在$Matlab$中,我们可以使用$repmat$函数。我们上采样完了,就可以计算这一层的灵敏度了:
\begin{equation}\label{12}
\delta_j^l=\beta _j^{l+1}\left( f’(u_j^l) \circ up(\delta_j^{l+1})\right)
\end{equation}

这个就是pooling惹的祸啊!下面还有更大的麻烦呢,那就是共享权值的结果,因为这里的$b_j,k_{ij}$ 都是共享的,换句话说,这里的参数训练是和整张的特征图都是相关的,而不是仅仅和输出的一个节点有关,那我们该怎么办呢?求和吧,少年!
\begin{equation}\label{13}
\frac{\partial E}{\partial b_j^l}=\sum_{u,v}(\delta_j^l)_{uv}
\end{equation}
\begin{equation}\label{18}
\frac{\partial E}{\partial k_{ij}^l}=\sum_{u,v}(\delta_j^l)_{uv}(p_i^{l-1})_{uv}
\end{equation}

需要注意的是这里的$p_i^{l-1}$ 是之前卷积的时候,与卷积核相乘的块的\emph{转置}(这就是局部感受野惹的祸啊),这个公式计算起来可能并不是那么容易,但是Jake Bouvrie 给出了对应的$Matlab$ 公式:
$$ \frac{\partial E}{\partial k_{ij}^l}=rot180\big(conv2(x_i^{l-1},rot180(\delta_j^l),’valid’)\big)$$

这样,这一块我们就基本了解了,下面我们来看看下采样层吧。

(2)下采样层:

我们还是先回顾一下之前的计算公式:
\begin{equation}\label{14}
x_j^l=f\left( \beta_j^l down(x_j^{l-1}) +b_j^l \right)
\end{equation}
这里的下采样是一个求均值的过程。

这一个地方的灵敏度是最不好计算的,因为上一层是一个卷机层,而且这里一个特征图只有两个参数,也就是说,整张特征图都共享这两个参数。这里给出一个公式:
\begin{equation}\label{17}
\delta_j^l=f’(u_j^l) \circ \sum_{u,v}(\delta_j^{l+1})_{uv}(k_j^{l+1})_{uv}
\end{equation}
Jake Bouvrie 给出了对应的$Matlab$ 公式:

$$ \delta_j^l=f’(u_j^l)\circ conv2(x_j^{l+1},rot180(k_j^{l+1}), ‘full’)$$

给出了灵敏度因子,一切就变得好计算了:

\begin{equation}\label{15}
\frac{\partial E}{\partial b_j^l}=\sum_{u,v}(\delta_j^l)_{uv}
\end{equation}

\begin{equation}\label{16}
\frac{\partial E}{\partial \beta_j^l}=\sum_{u,v}(\delta_j^l \circ d_j^l)_{uv}
\end{equation}
这里需要注意的是,因为是下采样层,所以计算$\beta$ 的导数的时候,还是会涉及到下采样$d_j^l=down(x_j^{l-1})$,这样就基本解释清楚了。