NNDL 作业7:第五章课后题(1×1 卷积核 | CNN BP)

目录

习题5-2 证明宽卷积具有交换性,即公式(5.13)。

习题5-3 分析卷积神经网络中用1x1的卷积核的作用。

习题5-4 对于一个输入为100x100x256的特征映射组,使用3x3的卷积核,输出为100x100x256的特征映射组的卷积层,求其时间和空间复杂度。如果引入一个1x1卷积核,先得到100x100x64的特征映射,再进行3x3的卷积,得到100x100x256的特征映射组,求其时间和空间复杂度。

习题5-7 忽略激活函数,分析卷积网络中卷积层的前向计算和反向传播(公式(5.39))是一种转置关系。

【选做】

推到CNN反向传播算法

设计简易CNN模型,分别用Numpy、Python实现卷积层和池化层的反向传播算子,并代入数值测试。

参考


习题5-2 证明宽卷积具有交换性,即公式(5.13)。

证明:
首先给定一个二维图像:和一个二维卷积核W\in \mathbb{R}^{U\times V}
因为要对图像X进行卷积,但是上面定义中提到了是宽卷积,所以先对他进行填充,两端各补U-1和V-1个零,得到全填充图像X\in \mathbb{R}^{(M+2U-2)\times (N+2V-2)}

为了方便我们先设一下

W=\begin{pmatrix} a_1&b_1\\ c_1&d_1\\ \end{pmatrix}

X=\begin{pmatrix} a_2&b_2&c_2\\ d_2&e_2&f_2\\ g_2&h_2&i_2\\ \end{pmatrix}

因为我们要证明交换性,所以要对W也填充,就是W两端各补M-1和N-1个零,得到全填充图像

W\in \mathbb{R}^{(U+2M-1)\times (V+2N-1)}

从我们设的X和W很容易可以看出来U=N=2,M=N=3。
那么填充完的

\widetilde{W}=\begin{pmatrix} 0&0&0&0&0&0\\ 0&0&0&0&0&0\\ 0&0&a_1&b_1&0&0\\ 0&0&c_1&d_1&0&0\\ 0&0&0&0&0&0\\ 0&0&0&0&0&0\\ \end{pmatrix}

\widetilde{X}=\begin{pmatrix} 0&0&0&0&0\\ 0&a_2&b_2&c_2&0\\ 0&d_2&e_2&f_2&0\\ 0&g_2&h_2&i_2&0\\ 0&0&0&0&0\\ \end{pmatrix}
然后把W和X旋转180°

 rot180(W)=\begin{pmatrix} d_1&c_1\\ b_1&a_1\\ \end{pmatrix}

rot180(X)=\begin{pmatrix} i_2&h_2&g_2\\ f_2&e_2&d_2\\ c_2&b_2&a_2\\ \end{pmatrix} 
然后就方便计算

rot180(W)\otimes \widetilde{X}=\begin{pmatrix} d_1&c_1\\ b_1&a_1\\ \end{pmatrix}\otimes \begin{pmatrix} 0&0&0&0&0\\ 0&a_2&b_2&c_2&0\\ 0&d_2&e_2&f_2&0\\ 0&g_2&h_2&i_2&0\\ 0&0&0&0&0\\ \end{pmatrix}=

\begin{pmatrix} a_1a_2&b_1a_2+a_1b_2&b_1b_2+a_1c_2&b_1c_2\\ c_1a_2+a_1d_2&d_1a_2+c_1b_2+b_1d_2+a_1e_2&d_1b_2+c_1c_2+b_1e_2+a_1f_2&d_1c_2+b_1f_2\\ c_1d_2+a_1g_2&d_1d_2+c_1e_2+b_1g_2+a_1h_2&d_1e_2+c_1f_2+b_1h_2+a_1i_2&d_1f_2+b_1i_2\\ c_1g_2&d_1g_2+c_1h_2&d_1h_2+c_1i_2&d_1i_2 \end{pmatrix}

 rot180(X)\otimes \widetilde{W}=\begin{pmatrix} i_2&h_2&g_2\\ f_2&e_2&d_2\\ c_2&b_2&a_2\\ \end{pmatrix}\otimes \begin{pmatrix} 0&0&0&0&0&0\\ 0&0&0&0&0&0\\ 0&0&a_1&b_1&0&0\\ 0&0&c_1&d_1&0&0\\ 0&0&0&0&0&0\\ 0&0&0&0&0&0\\ \end{pmatrix}=

 \begin{pmatrix} a_1a_2&b_1a_2+a_1b_2&b_1b_2+a_1c_2&b_1c_2\\ c_1a_2+a_1d_2&d_1a_2+c_1b_2+b_1d_2+a_1e_2&d_1b_2+c_1c_2+b_1e_2+a_1f_2&d_1c_2+b_1f_2\\ c_1d_2+a_1g_2&d_1d_2+c_1e_2+b_1g_2+a_1h_2&d_1e_2+c_1f_2+b_1h_2+a_1i_2&d_1f_2+b_1i_2\\ c_1g_2&d_1g_2+c_1h_2&d_1h_2+c_1i_2&d_1i_2 \end{pmatrix}

