多输入多输出通道|卷积神经网络|动手学深度学习

16 阅读14分钟

1. 假设我们有两个卷积核,大小分别为k1k_1k2k_2(中间没有非线性激活函数)。

  1. 证明运算可以用单次卷积来表示。
  2. 这个等效的单个卷积核的维数是多少呢?
  3. 反之亦然吗?

假设我们有两个卷积核,大小分别为 k1k_1k2k_2,在中间没有非线性激活函数。我们将证明连续的两次卷积操作可以用单次卷积操作来表示,并确定等效的单个卷积核的维数。

1. 证明运算可以用单次卷积来表示

考虑一个输入信号 XX,它首先通过卷积核 k1k_1 进行卷积得到中间结果 MM,然后 MM 再通过卷积核 k2k_2 进行卷积得到最终输出 YY。数学上,这可以表示为:

M=Xk1M = X \ast k_1 Y=Mk2=(Xk1)k2Y = M \ast k_2 = (X \ast k_1) \ast k_2

由于卷积运算具有结合律,即:

(Xk1)k2=X(k1k2)(X \ast k_1) \ast k_2 = X \ast (k_1 \ast k_2)

我们可以将两次卷积操作合并为一次卷积操作,其中新的卷积核 kk 是原来的两个卷积核 k1k_1k2k_2 的卷积结果:

k=k1k2k = k_1 \ast k_2

因此,运算可以用单次卷积来表示:

Y=XkY = X \ast k

2. 等效的单个卷积核的维数

假设 k1k_1k2k_2 的大小分别为 (m1×n1)(m_1 \times n_1)(m2×n2)(m_2 \times n_2),则等效的单个卷积核 kk 的大小为 (m1+m21)×(n1+n21)(m_1 + m_2 - 1) \times (n_1 + n_2 - 1)

这是因为卷积核的卷积(即两个核的交互)会导致核的尺寸增大,其大小由原来两个核的大小决定:

Size(k)=Size(k1)+Size(k2)1\text{Size}(k) = \text{Size}(k_1) + \text{Size}(k_2) - 1

3. 反之亦然吗?

反之,即将一个卷积核分解为两个较小的卷积核,通常是不可行的。这是因为:

  • 将一个卷积操作分解为两个连续的卷积操作实际上是一种分解操作,这相当于在信号处理或矩阵运算中的因式分解。
  • 一般情况下,卷积核没有唯一的分解形式,尤其是对高维信号或大尺寸核的分解可能不存在或不唯一。

虽然对于特定的卷积核,可能存在一些特定的分解方法(例如在某些特定情况下,利用SVD分解核矩阵),但这些方法依赖于卷积核的特性,无法作为通用的方法来适用所有卷积核。

总结起来:

  1. 证明运算可以用单次卷积来表示:两个卷积核的连续卷积可以合并为一个卷积核。
  2. 等效的单个卷积核的维数:新卷积核的维数为 (m1+m21)×(n1+n21)(m_1 + m_2 - 1) \times (n_1 + n_2 - 1)
  3. 反之亦然吗:一般情况下不可行,因为卷积核的分解在数学上并没有唯一的解。

2. 假设输入为ci×h×wc_i\times h\times w,卷积核大小为co×ci×kh×kwc_o\times c_i\times k_h\times k_w,填充为(ph,pw)(p_h, p_w),步幅为(sh,sw)(s_h, s_w)

  1. 前向传播的计算成本(乘法和加法)是多少?
  2. 内存占用是多少?
  3. 反向传播的内存占用是多少?
  4. 反向传播的计算成本是多少?

1. 前向传播的计算成本(乘法和加法)

假设输入张量 XX 的形状为 (ci,h,w)(c_i, h, w),卷积核 KK 的形状为 (co,ci,kh,kw)(c_o, c_i, k_h, k_w),填充为 (ph,pw)(p_h, p_w),步幅为 (sh,sw)(s_h, s_w)。卷积运算后,输出张量 YY 的形状为 (co,ho,wo)(c_o, h_o, w_o),其中:

ho=h+2phkhsh+1h_o = \left\lfloor \frac{h + 2p_h - k_h}{s_h} \right\rfloor + 1 wo=w+2pwkwsw+1w_o = \left\lfloor \frac{w + 2p_w - k_w}{s_w} \right\rfloor + 1

对于每个输出元素 Yco,ho,woY_{co, ho, wo} ,需要 ci×kh×kwc_i \times k_h \times k_w 次乘法和 ci×kh×kw1c_i \times k_h \times k_w - 1 次加法。因此,总的乘法和加法次数为:

  • 乘法次数: 乘法次数=co×ho×wo×(ci×kh×kw)\text{乘法次数} = c_o \times h_o \times w_o \times (c_i \times k_h \times k_w)

  • 加法次数: 加法次数=co×ho×wo×(ci×kh×kw1)\text{加法次数} = c_o \times h_o \times w_o \times (c_i \times k_h \times k_w - 1)

2. 内存占用

内存占用主要包括输入张量、卷积核和输出张量的内存。

  • 输入张量 XX 的内存占用: 输入张量=ci×h×w\text{输入张量} = c_i \times h \times w

  • 卷积核 KK 的内存占用: 卷积核=co×ci×kh×kw\text{卷积核} = c_o \times c_i \times k_h \times k_w

  • 输出张量 YY 的内存占用: 输出张量=co×ho×wo\text{输出张量} = c_o \times h_o \times w_o

总的内存占用为: 总内存=ci×h×w+co×ci×kh×kw+co×ho×wo\text{总内存} = c_i \times h \times w + c_o \times c_i \times k_h \times k_w + c_o \times h_o \times w_o

3. 反向传播的内存占用

反向传播时,我们需要存储以下内容:

  1. 输入张量 XXci×h×wc_i \times h \times w
  2. 卷积核 KKco×ci×kh×kwc_o \times c_i \times k_h \times k_w
  3. 输出张量 YYco×ho×woc_o \times h_o \times w_o
  4. 梯度张量 LY\frac{\partial L}{\partial Y}co×ho×woc_o \times h_o \times w_o
  5. 输入梯度 LX\frac{\partial L}{\partial X}ci×h×wc_i \times h \times w
  6. 卷积核梯度 LK\frac{\partial L}{\partial K}co×ci×kh×kwc_o \times c_i \times k_h \times k_w

因此,反向传播的总内存占用为: 反向传播总内存=2×(ci×h×w)+2×(co×ci×kh×kw)+2×(co×ho×wo)\text{反向传播总内存} = 2 \times (c_i \times h \times w) + 2 \times (c_o \times c_i \times k_h \times k_w) + 2 \times (c_o \times h_o \times w_o)

4. 反向传播的计算成本

反向传播时,计算成本主要包括计算梯度的乘法和加法操作:

计算 LX\frac{\partial L}{\partial X}

每个输入元素 LXci,h,w\frac{\partial L}{\partial X_{ci, h, w}} 需要 co×kh×kwc_o \times k_h \times k_w 次乘法和 co×kh×kw1c_o \times k_h \times k_w - 1 次加法。

  • 乘法次数: 乘法次数LX=ci×h×w×(co×kh×kw)\text{乘法次数}_{\frac{\partial L}{\partial X}} = c_i \times h \times w \times (c_o \times k_h \times k_w)

  • 加法次数: 加法次数LX=ci×h×w×(co×kh×kw1)\text{加法次数}_{\frac{\partial L}{\partial X}} = c_i \times h \times w \times (c_o \times k_h \times k_w - 1)

计算 LK\frac{\partial L}{\partial K}

