跟李沐学AI之卷积神经网络

这里写目录标题

  • CNN
    • 从全连接层到卷积
    • 图像卷积
    • 填充和步幅
    • 多输入多输出的通道数
    • 池化层
    • LeNet

CNN

从全连接层到卷积

无论哪种方法找到这个物体,都应该和物体的位置没有关系。使用一个物体检测器,该检测器将图像分割成多个区域,并为每个区域包含物体的可能性打分,卷积神经网络正是将空间不变性的这一个概念系统化,从而基于这个模型使用较少的参数来学习有用的表示。
设计适合计算机视觉的神经网络架构:
平移不变性:不管检测对象出现在图像的哪个位置,神经网络的前几层应该对相同的图像区域具有相似的反应。
局部性:神经网络的前几层应该只探索输入图像中的局部区域,而不过度在意图像中相隔较远区域的关系,最后可以聚合这些局部特征,以在整个图像级别进行预测。
X,H均为二维张量,但是为了使得每个神经元都能接收到每个输入像素的信息,将参数从权重据矩阵替换成四阶权重张量W,U中包含偏置参数可以将全连接层形式化为:
跟李沐学AI之卷积神经网络_第1张图片
不是需要通过重新变形和重新索引变成卷积,而是卷积是一种特殊的全连接层。卷积神经网络是包含全基层的一类特殊的神经网络,V被称为是卷积核,也被称为卷积层的权重。在图像处理中,卷积层通常比全连接层需要更少的参数,但依旧获得高效用的模型。多个输入和输出通道使模型在每个空间位置可以获取图像的多方面特征。

损失抖动很厉害的原因是数据集很大

图像卷积

卷积层所表达的运算其实是互相关运算,输入张量和核张量通过互相关运算产生输出张量。
跟李沐学AI之卷积神经网络_第2张图片
输出大小=输入大小-卷积核大小
在这里插入图片描述 输出的卷积层有时被称为特征映射,因为它可以被视为一个输入映射到下一层的空间维度的转换器,在卷积神经网络中,对于某一层的任意元素x,其感受野是指在前向传播期间可能影响x计算的所有元素(来自所有先前层),感受野可能大于输入的实际大小。
二维卷积的核心计算是二维互相关计算,最简单的计算是对二维输入数据和卷积核执行互相关操作,然后添加一个偏置。学习卷积核时,无论用严格卷积运算,或互相关运算,卷积层的输出不会受太大影响。当需要检测输入特征中更广的区域时,可以构建一个更深的卷积网络。

# 互相关运算
import torch
from torch import nn
from d2l import torch as d2l


# K 是核矩阵
def corr2d(X,K):
    """计算二维互相关运算"""
    # 拿出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
# 验证上述二维互相关运算的输出
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) 

运行结果:
tensor([[19., 25.],
        [37., 43.]])

实现二维卷积层

# 实现二维卷积层
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
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]])
# 输出的Y中代表的1从白色到黑色边缘,-1代表从黑色到白色边缘
Y = corr2d(X,K)
Y = 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.]])
       
 # 卷积核K只可以检测到垂直边缘 将X转置之后就没有办法进行检测
corr2d(X.t(),K)
运行结果:
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.]])        

学习卷积核

# 学习由X生成Y的卷积核 输入是1通道输出是1通道
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))
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
    if( i+1 )%2 == 0:
        print(f'batch{i+1},loss{l.sum():.3f}')
 
 运行结果:
 batch2,loss4.077
batch4,loss1.060
batch6,loss0.332
batch8,loss0.119
batch10,loss0.046

# 所学的卷积核的权重张量
conv2d.weight.data.reshape((1,2))
tensor([[ 0.9701, -1.0133]])

填充和步幅