显然:rot180(X)\otimes \widetilde{W}=rot180(W)\otimes \widetilde{X},得证。

习题5-3 分析卷积神经网络中用1x1的卷积核的作用。

- 其一,可用于降低维度。例如当图像大小为  **M** X **N** X **D**,可降维成 **M** X **N** 。
- 其二,将位于每个点位上的所有通道特征进行卷积,实现跨通道的特征抽取。
- 其三,减小时间复杂度。


习题5-4 对于一个输入为100x100x256的特征映射组,使用3x3的卷积核,输出为100x100x256的特征映射组的卷积层,求其时间和空间复杂度。如果引入一个1x1卷积核,先得到100x100x64的特征映射,再进行3x3的卷积,得到100x100x256的特征映射组,求其时间和空间复杂度。

对于第一种情况:M=100; K=3;  C~in~=256 ; C~out~=256

时间复杂度一:100×100×3×3×256×256 = 5.89824 x 10^9^

空间复杂度一:100×100×256 = 2.56 x 10^6^
 

对于第二种情况:M=100; K~1~=1; K~2~=3; C~in1~=256; C~out1~=64; C~in2~=64; C~out2~=256

时间复杂度二:100×100×1×1×256×64 + 100×100×3×3×64×256 = 1.6384 x 10^9^

空间复杂度二:100×100×64 + 100×100×256 = 3.2 x 10^6^


习题5-7 忽略激活函数,分析卷积网络中卷积层的前向计算和反向传播(公式(5.39))是一种转置关系。

以一个3×3的卷积核为例,输入为X输出为Y

X=\begin{pmatrix} x_1&x_2&x_3&x_4\\ x_5&x_6&x_7&x_8\\ x_9&x_{10}&x_{11}&x_{12}\\ x_{13}&x_{14}&x_{15}&x_{16}\\ \end{pmatrix}W=\begin{pmatrix} w_{00}&w_{01}&w_{02}\\ w_{10}&w_{11}&w_{12}\\ w_{20}&w_{21}&w_{22}\\ \end{pmatrix}Y=\begin{pmatrix} y_1&y_2\\ y_3&y_4\\ \end{pmatrix}

 将4×4的输入特征展开为16×1的矩阵,y展开为4×1的矩阵,

将卷积计算转化为矩阵相乘

 Y_{4\times 1}=C_{4\times 16}\times X_{16\times 1}

Y=\begin{bmatrix} y_1\\ y_2\\ y_3\\ y_4\\ \end{bmatrix} C=\begin{bmatrix} w_{20}&w_{01}&w_{02}&0&w_{10}&w_{11}&w_{12}&...\\ 0&w_{20}&w_{01}&w_{02}&0&w_{10}&w_{11}&...\\ 0&0&w_{20}&w_{01}&w_{02}&0&w_{10}&...\\ 0&0&0&w_{20}&w_{01}&w_{02}&0&...\\ \end{bmatrix}X=\begin{bmatrix} x_1\\ x_2\\ \vdots\\ x_4\\ \end{bmatrix}

\frac{\partial loss}{\partial x_j}=\sum_{i}^{4}\frac{\partial loss}{\partial y_i}\cdot \frac{\partial y_i}{\partial x_j} 而  y_i=\sum_{i=1}^{16}c_{ij}x_{j}\frac{\partial y_i}{\partial x_j}=c_{ij}

所以 \frac{\partial loss}{\partial x}=\begin{bmatrix} \frac{\partial loss}{\partial x_1}\\ \frac{\partial loss}{\partial x_2}\\ \vdots \\ \frac{\partial loss}{\partial x_16}\\ \end{bmatrix}=\begin{bmatrix} c_1^T\\ c_2^T\\ \vdots \\ c_{16}^T\\ \end{bmatrix}\frac{\partial loss}{\partial Y}=C^T\frac{\partial loss}{\partial Y}

再看一下上面的Y=CX可以发现忽略激活函数时卷积网络中卷积层的前向计算和反向传播是一种转置关系。 

【选做】

推到CNN反向传播算法

在前向传播算法时,池化层一般我们会用MAX或者Average对输入进行池化,池化的区域大小已知。现在我们反过来,要从缩小后的误差,还原前一次较大区域对应的误差。

    在反向传播时,我们首先会把误差的所有子矩阵矩阵大小还原成池化之前的大小,然后如果是MAX,则把误差的所有子矩阵的各个池化局域的值放在之前做前向传播算法得到最大值的位置。如果是Average,则把误差的所有子矩阵的各个池化局域的值取平均后放在还原后的子矩阵位置。这个过程一般叫做upsample。

    用一个例子可以很方便的表示:假设我们的池化区域大小是2x2。\delta ^{l}的第k个子矩阵为:

