习题5-2 证明宽卷积具有交换性,即公式(5.13)
现有
根据宽卷积定义
为了让x的下标形式和w的进行对换,进行变量替换,
令
故
则
已知
因此对于
由于宽卷积的条件,s和t的变动范围是可行的。
习题5-3 分析卷积神经网络中用1×1的卷积核的作用
1×1的卷积核在实际应用中类似于一根截面积为1个像素的正方形管道,用来贯穿整个输入特征。每个1×1的卷积核都试图提取基于相同像素位置的特征的融合表达。可以实现特征升维和降维的目的。并且增加了网络的非线性,提高拟合能力。
习题5-4 对于一个输入为100×100×256的特征映射组,使用3×3的卷积核,输出为100×100×256的特征映射组的卷积层,求其时间和空间复杂度。如果引入一个1×1的卷积核,先得到100×100×64的特征映射,再进行3×3的卷积,得到100×100×256的特征映射组,求其时间和空间复杂度。
时间复杂度:时间复杂度即模型的运行次数。
空间复杂度:空间复杂度即模型的参数数量。
时间复杂度一:256×100×100×256×3×3 = 5,898,240,000
空间复杂度一:256×100×100 = 2,560,000
时间复杂度二:64×100×100×256 + 256×100×100×64×3×3 = 1,638,400,000
空间复杂度二:64×100×100 + 256×100×100 = 3,200,000
习题5-7 忽略激活函数,分析卷积网络中卷积层的前向计算和反向传播是一种转置关系。
推导CNN反向传播
先回顾一下卷积运算:在卷积神经网络中,所谓的卷积运算,其实并不是严格的数学意义上的卷积。深度学习中的卷积实际上是信号处理和图像处理中的互相关运算,它们二者之间有细微的差别。深度学习中的卷积(严格来说是互相关)是卷积核在原始图像上遍历,对应元素相乘再求和,得到的新图像在尺寸上会有减小。可以通过下图直观的去理解。假设输入图像的有m行,n列,卷积核的尺寸为filter_size×filter_size,输出图像的尺寸即为(m-filter_size+1)×(n-filter_size+1)
在全连接神经网络,图像数据以及特征是以列向量的形式进行存储。而在卷积神经网络中,数据的格式主要是以张量(可以理解为多维数组)的形式存储。图片的格式为一个三维张量,行×列×通道数。卷积核的格式为一个四维张量,卷积核数×行×列×通道数。
卷积操作是每次取出卷积核中的一个,一个卷积核的格式为三维,为行×列×通道数。对应通道序号的图片与卷积核经过二维卷积操作后(即上图所示操作),得到该通道对应的卷积结果,将所有通道的结果相加,得到输出图像的一个通道。每个卷积核对应输出图像的一个通道,即输出图像的通道数等于卷积核的个数。
误差传播:
参数更新规则:梯度下降法,公式如下:
定义误差项δ,如下:
l代表卷积神经网络第l层, j、k表示其特征向量第j行,第k列。w表示权重,i对应下一层神经元特征向量个数,s代表上一层特征向量个数,m、n表示一个卷积核第(m,n)个的值,b为偏置,z为该层神经元输入,a为该层神经元输出。
由链式求导法则,得误差传播过程为:
得误差传递公式:
设计简易CNN模型,分别用Numpy、Pytorch实现卷积层和池化层的反向传播算子,并代入数值测试.
import numpy as np
def padding(X, pad):
X_pad = np.pad(X, (
(0, 0),
(pad, pad),
(pad, pad),
(0, 0)),
mode='constant', constant_values=(0, 0))
return X_pad
def conv_single_step(a_slice_prev, W, b):
s = np.multiply(a_slice_prev, W)
Z = np.sum(s)
Z = Z + float(b)
return Z
def conv_forward(A_prev, W, b, hparameters):
(m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
(f, f, n_C_prev, n_C) = W.shape
stride = hparameters['stride']
pad = hparameters['pad']
n_H = int((n_H_prev + 2 * pad - f) / stride) + 1
n_W = int((n_W_prev + 2 * pad - f) / stride) + 1
Z = np.zeros((m, n_H, n_W, n_C))
A_prev_pad = padding(A_prev, pad)
for i in range(m): # 依次遍历每个样本
a_prev_pad = A_prev_pad[i] # 获取当前样本
for h in range(n_H): # 在输出结果的垂直方向上循环
for w in range(n_W): # 在输出结果的水平方向上循环
# 确定分片边界
vert_start = h * stride
vert_end = vert_start + f
horiz_start = w * stride
horiz_end = horiz_start + f
for c in range(n_C):
a_slice_prev = a_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :]
weights = W[:, :, :, c]
biases = b[:, :, :, c]
Z[i, h, w, c] = conv_single_step(a_slice_prev, weights, biases)
assert (Z.shape == (m, n_H, n_W, n_C))
mask = (A_prev, W, b, hparameters)
return Z, mask
def backward(theta, mask):
(A_prev, W, b, hparameters) = mask
(m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
(f, f, n_C_prev,