深度学习中的卷积和反卷积(三)——卷积和反卷积的计算

news2025/1/14 18:10:45

1 Stride和Padding的介绍

计算卷积和反卷积绕不开stride和padding的讨论。卷积和反卷积里都有stride和padding参数,但是同一个参数在卷积和反卷积里的作用不一样,非常容易使人困惑,本文试图理清他们的关系,并用实际数值例子演示计算过程。

对于Stride而言,在卷积中,指卷积核与原始图像做卷积操作时的步长。可以想象卷积核是体检时做彩超的仪器,原始图是身体,输出图像是彩超图。步长为1表示医生每次移动仪器的幅度较小,扫描得很精细;步长较大表示医生每次移动仪器的幅度较大。在反卷积中,控制了输入图像元素之间填充0的个数。注意反卷积中的卷积运算步长固定为1。

对于Padding而言,在卷积中,指在原始图像四周填充上0,填充主要是为了保留原始图的边缘信息。在反卷积中,控制了输入图像四周填充0的个数。

StridePadding
卷积卷积步长控制四周填充0的个数
反卷积内部填充0的个数控制四周填充0的个数

2 卷积和反卷积中各维度关系

2.1 符号表示

符号含义
i输入维度
o输出维度
k卷积核维度
sStride, 步长
p表示某一方向padding的个数
pu/pd/pl/pr表示上下左右(up,down,left,right)padding的个数。当需要特别说明具体方向时用此符号

2.2 卷积中维度关系

o=\lfloor(i+2p-k)/s+1\rfloor

其中\lfloor\rfloor表示向下取整。

从公式字面意思上理解,首先p是左右侧填充的个数,2倍表示两侧都需要填充(实际中可以指定两侧填充数量不同),则i+2p就表示原始输入在左右侧填充了0的维度。维度i填充为p的矩阵做卷积等价于我们手动把输入矩阵两侧做填充得到的矩阵(维度为i+2p)做卷积。

k表示卷积核大小,从下图可以看出要扣除卷积核一个“身位”的大小。s表示步长,步长与输出维度有倍数的关系,步长越长,卷积时迈的步子越大,输出维度越低。向下取整是因为式子不一定可以整除,需要向下取整,在用卷积核对输入图像扫描时,如果剩余图像不够一个卷积核运算就舍弃了。这部分请大家自己动手画一画,文字很难解释清楚。

下面用一张图说明上式。

以5*5图像,填充为1,步长为2,卷积核3*3为例,求o。只看水平方向。填充后,图像宽度为7,其中蓝色部分是原始图像,绿色部分是填充的0。

最终图像宽度为o,因此一共有o次卷积操作。观察卷积核里的最左侧点A,这是卷积核的头,剩下2个圆圈是卷积核的身体。第一次在位置1,第二次在位置3,第三次在位置5,一般地,第o次位置在1+(o-1)*s,此时的位置是5,需要加上卷积核身体长度k-1才是原始图像的宽度。所以1+(o-1)*s+k-1=i+2p,得证o=(i+2p-k)/s+1

拓展一下,假设原始图像维度还可以增大,做3次卷积后剩余图像要求不够做一次卷积(否则就可以做第4次卷积了),假设增大的维度为a

那么1+(o-1)*s+k-1+a=i+2p,其中a<s。则o=(i+2p-k-a)/s+1,且右式可以整除。

(i+2p-k-a)/s=(i+2p-k)/s-a/s,注意到a/s是个小数,所以(i+2p-k)/s小数部分就等于a/s, 整数部分就是(i+2p-k)/s向下取整,整数部分还可以表示成(i+2p-k)/s-a/s,因此(i+2p-k)/s向下取整就等于(i+2p-k-a)/s。所以o为(i+2p-k)/s向下取整+1

此外还有一种表示方法:

o=\lceil(i+2p-k+1)/s\rceil

其中\lceil\rceil表示向上取整。

下面简要说明二者等价。

s=1时,二者显然相等。

s>=2时:

情况1:若(i+2p-k)/s能正好整除,则在式二中,多加了1/s一定是小数,需要去除,因此向上取整后与式一相同。

情况2:若(i+2p-k)/s不能正好整除,则加1向下取整等价于不加1向上取整。即(i+2p-k)/s向上取整。注意(i+2p-k)/s向上取整和(i+2p-k+1)/s向上取整是等价的,分别记这2个式子为A和B,因为A式分子分母除不尽,A式等于相除后的整数部分+1。A式中分子分母都是整数,假设分子必须加上a才能使得分子和分母整除。那么a一定是一个正整数,1<=a,因此B式中分子加了1,小数部分只是更接近下一个整数而已,最多能使分子分母除尽从而使整数部分加1。因此二者相等。

