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前向传播(正向传播)函数,该函数是该类的主体,通过直接传参调用。
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()
池化操作包括很多种,有最小池化,平均池化,最大池化等,最常用的是最大池化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()
激活函数上也分多种,较为常用的为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维,第一维代表打包的大小;第二维代表通道数;第三维代表图像的高;第四维代表图像的宽。
全连接层,也称线性层,该层的也有多种操作,其中最常见的为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)
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是用于以流程图的形式显示我们的神经网络,第一个参数是模型名,第二个参数是输入数据。
同样在官网中提供有多种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)。
优化器即对参数进行反向传播,对参数进行修改,即训练参数的过程,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负责在反向传播完之后将参数更新。
进入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)