pydata

Keep Looking, Don't Settle

tensorflow简介--07

这是以前看卷积神经网络做的笔记。我把大概的意思用中文写了,毕竟看中文还是快得多。但是关键的专有名字还是保留着英文,这样方便查看英文资料。比如查找帮助的时候经常会出现filter,FC layer这种说法,所以我觉得还是保留英文更好一些,而不是翻译成过滤器和全连接层。

卷积神经网络和其他神经网络有相似的地方:它们都是由神经元构成,每个神经元接受输入的数据,每个神经元有各个输入的权重(weights)和偏差(biases)参数,最后有对应的输出,然后通过最小化损失函数来求解相应的参数。

和通常的神经网络不同的是:卷积神经网络通常用来进行图像识别。它通过一些特殊的设计使得参数的数量大为减少。

结构总览

通常的神经网络中,神经元对每一个输入的数据点都会有一个参数与之对应。在处理图像的时候,这就会导致引入巨多的参数。比如一个1000万象素的图片,一个神经元就会有3000万个(3个chanel)参数。如果有很多神经元的时候参数就会非常多。

卷积神经网络的神经元都有三个维度:width, height, depth(这儿的depth指的是激活层的第三维,不是神经网络的layer的个数).比如CIFAR-10里一个32x32x3(width x height x depth)的图片,每个layer的神经元只连接这个图片的一小块。最后的输出是一个[1x1x10]的向量,用来表示图片是10个类别里的哪一个类别。

alt text

上图左边是通常的3层神经网络。第一层是输入层。第二层的4个神经元中每个神经元都与输入层的每个输入相连,同样第三层的每个神经元与第二层的每个输出相连。最后的输出层与前一层的所有神经元相连。上图右边是卷积神经网络,红色的是一个图片的输入层,所以其维度是[width x height x 3],中间layer的每个神经元只跟输入层的一小部分相连,比如通常用一个3x3x3的filter与图片中每个3x3x3的小区域相连。卷积神经网络由各个Layer构成。

构造CNN的Layers

卷积神经网络主要有三种不同的Layer:convolutional layer, pooling layer, fully-connected layer.我们通常把它们叠加起来构成卷积神经网络。

例子: 一个简单的CIFAR-10的分类卷积神经网络包含下面几个layer[INPUT-CONV-RELU-POOL-FC].

  • INPUT[32x32x3]是输入图像的像素矩阵。这些图片的width是32, height是32,有3个chanel
  • CONV层用来计算卷积神经网络:input的每一个小块会跟filter矩阵做一个卷积,如果有12个filter矩阵,那么输出就是[32x32x12]。
  • RELU层会对CONV的[32x32x12]矩阵里的每一个元素做一个变换\(max(0, x)\).RELU不改变矩阵的shape
  • POOL层用来沿着spatial维度(width, height)做一个downsampling,输出矩阵的维度为[16x16x3]
  • FC用来计算分类得分。对每一个输入的图片,它的输出是一个[1x1x10]的向量矩阵。这10个数字用来表示图片在每个分类的得分。和普通的NN一样,FC的神经元跟前面输入的每一个数字都有联系。

上面的例子中,CONV和FC包含参数,RELU和POOL不含参数。损失函数是FC里面每个分类的得分和图片真实分类的一个函数。通过最小化损失函数可以得到CONV和FC里面的参数的值。

tiny VGG : 下面是一个VGG结构的卷积神经网络示意图

alt text

上图最左边是一张输入的图片,中间是CNN的各个layer,最右边FC layer输出图片在每个分类里面的概率。

下面分别介绍CNN里面几个重要的Layers:CONV, POOLING, FC.

Convolutional Layer

卷积层是卷积神经网络的核心部分。简单地说就是对输入的数据通过Filter做卷积然后输出。

总览 卷积层的参数是是一系列的可优化的参数。每个filter对应输入的空间(spatially,width, height)的某一部分,但是在depth上shape是一样的。比如第一个卷积层上的filter的size为[5x5x3] (也就是在width和height上的5个像素,3是因为输入的图片depth为3,有3个chanel)。在forward pass过程中,每一个这样的filter沿着图片的width和height滑动(或者叫卷积),这样我们就会得到一个二维的矩阵。假设这个卷积层有12个filter,每个都这样做卷积,我们把这12个卷积的矩阵stack在一起就得到新的输出(输出维度为 [width X height X 12] ,输出的width和height的大小跟stride和padding有关,后面会具体介绍)

