文章

Stanford CNN

  1. 为什么普通神经网络不行
  2. CNN base
    1. single-channel filter
    2. padding
    3. strided 步长
    4. cross-correlation vs. convolution
    5. multi-channel filter
    6. multiple filters(multi-channel filter)
  3. CNN layer
    1. Conv
    2. Pooling Layer
      1. pooling layer vs. conv layer
    3. FC layer
      1. softmax
  4. CNN example
    1. input
    2. conv1
    3. pool1
    4. conv2
    5. pool2
    6. fc1
    7. fc2
    8. fc3
    9. 形状概览
  5. why CNN?
    1. parameter sharing input之间(output之间)共享参数
    2. sparsity of connection 层与层之间的稀疏连接
  6. ResNet - Residual Network 残差神经网络
  7. 1x1 convolution
  8. Inception Network

为什么普通神经网络不行

以计算机视觉领域为例,一张1000x1000的RGB照片,有1000x1000x3=3million个像素点,使用普通的神经网络,就有3M个输入特征,如果第二层有1000个神经元,二者之间的矩阵就是1000x3M=3billion个参数,太大了。

CNN base

假设要检测一张图像里物体的边缘。

single-channel filter

使用一个3x3的矩阵(称为filter,或者kernel,比如在pytorch里就是kernel),原图(假设为灰度图,6x6)卷积这个filter,会产生一个4x4的矩阵。

卷积是简单的对应位置数值相乘,求和,得出一个数。不是矩阵相乘。

如果这个filter选择的足够合适,就会在结果矩阵里产生明显的边界。

比如,假设数字越小越黑,越大越白,0基本是灰色:

\begin{bmatrix}
1&0&-1\\
1&0&-1\\
1&0&-1
\end{bmatrix}

是一个很好的检测vertical边界的矩阵。

某一块如果不是边缘,数值上应该变化不大,filter左边1,右边-1,乘完趋近于0。如果是边缘,数值变化较大,乘完不趋近于0(大于0或者小于0),所以乘出来的矩阵数字比较大的地方(产出的图片特别白或者特别黑的地方)就是原图的垂直边缘。

在卷积神经网络中,主要就是要学习出filter的参数,即这个3x3矩阵的9个参数。

同理,下面是一个水平边缘检测filter:

\begin{bmatrix}
1&1&1\\
0&0&0\\
-1&-1&-1
\end{bmatrix}

当然,也可以是其他的矩阵,不一定非得是这两个矩阵。具体用哪个,只要效果好就行。

padding

有两个问题:

  1. 每次图片和filter做一次卷积,尺寸都变小了一些;
  2. 原图的边缘像素,比如顶点位置只和filter乘了一次,但是中间位置的像素会和filter乘好几次,相当于在生成的结果中,原图每个像素所占比重不同了。

结果这两个问题的常用做法是padding:padding=1代表给原图加一圈像素。

  • n:原图size;
  • f:filter size;
  • p:padding;

对于原图n=6,p=1之后,n变成了8,输出结果还是6x6。相当于图没变小。用公式表示,结果矩阵的size就是:(n+2p)-(f-1)

两种专业说法:

  • valid convolution:no padding;
  • same convolution:padding之后使输出尺寸等于输入,也就是(n+2p)-(f-1)=n,即 2p=f-1

所以上述filter为3x3,p=1就能做到same padding。

计算机视觉里,使用的filter一般是奇数的,可能的原因有:

  • 好填充,padding的时候一圈都填充就行了,而不是不对称填充;
  • filter会有一个中心像素,所以好描述filter的位置;

遵守这个传统就行了。

strided 步长

横竖都应用步幅,大幅缩减生成矩阵的大小。

  • s:strided size;

生成矩阵原来(strided=1时候)是(n+2p)-(f-1),strided之后要除以s,如果不是整数,向上取整即可。

cross-correlation vs. convolution

这里所说的convolution实际上不是数学上的convolution,而是cross-correlation,因为没有卷,只进行了积(对应位置元素相乘求和)。

卷的话就是把filter矩阵顺时针或者逆时针旋转180°

multi-channel filter

比如RGB图片有三个channel,filter也得是三个channel,才能做“体积上”的卷积操作。但是输出还是一个channel。即,输出矩阵的一个值,是filter和输入矩阵在“体积上”的乘积和。

filter和输入的channel必须相同。

所以,RGB的filter有三个channel,分别对应R,G,B层,去filter每一层。

multiple filters(multi-channel filter)

一个filter产生一层输出,多个filter叠加起来,就是多层输出。即 multiple multi-channel filters

CNN layer

一般有三种layer:

  • Conv: Convolution
  • POOL: Pooling
  • FC: Fully Connected

Conv

其实就是filter。

CNN:input x filter -> ReLU(非线性处理一下,激活函数) -> output