\begin{pmatrix} y_2&y_4\\ y_6&y_8\\ \end{pmatrix}

    由于池化区域为2x2,我们先讲\delta _{k}^{l}做还原,即变成:

 

\begin{pmatrix} 0&0&0&0\\ 0&2&8&0\\ 0&4&6&0\\ 0&0&0&0\\ \end{pmatrix}

     如果是MAX,假设我们之前在前向传播时记录的最大值位置分别是左上,右下,右上,左下,则转换后的矩阵为:

\begin{pmatrix} 2&0&0&0\\ 0&0&0&8\\ 0&4&0&0\\ 0&0&6&0\\ \end{pmatrix}

    如果是Average,则进行平均:转换后的矩阵为:

\begin{pmatrix} 0.5&0.5&2&2\\ 0.5&0.5&2&2\\ 1&1&1.5&1.5\\ 1&1&1.5&1.5\\ \end{pmatrix}

  

    其中,upsample函数完成了池化误差矩阵放大与误差重新分配的逻辑。

    我们概括下,对于张量\delta ^{l-1},我们有:

 

在这里插入图片描述

可以看到,只有第一项不同。

 已知卷积层的误差,反向推导上一隐藏层的误差

公式如下:

我们再看一次普通网络的反向推导误差的公式:
在这里插入图片描述
可以看到区别在于,下一层的权重w的转置操作,变成了旋转180度的操作,也就是上下翻转一次,左右再翻转一次,这其实就是“卷积”一词的意义(我们可简单理解为数学上的trick),可参考下图,Q是下一层的误差,周围补0方便计算,W是180度翻转后的卷积核,P是W和Q做卷积的结果: 

 

 已知卷积层的误差,推导该层的W,b的梯度

经过以上各步骤,我们已经算出每一层的误差了,那么:

  1. 对于全连接层,可以按照普通网络的反向传播算法求该层W,b的梯度。
  2. 对于池化层,它并没有W,b,也不用求W,b的梯度。
  3. 只有卷积层的W,b需要求出,先看w:

 再对比一下普通网络的求w梯度的公式,发现区别在于,对前一层的输出做翻转180度的操作:
在这里插入图片描述

 而对于b,则稍微有些特殊,因为在CNN中,误差δ是三维张量,而b只是一个向量,不能像普通网络中那样直接和误差δ相等。通常的做法是将误差δ的各个子矩阵的项分别求和,得到一个误差向量,即为b的梯度:
在这里插入图片描述

 

设计简易CNN模型,分别用Numpy、Python实现卷积层和池化层的反向传播算子,并代入数值测试。


卷积层的反向传播:

 

