前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >卷积神经网络(CNN)| 笔记 | 1

卷积神经网络(CNN)| 笔记 | 1

作者头像
yiyun
发布2022-04-01 16:30:13
1.2K0
发布2022-04-01 16:30:13
举报
文章被收录于专栏:yiyun 的专栏

卷积层

代码语言:javascript
复制
输入图片: 5*5*3

卷积核:   3*3*3
  - 卷积核有2个: Filter W0, Filter W1
  
偏置:    1*1*1
  - 偏置有2个:   Bias b0, Bias b1
  
卷积结果(Output Volumn): 3*3*2

步长(stride):    2

补充: 输入: 7∗7∗37∗7 是因为 pad = 1 (在图片边界行和列都补零,补零的行和的数目是1) 补零作用:提取图片边界的特征。7∗7 指 图片高h * 宽w 而之所以 ∗3 是因为,对于彩色图片,一般采用 RGB 3原色表示,也称 3通道。 Q: 卷积核深度为何设为 3 ? A: 因为输入 是 3通道,所以卷积核深度 必须与 输入的深度相同。 至于 卷积核宽w,高h 则是可变化的,但是宽高必须相等。 => w0:,:,0 * x:,:,0蓝色区域矩阵(R通道) + w0:,:,1 * x:,:,1蓝色区域矩阵(G通道)+ w0:,:,2 * x:,:,2蓝色区域矩阵(B通道) + b0(千万不能丢,因为 y = w * x + b) 第一项 => 0 * 1 + 0 * 1 + 0 * 1 + 0 * (-1) + 1 * (-1) + 1 * 0 + 0 * (-1) + 1 * 1 + 1 * 0 = 0 第二项 => 0 * (-1) + 0 * (-1) + 0 * 1 + 0 * (-1) + 0 * 1 + 1 * 0 + 0 * (-1) + 2 * 1 + 2 * 0 = 2 第三项 => 0 * 1 + 0 * 0 + 0 * (-1) + 0 * 0 + 2 * 0 + 2 * 0 + 0 * 1 + 0 * (-1) + 0 * (-1) = 0 卷积核输出o0,0,0 = > 第一项 + 第二项 + 第三项 + b0 = 0 + 2 + 0 + 1 = 3

全连接层存在的问题

之前介绍的全连接的神经网络中使用了全连接层(Affine层)。 在全连接 层中,相邻层的神经元全部连接在一起,输出的数量可以任意决定。

全连接层存在什么问题呢?

那就是数据的形状被“忽视”了。

比如,输 入数据是图像时,图像通常是高、长、通道方向上的3维形状。

但是,向全 连接层输入时,需要将3维数据拉平为1维数据。

实际上,前面提到的使用 了MNIST数据集的例子中,输入图像就是1通道、高28像素、长28像素 的(1, 28, 28)形状,但却被排成1列,以784个数据的形式输入到最开始的Affine层。

而卷积层可以保持形状不变。 当输入数据是图像时,卷积层会以3维 数据的形式接收输入数据,并同样以3维数据的形式输出至下一层。因此,在CNN中,可以(有可能)正确理解图像等具有形状的数据。 另外,CNN中, 有时将卷积层的输入输出数据称为特征图(feature map)。 其中,卷积层的输入数据称为输入特征图(input feature map),输出 数据称为输出特征图(output feature map)。 本书中将“输入输出数据”和“特征图”作为含义相同的词使用。

卷积运算

卷积层进行的处理就是卷积运算。

卷积运算相当于图像处理中的“滤波 器运算”。在介绍卷积运算时,我们来看一个具体的例子(图7-3)。

如图7-3所示,卷积运算对输入数据应用滤波器。 在这个例子中,输入 数据是有高长方向的形状的数据,滤波器也一样,有高长方向上的维度。 假 设用(height, width)表示数据和滤波器的形状,则在本例中,输入大小是 (4, 4),滤波器大小是(3, 3),输出大小是(2, 2)。另外,有的文献中也会用“核”这个词来表示这里所说的“滤波器”。

