PyTorch基础知识

阅读前请注意:

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 (5
5 kernel) ======>>> Feature maps(32@3232)
==== MaxPooling (2
2 kernel) ======>>> Feature maps(32@1616)
==== Convolution (5
5 kernel) ======>>> Feature maps(32@1616)
==== MaxPooling (2
2 kernel) ======>>> Feature maps(32@88)
==== Convolution (5
5 kernel) ======>>> Feature maps(64@88)
==== MaxPooling (2
2 kernel) ======>>> Feature maps(64@44)
==== Flatten ======>>> Hidden Units(1@1
1032)
==== 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)
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

K2SO4钾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值