2.3 卷积下Padding的模式

在Tensorflow中,Padding除了传入数值,还可以传入VALID, SAME字符串。

在VALID模式下,不做任何填充。

在数值模式下,按用户实际传入的值填充。

在SAME模式下,Tensorflow会自动填充,使得此式满足:o=\lceil i/s\rceil。这里s相当于一个缩放倍数一样,将输入维度缩放成输出维度。

为了使得输出的维度为o,应该填补多少0呢?去掉向下取整符号,以水平维度为例,

o=(i+pl+pr-k)/s+1,得到pl+pr=(o-1)*s+k-i

这里去掉了向下取整符号,是假设括号内的部分可以正好除尽s,这好理解,因为我们填补0,正好填补到“够用”即可,没必要多填补用不到的部分。

如果上式右侧为偶数,则左侧和右侧各一半,如果上式右侧为奇数,则右侧多一个。即:

pl=[(o-1)*s+k-i]/2向下取整

pl=[(o-1)*s+k-i]/2向上取整

垂直维度同理,如果总填充数为奇数,则下侧多一个。

2.4 反卷积中维度关系

先回顾反卷积的计算流程

在反卷积中,有几点需要注意:

(1)Stride和Padding仅影响输入矩阵填充0的方式,Stride影响元素内部填充,Padding影响输入四周填充。最后一步做卷积运算时,Stride固定为1,Padding固定不填充(或者说前边的步骤已经填充过了)

(2)如果对反卷积的输出矩阵按照正向的Stride和Padding做卷积操作,结果的维度应等于反卷积的输入矩阵。这体现了卷积和反卷积是逆向过程。即如果反卷积的输入是i,输出是o,则应满足

i=\lfloor(o+2p-k)/s+1\rfloor

(3)反卷积需要指定输出的维度大小,在Tensorflow中参数名是output_shape。因为满足上式中条件的o可能不止一种。例如假设Padding=VALID表示不填充,k=2, s=3, i=2.即2=\lfloor(o-2)/3+1\rfloor,则o=5,6,7均可满足,所以传入output_shape可以是5、6、7维。

3 卷积和反卷积的计算

本节举数值例子演示卷积和反卷积计算过程,并使用tensorflow代码验证。

Tensorflow中有很多函数可以实现卷积和反卷积,甚至可以说有点眼花缭乱。可参考conv neural network - What does TensorFlow's `conv2d_transpose()` operation do? - Stack Overflow

本文主要使用tf.nn.conv2d和tf.nn.conv2d_transpose实现卷积和反卷积。关于函数详细用法可参考文档或其他博文。下文也会调用这2个函数,读者也可参考本文的使用方法。

3.1 卷积的计算

假设输入为3*3,卷积核为2*2

 数值的例子为:

 根据卷积的定义,输出为

y_{11}=a_{11}*k_{11}+a_{12}*k_{12}+a_{21}*k_{21}+a_{22}*k_{22}

y_{12}=a_{12}*k_{11}+a_{13}*k_{12}+a_{22}*k_{21}+a_{23}*k_{22}

y_{21}=a_{21}*k_{11}+a_{22}*k_{12}+a_{31}*k_{21}+a_{32}*k_{22}

y_{22}=a_{22}*k_{11}+a_{23}*k_{12}+a_{32}*k_{21}+a_{33}*k_{22}

所以卷积本质上还是y=\sum{w_{i}x_{i}}的形式,可以看做全连接层,只是中间很多权重为0。计算机内部可用矩阵而非循环运算,提高运算速度。这里的矩阵是一个稀疏矩阵(很多元素为0),以y_{11}为例,它只与a_{11},a_{12},a_{21},a_{22}有关,但完全可以把其他5个a写上,只是系数为0而已,这样就可以转化成矩阵相乘。

输出第一项37是由两个红色虚框中的元素对应相乘之和求出。

在Tensorflow中可以验证

import numpy as np
import tensorflow as tf

x = tf.constant(np.arange(1, 10).reshape([1, 3, 3, 1]), dtype=tf.float32)
filters = tf.constant(np.arange(1, 5).reshape(2, 2, 1, 1), dtype=tf.float32)
print(tf.nn.conv2d(x, filters, [1, 1, 1, 1], "VALID").numpy().reshape([2, 2]))

输出为

[[37. 47.]
 [67. 77.]]

3.2 反卷积的计算

反卷积的算法如下:

第一步,确定输出的大小。输出大小output_shape是由用户指定的,需要满足下表所示条件,不同Padding模式下是不同的。

第二步,确定最后一步做卷积操作时需要的元素数。最后一步卷积是按照不填充、步长为1运算的,因此很容易求出总共需要的元素数为o+k-1。则需要填充的个数为o+k-1-i

第三步,元素之间填充0。每个元素之间填充(s-1)个0。一共需要填充(i-1)次。

第四步,元素四周填充0。注意,网络对这里的填充规则说法不一,笔者在Tensorflow中验算,发现规则如下:在Padding=VALID和SAME模式下,左侧固定填充(k-1)个0,在数值模式下,左侧填充(k-1-pl)个0,如果此式小于0,还要对输入矩阵做裁剪,比如-1表示输入矩阵最左侧一列要删除。如果删除的列数超过了整体矩阵的列数,则整体矩阵删除,最终输出为空。总填充数减去左侧填充和内部填充,得到右侧填充数。上侧和下侧的规则同理。

第五步,将卷积核旋转180度,按照步长为1,不填充进行卷积运算。

这其中的要点总结如下:

Padding=VALIDPadding=SAMEPadding为数值
反卷积的输出大小满足⌈(o-k+1)/s⌉=i,是不定的满足⌈0/s⌉=i,是不定的满足⌈(0+p1+p2-k+1)/s⌉=i,是不定的
最后一步做卷积操作时需要的元素数o+k-1o+k-1o+k-1
总需要填充个数[1]o+k-1-io+k-1-io+k-1-i
输入矩阵元素之间填充0个数[2](s-1)*(i-1)(s-1)*(i-1)(s-1)*(i-1)
输入矩阵左侧填充0个数[3]k-1k-1k-1-pl
输入矩阵右侧填充0个数[1]-[2]-[3][1]-[2]-[3][1]-[2]-[3]
输入矩阵上侧填充0个数[4]k-1k-1k-1-pad_top
输入矩阵下侧填充0个数[1]-[2]-[4][1]-[2]-[4][1]-[2]-[4]

 在上一篇笔者以数值例子计算了反卷积,下面验证使用tensorflow计算,结果是否一致。

import tensorflow as tf
import numpy as np

a = tf.constant(np.arange(1, 10).reshape([1, 3, 3, 1]), dtype=tf.float32)
kernel = np.array([1, 0, -1, 0, 2, 0, 1, 0, -1]).reshape([3, 3])

kernel = tf.constant(kernel.reshape([3, 3, 1, 1]), dtype=tf.float32)


res = tf.nn.conv2d_transpose(a, kernel, output_shape=[1, 6, 6, 1], strides=[1, 2, 2, 1])
print(res.numpy().reshape([6, 6]))

可以发现结果一致。

[[ 1.  0.  1.  0.  1.  0.]
 [ 0.  2.  0.  4.  0.  6.]
 [ 5.  0.  2.  0.  2.  0.]
 [ 0.  8.  0. 10.  0. 12.]
 [11.  0.  2.  0.  2.  0.]
 [ 0. 14.  0. 16.  0. 18.]]

 按照反卷积的逻辑,手动填充图像用正向卷积的方式计算一下,看看结果是不是一致。

import tensorflow as tf
import numpy as np



b = tf.constant(np.array([[0, 0, 0, 0, 0, 0, 0, 0],
                          [0, 0, 0, 0, 0, 0, 0, 0],
                          [0, 0, 1, 0, 2, 0, 3, 0],
                          [0, 0, 0, 0, 0, 0, 0, 0],
                          [0, 0, 4, 0, 5, 0, 6, 0],
                          [0, 0, 0, 0, 0, 0, 0, 0],
                          [0, 0, 7, 0, 8, 0, 9, 0],
                          [0, 0, 0, 0, 0, 0, 0, 0]
                          ]).reshape([1, 8, 8, 1]), dtype=tf.float32)

kernel = np.array([1, 0, -1, 0, 2, 0, 1, 0, -1]).reshape([3, 3])
kernel2 = np.flip(kernel)  # 注意反卷积核和正向卷积核是reverse的关系
kernel2 = tf.constant(kernel2.reshape([3, 3, 1, 1]), dtype=tf.float32)
res = tf.nn.conv2d(b, kernel2, strides=[1, 1, 1, 1], padding="VALID")
print(res.numpy().reshape([6, 6]))

结果也是一致的。

