Stanford CNN

Stanford CNN

为什么普通神经网络不行

以计算机视觉领域为例,一张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/