import numpy as np
import torch.nn as nn
 
 
class Conv2D(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride, padding):
        super(Conv2D, self).__init__()
 
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.ksize = kernel_size
        self.stride = stride
        self.padding = padding
 
        self.weights = np.random.standard_normal((out_channels, in_channels, kernel_size, kernel_size))
        self.bias = np.zeros(out_channels)
 
        self.grad_w = np.zeros(self.weights.shape)
        self.grad_b = np.zeros(self.bias.shape)
 
    def forward(self, x):
        self.x = x
        weights = self.weights.reshape(self.out_channels, -1)  # o,ckk
 
        x = np.pad(x, ((0, 0), (0, 0), (self.padding, self.padding), (self.padding, self.padding)), 'constant',
                   constant_values=0)
        b, c, h, w = x.shape
 
        self.out = np.zeros(
            (b, self.out_channels, (h - self.ksize) // self.stride + 1, (w - self.ksize) // self.stride + 1))
 
        self.col_img = self.im2col(x, self.ksize, self.stride)  # bhw * ckk
        out = np.dot(weights, self.col_img.T).reshape(self.out_channels, b, -1).transpose(1, 0, 2)
 
        self.out = np.reshape(out, self.out.shape)
 
        return self.out
 
    def backward(self, grad_out):
        b, c, h, w = self.out.shape  #
 
        grad_out_ = grad_out.transpose(1, 0, 2, 3)  # b,oc,h,w * (bhw , ckk)
        grad_out_flat = np.reshape(grad_out_, [self.out_channels, -1])
 
        self.grad_w = np.dot(grad_out_flat, self.col_img).reshape(self.grad_w.shape)
        self.grad_b = np.sum(grad_out_flat, axis=1)
        tmp = self.ksize - self.padding - 1
        grad_out_pad = np.pad(grad_out, ((0, 0), (0, 0), (tmp, tmp), (tmp, tmp)), 'constant', constant_values=0)
 
        flip_weights = np.flip(self.weights, (2, 3))
        # flip_weights = np.flipud(np.fliplr(self.weights)) # rot(180)
        flip_weights = flip_weights.swapaxes(0, 1)  # in oc
        col_flip_weights = flip_weights.reshape([self.in_channels, -1])
 
        weights = self.weights.transpose(1, 0, 2, 3).reshape(self.in_channels, -1)
 
        col_grad = self.im2col(grad_out_pad, self.ksize, 1)  # bhw,ckk
 
        # (in,ckk) * (bhw,ckk).T
        next_eta = np.dot(weights, col_grad.T).reshape(self.in_channels, b, -1).transpose(1, 0, 2)
 
        next_eta = np.reshape(next_eta, self.x.shape)
 
        return next_eta
 
    def zero_grad(self):
        self.grad_w = np.zeros_like(self.grad_w)
        self.grad_b = np.zeros_like(self.grad_b)
 
    def update(self, lr=1e-3):
        self.weights -= lr * self.grad_w
        self.bias -= lr * self.grad_b
 
    def im2col(self, x, k_size, stride):
        b, c, h, w = x.shape
        image_col = []
        for n in range(b):
            for i in range(0, h - k_size + 1, stride):
                for j in range(0, w - k_size + 1, stride):
                    col = x[n, :, i:i + k_size, j:j + k_size].reshape(-1)
                    image_col.append(col)
 
        return np.array(image_col)
 
 
class Layers():
    def __init__(self, name):
        self.name = name
 
    # 前向
    def forward(self, x):
        pass
 
    # 梯度置零
    def zero_grad(self):
        pass
 
    # 后向
    def backward(self, grad_out):
        pass
 
    # 参数更新
    def update(self, lr=1e-3):
        pass
 
 
class Module():
    def __init__(self):
        self.layers = []  # 所有的Layer
 
    def forward(self, x):
        for layer in self.layers:
            x = layer.forward(x)
        return x
 
    def backward(self, grad):
        for layer in reversed(self.layers):
            layer.zero_grad()
            grad = layer.backward(grad)
 
    def step(self, lr=1e-3):
        for layer in reversed(self.layers):
            layer.update(lr)
 
 
# test_conv
if __name__ == '__main__':
    x = np.array([[[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
               [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]]])
    conv = Conv2D(2, 3, 2, 1, 0)
    y = conv.forward(x)
    print(y.shape)
    loss = y - (y + 1)
    grad = conv.backward(loss)
    print(grad.shape)


池化层的反向传播:

import numpy as np
import torch.nn as nn
 
 
class MaxPooling(nn.Module):
    def __init__(self, ksize=2, stride=2):
        super(MaxPooling,self).__init__()
        self.ksize = ksize
        self.stride = stride 
 
    def forward(self, x):
        n,c,h,w = x.shape
        out = np.zeros([n, c, h//self.stride,w//self.stride])
        self.index = np.zeros_like(x)
        for b in range(n):
            for d in range(c):
                for i in range(h//self.stride):
                    for j in range(w//self.stride):
                        _x = i*self.stride
                        _y = j*self.stride
                        out[b, d ,i , j] = np.max(
                            x[b, d ,_x:_x+self.ksize, _y:_y+self.ksize])
                        index = np.argmax(x[b, d ,_x:_x+self.ksize, _y:_y+self.ksize])
                        self.index[b,d,_x+index//self.ksize, _y+index%self.ksize] = 1
        return out
 
    def backward(self, grad_out):
        return np.repeat(np.repeat(grad_out, self.stride, axis=2), self.stride, axis=3) * self.index

参考

卷积神经网络(CNN)反向传播算法 - 刘建平Pinard - 博客园 (cnblogs.com)icon-default.png?t=M85Bhttps://www.cnblogs.com/pinard/p/6494810.html

 Backpropagation In Convolutional Neural Networks | DeepGrid (jefkine.com)icon-default.png?t=M85Bhttps://www.jefkine.com/general/2016/09/05/backpropagation-in-convolutional-neural-networks/

Convolutional Neural Networks backpropagation: from intuition to derivation – Grzegorz Gwardys (wordpress.com)icon-default.png?t=M85Bhttps://grzegorzgwardys.wordpress.com/2016/04/22/8/

卷积神经网络(CNN)反向传播算法推导 - 知乎 (zhihu.com)icon-default.png?t=M85Bhttps://zhuanlan.zhihu.com/p/61898234

十二、CNN的反向传播 - 知乎 (zhihu.com)icon-default.png?t=M85Bhttps://zhuanlan.zhihu.com/p/44361349CNN的反向传播推导icon-default.png?t=M85Bhttps://blog.csdn.net/czp_374/article/details/86580382

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值