​ 对于输入数据,卷积运算以一定间隔滑动滤波器的窗口并应用。这里所 说的窗口是指图7-4中灰色的3 × 3的部分。如图7-4所示,将各个位置上滤波器的元素和输入的对应元素相乘,然后再求和(有时将这个计算称为 乘积累加运算 )。然后,将这个结果保存到输出的对应位置。将这个过程在所有位置都进行一遍,就可以得到卷积运算的输出。

​ 在全连接的神经网络中,除了权重参数,还存在偏置。CNN中,滤波器的参数就对应之前的权重。并且,CNN中也存在偏置。图7-3的卷积运算 的例子一直展示到了应用滤波器的阶段。包含偏置的卷积运算的处理流如图7-5所示。

如图7-5所示,向应用了滤波器的数据加上了偏置。 偏置通常只有1个 (1 × 1)(本例中,相对于应用了滤波器的4个数据,偏置只有1个),这个值会被加到应用了滤波器的所有元素上。

填充

在进行卷积层的处理之前, 有时要向输入数据的周围填入固定的数据(比 如0等),这称为 填充(padding), 是卷积运算中经常会用到的处理。 比如, 在图7-6的例子中, 对大小为(4, 4)的输入数据应用了幅度为1的填充。“幅度为1的填充”是指用幅度为1像素的0填充周围。

如图7-6所示, 通过填充,大小为(4, 4)的输入数据变成了(6, 6)的形状。 然后,应用大小为(3, 3)的滤波器,生成了大小为(4, 4)的输出数据。 这个例 子中将填充设成了1,不过填充的值也可以设置成2、3等任意的整数。 在图7-5 的例子中,如果将填充设为2,则输入数据的大小变为(8, 8);如果将填充设为3,则大小变为(10, 10)。

因此最后,输出 (4, 4)

注意: 使用填充主要是为了调整输出的大小。 比如,对大小为(4, 4)的输入 数据应用(3, 3)的滤波器时,输出大小变为(2, 2),相当于输出大小 比输入大小缩小了2个元素。 这在反复进行多次卷积运算的深度网络中会成为问题。 为什么呢?因为如果每次进行卷积运算都会缩小 空间,那么在某个时刻输出大小就有可能变为1,导致无法再应用 卷积运算。 为了避免出现这样的情况,就要使用填充。 在刚才的例 子中,将填充的幅度设为1,那么相对于输入大小(4, 4),输出大小 也保持为原来的(4, 4)。 因此,卷积运算就可以在保持空间大小不变的情况下将数据传给下一层。

步幅

应用滤波器的位置间隔称为 步幅(stride)

之前的例子中步幅都是1,如 果将步幅设为2,则如图7-7所示,应用滤波器的窗口的间隔变为2个元素。

在图7-7的例子中, 对输入大小为(7, 7)的数据,以步幅2应用了滤波器。 通过将步幅设为2,输出大小变为(3, 3)。像这样,步幅可以指定应用滤波器的间隔。 综上,增大步幅后,输出大小会变小。而增大填充后,输出大小会变大。 如果将这样的关系写成算式,会如何呢? 接下来,我们看一下对于填充和步幅,如何计算输出大小。

这里,假设输入大小为(H,W) ,滤波器大小为 (FH,FW) ,输出大小为 (OH,OW) ,填充为P,步幅为S。

此时,输出大小可通过式(7.1)进行计算。

现在,我们使用这个算式,试着做几个计算。

例1:图7-6的例子

输入大小:(4, 4);填充:1;步幅:1;滤波器大小:(3, 3)

例2:图7-7的例子

输入大小:(7, 7);填充:0;步幅:2;滤波器大小:(3, 3)

例3

输入大小:(28, 31);填充:2;步幅:3;滤波器大小:(5, 5)

3维数据的卷积运算

之前的卷积运算的例子都是以有高、长方向的2维形状为对象的。 但是, 图像是3维数据,除了高、长方向之外,还需要处理通道方向。 这里,我们按照与之前相同的顺序,看一下对加上了通道方向的3维数据进行卷积运算的例子。 图7-8是卷积运算的例子,图7-9是计算顺序。 这里以3通道的数据为例, 展示了卷积运算的结果。 和2维数据时(图7-3的例子)相比,可以发现纵深 方向(通道方向)上特征图增加了。 通道方向上有多个特征图时,会按通道进行输入数据和滤波器的卷积运算,并将结果相加,从而得到输出。

