Pytorch搭建和训练神经网络模型

Pytorch搭建神经网络步骤

  • 1.神经网络骨架:继承Containers中的Module类
  • 2.卷积操作:Conv2d类
  • 3.池化操作:MaxPool2d类
  • 4.非线性激活操作:ReLU类
  • 5.全连接操作:Linear类
  • 6.序列操作:Sequential类
  • 7.损失函数:loss类
  • 8.优化器:optim.SGD类
  • 9.对现有网络模型VGG16的修改和使用

进入Pytorch官网,点击Docs下的Pytorch,在右侧点击 torch.nn,可以看到搭建神经网络以下步骤中出现的每个类的定义和使用方法。

1.神经网络骨架:继承Containers中的Module类

Containers译为容器,可以形象得翻译为骨架或框架,该包在torch.nn中。而其中一般只使用Module类,重写该类后,相当于完成了对我们要搭建的神经网络的骨架设计,后续的所有操作,包括卷积、池化等都在该类中写明。例如,一个简易的重写该类代码为:

import torch
from torch import nn


class test_Model(nn.Module):
    def __init__(self):
    	# 必须包含该语句,意为执行父类,即Module的初始化函数
        super().__init__()

    def forward(self, input):
        output = input + 1
        return output


test_model = test_Model()
x = torch.tensor(1.0)
output = test_model(x)
print(output)

可以看到,Module类中,需重写两个函数,一个是初始化init函数,该函数中必须包含继承父类初始化的语句;另一个是forward前向传播(正向传播)函数,该函数是该类的主体,通过直接传参调用。

2.卷积操作:Conv2d类

Conv2d类为进行卷积操作的类,其中的2d代表是对二维数据操作,或者说卷积核为二维,该类的官方定义为:

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode=‘zeros’, device=None, dtype=None)

其中的参数分别表示:

in_channels:输入图像的通道数,即channel,彩色图像为3;
out_channels:卷积后输出的通道数,实际上输出多少层通道数便会令卷积核有多少层;
kernel_size:卷积核大小,可以是一个数,也可以是一个元组,如果是一个数x,则卷积核大小为xx,如果为一个元组如(x, y),则卷积核大小为xy;
stride:步长,即每次卷积核移动长度,默认值为1;
padding:对输入图像进行边缘填充,如果为1则上下左右各自加一排填充,即如果原来输入图像尺寸是xx,填充后为(x+2)(x+2)。默认值为0;
padding_mode:选择填充方式,即填充的数是什么,默认是’zeros’;
不常用参数:
dilation:卷积核间的距离,默认值为1;
group:一般只用默认值,默认值为1;
bias:是否添加偏置,即kx+b中的b,为True则为添加,默认值为True。

例如要构建只有一层3*3的卷积网络,输入时通道数channel为3,输出时channel为6,则有如下代码:

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("../data", train=False, transform=torchvision.transforms.ToTensor(),
                                       download=True)
dataloader = DataLoader(dataset, batch_size=64)

class test_Model(nn.Module):
    def __init__(self):
        super(test_Model, 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

test_model = test_Model()

writer = SummaryWriter("../logs")

step = 0
for data in dataloader:
    imgs, targets = data
    output = test_model(imgs)
    print(imgs.shape)
    print(output.shape)
    # 输入前图像img尺寸为torch.Size([64, 3, 32, 32]),其中64是我们的打包个数所以其实输入的是一个四维的数据
    writer.add_images("input", imgs, step)
    # 输出图像output尺寸为torch.Size([64, 6, 30, 30]),我们想要将其转化为[xxx, 3, 30, 30],所以reshape
    output = torch.reshape(output, (-1, 3, 30, 30))
    writer.add_images("output", output, step)

    step = step + 1

writer.close()

3.池化操作:MaxPool2d类

池化操作包括很多种,有最小池化,平均池化,最大池化等,最常用的是最大池化MaxPool2d。该类的官方定义为:

torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)

其中参数分别表示:

kernel_size:池化核大小,用于取多大的空间中的最大值,和卷积核一样,可以是一个数,也可以是一个元组,如果是一个数x,则卷积核大小为xx,如果为一个元组如(x, y),则卷积核大小为xy;
stride:步长,和卷积中一样,不过其默认值为kerenel_size,也就是每次取下一个块与上一个块没有重合部分;
padding:填充,和卷积中完全一样;
dilation:核间距离,一般不设置,可去官网查看,官网的动图较为形象理解;
return_indices:用得极少,一般不设置;
ceil_mode:是否取不足的部分;池化过程中有可能会有出现比如说剩余部分是12的大小,而池化核是22大小,那么这一部分1*2的还需不需要池化呢,或者说还需不需要取最大值呢;那么该值其为True时,则保留该部分,即取该多余部分的最大值,如果该值为False则舍去这一部分。

例子代码如下:

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("../data", train=False, download=True,
                                       transform=torchvision.transforms.ToTensor())

dataloader = DataLoader(dataset, batch_size=64)

class test_Model(nn.Module):
    def __init__(self):
        super(test_Model, self).__init__()
        self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=False)

    def forward(self, input):
        output = self.maxpool1(input)
        return output

test_model = test_Model()

writer = SummaryWriter("../logs_maxpool")
step = 0

for data in dataloader:
    imgs, targets = data
    writer.add_images("input", imgs, step)
    output = test_model(imgs)
    writer.add_images("output", output, step)
    step = step + 1

writer.close()

4.非线性激活操作:ReLU类

激活函数上也分多种,较为常用的为ReLU类,激活函数较为简单,传递的参数较少,如在ReLU类中官方定义为:

torch.nn.ReLU(inplace=False)
仅有一个参数inplace,该参数代表是否替代原数据,即当数据经过非线性激活后,是将新数据直接覆盖在原变量上,还是创建新变量以存储新产生的数据。当值为True时,表示直接替换;当值为False时,表示保留原值,生成新的变量以存储激活完之后的数据。一般情况下都取False,这样可以保留原数据,默认值为False。

代码为:

import torch
import torchvision
from torch import nn
from torch.nn import ReLU, Sigmoid
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

input = torch.tensor([[1, -0.5],
                      [-1, 3]])

input = torch.reshape(input, (-1, 1, 2, 2))
print(input.shape)

dataset = torchvision.datasets.CIFAR10("../data", train=False, download=True,
                                       transform=torchvision.transforms.ToTensor())

dataloader = DataLoader(dataset, batch_size=64)

class test_Model(nn.Module):
    def __init__(self):
        super(test_Model, self).__init__()
        self.relu1 = ReLU()
        self.sigmoid1 = Sigmoid()

    def forward(self, input):
        output = self.sigmoid1(input)
        return output

test_model = test_Model()

writer = SummaryWriter("../logs_relu")
step = 0
for data in dataloader:
    imgs, targets = data
    writer.add_images("input", imgs, global_step=step)
    output = test_model(imgs)
    writer.add_images("output", output, step)
    step += 1

writer.close()

代码中的input和dataloader任选一个作为输入,以验证网络。其中,input必须将其reshape为4维数据,因为前文提到,Module的输入数据应该是4维,第一维代表打包的大小;第二维代表通道数;第三维代表图像的高;第四维代表图像的宽。

5.全连接操作:Linear类

全连接层,也称线性层,该层的也有多种操作,其中最常见的为Linear类的操作,即课上学习的全连接层操作,其官方定义为:

torch.nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)

其中参数表示:

in_features:输入数据尺寸,该尺寸为一个数值,该值等于输入数据所有维度乘积,例如输入数据尺寸为(2, 2, 2, 2),则该参数此时应填16;
out_features:输出数据尺寸,该尺寸也为一个数值,输出之后是一个一维数组;
bias:是否需要偏置项,默认为True需要;
device, dtype不常用。