填充和步幅都是卷积层的超参数
填充在输入周围添加额外的行/列,来控制输出形状的减少量
在这里插入图片描述
我们需要设置ph=kh-1和pw=kw-1,使得输入和输出具有相同的宽度和高度。假设kh是奇数,将在高度的两侧填充ph/2行,如果kh是偶数,一种可能性是在输入顶部填充[ph/2]行,在底部填充[ph/2]行,按照相同的道理,我们填充宽度的两侧。
卷积神经网络中卷积核的高度和宽度通常为奇数,选择奇数的好处是,保持空间维度的同时,可以在顶部和底部填充相同的数量的行,在左侧和右侧填充相同数量的列。
在这里插入图片描述
加粗样式
在这里插入图片描述
超参数的排序怎么样,填充一般设置为核减一,通常来讲 步幅为1更好一些,核大小最为关键。卷积核的边长一般取奇数,对称会更好填充。通过多层卷积最后输出和输入的形状相同,特征会丢失,机器学习本身是一个信息压缩的过程。自动训练参数,只要验证数据集取的更好一些,过拟合可以更好的避免。使用小的卷积会更快一些。一个不同的卷积匹配不同的纹理特征。填充和步幅可用于有效的调整数据的维度。

# 填充和步幅

# 在所有侧边填充一个像素
import torch 
from torch import nn
def comp_conv2d(conv2d,X):
    X = X.reshape((1,1)+X.shape)
    Y = conv2d(X)
    return Y.reshape(Y.shape[2:])

# 左右各填充一行 核为3 不会对输出大小产生什么改变
conv2d = nn.Conv2d(1,1,kernel_size = 3,padding = 1)
X = torch.rand(size=(8,8))
comp_conv2d(conv2d,X).shape

torch.Size([8, 8])

#填充不同的高度和宽度
conv2d = nn.Conv2d(1,1,kernel_size = (5,3),padding=(2,1))
comp_conv2d(conv2d,X).shape

torch.Size([8, 8])

# 将宽度和高度的步幅设置为2
conv2d = nn.Conv2d(1,1,kernel_size = 3,padding=1,stride = 2)
comp_conv2d(conv2d,X).shape

torch.Size([4, 4])

# 一个稍微复杂的例子
conv2d = nn.Conv2d(1,1,kernel_size=(3,5),padding=(0,1),stride=(3,4))
comp_conv2d(conv2d,X).shape

torch.Size([2, 2])

多输入多输出的通道数

当输入包含多个通道时,需要构造一个于输入数据具有相同输入通道数的卷积核。对每个通道输入的二维张量和卷积核的二维张量进行互相关运算,再对通道求和得到二维张量,这是多通道输入和多输入通道卷积核之间进行二维互相运算的结果。
跟李沐学AI之卷积神经网络_第3张图片
多个输入通道,彩色图像可能有RGB三个通道,转换为灰度会丢失信息。每个通道都有一个卷积核,结果是所有通道卷积结果的和。
每一层有多个输出通道是至关重要的。随着神经网络层数的加深,我们回增加输出的通道的维数,通过减少空间分辨率以获得更大的通道深度,我们可以将每个通道看作是对不同特征的响应。但是每个通道不是独立学习的,而是为了共同使用而优化的,多输出通道并不仅是学习多个单通道的检测器。
多个输出通道,无论多少个输入通道,到目前为止我们只使用到单输出通道。我们可以有多个三维卷积核,每个核生成一个输出通道。每个输出通道可以识别特定模式。输入通道核识别并组合输入中的模式。
跟李沐学AI之卷积神经网络_第4张图片
1*1的卷积层,不识别空间模式,只是融合通道.
跟李沐学AI之卷积神经网络_第5张图片

#1*1的卷积
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.matmul(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)
Y2 = corr2d_multi_in_out(X,K)
assert float(torch.abs(Y1-Y2).sum())<1e-6

多输入输出通道可以用来扩展卷积层的模型
当以每像素为基础应用时,11的卷积层相当于全连接层
1
1卷积层通常用于调整网络层的通道数量和控制模型复杂性。

# 多输入多输出通道互相关运算
import torch
from d2l import torch as d2l

def corr2d_multi_in(X,K):
    return sum(d2l.corr2d(x,k) for x,k in zip(X,K))