[[ 1.  0.  1.  0.  1.  0.]
 [ 0.  2.  0.  4.  0.  6.]
 [ 5.  0.  2.  0.  2.  0.]
 [ 0.  8.  0. 10.  0. 12.]
 [11.  0.  2.  0.  2.  0.]
 [ 0. 14.  0. 16.  0. 18.]]

4 Tensorflow自己动手实现反卷积

根据前文总结的反卷积计算逻辑,本节自己动手实现反卷积的逻辑,并与上一小节Tensorflow中的反卷积函数对比,验证计算逻辑。

4.1 引入模块并定义输入矩阵和卷积核

import math

import tensorflow as tf
import numpy as np

x2 = tf.constant(np.array([[1, 2],
                           [3, 4]]).reshape([1, 2, 2, 1]), dtype=tf.float32)
x3 = tf.constant(np.array([[1, 2, 3],
                           [4, 5, 6],
                           [7, 8, 9]]).reshape([1, 3, 3, 1]), dtype=tf.float32)
x4 = tf.constant(np.array([[1, 2, 3, 4],
                           [5, 6, 7, 8],
                           [9, 10, 11, 12],
                           [13, 14, 15, 16]]).reshape([1, 4, 4, 1]), dtype=tf.float32)
k2 = tf.constant(np.array([[1, 2],
                           [3, 4]]).reshape(2, 2, 1, 1), dtype=tf.float32)
k3 = tf.constant(np.array([[1, 2, 3],
                           [4, 5, 6],
                           [7, 8, 9]]).reshape(3, 3, 1, 1), dtype=tf.float32)
k4 = tf.constant(np.array([[1, 2, 3, 4],
                           [5, 6, 7, 8],
                           [9, 10, 11, 12],
                           [13, 14, 15, 16]]).reshape(4, 4, 1, 1), dtype=tf.float32)

这里定义了3种输入矩阵和3种卷积核,分别是2、3、4维。

4.2 引用Tensorflow中的反卷积计算

def conv2d_transpose(x, k, s, o, padding):
    i = len(x.numpy()[0])
    k_length = len(k.numpy()[0])
    print(f"i={i},k={k_length},i={o},padding={padding}")
    print("Tensorflow计算结果")
    if isinstance(o, list):
        output_shape = [1, o[0], o[1], 1]
    else:
        output_shape = [1, o, o, 1]
    return tf.nn.conv2d_transpose(x, k, output_shape, strides=s,
                                  padding=padding).numpy().reshape([output_shape[1], output_shape[2]])

这里调用了tensorflow中的反卷积,后续会将自己动手实现的函数与此函数对比。

4.3 定义零元素填充功能函数

def padding_zero(matrix, direction, n):
    """
    在matrix的上下左右/内部补0
    Args:
        matrix: 原始矩阵
        direction: 方向,可选left, up, right, down, inside
        n: 补0的个数

    Returns:
        补零后的矩阵
    Examples:
        >>> a = tf.constant(np.array(
        ...                                [[1, 2, 3],
        ...                                 [4, 5, 6],
        ...                                 [7, 8, 9]]).reshape([1, 3, 3, 1]), dtype=tf.float32
        >>> res = padding_zero(a, "up", 2).numpy()
        >>> res.reshape([len(res[0]), len(res[0][0])])
        [[0. 0. 0.]
         [0. 0. 0.]
         [1. 2. 3.]
         [4. 5. 6.]
         [7. 8. 9.]]
        >>> res = padding_zero(a, "inside", 2).numpy()
        >>> res.reshape([len(res[0]), len(res[0][0])])
        [[1. 0. 0. 2. 0. 0. 3.]
         [0. 0. 0. 0. 0. 0. 0.]
         [0. 0. 0. 0. 0. 0. 0.]
         [4. 0. 0. 5. 0. 0. 6.]
         [0. 0. 0. 0. 0. 0. 0.]
         [0. 0. 0. 0. 0. 0. 0.]
         [7. 0. 0. 8. 0. 0. 9.]]
    """
    if n < 0:
        assert direction != "inside"  # inside不缩减零
        return cutting_zero(matrix, direction, -n)
    a = matrix.numpy()  # 维度为1 * x * y * 1
    row_length = matrix.shape[1]
    column_length = matrix.shape[2]
    a = a.reshape([row_length, column_length])
    res = a
    if direction == "up":
        if row_length > 0:
            res = np.pad(a, [[n, 0], [0, 0]], constant_values=0)
            row_length = row_length + n
    elif direction == "down":
        if row_length > 0:
            res = np.pad(a, [[0, n], [0, 0]], constant_values=0)
            row_length = row_length + n
    elif direction == "left":
        if column_length > 0:
            res = np.pad(a, [[0, 0], [n, 0]], constant_values=0)
            column_length = column_length + n
    elif direction == "right":
        if column_length > 0:
            res = np.pad(a, [[0, 0], [0, n]], constant_values=0)
            column_length = column_length + n
    else:
        row_length = (row_length - 1) * n + row_length  # 需要补x-1个位置,每个位置补n个0
        column_length = (column_length - 1) * n + column_length  # 需要补y-1个位置,每个位置补n个0
        res = np.zeros([row_length, column_length])
        x_position = np.arange(0, len(res), n + 1)  # 原矩阵在新矩阵中的位置
        y_position = np.arange(0, len(res[0]), n + 1)  # 原矩阵在新矩阵中的位置
        for i in range(len(x_position)):
            for j in range(len(y_position)):
                res[x_position[i]][y_position[j]] = a[i][j]
    res = tf.constant(np.array(res).reshape([1, row_length, column_length, 1]), dtype=tf.float32)
    return res


