Conv 卷积
pytorch
本文字数:1.8k 字 | 阅读时长 ≈ 8 min

Conv 卷积

pytorch
本文字数:1.8k 字 | 阅读时长 ≈ 8 min

本文主要讲解 PyTorch 中卷积相关的函数,目前总结了 Conv2d, Conv3d

1. Conv2d

nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)

此函数作一个卷积操作,将输入为$(N, ~C_{in}, ~H, ~W)$的输入变为$(N, ~C_{out}, ~H_{out}, ~W_{out})$,其中$N$是batch size,$C$代表通道数,$H$和$W$分别是图像的长和宽

x = torch.randn(1,1,4,4)
l = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, padding=0,stride=1)
y = l(x)
print(y.size())  # torch.Size([1, 1, 2, 2])

''' padding=2,stride=1,kernel_size=4 '''
x = torch.randn(1,1,5,5)
l = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=4, padding=2)
y = l(x) 		 
print(y.size())  # torch.Size([1, 1, 6, 6])

下面图示了一个 4*4 图片,kernel size=4 stride=1 padding=0 的卷积图示(只展示了一个卷积核)

注意:对于 padding 的图,仅仅变化的就是原图,其余的过程不变

1.1 Padding

padding 的一个作用是,当我们用 kernal size=3 stride=1 的卷积核对图片进行操作时,如果不进行 padding,最后的特征图会比原图片小,所以要进行 padding,padding 的方式有四种:zeros, reflect, replicate, circular,下面举例说明其区别

首先我们初始化一个(1, 1, 4, 4)的矩阵,之后都对这个矩阵进行操作,其中卷积的权重我们初始化为 1,卷积核的大小和数量也为 1,这样每次卷积之后输入的矩阵 x 原来的元素是不会发生变化的

x = torch.nn.Parameter(torch.reshape(torch.arange(0,16,dtype=torch.float), (1,1,4,4)))
'''
Parameter containing:
tensor([[[[ 0.,  1.,  2.,  3.],
          [ 4.,  5.,  6.,  7.],
          [ 8.,  9., 10., 11.],
          [12., 13., 14., 15.]]]], requires_grad=True)
'''

zeros

zeros 就是填充的每个元素都为 0

conv = torch.nn.Conv2d(1,1,1,1,padding=1,padding_mode='zeros',bias=False)
conv.weight = torch.nn.Parameter(torch.ones(1,1,1,1))
conv(x)
'''
tensor([[[[ 0.,  0.,  0.,  0.,  0.,  0.],
          [ 0.,  0.,  1.,  2.,  3.,  0.],
          [ 0.,  4.,  5.,  6.,  7.,  0.],
          [ 0.,  8.,  9., 10., 11.,  0.],
          [ 0., 12., 13., 14., 15.,  0.],
          [ 0.,  0.,  0.,  0.,  0.,  0.]]]], grad_fn=<ThnnConv2DBackward>)
'''

reflect

reflect 是以矩阵的边为对称轴,填充元素为内部的对称元素

conv = torch.nn.Conv2d(1,1,1,1,padding=1,padding_mode='reflect',bias=False)
conv.weight = torch.nn.Parameter(torch.ones(1,1,1,1))
conv(x)
'''
tensor([[[[ 5.,  4.,  5.,  6.,  7.,  6.],
          [ 1.,  0.,  1.,  2.,  3.,  2.],
          [ 5.,  4.,  5.,  6.,  7.,  6.],
          [ 9.,  8.,  9., 10., 11., 10.],
          [13., 12., 13., 14., 15., 14.],
          [ 9.,  8.,  9., 10., 11., 10.]]]], grad_fn=<ThnnConv2DBackward>)
'''

replicate

填充元素均为矩阵边上的元素

conv = torch.nn.Conv2d(1,1,1,1,padding=2,padding_mode='replicate',bias=False)
conv.weight = torch.nn.Parameter(torch.ones(1,1,1,1))
conv(x)
'''
tensor([[[[ 0.,  0.,  0.,  1.,  2.,  3.,  3.,  3.],
          [ 0.,  0.,  0.,  1.,  2.,  3.,  3.,  3.],
          [ 0.,  0.,  0.,  1.,  2.,  3.,  3.,  3.],
          [ 4.,  4.,  4.,  5.,  6.,  7.,  7.,  7.],
          [ 8.,  8.,  8.,  9., 10., 11., 11., 11.],
          [12., 12., 12., 13., 14., 15., 15., 15.],
          [12., 12., 12., 13., 14., 15., 15., 15.],
          [12., 12., 12., 13., 14., 15., 15., 15.]]]], grad_fn=<ThnnConv2DBackward>)
'''

circular

不好讲,直接看图示结果吧,就是将原数据 copy 几份围绕在他的周围,然后按照所需的 padding 裁切

conv = torch.nn.Conv2d(1,1,1,1,padding=2,padding_mode='circular',bias=False)
conv.weight = torch.nn.Parameter(torch.ones(1,1,1,1))
conv(x)
'''
tensor([[[[10., 11.,  8.,  9., 10., 11.,  8.,  9.],
          [14., 15., 12., 13., 14., 15., 12., 13.],
          [ 2.,  3.,  0.,  1.,  2.,  3.,  0.,  1.],
          [ 6.,  7.,  4.,  5.,  6.,  7.,  4.,  5.],
          [10., 11.,  8.,  9., 10., 11.,  8.,  9.],
          [14., 15., 12., 13., 14., 15., 12., 13.],
          [ 2.,  3.,  0.,  1.,  2.,  3.,  0.,  1.],
          [ 6.,  7.,  4.,  5.,  6.,  7.,  4.,  5.]]]], grad_fn=<ThnnConv2DBackward>)
'''