# 验证互相关运算的输出
X = torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
        [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
K = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])
corr2d_multi_in(X,K)

运行结果:
tensor([[ 56.,  72.],
        [104., 120.]])
# 计算多个通道的输出的互相关函数
# K是4D,k是3D
def corr2d_multi_in_out(X,K):
    return torch.stack([corr2d_multi_in(X,k) for k in K],0)
# 创建3个输出通道
K = torch.stack((K,K+1,K+2),0)
K.shape
运行结果:
torch.Size([3, 2, 2, 2])
corr2d_multi_in_out(X,K)=tensor([[[ 56.,  72.],
         [104., 120.]],

        [[ 76., 100.],
         [148., 172.]],

        [[ 96., 128.],
         [192., 224.]]])

输出通道是卷积层的超参数
每个输入通道有独立的二维卷积核,所有通道结果相加得到一个输出通道结果
每个输出通道有独立的三维卷积核
假设输出和输入的高宽没有变化的情况下,不会修改channel的值。当输入和输出的高宽都减半的时候,通常会把输出的通道数增加1倍。
padding会影响性能不会影响精度。每个通道的卷积核权重值是不一样的,不同通道的卷积核的大小是一样的。bias的值是有用的,但是影响不大。卷积可以获取位置信息,位置信息是由输出元素所处的位置所决定的。通道之间是不共享参数的。输入的高宽和输出的高宽是相关的。

池化层

随着神经网络中层叠的上升,每个神经元对敏感的感受野就越大。降低卷积层对位置的敏感性,同时降低对空间采样表示的敏感性。
跟李沐学AI之卷积神经网络_第6张图片
池化操作可以较好的解决上面问题。池化层与卷积层类似,都具有填充和步幅,可以进行指定。没有可以学习的参数,默认步幅与汇聚窗口的大小相同。在每个输入通道应用池化层以获得相应的输出通道,不会像卷积一样去融合多输入通道。输出通道等于输入通道,在每个输入通道上单独运算,而不是像卷积层一样在通道上对输入进行汇总。
最大池化层:每个窗口中最强的模式信号
平均池化层:将最大池化层中的“最大”操作替换为“平均”
1.池化层返回窗口中最大或平均值
2.缓解卷积层存在的位置敏感问题以及减少计算量
3.同样有窗口大小、填充和步幅作为超参数
4.池化层一般都放在卷积层的后面
5.池化层窗口有重叠和没有重叠没有什么影响
6.通过卷积也会对减少计算量 后期会做数据增强 使得卷积层不会过拟合到某个位置,所以池化层就不那么重要
7.使用最大汇聚层以及大于1的步幅,可以减少空间维度。

# 池化层
# 实现池化层的正向传播
import torch
from torch import nn
from d2l import torch as d2l
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]])
pool2d(X,(2,2))  

tensor([[4., 5.],
        [7., 8.]])  
# 验证平均池化层
pool2d(X,(2,2),'avg')
tensor([[2., 3.],
        [5., 6.]])
#填充和步幅
X = torch.arange(16,dtype=torch.float32).reshape((1,1,4,4))
X=tensor([[[[ 0.,  1.,  2.,  3.],
          [ 4.,  5.,  6.,  7.],
          [ 8.,  9., 10., 11.],
          [12., 13., 14., 15.]]]])
# 深度学习框架中的步幅与池化窗口的大小是相同的
pool2d = nn.MaxPool2d(3)
pool2d(X) = tensor([[[[10.]]]])
#填充和步幅可以手动指定
pool2d = nn.MaxPool2d(3,padding=1,stride=2)
pool2d(X)= tensor([[[[ 5.,  7.],
          [13., 15.]]]])
# 设定一个任意大小的矩形池化窗口,分别设定填充和步幅的高度和宽度
pool2d = nn.MaxPool2d((2,3),padding=(1,1),stride=(2,3))
pool2d(X)  =  tensor([[[[ 1.,  3.],
          [ 9., 11.],
          [13., 15.]]]]) 
