• 【动手学深度学习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由于负号学出来的应该是一个反着的东西,反一下,就可以和二维交叉相关得到同样的效果。

    ◼ 一维和三维交叉相关

    一维,三维交叉相关和二维交叉相关其实本质上是一样的。

    ◼ 总结

    核卷积矩阵的大小控制局部性。

    二、代码实现图像卷积

    ◼ 互相关运算

    1. # 互相关运算
    2. import torch
    3. from torch import nn
    4. from d2l import torch as d2l
    5. def corr2d(X, K): # X 为输入,K为核矩阵
    6. """计算二维互相关信息"""
    7. h, w = K.shape # 核矩阵的行数和列数
    8. Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1)) # X.shape[0]为输入高 , X.shape[1]为输入宽
    9. for i in range(Y.shape[0]): # 遍历所有的Yij做计算
    10. for j in range(Y.shape[1]):
    11. Y[i, j] = (X[i:i + h, j:j + w] * K).sum() # 图片的小方块区域[i:i + h, j:j + w],与卷积核做点积
    12. return Y
    13. # 验证上述二维互相关运算的输出
    14. X = torch.tensor([[0.0,1.0,2.0],[3.0,4.0,5.0],[6.0,7.0,8.0]])
    15. K = torch.tensor([[0.0,1.0],[2.0,3.0]])
    16. corr2d(X,K)

    验证上述二维互相关运算的输出,

    与图中的结果一一对应。

    ◼ 二维卷积层

    1. # 实现二维卷积层
    2. class Conv2D(nn.Module):
    3. def __init__(self, kernel_size):
    4. self.weight = nn.Parameter(torch.rand(kernel_size)) # 取随机值
    5. self.bias = nn.Parameter(torch.zeros(1)) # 随机变量
    6. def forward(Self, x):
    7. 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。

    1. # 卷积层的一个简单应用:检测图片中不同颜色的边缘
    2. X = torch.ones((6,8))
    3. X[:,2:6] = 0 # 把中间四列设置为0
    4. print(X) # 0 与 1 之间进行过渡,表示边缘
    5. # 做一个1x2的核
    6. K = torch.tensor([[1.0,-1.0]]) # 如果左右原值相等,那么这两原值乘1和-1相加为0,则不是边缘
    7. Y = corr2d(X, K)
    8. print(Y)
    9. print(corr2d(X.t(), K)) # X.t() 为X的转置,而K卷积核只能检测垂直边缘

    (2)那么接下来,给定我们的输入X和输出Y,我们去学习卷积核K

    给定一个6x8的输入,把中间四列设置为0,用0 与 1 之间进行过渡,表示边缘。

    我们想把边缘检测出来,并且给定的输出Y中的1代表从白色到黑色的边缘,-1代表从黑色到白色的边缘。

    我们应该怎么去学习这个1x2的卷积核K?

    1. """学习由X生成Y的卷积核"""
    2. # 单个矩阵,输入通道为1,黑白图片通道为1,彩色图片通道为3。所以这里输入通道为1,输出通道为1.
    3. # 去学习1x2的卷积核K
    4. # 不需要bais
    5. conv2d = nn.Conv2d(1, 1, kernel_size=(1,2), bias=False)
    6. # 给X添加2个维度,通道维:通道数,RGB图3通道,灰度图1通道;批量维就是样本维,就是样本数
    7. X = X.reshape((1,1,6,8))
    8. Y = Y.reshape((1,1,6,7))
    9. for i in range(10):
    10. Y_hat = conv2d(X)
    11. l = (Y_hat - Y) ** 2 # 使用均方误差作为loss
    12. conv2d.zero_grad()
    13. l.sum().backward()
    14. conv2d.weight.data[:] -= 3e-2 * conv2d.weight.grad # 梯度下降,3e-2是学习率
    15. if(i+1) % 2 == 0: # 每2个batch就打印loss
    16. print(f'batch {i+1},loss {l.sum():.3f}')
    17. # 所学的卷积核的权重张量
    18. print(conv2d.weight.data.reshape((1,2)))

    可以看到,学习出来的这个1x2的卷积核K的值,基本上就和我们之前手动构造出来的[1,-1]差别不大。

    这就是我们最简单的卷积层的定义,输入和输出通道都为1,没有做任何的填充和步幅。

  • 相关阅读:
    基于JavaSwing开发文件传输与聊天系统 课程设计 大作业 毕业设计
    4.Gin HTML 模板渲染
    STM32读写RTC内部时钟外设,设置和显示时钟
    【考研复试】计算机专业考研复试英语常见问题五(兴趣爱好/实践经历篇)
    jenkins 部署spring-boot 项目
    maven与nexus
    【Android】android studio 怎么下载NDK
    数据结构和算法:复杂度分析
    Linux系统了解 Samba服务器配置的工作流程
    Qt一些不起眼但很实用的小技巧汇总
  • 原文地址:https://blog.csdn.net/qq_45956730/article/details/127513650