文章目录
二维卷积运算是一种在多个领域,特别是在信号处理和图像处理中,非常关键的技术。理解二维卷积的数学原理对于进一步理解相关技术如图像滤波、特征提取等有重要意义。
一、基本定义
二维卷积涉及两个主要的函数:
- 输入函数 f ( x , y ) f(x, y) f(x,y),通常是一个图像,其中 x x x 和 y y y 是图像上的像素坐标。
- 卷积核(或滤波器) g ( x , y ) g(x, y) g(x,y),它定义了在每个像素位置上如何结合 f ( x , y ) f(x, y) f(x,y) 周围的像素。
1.1、卷积运算
二维卷积操作定义为: ( f ∗ g ) ( i , j ) = ∑ m ∑ n f ( m , n ) ⋅ g ( i − m , j − n ) (f * g)(i, j) = \sum_m \sum_n f(m, n) \cdot g(i - m, j - n) (f∗g)(i,j)=m∑n∑f(m,n)⋅g(i−m,j−n)其中:
- ( i , j ) (i, j) (i,j) 是输出图像中的坐标。
- m m m 和 n n n 是遍历整个输入图像 f f f 的索引。
- f ( m , n ) f(m, n) f(m,n) 是输入图像在位置 ( m , n ) (m, n) (m,n) 的像素值。
- g ( i − m , j − n ) g(i - m, j - n) g(i−m,j−n) 是卷积核在相对于 ( i , j ) (i, j) (i,j) 的偏移位置 ( m , n ) (m, n) (m,n) 的值。
在深度学习问题中,二维卷积并不需要这样复杂考虑,因为卷积核会通过训练修改参数,卷积核是否翻转并不影响,因此只需要对应位置相乘即可。不过卷积核的定义如此,因此我们在这里初学时,考虑翻转的问题。
1.2、边界效应
在实际应用中,输入图像的边缘处理是卷积运算中一个需要特别注意的问题。由于卷积核可能超出图像边界,通常有几种处理方法:
- 扩展边界(比如通过复制边缘像素)。
- 填充零(零填充)。
- 忽略边界区域,只对图像的内部区域进行卷积,这将导致输出图像比输入图像小。
1.3、例子
这里将通过一个简单的例子来演示二维卷积的基本操作,即使用一个小的卷积核来对一个小的图像进行卷积处理。我们将使用以下输入图像 f f f 和卷积核 g g g:
输入图像 f f f
设输入图像为一个 3 × 3 3 \times 3 3×3 矩阵:
f = [ 1 2 3 4 5 6 7 8 9 ] f = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{bmatrix} f=
147258369
卷积核 g g g
设卷积核为一个 2 × 2 2 \times 2 2×2 矩阵:
g = [ 1 0 0 − 1 ] g = \begin{bmatrix} 1& 0 \\ 0 & -1 \end{bmatrix} g=[100−1]
卷积操作
进行卷积操作时,先将卷积核进行180°翻转,卷积核 g g g 将会在输入图像 f f f 上从左到右、从上到下滑动。在每个位置上,卷积核覆盖的图像部分与卷积核对应元素相乘后的结果求和,形成新的图像 h h h 的一个元素。
因为我们使用了 2 × 2 2 \times 2 2×2 的卷积核,所以输出图像 h h h 的大小将是 ( 3 − 2 + 1 ) × ( 3 − 2 + 1 ) = 2 × 2 (3-2+1) \times (3-2+1) = 2 \times 2 (3−2+1)×(3−2+1)=2×2,计算如下:
输出图像 h h h
在位置 (0, 0):
h ( 0 , 0 ) = 1 ⋅ ( − 1 ) + 2 ⋅ 0 + 4 ⋅ 0 + 5 ⋅ 1 = − 1 + 0 + 0 + 5 = 4 h(0, 0) = 1 \cdot (-1) + 2 \cdot 0 + 4 \cdot 0 + 5 \cdot 1 = -1 + 0 + 0 + 5 = 4 h(0,0)=1⋅(−1)+2⋅0+4⋅0+5⋅1=−1+0+0+5=4在位置 (0, 1):
h ( 0 , 1 ) = 2 ⋅ ( − 1 ) + 3 ⋅ 0 + 5 ⋅ 0 + 6 ⋅ 1 = − 2 + 0 + 0 + 6 = 4 h(0, 1) = 2 \cdot (-1) + 3 \cdot 0 + 5 \cdot 0 + 6 \cdot 1 = -2 + 0 + 0 + 6 = 4 h(0,1)=2⋅(−1)+3⋅0+5⋅0+6⋅1=−2+0+0+6=4在位置 (1, 0):
h ( 1 , 0 ) = 4 ⋅ ( − 1 ) + 5 ⋅ 0 + 7 ⋅ 0 + 8 ⋅ 1 = − 4 + 0 + 0 + 8 = 4 h(1, 0) = 4 \cdot (-1) + 5 \cdot 0 + 7 \cdot 0 + 8 \cdot 1 = -4 + 0 + 0 + 8 = 4 h(1,0)=4⋅(−1)+5⋅0+7⋅0+8⋅1=−4+0+0+8=4在位置 (1, 1):
h ( 1 , 1 ) = 5 ⋅ ( − 1 ) + 6 ⋅ 0 + 8 ⋅ 0 + 9 ⋅ 1 = − 5 + 0 + 0 + 9 = 4 h(1, 1) = 5 \cdot (-1) + 6 \cdot 0 + 8 \cdot 0 + 9 \cdot 1 = -5 + 0 + 0 + 9 = 4 h(1,1)=5⋅(−1)+6⋅0+8⋅0+9⋅1=−5+0+0+9=4
最终,输出图像 h h h 为:
h = [ 4 4 4 4 ] h = \begin{bmatrix} 4 & 4 \\ 4 & 4 \end{bmatrix} h=[4444]
这个简单的例子说明了卷积如何在图像的不同部分“滑动”卷积核。
二、torch.flip()函数
在PyTorch中,torch.flip
是一个非常有用的函数,用于翻转张量中的一个或多个维度。这可以在进行图像处理或数据增强时特别有用,例如,翻转图像以创建更多训练数据或模拟不同的视角。这里我们主要用来翻转卷积核。
2.1、基本用法
函数 torch.flip
的基本语法是:
torch.flip(input, dims)
- input: 要翻转的张量。
- dims: 一个整数列表或元组,指定了要翻转的维度。
2.2、示例
假设我们有一个三维的张量(例如,一个颜色图像,其中维度是通道、高度和宽度),我们可以按照以下方式使用 torch.flip
:
import torch
# 创建一个示例张量,形状为 [3, 4, 4],模拟一个 4x4 的彩色图像(3个颜色通道)
x = torch.arange(3*4*4).reshape(3, 4, 4)
# 输出原始张量
print("Original Tensor:\n", x)
# 沿宽度(最后一个维度,维度编号为2)翻转图像
flipped_x = torch.flip(x, [2])
# 输出翻转后的张量
print("Flipped Tensor:\n", flipped_x)
这个例子中,我们创建了一个形状为 [3, 4, 4] 的张量,并沿着最后一个维度(宽度)翻转。如果你想沿着高度(第二个维度,维度编号为1)或者同时沿着高度和宽度翻转,你可以相应地调整 dims
参数:
# 沿高度翻转
flipped_y = torch.flip(x, [1])
print("Flipped along height:\n", flipped_y)
# 同时沿高度和宽度翻转
flipped_xy = torch.flip(x, [1, 2])
print("Flipped along both height and width:\n", flipped_xy)
这里说的沿高度和宽度翻转,实际上就是同时进行,图像的顶部和底部交换,并同时将左侧和右侧交换。 因此可以实现180°翻转。
2.3、卷积核翻转
import torch
conv2d_kernel=torch.Tensor([1,2,3])
print(conv2d_kernel.size())
conv2d_kernel_flipped=torch.flip(conv2d_kernel,[0])
conv2d_kernel_flipped
torch.Size([3])
tensor([3., 2., 1.])
import torch
conv2d_kernel=torch.Tensor([[1,2,3],[5,6,7]])
print(conv2d_kernel.size())
conv2d_kernel_flipped=torch.flip(conv2d_kernel,[0,1])
conv2d_kernel_flipped
torch.Size([2, 3])
tensor([[7., 6., 5.],
[3., 2., 1.]])
三、torch.nn.Conv2d类
torch.nn.Conv2d
是 PyTorch 库中的一个非常重要的类,用于实现二维卷积操作,广泛应用于处理图像数据的卷积神经网络(CNN)中。和一维卷积差不多,二维卷积同样有 先定义torch.nn.Conv2d
类再输入图像计算 也有 直接使用torch.nn.functional.conv2d
函数计算两种方式。
3.1、构造函数参数说明
torch.nn.Conv2d
的构造函数接受多个参数,用于定义卷积层的结构和行为:
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros')
- in_channels (int): 输入数据的通道数(对于彩色图像通常是3,灰度图像是1)。
- out_channels (int): 卷积操作后的输出通道数。这通常对应于卷积核的数量,每个卷积核提取输入数据的一种特定特征。
- kernel_size (int or tuple): 卷积核的大小。可以是单一整数或一个由两个整数构成的元组(height, width)。
- stride (int or tuple, optional): 卷积步长,决定卷积核滑动的速度和方式。默认为1。可以是单一整数或一个由两个整数构成的元组。
- padding (int or tuple, optional): 在输入数据周围填充0的层数,用于控制输出的空间尺寸。默认为0。可以是单一整数或一个由两个整数构成的元组。
- dilation (int or tuple, optional): 卷积核中元素之间的空间。用于实现空洞卷积(dilated convolution),可以增大感受野。默认为1。
- groups (int, optional): 将输入通道和输出通道分组,使得每一组使用不同的卷积核。这通常用于实现分组卷积,如在深度可分离卷积中。默认为1。
- bias (bool, optional): 是否添加偏置项(一个在每个输出通道上加的可学习参数)。默认为True。偏置是一个 C o u t C_{out} Cout通道大小 同维度的张量,输出图像的一个特定通道中的所有元素都会添加上 对应偏置值。
注意:
- input: 输入张量,形状应为 ( N , C i n , H , W ) (N, C_{in}, H, W) (N,Cin,H,W),其中:
- N N N是批大小(batch size)
- C i n C_{in} Cin是通道数(channel number)
- H H H是图像高度
- W W W 是图像宽度
- weight: 卷积核(滤波器)张量,形状应为 ( C o u t , C i n / g r o u p s , k H , k W ) (C_{out}, C_{in}/groups, kH, kW) (Cout,Cin/groups,kH,kW),其中:
- C o u t C_{out} Cout 是输出通道数
- k H kH kH 是卷积核的高度
- k W kW kW 是卷积核的宽度
3.2、示例代码
下面是一个使用 torch.nn.Conv2d
创建卷积层的例子:
import torch
import torch.nn as nn
# 创建一个卷积层
# 输入通道数为 3(彩色图像),输出通道数为 16,卷积核大小为 5x5
# 类对象的初始化
conv_layer = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=5, stride=1, padding=2, bias=True)
# 假设有一个随机初始化的图像批量,批大小为 10,图像尺寸为 32x32
input_images = torch.randn(10, 3, 32, 32)
# 应用卷积层
output_features = conv_layer(input_images)
# 查看输出特征的形状
print("Output features shape:", output_features.shape)
print("卷积核形状:",conv_layer.weight.shape)
在这个例子中,输入是一个形状为 (10, 3, 32, 32)
的张量,表示有10个32x32像素的彩色图像。经过卷积层处理后,输出的形状为 (10, 16, 32, 32)
,表示每个图像现在被转换成了16个32x32的特征映射。
卷积核形状为torch.Size([16, 3, 5, 5])