5.14学习日志

卷积神经网络CNN

一.从全连接层到卷积

将输入和输出由原本的一维向量变形为矩阵(宽度,高度),那么权重将会变形为四维张量

使用Xij代表输入图像中位置(i,j)处的像素,Hij代表隐藏中位置(i,j)处的像素

i,j是h高度的参数,kl是w平面上的参数

使得k=i+a,l=j+b,则可以得到重新索引

原则一:平移不变性 

对于权重作出限制,对模型的复杂度降低,不用再存过多元素,不依赖于高度坐标,只依赖于a,b坐标

原则二: 局部性

只局限在Xi,j附近,超过一定范围,加上限制条件,将权重归为0

卷积层 

 

二维互相关运算 

def corr2d(X,K):     #X是输入,K是核矩阵
    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

卷积层: 

'''二维卷积'''
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

 

图像中目标的边缘检测

'''检测边缘'''
X=torch.ones((6,8))
X[:,2:6]=0#中间四列为黑色是0,其他列为白色是1
print(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.]])'''
K=torch.tensor([[1.0,-1.0]])#如果水平相邻元素相同,则输出为0,否则输出为非0
Y=corr2d(X,K)
print(Y)#1为白色到黑色的边缘,0为黑色到白色的边缘
'''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.]])'''

不同的核进行卷积操作,得到不同的效果,关键点在于想要什么效果,用网络学习出一个能达到效果的核

卷积层将输入和核矩阵进行交叉相关,加上偏移后得到输出,核矩阵和偏移是可学习的参数,核矩阵大小是超参数(限制了检索范围)

学习由X生成Y的卷积核矩阵

已知输入和输出,现在求出核矩阵

import torch
from torch import nn
#构造一个二维卷积层,输出通道数为1,和形状为(1,2)的卷积核
conv2d=nn.Conv2d(1,1,kernel_size=(1,2),bias=False)

#四维输入和输出格式(批量大小,通道,高度,宽度)
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)#得到预测输出Y_hat
    l=(Y_hat-Y)**2#计算预测输出 Y_hat 和目标输出 Y 之间的均方误差损失 l
    #平方运算的目的是放大较大的误差
    conv2d.zero_grad()#将卷积层的梯度缓存清零,以准备下一次反向传播
    l.sum().backward()# 对损失求和并进行反向传播,计算卷积层权重的梯度
    #迭代卷积核
    conv2d.weight.data[:]-=lr*conv2d.weight.grad#将卷积核的权重减去学习率乘以梯度的乘积,实现梯度下降更新
    if(i+1)%2==0:#如果当前迭代次数加 1 能被 2 整除,则打印当前的迭代次数和损失值
        print(f'epoch{i+1},loss{l.sum():.3f}')

conv2d.weight.data.reshape((1,2))#误差足够小后得到的权重张量

通过这个训练过程,卷积神经网络通过不断调整卷积核的权重,使预测输出尽可能接近目标输出,从而最小化损失函数

二.填充和步幅

填充

问题:给定大小图像,用卷积核进行卷积,层数是有限的,但是深度学习的根本,是用更深层的网络解决,这种情况怎么办?解决这个问题:填充

在输入的周围加上一些行和列,可以让输出比原来更大

通常取值会让输入和输出的形状一致,这样的好处就在于无论选取多大的卷积核,不会让输入的形状发生改变                                

注意这里是填充ph行,上下一共行数 ,padding是每一边填充的行数

填充两行ph=2,上下左右各一行的例子:

def comp_conv2d(conv2d,X):
    X=X.reshape((1,1)+X.shape)
    #调整为四维张量的格式 ,假设 X 的原始形状为二维 (高度, 宽度),添加了批量大小和通道维度
    Y=conv2d(X)
    return Y.reshape(Y.shape[2:])#表示获取 Y 的形状中从索引 2 开始的维度,即 (高度, 宽度)

conv2d = nn.Conv2d(1,1,kernel_size=3,padding=1)# 一个 nn.Conv2d 类的实例
X=torch.rand(size=(8,8))#创建了一个随机的输入张量 X,形状为 (8, 8)
print(comp_conv2d(conv2d,X).shape)
#torch.Size([8, 8])

也可以上下和左右填充不一样

conv2d = nn.Conv2d(1,1,kernel_size=(5,3),padding=(2,1))# 一个 nn.Conv2d 类的实例
X=torch.rand(size=(8,8))#创建了一个随机的输入张量 X,形状为 (8, 8)
print(comp_conv2d(conv2d,X).shape)
#torch.Size([8, 8])
步幅 

问题:一个较大的输入大小,卷积核通常不会取太大一般就是5*5,3*3,那么这种情况下,需要很多层数才能将输出降低?解决这个问题:步幅

 步幅是指控制核矩阵在输入上移动的距离

步幅例子:

conv2d = nn.Conv2d(1,1,kernel_size=(3,5),padding=(0,1),stride=(3,4))# 一个 nn.Conv2d 类的实例
X=torch.rand(size=(8,8))#创建了一个随机的输入张量 X,形状为 (8, 8)
print(comp_conv2d(conv2d,X).shape)
#torch.Size([2, 2])

填充和步幅都是卷积层的超参数

三.卷积层的多个输入和输出通道

多个输入通道:

彩色图像有RGB三个通道,每个通道都有自己的卷积核,做完卷积的结果后,相加得到最终结果。

输出是一个单通道

代码:

def corr2d_multi_in(X,K):
    return sum(d2l.corr2d(x,k) for x,k in zip(X,K))
#zip用于将两个或多个可迭代对象的对应元素配对,
#对于每个 (x, k) 对,使用 d2l.corr2d(x, k) 计算二维互相关运算的结果

X=torch.tensor([[[0,1,2],[3,4,5],[6,7,8]],
                [[1,2,3],[4,5,6],[7,8,9]]])
K=torch.tensor([[[0,1],[2,3]],
                [[1,2],[3,4]]])
print(corr2d_multi_in(X,K))
'''tensor([[ 56.,  72.],
        [104., 120.]])'''
多个输出通道:

Ci和Co分别代表输入和输出通道的数量,可以为每个输出通道创建一个形状为Ci*Kh*Kw的卷积核张量。运算就是每个输出通道先获取所有输入通道,再以对应该输出通道的卷积核计算出结果。 

代码:

def corr2d_multi_in_out(X,K):
    return torch.stack([corr2d_multi_in(X,k) for k in K],0)
K=torch.stack((K,K+1,K+2),0)

K原本是三维,stack就是新建立一个0的维度,在0的维度上把之前的三维堆叠起来,输出通道是3.

多输出通道就是每个输出通道在识别特定模式:不同角度的边缘,某个颜色通道的点......

多输入通道就是现在有了这些不同特定模式,统一传到一层卷积,将它们组合起来

1*1的卷积层:

卷积层高和宽都为1,不识别空间模式,只融合通道

 

def corr2d_multi_in_out(X,K):
    return torch.stack([corr2d_multi_in(X,k) for k in K],0)
K=torch.stack((K,K+1,K+2),0)

def corr2d_multi_in_out_1x1(X,K):
    c_i,h,w=X.shape
    c_o=K.shape[0]
    X=X.reshape((c_i,h*w))
    K=K.reshape((c_o,c_i))
    Y=torch.matual(K,X)
    return Y.reshape((c_o,h,w))

X= torch.normal(0,1,(3,3,3))
K=torch.normal(0,1,(2,3,1,1))

Y1=corr2d_multi_in_out_1x1(X,K)

总结:输出通道数是卷积层的超参数,每个输入通道有独立的二维卷积核,每个输出通道有独立的三维卷积核

四.池化层

主要是为了缓解卷积层对位置的敏感性

二维最大池化:

同样是有窗口,取窗口范围内的最大值

垂直边缘检测效果就是将边缘模糊化,可容输入一个像素的偏移,出现偏差后不会差距过大

填充和步幅是与卷积类似的两个超参数

在多输入的情况下,在每个输入通道应用池化层获得相应的输出通道,不会进行通道融合,因此输出通道数等于输入通道数

平均池化层:

返回窗口内的平均值

代码:
def pool2d(X,pool_size,mode='max'):#默认情况下是最大池化层
    p_h,p_w=pool_size
    Y=torch.zeros((X.shape[0]-p_h+1,X.shape[1]-p_w+1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            if mode == 'max':
                Y[i,j] = X[i:i+p_h,j:j+p_w].max()
            elif mode == 'avg':
                Y[i,j] = X[i:i+p_h,j:j+p_w].mean()
    return Y

X=torch.tensor([[0.0,1.0,2.0],[3.0,4.0,5.0],[6.0,7.0,8.0]])
print(pool2d(X,(2,2)))
'''tensor([[4., 5.],
        [7., 8.]])'''
print(pool2d(X,(2,2),'avg'))
'''tensor([[2., 3.],
        [5., 6.]])'''
X = torch.arange(16,dtype=torch.float32).reshape((1,1,4,4))
pool2d=nn.MaxPool2d(3)
print(pool2d(X))#深度学习框架中的步幅默认和池化窗口大小相同
#tensor([[[[10.]]]])

填充和步幅也可以手动指定

X = torch.arange(16,dtype=torch.float32).reshape((1,1,4,4))
pool2d=nn.MaxPool2d(3,padding=1,stride=2)
print(pool2d(X))
#tensor([[[[ 5.,  7.],
 #         [13., 15.]]]])

池化层在每个输入通道上单独运算

cat是在现有维度上的连接,stack是在新维度上的堆叠

X = torch.arange(16,dtype=torch.float32).reshape((1,1,4,4))
#池化层在每个输入通道上单独运算
X=torch.cat((X,X+1),1)
print(X)
'''tensor([[[[ 0.,  1.,  2.,  3.],
          [ 4.,  5.,  6.,  7.],
          [ 8.,  9., 10., 11.],
          [12., 13., 14., 15.]],

         [[ 1.,  2.,  3.,  4.],
          [ 5.,  6.,  7.,  8.],
          [ 9., 10., 11., 12.],
          [13., 14., 15., 16.]]]])'''
pool2d=nn.MaxPool2d(3,padding=1,stride=2)
print(pool2d(X))
'''tensor([[[[ 5.,  7.],
          [13., 15.]],

         [[ 6.,  8.],
          [14., 16.]]]])'''

总结:池化层通常放在卷积层后面,让其对位置不要过于敏感

  • 30
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值