第七章主要讲CNN的实现。第八章更多的作者对未来的展望,粗略阅读即可,介绍了一点CV的应用和发展期待。
卷积神经网络(CNN)
使用场景
- 图像识别,语音识别
基本概念
- 总体上,以 卷积层——输出层(ReLu)——池化层(Pooling)
- 在全连接层中,MNIST数据集被拉成一个784 * 1的向量,这忽视了各个相近像素块之间的信息,而且输入特征太多了,计算复杂,而卷积则解决了该问题
- 随着卷积层数的加深,信息会越来越 抽象。比如一开始对图像的边缘有反应,到后来是对复杂的物体表面有反应
基本操作
- 卷积:输入图像对应区域 与 卷积核做相乘相加操作,把结果保存到对应位置中
- 填充(Padding):指在输入的图像上加上一圈0(行列都加),这个操作的目的是 调整输出的大小
- 步幅(Stride):卷积核会在输入图像上按照 滑动窗口的思想进行滑动计算,每次滑行的距离称为 步幅
- 计算公式,假设输入大小是(H,W),卷积核大小是(FH,FW),填充为P,Stride为S,则输出图像大小为?
模块
im2col函数
- 该函数主要是为了落地卷积使用的。实际使用中,采用 矩阵乘法的形式来进行卷积,为此需要把卷积核化为 列向量,把输入图像卷积的图像化为 行向量
import numpy as np
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
# 输入是一个批次为 N , 通道为C,高为H,宽度为W的图像
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 #计算卷积后的图像宽度
# 对图像进行Padding填充
img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
# 6D张量来存储图像的每个滑动窗口。这个张量的维度分别代表:批量大小、通道数、卷积核的高度、卷积核的宽度、输出的高度和输出的宽度。
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]
# 这两个嵌套的for循环遍历了卷积核的每个位置。对于每个位置,从填充后的图像中提取与卷积核对应位置的子图像,并将其存储在col张量中。
col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
# 首先,使用transpose方法对col张量的维度进行重新排序,使其从(N, C, filter_h, filter_w, out_h, out_w)变为(N, out_h, out_w, C, filter_h, filter_w)。然后,使用reshape方法将其变形成一个2D张量,其中行数为N*out_h*out_w(即所有可能的滑动窗口的数量),列数为C*filter_h*filter_w(即每个滑动窗口中的元素数量)。
return col
x1 = np.random.randn(1,3,7,7) # 1个数据,3个通道,宽高为7
col1 = im2col(x1, 5,5,stride=1,pad=0)
print(col1.shape)
#结果是 (9,75)x1 = np.random.randn(10,3,7,7)
col1 = im2col(x1,5,5,stride=1,pad=0)
print(col1.shape)
#结果是 (90,75)
卷积层
三维卷积
- 图像的输入是通道,高,宽,用(C,H,W)来表示。那么卷积核的大小就是(C,FH,FW)。也就是说,通道数必须是一致的
实现
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-FH+2*self.pad)/self.stride)
out_w = int(1 + (W-FW+2*self.pad)/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维度从N,H,W,C变为 N,C,H,Wout = out.reshape(N,out_h,out_w,-1).transpose(0,3,1,2)
池化层
- 目的是把特征图像进行高,宽方向上的缩小,比如把一个2* 2大小的矩阵,缩小成1个元素,如果该元素是矩阵中最大的,也叫 MaxPooLing
- 特点:1.没有参数2.通道数目不变3.对微小变化有健壮性(池化的原因,小的变了不影响大的)
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 foward(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)
col = im2col(x,self.pool_h,self.pool_w,self.stride,self.pad)
col = col.reshape(-1,self.pool_h*self.pool_w)
out = np.max(col,axis=1)
out = out.reshape(N,out_h,out_w,C).transpose(0,3,1,2)
return out
其他网络模型(这本书就略微提了一下)
VGG
- 由卷积层和池化层构成的CNN,有16层和19层。
- 3* 3的小滤波器连续进行,最后由全连接层输出结果
GooLeNet
- 横向上有 宽度,这种结构称为 Inception,使用了多个 大小不同的滤波器和池化层,最后再合并他们的结果。使用了1* 1的滤波,减少了通道大小和参数。
ResNet
- 使用了 快捷结构,在连续的两层卷积层中,会把卷积层的输出由.引入这种结构后,加深层数也能继续学习,因为反向传播的时候信号可以没有衰减地传递,可以缓解梯度消失的问题。