Local Connectivity 当处理高维输入的时候,一般不能把前一个输入的所有神经元都跟卷积层的神经元连接起来,那样会引入天量的参数。我们只是把输入的每一小块区域跟当前卷积层的神经元联系起来。这一小块区域的空间维度大小叫做接受域(receptive field,也就是filter的size),这是一个可以变化的超参数。在深度(depth)上他们是一样大小。

例1 每张CIFAR-10的图片的初始输入维度为[32x32x3],如果接受域(receptive field, 或者叫filter size)的大小为5x5,那么卷积层的每个神经元都与输入图片的每个[5x5x3]的区域通过filter做卷积(所以filter的大小也为5x5x3,共计75个参数,加上一个bias,总共76个参数)。注意depth方向的大小为3,因为depth方向的大小总是跟输入的depth一样大。

例2 假设输入的size为[16x16x20].假如receptive field的size是3x3,那么卷积层上的每个神经元都与输入矩阵的每个[3x3x20]的区域通过filter做卷积。所以每个filter的参数数量是181个[3x3x20=180加1个bias,共计181个]。注意的是这时候沿着width,height的空间大小是3x3,但是沿着depth是20.因为输入的矩阵的depth是20.

alt text

上图最左边粉色图片大小为[32x32x3],receptive field是其中每个粉色的小块,大小为[5x5x3],所以filter的大小为5x5x3。中间的蓝色的图片说明有5个filter,所以conv layer的输出的depth为5。如果假设width和hegith不变,那么中间蓝色的输出区域大小为[32x32x5]。

Spatial Arrangement 前面介绍了卷积神经网络的神经元怎么与输入的数据相连。下面我们介绍在卷积神经网络里面的输出数据是什么样子(这儿的所有的数据都是指numpy array)。输出数据的size决定于下面三个超参数:depth, stride, zero-padding

  1. depth是输出结果的一个超参数,简单来说它指的是filter的数量。有时候也叫fibre
  2. stride指的是每次滑动filter时移动的步长。stride为1表示每次沿着width或者height方向移动一个点,stride为2表示每次移动两个点的步长;如果步长为2,那么新产生的output就会比输入的数据的在width和height上面小(spatial size会变小)。
  3. zero-padding 0填充:有时候需要在输入的矩阵外面填充上0,通常这样做的目的是为了控制输出的空间大小(spatial size),比如说为了使得输入和输出在width和height上大小一样。

输出的空间大小可以有下面几个值计算出来:输入的大小(\(W\)),接受域(filter)的大小(\(F\)),步长(\(S\)),以及外围补充的0的个数(\(P\))。这样得到的输出的神经元的个数为\((W - F + 2P)/S + 1\)。比如对一个7x7(depth为1,略去)的输入,filter的大小是3x3,步长为1,0个0填充,那么输出的大小为5x5。如果步长为2,那么输出的大小为3x3。

使用0填充 通常当S=1的时候,需要设置0填充的数量\(P= (F-1)/2\),这样的话输入和输出会有同样的空间尺寸。

步长的限制 设置步长S的值的时候必须要注意使得公式\((W - F + 2P)/S + 1\)有效。如果不能整除的话那么步长的设置就是无效的:比如W=10,P=0,F=3,假设设置S=2,那么\((W - F + 2P)/S + 1 = (10 - 3 + 0) / 2 + 1 = 4.5\)。这个步长就无效。实际计算的时候要么会报错,要么就是需要采取0填充来使之有效。

参数共享 在用filter滑过输入数据的时候,通常不会对输入数据的每一个小块都选择一个不同的filter,如果这样做的话会导致filter过多,因而模型里参数的个数会急剧上升。通常所有输入数据小块会滑过同一个filter,得到一个输出矩阵。然后所有的输入数据小块再滑过一个新的filter,再得到一个新的输出矩阵。最后把这些输出矩阵stack在一起得到最终的输出矩阵。有几个矩阵stack在一起,最后的输出矩阵的depth就是几。