def cutting_zero(matrix, direction, n):
    """
    在matrix的上下左右缩减
    Args:
        matrix: 原始矩阵
        direction: 方向,可选left, up, right, down
        n: 缩减行/列数

    Returns:
        缩减后的矩阵
    Examples:
        >>> a = tf.constant(np.array(
        ...                                [[1, 2, 3],
        ...                                 [4, 5, 6],
        ...                                 [7, 8, 9]]).reshape([1, 3, 3, 1]), dtype=tf.float32
        >>> res = cutting_zero(a, "up", 1).numpy()
        >>> res.reshape([len(res[0]), len(res[0][0])])
        [[4. 5. 6.]
         [7. 8. 9.]]
    """
    a = matrix.numpy()  # 维度为1 * x * y * 1
    row_length = len(a[0])
    column_length = len(a[0][0])
    a = a.reshape([row_length, column_length])
    if direction == "up":
        res = a[n:, :]
        row_length = max(row_length - n, 0)
    elif direction == "down":
        res = a[:-n, :]
        row_length = max(row_length - n, 0)
    elif direction == "left":
        res = a[:, n:]
        column_length = max(column_length - n, 0)
    else:
        res = a[:, :-n]
        column_length = max(column_length - n, 0)
    res = tf.constant(np.array(res).reshape([1, row_length, column_length, 1]), dtype=tf.float32)
    return res

定义了零元素填充函数padding_zero,实现在四周、内部填充。注意反卷积有时需要对输入矩阵做裁剪,因此也定义了cutting_zero函数。

4.4 反卷积运算

def conv2d_transpose2(x, filters, s, o, padding):
    print("手算结果")
    i = len(x.numpy()[0])
    k = len(filters.numpy()[0])
    if isinstance(o, list):
        output_shape = [1, o[0], o[1], 1]
    else:
        output_shape = [1, o, o, 1]
    # 检查传入的i是否合适
    if padding == "VALID":
        assert math.ceil((o - k + 1) / s) == i
    elif padding == "SAME":
        assert o == i * s
    else:
        assert i == math.ceil((output_shape[1] + sum(padding[1]) - k + 1) / s)
        assert i == math.ceil((output_shape[2] + sum(padding[2]) - k + 1) / s)
    # 在x中间补0的个数
    num_of_zero_inside = s - 1
    if padding in ["SAME", "VALID"]:
        # 做卷积需要的维度数
        i_padding = o + k - 1
        # 左、上补0的个数:k-1
        num_of_zero_left = k - 1
        num_of_zero_up = k - 1
        # 右、下补0的个数
        num_of_zero_right = i_padding - i - num_of_zero_inside * (i - 1) - num_of_zero_left
        num_of_zero_down = i_padding - i - num_of_zero_inside * (i - 1) - num_of_zero_up
    else:
        # 做卷积需要的维度数
        i_padding_row = output_shape[1] + k - 1
        i_padding_column = output_shape[2] + k - 1
        # 左、上补0的个数:k-1-pl或pu
        pad_top = padding[1][0]
        pad_left = padding[2][0]
        num_of_zero_left = k - 1 - pad_left
        num_of_zero_up = k - 1 - pad_top
        # 右、下补0的个数
        num_of_zero_right = i_padding_column - i - num_of_zero_inside * (i - 1) - num_of_zero_left
        num_of_zero_down = i_padding_row - i - num_of_zero_inside * (i - 1) - num_of_zero_up

    x_new = padding_zero(x, "inside", num_of_zero_inside)
    x_new = padding_zero(x_new, "left", num_of_zero_left)
    x_new = padding_zero(x_new, "up", num_of_zero_up)
    x_new = padding_zero(x_new, "right", num_of_zero_right)
    x_new = padding_zero(x_new, "down", num_of_zero_down)
    # 对卷积核做reverse
    filters_numpy = filters.numpy()  # 维度为n*n*1*1
    dim_filters = len(filters_numpy)
    filters_reverse = np.flip(filters_numpy.reshape([dim_filters, dim_filters]))  # 翻转
    filters_reverse = tf.constant(filters_reverse.reshape([dim_filters, dim_filters, 1, 1]), dtype=tf.float32)
    # 此处s=1,p=0,判断做卷积时i-k+1应大于0,避免极端情况例如x_new为空
    if x_new.shape[1] - filters_reverse.shape[0] + 1 > 0 and x_new.shape[2] - filters_reverse.shape[1] + 1 > 0:
        res = tf.nn.conv2d(x_new, filters_reverse, strides=1, padding="VALID")
        return res.numpy().reshape([output_shape[1], output_shape[2]])
    else:
        return []

