目录
假设我们用手机拍了1200万像素----12M像素的图片,而且是RGB图片,那么就是3600万像素----36M像素。

假设我用一个单隐藏层的MLP来训练,隐藏层的大小为100的话,那么这个模型有3.6亿个元素,远远多于世界上所有的猫和狗的总数。那么模型还不如直接记住世界上所有的猫和狗的总数呢。
这对于网络来说,是一个问题。

隐藏层的大小100,那么权重就是100x3600万个,把这么多参数存下来的话,需要14GB。那么我如果用单隐藏层就会需要14GB的内存,这还没设计到做运算。

找的话,有两个原则:

那么,我们怎么样从全连接层出发,利用这两个原则,得到卷积?
我们之前单隐藏层的MLP来训练时,虽然一个图片是一个矩阵,但是是将输入做成一个一位的向量了。
现在我们还是还原成一个矩阵,因为我们要考虑空间的一些信息,所以我们还是将输入和输出变形成矩阵(宽度,高度)。
那么我们对应的可以将权重W变形成为一个四维的张量。之前是一个输入长度到输出长度的变化,现在是一个输入的宽度和高度到输出的宽度和高度的变化。
接下来,对W重新做一个的索引,把W的元素重新排列一下作用到v上面。使得
![]()

现在,我们看一下还有什么问题?

hij是通过x算出来的,那么假设x的位置发生变化的话,比如x平移,对应Vij会导致 hij 的平移,整个hij也会发生变化。但是V不应该依赖于(i,j),即i和j变化不应该会引起V发生变化。
给出的解决方案是:
,使得i和j变化不会引起V发生变化
所以直接得到了这个式子,我们一般叫做二维卷积,这是个误会,严格意义上来说,它是二维的交叉相关。

i和j这个输入,也就是我输出里面的像素,是等于以我输入的i和j对应像素为中心, 不断做offset,加一点减一点,往边上挪的时候,和模式Va,b做内积。
所以现在我们可以认为,二维卷积就是全连接或者矩阵乘法,但是权重使得它的一些东西是重复的,即不是每一个元素都可以自由变换。
我们需要知道,当我把一个模型的取值范围做了限制的时候,我就相当于吧这个模型的复杂度降低了,也就意味着我不需要存储那么多元素了。

假设我要计算hi,j这个输出的话,

这个式子中,对于Xi+a,j+b来说,a和b是变量。
但是实际上来说,我们不应该去看那么远的地方,i和j的结果只应该由输入附近的那些点就可以了。
我们可以做一些限制,解决方案就是:我在i和j那个点,离我当超过代尔塔的时候,就使得Va,b=0,不看V。
那就是说,我要做下面这个式子求和的时候,只需要计算i对于a从负达尔塔到达尔塔,j对于b从负达尔塔到达尔塔的局部性的变换。


卷积是一个特殊的全连接层,是weight shared全连接。
写成一个二维的输入和输出,对权重重新进行索引,

① 当在图片中形成一个识别器后,在一定像素大小的范围内,它都有自己的权重,当这个识别器在图片上换位置之后,它的权重应该不变。
② 理解成用同一张卷积核遍历整张图片。卷积核不会随着位置变化而变化。
③ 权重就是特征提取器,不应该随位置而发生变化。
④ 简而言之卷积核就是个框,在图片上不断扫描,无论扫在图上的哪个位置,卷积核都是不变的。
⑤ 对于一张图片应该有多个卷积核,但是每个卷积核要识别的东西不同,一个卷积核就是一个分类器。
⑥ 卷积确实是weight shared,但不是全联接,每个神经元是对应卷积核大小个输入。
⑦ 卷积就是weight shared全连接。
假设输入是一个 3x3 的矩阵,卷积核W(我们一般叫做Kernel)是一个2x2,其实就是说达尔塔等于1,那么输出就是:

当计算完19,计算25的时候,输出往右移了一位,那么输入也会往右移一位,那么对应的窗口就会发生变化,但核是不变的,满足平移不变性。而且输出只使用了2x2的窗口,满足局部性原则。