Numpy Examples 下面是一个具体的例子,使用numpy的代码来帮助解释清楚。假如输入矩阵是一个numpy数组X,那么

  1. 在width,height为(x, y)位置处depth上的值可以表示为X[x, y, :]
  2. 在depth为d的切片(所有width,height上的值)是一个矩阵,表示为X[:, :, d]

Conv Layer 假设X的维度为X.shape: (11, 11, 4),假设没有0填充(P=0),filter的大小为F=5,步长为S=2,那么输出矩阵的spatial尺寸为(11-5)/2+1 = 4,也就是输出矩阵的width和height大小为4.输出矩阵V看起来如下

  • V[0, 0, 0] = np.sum(X[:5, :5, :] * W0) + b0
  • V[1, 0, 0] = np.sum(X[2:7, :5, :] * W0) + b0,注意步长为2
  • V[2, 0, 0] = np.sum(X[4:9, :5, :] * W0) + b0
  • V[3, 0, 0] = np.sum(X[6:11, :5, :] * W0) + b0

在numpy里面,上面的*表示矩阵对应的元素相乘,不是矩阵相乘。W0是权重矩阵filter,其size为W0.shape: (5, 5, 4).b0是bias。注意这里的参数共享:在输出的depth的index=0(V[:, : 0])的维度上,W0b0都相同。下面计算数据矩阵depth=1上面的值V[:, :, 1]

  • V[0, 0, 1] = np.sum(X[:5, :5, :] * W1) + b1
  • V[1, 0, 1] = np.sum(X[2:7, :5, :] * W1) + b1
  • V[2, 0, 1] = np.sum(X[4:9, :5, :] * W1) + b1
  • V[3, 0, 1] = np.sum(X[6:11, :5, :] * W1) + b1
  • V[0, 1, 1] = np.sum(X[:5, 2:7, :] * W1 + b1)
  • V[2, 3, 1] = np.sum(X[4:9, 6:11, :] * W1) + b1

我们可以看到,在上面输出矩阵depth的index=1(V[:, :, 1])上面,我们所有的参数都是W1b1

总结 在卷积层上:

  • 接受一个输入矩阵,size(shape)为\(W_1 \times H_1 \times D_1\)
  • 需要有4个超参数
    • Filter的数量\(K\)
    • Filter的size \(F\), (输入矩阵每次选取的小块的size)
    • 步长\(S\)
    • 0填充的大小\(P\)
  • 输出一个size为\(W_2 \times H_2 \times D_2\)的矩阵,其中
    • \(W_2 = (W_1 - F + 2P) / S + 1\)
    • \(H_2 = (H_1 - F + 2P) / S +1\)
    • \(D_2 = K\)
  • 因为参数共享,每个filter有\(F \cdot F \cdot D1\)个权重参数,\(K\)个filter总共有\((F \cdot F \cdot D_1) \cdot K\)个权重参数与\(K\)个bias参数
  • 输出矩阵上第\(d\)个depth的slice上(即V[:, :, d],size为\(W_2 \times H_2\))的矩阵是由输入矩阵跟第\(d\)个filter作用产生的结果

通常超参数的的值会被设置为\(F = 3, S = 1, P = 1\)

卷积示例 下面是一个卷积层的示例。输入数据的3个通道被表示为3个矩阵(下面的 x[:, :, 0], x[:, :, 1], x[:, :, 2]),输入的size大小为\(W_1 = 5, H_1 = 5, D_1 = 3\),卷积层的超参数是\(K = 2, F = 3, S = 2, P = 1\)。即有两个width x height大小为[3x3],depth = 3的filter,分别为\(W_0, W_1\)。输出的空间size为(5 - 3 + 2)/2 + 1 = 3.输出的depth = 2,因为有两个filter。所以最后的输出是两个3x3的矩阵。

alt text