最终实现了反卷积运算函数conv2d_transpose2,下节将此函数与tensorflow自带的函数做对比。

4.5 实例验证

"""
VALID模式
"""
print("VALID模式")
print(conv2d_transpose(x3, k2, 1, 4, "VALID"))
print(conv2d_transpose2(x3, k2, 1, 4, "VALID"))

print(conv2d_transpose(x4, k2, 1, 5, "VALID"))
print(conv2d_transpose2(x4, k2, 1, 5, "VALID"))

print(conv2d_transpose(x2, k2, 2, 4, "VALID"))
print(conv2d_transpose2(x2, k2, 2, 4, "VALID"))

print(conv2d_transpose(x2, k2, 2, 5, "VALID"))
print(conv2d_transpose2(x2, k2, 2, 5, "VALID"))

print(conv2d_transpose(x3, k2, 2, 6, "VALID"))
print(conv2d_transpose2(x3, k2, 2, 6, "VALID"))

print(conv2d_transpose(x2, k2, 3, 6, "VALID"))
print(conv2d_transpose2(x2, k2, 3, 6, "VALID"))

print(conv2d_transpose(x3, k2, 4, 10, "VALID"))
print(conv2d_transpose2(x3, k2, 4, 10, "VALID"))

print(conv2d_transpose(x3, k2, 4, 11, "VALID"))
print(conv2d_transpose2(x3, k2, 4, 11, "VALID"))

print(conv2d_transpose(x3, k2, 4, 12, "VALID"))
print(conv2d_transpose2(x3, k2, 4, 12, "VALID"))

print(conv2d_transpose(x3, k2, 4, 13, "VALID"))
print(conv2d_transpose2(x3, k2, 4, 13, "VALID"))

print(conv2d_transpose(x2, k3, 8, 17, "VALID"))
print(conv2d_transpose2(x2, k3, 8, 17, "VALID"))

"""
SAME模式
"""
print("SAME模式")
print(conv2d_transpose(x3, k2, 1, 3, "SAME"))
print(conv2d_transpose2(x3, k2, 1, 3, "SAME"))

print(conv2d_transpose(x3, k2, 2, 6, "SAME"))
print(conv2d_transpose2(x3, k2, 2, 6, "SAME"))

print(conv2d_transpose(x3, k2, 3, 9, "SAME"))
print(conv2d_transpose2(x3, k2, 3, 9, "SAME"))

print(conv2d_transpose(x3, k2, 4, 12, "SAME"))
print(conv2d_transpose2(x3, k2, 4, 12, "SAME"))

print(conv2d_transpose(x3, k3, 8, 24, "SAME"))
print(conv2d_transpose2(x3, k3, 8, 24, "SAME"))

"""
数值模式
"""

pad_top = 8
pad_bottom = 0
pad_left = 0
pad_right = 0
print(conv2d_transpose(x2, k3, 3, [0, 6], [[0, 0], [pad_top, pad_bottom],
                                           [pad_left, pad_right], [0, 0]]))
print(conv2d_transpose2(x2, k3, 3, [0, 6], [[0, 0], [pad_top, pad_bottom],
                                            [pad_left, pad_right], [0, 0]]))

pad_top = 1
pad_bottom = 2
pad_left = 3
pad_right = 2
print(conv2d_transpose(x2, k3, 3, [3, 1], [[0, 0], [pad_top, pad_bottom],
                                           [pad_left, pad_right], [0, 0]]))