每个卷积核元素 LKco,ci,kh,kw\frac{\partial L}{\partial K_{co, ci, kh, kw}} 需要 ho×woh_o \times w_o 次乘法和 ho×wo1h_o \times w_o - 1 次加法。

  • 乘法次数: 乘法次数LK=co×ci×kh×kw×(ho×wo)\text{乘法次数}_{\frac{\partial L}{\partial K}} = c_o \times c_i \times k_h \times k_w \times (h_o \times w_o)

  • 加法次数: 加法次数LK=co×ci×kh×kw×(ho×wo1)\text{加法次数}_{\frac{\partial L}{\partial K}} = c_o \times c_i \times k_h \times k_w \times (h_o \times w_o - 1)

计算 LY\frac{\partial L}{\partial Y}

这个是已知的损失梯度传递,无需额外计算。

总计算成本

  • 乘法次数: 总乘法次数=ci×h×w×(co×kh×kw)+co×ci×kh×kw×(ho×wo)\text{总乘法次数} = c_i \times h \times w \times (c_o \times k_h \times k_w) + c_o \times c_i \times k_h \times k_w \times (h_o \times w_o)

  • 加法次数: 总加法次数=ci×h×w×(co×kh×kw1)+co×ci×kh×kw×(ho×wo1)\text{总加法次数} = c_i \times h \times w \times (c_o \times k_h \times k_w - 1) + c_o \times c_i \times k_h \times k_w \times (h_o \times w_o - 1)

总结如下:

  1. 前向传播的计算成本(乘法和加法次数):

    • 乘法次数:co×ho×wo×(ci×kh×kw)c_o \times h_o \times w_o \times (c_i \times k_h \times k_w)
    • 加法次数:co×ho×wo×(ci×kh×kw1)c_o \times h_o \times w_o \times (c_i \times k_h \times k_w - 1)
  2. 内存占用ci×h×w+co×ci×kh×kw+co×ho×woc_i \times h \times w + c_o \times c_i \times k_h \times k_w + c_o \times h_o \times w_o

  3. 反向传播的内存占用2×(ci×h×w)+2×(co×ci×kh×kw)+2×(co×ho×wo)2 \times (c_i \times h \times w) + 2 \times (c_o \times c_i \times k_h \times k_w) + 2 \times (c_o \times h_o \times w_o)

  4. 反向传播的计算成本(乘法和加法次数):

    • 乘法次数:ci×h×w×(co×kh×kw)+co×ci×kh×kw×(ho×wo)c_i \times h \times w \times (c_o \times k_h \times k_w) + c_o \times c_i \times k_h \times k_w \times (h_o \times w_o)
    • 加法次数:ci×h×w×(co×kh×kw1)+co×ci×kh×kw×(ho×wo1)c_i \times h \times w \times (c_o \times k_h \times k_w - 1) + c_o \times c_i \times k_h \times k_w \times (h_o \times w_o - 1)

3. 如果我们将输入通道cic_i和输出通道coc_o的数量加倍,计算数量会增加多少?如果我们把填充数量翻一番会怎么样?

让我们详细分析一下输入通道 cic_i 和输出通道 coc_o 数量加倍以及填充数量翻一番对计算数量的影响。

1. 输入通道和输出通道数量加倍

前向传播计算成本

假设原始输入通道数量为 cic_i,输出通道数量为 coc_o。若将输入通道和输出通道数量加倍,新的通道数量分别为 2ci2c_i2co2c_o

前向传播的计算成本(乘法和加法)原始为:

  • 乘法次数:co×ho×wo×(ci×kh×kw)c_o \times h_o \times w_o \times (c_i \times k_h \times k_w)
  • 加法次数:co×ho×wo×(ci×kh×kw1)c_o \times h_o \times w_o \times (c_i \times k_h \times k_w - 1)

