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

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

现有在这里插入图片描述
根据宽卷积定义在这里插入图片描述
为了让x的下标形式和w的进行对换,进行变量替换
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
已知在这里插入图片描述
因此对于在这里插入图片描述
由于宽卷积的条件,s和t的变动范围是可行的。

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

1、降维

比如,一张100 100且厚度depth为100 的图片在50个filter上做11的卷积,那么结果的大小为10010050。

2、升维(用最少的参数拓宽网络channal)

例子:64的卷积核的channel是64,只需添加一个11,256的卷积核,只用64256个参数就能把网络channel从64拓宽4倍到256。

3、加入非线性。

卷积层之后经过激励层,1*1的卷积在前一层的学习表示上添加了非线性激励,提升网络的表达能力。

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

时间复杂度:时间复杂度即模型的运行次数。

单个卷积层的时间复杂度:Time~O(M^2 * K^2 * Cin * Cout)

M:输出特征图(Feature Map)的尺寸。
K:卷积核(Kernel)的尺寸。
Cin:输入通道数。
Cout:输出通道数。
注:每一层卷积都包含一个偏置参数(bias),这里也给忽略了。加上的话时间复杂度则为:O(M^2 * K^2 * Cin * Cout+Cout)。

空间复杂度:空间复杂度即模型的参数数量。1125664+10010064+3364256+100100256

单个卷积的空间复杂度:Space~O(K^2 * Cin * Cout+M^2*Cout)

①:时间复杂度=10010033**256256=5898240000

   空间复杂度=3*3**256*256+100*100*256=3149824

②:时间复杂度=1001001125664+1001003364256=1638400000

   空间复杂度=1*1*256*64+100*100*64+3*3*64*256+100*100*256=3363840

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

在这里插入图片描述

CNN反向传播

误差传播:
参数更新规则:梯度下降法,公式如下:
在这里插入图片描述
定义误差项δ,如下:在这里插入图片描述
在这里插入图片描述
l代表卷积神经网络第l层, j、k表示其特征向量第j行,第k列。w表示权重,i对应下一层神经元特征向量个数,s代表上一层特征向量个数,m、n表示一个卷积核第(m,n)个的值,b为偏置,z为该层神经元输入,a为该层神经元输出。
由链式求导法则,得误差传播过程为:
在这里插入图片描述
这里的式子其实和DNN的类似,区别在于对于含有卷积的式子求导时,卷积核被旋转了180度。即式子中的rot180(),翻转180度的意思是上下翻转一次,接着左右翻转一次。在DNN中这里只是矩阵的转置。那么为什么呢?由于这里都是张量,直接推演参数太多了。我们以一个简单的例子说明为啥这里求导后卷积核要翻转。

假设我们l−1层的输出al−1是一个3x3矩阵,第l层的卷积核Wl是一个2x2矩阵,采用1像素的步幅,则输出zl是一个2x2的矩阵。我们简化bl都是0,则有
    在这里插入图片描述

我们列出a,W,z的矩阵表达式如下:
在这里插入图片描述

利用卷积的定义,很容易得出:
z11=a11w11+a12w12+a21w21+a22w22
z12=a12w11+a13w12+a22w21+a23w22
z21=a21w11+a22w12+a31w21+a32w22
z22=a22w11+a23w12+a32w21+a33w22
    接着我们模拟反向求导:
*加粗样式

从上式可以看出,对于al−1的梯度误差∇al−1,等于第l层的梯度误差乘以∂zl∂al−1,而∂zl∂al−1对应上面的例子中相关联的w的值。假设我们的z矩阵对应的反向传播误差是δ11,δ12,δ21,δ22组成的2x2矩阵,则利用上面梯度的式子和4个等式,我们可以分别写出∇al−1的9个标量的梯度。

得误差传递公式:
在这里插入图片描述
这里附上链接https://www.cnblogs.com/pinard/p/6494810.html

设计简易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, n_C) = W.shape
    stride = hparameters['stride']
    pad = hparameters['pad']
    (m, n_H, n_W, n_C) = theta.shape
    dA_prev = np.zeros_like(A_prev)
    dW = np.zeros_like(W)
    db = np.zeros_like(b)
    A_prev_pad = padding(A_prev, pad)
    dA_prev_pad = padding(dA_prev, pad)
    for i in range(m):
        a_prev_pad = A_prev_pad[i]
        da_prev_pad = dA_prev_pad[i]
        for h in range(n_H):
            for w in range(n_W):
                for c in range(n_C):
                    vert_start = h * stride
                    vert_end = vert_start + f
                    horiz_start = w * stride
                    horiz_end = horiz_start + f
                    a_slice = a_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :]
                    da_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :] += W[:, :, :, c] * theta[i, h, w, c]
                    dW[:, :, :, c] += a_slice * theta[i, h, w, c]
                    db[:, :, :, c] += theta[i, h, w, c]
        dA_prev[i, :, :, :] = da_prev_pad[pad:-pad, pad:-pad, :]
    assert (dA_prev.shape == (m, n_H_prev, n_W_prev, n_C_prev))
    return dA_prev, dW, db
 
A_prev = np.random.randn(10, 4, 4, 3)
W = np.random.randn(2, 2, 3, 8)
b = np.random.randn(1, 1, 1, 8)
hparameters = {"pad": 2,
               "stride": 2}