print(conv2d_transpose2(x2, k3, 3, [3, 1], [[0, 0], [pad_top, pad_bottom],
                                            [pad_left, pad_right], [0, 0]]))

pad_top = 1
pad_bottom = 5
pad_left = 3
pad_right = 4
# (i+k-1)/s=2
print(conv2d_transpose(x2, k4, 3, [1, 1], [[0, 0], [pad_top, pad_bottom],
                                           [pad_left, pad_right], [0, 0]]))
print(conv2d_transpose2(x2, k4, 3, [1, 1], [[0, 0], [pad_top, pad_bottom],
                                            [pad_left, pad_right], [0, 0]]))

代码对比了各种模式下、各种参数下Tensorflow的结果与自己动手计算的结果。例如其中一处输出如下图所示。

经过对比所有的输出均一致,说明笔者的理解是正确的。

SAME模式
i=3,k=2,i=3,padding=SAME
Tensorflow计算结果
[[ 1.  4.  7.]
 [ 7. 23. 33.]
 [19. 53. 63.]]
手算结果
[[ 1.  4.  7.]
 [ 7. 23. 33.]
 [19. 53. 63.]]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2276590.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网页美观进阶:每一种渐变的实现方式

CSS 渐变效果详解&#xff1a;每一种渐变的实现方式 在现代网页设计中&#xff0c;CSS 渐变效果为我们提供了一种丰富的视觉表现手段&#xff0c;可以使简单的背景或元素具有动态和立体感。渐变从单一颜色转变为另一种颜色&#xff0c;为网站增添了活力与美感。在这篇博文中&a…

Mac MySQL 8.0.30的安装(保姆级教程)

目录预览&#xff1a; 一、下载及安装1.下载2.安装 二、环境变量配置1.编辑文件2.添加配置3.配置生效4.版本查看 三、启动1.MySQL服务的启停和状态的查看2.启动mysql2.1 查看服务状态2.2 Mysql关掉重启2.2.1 查看进程2.2.2 杀死进程2.2.3 验证进程是否成功杀死2.2.4 重新启动My…

Linux服务器网络丢包场景及解决办法

一、Linux网络丢包概述 在数字化浪潮席卷的当下&#xff0c;网络已然成为我们生活、工作与娱乐不可或缺的基础设施&#xff0c;如同空气般&#xff0c;无孔不入地渗透到各个角落。对于 Linux 系统的用户而言&#xff0c;网络丢包问题却宛如挥之不去的 “噩梦”&#xff0c;频繁…

浅谈云计算09 | 服务器虚拟化

服务器虚拟化基础 一、虚拟化的定义二、系统虚拟化三、服务器虚拟化的核心要义四、典型实现&#xff1a;探索不同路径五、全虚拟化与半虚拟化六、主流服务器虚拟化技术 一、虚拟化的定义 虚拟化是一种将物理资源抽象为逻辑资源的技术&#xff0c;通过在物理硬件与操作系统、应…

traceroute原理探究

文章中有截图&#xff0c;看不清的话&#xff0c;可以把浏览器显示比例放大到200%后观看。 linux下traceroute的原理 本文通过抓包观察一下linux下traceroute的原理 环境&#xff1a;一台嵌入式linux设备&#xff0c;内网ip是192.168.186.195&#xff0c;其上有192.168.202.…

uni-app无限级树形组件简单实现

因为项目一些数据需要树形展示&#xff0c;但是官网组件没有。现在简单封装一个组件在app中使用&#xff0c;可以无线嵌套&#xff0c;展开&#xff0c;收缩&#xff0c;获取子节点数据等。 简单效果 组件TreeData <template><view class"tree"><te…

4种革新性AI Agent工作流设计模式全解析

文章目录 导读&#xff1a;AI Agent的四种关键设计模式如下&#xff1a;1. 反思2. 工具使用3. 规划4. 多Agent协作 总结内容简介&#xff1a; 导读&#xff1a; AI Agent是指能够在特定环境中自主执行任务的人工智能系统&#xff0c;不仅接收任务&#xff0c;还自主制定和执行…

GO语言实现KMP算法

前言 本文结合朱战立教授编著的《数据结构—使用c语言&#xff08;第五版&#xff09;》&#xff08;以下简称为《数据结构&#xff08;第五版&#xff09;朱站立》&#xff09;中4.4.2章节内容编写&#xff0c;KMP的相关概念可参考此书4.4.2章节内容。原文中代码是C语言&…

Trimble自动化激光监测支持历史遗产实现可持续发展【沪敖3D】