2. Dilated

空洞卷积的函数与 Conv 的函数相同,唯一的区别就是修改 dilation 参数,dilation 控制 kernal 中每个元素之间的距离:这个链接给了一个很好的图示

dilation 代表卷积核参数之间的空隙,不理解的可以直接看下图

下面通过一个例子展示 DilatedConv 的执行过程

x = torch.nn.Parameter(torch.reshape(torch.arange(0, 25, dtype=torch.float), (1, 1, 5, 5)))
print(x)
'''
Parameter containing:
tensor([[[[ 0.,  1.,  2.,  3.,  4.],
          [ 5.,  6.,  7.,  8.,  9.],
          [10., 11., 12., 13., 14.],
          [15., 16., 17., 18., 19.],
          [20., 21., 22., 23., 24.]]]], requires_grad=True)
'''

# Conv2d: dilation=1
conv = torch.nn.Conv2d(1, 1, 3, 1, padding=0, dilation=1, bias=False)
conv.weight = torch.nn.Parameter(torch.ones(1,1,3,3))
print(conv(x))
'''
tensor([[[[ 54.,  63.,  72.],
          [ 99., 108., 117.],
          [144., 153., 162.]]]], grad_fn=<ThnnConv2DBackward>)
'''

# Dilate: dilation=2
conv = torch.nn.Conv2d(1, 1, 3, 1, padding=0, dilation=2, bias=False)
conv.weight = torch.nn.Parameter(torch.ones(1,1,3,3))
print(conv(x))
'''
tensor([[[[108.]]]], grad_fn=<SlowConvDilated2DBackward>)
'''

3. Conv3d

Conv3d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)

举个例子

conv3d = nn.Conv3d(in_channels=3, out_channels=4, kernel_size=(3, 3, 3), stride=(1, 1, 1), padding=(1, 1, 1))   
# 形状: (batch_size=2, channels=3, depth=5, height=4, width=4)
input_tensor = torch.randn(2, 3, 5, 4, 4)
# padding之后(2,3,7,6,6)
output_tensor = conv3d(input_tensor)

这里将输入想象成多个视频帧的前后叠加,(3, 3, 3) 卷积核再进行操作的时候,先对 1-3 帧进行操作,并且进行水平和垂直的卷积,然后再对 2-4 帧进行操作,最后对 3-5 帧进行操作,最后输出的形状为 (2, 4, 5, 4, 4),其中 4 是卷积核的个数

Input Tensor Shape: torch.Size([2, 3, 5, 4, 4])
Output Tensor Shape: torch.Size([2, 4, 5, 4, 4])

4. CausalConv3d

import torch
import torch.nn as nn
import torch.nn.functional as F

class CausalConv3d(nn.Conv3d):
    """
    实现因果 3D 卷积(Causal 3D Convolution),确保输出时间步 `t` 仅依赖于时间 <= `t` 的数据。
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 自定义非对称 padding,确保因果性
        self._padding = (self.padding[2], self.padding[2],       # 高度方向 (上下)
                         self.padding[1], self.padding[1],       # 宽度方向 (左右)
                         2 * self.padding[0], 0)                 # 时间方向 (前后)
        self.padding = (0, 0, 0)  # 禁用父类默认 padding

    def forward(self, x, cache_x=None):
        # 自定义 padding 逻辑,支持延时输入缓存 cache_x
        padding = list(self._padding)
        if cache_x is not None and self._padding[4] > 0:  # 拼接缓存数据
            cache_x = cache_x.to(x.device)
            x = torch.cat([cache_x, x], dim=2)            # 时间维度拼接
            padding[4] -= cache_x.shape[2]
        x = F.pad(x, padding)                             # 手动 padding
        return super().forward(x)                         # 调用父类 forward

# 测试因果卷积操作
def test_causal_conv3d():
    x = torch.randn(2, 3, 5, 4, 4) # 输入张量 (batch_size=2, channels=3, depth=5, height=4, width=4)
    print("Input Shape:", x.shape)
    causal_conv3d = CausalConv3d(
        in_channels=3, out_channels=4, kernel_size=(3, 3, 3),
        stride=(1, 1, 1), padding=(1, 1, 1)
    )
    y = causal_conv3d(x)
    print("Output Shape:", y.shape)  # 期望形状: [2, 4, 5, 4, 4]

if __name__ == "__main__":
    test_causal_conv3d()

self.padding 有三个元素,假设是 (1, 1, 1),第一个是代表 time 方向,第二个是 height 方向,第三个是 width 方向,默认的 3d 卷积在这三个方向上都是对称 padding,这里 padding=1 就是在前后各填充 1 层。因果 3d 卷积区别是把 time 方向后面的 padding 变为 0,前面的 padding 变为原来的两倍

Input Shape: torch.Size([2, 3, 5, 4, 4])
Output Shape: torch.Size([2, 4, 5, 4, 4])

8月 26, 2025