当前位置:   article > 正文

【深度学习实战】从零开始深度学习(一):利用PyTorch开始深度学习_pytorch深度学习入门与实战结课

pytorch深度学习入门与实战结课

参考资料:

  1. 《Pytorch深度学习》(人民邮电出版社)第三章 深入了解神经网络
    《Pytorch深度学习》(人民邮电出版社)第四章 机器学习基础
  2. Pytorch官方文档
  3. 其他有参考的资料都在文章中以超链接的形式给出啦

0 写在前面

0.1. 利用GPU加速深度学习

疫情期间没有办法用实验室的电脑来跑模型,只好用自己的笔记本来弄。发现如果没有GPU来跑的话真的是太慢了,非常推荐利用GPU加速深度学习的训练速度。

可以参考这篇文章:深度学习pytorch GPU windows 环境搭建

如果采用GPU的话,训练函数train_model(*)中数据的输入要改变一下,也就是需要将数据放在GPU上

inputs, labels = Variable(inputs).cuda(), Variable(labels).cuda()
  • 1

另外,使用GPU训练可能会导致GPU内存不足的情况(CUDA out of memory),有一个办法就是将batch_size的值调小(其中一个原因是GPU没有办法一下子处理打包过来的那么多图片)。batch_size调小之后面临的问题自然就是训练的速度变慢。

0.2. 监控你的显存占用情况

在训练的过程中可以随时监控自己的显存占用情况,输入下面这个命令就可以:

C:\Program Files\NVIDIA Corporation\NVSMI>nvidia-smi
  • 1

得到的结果如下:
在这里插入图片描述
0.3. optimal.step()和scheduler.step()
如果有更新到PyTorch 1.1.0之后的版本,就会出现“Detected call of lr_scheduler.step() before optimizer.step().”这样的错误。可以参考这篇文章解决。

pytorch安装相关

conda安装Pytorch下载过慢解决办法
完美解决 OSError: [WinError 126] 找不到指定的模块

1 神经网络的组成部分

训练深度学习算法需要的几个步骤:

  1. 构建数据管道
  2. 构建网络架构
  3. 使用损失函数评估架构
  4. 使用优化算法优化网络架构的权重

当运用神经网络去处理一些比较复杂的问题时,神经网络的架构就会变得特别复杂。为此,诸如pytorch、tensorflow这样的深度学习框架都对一些复杂的高级功能进行了抽象,方便使用者的使用。

抽象出底层的运算并训练深度学习算法的过程如下图所示
在这里插入图片描述

1.1 层

层(Layer)是神经网络的基本组成,线性层是其中最重要的一种。在pytorch里面,线性层只需要一行代码就可以实现:

from torch.nn import Linear
myLayer = Linear(in_features = 10, out_features = 5, bias = True)
  • 1
  • 2

上面这行代码的作用在于对输入数据进行一个线性变换 y=Wx+b

其中,in_features是输入数据的维度,out_features是输出数据的维度,bias是“b”的值,默认为True;如果bias=False,则b=0。

例子:

import torch
from torch.nn import Linear
m = Linear(20, 30)
inp = torch.randn(128, 20)
out = m(inp)
print(out.size())
# output:torch.Size([128, 30])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

线性层Linear可以查询两个训练参数:W和b

# 查询weight
w = m.weight
# 查询bias
b = m.bias
  • 1
  • 2
  • 3
  • 4

1.2 非线性激活函数

我们知道,神经网络每一层的的输出应该是 z=g(Wx+b),其中g(*)为非线性的激活函数。Pytorch里面也提供了一些非线性的激活函数可以使用。

  1. sigmoid函数.
    sigmoid函数应该是大家最熟悉的非线性函数之一。它的数学定义很简单:
σ(x)=1/(1+e^-x^)

sigmoid函数以实数作为输入,并以一个0-1之间的数值作为输出。

sigmoid一度被用于不同的机器学习框架,但它存在一个主要的弊端:当sigmoid函数的输出值接近0或1时,sigmoid函数前一层的梯度接近于0,由于前一层的学习参数的梯度接近于0,使得权重不能经常调整,从而产生了无效神经元。

  1. tanh函数
  2. ReLU
    ReLU最近变得很受欢迎,它的数学公式也很简单:
    f(x)=max(0, x)

