【动手学深度学习PyTorch版】12 卷积层

 上一篇移步【动手学深度学习PyTorch版】11 使用GPU_水w的博客-CSDN博客

目录

一、卷积层

1.1从全连接到卷积

◼ 回顾单隐藏层MLP

◼ Waldo在哪里?

◼ 原则1-平移不变性

◼ 原则2-局部性

 ◼ 总结

1.2 卷积层

◼ 二维交叉相关

◼ 二维卷积层

◼ 交叉相关和卷积

◼ 一维和三维交叉相关

◼ 总结

二、代码实现图像卷积

◼ 互相关运算

◼ 二维卷积层


一、卷积层

1.1从全连接到卷积

◼ 回顾单隐藏层MLP

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

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

这对于网络来说,是一个问题。

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

◼ Waldo在哪里?

找的话,有两个原则:

  • 平移不变性:假设 Waldo出现在这个地方A,那么他出现在别的地方B肯定也差不多的,如果我有一个分类器去识别看 Waldo是否在地方A,那么我同样的分类器也可以用在另外一个地方B。
  • 局部性:我要找 Waldo是否在地方A,那么我只要关注局部区域的信息就可以了,不需要看太远的地方。

那么,我们怎么样从全连接层出发,利用这两个原则,得到卷积

我们之前单隐藏层的MLP来训练时,虽然一个图片是一个矩阵,但是是将输入做成一个一位的向量了。

现在我们还是还原成一个矩阵,因为我们要考虑空间的一些信息,所以我们还是将输入和输出变形成矩阵(宽度,高度)。

那么我们对应的可以将权重W变形成为一个四维的张量。之前是一个输入长度到输出长度的变化,现在是一个输入的宽度和高度到输出的宽度和高度的变化。

接下来,对W重新做一个的索引,把W的元素重新排列一下作用到v上面。使得

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

◼ 原则1-平移不变性

hij是通过x算出来的,那么假设x的位置发生变化的话,比如x平移,对应Vij会导致 hij 的平移,整个hij也会发生变化。但是V不应该依赖于(i,j),即i和j变化不应该会引起V发生变化。

给出的解决方案是:,使得i和j变化不会引起V发生变化

 所以直接得到了这个式子,我们一般叫做二维卷积,这是个误会,严格意义上来说,它是二维的交叉相关

i和j这个输入,也就是我输出里面的像素,是等于以我输入的i和j对应像素为中心, 不断做offset,加一点减一点,往边上挪的时候,和模式Va,b做内积。

所以现在我们可以认为,二维卷积就是全连接或者矩阵乘法,但是权重使得它的一些东西是重复的,即不是每一个元素都可以自由变换

我们需要知道,当我把一个模型的取值范围做了限制的时候,我就相当于吧这个模型的复杂度降低了,也就意味着我不需要存储那么多元素了。

◼ 原则2-局部性

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

这个式子中,对于Xi+a,j+b来说,a和b是变量。

但是实际上来说,我们不应该去看那么远的地方,i和j的结果只应该由输入附近的那些点就可以了。

我们可以做一些限制,解决方案就是:我在i和j那个点,离我当超过代尔塔的时候,就使得Va,b=0,不看V。

那就是说,我要做下面这个式子求和的时候,只需要计算i对于a从负达尔塔到达尔塔,j对于b从负达尔塔到达尔塔的局部性的变换。

 

 ◼ 总结

卷积是一个特殊的全连接层,是weight shared全连接。

写成一个二维的输入和输出,对权重重新进行索引,

  • Vi,ja,b丢掉了前面的两个维数,压成了两个维度的Va,b;
  • a和b限制成了从负达尔塔到达尔塔之间的值;

① 当在图片中形成一个识别器后,在一定像素大小的范围内,它都有自己的权重,当这个识别器在图片上换位置之后,它的权重应该不变。

② 理解成用同一张卷积核遍历整张图片。卷积核不会随着位置变化而变化。

③ 权重就是特征提取器,不应该随位置而发生变化。

④ 简而言之卷积核就是个框,在图片上不断扫描,无论扫在图上的哪个位置,卷积核都是不变的。

⑤ 对于一张图片应该有多个卷积核,但是每个卷积核要识别的东西不同,一个卷积核就是一个分类器。

⑥ 卷积确实是weight shared,但不是全联接,每个神经元是对应卷积核大小个输入。

⑦ 卷积就是weight shared全连接。

1.2 卷积层

◼ 二维交叉相关

假设输入是一个 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,没有做任何的填充和步幅。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值