FA :Formula and Algorithm,CNN:Convolution Netural Network
1. 引言
卷积神经网络(CNN)是深度学习中处理图像、语音等网格结构数据的核心模型。理解 CNN,关键在于搞清楚一个问题:卷积层到底是从全连接层"进化"而来的哪一步?
本文从全连接层出发,逐步推导出卷积层的两大核心特性------局部性 与平移不变性,再通过 PyTorch 手写互相关运算与卷积层,把抽象概念落到可运行的代码上。
2. 从全连接到卷积
2.1 全连接层的表示方式
先回顾全连接层是如何处理一张图片的。
- 设 kkk、lll 分别表示图片的第 kkk 行、第 lll 列个像素,它相当于全连接层的输入 xix_ixi。xix_ixi 乘以一个权重值,就得到全连接层中某个神经元的一条输入线的值。
- 权重 WWW 是一个四维张量,里面包含很多个 www。例如前图的全连接层有 100 个 www,用 iii、jjj 表示这个 www 的坐标,遍历所有 iii、jjj 恰好合为 100 个。每个 www 本身又是一个矩阵,连接所有像素,对应矩阵的宽高为 kkk 和 lll。因此下图中的 hi,jh_{i,j}hi,j 就是全连接层中一个神经元的输出。
- 原来的 kkk、lll 是基于图片的绝对位置 ,而 aaa、bbb 是根据 iii、jjj 的相对位置。这里可以省略绝对位置,只需要一个原点加上相对原点的相对位置,就能表示位置信息。
2.2 平移不变性:权重不随位置变化
从全连接层走向卷积层,第一个关键假设是平移不变性。
- 当在图片中形成一个识别器后,在一定像素大小的范围内,它都有自己的权重;当这个识别器在图片上换位置之后,它的权重应该保持不变。
- 可以理解成:用同一张卷积核遍历整张图片,卷积核不会随着位置变化而变化。
- 权重就是特征提取器,不应该随位置而发生变化。
- 简而言之,卷积核就是个"框",在图片上不断扫描,无论扫在图上的哪个位置,卷积核都是不变的。
- 对于一张图片应该有多个卷积核,但每个卷积核要识别的东西不同,一个卷积核就是一个分类器。
- 卷积确实是 weight shared,但不是全连接,每个神经元只对应卷积核大小个输入。
- 一句话总结:卷积就是 weight shared 的全连接。
2.3 局部性:只看一小块
第二个关键假设是局部性。
- 指取一个不那么大的框,即每个输出神经元只与输入的一个局部窗口相连,而不是与全部像素相连。
正是"局部性 + 平移不变性"这两个假设,把全连接层中数量爆炸的权重,压缩成了一个小小可复用的卷积核。
3. 卷积层
3.1 卷积核在做什么
- 卷积核遇到和自己相似的区域,响应会极度膨胀;遇到和自己不一样的区域,响应会极度缩小。
- 卷积层用于提取图像特征,且卷积层越深,提取的越是语义层面的特征。
3.2 为什么卷积能省参数
假如就是要看一个 3×33 \times 33×3 的局部信息,那么卷积核就是一个 3×33 \times 33×3 的矩阵。它不会因为输入图片变得特别大,就导致权重数量变得特别大------这正是卷积相对全连接层的最大优势。
4. 卷积层操作(使用自定义实现)
下面用 PyTorch 从零实现二维互相关运算,并逐步构建一个卷积层。
4.1 互相关运算
python
import torch
from torch import nn
from d2l import torch as d2l
def corr2d(X, K): # X 为输入,K 为核矩阵
"""计算二维互相关运算"""
h, w = K.shape # 核矩阵的行数和列数
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1)) # X.shape[0] 为输入高
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
验证上述二维互相关运算的输出:
python
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)
输出:
text
tensor([[19., 25.],
[37., 43.]])
4.2 实现二维卷积层
python
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
4.3 应用:检测图片中的边缘
卷积层的一个简单应用是检测图片中不同颜色的边缘。
python
X = torch.ones((6, 8))
X[:, 2:6] = 0 # 把中间四列设置为 0
print(X) # 0 与 1 之间进行过渡,表示边缘
K = torch.tensor([[1.0, -1.0]]) # 左右原值相等时,乘 1 和 -1 相加为 0,则不是边缘
Y = corr2d(X, K)
print(Y)
print(corr2d(X.t(), K)) # X.t() 为 X 的转置,而 K 卷积核只能检测垂直边缘
输出:
text
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.]])
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.]])
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.]])
可以看到,KKK 只能检测垂直边缘,对转置后的水平边缘毫无响应。
4.4 学习由 X 生成 Y 的卷积核
更进一步,我们不必手工设计卷积核,而是让模型自己学出来。
python
conv2d = nn.Conv2d(1, 1, kernel_size=(1, 2), bias=False)
# 输入通道为 1(黑白图片通道为 1,彩色图片通道为 3),输出通道为 1
X = X.reshape((1, 1, 6, 8)) # 通道维:通道数;批量维:样本数
Y = Y.reshape((1, 1, 6, 7))
for i in range(10):
Y_hat = conv2d(X)
l = (Y_hat - Y) ** 2
conv2d.zero_grad()
l.sum().backward()
conv2d.weight.data[:] -= 3e-2 * conv2d.weight.grad # 3e-2 是学习率
if (i + 1) % 2 == 0:
print(f'batch {i + 1}, loss {l.sum():.3f}')
输出:
text
batch 2, loss 9.914
batch 4, loss 3.107
batch 6, loss 1.113
batch 8, loss 0.429
batch 10, loss 0.171
查看学到的卷积核权重:
python
print(conv2d.weight.data.reshape((1, 2)))
输出:
text
tensor([[ 0.9473, -1.0320]])
学到的权重非常接近手工设计的 [[1.0, -1.0]],说明卷积核确实可以从数据中自动学习得到。
5. 总结
- 卷积层可以看作带权重共享约束的全连接层:局部性限制了每个神经元的感受野,平移不变性让同一组权重在整张图上复用。
- 卷积核本质上是一个可学习的特征提取器,多个卷积核负责识别不同的模式。
- 卷积层越深,提取的特征越偏向语义层面。
- 通过
corr2d与nn.Conv2d的对比实验可以看到,卷积核既能手工设计,也能通过梯度下降自动学习。
理解"从全连接到卷积"的推导过程,是掌握 CNN 设计思想的第一步。