新情况下:

  • 乘法次数:2co×ho×wo×(2ci×kh×kw)=4×(co×ho×wo×(ci×kh×kw))2c_o \times h_o \times w_o \times (2c_i \times k_h \times k_w) = 4 \times (c_o \times h_o \times w_o \times (c_i \times k_h \times k_w))
  • 加法次数:2co×ho×wo×(2ci×kh×kw1)=4×(co×ho×wo×(ci×kh×kw))2×(co×ho×wo)2c_o \times h_o \times w_o \times (2c_i \times k_h \times k_w - 1) = 4 \times (c_o \times h_o \times w_o \times (c_i \times k_h \times k_w)) - 2 \times (c_o \times h_o \times w_o)

可以看到,当输入通道和输出通道加倍时,前向传播的乘法和加法次数大约增加了四倍。

内存占用

内存占用原始为: ci×h×w+co×ci×kh×kw+co×ho×woc_i \times h \times w + c_o \times c_i \times k_h \times k_w + c_o \times h_o \times w_o

新情况下: 2ci×h×w+2co×2ci×kh×kw+2co×ho×wo=2×(ci×h×w)+4×(co×ci×kh×kw)+2×(co×ho×wo)2c_i \times h \times w + 2c_o \times 2c_i \times k_h \times k_w + 2c_o \times h_o \times w_o = 2 \times (c_i \times h \times w) + 4 \times (c_o \times c_i \times k_h \times k_w) + 2 \times (c_o \times h_o \times w_o)

当输入通道和输出通道加倍时,内存占用约增加两倍至四倍。

反向传播计算成本

原始反向传播计算成本为:

  • 乘法次数:ci×h×w×(co×kh×kw)+co×ci×kh×kw×(ho×wo)c_i \times h \times w \times (c_o \times k_h \times k_w) + c_o \times c_i \times k_h \times k_w \times (h_o \times w_o)
  • 加法次数:ci×h×w×(co×kh×kw1)+co×ci×kh×kw×(ho×wo1)c_i \times h \times w \times (c_o \times k_h \times k_w - 1) + c_o \times c_i \times k_h \times k_w \times (h_o \times w_o - 1)

新情况下:

  • 乘法次数:2ci×h×w×(2co×kh×kw)+2co×2ci×kh×kw×(ho×wo)=4×(ci×h×w×(co×kh×kw))+4×(co×ci×kh×kw×(ho×wo))2c_i \times h \times w \times (2c_o \times k_h \times k_w) + 2c_o \times 2c_i \times k_h \times k_w \times (h_o \times w_o) = 4 \times (c_i \times h \times w \times (c_o \times k_h \times k_w)) + 4 \times (c_o \times c_i \times k_h \times k_w \times (h_o \times w_o))
  • 加法次数:2ci×h×w×(2co×kh×kw1)+2co×2ci×kh×kw×(ho×wo1)=4×(ci×h×w×(co×kh×kw1))+4×(co×ci×kh×kw×(ho×wo1))2c_i \times h \times w \times (2c_o \times k_h \times k_w - 1) + 2c_o \times 2c_i \times k_h \times k_w \times (h_o \times w_o - 1) = 4 \times (c_i \times h \times w \times (c_o \times k_h \times k_w - 1)) + 4 \times (c_o \times c_i \times k_h \times k_w \times (h_o \times w_o - 1))

当输入通道和输出通道加倍时,反向传播的乘法和加法次数大约增加了四倍。

2. 填充数量翻一番

填充数量翻一番对计算成本的影响如下:

前向传播计算成本

填充数量 (ph,pw)(p_h, p_w) 翻一番后,新填充数量为 (2ph,2pw)(2p_h, 2p_w)

填充只会影响输出特征图的大小 hoh_owow_o,具体公式如下: ho=h+2phkhsh+1h_o = \left\lfloor \frac{h + 2p_h - k_h}{s_h} \right\rfloor + 1 wo=w+2pwkwsw+1w_o = \left\lfloor \frac{w + 2p_w - k_w}{s_w} \right\rfloor + 1