# 池化层在每个输入通道上单独运算
X = torch.cat((X,X+1),1)
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.]],

         [[ 1.,  2.,  3.,  4.],
          [ 5.,  6.,  7.,  8.],
          [ 9., 10., 11., 12.],
          [13., 14., 15., 16.]],

         [[ 2.,  3.,  4.,  5.],
          [ 6.,  7.,  8.,  9.],
          [10., 11., 12., 13.],
          [14., 15., 16., 17.]],

         [[ 1.,  2.,  3.,  4.],
          [ 5.,  6.,  7.,  8.],
          [ 9., 10., 11., 12.],
          [13., 14., 15., 16.]],

         [[ 2.,  3.,  4.,  5.],
          [ 6.,  7.,  8.,  9.],
          [10., 11., 12., 13.],
          [14., 15., 16., 17.]],

         [[ 2.,  3.,  4.,  5.],
          [ 6.,  7.,  8.,  9.],
          [10., 11., 12., 13.],
          [14., 15., 16., 17.]],

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

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

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

         [[ 7.,  9.],
          [15., 17.]],

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

         [[ 7.,  9.],
          [15., 17.]],

         [[ 7.,  9.],
          [15., 17.]],

         [[ 8., 10.],
          [16., 18.]]]])

LeNet

LeNet是早期成功的神经网络
跟李沐学AI之卷积神经网络_第7张图片
每个卷积块中的基本单元是一个卷积层,一个sigmoid激活函数和平均汇聚层,每个卷积层使用55卷积核和一个sigmoid激活函数。这些层将输入映射到多个二维特征输出,通常同时增加通道的数量。第一层卷积有6个输出通道,而第二个卷积层有16个输出通道。每个22池操作通过空间下采样将维数减少4倍,卷积的输出形状由批量大小、通道数、高度、宽度决定。为了将卷积块的输出传递给稠密块,必须在小批量中展平每个样本。将四维输入转换成全连接层所期望的二维输入。二维表示的第一个维度索引小批量中的样本,第二个维度给出每个样本的平面向量表示。稠密块有三个全连接层,分别有120,84,10个输出。
请注意,在整个卷积块中,与上一层相比,每一层特征的高度和宽度都减小了。 第一个卷积层使用2个像素的填充,来补偿卷积核导致的特征减少。 相反,第二个卷积层没有填充,因此高度和宽度都减少了4个像素。 随着层叠的上升,通道的数量从输入时的1个,增加到第一个卷积层之后的6个,再到第二个卷积层之后的16个。 同时,每个汇聚层的高度和宽度都减半。最后,每个全连接层减少维数,最终输出一个维数与结果分类数相匹配的输出。

# 由两个部分组成,卷积编码器和全连接层密集块
# 换成relu激活函数 需要调节学习率 否则不收敛
import torch
from torch import nn
from d2l import torch as d2l

class Reshape(torch.nn.Module):
    def forward(self,x):
        return x.view(-1,1,28,28)
    
net = torch.nn.Sequential(
     Reshape(),
     # 32*32 填充
     nn.Conv2d(1,6,kernel_size=5,padding=2),nn.Sigmoid(),
     nn.AvgPool2d(kernel_size=2,stride=2),
     nn.Conv2d(6,16,kernel_size=5),nn.Sigmoid(),
     nn.AvgPool2d(kernel_size=2,stride=2),
     # 通道数以及高宽变成一个一维的向量
     nn.Flatten(),
     nn.Linear(16*5*5,120),nn.Sigmoid(),
     nn.Linear(120,84),nn.Sigmoid(),
     nn.Linear(84,10))