故事桥&#xff08;Story Bridge&#xff09;位于澳大利亚布里斯班&#xff0c;建造于1940年&#xff0c;全长777米&#xff0c;横跨布里斯班河&#xff0c;可载汽车、自行车和行人往返于布里斯班的北部和南部郊区。故事桥是澳大利亚最长的悬臂桥&#xff0c;是全世界两座手工建…

深度学习笔记11-优化器对比实验(Tensorflow)

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 目录 一、导入数据并检查 二、配置数据集 三、数据可视化 四、构建模型 五、训练模型 六、模型对比评估 七、总结 一、导入数据并检查 import pathlib,…

MySQL 16 章——变量、流程控制和游标

一、变量 在MySQL数据库的存储过程和存储函数中&#xff0c;可以使用变量来存储查询或计算的中间结果数据&#xff0c;或者输出最终的结果数据 在MySQL数据库中&#xff0c;变量分为系统变量和用户自定义变量 &#xff08;1&#xff09;系统变量 1.1.1系统变量分类 变量由…

【HTML+CSS+JS+VUE】web前端教程-13-Form表单

表单在web网页中用来给用户填写信息,从而能采用户信息,使网页具有交互的功能, 所有的用户输入内容的地方都用表单来写,如登录注册、搜索框。 表单是由容器和控件组成的,一个表单一般应该包含用户填写信息的输入框,提交按钮等,这些输入框,按钮叫做控件,表单就是容器,他…

LabVIEW滤波器功能

程序通过LabVIEW生成一个带噪声的正弦波信号&#xff0c;并利用滤波器对其进行信号提取。具体来说&#xff0c;它生成一个正弦波信号&#xff0c;叠加高频噪声后形成带噪信号&#xff0c;再通过低通滤波器滤除噪声&#xff0c;提取原始正弦波信号。整个过程展示了信号生成、噪声…

linux: 文本编辑器vim

文本编辑器 vi的工作模式 (vim和vi一致) 进入vim的方法 方法一:输入 vim 文件名 此时左下角有 "文件名" 文件行数,字符数量 方法一: 输入 vim 新文件名 此时新建了一个文件并进入vim,左下角有 "文件名"[New File] 灰色的长方形就是光标,输入文字,左下…

Java Web开发进阶——错误处理与日志管理

错误处理和日志管理是任何生产环境中不可或缺的一部分。在 Spring Boot 中&#xff0c;合理的错误处理机制不仅能够提升用户体验&#xff0c;还能帮助开发者快速定位问题&#xff1b;而有效的日志管理能够帮助团队监控应用运行状态&#xff0c;及时发现和解决问题。 1. 常见错误…

《零基础Go语言算法实战》【题目 2-25】goroutine 的执行权问题

《零基础Go语言算法实战》 【题目 2-25】goroutine 的执行权问题 请说明以下这段代码为什么会卡死。 package main import ( "fmt" "runtime" ) func main() { var i byte go func() { for i 0; i < 255; i { } }() fmt.Println("start&quo…

Ubuntu系统Qt的下载、安装及入门使用,图文详细,内容全面

文章目录 说明1 在线安装2 离线安装3 使用Qt Creator创建Qt应用程序并构建运行补充补充一&#xff1a;注册Qt账号 说明 本文讲解Ubuntu系统下安装Qt&#xff0c;包括在线安装和离线安装两种方式&#xff0c;内容充实细致&#xff0c;话多但是没有多余&#xff08;不要嫌我啰嗦…

线形回归与小批量梯度下降实例

1、准备数据集 import numpy as np import matplotlib.pyplot as pltfrom torch.utils.data import DataLoader from torch.utils.data import TensorDataset######################################################################### #################准备若干个随机的x和…

P3884 [JLOI2009] 二叉树问题

题目描述&#xff1a; 如下图所示的一棵二叉树的深度、宽度及结点间距离分别为&#xff1a; - 深度&#xff1a;4 - 宽度&#xff1a;4 - 结点 8 和 6 之间的距离&#xff1a;8 - 结点 7 和 6 之间的距离&#xff1a;3 其中宽度表示二叉树上同一层最多的结点个数&#xff0c;节…

ssm旅游攻略网站设计+jsp

系统包含&#xff1a;源码论文 所用技术&#xff1a;SpringBootVueSSMMybatisMysql 需要源码或者定制看文章最下面或看我的主页 目 录 目 录 III 1 绪论 1 1.1 研究背景 1 1.2 目的和意义 1 1.3 论文结构安排 2 2 相关技术 3 2.1 SSM框架介绍 3 2.2 B/S结构介绍 3 …