假设原始输出大小为 hoh_owow_o,新情况下填充翻倍后输出大小变为 hoh'_owow'_o,如下: ho=h+4phkhsh+1h'_o = \left\lfloor \frac{h + 4p_h - k_h}{s_h} \right\rfloor + 1 wo=w+4pwkwsw+1w'_o = \left\lfloor \frac{w + 4p_w - k_w}{s_w} \right\rfloor + 1

这意味着输出特征图的大小可能增大,具体增大多少取决于输入大小、卷积核大小和步幅,但不会改变计算的乘法和加法次数的规模性。

内存占用

由于填充增加会导致输出特征图变大,影响如下:

  • 输出张量 YY 的内存占用变大:co×ho×woc_o \times h'_o \times w'_o

但输入张量和卷积核的内存占用保持不变。因此,填充翻倍后内存占用增加量主要取决于输出特征图增大的程度。

反向传播计算成本和内存占用

反向传播计算成本和内存占用同样受输出特征图大小变化影响,具体变化与前向传播类似:

  • 乘法次数:增加比例取决于 hoh'_owow'_o
  • 加法次数:增加比例取决于 hoh'_owow'_o

总结:

  1. 输入通道和输出通道数量加倍

    • 计算数量增加约四倍。
    • 内存占用增加约两倍至四倍。
    • 反向传播计算成本增加约四倍。
  2. 填充数量翻一番

    • 计算数量增加比例取决于输出特征图的增加量。
    • 内存占用增加比例取决于输出特征图的增加量。
    • 反向传播计算成本增加比例取决于输出特征图的增加量。

总体来看,输入通道和输出通道数量加倍对计算成本和内存占用的影响较大,而填充数量翻一番主要影响输出特征图的大小,进而影响计算成本和内存占用。

4. 如果卷积核的高度和宽度是kh=kw=1k_h=k_w=1,前向传播的计算复杂度是多少?

当卷积核的高度和宽度都是 kh=kw=1k_h = k_w = 1 时,我们可以简化卷积操作的计算复杂度分析。对于多输入多输出通道的卷积操作,计算复杂度取决于输入张量的大小、卷积核的大小、输出通道数等参数。

符号说明

  • XX 是输入张量,形状为 (ci,h,w)(c_i, h, w)
  • KK 是卷积核张量,形状为 (co,ci,1,1)(c_o, c_i, 1, 1)
  • YY 是输出张量,形状为 (co,h,w)(c_o, h, w)

前向传播计算复杂度

计算复杂度推导

对于每个输出通道 coc_o 和每个输出位置 (i,j)(i, j) 处的计算,前向传播涉及的乘法和加法如下:

  1. 每个输出位置的计算

    • 每个输出位置 Yco,i,jY_{c_o, i, j} 的计算公式: Yco,i,j=ciXci,i,j×Kco,ci,1,1 Y_{c_o, i, j} = \sum_{c_i} X_{c_i, i, j} \times K_{c_o, c_i, 1, 1}
    • 由于卷积核的大小是 1×11 \times 1,每个输出位置的计算涉及 cic_i 次乘法和 ci1c_i - 1 次加法。
  2. 每个输出通道的计算

    • 每个输出通道有 h×wh \times w 个位置,每个位置需要进行 cic_i 次乘法和 ci1c_i - 1 次加法。
    • 因此,每个输出通道总共需要进行 ci×h×wc_i \times h \times w 次乘法和 (ci1)×h×w(c_i - 1) \times h \times w 次加法。
  3. 所有输出通道的计算

    • 总共有 coc_o 个输出通道,因此总的计算量为: 总乘法次数=co×ci×h×w \text{总乘法次数} = c_o \times c_i \times h \times w 总加法次数=co×(ci1)×h×w \text{总加法次数} = c_o \times (c_i - 1) \times h \times w

计算复杂度总结

当卷积核大小为 1×11 \times 1 时,前向传播的总计算复杂度(包括乘法和加法)可以总结如下:

总计算复杂度=乘法次数+加法次数=co×ci×h×w+co×(ci1)×h×w\text{总计算复杂度} = \text{乘法次数} + \text{加法次数} = c_o \times c_i \times h \times w + c_o \times (c_i - 1) \times h \times w

因为加法次数和乘法次数是同阶的,常见情况下可以简化为主要考虑乘法次数:

总计算复杂度co×ci×h×w\text{总计算复杂度} \approx c_o \times c_i \times h \times w

示例

假设输入张量 XX 的形状为 (3,32,32)(3, 32, 32),卷积核张量 KK 的形状为 (64,3,1,1)(64, 3, 1, 1),那么:

  • ci=3c_i = 3(输入通道数)
  • h=32h = 32(输入高度)
  • w=32w = 32(输入宽度)
  • co=64c_o = 64(输出通道数)

前向传播的总计算复杂度为:

总计算复杂度64×3×32×32=196,608\text{总计算复杂度} \approx 64 \times 3 \times 32 \times 32 = 196,608

这表明在 1×11 \times 1 卷积核情况下,计算复杂度主要依赖于输入输出通道数和输入特征图的尺寸。在这种情况下,计算复杂度相对较低,因此 1×11 \times 1 卷积在深度学习模型中广泛用于降低计算成本和参数量。

5. 本节最后一个示例中的变量Y1Y2是否完全相同?为什么?

def corr2d_multi_in_out(X, K):
    # 迭代“K”的第0个维度,每次都对输入“X”执行互相关运算。
    # 最后将所有结果都叠加在一起
    return torch.stack([corr2d_multi_in(X, k) for k in K], 0)

def corr2d_multi_in_out_1x1(X, K):
    c_i, h, w = X.shape
    c_o = K.shape[0]
    X = X.reshape((c_i, h * w))
    K = K.reshape((c_o, c_i))
    # 全连接层中的矩阵乘法
    Y = torch.matmul(K, X)
    return Y.reshape((c_o, h, w))

X = torch.normal(0, 1, (3, 3, 3))
K = torch.normal(0, 1, (2, 3, 1, 1))

Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
assert float(torch.abs(Y1 - Y2).sum()) < 1e-6

# print(Y1)
# print(Y2)
# assert Y1.shape == Y2.shape
# Y1 == Y2
tensor([[[-1.5243,  1.0310, -1.1528],
         [-0.7533,  0.6093,  0.1063],
         [ 0.4095,  1.3068, -0.2419]],

        [[-1.5065,  1.9989, -2.2026],
         [-1.9817,  1.5140, -0.8929],
         [ 0.3886,  0.2262, -0.2948]]])
tensor([[[-1.5243,  1.0310, -1.1528],
         [-0.7533,  0.6093,  0.1063],
         [ 0.4095,  1.3068, -0.2419]],

        [[-1.5065,  1.9989, -2.2026],
         [-1.9817,  1.5140, -0.8929],
         [ 0.3886,  0.2262, -0.2948]]])

tensor([[[True, True, True],
         [True, True, True],
         [True, True, True]],

        [[True, True, True],
         [True, True, True],
         [True, True, True]]])

示例中的变量 Y1Y2 应该是完全相同的。以下是详细解释为什么它们会相同。

代码实现回顾

函数 corr2d_multi_in_out

这个函数实现了标准的二维卷积操作。对于多输入通道和多输出通道的卷积核,它逐通道地计算输入张量 X 和每个卷积核的卷积,并将结果堆叠在一起。

def corr2d_multi_in_out(X, K):
    return torch.stack([corr2d_multi_in(X, k) for k in K], 0)
  • X 是形状为 (ci,h,w)(c_i, h, w) 的输入张量。
  • K 是形状为 (co,ci,kh,kw)(c_o, c_i, k_h, k_w) 的卷积核张量。
  • corr2d_multi_in(X, k) 对输入 X 和每个卷积核 k 进行多通道二维卷积。
  • 最终结果 Y2 是所有输出通道的卷积结果堆叠在一起,形状为 (co,ho,wo)(c_o, h_o, w_o)