线性整流函数(Rectified Linear Unit, ReLU),又称修正线性单元,是一种人工神经网络中常用的激活函数(activation function),通常指代以斜坡函数及其变种为代表的非线性函数。

ReLU函数有助于优化器更快地找到正确的权重集合,可以使随机梯度下降收敛得更快;而且计算成本更低,因为只需要设置一个阈值。但是当一个很大的梯度进行反向传播时,会出现一些无效神经元。对于这些无效神经元,可以通过调整学习率来控制。

  1. Leaky ReLU

Pytorch里面的非线性激活函数的使用:

import torch
import torch.nn as nn

m = nn.ReLU()
inp = torch.randn(2)
output = m(inp)

print(output)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2 利用Pytorch构建深度学习框架

import torch.nn as nn

class MyFirstNetwork(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(MyFirstNetwork, self).__init__()
        self.layer1 = nn.Linear(input_size, hidden_size)
        self.layer2 = nn.Linear(hidden_size, output_size)
    
    def __forward__(self, input):
        out = self.layer1(input)
        out = nn.ReLU(out)
        out = self.layer2(out)
        return out
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在PyTorch中,所有的网络都实现为类,因此,神经网络MyFirstNetwork被创建为PyTorch类nn.Module的子类(从nn.Module继承),并实现init和forward方法。super方法用于将子类的参数传给父类。

在init方法中,初始化层,这里是构建了两个线性层。

在forward方法中,把数据传入init方法中初始化的层,并返回最终的输出。非线性层经常被forward函数直接调用,有些时候也可以在init方法中实现。

2.1 数据预处理与特征工程

数据预处理的目的是减小数据尺度等对深度学习算法训练的影响,主要包括以下数据处理步骤:

  • 向量化
    向量化主要是将各种格式(文本、声音、图片、视频等)的数据转换成PyTorch张量。
  • 归一化
    归一化是指将特定特征的数据表示成均值为0、标准差为1的数据的过程,有助于更快地训练算法并达到更高的性能。主要有两种归一化方法
    (1)min-max标准化(Min-Max Normalization)
    也称为离差标准化,是对原始数据的线性变换,使结果值映射到[0 - 1]之间。
    (2)Z-score标准化方法
    这种方法给予原始数据的均值(mean)和标准差(standard deviation)进行数据的标准化。经过处理的数据符合标准正态分布,即均值为0,标准差为1。
  • 缺失值处理
    缺失值的处理
  • 特征提取
    深度学习算法可以使用大量的数据自己学习出特征,不再使用手动的特征工程。

2.2 如何决定要使用的层?

要使用哪些层通常由具体的机器学习来决定。机器学习问题中,通常要解决的问题有三类,无论前面的层如何设计,最后一层通常是确定的:

  • 对于回归问题,最后使用的是有一个输出的线性层,输出值是连续的;
  • 对于二分类问题,通常最后一层会使用sigmoid激活函数。
  • 对于多分类问题,网络最后会使用softmax层。softmax的数学意义在于将概率和分类进行挂钩,输入概率值最大的分类。

2.3 损失函数

定义好了网络架构,还剩下了最重要的两步——评估和优化。

评估神经网络通常会利用损失函数,一般来说,损失函数越小,模型越好。损失函数的梯度可以对模型的参数进行优化。

PyTorch提供了一些可用的损失函数:

损失函数用法/作用
L1 loss通常作为正则化器使用
MSE loss均方误差损失,用于回归问题的损失函数
Cross-entropy loss交叉熵损失,用于二分类和多类别问题
NLL Loss用于分类问题,允许用户使用特定的权重处理不平衡数据集
NLL Loss2d用于像素级分类,通常和图像分割问题有关

在PyTorch里面使用这些损失函数,只要调用相应的函数就可以了:

import torch
import torch.nn as nn

loss = nn.CrossEntropyLoss()
inp = torch.randn(3,5,requires_grad = True)
target = torch.empty(3, dtype=torch.long).random_(5)
output = loss(inp, target)
output.backward()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这里以交叉熵Cross-entropy loss为例。

对于backward()函数的理解,参考Pytorch中的自动求导函数backward()所需参数含义

2.4 优化器的选择

计算出网络的损失函数之后,需要对网络的参数进行优化以减少损失。PyTorch提供了很多优化器,这些优化器可以看成是一个黑盒子,他们接受损失函数和所有的学习参数,并微量调整来改善网络性能。这些可以在PyTorch官方文章中的torch.optim里面找到。

2.5 评估机器学习模型

为了评估机器学习模型,通常会把数据集分成3个不同的部分——训练集、验证集、测试集。训练集和验证集用于训练算法并调优所有超参数;测试集用于评估模型的泛化能力。

训练集和测试集的划分通常由3种策略:简单保留验证、K折验证和迭代K这验证。在拆分数据时,需要考虑数据代表性、时间敏感性和数据冗余,这三种特性本质上都是体现样本的分布。

2.6 模型的选择

要使模型能够工作,有三个选择至关重要:

  • 最后一层的选择(激活函数的选择)和损失函数的选择
    对于不同的机器学习问题,激活函数的选择和损失函数的选择可以概括成下表:
机器学习问题激活函数损失函数
二分类sigmoidnn.CrossEntropyLoss()
多类别分类softmaxnn.CrossEntropyLoss()
多标签分类sigmoidnn.CrossEntropyLoss()
回归无(生成标量值作为输出的线性层)MSE(均方误差)
向量回归MSE(均方误差)
  • 优化器的选择
  • 学习率的选择
    如何找到适合模型的学习率目前还是一个正在研究的课题,不过PyTorch提供了一些工具来动态选择学习率(torch.optim.lr_scheduler):
    StepLR:StepLR有两个重要的参数,第一个参数step_size是步长,它表示学习率多少轮改变一次;第二个参数是gamma,他决定学习率必须改变多少,例如这一次的学习率是0.001,那么经过30轮后,学习率变成0.001*gamma=0.0001。
torch.optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
  • 1

        MultiStepLR:与StepLR类似,只不过步长是以列表的形式给出。

torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[30,80], gamma=0.1)
  • 1

        ExponentialLR:每一轮都将学习率乘上gamma值。

torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.1)
  • 1

        ReduceLROnPlateau:这是最常用的学习率改变策略之一。当特定的度量指标,如训练损失、验证损失或者准确率不再变化时,学习率就会改变。通常会将学习率的原始值降低为原来的1/2~1/10。

>>> ptimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
>>> scheduler = ReduceLROnPlateau(optimizer, 'min')
>>> for epoch in range(10):
>>>     train(...)
>>>     val_loss = validate(...)
>>>     # Note that step should be called after validate()
>>>     scheduler.step(val_loss)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3 案例实践——猫狗图像分类

光说不练假把式,我们下面就通过一个案例来巩固一下学习到的东西吧。

数据集:Dogs vs. Cats。数据集有个文件夹,一个是train(训练数据集),一个是test(测试集)。如果官网下载很慢的话,这里指路一个百度网盘的链接:【猫狗数据集】使用预训练的resnet18模型。在训练集中,有猫和狗的照片各12500张,每一张都通过文件名打标签:
在这里插入图片描述

3.1 数据集的建立(训练集+验证集)

首先是数据的分类,将数据集分为训练集(training set)和验证集(validation set)。

import os
import numpy as np

'''
数据集的分配
'''
path = 'train'
# 读取文件夹内的所有文件
files= os.listdir(path) #得到文件夹下的所有文件名称
print(f'Total no of images {len(files)}')
no_of_images = len(files)
# 创建验证集的随机文件索引
shuffle = np.random.permutation(no_of_images)
# 将训练数据集进行划分,2000个样本归入验证集,剩下的样本归入训练集
validation_index = shuffle[:2000]
train_index = shuffle[2000:]
# 创建验证集和训练集文件夹
os.mkdir(os.path.join(path, 'validation'))
os.mkdir(os.path.join(path, 'training'))
for t in ['training', 'validation']:
    for folder in ['dog', 'cat']:
        os.mkdir(os.path.join(path, t, folder))
