阅读前请注意:
1. 该学习笔记是本人根据B站UP“我是土堆”PyTorch基础课程所记录的个人笔记,用于个人记录,包含了一些实践总结。
2. 这篇笔记所涉及的PyTorch内容非常非常基础,仅用于了解PyTorch的基本使用方法,不适合对PyTorch有一定了解的同学。
3. 请谅解笔记可能会出现的错误,欢迎指正讨论。
文章目录
实战1:Dataset类实战
Dataset主要用于存储神经网络数据集。Dataset类的设置要根据数据集文件夹结构的不同来设置。该类当中需要定义的方法如下:
def __init__(self, root_dir, label_dir) # 初始化函数,定义数据集基地址+标签名称,并使用os.path.join函数融合找到图片集所在路径,使用os.listdir将路径下图片转换为列表。
def __getitem__(self, idx) # 用于返回图片和标签。从列表中取出图片名称,再使用os.path.join结合基地址和标签名称找到对应图片的路径。
def __len__(self) # 定义len函数返回数据集的数量。
实战2:Tensorboard的使用
Tensorboard是tensorflow内置的一个可视化工具,它通过将tensorflow程序输出的日志文件的信息可视化使得tensorflow程序的理解、调试和优化更加简单高效。
使用前需要导入包from torch.utils.tensorboard import SummaryWriter
。SummerWriter的作用是,直接向文件夹log_dir写入事件文件,该文件可以被TensorBoard解析。
使用前需要创建实例writer = SummaryWriter("logs_dir")
,使用完成后应设置关闭函数writer.close()
。
常用的tensorboard函数有:
# 用于绘制函数
writer.add_scalar("图像名", scalar_value(纵坐标), global_step(横坐标))
# 用于绘制图像
writer.add_image("图像名", 图像变量, global_step)
# 用于绘制多组图像
writer.add_images("图像名", 图像变量, global_step)
# 用于绘制神经网络模型
writer.add_graph(model, input_to_model)
写入事件文件后,如果需要用tensorboard查看,就需要打开环境中断,键入以下代码:
Tensorboard –logdir=D:\ --port=600x
将网址复制进入tensorboard页面查看即可。
实战3:transforms实战
Transforms相关函数用于对图像进行基本处理,包括图像变换、数据类型转换、尺寸变换等。使用前需要导入包from torchvision import transforms
,使用各函数之前必须创建实例才能使用。
为什么需要tensor张量数据类型?因为tensor当中的某些属性是独有的,例如_backward_hooks表示反向传播钩子,_grad表示梯度,_grad_fn表示梯度方法,_drive可以看到具体使用的设备等等。tensor相较于传统的图像数据,包含了神经网络理论基础的参数。
常用函数如下:
ToTensor() # 将一个PIL Image图片或numpy.ndarray类型的图片转换为tensor
ToPILImage() # 将一个其它类型的图片转换为PIL Image
Normalize([mean_x], [sigma_x]) # 依照一个tensor图片的平均值和标准差来归一化(正则化)图片,需要给出每一个通道的均值和标准差
transforms.Resize([h, w]) # 将输入的PIL Image图片重新设置为给定尺寸
Compose([transforms_1], [transforms_2]) # transforms组合操作
Torch.reshape(img, [shape]) # 重新设置数据的尺寸大小
RandomCrop([h, w]) # 对一个PIL Image进行随机裁剪
实战4:torchvision.datasets实战
Pytorch官网提供了很多种类的数据集。使用数据集之前,先查看其类的定义,再进行调用下载。例如CIFAR数据集的类定义如下:
CLASS:
torchvision.datasets.CIFAR10(root: str, train: bool = True, transform: Optional[Callable] = None, target_transform: Optional[Callable] = None, download: bool = False)
常用参数的含义如下:
root(string):数据集所在位置(cifar-10-batches-py所在位置)
train(bool):选择是否为训练集,否则为测试集
transform(callable):对数据集进行transform变换
target_transform(callable):对于目标进行transform变换
download(bool):是否自动从网上下载数据集
按照参数定义进行下载即可。注意,路径由于Windows的转义问题必须使用双反斜杠。如果下载速度过慢,可以使用镜像源。
数据集中的class_to_idx展示了类别(target)和编号(index)的对应关系。
实战5:dataloader的使用
dataloader是一个数据加载器,用于将数据加载进入神经网络中,
它与dataset的区别是,dataset只是表示数据集存储的位置,并不能进行神经网络处理,而dataloader会从dataset当中以一定的方式(设定参数)加载数据。使用前需要导入from torch.utils.data import DataLoader
。
调用方法如下:
DataLoader(dataset = test_set, # 选择数据集
batch_size = 64, # 选择打包组的大小
shuffle = True, # 选择是否随机洗牌
num_workers = 0,
drop_last = False) # 选择是否舍弃尾部数据
如果对 dataset进行取值操作,会返回两个参数:getitem(): return imgs, targets。查看时应采用循环的方式进行查看。
实战6:神经网络骨架、卷积层实战
Pytorch为我们提供了很多神经网络的Containers(基本骨架)、 Convolution Layers(卷积层),Pooling Layers(池化层),
Padding Layers(padding填充),Non-linear Activations(非线性激活函数),Normalization Layers(正则化层)等等。根据需要进行重写,以设计自己的神经网络。
Module是所有神经网络模型最基础的类,是 Containers中的重要类。nn.Module给所有神经网络提供了一个模板(作为父类),因此非常重要。
其类定义的模板参考如下:
class Model(nn.Module):
def __init__(self): # 初始化函数
super(module_1, self).__init__() #用于初始化
self.conv1 = nn.Conv2d(1, 20, 5) # 定义了一些卷积操作
self.conv2 = nn.Conv2d(20, 20, 5)
def forward(self, x): # 前向传播函数,定义了在每一次计算过程中的前向传播方向,需要在所有子类中重写
x = F.relu(self.conv1(x))
return F.relu(self.conv2(x))
官方文档给出了多种实现卷积层的函数,其中最常用的是二维卷积nn.Conv2d。其在Module类的__init__()里初始化如下:
CLASS:
torch.nn.Conv2d(in_channels,
# 输入图像通道数量,例如彩色图像是3通道
out_channels,
# 经过卷积操作处理后输出图像通道数量
kernel_size,
# 卷积核(滤波器)形状,int或tuple,卷积核内的参数由分布函数决定,已经固定不能调整,详见参考文档
stride=1,
# 规定卷积核每次移动的步长,可以是单个数(同时控制横向纵向),或一个元组(sH, sW)(横向路径,纵向路径)
padding=0,
# padding填充的大小,int或tuple
dilation=1,
# 卷积核对应位的距离(卷积过程中核之间的距离,常用于空洞卷积)
groups=1,
# 用于分组卷积,其值应可以被in_channels整除
bias=True, # 偏置,将卷积结果加减常数
padding_mode='zeros',
# 填充值,可以取'zeros','reflect','replicate','circular'
device=None, dtype=None)
自定义的二维卷积层函数可由如下模板定义:
import torch.nn.functional as F
def conv2d(input: Tensor,
# 输入tensor图片的形状,(minibatch, in_channels, iH, iW)
weight: Tensor,
# 卷积核(滤波器)形状,(out_channels, in_channels/groups, kH, kW)
bias: Optional[Tensor]=None,
# 偏置,Optional bias tensor of shape (out_channels)
stride: Union[_int, _size]=1,
# 步长,取整数或元组(sH, sW)
padding: Union[_int, _size]=0,
# padding填充,取整数或元组(pH, pW)
dilation: Union[_int, _size]=1,
# The spacing between kernel elements, 取整数或元组(dH, dW)
groups: _int=1) -> Tensor: ...
# 将输入分组,in_channels应当可以被groups整除
output = conv2d(input)
需要注意的是,卷积核内的参数是从一些分布当中进行采样,卷积核内的值会根据网络训练过程进行不断调整。参考链接:https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html#torch.nn.Conv2d
实战7:池化层实战
常见的池化有nn.MaxPool2d(最大池化下采样),nn.MaxUnpool2d(最大池化上采样),nn.AvgPool2d(平均池化),nn.LPPool2d(指数平均池化),nn.AdaptiveAvgPool2d(自适应平均池化)。
Pooling的主要作用:
(1)首要作用:下采样,降维,去除冗杂信息,同时增大了感受野,
保留了feature map的特征信息,降低了参数量
(2)实现非线性,在一定程度上防止过拟合发生
(3)可实现特征不变性
通常来讲,max-pooling效果更好,虽然max-pooling和avg-pooling都对数据做了下采样,但max-pooling更大程度上是做了特征选择,选出了分类辨识度更好的特征,提供了非线性。
这就类似于nms(非极大抑制),一方面能抑制噪声,另一方面能提升特征图在区域内的显著性,筛选出极大值。
可以理解为,最大池化保留了纹理特征,平均池化保留了整体的数据特征,全局平均池化有定位作用。而平均池化能减少低一种误差,更多保留图像背景信息,max-pooling能减小第二种误差,更多的保留了纹理信息。
二维最大池化的类定义如下(写在Module的类的init函数里):
CLASS:
torch.nn.MaxPool2d(kernel_size,
# 卷积核大小,int or tuple
stride=None,
# 步长,int, 默认的步长是池化核的大小
padding=0,
# padding填充,int or tuple
dilation=1,
# 卷积核每个像素之间的距离
return_indices=False,
ceil_mode=False)
# 值为True时,将会使用ceil而非floor来计算输出大小,默认为False
输入input数据要求:(batches, C, Hin, Win),数据类型dtype = torch.float32(不支持long)
输出大小为:(batches, C, Hout, Wout),其中:
Hout = floor( (1/stride[0]) * (Hin + 2padding[0] -dilation[0](kernel_size[0]-1) - 1) + 1)
Wout = floor( (1/stride[1]) * (Win + 2padding[1] - dilation[1](kernel_size[1]-1) - 1) + 1)
池化操作即使用卷积核对对应区域采用相关操作,例如MaxPooling就是取区域内最大值,AvgPooling就是取区域内均值。由于步长一般与池化核的大小相同,因此可能池化核在边界上会有一部分池化核覆盖了输入图像、另一部分在输入图像之外,此时,ceil_mode就用于选择是否保留这一区域的池化结果,如果保留,采用ceil_mode = True,否则为False。
实战8:非线性激活函数实战
非线性激活的目的是给神经网络引入非线性特质,最常见的是nn.ReLu(是一个因果的u^(-1)(t),即将所有小于0的输入变为0,大于0的不变),nn.Sigmoid(Sigmoid = 1/(1+exp(-x)))。
其输入输出统一为:
Input: (N, *),*表示任意维度的数据,N为batch_size
Output: (N, *),与输入相同大小
在class中定义时,需要定义的参数是inplace = bool,其规定了将结果赋值给新变量还是覆盖输入变量,
若inplace = True,则将覆盖原变量,False则将赋值给一个新变量。默认为False。
实战9:神经网络线性层及其它层实战
正则化层的主要作用是加快网络训练的速度。它能消除部分隐藏单元的影响,让神经网络更简单,避免过拟合的情况发生。常用的有nn.BatchNorm2d,其在module类中的定义如下:
CLASS:
torch.nn.BatchNorm2d(num_features, # 输入数据的通道数量C
eps=1e-05,
momentum=0.1,
affine=True,
track_running_stats=True,
device=None,
dtype=None)
调用时,使用output = nn.BatchNorm2d(input), 其中:input大小为(Batches, C, H, W),output大小与input相同。
在循环层Recurrent Layers中,常用到nn.RNN、nn.LSTM,这些层都是针对某些特定的场景,例如文字识别、自然语言处理等等,属于一种特定的网络层,根据需要添加。
变换层Transformer Layers也是一种特定的网络层,具体情况具体分析。
随机失活层Dropout Layers主要是通过随机失活来避免过拟合问题的发生,以概率P对输入进行随机失活。
稀疏层Sparse Layers中的nn.Embedding主要用于自然语言处理当中。
在官网的torchvision.models里,有已经预设好的常用模型,例如VGG,ResNet(关于图像处理的model)。此外,这当中还有用于分类的(classification),语义分割的(Semantic Segmentation),物体检测、实例分割和人体关键点检测(Object Detection, Instance Segmentation, Person Keypoint Detection)的各类模型。按照其类的定义来初始化、调用。
Linear Layers表示线性层,其作用是为传入数据进行线性变换,y = xA^T + b, 输入数据类型支持TensorFloat32。线性层常用于神经网络末尾的全连接层。下图简要地画出了其结构:
x1 -- k1x1+b1 --> g1 -- beta1 --> O1
x2 -- k2x2+b2 --> g2 -- beta2 --> O2
X => G => O =>
.. ... ...
xd -- kxd+b --> gl -- beta --> On
input_feature hidden_layer output_layer
(上面这一段可能在部分设备上看会有错位,不过大家理解这个意思就好)相当于,如果把一张1通道5 * 5的图片经过线性层输出为1 * 3的数据量,那么in_features大小为(1,25), 输出为(1,3)。是否有偏置b由bias参数决定,每一个k和b由反向传导算法进行调整,而k和b该怎么取,是由一个分布决定,具体见参考文档。
其在module类中的定义如下:
CLASS:
torch.nn.Linear(in_features, # 输入样本的数据量大小
out_features, # 输出样本的数据量大小
bias=True, # 是否设置偏置b,默认为True
device=None, dtype=None)
实战10:神经网络搭建实战、Sequential的使用方法
torch.nn -> Container中还有一个经常用到的初始化模型函数,Sequential。相较于Module,Sequential可以使代码在书写的时候更简洁,管理更方便。定义如下:
CLASS:
torch.nn.Sequential(*args)
用例:
model = nn.Sequential(
nn.Conv2d(1,20,5),
nn.ReLU(),
nn.Conv2d(20,64,5),
nn.ReLU())
设计一个VGG网络,对CIFAR10进行分类,神经网络结构如下:
Inputs (3@3232)
==== Convolution (55 kernel) ======>>> Feature maps(32@3232)
==== MaxPooling (22 kernel) ======>>> Feature maps(32@1616)
==== Convolution (55 kernel) ======>>> Feature maps(32@1616)
==== MaxPooling (22 kernel) ======>>> Feature maps(32@88)
==== Convolution (55 kernel) ======>>> Feature maps(64@88)
==== MaxPooling (22 kernel) ======>>> Feature maps(64@44)
==== Flatten ======>>> Hidden Units(1@11032)
==== Linear ======>>> Hidden Units(1@1*64)
==== Full Connected(Linear) ======>>> Output(10)
完整的代码记录如下:
test_set = torchvision.datasets.CIFAR10(root = " ",
train = False, transform = torchvision.transforms.ToTensor(), download = True)
dataloader = DataLoader(test_set, batch_size = 64, drop_last = True)
# 要把最后几个舍去,因为大小不匹配
class Module_1(nn.Module):
def __init__(self):
super(Module_1, self).__init__()
self.conv1 = nn.Conv2d(in_channels = 3, out_channels = 32,
kernel_size = [5,5],
stride = 1,
padding = 2)
# 需要让输出的大小为32,调整stride和padding
self.maxpooling1 = nn.MaxPool2d(kernel_size = [2,2])
# MaxPooling里不要乱设步长!
self.conv2 = nn.Conv2d(in_channels = 32, out_channels = 32,
kernel_size = [5,5], stride = 1, padding = 2)
self.maxpooling2 = nn.MaxPool2d(kernel_size = [2,2])
self.conv3 = nn.Conv2d(in_channels = 32, out_channels = 64,
kernel_size = [5,5], stride = 1, padding = 2)
self.maxpooling3 = nn.MaxPool2d(kernel_size = [2,2])
self.flatten = nn.Flatten()
self.linear1 = nn.Linear(in_features = 1024, out_features = 64)
self.linear2 = nn.Linear(in_features = 64, out_features = 10)
def forward(self, x):
x = self.conv1(x)
x = self.maxpooling1(x)
x = self.conv2(x)
x = self.maxpooling2(x)
x = self.conv3(x)
x = self.maxpooling3(x)
x = self.flatten(x)
x = self.linear1(x)
x = self.linear2(x)
return x
# 核心关注:img在每一层的大小,简单地代入一组数据进行测试更方便
module_1 = Module_1()
writer = SummaryWriter(" ")
step = 0
for data in dataloader:
imgs, targets = data
output = module_1(imgs) # 输出结果是每个图片的分类结果
writer.add_images("input", imgs, step)
step += 1
writer.add_graph(module_1, imgs) # 绘制网络的图像
writer.close()
实战11:损失函数
损失函数的作用主要有2个:1.计算实际输出和目标值之间的差距; 2.为更新输出提供一定依据(反向传播)。一定要根据自己的需要去选择。
nn.L1Loss:
nn.L1Loss,也称平均绝对值误差(MAE),它将输出和目标之间作绝对值,求和并取均值。公式定义如下:
l(x,y) = L = {l1,…,lN}^T,其中N为batch_size,ln = |xn - yn|
l(x,y) = mean(L), if reduction = ‘mean’ (default)
= sum(L), if reduction = ‘sum’
类的定义如下:
CLASS:
torch.nn.L1Loss(size_average=None, reduce=None,
reduction='mean')
调用时,输入数据 input = (N, *), N为batch_size, *为任意维度的数据,最常用的为(N, C, H, W),并且输入类型必须为float32,目标值数据 Target 与 input 相同大小、类型,输出为标量,但如果 reduction = ‘none’,则输出数据大小为 (N, *)。
nn.MSELoss:
nn.MSELoss,也称均方误差(MSE),它将输入与目标求平方差,并累加。数学公式如下:
l(x,y) = L = {l1,…,lN}^T,其中N为batch_size,ln = (xn - yn)^2
l(x,y) = mean(L), if reduction = ‘mean’ (default)
= sum(L), if reduction = ‘sum’
类定义如下:
CLASS:
torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
输入输出与L1Loss同理。
nn.CrossEntropyLoss:
nn.CrossEntropyLoss,也称交叉熵,主要目的是检验分类器分类能力。如果说神经网络输出对每一类的概率都差不多,那么交叉熵就会很大,说明神经网络不能很好地分类。
交叉熵的loss函数描述如下,假设某图片经过神经网络三分类之后对每一类的概率为x = [x1,x2,x3],该图片为第class类:
loss(x, class) = -log( exp(x[class]) / 求和exp(x[j]) )
= -x[class] + log(求和exp(x[j]))
例如,现在有三个类别,分别为[0, 1, 2],某个图片经过神经网络之后对每一个种类的输出概率为 x = [0.1, 0.2, 0.3],该图片的种类为 class = 1 ,那么其损失值为:
loss(x, class = 1) = -0.2 + log(exp(0.1)+exp(0.2)+exp(0.3))
类定义如下:
CLASS:
torch.nn.CrossEntropyLoss(weight=None,
size_average=None,
ignore_index=- 100,
reduce=None,
reduction='mean',
label_smoothing=0.0)
输入输出的大小:
Input: (N, Class),N为batch_size,C为分类总数,相当于N行C列;
Target: (N)
需要注意的是, input必须要是没有经过处理的对每一类的得分值(也就是概率)。
实战12:优化器实战(包含了模型的保存与加载)
loss函数的反向传播函数给出了每个需要调节的参数(例如卷积核取值,线性层的参数等)的梯度,优化器可以根据得到的梯度对各个参数进行调整,以达到减小损失值的目的。
如何使用一个优化器?
(1)构造优化器的对象,该对象将保存当前状态并根据计算的梯度更新参数
例如:
optimizer = optim.SGD(model.parameters(), # 模型参数
lr=0.01, # 学习速率
momentum=0.9)
optimizer = optim.Adam([var1, var2], lr=0.0001)
(2)调用优化器的step方法,由梯度来对参数进行更新
例如:
# 注意,一定要循环起来
for input, target in dataset:
optimizer.zero_grad() # 非常重要!将上一次循环中求得的梯度清零!
output = model(input) # 将输入数据输入神经网络,得到输出结果
loss = loss_fn(output, target) # 计算损失值
loss.backward() # 反向传播求梯度
optimizer.step() # 由梯度,对神经网络的参数进行调整
优化器基本结构如下:
CLASS:
torch.optim.Optimizer(params, defaults)
由于不同种类的优化器底层数学原理不一样、需要的参数不一样,因此要根据不同的模型来选择合适的优化器。重点关注优化器的 parameters 和 learning rate 两个参数。parameters是模型的参数,用于告诉优化器模型长什么样、可以调整的参数有哪些。而lr表示学习速率,规定了每一次参数更新迭代的速率,速率大或小都有其优缺点。 lr太大时,模型训练不稳定;lr太小时,训练速度慢,一般来说,先让模型训练速度快,之后再将训练速度下降。其它参数因优化器不同而不同。
简单介绍一个常用的优化器——随机梯度下降优化器 torch.optim.SGD:
结构如下:
CLASS:
torch.optim.SGD(params,
# 初始化时,写成 “模型名称.parameters()”
lr=<required parameter>, # 一般来说是0.01
momentum=0, dampening=0, weight_decay=0,
nesterov=False, *, maximize=False, foreach=None)
优化器在每一次训练网络的迭代中的定义方式:
step = 0
for epoch in range(20):
# 训练20代(因为如果只有1代的话很难对网络参数进行很好的调整)
running_loss = 0.0
# 每一次迭代的所有数据loss值之和(所有数据的损失值)
for data in dataloader:
optim.zero_grad() # 非常重要的一步!使梯度清零
imgs, targets = data
output = module_1(imgs) # 输出结果是每个图片的分类结果
writer.add_images("input", imgs, step)
result_Loss = Loss(output, targets) # 计算交叉熵
result_Loss.backward()
# 对计算得到的损失函数值进行反向传导算法来计算梯度
optim.step()
# 每一次迭代都会根据每个节点的梯度对网络中的参数进行调优
running_loss = running_loss + result_Loss
step += 1
writer.add_scalar("LossFunction", running_loss, step)
writer.add_graph(module_1, imgs) # 绘制网络的图像
writer.close()
注意,module_1.state_dict()可以查看模型内部参数。一些保护参数在Spyder中不能直接从变量存储器当中查看,否则会报错。
网络模型保存、加载有2种方法。
保存/加载方式1:保存网络结构、网络参数。
torch.save(module_1, "路径.pth")
module_2 = torch.load("路径.pth ")
第一类保存/加载方式有一个陷阱,即必须在前面定义该神经网络的类(第二类不需要),否则报错。
保存/加载方式2:保存网络参数到一个字典里(官方推荐,占用空间更小)。
torch.save(module_1.state_dict(), "路径.pth ")
module_3_param = torch.load("路径.pth ")
# 如果需要加载为模型
module_3 = Module_1()
module_3.load_state_dict(torch.load("路径.pth "))
一种好的保存、加载模型的习惯是,将训练好的模型统一保存在一个地方,再写一个定义这些模型的类的一个model.py文件,需要用到的时候直接 from model.py import *
即可。
实战13:现有网络模型的使用及修改实战
VGG网络是Oxford的Visual Geometry Group的研究组提出的,这也就是VGG的由来。VGG是一种常用的图像分类深度学习神经网络,在多个迁移学习任务中的表现要优于GoogLeNet,并且从图像中提取CNN特征,VGG模型是首选算法。但它的缺点在于,参数量有140M之多,需要更大的存储空间。常用的是VGG16和VGG19。
调用vgg16函数的方法如下:
torchvision.models.vgg16(*, weights: Optional[torchvision.models.vgg.VGG16_Weights] = None,
# 是否预训练网络(可选择VGG16_Weights.IMAGENET1K_V1),默认为None
progress: bool = True,
# 是否展示训练进度条
**kwargs: Any)
使用预训练网络时需要导入包:from torchvision.models import VGG16_Weights
。
对一个pytorch官网给出的既有模型,可以通过增加结构、修改结构两种方式来按需修改网络。
增加网络结构的例子:
VGG16.classifier.add_module('add_linear', nn.Linear(in_features = 1000, out_features = 10))
修改网络结构的例子:
VGG16.classifier[6] = nn.Linear(in_features = 4096, out_features = 100)
需要注意的是,即使已经更改了网络结构的名称,网络中的各结构依然会按照列表的方式顺序存储。
实战14:完整模型训练实战
完整的网络模型训练代码如下:
# In[a1]: 导入包
import torch
from torch.utils.data import DataLoader
from torch import nn
from torch.utils.tensorboard import SummaryWriter
import torchvision
from model import * # 必须跟Script文件在同一路径下,且必须与类同名
# 要导入自己写的model.py包,必须要先把这个包运行一遍才能导入
writer = SummaryWriter("D:\Anaconda\Project_File\proj_pytorch_course_2\Script\logs_14")
# In[a2]: 准备数据集
train_set = torchvision.datasets.CIFAR10(
root = "D:\\Anaconda\\Project_File\\proj_pytorch_course_2\\Dataset\\Dataset_cifar10_data",
train = True, transform = torchvision.transforms.ToTensor(), download = True)
test_set = torchvision.datasets.CIFAR10(
root = "D:\\Anaconda\\Project_File\\proj_pytorch_course_2\\Dataset\\Dataset_cifar10_data",
train = False, transform = torchvision.transforms.ToTensor(), download = True)
train_set_len = len(train_set) # 获得数据集长度
test_set_len = len(test_set)
print(f"训练集长度:{train_set_len}")
print(f"测试集长度:{test_set_len}")
# In[a3]: 使用DataLoader加载数据集
train_dataloader = DataLoader(train_set, batch_size = 64)
test_dataloader = DataLoader(test_set, batch_size = 64)
# In[a4]: 搭建神经网络(10分类)
"""
这些已经放进了model_14.py中
"""
"""
神经网络具体结构如下:
Inputs (3@32*32)
==== Convolution (5*5 kernel) ======>>> Feature maps(32@32*32)
==== MaxPooling (2*2 kernel) ======>>> Feature maps(32@16*16)
==== Convolution (5*5 kernel) ======>>> Feature maps(32@16*16)
==== MaxPooling (2*2 kernel) ======>>> Feature maps(32@8*8)
==== Convolution (5*5 kernel) ======>>> Feature maps(64@8*8)
==== MaxPooling (2*2 kernel) ======>>> Feature maps(64@4*4)
==== Flatten ======>>> Hidden Units(1@1*1032)
==== Linear ======>>> Hidden Units(1@1*64)
==== Full Connected(Linear) ======>>> Output(10)
"""
"""
class Module_1(nn.Module):
def __init__(self):
super(Module_1, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels = 3, out_channels = 32,
kernel_size = [5,5], stride = 1, padding = 2),
nn.MaxPool2d(kernel_size = [2,2]),
nn.Conv2d(in_channels = 32, out_channels = 32,
kernel_size = [5,5], stride = 1, padding = 2),
nn.MaxPool2d(kernel_size = [2,2]),
nn.Conv2d(in_channels = 32, out_channels = 64,
kernel_size = [5,5], stride = 1, padding = 2),
nn.MaxPool2d(kernel_size = [2,2]),
nn.Flatten(),
nn.Linear(in_features = 1024, out_features = 64),
nn.Linear(in_features = 64, out_features = 10)
)
def forward(self, x):
x = self.model(x)
return x
"""
# In[a5]: 创建网络模型
module_1 = Module_1()
# In[a6]: 定义损失函数
loss_fun = nn.CrossEntropyLoss() # 定义损失函数
# In[a7]: 定义优化器
learning_rate = 0.01 # 方便进行修改
optim = torch.optim.SGD(module_1.parameters(), lr = 0.01)
# In[a8]: 设置训练网络参数
total_train_step = 0 # 训练次数
total_test_step = 0 # 测试次数
total_epoch = 10 # 训练轮数
for epoch in range(total_epoch):
print(f"---------->>第{epoch+1}次训练>>--------------")
# 训练网络
module_1.train() # 把网络设置为训练模式(仅对部分层,例如Dropout、BatchNorm等,有作用)
for data in train_dataloader:
imgs, targets = data
outputs = module_1(imgs)
loss = loss_fun(outputs, targets)
optim.zero_grad() # 梯度清零
loss.backward() # 反向传导
optim.step() # 对网络参数调整
total_train_step += 1
if total_train_step % 100 == 0: # 每100次训练输出
print(f"训练次数{total_train_step},损失值{loss.item()}") # item()会将tensor类型转为数字
writer.add_scalar("train_loss", loss.item(), total_train_step)
module_1.eval() # 把网络设置为验证模式(仅对部分层,例如Dropout、BatchNorm等,有作用)
# 每一轮完成之后都会在测试集上跑一遍,以测试集上的损失/正确率评估模型是否训练好
# 在测试集上不需要使用优化器
total_test_loss = 0 # 整个测试集上的loss
total_accuracy = 0 # 正确率
with torch.no_grad(): # 不需要梯度
for data in test_dataloader:
imgs, targets = data
outputs = module_1(imgs)
# loss在一定程度上并不能代表网络的优劣。
# Outputs得到的是图像在每一种类上的概率,其中每一行对应一张输入图片,
# 使用argmax函数可以求出最大概率对应的种类,即每一行最大值所对应的下标,
# 再将下标对应的类别与target相比较,得到true或false,求和,得到所有图片是否
# 被正确标注。即可得到准确率。
# a.argmax(1)是按照行向量来看,a.argmax(0)是按照列向量来看
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy += accuracy
loss = loss_fun(outputs, targets)
total_test_loss += loss
print(f"### 第{epoch}代训练完成后整体测试集上的损失值:{total_test_loss}")
print(f"### 第{epoch}代训练完成后整体测试集上的正确率:{total_accuracy/test_set_len}")
writer.add_scalar("test_loss", total_test_loss, total_test_step)
writer.add_scalar("test_accuracy", total_accuracy/test_set_len, total_test_step)
total_test_step += 1
# 保存每一次迭代的模型
torch.save(module_1,
f"D:\Anaconda\Project_File\proj_pytorch_course_2\Model\Model_of_PL14\model_{epoch+1}.pth")
print("模型已保存!")
writer.close()
需要注意的事项如下:
实战15:使用GPU训练
GPU可以加快网络训练速度。一个NVIDIA显卡包含了其驱动和CUDA工具包ToolKit。使用任务管理器-性能可以查看GPU型号,只有支持CUDA的NVIDIA显卡才能使用CUDA对网络训练进行加速。通过torch.cuda.is_available()
可以快捷查看CUDA是否能够使用。
有2种方式可以使用CUDA进行加速。
方法1:对网络模型、数据(输入、标注等)、损失函数,调用其.cuda()函数并返回。
实例:
if torch.cuda.is_available():
module_1 = module_1.cuda()
if torch.cuda.is_available():
loss_fun = loss_fun.cuda()
if torch.cuda.is_available():
imgs = imgs.cuda()
targets = targets.cuda()
方法2:对网络模型、数据(输入、标注等)、损失函数,调用其.to(device)函数并返回即可,其中device需要指定显卡类型,例如device = torch.device(“cpu”)表示不用独立显卡,device = torch.device(“cuda”)表示使用CUDA, (“cuda:0”)表示使用第一张独立显卡。需要注意的是,只有数据需要另外赋值,网络和损失函数不需要另外赋值,直接使用a.to(device)即可。
实例:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
module_1 = module_1.to(device)
loss_fun = loss_fun.to(device)
imgs = imgs.to(device)
targets = targets.to(device)