函数 corr2d_multi_in_out_1x1

这个函数专门处理卷积核大小为 1×11 \times 1 的情况。它将卷积操作简化为矩阵乘法。

def corr2d_multi_in_out_1x1(X, K):
    c_i, h, w = X.shape
    c_o = K.shape[0]
    X = X.reshape((c_i, h * w))
    K = K.reshape((c_o, c_i))
    Y = torch.matmul(K, X)
    return Y.reshape((c_o, h, w))
  • X 的形状是 (ci,h,w)(c_i, h, w),卷积核 K 的形状是 (co,ci,1,1)(c_o, c_i, 1, 1)
  • X 被重塑为 (ci,h×w)(c_i, h \times w)
  • K 被重塑为 (co,ci)(c_o, c_i)
  • 使用矩阵乘法计算结果 Y,形状为 (co,h×w)(c_o, h \times w),然后重塑为 (co,h,w)(c_o, h, w)

数学解释

对于 1×11 \times 1 卷积核,卷积操作可以看作对输入张量的每个位置应用一个线性变换(矩阵乘法),这等价于:

Yco,i,j=ciXci,i,j×Kco,ci,1,1Y_{c_o, i, j} = \sum_{c_i} X_{c_i, i, j} \times K_{c_o, c_i, 1, 1}

为什么 Y1Y2 应该相同?

  1. 操作本质一致

    • corr2d_multi_in_out_1x1 中,将 1×11 \times 1 卷积核的卷积运算转化为矩阵乘法,计算每个位置的线性组合。
    • corr2d_multi_in_out 中,使用标准卷积操作计算每个位置的线性组合,卷积核也是 1×11 \times 1
    • 这两种方法本质上都在对输入张量的每个位置应用相同的线性变换。
  2. 精度

    • 数值计算中的浮点运算精度问题可能会引起微小的误差,但这些误差通常在合理范围内,不影响结果的总体一致性。
    • 在这个例子中,assert float(torch.abs(Y1 - Y2).sum()) < 1e-6 验证了 Y1Y2 在数值上几乎完全一致,误差小于 10610^{-6},这表示它们可以被认为是相同的。

验证结果

X = torch.normal(0, 1, (3, 3, 3))
K = torch.normal(0, 1, (2, 3, 1, 1))

Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
assert float(torch.abs(Y1 - Y2).sum()) < 1e-6
  • Y1 是通过矩阵乘法得到的结果。
  • Y2 是通过标准卷积操作得到的结果。
  • assert 语句验证了 Y1Y2 在数值上几乎完全一致。

因此,Y1Y2 在理论上和实现上应该是完全相同的。即使在浮点数运算中可能存在微小的误差,这些误差在实际应用中通常可以忽略不计。

6. 当卷积窗口不是1×11\times 1时,如何使用矩阵乘法实现卷积?

当卷积窗口不是 1×11 \times 1 时,卷积操作依然可以使用矩阵乘法来实现。这种方法称为 im2col 技术(即将图像数据重排列为列),然后将其与卷积核进行矩阵乘法。下面是实现步骤和原理:

基本思想

  1. 将输入张量 X 转换为列矩阵:通过将每个卷积窗口的元素重排列成列,我们可以将卷积操作转换为矩阵乘法。
  2. 将卷积核 K 展开成行矩阵:将卷积核重排列成一个二维矩阵,以便与重排后的输入进行矩阵乘法。
  3. 进行矩阵乘法:通过矩阵乘法计算卷积结果。
  4. 将结果重排回卷积后的形状:将矩阵乘法的结果重排为卷积后的输出形状。

详细步骤

输入张量 X 和卷积核 K 的定义

假设输入张量 X 的形状为 (ci,h,w)(c_i, h, w),卷积核 K 的形状为 (co,ci,kh,kw)(c_o, c_i, k_h, k_w)

