深度学习入门:卷积神经网络
目录
- 1、整体结构
- 2、卷积层
- 2.1 全连接层存在的问题
- 2.2 卷积运算
- 2.3 填充
- 2.4 步幅
- 2.5 3维数据的卷积运算
- 2.6 结合方块思考
- 2.7 批处理
- 3、池化层
- 4、卷积层和池化层的实现
- 4.1 4维数组
- 4.2 基于im2col的展开
- 4.3 卷积层的实现
- 4.4 池化层的实现
- 5、CNN的实现
- 6、CNN的可视化
- 6.1 第一层权重的可视化
- 6.2 基于分层结构的信息提取
- 7、具有代表性的CNN
- 7.1 LeNet
- 7.2 AlexNet
- 8、小结
1、整体结构
之前介绍的神经网络中,相邻层的所有神经元之间都有连接,这称为全连接(fully-connected)。另外,我们用Affine层实现了全连接层。如果使用这个Affine层,一个5层的全连接的神经网络就可以通过图所示的网络结构来实现。
CNN中新出现了卷积层(Convolution层)和池化层(Pooling层)。
CNN 中新增了 Convolution 层 和 Pooling 层。CNN 的层的连接顺序是“Convolution - ReLU -(Pooling)”(Pooling层有时会被省略)。这可以理解为之前的“Affi ne - ReLU”连接被替换成了“Convolution - ReLU -(Pooling)”连接。还需要注意的是,在CNN中,靠近输出的层中使用了之前的“Affi ne - ReLU”组合。此外,最后的输出层中使用了之前的“Affi ne - Softmax”组合。这些都是一般的CNN中比较常见的结构。
2、卷积层
2.1 全连接层存在的问题
全连接层存在什么问题呢?那就是数据的形状被“忽视”了。比如,输入数据是图像时,图像通常是高、长、通道方向上的3维形状。但是,向全连接层输入时,需要将3维数据拉平为1维数据。实际上,前面提到的使用了MNIST数据集的例子中,输入图像就是1通道、高28像素、长28像素的(1, 28, 28)形状,但却被排成1列,以784个数据的形式输入到最开始的Affine层。
图像是3维形状,这个形状中应该含有重要的空间信息。比如,空间上邻近的像素为相似的值、RBG的各个通道之间分别有密切的关联性、相距较远的像素之间没有什么关联等,3维形状中可能隐藏有值得提取的本质模式。但是,因为全连接层会忽视形状,将全部的输入数据作为相同的神经元(同一维度的神经元)处理,所以无法利用与形状相关的信息。
而卷积层可以保持形状不变。当输入数据是图像时,卷积层会以3维数据的形式接收输入数据,并同样以3维数据的形式输出至下一层。因此,在CNN中,可以(有可能)正确理解图像等具有形状的数据。
另外,CNN 中,有时将卷积层的输入输出数据称为特征图(feature map)。其中,卷积层的输入数据称为输入特征图(input feature map),输出数据称为输出特征图(output feature map)。
2.2 卷积运算
卷积层进行的处理就是卷积运算。卷积运算相当于图像处理中的“滤波器运算”。
对于输入数据,卷积运算以一定间隔滑动滤波器的窗口并应用。这里所说的窗口是指下图中灰色的3 × 3的部分。如下图所示,将各个位置上滤波器的元素和输入的对应元素相乘,然后再求和(有时将这个计算称为乘积累加运算)。然后,将这个结果保存到输出的对应位置。将这个过程在所有位置都进行一遍,就可以得到卷积运算的输出。
在全连接的神经网络中,除了权重参数,还存在偏置。CNN中,滤波器的参数就对应之前的权重。并且,CNN中也存在偏置。
如图所示,向应用了滤波器的数据加上了偏置。偏置通常只有1个(1 × 1)(本例中,相对于应用了滤波器的4个数据,偏置只有1个),这个值会被加到应用了滤波器的所有元素上。
2.3 填充
在进行卷积层的处理之前,有时要向输入数据的周围填入固定的数据(比如0等),这称为填充(padding),是卷积运算中经常会用到的处理。比如,在如图的例子中,对大小为(4, 4)的输入数据应用了幅度为1(幅度可以为任意整数值)的填充。“幅度为1的填充”是指用幅度为1像素的0填充周围。
使用填充主要是为了调整输出的大小。比如,对大小为(4, 4)的输入数据应用(3, 3)的滤波器时,输出大小变为(2, 2),相当于输出大小比输入大小缩小了 2个元素。这在反复进行多次卷积运算的深度网络中会成为问题。为什么呢?因为如果每次进行卷积运算都会缩小空间,那么在某个时刻输出大小就有可能变为 1,导致无法再应用卷积运算。为了避免出现这样的情况,就要使用填充。在刚才的例子中,将填充的幅度设为 1,那么相对于输入大小(4, 4),输出大小也保持为原来的(4, 4)。因此,卷积运算就可以在保持空间大小不变的情况下将数据传给下一层。
2.4 步幅
应用滤波器的位置间隔称为步幅(stride)。之前的例子中步幅都是1,如果将步幅设为2,则如图所示,应用滤波器的窗口的间隔变为2个元素。
综上,增大步幅后,输出大小会变小。而增大填充后,输出大小会变大。如果将这样的关系写成算式,会如何呢?接下来,我们看一下对于填充和步幅,如何计算输出大小。
这里,假设输入大小为(H, W),滤波器大小为(FH, FW),输出大小为(OH, OW),填充为P,步幅为S。此时,输出大小可通过下式进行计算。
如这些例子所示,通过在式中代入值,就可以计算输出大小。这里需要注意的是,虽然只要代入值就可以计算输出大小,但是所设定的值必须使式中的答案分别可以除尽。当输出大小无法除尽时(结果是小数时),需要采取报错等对策。顺便说一下,根据深度学习的框架的不同,当值无法除尽时,有时会向最接近的整数四舍五入,不进行报错而继续运行。
2.5 3维数据的卷积运算
之前的卷积运算的例子都是以有高、长方向的2维形状为对象的。但是,图像是3维数据,除了高、长方向之外,还需要处理通道方向。这里,我们按照与之前相同的顺序,看一下对加上了通道方向的3维数据进行卷积运算的例子。
图1是卷积运算的例子,图2是计算顺序。这里以3通道的数据为例,展示了卷积运算的结果。和2维数据时相比,可以发现纵深方向(通道方向)上特征图增加了。通道方向上有多个特征图时,会按通道进行输入数据和滤波器的卷积运算,并将结果相加,从而得到输出。
需要注意的是,在3维数据的卷积运算中,输入数据和滤波器的通道数要设为相同的值。滤波器大小可以设定为任意值(不过,每个通道的滤波器大小要全部相同)。
2.6 结合方块思考
将数据和滤波器结合长方体的方块来考虑,3维数据的卷积运算会很容易理解。方块是如图所示的3维长方体。把3维数据表示为多维数组时,书写顺序为(channel, height, width)。比如,通道数为C、高度为H、长度为W的数据的形状可以写成(C, H, W)。滤波器也一样,要按(channel, height, width)的顺序书写。比如,通道数为C、滤波器高度为FH(Filter Height)、长度为FW(Filter Width)时,可以写成(C, FH, FW)。
在这个例子中,数据输出是1张特征图。所谓1张特征图,换句话说,就是通道数为1的特征图。那么,如果要在通道方向上也拥有多个卷积运算的输出,该怎么做呢?为此,就需要用到多个滤波器(权重)。用图表示的话,如图所示。
图中,通过应用FN个滤波器,输出特征图也生成了FN个。如果将这FN个特征图汇集在一起,就得到了形状为(FN, OH, OW)的方块。将这个方块传给下一层,就是CNN的处理流。
如图所示,关于卷积运算的滤波器,也必须考虑滤波器的数量。因此,作为4维数据,滤波器的权重数据要按(output_channel, input_channel, height, width)的顺序书写。比如,通道数为3、大小为5 × 5的滤波器有20个时,可以写成(20, 3, 5, 5)。
卷积运算中(和全连接层一样)存在偏置。在该例子中,如果进一步追加偏置的加法运算处理,则结果如下面的图所示。
图中,每个通道只有一个偏置。这里,偏置的形状是(FN, 1, 1),滤波器的输出结果的形状是(FN, OH, OW)。这两个方块相加时,要对滤波器的输出结果(FN, OH, OW)按通道加上相同的偏置值。另外,不同形状的方块相加时,可以基于NumPy的广播功能轻松实现。
2.7 批处理
神经网络的处理中进行了将输入数据打包的批处理。之前的全连接神经网络的实现也对应了批处理,通过批处理,能够实现处理的高效化和学习时对mini-batch的对应。
我们希望卷积运算也同样对应批处理。为此,需要将在各层间传递的数据保存为4维数据。具体地讲,就是按(batch_num, channel, height, width)的顺序保存数据。
图中的批处理版的数据流中,在各个数据的开头添加了批用的维度。像这样,数据作为4维的形状在各层间传递。这里需要注意的是,网络间传递的是4维数据,对这N个数据进行了卷积运算。也就是说,批处理将N次的处理汇总成了1次进行。(一次运算N个数据)
3、池化层
池化是缩小高、长方向上的空间的运算。比如,如图所示,进行将2 × 2的区域集约成1个元素的处理,缩小空间大小。
该例子是按步幅2进行2 × 2的Max池化时的处理顺序。“Max池化”是获取最大值的运算,“2 × 2”表示目标区域的大小。如图所示,从2 × 2的区域中取出最大的元素。此外,这个例子中将步幅设为了2,所以2 × 2的窗口的移动间隔为2个元素。另外,一般来说,池化的窗口大小会和步幅设定成相同的值。比如,3 × 3的窗口的步幅会设为3,4 × 4的窗口的步幅会设为4等。
池化层有以下特征:
①没有要学习的参数:
池化层和卷积层不同,没有要学习的参数。池化只是从目标区域中取最大值(或者平均值),所以不存在要学习的参数。
②通道数不发生变化:
经过池化运算,输入数据和输出数据的通道数不会发生变化。如图所示,计算是按通道独立进行的。
对微小的位置变化具有鲁棒性(健壮)
输入数据发生微小偏差时,池化仍会返回相同的结果。因此,池化对输入数据的微小偏差具有鲁棒性。比如,3 × 3的池化的情况下,如图所示,池化会吸收输入数据的偏差(根据数据的不同,结果有可能不一致)。
4、卷积层和池化层的实现
4.1 4维数组
如前所述,CNN中各层间传递的数据是4维数据。所谓4维数据,比如数据的形状是(10, 1, 28, 28),则它对应10个高为28、长为28、通道为1的数据。用Python来实现的话,如下所示。
>>> x = np.random.rand(10, 1, 28, 28) # 随机生成数据
>>> x.shape
(10, 1, 28, 28)
这里,如果要访问第1个数据,只要写x[0]就可以了(注意Python的索引是从0开始的)。同样地,用x[1]可以访问第2个数据。
>>> x[0].shape # (1, 28, 28)
>>> x[1].shape # (1, 28, 28)
如果要访问第1个数据的第1个通道的空间数据,可以写成下面这样。
>>> x[0, 0] # 或者x[0][0]
像这样,CNN中处理的是4维数据,因此卷积运算的实现看上去会很复杂,但是通过使用下面要介绍的im2col这个技巧,问题就会变得很简单。
4.2 基于im2col的展开
im2col是一个函数,将输入数据展开以适合滤波器(权重)。如图所示,对3维的输入数据应用im2col后,数据转换为2维矩阵(正确地讲,是把包含批数量的4维数据转换成了2维数据)。
im2col会把输入数据展开以适合滤波器(权重)。具体地说,如图所示,对于输入数据,将应用滤波器的区域(3维方块)横向展开为1列。im2col会在所有应用滤波器的地方进行这个展开处理。
为了便于观察,将步幅设置得很大,以使滤波器的应用区域不重叠。而在实际的卷积运算中,滤波器的应用区域几乎都是重叠的。在滤波器的应用区域重叠的情况下,使用im2col展开后,展开后的元素个数会多于原方块的元素个数。因此,使用im2col的实现存在比普通的实现消耗更多内存的缺点。但是,汇总成一个大的矩阵进行计算,对计算机的计算颇有益处。比如,在矩阵计算的库(线性代数库)等中,矩阵计算的实现已被高度最优化,可以高速地进行大矩阵的乘法运算。因此,通过归结到矩阵计算上,可以有效地利用线性代数库。
im2col这个名称是“image to column”的缩写,翻译过来就是“从图像到矩阵”的意思。Caffe、Chainer 等深度学习框架中有名为im2col的函数,并且在卷积层的实现中,都使用了im2col。
使用im2col展开输入数据后,之后就只需将卷积层的滤波器(权重)纵向展开为1列,并计算2个矩阵的乘积即可。这和全连接层的Affi ne层进行的处理基本相同。如图所示,基于im2col方式的输出结果是2维矩阵。因为CNN中数据会保存为4维数组,所以要将2维输出数据转换为合适的形状。以上就是卷积层的实现流程。
4.3 卷积层的实现
im2col这一便捷函数具有以下接口。
im2col (input_data, filter_h, filter_w, stride=1, pad=0)
• input_data―由(数据量,通道,高,长)的4维数组构成的输入数据
• filter_h―滤波器的高
• filter_w―滤波器的长
• stride―步幅
• pad―填充
im2col会考虑滤波器大小、步幅、填充,将输入数据展开为2维数组。现在,
我们来实际使用一下这个im2col。
import sys, os
sys.path.append(os.pardir)
from common.util import im2colx1 = np.random.rand(1, 3, 7, 7)
col1 = im2col(x1, 5, 5, stride=1, pad=0)
print(col1.shape) # (9, 75)x2 = np.random.rand(10, 3, 7, 7) # 10个数据
col2 = im2col(x2, 5, 5, stride=1, pad=0)
print(col2.shape) # (90, 75)
这里举了两个例子。第一个是批大小为1、通道为3的7 × 7的数据,第二个的批大小为10,数据形状和第一个相同。分别对其应用im2col函数,在这两种情形下,第2维的元素个数均为75。这是滤波器(通道为3、大小为5 × 5)的元素个数的总和。批大小为1时,im2col的结果是(9, 75)。而第2个例子中批大小为10,所以保存了10倍的数据,即(90, 75)。现在使用im2col来实现卷积层。这里我们将卷积层实现为名为Convolution的类。
class Convolution:def __init__(self, W, b, stride=1, pad=0):self.W = Wself.b = bself.stride = strideself.pad = paddef forward(self, x):FN, C, FH, FW = self.W.shapeN, C, H, W = x.shapeout_h = int(1 + (H + 2*self.pad - FH) / self.stride)out_w = int(1 + (W + 2*self.pad - FW) / self.stride)col = im2col(x, FH, FW, self.stride, self.pad)col_W = self.W.reshape(FN, -1).T # 滤波器的展开out = np.dot(col, col_W) + self.bout = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)return out
卷积层的初始化方法将滤波器(权重)、偏置、步幅、填充作为参数接收。滤波器是 (FN, C, FH, FW)的 4 维形状。另外,FN、C、FH、FW分别是 Filter Number(滤波器数量)、Channel、Filter Height、Filter Width的缩写。这里用粗体字表示Convolution层的实现中的重要部分。在这些粗体字部分,用im2col展开输入数据,并用reshape将滤波器展开为2维数组。然后,计算展开后的矩阵的乘积。
展开滤波器的部分(代码段中的粗体字)如上图所示,将各个滤波器的方块纵向展开为1列。这里通过reshape(FN,-1)将参数指定为-1,这是reshape的一个便利的功能。通过在reshape时指定为-1,reshape函数会自动计算-1维度上的元素个数,以使多维数组的元素个数前后一致。比如,(10, 3, 5,5)形状的数组的元素个数共有750个,指定reshape(10,-1)后,就会转换成(10, 75)形状的数组。
forward的实现中,最后会将输出大小转换为合适的形状。转换时使用了NumPy的transpose函数。transpose会更改多维数组的轴的顺序。如图所示,通过指定从0开始的索引(编号)序列,就可以更改轴的顺序。
4.4 池化层的实现
池化层的实现和卷积层相同,都使用im2col展开输入数据。不过,池化的情况下,在通道方向上是独立的,这一点和卷积层不同。具体地讲,如图所示,池化的应用区域按通道单独展开。
像这样展开之后,只需对展开的矩阵求各行的最大值,并转换为合适的形状即可。
上面就是池化层的forward处理的实现流程。下面来看一下Python的实现示例。
class Pooling:def __init__(self, pool_h, pool_w, stride=1, pad=0):self.pool_h = pool_hself.pool_w = pool_wself.stride = strideself.pad = paddef forward(self, x):N, C, H, W = x.shapeout_h = int(1 + (H - self.pool_h) / self.stride)out_w = int(1 + (W - self.pool_w) / self.stride)# 展开(1)col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)col = col.reshape(-1, self.pool_h*self.pool_w)# 最大值(2)out = np.max(col, axis=1)# 转换(3)out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)return out
如上图所示,池化层的实现按下面3个阶段进行。
1.展开输入数据。
2.求各行的最大值。
3.转换为合适的输出大小。
最大值的计算可以使用 NumPy 的 np.max方法。np.max可以指定axis参数,并在这个参数指定的各个轴方向上求最大值。比如,如果写成np.max(x, axis=1),就可以在输入x的第1维的各个轴方向上求最大值。
5、CNN的实现
如图所示,网络的构成是“Convolution - ReLU - Pooling -Affine - ReLU - Affine - Softmax”,我们将它实现为名为SimpleConvNet的类。首先来看一下SimpleConvNet的初始化(init),取下面这些参数。
参数
• input_dim―输入数据的维度:(通道,高,长)
• conv_param―卷积层的超参数(字典)。字典的关键字如下:
filter_num―滤波器的数量
filter_size―滤波器的大小
stride―步幅
pad―填充
• hidden_size―隐藏层(全连接)的神经元数量
• output_size―输出层(全连接)的神经元数量
• weitght_int_std―初始化时权重的标准差
这里,卷积层的超参数通过名为conv_param的字典传入。我们设想它会像{‘filter_num’:30,‘filter_size’:5, ‘pad’:0, ‘stride’:1}这样,保存必要的超参数值。
SimpleConvNet的初始化的实现稍长,我们分成3部分来说明,首先是初始化的最开始部分。
class SimpleConvNet:def __init__(self, input_dim=(1, 28, 28),conv_param={'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1},hidden_size=100, output_size=10, weight_init_std=0.01):filter_num = conv_param['filter_num']filter_size = conv_param['filter_size']filter_pad = conv_param['pad']filter_stride = conv_param['stride']input_size = input_dim[1]conv_output_size = (input_size - filter_size + 2*filter_pad) / \filter_stride + 1pool_output_size = int(filter_num * (conv_output_size/2) *(conv_output_size/2))
这里将由初始化参数传入的卷积层的超参数从字典中取了出来(以方便后面使用),然后,计算卷积层的输出大小。接下来是权重参数的初始化部分。
self.params = {}
self.params['W1'] = weight_init_std * \np.random.randn(filter_num, input_dim[0],filter_size, filter_size)
self.params['b1'] = np.zeros(filter_num)
self.params['W2'] = weight_init_std * \np.random.randn(pool_output_size,hidden_size)
self.params['b2'] = np.zeros(hidden_size)
self.params['W3'] = weight_init_std * \np.random.randn(hidden_size, output_size)
self.params['b3'] = np.zeros(output_size)
学习所需的参数是第1层的卷积层和剩余两个全连接层的权重和偏置。将这些参数保存在实例变量的params字典中。将第1层的卷积层的权重设为关键字W1,偏置设为关键字b1。同样,分别用关键字W2、b2和关键字W3、b3来保存第2个和第3个全连接层的权重和偏置。
最后,生成必要的层。
self.layers = OrderedDict()
self.layers['Conv1'] = Convolution(self.params['W1'],self.params['b1'],conv_param['stride'],conv_param['pad'])
self.layers['Relu1'] = Relu()
self.layers['Pool1'] = Pooling(pool_h=2, pool_w=2, stride=2)
self.layers['Affine1'] = Affine(self.params['W2'],self.params['b2'])
self.layers['Relu2'] = Relu()
self.layers['Affine2'] = Affine(self.params['W3'],self.params['b3'])
self.last_layer = softmaxwithloss()
从最前面开始按顺序向有序字典(OrderedDict)的layers中添加层。只有最后的SoftmaxWithLoss层被添加到别的变量lastLayer中。
以上就是SimpleConvNet的初始化中进行的处理。像这样初始化后,进行推理的predict方法和求损失函数值的loss方法就可以像下面这样实现。
def predict(self, x):for layer in self.layers.values():x = layer.forward(x)return xdef loss(self, x, t):y = self.predict(x)return self.lastLayer.forward(y, t)
这里,参数x是输入数据,t是教师标签。用于推理的predict方法从头开始依次调用已添加的层,并将结果传递给下一层。在求损失函数的loss方法中,除了使用 predict方法进行的 forward处理之外,还会继续进行forward处理,直到到达最后的SoftmaxWithLoss层。
接下来是基于误差反向传播法求梯度的代码实现。
def gradient(self, x, t):# forwardself.loss(x, t)# backwarddout = 1dout = self.lastLayer.backward(dout)layers = list(self.layers.values())layers.reverse()for layer in layers:dout = layer.backward(dout)# 设定grads = {}grads['W1'] = self.layers['Conv1'].dWgrads['b1'] = self.layers['Conv1'].dbgrads['W2'] = self.layers['Affine1'].dWgrads['b2'] = self.layers['Affine1'].dbgrads['W3'] = self.layers['Affine2'].dWgrads['b3'] = self.layers['Affine2'].dbreturn grads
参数的梯度通过误差反向传播法(反向传播)求出,通过把正向传播和反向传播组装在一起来完成。因为已经在各层正确实现了正向传播和反向传播的功能,所以这里只需要以合适的顺序调用即可。最后,把各个权重参数的梯度保存到grads字典中。这就是SimpleConvNet的实现。
6、CNN的可视化
CNN中用到的卷积层在“观察”什么呢?本节将通过卷积层的可视化,探索CNN中到底进行了什么处理。
6.1 第一层权重的可视化
刚才我们对MNIST数据集进行了简单的CNN学习。当时,第1层的卷积层的权重的形状是(30, 1, 5, 5),即30个大小为5 × 5、通道为1的滤波器。滤波器大小是5 × 5、通道数是1,意味着滤波器可以可视化为1通道的灰度图像。现在,我们将卷积层(第1层)的滤波器显示为图像。这里,我们来比较一下学习前和学习后的权重,结果如图所示。
图中,学习前的滤波器是随机进行初始化的,所以在黑白的浓淡上没有规律可循,但学习后的滤波器变成了有规律的图像。我们发现,通过学习,滤波器被更新成了有规律的滤波器,比如从白到黑渐变的滤波器、含有块状区域(称为blob)的滤波器等。
如果要问上图中右边的有规律的滤波器在“观察”什么,答案就是它在观察边缘(颜色变化的分界线)和斑块(局部的块状区域)等。比如,左半部分为白色、右半部分为黑色的滤波器的情况下,如下图所示,会对垂直方向上的边缘有响应。
图中显示了选择两个学习完的滤波器对输入图像进行卷积处理时的结果。我们发现“滤波器1”对垂直方向上的边缘有响应,“滤波器2”对水平方向上的边缘有响应。由此可知,卷积层的滤波器会提取边缘或斑块等原始信息。而刚才实现的CNN会将这些原始信息传递给后面的层。
6.2 基于分层结构的信息提取
上面的结果是针对第1层的卷积层得出的。第1层的卷积层中提取了边缘或斑块等“低级”信息,那么在堆叠了多层的CNN中,各层中又会提取什么样的信息呢?根据深度学习的可视化相关的研究,随着层次加深,提取的信息(正确地讲,是反映强烈的神经元)也越来越抽象。
图中展示了进行一般物体识别(车或狗等)的8层CNN。这个网络结构的名称是下一节要介绍的AlexNet。AlexNet网络结构堆叠了多层卷积层和池化层,最后经过全连接层输出结果。图的方块表示的是中间数据,对于这些中间数据,会连续应用卷积运算。
如图所示,如果堆叠了多层卷积层,则随着层次加深,提取的信息也愈加复杂、抽象,这是深度学习中很有意思的一个地方。最开始的层对简单的边缘有响应,接下来的层对纹理有响应,再后面的层对更加复杂的物体部件有响应。也就是说,随着层次加深,神经元从简单的形状向“高级”信息变化。换句话说,就像我们理解东西的“含义”一样,响应的对象在逐渐变化。
7、具有代表性的CNN
关于CNN,迄今为止已经提出了各种网络结构。这里,我们介绍其中特别重要的两个网络,一个是在1998年首次被提出的CNN元祖LeNet,另一个是在深度学习受到关注的2012年被提出的AlexNet。
7.1 LeNet
LeNet在1998年被提出,是进行手写数字识别的网络。如图所示,它有连续的卷积层和池化层(正确地讲,是只“抽选元素”的子采样层),最后经全连接层输出结果。
和“现在的CNN”相比,LeNet有几个不同点。第一个不同点在于激活函数。LeNet中使用sigmoid函数,而现在的CNN中主要使用ReLU函数。此外,原始的LeNet中使用子采样(subsampling)缩小中间数据的大小,而现在的CNN中Max池化是主流。
7.2 AlexNet
AlexNet叠有多个卷积层和池化层,最后经由全连接层输出结果。虽然结构上AlexNet和LeNet没有大的不同,但有以下几点差异。
• 激活函数使用ReLU。
• 使用进行局部正规化的LRN(Local Response Normalization)层。
• 使用Dropout。
8、小结
• CNN在此前的全连接层的网络中新增了卷积层和池化层。
• 使用im2col函数可以简单、高效地实现卷积层和池化层。
• 通过CNN的可视化,可知随着层次变深,提取的信息愈加高级。
• LeNet和AlexNet是CNN的代表性网络。
• 在深度学习的发展中,大数据和GPU做出了很大的贡献。