需要注意的是,在3维数据的卷积运算中,输入数据和滤波器的通道数 要设为相同的值。 在这个例子中,输入数据和滤波器的通道数一致,均为3。 滤波器大小可以设定为任意值(不过,每个通道的滤波器大小要全部相同)。 这个例子中滤波器大小为(3, 3),但也可以设定为(2, 2)、(1, 1)、(5, 5)等任 意值。 再强调一下,通道数只能设定为和输入数据的通道数相同的值(本例中为3)。

结合方块思考

将数据和滤波器结合长方体的方块来考虑,3维数据的卷积运算会很容易理解。

方块是如图7-10所示的3维长方体。把3维数据表示为多维数组 时,书写顺序为(channel, height, width)。

比如,通道数为C、高度为H、 长度为W的数据的形状可以写成(C, H,W)。

滤波器也一样,要按(channel, height, width)的顺序书写。

比如,通道数为C、滤波器高度为FH(FilterHeight)、长度为FW(Filter Width)时,可以写成(C, FH, FW)。

在这个例子中,数据输出是1张特征图。 所谓1张特征图,换句话说, 就是通道数为1的特征图。 那么,如果要在通道方向上也拥有多个卷积运算的输出,该怎么做呢? 为此,就需要用到多个滤波器(权重)。用图表示的话, 如图7-11所示。

图7-11中,通过应用FN个滤波器,输出特征图也生成了FN个。 如果 将这FN个特征图汇集在一起,就得到了形状为(FN, OH,OW)的方块。 将这个方块传给下一层,就是CNN的处理流。 如图7-11所示,关于卷积运算的滤波器,也必须考虑滤波器的数 量。 因此,作为4维数据,滤波器的权重数据要按(output_channel, input_ channel, height, width)的顺序书写。 比如,通道数为3、大小为5 × 5的滤波器有20个时,可以写成(20, 3, 5, 5)。 卷积运算中(和全连接层一样)存在偏置。 在图7-11的例子中,如果进 一步追加偏置的加法运算处理,则结果如下面的图7-12所示。 图7-12中,每个通道只有一个偏置。 这里,偏置的形状是(FN, 1, 1), 滤波器的输出结果的形状是(FN, OH,OW)。 这两个方块相加时,要对滤波 器的输出结果(FN, OH,OW)按通道加上相同的偏置值。 另外,不同形状的方块相加时,可以基于NumPy的广播功能轻松实现(1.5.5节)。

批处理

神经网络的处理中进行了将输入数据打包的批处理。 之前的全连接神经 网络的实现也对应了批处理,通过批处理,能够实现处理的高效化和学时对mini-batch的对应。 我们希望卷积运算也同样对应批处理。 为此,需要将在各层间传递的数 据保存为4维数据。 具体地讲,就是按(batch_num, channel, height, width) 的顺序保存数据。 比如,将图7-12中的处理改成对N个数据进行批处理时,数据的形状如图7-13所示。 图7-13的批处理版的数据流中,在各个数据的开头添加了批用的维度。 像这样,数据作为4维的形状在各层间传递。 这里需要注意的是,网络间传递的是4维数据,对这N个数据进行了卷积运算。 也就是说,批处理将N次的处理汇总成了1次进行。

池化层

池化是缩小高、长方向上的空间的运算。 比如,如图7-14所示,进行将 2 × 2的区域集约成1个元素的处理,缩小空间大小。

图7-14的例子是按步幅2进行2 × 2的Max池化时的处理顺序。 “Max 池化”是获取最大值的运算,“2 × 2”表示目标区域的大小。 如图所示,从 2 × 2的区域中取出最大的元素。 此外,这个例子中将步幅设为了2,所以 2 × 2的窗口的移动间隔为2个元素。 另外,一般来说,池化的窗口大小会 和步幅设定成相同的值。 比如,3 × 3的窗口的步幅会设为3,4 × 4的窗口的步幅会设为4等。 注意: 除了Max池化之外,还有Average池化等。 相对于Max池化是从 目标区域中取出最大值,Average池化则是计算目标区域的平均值。 在图像识别领域,主要使用Max池化。 因此,本书中说到“池化层”时,指的是Max池化。