# 将图片的一小部分复制到validation文件夹      
for i in validation_index:
    folder = files[i].split('/')[-1].split('.')[0]
    image = files[i].split('/')[-1]
    os.rename(os.path.join(path, files[i]), os.path.join(path, 'validation', folder, image))
# 将剩下的图片复制到training文件夹
for i in train_index:
    folder = files[i].split('/')[-1].split('.')[0]
    image = files[i].split('/')[-1]
    os.rename(os.path.join(path, files[i]), os.path.join(path, 'training', folder, image))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

上面的代码将数据集中的样本图片分成了训练集(23000个样本)和验证集(2000个样本),并在相应的目录底下创建了对应的类别文件夹(cat和dog)

3.2 数据预处理(图片数据转换成PyTorch张量)

数据预处理的目的是将图片加载成PyTorch张量。PyTorch的torchvision.datasets包提供了一个名为ImageFolder的工具类,可以用于加载图片以及相应的标签。

在这里插入图片描述
参数解释:
root:指定图片存储的路径
transform: 一个函数,原始图片作为输入,返回一个转换后的图片。
target_transform - 一个函数,输入为target,输出对其的转换。例子,输入的是图片标注的string,输出为word的索引。

预处理通常会经过以下三个步骤:

  • 把所有的图片转换成同等大小,希望图片具有相同的尺寸;
  • 用数据集的均值和标准差把数据集归一化;
  • 把图片数据集转换成PyTorch张量

PyTorch在transforms模块中提供了很多工具函数,可以用于完成这些预处理的步骤。

'''
数据输入及加载
'''
import torchvision.datasets as dset
import torchvision.transforms as ts
import numpy as np
import matplotlib.pyplot as plt

simple_transform = ts.Compose([ts.Resize((224, 224)), 
                               ts.ToTensor(), 
                               ts.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225])
                               ])
train = dset.ImageFolder('train/training/', simple_transform)
valid = dset.ImageFolder('train/validation/', simple_transform) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

torchvision.transforms是pytorch中的图像预处理包,包含了很多种对图像数据进行变换的函数。

注意这里的transforms是torchvision里面的transforms,不是torch.distributions里面的transforms

transforms.Compose()用于将多种转换合并在一起。
transforms.Resize()将图片调整成制定的大小。
transforms.ToTensor()将PILImage转变为torch.FloatTensor的数据形式;
transforms.Normalize()进行归一化数据

多种组合变换有一定的先后顺序,处理PILImage的变换方法(大多数方法)都需要放在ToTensor方法之前,而处理tensor的方法(比如Normalize方法)就要放在ToTensor方法之后。

更多的图片转换方法可以参考pytorch中transform常用的几个方法

在train对象中,保留了所有图片以及相应的标签:

print(train.class_to_idx)
# out: {'cat': 0, 'dog': 1}
print(train.classes)
# out: ['cat', 'dog']
  • 1
  • 2
  • 3
  • 4

同样,可以对得到的张量进行再次变形并将值反归一化,就可以得到相应的图片:

# 对张量进行可视化
def imshow(inp):
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    plt.imshow(inp)

imshow(train[50][0])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

输出图片:
在这里插入图片描述

3.3 批量加载PyTorch张量