所以一般为了得到in_features,我们会在输入之前将数据变为111*x的数据,以此获得输入数据尺寸,所以要调用torch.flatten方法,该方法将原数据展平,即不论几维数据,都将其变为一维数组。代码为:

import torch
import torchvision
from torch import nn
from torch.nn import Linear
from torch.utils.data import DataLoader

dataset = torchvision.datasets.CIFAR10("../data", train=False, transform=torchvision.transforms.ToTensor(),
                                       download=True)

dataloader = DataLoader(dataset, batch_size=64, drop_last=True)

class test_Model(nn.Module):
    def __init__(self):
        super(test_Model, self).__init__()
        self.linear1 = Linear(196608, 10)

    def forward(self, input):
        output = self.linear1(input)
        return output

test_model = test_Model()

for data in dataloader:
    imgs, targets = data
    print(imgs.shape)
    output = torch.flatten(imgs)
    print(output.shape)
    output = test_model(output)
    print(output.shape)

6.序列操作:Sequential类

Sequential类的作用是将所有的操作进行打包,使得骨架内只需要一个Sequential类,可以简化代码,用法较为简单,例如对某网络使用该类的代码如下:

import torch
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.tensorboard import SummaryWriter


class test_Model(nn.Module):
    def __init__(self):
        super(test_Model, self).__init__()
        self.model1 = Sequential(
            Conv2d(3, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, 5, padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)
        )

    def forward(self, x):
        x = self.model1(x)
        return x

test_model = test_Model()
print(tudui)
input = torch.ones((64, 3, 32, 32))
output = test_model(input)
print(output.shape)

writer = SummaryWriter("../logs_seq")
writer.add_graph(tudui, input)
writer.close()

其中,add_graph是用于以流程图的形式显示我们的神经网络,第一个参数是模型名,第二个参数是输入数据。

7.损失函数:loss类

同样在官网中提供有多种loss函数,最简易的为绝对值损失函数L1Loss,即把输出结果与目标正确结果直接相减取绝对值,作为loss。用法为:

torch.nn.L1Loss(size_average=None, reduce=None, reduction=‘mean’)
前两个参数一般都不改变,reducetion参数为计算方式,默认为mean是多个loss取平均值,也可设置为sum,即多个loss直接相加。

创建了L1Loss的实例对象后给其传入要验证的结果和真实结果两组数据即可。其它loss类似,例如绝对值损失L1Loss、平方差损失MSELoss和交叉熵CrossEntropyLoss代码如下:

import torch
from torch.nn import L1Loss
from torch import nn

inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
targets = torch.tensor([1, 2, 5], dtype=torch.float32)

inputs = torch.reshape(inputs, (1, 1, 1, 3))
targets = torch.reshape(targets, (1, 1, 1, 3))

loss = L1Loss(reduction='sum')
result = loss(inputs, targets)
# 平方差loss
loss_mse = nn.MSELoss()
result_mse = loss_mse(inputs, targets)

print(result)
print(result_mse)

# 交叉熵loss
x = torch.tensor([0.1, 0.2, 0.3])
y = torch.tensor([1])
x = torch.reshape(x, (1, 3))
loss_cross = nn.CrossEntropyLoss()
result_cross = loss_cross(x, y)
print(result_cross)

注意:L1Loss和MSELoss创建的实例对象中,传入的inputs和targets应当都是是N1的数组,即包含相同个数数据的一维数组,其中N是batch_size;在CrossEntropyLoss中,如果x是尺寸为NC的数组,y应该是尺寸为N的数组,其中N是batch_size,C是类别数(二分类是2,十分类则是10)。

8.优化器:optim.SGD类

优化器即对参数进行反向传播,对参数进行修改,即训练参数的过程,optim中有多种优化器,以SGD为例,其用法为:

torch.optim.SGD(params, lr=, momentum=0, dampening=0, weight_decay=0, nesterov=False)

参数分别表示:

params:要调整的参数,即w和b,传递时只需将模型的参数传入即可;
lr:学习率,一般先取较大值训练,一段时间后取较小值;
其余参数是涉及该优化器算法的参数设置,一般不改变。