池化层的特征

池化层有以下特征。

  1. 没有要学的参数 池化层和卷积层不同,没有要学的参数。 池化只是从目标区域中取最大值(或者平均值),所以不存在要学的参数。
  2. 通道数不发生变化 经过池化运算,输入数据和输出数据的通道数不会发生变化。 如图7-15 所示,计算是按通道独立进行的。
  1. 对微小的位置变化具有鲁棒性(健壮) 输入数据发生微小偏差时,池化仍会返回相同的结果。 因此,池化对 输入数据的微小偏差具有鲁棒性。 比如,3 × 3的池化的情况下,如图 7-16所示,池化会吸收输入数据的偏差(根据数据的不同,结果有可能不一致)。

卷积层和池化层的实现

前面我们详细介绍了卷积层和池化层,本节我们就用Python来实现这 两个层。 和第5章一样,也给进行实现的类赋予forward和backward方法,并使其可以作为模块使用。 大家可能会感觉卷积层和池化层的实现很复杂,但实际上,通过使用某 种技巧,就可以很轻松地实现。 本节将介绍这种技巧,将问题简化,然后再进行卷积层的实现。

4维数组

如前所述,CNN中各层间传递的数据是4维数据。 所谓4维数据,比如 数据的形状是__(10, 1, 28, 28)__,则它对应__10个高为28、长为28、通道为1__的数据。 用Python来实现的话,如下所示。

代码语言:javascript
复制
import numpy as np

x = np.random.rand(10, 1, 28, 28) # 随机生成数据

x.shape

# (10, 1, 28, 28)

如果要访问第1个数据的第1个通道的空间数据,可以写成下面这样。

代码语言:javascript
复制
x[0, 0] # 或者x[0][0]
代码语言:javascript
复制
x[0, 0].shape

# (28, 28)

像这样,CNN中处理的是4维数据,因此卷积运算的实现看上去会很复杂,但是通过使用下面要介绍的im2col这个技巧,问题就会变得很简单。

基于im2col的展开

如果老老实实地实现卷积运算,估计要重复好几层的for语句。 这样的实现有点麻烦,而且,NumPy中存在使用for语句后处理变慢的缺点(NumPy 中,访问元素时最好不要用for语句)。 这里,我们不使用for语句,而是使用im2col这个便利的函数进行简单的实现。 im2col是一个函数,将输入数据展开以适合滤波器(权重)。 如图7-17所示, 对3维的输入数据应用im2col后,数据转换为2维矩阵(正确地讲,是把包含批数量的4维数据转换成了2维数据)。

im2col会把输入数据展开以适合滤波器(权重)。 具体地说,如图7-18所示, 对于输入数据,将应用滤波器的区域(3维方块)横向展开为1列。 im2col会在所有应用滤波器的地方进行这个展开处理。

在图7-18中,为了便于观察,将步幅设置得很大,以使滤波器的应用区域不重叠。 而在实际的卷积运算中,滤波器的应用区域几乎都是重叠的。 在 滤波器的应用区域重叠的情况下,使用im2col展开后,展开后的元素个数会 多于原方块的元素个数。 因此,使用im2col的实现存在比普通的实现消耗更多内存的缺点。 但是,汇总成一个大的矩阵进行计算,对计算机的计算颇有益处。 比如,在矩阵计算的库(线性代数库)等中,矩阵计算的实现已被高度最优化,可以高速地进行大矩阵的乘法运算。 因此,通过归结到矩阵计算上,可以有效地利用线性代数库。 注: im2col这个名称是“image to column”的缩写,翻译过来就是“从 图像到矩阵”的意思。 Caffe、Chainer等深度学框架中有名为im2col的函数,并且在卷积层的实现中,都使用了im2col。 使用im2col展开输入数据后,之后就只需将卷积层的滤波器(权重)纵 向展开为1列,并计算2个矩阵的乘积即可(参照图7-19)。这和全连接层的Affine层进行的处理基本相同。 如图7-19所示,基于im2col方式的输出结果是2维矩阵。 因为CNN中 数据会保存为4维数组,所以要将2维输出数据转换为合适的形状。 以上就是卷积层的实现流程。