# 检查模型
X = torch.rand(size=(1,1,28,28),dtype=torch.float32)
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape:\t',X.shape)
Reshape output shape:	 torch.Size([1, 1, 28, 28])
Conv2d output shape:	 torch.Size([1, 6, 28, 28])
Sigmoid output shape:	 torch.Size([1, 6, 28, 28])
AvgPool2d output shape:	 torch.Size([1, 6, 14, 14])
Conv2d output shape:	 torch.Size([1, 16, 10, 10])
Sigmoid output shape:	 torch.Size([1, 16, 10, 10])
AvgPool2d output shape:	 torch.Size([1, 16, 5, 5])
Flatten output shape:	 torch.Size([1, 400])
Linear output shape:	 torch.Size([1, 120])
Sigmoid output shape:	 torch.Size([1, 120])
Linear output shape:	 torch.Size([1, 84])
Sigmoid output shape:	 torch.Size([1, 84])
Linear output shape:	 torch.Size([1, 10])

# LeNet在Fashion-MNIST数据集上的表现
batch_size = 256
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)

# 对evaluate_accuracy函数进行轻微的修改
def evaluate_accuracy_gpu(net,data_iter,device=None):
    """使用GPU模型在数据集上的精度"""
    if isinstance(net,torch.nn.Module):
        net.eval()
        if not device:
            device = next(iter(net.parameters())).device
    # 累加器
    metric = d2l.Accumulator(2)
    # 将数据转到对应的设备
    for X,y in data_iter:
        if isinstance(X,list):
            X = [x.to(device) for x in X]
        else:
            X = X.to(device)
        y = y.to(device)
        metric.add(d2l.accuracy(net(X),y),y.numel())
    # 分类正确的个数/y的个数
    return metric[0]/metric[1]

# 为了使用GPU还需要一点改动  训练模型
def train_ch6(net,train_iter,test_iter,num_epochs,lr,device):
    def init_weights(m):
        if type(m) == nn.Linear or type(m) == nn.Conv2d:
            # 初始化
            nn.init.xavier_uniform_(m.weight)
    net.apply(init_weights)
    print('training on ',device)
    # 参数转移到gpu内存上
    net.to(device)
    optimizer = torch.optim.SGD(net.parameters(),lr=lr)
    loss = nn.CrossEntropyLoss()
    #  动画效果
    animator = d2l.Animator(xlabel='epoch',xlim=[1,num_epochs],
                           legend=['train loss','train acc','test acc'])
    timer,num_batches = d2l.Timer(),len(train_iter)
    # 迭代
    for epoch in range(num_epochs):
        metric = d2l.Accumulator(3)
        net.train()
        # batch
        for i,(X,y)in enumerate(train_iter):
            timer.start()
            optimizer.zero_grad()
            X,y = X.to(device),y.to(device)
            y_hat = net(X)
            l = loss(y_hat,y)
            l.backward()
            optimizer.step()
            metric.add(l*X.shape[0],d2l.accuracy(y_hat,y),X.shape[0])
            timer.stop()
            train_l = metric[0]/metric[2]
            train_acc = metric[1]/metric[2]
            if(i+1)%(num_batches//5) == 0 or i == num_batches-1:
                animator.add(epoch+(i+1)/num_batches,(train_l,train_acc,None))
        test_acc = evaluate_accuracy_gpu(net,test_iter)
        animator.add(epoch+1,(None,None,test_acc))
        
    print(f'loss{train_l:.3f},train acc {train_acc:.3f},'f'test acc{test_acc:.3f}')
    print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
          f'on {str(device)}')
# 训练和评估LeNet-5模型  过拟合现象比mlp更小一些
lr,num_epochs = 0.9,10
train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())

跟李沐学AI之卷积神经网络_第8张图片

高宽减半,通道数翻倍
view和reshape没有本质区别 view不会发生本质变化 reshape功能高于view
输出通道匹配的某种特定的模式
小数据集通常跑5次然后取平均
最大池化不一定比平均池化会损失更多的信息
卷积的2d和3d的区别

1.卷积神经网络是一类使用卷积层的网络
2.在卷积神经网络中,我们组合使用卷积层、非线性激活函数和汇聚层。
3.为了构造高性能的卷积神经网络,通常对卷积层进行排列,逐渐降低其表示的空间分辨率,同时增加通道数。
4.在传统的卷积神经网络中,卷积块编码得到的表征在输出之前需要由一个或多个全连接层进行处理。

你可能感兴趣的:(深度学习,深度学习)