通过前面的模型和loss等学习,整合后的代码为:

import torch
import torchvision
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.optim.lr_scheduler import StepLR
from torch.utils.data import DataLoader

dataset = torchvision.datasets.CIFAR10("../data", train=False, transform=torchvision.transforms.ToTensor(),
                                       download=True)

dataloader = DataLoader(dataset, batch_size=1)

class test_Model(nn.Module):
    def __init__(self):
        super(test_Model, self).__init__()
        self.model1 = Sequential(
            Conv2d(3, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, 5, padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)
        )

    def forward(self, x):
        x = self.model1(x)
        return x


loss = nn.CrossEntropyLoss()
test_model = test_Model()
# 创建优化器
optim = torch.optim.SGD(test_model.parameters(), lr=0.01)
# 设置轮次为20轮
for epoch in range(20):
    # 记录每轮的总loss
    running_loss = 0.0
    for data in dataloader:
        imgs, targets = data
        outputs = test_model(imgs)
        result_loss = loss(outputs, targets)
        # 设置每次反向传播后将各参数梯度重新设为0
        optim.zero_grad()
        # 获取各个参数的梯度反向传播
        result_loss.backward()
        # 将更新后的参数传入模型中
        optim.step()
        running_loss = running_loss + result_loss
    print(running_loss)

注意:optim.zero_grad必须在每次循环反向传播前调用,为了先将梯度清零,防止影响到此次反向传播的梯度;optim.step负责在反向传播完之后将参数更新。

9.对现有网络模型VGG16的修改和使用

进入Pytorch官网,点击Docs下的torchvision,在左侧点击torchvision.models,再点击右侧Classification下的VGG。
现有网络模型在该页面右侧,包括图像分类、目标检测、语义分割等,VGG16是图像分类中较为经典的网络,接下来介绍如何利用Pytorch给的现有网络进行修改或者增加,形成自己所需要的网络。
VGG16模型类的调用官方定义为:

torchvision.models.vgg16(pretrained: bool = False, progress: bool = True, **kwargs: Any)

其中参数表示:

pretrained:模型参数是否采用ImageNet中已经训练好的;即ImageNet中训练过的参数可能可以更快得适应于我们自己的数据集,如果为True则是通过ImageNet训练过的,如果为False则是随机初始化的,默认值为False;
progress:在Pretrained为True时,是否显示下载进度条,因为当Pretrained为True时,要下载ImageNet训练完的参数;默认为True。

例如我们要用VGG16的网络模型训练CIFAR10数据集,则需要修改最后一层网络结构,给出两种修改方式,代码为:

import torchvision

# 本来想下载ImageNet数据集比较一下训练完的参数,但因该数据集太大,现在已经无法从该网站下载
# train_data = torchvision.datasets.ImageNet("../data_image_net", split='train', download=True,
#                                            transform=torchvision.transforms.ToTensor())
from torch import nn

# 比较ImageNet训练好的参数和随机初始化的参数的差别
vgg16_false = torchvision.models.vgg16(pretrained=False)
vgg16_true = torchvision.models.vgg16(pretrained=True)
print(vgg16_true)

train_data = torchvision.datasets.CIFAR10('../data', train=True, transform=torchvision.transforms.ToTensor(),
                                          download=True)
# 因为VGG16最后一层分类器是1000个类,而我们现在的数据集CIFAR10是十分类,所以要修改网络,使之最后一层输出为10
# 一种方法是在最后一层后加一层全连接网络,输入为1000,输出为10
vgg16_true.classifier.add_module('add_linear', nn.Linear(1000, 10))
print(vgg16_true)

# 另一种方法是把最后一层网络原来是(4096, 1000)改为(4096, 10)
print(vgg16_false)
vgg16_false.classifier[6] = nn.Linear(4096, 10)
print(vgg16_false)

你可能感兴趣的:(pytorch,pytorch,神经网络,深度学习)