卷积层的实现

im2col会考虑滤波器大小、步幅、填充,将输入数据展开为2维数组。现在, 我们来实际使用一下这个im2col。

代码语言:javascript
复制
import sys, os 
sys.path.append(os.pardir) 
from common.util import im2col

print('完成')
代码语言:javascript
复制
x1 = np.random.rand(1, 3, 7, 7) 
col1 = im2col(x1, 5, 5, stride=1, pad=0) 

print(col1.shape) # (9, 75)
代码语言:javascript
复制
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 的类。

代码语言:javascript
复制
class Convolution:
    
    def __init__(self, w, b, stride = 1, pad = 0):
            self.w = w
            self.b = b
            self.stride = stride
            self.pad = pad
            
    def forward(self, x):
        FN, C, FH, FW = self.w.shape
        N, C, H, W = x.shape
        out_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.b
        
        out = 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维数组。 然后,计算展开后的矩阵的乘积。 展开滤波器的部分(代码段中的粗体字)如图7-19所示,将各个滤波器 的方块纵向展开为1列。 这里通过reshape(FN,-1)将参数指定为-1,这是 reshape的一个便利的功能。 通过在reshape时指定为-1,reshape函数会自 动计算-1维度上的元素个数,以使多维数组的元素个数前后一致。 比如, (10, 3, 5, 5)形状的数组的元素个数共有750个,指定reshape(10,-1)后,就会转换成(10, 75)形状的数组。 forward的实现中,最后会将输出大小转换为合适的形状。 转换时使用了 NumPy的transpose函数。 transpose会更改多维数组的轴的顺序。 如图7-20所示,通过指定从0开始的索引(编号)序列,就可以更改轴的顺序。

以上就是卷积层的forward处理的实现。 通过使用im2col进行展开,基 本上可以像实现全连接层的Affine层一样来实现(5.6节)。 接下来是卷积层 的反向传播的实现,因为和Affine层的实现有很多共通的地方,所以就不再 介绍了。 但有一点需要注意,在进行卷积层的反向传播时,必须进行im2col的逆处理。 这可以使用本书提供的col2im函数(col2im的实现在common/util.py中)来进行。 除了使用col2im这一点,卷积层的反向传播和Affine层的实现方式都一样。 卷积层的反向传播的实现在common/layer.py中,有兴趣的读者可以参考。

池化层的实现

池化层的实现和卷积层相同,都使用im2col展开输入数据。 不过,池化 的情况下,在通道方向上是独立的,这一点和卷积层不同。 具体地讲,如图7-21所示,池化的应用区域按通道单独展开。

像这样展开之后,只需对展开的矩阵求__各行的最大值__,并转换为合适的 形状即可(图7-22)。

上面就是池化层的forward处理的实现流程。下面来看一下Python的实现示例。

代码语言:javascript
复制
class Pooling:
    
    def __init__(self, pool_h, pool_w, stride = 1, pad = 0):
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad
        
        
    def forward(self, x):
        N, C, H, W = x.shape
        out_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

如图7-22所示,池化层的实现按下面3个阶段进行。

  1. 展开输入数据。
  2. 求各行的最大值。
  3. 转换为合适的输出大小。 各阶段的实现都很简单,只有一两行代码。

补充: 最大值的计算可以使用NumPy的np.max方法。 np.max可以指定 axis参数,并在这个参数指定的各个轴方向上求最大值。 比如,如 果写成np.max(x, axis=1),就可以在输入x的第1维的各个轴方向上求最大值。

补充

全连接: 相邻层的所有神经元之间都有连接,这称为全连接(fully-connected)

参考

感谢帮助!

本文作者: yiyun

本文链接: https://cloud.tencent.com/developer/article/1970827

版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-02-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 卷积层
    • 全连接层存在的问题
      • 卷积运算
        • 填充
          • 步幅
            • 3维数据的卷积运算
              • 结合方块思考
                • 批处理
                • 池化层
                  • 池化层的特征
                  • 卷积层和池化层的实现
                    • 4维数组
                      • 基于im2col的展开
                        • 卷积层的实现
                          • 池化层的实现
                          • 补充
                          • 参考
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档