采用矩阵相乘来计算 上面卷积层上的计算实际上是各个小矩阵元素相乘的和然后再加上bias,我们可以把它统一成一个大的矩阵相乘的表达式。

  1. 我们把输入矩阵的没一小块都拉长为一个列向量,记为im2col.比如输入为[227x227x3],filter为[11x11x3],步长为4。我们从输入矩阵上拿出[11x11x3]的小块然后拉长为一个向量,向量的长度为11x11x3=363.每隔步长为4再拿出一小块[11x11x3]的矩阵做这样的拉长,在width和height方向上分别有(227 - 11)/4 + 1 = 55个小矩阵(所以总共有55x55=3025)个[11x11x3]的小矩阵。把3025个小矩阵都拉长为长度363的向量,放在一起就变成了[363x3025]的矩阵。把输入矩阵通过im2col拉长以后变成的矩阵记为X_col
  2. 把卷积层上的权重也拉成向量。比如有96个size为[11x11x3]的filter,这样得到一个矩阵记为W_row,大小为[96x363]
  3. 这样卷积就可以表示为矩阵点乘np.dot(W_row, X_col),结果为[96x3025]的矩阵
  4. 最后需要把[96x3025]的矩阵reshape成[55x55x96]

上面的办法简单易懂,但是会比较费内存,因为在把输入矩阵拉成向量的时候有很多元素是重复出现的。这就增加了内存的消耗。但是好处是我们有非常快速的算法来计算,而且同样的im2col想法可以用在下面的pooling里面。

Backpropagation(反向传播) 在最小化损失函数的时候,通常使用迭代的办法,沿着梯度方向慢慢收敛。要计算梯度就必须计算偏导数,因为损失函数通常是函数的函数,或者更多的函数复合起来的函数,所以需要使用链式规则(chain rules)层层展开。反向传播其实就是使用链式规则求偏导数。

Pooling Layer (sub-sampling)

通常在一个卷积层做好后,把输出用来作为新的输入,再做下一个卷积层之前,都会加上pooling层(抽样),这样可以减少参数的个数(输入矩阵变小了),也就相应的减少了overfitting.在实际当中的意义通常是:各个卷积层的目的是从输入图片中寻找到某些feature,通过抽样会使卷积层更多的去关注输入的图片的features,而不是去关注那些太detail的东西。因为太关注detail通常会导致模型过分拟合。 pooling层通常做的就是max-pooling:把输入矩阵选一小块(通常见到的是[2x2]的小矩阵),然后只保留这一小块(4个数字里面)的最大的那个值。一般情况下,pooling层有如下性质:

  • 输入的矩阵的大小为\(W_1 \times H_1 \times D1\)
  • 需要两个超参数
    • 他们的空间大小F
    • 步长S
  • 长生一个大小为\(W_2 \times H_2 \times D2\)的矩阵,其中
    • \(W_2 = (W_1 - F)/S +1\)
    • \(H_2 = (H_1 - F)/S + 1\)
    • \(D2 = D1\)
  • 这一步没有新的参数,因为只是抽样
  • 通常也不会使用0填充

通常见到比较多的是\(F = 2, S=2\),或者\(F = 3, S = 2\).

下图是一个max pooling的示意图,从每个[2x2]的小区域里面选取最大的一个数,所以输出的矩阵大小是输入的矩阵大小的一半。 alt text

Normalization Layer / 正则层

常见的有batch normalization和layer normalization。尽管stanford那个教程说normalization现在渐渐不是那么流行了,但是我自己的经验是做了normalization还是有效果的。通过normalization以后,所有的数据都一致化了,这样的话做梯度下降的时候更stable。否则的话那些绝对数值过大的数据会影响梯度下降的效果。

Fully-connected Layer / 全连接层

跟通常神经网络一样,全连接层里面的神经元跟输入数据的每个点都相连。所以全连接层可以看作是一个矩阵相乘,然后加上bias。

卷积神经网络结构

卷积神经网络通常由这三层构成:conv层,pooling层和FC层。

各层结构

常见的卷积神经网络是由CONV-RELU层累加在一起,然后跟着pooling层,然后重复这个结构,最后是FC层。就是

INPUT -> [[CONV -> relu]*N -> POOL?]*M -> [FC -> RELU]*k -> FC

这里的*表示重复,POOL?表示可选,通常0 <= N <= 3, M >= 0, 0 <= K < 3

