# 17.4 卷积反向传播代码实现

## 17.4 卷积反向传播代码实现⚓︎

### 17.4.1 方法1⚓︎

    def backward_numba(self, delta_in, flag):
# 如果正向计算中的stride不是1，转换成是1的等价误差数组
dz_stride_1 = expand_delta_map(delta_in, ...)
# 计算本层的权重矩阵的梯度
# 由于输出误差矩阵的尺寸必须与本层的输入数据的尺寸一致，所以必须根据卷积核的尺寸，调整本层的输入误差矩阵的尺寸
# 计算本层输出到下一层的误差矩阵
#return delta_out
return delta_out, self.WB.dW, self.WB.dB

# 用输入数据乘以回传入的误差矩阵,得到卷积核的梯度矩阵
# 先把输入矩阵扩大，周边加0
# 输入矩阵与误差矩阵卷积得到权重梯度矩阵

# 用输入误差矩阵乘以（旋转180度后的）卷积核
def _calculate_delta_out(self, dz, layer_idx):
if layer_idx == 0:
return None
# 旋转卷积核180度
rot_weights = self.WB.Rotate180()
# 定义输出矩阵形状
delta_out = np.zeros(self.x.shape)
# 输入梯度矩阵卷积旋转后的卷积核，得到输出梯度矩阵
delta_out = calculate_delta_out(dz, ..., delta_out)

return delta_out


### 17.4.2 方法2⚓︎

#### 代码实现⚓︎

    def backward_col2img(self, delta_in, layer_idx):
OutC, InC, FH, FW = self.WB.W.shape
# 误差矩阵变换
delta_in_2d = np.transpose(delta_in, axes=(0,2,3,1)).reshape(-1, OutC)
# 计算Bias的梯度
self.WB.dB = np.sum(delta_in_2d, axis=0, keepdims=True).T / self.batch_size
# 计算Weights的梯度
dW = np.dot(self.col_x.T, delta_in_2d) / self.batch_size
# 转换成卷积核的原始形状
self.WB.dW = np.transpose(dW, axes=(1, 0)).reshape(OutC, InC, FH, FW)# 计算反向传播误差矩阵
dcol = np.dot(delta_in_2d, self.col_w.T)
# 转换成与输入数据x相同的形状
delta_out = col2img(dcol, self.x.shape, FH, FW, self.stride, self.padding)
return delta_out, self.WB.dW, self.WB.dB


#### 单样本单通道的实例讲解⚓︎

x=
[[[[0 1 2]
[3 4 5]
[6 7 8]]]]

col_x=
[[0. 1. 3. 4.]
[1. 2. 4. 5.]
[3. 4. 6. 7.]
[4. 5. 7. 8.]]


w=
[[[[0 1]
[2 3]]]]


col_w=
[[0]
[1]
[2]
[3]]


delta_in=
[[[[0 1]
[2 3]]]]


delta_in_2d = np.transpose(delta_in, axes=(0,2,3,1)).reshape(-1, OutC)


delta_in_2d=
[[0]
[1]
[2]
[3]]


self.WB.dB = np.sum(delta_in_2d, axis=0, keepdims=True).T / self.batch_size


dB=
[[6.]]


dW = np.dot(self.col_x.T, delta_in_2d) / self.batch_size


dW=
[[19.]
[25.]
[37.]
[43.]]


self.WB.dW = np.transpose(dW, axes=(1, 0)).reshape(OutC, InC, FH, FW)


dW=
[[[[19. 25.]
[37. 43.]]]]


dcol = np.dot(delta_in_2d, self.col_w.T)


dcol=
[[0 0 0 0]
[0 1 2 3]
[0 2 4 6]
[0 3 6 9]]


delta_out = col2img(dcol, self.x.shape, FH, FW, self.stride, self.padding)


delta_out=
[[[[ 0.  0.  1.]
[ 0.  4.  6.]
[ 4. 12.  9.]]]]


1. 左侧第一行红色椭圆内的四个元素移到右侧红色圆形内；
2. 在1的基础上，左侧第二行黄色椭圆内的四个元素移到右侧黄色圆形内，其中与原有元素重叠的地方则两个值相加。比如中间那个元素就是0+2=2；
3. 在2的基础上，左侧第三行蓝色椭圆内的四个元素移到右侧蓝色圆形内，其中与原有元素重叠的地方则两个值相加。比如中间那个元素再次加2；
4. 在3的基础上，左侧第四行绿色椭圆内的四个元素移到右侧绿色圆形内，其中与原有元素重叠的地方则两个值相加，中间的元素再次加0，还是4；中间靠下的元素原值是6，加6后为12。