在深度学习或者机器学习中把图片进行批量取样是一个常用的方法,PyTorch提供了DataLoader类,它是PyTorch中数据读取的一个重要接口,该接口定义在dataloader.py中,只要是用PyTorch来训练模型基本都会用到该接口。该接口的目的:将自定义的Dataset根据batch size大小、是否shuffle等封装成一个Batch Size大小的Tensor,用于后面的训练。(可以参考PyTorch源码解读(一)torch.utils.data.DataLoader

下面的代码将前面的train数据集和valid数据集转换到数据加载器(data loader)中:

import torch 
# 按批加载Pytorch张量
train_data_gen = torch.utils.data.DataLoader(train, batch_size = 64, num_workers = 3)
valid_data_gen = torch.utils.data.DataLoader(valid, batch_size = 64, num_workers = 3)
dataset_sizes = {'train':len(train_data_gen.dataset),'valid':len(valid_data_gen.dataset)}
dataloaders = {'train':train_data_gen,'valid':valid_data_gen}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这里的num_workers用于多线程处理的设置。如果在运行代码的时候碰到下面这个错误代码:

[Errno 32] Broken pipe
  • 1

那么可以参考这个原因:BrokenPipeError: [Errno 32] Broken pipe
把上面的train_data_gen和valid_data_gen改成下面的形式就可以了:

train_data_gen = torch.utils.data.DataLoader(train, batch_size = 64)
valid_data_gen = torch.utils.data.DataLoader(valid, batch_size = 64)
  • 1
  • 2

关于DataLoader的介绍可以看这篇文章:pytorch之dataloader深入剖析
文章中也给了一些参数的介绍:

  • dataset(Dataset):传入的数据集
  • batch_size(int, optional):每个batch有多少个样本
  • shuffle(bool, optional):在每个epoch开始的时候,对数据进行重新排序
  • sampler(Sampler, optional):自定义从数据集中取样本的策略,如果指定这个参数,那么shuffle必须为False
  • batch_sampler(Sampler,optional):与sampler类似,但是一次只返回一个batch的indices(索引),需要注意的是,一旦指定了这个参数,那么batch_size,shuffle,sampler,drop_last就不能再制定了(互斥——Mutually exclusive)
  • num_workers (int, optional):这个参数决定了有几个进程来处理data loading。0意味着所有的数据都会被load进主进程。(默认为0)
  • collate_fn (callable, optional):将一个list的sample组成一个mini-batch的函数
  • pin_memory (bool, optional):如果设置为True,那么data loader将会在返回它们之前,将tensors拷贝到CUDA中的固定内存(CUDA pinned memory)中.
  • drop_last (bool, optional): 如果设置为True,这个是对最后的未完成的batch来说的,比如你的batch_size设置为64,而一个epoch只有100个样本,那么训练的时候后面的36个就被扔掉了…如果为False(默认),那么会继续正常执行,只是最后的batch_size会小一点。
  • timeout(numeric, optional):如果是正数,表明等待从worker进程中收集一个batch等待的时间,若超出设定的时间还没有收集到,那就不收集这个内容了。这个numeric应总是大于等于0。默认为0。
  • worker_init_fn (callable, optional): 每个worker初始化函数 If not None, this will be called on each worker subprocess with the worker id (an int in [0, num_workers - 1]) as input, after seeding and before data loading. (default: None)

3.4 构建网络架构

对于计算机视觉中的大多数案例,我们可以使用已有的不同架构来解决实际的问题。torchvision.models模块里面提供了很多现成的应用:

import torchvision.models as models
resnet18 = models.resnet18()
alexnet = models.alexnet()
vgg16 = models.vgg16()
squeezenet = models.squeezenet1_0()
densenet = models.densenet161()
inception = models.inception_v3()
googlenet = models.googlenet()
shufflenet = models.shufflenet_v2_x1_0()
mobilenet = models.mobilenet_v2()
resnext50_32x4d = models.resnext50_32x4d()
wide_resnet50_2 = models.wide_resnet50_2()
mnasnet = models.mnasnet1_0()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里我们使用ResNet架构来解决。

# 构建网络架构
import torchvision.models as models
import torch.nn as nn
import torch.optim as op
model_ft = models.resnet18(pretrained = True)
num_ftrs = model_ft.fc.in_features
model_ft.fc = nn.Linear(num_ftrs, 2)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里,model_fit = models.resnet18(pretrained = True)创建了算法的实例,实例是PyTorch层的集合。ResNet架构是一个层的集合,具体可以参考 “Deep Residual Learning for Image Recognition”。同时,可以在这里预下载好ResNet-18模型,模型放在“C:\Users\Administrator.torch\models”文件夹下面。

pretrained (bool) – If True, returns a model pre-trained on ImageNet

ImageNet使用广泛的WordNet架构的变体来对对象进行分类,其预测的类别有1000种。使用预训练的权重会比随机分配权重得到的模型准确率更高一些。

然而,再我们这个案例中,我们最终预测的类别只有猫/狗两类,因此需要将ResNet模型的最后一层的输出特征改为2。即 model_ft.fc = nn.Linear(num_ftrs, 2)

如果在基于GPU的机器上面运行算法,需要在模型上调用cuda方法,让算法在GPU上运行:

# 检查是否可以在GPU上运行
if torch.cuda.is_available():
    model_ft = model_ft.cuda()
  • 1
  • 2
  • 3

接下来建立损失函数和基于SGD的优化器

# 损失函数和优化器
learning_rate = 0.001
criterion = nn.CrossEntropyLoss()
optimizer_ft = op.SGD(model_ft.parameters(), lr = learning_rate, momentum = 0.9)
exp_lr_scheduler = op.lr_scheduler.StepLR(optimizer_ft, step_size = 7, gamma = 0.1)
  • 1
  • 2
  • 3
  • 4
  • 5

StepLR函数帮助动态修改学习率。在scheduler的step_size表示scheduler.step()每调用step_size次,对应的学习率就会按照策略调整一次。

3.5 训练模型

我们首先来看一下训练模型的整体代码,再详细进行解读。下面的train_model函数获取模型输入,并通过多轮训练调优算法的权重,降低损失函数:

import time
from torch.autograd import Variable
# 训练模型
def train_model(model, criterion, optimizer, scheduler, num_epochs = 25):
    since = time.time()
    
    best_model_wts = model.state_dict()
    best_acc = 0.0
    
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs-1))
        print('-'*10)
        
        # 每轮都有训练和验证的阶段
        for phase in ['train', 'valid']:
            if phase == 'train':
                scheduler.step()
                model.train(True) # 模型设置为训练模式
            else:
                model.train(False) # 模型设置为评估模式
            
            running_loss = 0.0
            running_correct = 0
            
            # 在数据上迭代
            for data in dataloaders[phase]:
                # 获取输入
                inputs, labels = data
                # 封装成变量
                inputs, labels = Variable(inputs), Variable(labels)
                # 梯度参数清零
                optimizer.zero_grad()
                # 前向
                outputs = model(inputs)
                _, preds = torch.max(outputs.data, 1)
                loss = criterion(outputs, labels)
                # 只在训练阶段反向优化
                if phase == 'train':
                    loss.backward()
                    optimizer.step()
                # 统计
                running_loss += loss.item()
                running_correct += torch.sum(preds == labels.data)
            
            epoch_loss = running_loss / dataset_sizes[phase]
            epoch_acc = running_correct / dataset_sizes[phase]
            
            print('{} Loss: (:.4f) Acc: (:.4f)'.format(phase, epoch_loss, epoch_acc))
            
            # 深度复刻模型
            if phase == 'valid' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = model.state_dict()
                
        print()
    
    time_elapsed = time.time() - since
    print('Training Complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))
    
    #加载最优权重
    model.load_state_dict(best_model_wts)
    return model
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

上述函数主要实现以下四个功能:

  • 传入图片并计算损失;
  • 在训练阶段反向传播,在验证/测试阶段不调整权重;
  • 每轮训练中的损失值跨批次累加
  • 存储最优模型并打印验证准确率。

输入参数:

  • model:构建好的神经网络框架
  • criterion:损失函数
  • optimizer:构建的优化器
  • scheduler:学习率的修改
  • num_epochs = 25:循环次数
model_ft = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler, num_epochs=2)
  • 1

代码要点:

  • optimizer.step()和scheduler.step()的区别
    optimizer.step()通常用在每个mini-batch之中,而scheduler.step()通常用在epoch里面,但是不绝对,可以根据具体的需求来做。只有用了optimizer.step(),模型才会更新,而scheduler.step()是对lr进行调整。
    在scheduler的step_size表示scheduler.step()每调用step_size次,对应的学习率就会按照策略调整一次。所以如果scheduler.step()是放在mini-batch里面,那么step_size指的是经过这么多次迭代,学习率改变一次。
  • pytorch 状态字典:state_dict
    pytorch 中的 state_dict 是一个简单的python的字典对象,将每一层与它的对应参数建立映射关系.(如model的每一层的weights及偏置等等)。注意,只有那些参数可以训练的layer才会被保存到模型的state_dict中,如卷积层,线性层等等。优化器对象Optimizer也有一个state_dict,它包含了优化器的状态以及被使用的超参数(如lr, momentum,weight_decay等)
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/677872
推荐阅读
相关标签
  

闽ICP备14008679号