【动手学深度学习PyTorch】图像卷积
图像卷积
一、互相关运算
严格来说,卷积层是个错误的叫法,因为它所表达的运算其实是互相关运算(cross-correlation),而不是卷积运算。在卷积层中,输入张量和核张量通过(互相关运算)产生输出张量。
在下图中,输入是高度为333、宽度为333的二维张量(即形状为3×33 \times 33×3)。卷积核的高度和宽度都是222,而卷积核窗口(或卷积窗口)的形状由内核的高度和宽度决定(即2×22 \times 22×2)。

在二维互相关运算中,卷积窗口从输入张量的左上角开始,从左到右、从上到下滑动。
当卷积窗口滑动到新一个位置时,包含在该窗口中的部分张量与卷积核张量进行按元素相乘,得到的张量再求和得到一个单一的标量值,由此我们得出了这一位置的输出张量值。
在如上例子中,输出张量的四个元素由二维互相关运算得到,这个输出高度为222、宽度为222,如下所示:
0×0+1×1+3×2+4×3=19,1×0+2×1+4×2+5×3=25,3×0+4×1+6×2+7×3=37,4×0+5×1+7×2+8×3=43.
0\times0+1\times1+3\times2+4\times3=19,\\
1\times0+2\times1+4\times2+5\times3=25,\\
3\times0+4\times1+6\times2+7\times3=37,\\
4\times0+5\times1+7\times2+8\times3=43.
0×0+1×1+3×2+4×3=19,1×0+2×1+4×2+5×3=25,3×0+4×1+6×2+7×3=37,4×0+5×1+7×2+8×3=43.
注意,输出大小略小于输入大小。这是因为卷积核的宽度和高度大于1,
而卷积核只与图像中每个大小完全适合的位置进行互相关运算。
所以,输出大小等于输入大小nh×nwn_h \times n_wnh×nw减去卷积核大小kh×kwk_h \times k_wkh×kw,即:
(nh−kh+1)×(nw−kw+1).(n_h-k_h+1) \times (n_w-k_w+1).(nh−kh+1)×(nw−kw+1).
import torch
from torch import nn
from d2l import torch as d2l
# 实现2D卷积过程
# 函数接受输入张量X和卷积核张量K,并返回输出张量Y
def corr2d(X, K): #@save
"""计算二维互相关运算"""
h, w = K.shape
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i:i + h, j:j + w] * K).sum()
return Y
通过上图中的输入张量X和卷积核张量K,我们来[验证上述二维互相关运算的输出]。
X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
corr2d(X, K)
tensor([[19., 25.],
[37., 43.]])
二、卷积层
卷积层中的两个被训练的参数是卷积核权重和标量偏置。
在训练基于卷积层的模型时,需要随机初始化卷积核权重。
class Conv2D(nn.Module):
def __init__(self, kernel_size):
super().__init__()
self.weight = nn.Parameter(torch.rand(kernel_size))
self.bias = nn.Parameter(torch.zeros(1))
def forward(self, x):
return corr2d(x, self.weight) + self.bias
高度和宽度分别为hhh和www的卷积核可以被称为h×wh \times wh×w卷积或h×wh \times wh×w卷积核。
我们也将带有h×wh \times wh×w卷积核的卷积层称为h×wh \times wh×w卷积层。
三、图像中目标的边缘检测
如下是[卷积层的一个简单应用:]通过找到像素变化的位置,来(检测图像中不同颜色的边缘)。
首先,我们构造一个6×86\times 86×8像素的黑白图像。中间四列为黑色(000),其余像素为白色(111)。
X = torch.ones((6, 8))
X[:, 2:6] = 0
X
tensor([[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.]])
接下来,我们构造一个高度为111、宽度为222的卷积核K。当进行互相关运算时,如果水平相邻的两元素相同,则输出为零,否则输出为非零。
K = torch.tensor([[1.0, -1.0]])
现在,我们对参数X(输入)和K(卷积核)执行互相关运算。
如下所示,[输出Y中的1代表从白色到黑色的边缘,-1代表从黑色到白色的边缘],其他情况的输出为000。
Y = corr2d(X, K)
Y
tensor([[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.]])
现在我们将输入的二维图像转置,再进行如上的互相关运算。
其输出如下,之前检测到的垂直边缘消失了。
不出所料,这个[卷积核K只可以检测垂直边缘],无法检测水平边缘。
corr2d(X.t(), K)
tensor([[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]])
四、学习卷积核
我们先构造一个卷积层,并将其卷积核初始化为随机张量。接下来,在每次迭代中,我们比较Y与卷积层输出的平方误差,然后计算梯度来更新卷积核。为了简单起见,我们在此使用内置的二维卷积层,并忽略偏置。
# 构造一个二维卷积层,它具有1个输出通道和形状为(1,2)的卷积核
conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False)
# 这个二维卷积层使用四维输入和输出格式(批量大小、通道、高度、宽度),
# 其中批量大小和通道数都为1
X = X.reshape((1, 1, 6, 8))
Y = Y.reshape((1, 1, 6, 7))
lr = 3e-2 # 学习率
for i in range(10):
Y_hat = conv2d(X)
l = (Y_hat - Y) ** 2
conv2d.zero_grad()
l.sum().backward()
# 迭代卷积核
conv2d.weight.data[:] -= lr * conv2d.weight.grad
if (i + 1) % 2 == 0:
print(f'epoch {i+1}, loss {l.sum():.3f}')
epoch 2, loss 6.422
epoch 4, loss 1.225
epoch 6, loss 0.266
epoch 8, loss 0.070
epoch 10, loss 0.022
在101010次迭代之后,误差已经降到足够低。现在我们来看看我们[所学的卷积核的权重张量]。
conv2d.weight.data.reshape((1, 2))
tensor([[ 1.0010, -0.9739]])
学习到的卷积核权重非常接近我们之前定义的卷积核K。
五、互相关和卷积
1. 数学上的区别
-
卷积(Convolution):
数学里定义卷积的时候,卷积核要翻转 180°(先左右,再上下),然后再和输入做滑动相乘求和。
公式大概长这样:
(f∗g)(t)=∑τf(τ)g(t−τ) (f * g)(t) = \sum_\tau f(\tau) g(t - \tau) (f∗g)(t)=τ∑f(τ)g(t−τ)
👉 注意这里的 (g) 被翻转了。 -
互相关(Cross-correlation):
不做翻转,直接拿卷积核在输入上“平移”,点乘求和。
(f⋆g)(t)=∑τf(τ)g(t+τ) (f \star g)(t) = \sum_\tau f(\tau) g(t + \tau) (f⋆g)(t)=τ∑f(τ)g(t+τ)
所以从定义上看,差别就一个字:卷积要翻核,互相关不翻核。
2. 在深度学习中的实际用法
- 框架里(PyTorch、TensorFlow 等)卷积层的实现,其实用的是互相关,不是严格的数学卷积!
原因很简单:- 翻转没啥必要,
- 卷积核是学出来的,你让它翻还是不翻,最后都能学到合适的模式。
- 所以大家口头上都叫“卷积层”,其实背后计算的是“互相关”。
3. 直观理解
- 可以把卷积核看成一副“探测眼镜”:比如一个 3×3 的边缘检测核。
- 卷积(数学意义):先把眼镜翻过来再看。
- 互相关(深度学习实际操作):直接戴上眼镜就看。
- 最终效果差别不大,因为“眼镜”是训练出来的,模型自己会调整成能看的样子。
六、特征映射和感受野
1. 特征映射(Feature Map)
- 是什么?
卷积层的输出结果,就是一张张“特征图”。
每个卷积核都会扫描整张输入图片,然后生成一张新的二维图,这张图里每个像素值就代表了“这个位置对某种特征的响应强度”。- 比如一个卷积核可能专门学会检测“水平边缘”,那它生成的特征映射里,凡是有横线的地方就会亮起来。
- 直观理解
想象你在看一张风景图:- 一个卷积核像是戴了“找树的眼镜”,它会输出一张“树的特征图”;
- 另一个卷积核像是戴了“找房子的眼镜”,它就输出一张“房子的特征图”。
这些不同的特征图堆叠在一起,就构成了这一层的“输出通道”。
2. 感受野(Receptive Field)
- 是什么?
就是卷积神经网络里某一层的一个神经元,能看到的输入区域大小。- 比如在输入层(原图像),一个神经元只“盯着”一个像素点;
- 经过卷积层后,感受野就扩大了,一个神经元可能代表输入图像中的一个小方块区域;
- 越往深层走,感受野越大,最终可能覆盖整张图像。
- 直观理解
就像你用相机拍照:- 一开始你只能盯着一块草地(小感受野);
- 拉远镜头,能看到整片草原(大感受野);
- 再拉远,整个风景尽收眼底(全局感受野)。
网络训练也是类似过程,从低层的小细节到高层的大局。
小结
- 二维卷积层的核心计算是二维互相关运算。最简单的形式是,对二维输入数据和卷积核执行互相关操作,然后添加一个偏置。
- 我们可以设计一个卷积核来检测图像的边缘。
- 我们可以从数据中学习卷积核的参数。
- 学习卷积核时,无论用严格卷积运算或互相关运算,卷积层的输出不会受太大影响。
- 当需要检测输入特征中更广区域时,我们可以构建一个更深的卷积网络。
内容声明
本文基于开源教材《动手学深度学习》(Dive into Deep Learning, 作者:Aston Zhang、Zachary C. Lipton、Mu Li、Alexander J. Smola 等)整理,原始项目地址:https://github.com/d2l-ai/d2l-zh。
在整理过程中对部分内容进行了删改和补充,仅用于个人学习与交流,版权归原作者所有。
更多推荐
所有评论(0)