也就是说,一个核窗口,不断的在输入上往左移右移,上移下,来扫描几遍,得到我们的输出,这就是我们的二维交叉相关。

输出会变小,是因为在窗口移动的过程中,输入不足的时候,就会在输出的时候会丢掉一些东西。

我们可以从这个例子看出,不同的卷积核的值可以带来不同的效果,
比如说第一个:中间的8是一个比较大的值,边上的-1是一个比较小的值,会得到边缘检测的效果,把边缘值高亮出来。
我们可以认为是说,我的是神经网络可以去学习不同的核,来得到我想要的输出。
交叉相关和卷积其实没有太多区别,唯一的区别就是卷积的式子中有个负号。但是由于对称性,所以在实际的使用当中没有区别。

就相当于是说,如果二维交叉相关的W学出来是一个东西的话,那么二维卷积的W由于负号学出来的应该是一个反着的东西,反一下,就可以和二维交叉相关得到同样的效果。
一维,三维交叉相关和二维交叉相关其实本质上是一样的。

核卷积矩阵的大小控制局部性。
- # 互相关运算
- 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]为输入高 , X.shape[1]为输入宽
- for i in range(Y.shape[0]): # 遍历所有的Yij做计算
- for j in range(Y.shape[1]):
- Y[i, j] = (X[i:i + h, j:j + w] * K).sum() # 图片的小方块区域[i:i + h, j:j + w],与卷积核做点积
- return Y
-
- # 验证上述二维互相关运算的输出
- 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)
验证上述二维互相关运算的输出,


与图中的结果一一对应。
- # 实现二维卷积层
- class Conv2D(nn.Module):
- def __init__(self, kernel_size):
- 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 # 用X和weight做互相关运算,再加上bias
(1)卷积层的一个简单应用:检测图片中不同颜色的边缘。
给定一个6x8的输入,把中间四列设置为0,用0 与 1 之间进行过渡,表示边缘。
要想把边缘检测出来,我们做一个1x2的核K,输出的Y中的1代表从白色到黑色的边缘,-1代表从黑色到白色的边缘。
当然,卷积核K只能检测到垂直的边缘。
比如说,我们把X.t() 为X的转置,而K卷积核只能检测垂直边缘。所以结果全部为0。
- # 卷积层的一个简单应用:检测图片中不同颜色的边缘
- X = torch.ones((6,8))
- X[:,2:6] = 0 # 把中间四列设置为0
- print(X) # 0 与 1 之间进行过渡,表示边缘
-
- # 做一个1x2的核
- 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卷积核只能检测垂直边缘

(2)那么接下来,给定我们的输入X和输出Y,我们去学习卷积核K。
给定一个6x8的输入,把中间四列设置为0,用0 与 1 之间进行过渡,表示边缘。
我们想把边缘检测出来,并且给定的输出Y中的1代表从白色到黑色的边缘,-1代表从黑色到白色的边缘。
我们应该怎么去学习这个1x2的卷积核K?
- """学习由X生成Y的卷积核"""
-
- # 单个矩阵,输入通道为1,黑白图片通道为1,彩色图片通道为3。所以这里输入通道为1,输出通道为1.
- # 去学习1x2的卷积核K
- # 不需要bais
- conv2d = nn.Conv2d(1, 1, kernel_size=(1,2), bias=False)
- # 给X添加2个维度,通道维:通道数,RGB图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 # 使用均方误差作为loss
- conv2d.zero_grad()
- l.sum().backward()
- conv2d.weight.data[:] -= 3e-2 * conv2d.weight.grad # 梯度下降,3e-2是学习率
- if(i+1) % 2 == 0: # 每2个batch就打印loss
- print(f'batch {i+1},loss {l.sum():.3f}')
-
-
- # 所学的卷积核的权重张量
- print(conv2d.weight.data.reshape((1,2)))

可以看到,学习出来的这个1x2的卷积核K的值,基本上就和我们之前手动构造出来的[1,-1]差别不大。
这就是我们最简单的卷积层的定义,输入和输出通道都为1,没有做任何的填充和步幅。