#### 多样本多通道的实例讲解⚓︎

• batch size = 2
• input channel = 3
• input height = 3
• input width = 3
• filter height = 2
• filter width = 2
• stride = 1
• output channel = 2
• output height = 2
• output width = 2

#### 误差输入矩阵⚓︎

delta_in是本层的误差输入矩阵，它的形状应该和本层的前向计算结果一样。在本例中，误差输入矩阵的形状应该是：(batch_size * output_channel * output_height * output_width) = (2 x 2 x 2 x 2)：

delta_in=
(样本1)
(通道1)
[[[[ 0  1]
[ 2  3]]
(通道2)
[[ 4  5]
[ 6  7]]]
(样本2)
(通道1)
[[[ 8  9]
[10 11]]
(通道2)
[[12 13]
[14 15]]]]


delta_in_2d = np.transpose(delta_in, axes=(0,2,3,1)).reshape(-1, OutC)

delta_in_2d=
[[ 0  4]
[ 1  5]
[ 2  6]
[ 3  7]
[ 8 12]
[ 9 13]
[10 14]
[11 15]]


dW = np.dot(self.col_x.T, delta_in_2d) / self.batch_size


dW=
[[ 564.|  812.]
[ 586.|  850.]
[ 630.|  926.]
[ 652.|  964.]
------+-------
[ 762.| 1154.]
[ 784.| 1192.]
[ 828.| 1268.]
[ 850.| 1306.]
------+-------
[ 960.| 1496.]
[ 982.| 1534.]
[1026.| 1610.]
[1048.| 1648.]]


self.WB.dW = np.transpose(dW, axes=(1, 0)).reshape(OutC, InC, FH, FW)


dW=
(过滤器1)                 (过滤器2)
(卷积核1)                 (卷积核1)
[[[[ 564.  586.]        [[[ 812.  850.]
[ 630.  652.]]          [ 926.  964.]]
(卷积核2)                 (卷积核2)
[[ 762.  784.]          [[1154. 1192.]
[ 828.  850.]]          [1268. 1306.]]
(卷积核3)                 (卷积核3)
[[ 960.  982.]          [[1496. 1534.]
[1026. 1048.]]]         [1610. 1648.]]]]


dcol = np.dot(delta_in_2d, self.col_w.T)


dcol=
[[ 48  52  56  60  64  68  72  76  80  84  88  92]
[ 60  66  72  78  84  90  96 102 108 114 120 126]
[ 72  80  88  96 104 112 120 128 136 144 152 160]
[ 84  94 104 114 124 134 144 154 164 174 184 194]
[144 164 184 204 224 244 264 284 304 324 344 364]
[156 178 200 222 244 266 288 310 332 354 376 398]
[168 192 216 240 264 288 312 336 360 384 408 432]
[180 206 232 258 284 310 336 362 388 414 440 466]]


delta_out = col2img(dcol, self.x.shape, FH, FW, self.stride, self.padding)


delta_out=
(样本1)                     (样本2)
(通道1)                     (通道1)
[[[[  48.  112.   66.]    [[[ 144.  320.  178.]
[ 128.  296.  172.]      [ 352.  776.  428.]
[  88.  200.  114.]]     [ 216.  472.  258.]]
(通道2)                     (通道2)
[[  64.  152.   90.]     [[ 224.  488.  266.]
[ 176.  408.  236.]      [ 528. 1144.  620.]
[ 120.  272.  154.]]     [ 312.  672.  362.]]
(通道3)                     (通道3)
[[  80.  192.  114.]     [[ 304.  656.  354.]
[ 224.  520.  300.]      [ 704. 1512.  812.]
[ 152.  344.  194.]]]    [ 408.  872.  466.]]]]


### 17.4.3 正确性与性能测试⚓︎

def test_performance():
...


method numba: 11.830008506774902
method img2col: 3.543151378631592
compare correctness of method 1 and method 2:
forward: True
backward: True
dW: True
dB: True


### 代码位置⚓︎

ch17, Level4

Level4_Col2Img_Test.py中有两个方法：

• understand_4d_col2img_simple - 用单样本单通道理解反向传播
• understand_4d_col2img_complex - 用多样本多通道理解反向传播

Level4_BackwardTest.py用来测试两种方法的性能。