深度学习入门--卷积神经网络-卷积层
卷积神经网络(CNN)被广泛应用于图像识别这个领域,几乎所有的基于深度学习的图像识别都是以卷积神经网络作为技术基础的。
卷积神经网络不同于其他网络的是它新出现了“卷积层”和“池化层”。卷积层可以保持形状不变,当输入数据是图像时,卷积层会以3维数据的形式接收输入数据,并同样以3维数据的形式输出至下一层。
CNN和之前介绍的神经网络一样,可以像乐高积木那样通过组装层来构建。下图是普通神经网络和卷积神经网络的区别: 基于全连接层(Affine层)的网络的例子基于CNN的网络的例子
卷积运算
卷积层进行的运算顾名思义就是卷积运算。
对于输入数据,卷积运算以一定间隔(间隔也称为步幅)滑动滤波器的窗口,再将各个位置上滤波器的元素和输入的对应元素相乘,然后再求和,然后将这个结果保存到输出的相应位置。将这个过程在所有的位置都进行一遍,就可以得到卷积运算的输出。
卷积运算的过程可以简称为乘积累加运算。在CNN中,滤波器中的数据就是权重,并且,CNN中也存在偏置,下图的示例就是一个步幅为1的含有偏置的卷积运算:
含有偏置的卷积运算填充
在进行卷积层的处理之前,有时要向输入数据的周围填入固定的数据(比如0),这个过程称为填充。
下图是对大小为(4,4)的输入数据应用了幅度为1的填充: 幅度为1的填充指用幅度为1像素的0填充周围为什么要使用填充?
使用填充主要是为了调整输出的大小(使输出大小变大)。因为卷积运算会缩小输出,若反复进行多次卷积运算,输出可能会变为1,导致继而无法使用卷积运算,为了避免这种情况,就要使用填充。如上一个例子,使用幅度为1的填充之后,输出数据的大小和输入数据保持一致。
步幅
应用滤波器的位置间隔称为步幅,之前的例子中步幅都是1,如果将步幅设为2,则如下图所示: 步幅为2的卷积运算综上:增大步幅后,输出大小会变小;而增大填充后,输出大小会变大。
卷积层的实现
CNN中各层传递的数据是4维数据。所谓4维数据,比如数据的形状是(10,1,28,28),则它对应10个高为28、长为28、通道为1的数据。
用Python实现如下:
x = np.random.rand(10,1,28,28) # 随机生成数据
对于四维数据,如果老老实实进行卷积运算,会有很多层的for循环,这样实现起来有些麻烦,而且,Numpy中存在使用for语句后处理变慢的缺点。所以这里我们不使用for语句,而使用im2col
这个便利的函数。
im2col函数
im2col
是一个函数,im2col
这个名称是“image to column”的缩写,即是“从图像到矩阵”的意思。把包含批数量的四维数据应用im2col
后,数据转换为2维矩阵。
使用im2col
展开输入数据后,之后再将滤波器(权重)纵向展开为1列,并计算两个矩阵的乘积即可,这样就可以把复杂的卷积运算转换为简单快速的矩阵运算。
im2col函数
的原型如下:
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
"""
Parameters
----------
input_data : 由(数据量, 通道, 高, 长)的4维数组构成的输入数据
filter_h : 滤波器的高
filter_w : 滤波器的长
stride : 步幅
pad : 填充
Returns
-------
col : 2维数组
"""
N, C, H, W = input_data.shape
out_h = (H + 2*pad - filter_h)//stride + 1
out_w = (W + 2*pad - filter_w)//stride + 1
img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
for y in range(filter_h):
y_max = y + stride*out_h
for x in range(filter_w):
x_max = x + stride*out_w
col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
return col
接下来使用im2col
实现卷积层:
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 表示滤波器的宽度
"""
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) # 输入数据运用im2col函数
col_w = self.w.reshape(FN, -1).T # 滤波器的展开
out = np.dot(col, col_w) + self.b
# 使用Numpy的transpose函数将输出转换为合适的大小
out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
return out
每天学习一点点,每天进步一点点。