神经网络的整体构建
神经网络的基本骨架
首先可以在Pytorch官网的Python API中查看torch.nn的使用,如下所示。可以看到神经网络包括Container(基本骨架)、卷积层、池化层、Padding层、非线性激活等等。
构建一个神经网络首先要先构建起基本骨架,也就是Containers
nn.Moudle的使用
这是官网中给出的具体示例,重点在于创建我们自己的神经网络类的时候必须要继承父类nn.Moudle,然后就可以重写里面的函数等,这里的forward是前向传播函数,后面会有反向传播函数
这是一个简单的nn.Moudle使用示例,并没有涉及到神经网络的卷积层等。可以通过断点调试来查看具体的代码执行流程
from torch import nn class CY(nn.Module): def __init__(self): super().__init__() def forward(self,input): output=input+1 return output cy=CY() input=1 output= cy(input) print(output)
卷积层
构建好基本骨架之后,就需要对卷积层进行操作,可以看到官方给出的卷积层包括以下方式,其中对于图像来说常用的就是卷积2d操作
图像的卷积
首先明确一下图像卷积的概念,如下图所示,图像卷积就是用卷积核在输入图像上一步步的滑动,每个方格内的元素对应相乘后相加作为输出的对应位置的元素
官方文档中给出的示例是这样的,对于参数的解释已经很详细了
这里要注意的一个点就是 卷积层的输入和卷积核都要描述成(N,C,H,W)的tensor格式,其中N表示有多少张图片,C表示有多少个通道,H表示图片的高度,W表示图片的宽度。所以初始设置输入的时候不仅要用torch.tensor
变成tensor格式,后续还需要将torch.reshape(input,[1,1,5,5])
转变为conv2d的格式,因为初始格式是只有宽和高这两个参数的
这其中有几个参数可以解释一下:
- stride:也就是卷积核一次移动的步数
- padding:是否要将输入图像进行零填充,默认为0.可以看到设置填充之后,卷积得到的结果会比原来的大
具体代码如下:
import torch import torch.nn as nn import torch.nn.functional as F input=torch.tensor([[1,2,0,3,1], # 输入图像 [0,1,2,3,1], [1,2,1,0,0], [5,2,3,1,1], [2,1,0,1,1]]) kernel=torch.tensor([[1,2,1], # 卷积核 [0,1,0], [2,1,0]]) print(input.shape) input = torch.reshape(input,[1,1,5,5]) kernel=torch.reshape(kernel,[1,1,3,3]) output= F.conv2d(input,kernel,stride=1) print(output) output_1= F.conv2d(input,kernel,stride=2) print(output_1) output_2=F.conv2d(input,kernel,stride=1,padding=1) print(output_2)
nn.conv2d的使用
官方给出的函数使用方法如下:
这里要注意的就是in_channels和out_channels的理解,可以说in_channels就是图像的通道数,也就是RGB=3,out_channels代表的是用多少个卷积核来对图像进行卷积,如果out_channels=6的时候就是用6个卷积核来对图像进行卷积,然后对得到的输出进行处理
参数的具体描述如下:
具体代码如下,要注意的是使用tensorboard对图像进行显示的时候,由于tensorboard显示的图像格式是规定的3个通道,所以上面得到的6个通道的图像是会报错的。所以我们可以用 output = torch.reshape(output, (-1, 3, 30, 30))
来将图像格式进行重新设置,其中-1表示的是占位符,表示这个位置的参数交给后面的参数来计算
# -*- coding: utf-8 -*- import torch import torchvision from torch import nn from torch.nn import Conv2d from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter dataset = torchvision.datasets.CIFAR10("dataset/cifar-10-batches-py", train=False, transform=torchvision.transforms.ToTensor(), download=True) dataloader = DataLoader(dataset, batch_size=64) class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.conv1 = Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1, padding=0) def forward(self, x): x = self.conv1(x) return x tudui = Tudui() writer = SummaryWriter("logs") step = 0 for data in dataloader: imgs, targets = data output = tudui(imgs) print(imgs.shape) print(output.shape) # torch.Size([64, 3, 32, 32]) writer.add_images("input", imgs, step) # torch.Size([64, 6, 30, 30]) -> [xxx, 3, 30, 30] output = torch.reshape(output, (-1, 3, 30, 30)) writer.add_images("output", output, step) step = step + 1 writer.close()
最后得到到图像是这样的:
可以看到输出图像一个批次中有128个图像,这也就是我们将6个通道变为8个通道导致的,和初步设想一致
池化层
这里主要讲解的是2D类型最大池化层,同样的,详细的函数信息在官网上:
主要注意的就是**ceil_mode 这个参数,这里的意思其实就是要向下取整还是向上取整,如果为True的话就是向上取整,False的话就是向下取整。也就是说,在下图这个示例中,如果取为True的时候在进行池化的时候对于多出来的部分(原图像是5×5,池化核是3×3),会进行保留并得出结果,而为False的时候就不会保留结果。
dilation这个参数其实就是池化的时候是否要跳步进行
代码如下:
import torch import torchvision from torch import nn from torch.nn import MaxPool2d from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter dataset = torchvision.datasets.CIFAR10("../dataset", train=False, download=True, transform=torchvision.transforms.ToTensor()) dataloader = DataLoader(dataset, batch_size=64) class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=False) def forward(self, input): output = self.maxpool1(input) return output tudui = Tudui() writer = SummaryWriter("../logs_maxpool") step = 0 for data in dataloader: imgs, targets = data writer.add_images("input", imgs, step) output = tudui(imgs) writer.add_images("output", output, step) step = step + 1 writer.close()
得到的结果如下,其实池化就是相当于做一个缩略马赛克处理
非线性激活
非线性激活就是例如ReLu、Sigmod等非线性激活函数,在Pytorch中的使用是比较简单的,调用函数即可,例如Sigmod函数:
这里要注意inplace的作用就是是否要有一个新的返回值来存储输出值,默认为False,如果为True的话输出值覆盖输入值
除了上面列举的一些神经网络最基本必须的网络之外,torch.nn中还有很多其他的层:正则化层、线性层、Transformer层等等,有一些在特定的网络中需要特定使用,可以去了解一下
Sequential的作用
sequential的作用就是将我们要创建的神经网络的层数按照顺序堆叠起来,个人觉得用处就是简化代码,后面可以再了解看看,如下图所示,用sequential堆叠起神经网络之后就可以直接创建实例并输入。相较于用x输出承接x输入是简洁很多的。
损失函数和优化器
损失函数和优化器在模型构建中是十分重要的,直接决定到模型的最终效果好坏,不过与前面不同的是,这一部分在代码调用十分简单,重点在于理解 损失函数和优化算法的原理,所以直接看官方文档:损失函数、优化算法