Z, mask_conv = conv_forward(A_prev, W, b, hparameters)
dA, dW, db = backward(Z, mask_conv)
print("卷积层卷积核参数反向传播的梯度:", dW)
print("卷积层偏置项反向传播的梯度:", db)
卷积层卷积核参数反向传播的梯度: [[[[  24.46993556   67.48514002  -38.62247492   11.71810996
     136.05647178   53.61172008  -11.17085223  -16.70240528]
   [ -21.13216474   26.92151364  -66.03969944   12.89381138
       8.8949686    53.08811601   89.00863725  -20.07174484]
   [   3.52904589   -4.70198615   44.42749098   27.68001942
     -37.03866503   16.64156566  -20.11960752   -3.27642925]]

  [[ -41.18779959    8.41305075  -52.35345346  -46.51516058
      48.20203089    4.82454353   20.35719883   35.94198231]
   [  30.42150901   90.63882904  -25.95882993    8.62160859
     108.48051936   54.34444806  -11.97409565  -15.28450426]
   [ -53.60374511  -80.05025739   58.67535411    5.86996875
     -38.90601022    6.92952065  -56.93950176   10.95374156]]]


 [[[  11.04295674  -51.29538567  -78.34533783  -29.88166331
      48.53786758  -55.61498217   70.91304834   -2.98148387]
   [  15.67013671  101.9608459   -25.78512253   46.2113692
       0.79111516   51.83057976    4.25198298  -76.55831864]
   [ -15.95251537  -26.32560063   70.03015637  -61.38480126
     -61.27283794   16.83619186  -88.33035642  101.64997559]]

  [[ -28.2116401    -2.21187936   40.87145309  -36.82654332
     -92.74568755   10.0069149    47.36069285   42.3576569 ]
   [ -13.59411912   42.15270286  -54.6036216    21.53819946
      29.12158802   33.25062589   -4.03293776    9.83912874]
   [  20.6598245   -46.63713292  -48.39657288  113.71349443
    -107.23204237   -8.60012475    7.94910473  -64.88493673]]]]
卷积层偏置项反向传播的梯度: [[[[ -55.79936968  -90.66820199 -175.22062232   53.58215445
     119.26937661  -93.45780871 -115.32030921  -56.94879437]]]]

import numpy as np


def mask1(x):
    mask = (x == np.max(x))
    return mask


def distribute_value(dz, shape):
    (n_H, n_W) = shape
    average = dz / (n_H * n_W)
    a = np.ones(shape) * average
    return a


def pool_forward(A_prev, hparameters, mode="max"):
    (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
    f = hparameters["f"]
    stride = hparameters["stride"]
    # 计算输出数据的维度
    n_H = int(1 + (n_H_prev - f) / stride)
    n_W = int(1 + (n_W_prev - f) / stride)
    n_C = n_C_prev
    # 定义输出结果
    A = np.zeros((m, n_H, n_W, n_C))
    # 逐个计算,对A的元素进行赋值
    for i in range(m):  # 遍历样本
        for h in range(n_H):  # 遍历n_H维度
            # 确定分片垂直方向上的位置
            vert_start = h * stride
            vert_end = vert_start + f

            for w in range(n_W):  # 遍历n_W维度
                # 确定分片水平方向上的位置
                horiz_start = w * stride
                horiz_end = horiz_start + f

                for c in range(n_C):  # 遍历通道
                    # 确定当前样本上的分片
                    a_prev_slice = A_prev[i, vert_start:vert_end, horiz_start:horiz_end, c]
                    # 根据池化方式,计算当前分片上的池化结果
                    if mode == "max":  # 最大池化
                        A[i, h, w, c] = np.max(a_prev_slice)
                    elif mode == "average":  # 平均池化
                        A[i, h, w, c] = np.mean(a_prev_slice)

    # 将池化层的输入和超参数缓存
    cache = (A_prev, hparameters)

    # 确保输出结果维度正确
    assert (A.shape == (m, n_H, n_W, n_C))

    return A, cache


def pool_backward(dA, cache, mode="max"):
    (A_prev, hparameters) = cache

    stride = hparameters['stride']
    f = hparameters['f']

    m, n_H_prev, n_W_prev, n_C_prev = A_prev.shape
    m, n_H, n_W, n_C = dA.shape

    # 对输出结果进行初始化
    dA_prev = np.zeros_like(A_prev)

    for i in range(m):  # 遍历m个样本
        a_prev = A_prev[i]

        for h in range(n_H):  # 在垂直方向量遍历
            for w in range(n_W):  # 在水平方向上循环
                for c in range(n_C):  # 在通道上循环
                    # 找到输入的分片的边界
                    vert_start = h * stride
                    vert_end = vert_start + f
                    horiz_start = w * stride
                    horiz_end = horiz_start + f

                    # 根据池化方式选择不同的计算过程
                    if mode == "max":
                        # 确定输入数据的切片
                        a_prev_slice = a_prev[vert_start:vert_end, horiz_start:horiz_end, c]
                        # 创建掩码
                        mask = mask1(a_prev_slice)
                        # 计算dA_prev
                        dA_prev[i, vert_start: vert_end, horiz_start: horiz_end, c] += np.multiply(mask, dA[i, h, w, c])

                    elif mode == "average":
                        # 获取da值, 一个实数
                        da = dA[i, h, w, c]
                        shape = (f, f)
                        # 反向传播
                        dA_prev[i, vert_start: vert_end, horiz_start: horiz_end, c] += distribute_value(da, shape)

    assert (dA_prev.shape == A_prev.shape)

    return dA_prev


np.random.seed(1)
A_prev = np.random.randn(5, 5, 3, 2)
hparameters = {"stride": 1, "f": 2}
A, cache = pool_forward(A_prev, hparameters)
dA = np.random.randn(5, 4, 2, 2)

dA_prev = pool_backward(dA, cache, mode="max")
print("============最大池化========")
print('反向传播梯度 ', dA)
dA_prev = pool_backward(dA, cache, mode="average")
print("===========平均池化========")
print('反向传播梯度', dA)

在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值