1. 将输入张量 X 转换为列矩阵

使用 im2col 技术:

  • 对于输入张量中的每一个卷积窗口(大小为 kh×kwk_h \times k_w),将其展平成一列。
  • 结果是一个新的矩阵,形状为 (ci×kh×kw,h×w)(c_i \times k_h \times k_w, h' \times w'),其中 hh'ww' 是输出张量的高度和宽度。
import torch
import torch.nn.functional as F

def im2col(X, k_h, k_w, stride, padding):
    # Unfold input tensor to columns
    X_unfold = F.unfold(X, kernel_size=(k_h, k_w), stride=stride, padding=padding)
    return X_unfold

# Example
X = torch.randn(1, 3, 5, 5)  # Example input with batch size 1, 3 channels, 5x5 spatial dimensions
k_h, k_w = 3, 3  # Kernel height and width
stride, padding = 1, 1

X_col = im2col(X, k_h, k_w, stride, padding)

2. 将卷积核 K 展开成行矩阵

  • 将卷积核 K 展开为一个二维矩阵,形状为 (co,ci×kh×kw)(c_o, c_i \times k_h \times k_w)
K = torch.randn(2, 3, 3, 3)  # Example kernels with 2 output channels, 3 input channels, 3x3 kernel size
K_reshaped = K.view(2, -1)

3. 进行矩阵乘法

  • 矩阵乘法:将重排后的输入矩阵与卷积核矩阵进行矩阵乘法。
Y_col = K_reshaped @ X_col

4. 将结果重排回卷积后的形状

  • 将矩阵乘法的结果重排为卷积后的输出形状。
# Reshape the result to the output tensor shape
batch_size, _, h_out, w_out = X.shape[0], K.shape[0], (X.shape[2] - k_h + 2 * padding) // stride + 1, (X.shape[3] - k_w + 2 * padding) // stride + 1
Y = Y_col.view(batch_size, -1, h_out, w_out)

完整代码示例

import torch
import torch.nn.functional as F

def im2col(X, k_h, k_w, stride, padding):
    X_unfold = F.unfold(X, kernel_size=(k_h, k_w), stride=stride, padding=padding)
    return X_unfold

# Input tensor and kernel tensor
X = torch.randn(1, 3, 5, 5)  # Batch size 1, 3 channels, 5x5 spatial dimensions
K = torch.randn(2, 3, 3, 3)  # 2 output channels, 3 input channels, 3x3 kernel size

# Parameters
k_h, k_w = 3, 3
stride, padding = 1, 1

# Step 1: Convert input tensor to column matrix
X_col = im2col(X, k_h, k_w, stride, padding)

# Step 2: Reshape kernels to row matrix
K_reshaped = K.view(2, -1)

# Step 3: Perform matrix multiplication
Y_col = K_reshaped @ X_col

# Step 4: Reshape the result back to the output tensor shape
batch_size, _, h_out, w_out = X.shape[0], K.shape[0], (X.shape[2] - k_h + 2 * padding) // stride + 1, (X.shape[3] - k_w + 2 * padding) // stride + 1
Y = Y_col.view(batch_size, -1, h_out, w_out)

print(Y.shape)  # Output should have shape (1, 2, 5, 5) for this example

计算复杂度分析

通过这种方法实现卷积,计算复杂度主要由矩阵乘法的复杂度决定:

  • 将输入转换为列矩阵的复杂度:O(ci×kh×kw×h×w)O(c_i \times k_h \times k_w \times h' \times w')
  • 卷积核重排后的矩阵乘法复杂度:O(co×(ci×kh×kw)×(h×w))O(c_o \times (c_i \times k_h \times k_w) \times (h' \times w'))
  • 重排回输出形状的复杂度:O(co×h×w)O(c_o \times h' \times w')

总体来说,通过矩阵乘法实现卷积在实际应用中能够利用矩阵运算的高度优化和并行计算特性,从而提升计算效率,尤其是在GPU上。