下面是几个常见个卷积网络

  • INPUT -> FC, 这是一个线性分类器. 其中 N = M = K = 0.
  • INPUT -> CONV -> RELU -> FC
  • INPUT -> [CONV -> RELU -> POOL]*2 -> FC -> RELU -> FC. 每个CONV层后面都有一个POOL层.
  • INPUT -> [CONV -> RELU -> CONV -> RELU -> POOL]*3 -> [FC -> RELU]*2 -> FC 在POOL层之前有两个CONV层迭加。这样可以建立一些深度比较深的网络,因为不同的卷积层迭加可以用来描述比较复杂的features

最后用VGG16作为一个例子来帮助解释上面卷积神经网络的讲解:

VGG16曾经取得过很好的成绩,一直到现在还是被很多人使用。它的方法很简单,每次的卷积filter的width和height都是[3x3]。简单高效。

下面是各层的说明

  • lambda_1:读进去的输入data的大小,224x224,3个通道
  • zeropadding2d_1:在输入数据外面进行0填充,P=1,P是超参数,这一步没有模型需要fit的参数。
  • convolution2d_1:第一个卷积层,filter的size是3x3x3,bias有一个参数,所以每个filter有28个参数,总计64个filter,所以参数个数为28x64=1792
  • zeropadding2d_2:0填充,P=1,没有需要fit的参数
  • convolution2d_2:第二个卷积,filter的size是3x3x64,bias有一个参数,所以每个filter参数为577,共计64个filter,所以参数个数为577x64=36928
  • maxpooling2d_1: 对每个2x2的矩阵抽样,选取最大的值。这一步没有参数,但是因为抽样,所以输出矩阵的size变为[64x112x112]
  • ......如此反复
  • flatten_1: 把前一层maxpooling2d_5进行拉直,所以元素的个数为512x7x7=25088
  • dense_1: 是一个FC层,所以与输入的每个元素都相连,输出为一个4096的向量,所以总共的参数是(25088+1)x4096=102764544个
  • dropout_1:直接drop掉一部分数据,这样做的原因也是防止overfitting

具体的每层结构详细情况如下:

import keras
import vgg16
from vgg16 import Vgg16
vgg = Vgg16()
vgg.model.summary()
____________________________________________________________________________________________________
Layer (type)                     Output Shape          Param #     Connected to                     
====================================================================================================
lambda_1 (Lambda)                (None, 3, 224, 224)   0           lambda_input_1[0][0]             
____________________________________________________________________________________________________
zeropadding2d_1 (ZeroPadding2D)  (None, 3, 226, 226)   0           lambda_1[0][0]                   
____________________________________________________________________________________________________
convolution2d_1 (Convolution2D)  (None, 64, 224, 224)  1792        zeropadding2d_1[0][0]            
____________________________________________________________________________________________________
zeropadding2d_2 (ZeroPadding2D)  (None, 64, 226, 226)  0           convolution2d_1[0][0]            
____________________________________________________________________________________________________
convolution2d_2 (Convolution2D)  (None, 64, 224, 224)  36928       zeropadding2d_2[0][0]            
____________________________________________________________________________________________________
maxpooling2d_1 (MaxPooling2D)    (None, 64, 112, 112)  0           convolution2d_2[0][0]            
____________________________________________________________________________________________________
zeropadding2d_3 (ZeroPadding2D)  (None, 64, 114, 114)  0           maxpooling2d_1[0][0]             
____________________________________________________________________________________________________
convolution2d_3 (Convolution2D)  (None, 128, 112, 112) 73856       zeropadding2d_3[0][0]            
____________________________________________________________________________________________________
zeropadding2d_4 (ZeroPadding2D)  (None, 128, 114, 114) 0           convolution2d_3[0][0]            
____________________________________________________________________________________________________
convolution2d_4 (Convolution2D)  (None, 128, 112, 112) 147584      zeropadding2d_4[0][0]            
____________________________________________________________________________________________________
maxpooling2d_2 (MaxPooling2D)    (None, 128, 56, 56)   0           convolution2d_4[0][0]            
____________________________________________________________________________________________________
zeropadding2d_5 (ZeroPadding2D)  (None, 128, 58, 58)   0           maxpooling2d_2[0][0]             
____________________________________________________________________________________________________
convolution2d_5 (Convolution2D)  (None, 256, 56, 56)   295168      zeropadding2d_5[0][0]            
____________________________________________________________________________________________________
zeropadding2d_6 (ZeroPadding2D)  (None, 256, 58, 58)   0           convolution2d_5[0][0]            
____________________________________________________________________________________________________
convolution2d_6 (Convolution2D)  (None, 256, 56, 56)   590080      zeropadding2d_6[0][0]            
____________________________________________________________________________________________________
zeropadding2d_7 (ZeroPadding2D)  (None, 256, 58, 58)   0           convolution2d_6[0][0]            
____________________________________________________________________________________________________
convolution2d_7 (Convolution2D)  (None, 256, 56, 56)   590080      zeropadding2d_7[0][0]            
____________________________________________________________________________________________________
maxpooling2d_3 (MaxPooling2D)    (None, 256, 28, 28)   0           convolution2d_7[0][0]            
____________________________________________________________________________________________________
zeropadding2d_8 (ZeroPadding2D)  (None, 256, 30, 30)   0           maxpooling2d_3[0][0]             
____________________________________________________________________________________________________
convolution2d_8 (Convolution2D)  (None, 512, 28, 28)   1180160     zeropadding2d_8[0][0]            
____________________________________________________________________________________________________
zeropadding2d_9 (ZeroPadding2D)  (None, 512, 30, 30)   0           convolution2d_8[0][0]            
____________________________________________________________________________________________________
convolution2d_9 (Convolution2D)  (None, 512, 28, 28)   2359808     zeropadding2d_9[0][0]            
____________________________________________________________________________________________________
zeropadding2d_10 (ZeroPadding2D) (None, 512, 30, 30)   0           convolution2d_9[0][0]            
____________________________________________________________________________________________________
convolution2d_10 (Convolution2D) (None, 512, 28, 28)   2359808     zeropadding2d_10[0][0]           
____________________________________________________________________________________________________
maxpooling2d_4 (MaxPooling2D)    (None, 512, 14, 14)   0           convolution2d_10[0][0]           
____________________________________________________________________________________________________
zeropadding2d_11 (ZeroPadding2D) (None, 512, 16, 16)   0           maxpooling2d_4[0][0]             
____________________________________________________________________________________________________
convolution2d_11 (Convolution2D) (None, 512, 14, 14)   2359808     zeropadding2d_11[0][0]           
____________________________________________________________________________________________________
zeropadding2d_12 (ZeroPadding2D) (None, 512, 16, 16)   0           convolution2d_11[0][0]           
____________________________________________________________________________________________________
convolution2d_12 (Convolution2D) (None, 512, 14, 14)   2359808     zeropadding2d_12[0][0]           
____________________________________________________________________________________________________
zeropadding2d_13 (ZeroPadding2D) (None, 512, 16, 16)   0           convolution2d_12[0][0]           
____________________________________________________________________________________________________
convolution2d_13 (Convolution2D) (None, 512, 14, 14)   2359808     zeropadding2d_13[0][0]           
____________________________________________________________________________________________________
maxpooling2d_5 (MaxPooling2D)    (None, 512, 7, 7)     0           convolution2d_13[0][0]           
____________________________________________________________________________________________________
flatten_1 (Flatten)              (None, 25088)         0           maxpooling2d_5[0][0]             
____________________________________________________________________________________________________
dense_1 (Dense)                  (None, 4096)          102764544   flatten_1[0][0]                  
____________________________________________________________________________________________________
dropout_1 (Dropout)              (None, 4096)          0           dense_1[0][0]                    
____________________________________________________________________________________________________
dense_2 (Dense)                  (None, 4096)          16781312    dropout_1[0][0]                  
____________________________________________________________________________________________________
dropout_2 (Dropout)              (None, 4096)          0           dense_2[0][0]                    
____________________________________________________________________________________________________
dense_3 (Dense)                  (None, 1000)          4097000     dropout_2[0][0]                  
====================================================================================================
Total params: 138357544
____________________________________________________________________________________________________