像极了普通神经网络:a2 = ReLU(W1*a1 + b1)

  • filter = W1
  • input = a1

一层CNN有多少个参数?假设有10个size=5,channel=3的filter,一个filter有5x5x3=75个参数,10个filter就是750个参数。(如果不考虑ReLU & bias的话)

一层CNN的参数跟input size有关吗?没关(当然,channel是要一致的),图片尺寸大点小点,不影响filter的尺寸。filter定义好size之后,参数个数是一定的,input大只会导致output大。

所以处理图片问题,CNN要比传统的神经网络好啊。传统神经网络处理的图片变大了,相当于特征变多了,W矩阵也要变大,参数就变多了。 那么我就有个问题了——假设以后计算能力无限扩大了,是不是不需要CNN了?

设计CNN,就是要设计一些超参数,比如多少个filter,padding多少,strided多少。

Pooling Layer

pool: to collect resource or sth.

比如Max pooling:

  • input:4x4矩阵
  • pool:2x2矩阵,strided=2。一般pooling layer的stide和size是一样的,这样就可以不重叠了;
  • output:2x2矩阵

和卷积类似,但是简单很多。不是乘积求和,而是简单的比大小。

pooling layer的意义:减少representation(减小conv layer输出矩阵的大小),以此来提高网络的计算速度。

为什么可以这么做?假设是在检测图像中猫是否存在,现在无论如何都得扔掉一些像素,以使得网络计算速度加快。扔哪些?max pooling是在保留最大值,扔掉其他值,也就是说如果这一块区域没出现猫的特征相关数值,即使保留最大的值,也是很小的数字。同样是扔,这种扔法显然要比随机扔更有策略。

pooling layer vs. conv layer

  • 操作方式相似,都是把一个input搞成output,流程也类似,只不过一个是卷积(乘积求和),另一个求max;
  • pooling layer没有parameter!conv是求积再求和,既然求积,用哪个数和input相乘?得训练出来,这就是conv层的参数。pooling layer不一样,比如max pooling就是给input求个max,自己不需要有任何参数
  • max pooling一般不padding;

max pooling不是唯一的pooling,也有average pooling,但是现在都用max pooling,不怎么用average pooling了。

一般情况下,conv和pool合称一个layer,因为pooling layer没有参数。比如第一个layer,分别称为conv1和pool1。

FC layer

最后要把输出矩阵flatten一下,像传统神经网络一样,不停降元,最终输出为一个(logistic regression)或多个(1xn向量,分类问题)。

其实就是传统神经网络,叫做fully connection。

softmax

softmax函数:值输出转换为概率输出,用于多分类,相当于二分类里的sigmoid函数。比如分类结果有10中可能把一个1x10的向量(或者10x1,都行)的值输出转换为概率输出。

  • https://deepai.org/machine-learning-glossary-and-terms/softmax-layer

CNN example

以一个pytorch官网的例子来说明CNN结构:

图画的和代码不太一致,维度有差别,仅参考结构即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # 1 input image channel, 6 output channels, 3x3 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 6 * 6, 120)  # 6*6 from image dimension
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If the size is a square you can only specify a single number
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
print(net)

input

32x32x1,灰度图片

conv1

Conv2d(1, 6, 3)

  • 2d:图片,所以是2d;
  • 1:input channel;
  • 6:output channel;
  • 3:filter size;

所以输入是32x32x1,输出是30x30x6。

pool1

max_pool2d(F.relu(self.conv1(x)), (2, 2))

  • 2d:图片,所以2d;
  • (2, 2):kernel_size,也就是filter_size,方形的也可以传一个数值;
  • stide默认是kernel_size,padding默认是0;

所以对conv1的输出做了一个ReLU,然后max pooling一下。

所以输出是15x15x6。

怎么知道channel是多少?看它的api,https://pytorch.org/docs/stable/generated/torch.nn.MaxPool2d.html:

1
2
3
Shape:
Input: (N, C, H_{in}, W_{in})
Output: (N, C, H_{out}, W_{out})

所以channel不变。

conv2

输入15x15x6,kernel_size=3,channel_out=16,所以输出是13x13x16

pool2

输入是13x13x16,输出是6x6x16。凑不成整数的扔了。

我记得是扔了。所以是6,而不是7

fc1

首先,flatten了一下,将conv和pool layer的输出搞成了一个一维向量。shape:1x(16x6x6)

关于torch.view改变tensor形状(其实就是reshape):

  • https://stackoverflow.com/a/50793899/7676237
  • https://pytorch.org/docs/stable/tensors.html#torch.Tensor.view

然后,使用普通神经网络的线性变换Linear(16x16x6, 120)

  • 16x6x6=576: input,这是pool layer处理后的输出矩阵flatten之后的个数;
  • 120:output

Linear的shape:

1
2
3
4
Shape:
Input: (N, *, H_{in}), where `*` means any number of additional dimensions and H_{in} = in_features

Output: (N, *, H_{out}), where all but the last dimension are the same shape as the input and H_{out} = out_features

和Andrew Ng不太相同的是,torch这里flatten之后是1xn的向量,Linear也是比较直白的input size -> output size。搞成1xn主要是因为Linear是特征向量乘以变换矩阵,而不是变换矩阵乘以特征向量

输出是1x120

fc2

Linear(120, 84)

输入是1x120,输出是1x84

fc3

Linear(84, 10)

输入是1x84,输出是1x10

1x10对应十个输出结果。

至于softmax,这个网络没有定义。我看后续使用是拿着CNN得到的这个1x10的向量,使用torch.max获取其最大值和index,直接把这个当做最终的预测结果。并不关心每一个的概率是多大。

形状概览

形状概览看到的只是这些layer的超参数:

1
2
3
4
5
6
7
Net(
  (conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
  (fc1): Linear(in_features=576, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)
  • 使用torch构建CNN时,不关心某个filter共计多少个参数。设置一下filter的超参数,只关心输入形状和输出形状。根据输出形状,设置下一个layer的超参数
  • 一般矩阵随着layer的层层递进,输出矩阵的size会减少,channel会变多;

why CNN?

传统神经网络其实就是fully connected neural network

同样多的输入,想达到同样规模的输出,fc需要大量的参数矩阵,CNN却只需要几个filter,相比fc拥有少了好几个量级的参数。CNN怎么做到的?

parameter sharing input之间(output之间)共享参数

比如检测边界,一个3x3的filter,9个参数,可以被图片的不同位置共用。而检测边界只需要这样的9个参数就够了。

sparsity of connection 层与层之间的稀疏连接

CNN的下一层神经元和上一层的输入建立的是稀疏的连接,比如还是用3x3的filter的例子,一个output仅仅和3x3=9个input有关,和其他input无关。而fc则是全连接,一个output跟所有的input都有关联。所以参数多。

但实际上,比如一个图片,是一只猫,某些像素变了,它还是一只猫,如果是fc那种全连接,可能就得到不同的结果了。所以CNN和fc比,更robust,更适用于一些平移不变的特性。

ResNet - Residual Network 残差神经网络

网络层次越来越深,理论上会让结果越来越准确,但实际发现更难训练,而且效果还可能变差了。

ResNet:比如在训练第5层的时候,不仅使用第四层的输出,还是用第二层的输出,二者叠加。即:

1
a[5] = g((W*a[4] + b[4]) + a[2])

这样,因为W被纳入了cost function,所以W会比较小,即a5约等于直接g(a2),这意味着极限情况下,第三层和第四层被跳过去了。所以即使网络层次很深,多添加了几层(residual block),也不至于特别难训练,效果变差。最差a5效果和a2相同。而且,一旦这些residual block还学到了一些东西的话,效果会更好。

ResNet给人的感觉就是,想加深一下层次,让效果更好,但加深又不一定好,所以ResNet用shortcut短路掉这些新加的layer来保底,最差情况就是跳过被shortcut的layer,非最差情况下,又相当于不管怎样还是加了一些layer的。所以ResNet相当于加了零点几layer吧

一个要注意的事情就是,a[2]和a[4]的维度应该相同。如果不同,就得给a[2]乘个系数矩阵,让他们维度相同。

1x1 convolution

如果input只有1个channel,使用1x1x1的convolution layer,就相当于给原来的input乘以一个plain number,没什么卵用。

如果input有多个channel,使用1x1xc_input的convolution相当于没有缩减input的size,但是控制filter的个数,可以改变output的channel

其实相当于1x1的filter和input的一块做了一个fully connection。

和pooling layer相比,pooling layer主要是改变input的size(当然也能改变channel)。

如果pooling layer size=1,stride=1,就相当于是1x1的conv。这又印证了conv layer和pooling layer是非常相似的。

Inception Network

名字来源于盗梦空间Inception,In进入,cep拿。进入&拿,盗梦空间用了这个表面意思。(inception的实际意思是起始,开端)

Inception network只是借用了盗梦空间层层深入的意思,说明它是一层层深入的网络。

Inception network的思想就是:用1x1 filter还是3x3 filter?或者说用3x3的pooling layer而不是filtre?答案就是小孩子才做选择,我都要!把input用他们分别都处理一下,也就是说所有的layer都用到了,让他们有相同的output size(channel无所谓),然后再把这些output拼接起来,变成一个叠加channel的矩阵,作为下一层网络的output。

所以一般无论是conv layer还是pooling layer都做same convolution,让输出都和输入同size,这样所有的输出就同size,可以拼接了。

https://colah.github.io/posts/2014-07-Conv-Nets-Modular/

本文由作者按照 CC BY 4.0 进行授权