pytoch使用resnet50迁移demo

目录

一、介绍

    1.1、迁移学习Fintune的几种策略              

    1.2、模型效果 

二、前文

    2.1、pytorch 基本 API使用

    2.2、pytorch CNN分类MNIST数据代码

三、代码【只展示第六个练习的代码】

  3.1、代码结构

  3.2、代码解析

    3.2.1、使用python3的语法可以执行python2的代码 __future__

    3.2.2、MNIST数据准备和预处理

    3.2.3、模型搭建

    3.2.4、优化器optimizer设置分层学习率

    3.2.5、 学习率衰减策略

    3.2.6、LOSS构建——Focal loss

    3.2.7、Train & Eval & 模型保存【断点续传】

    3.2.8、截图手写数字 predict 预测

  3.2、代码

四、思考的问题


一、介绍

框架:pytorch
数据:MNIST 手写数据集分类
预训练模型:
    使用 resnet50 imagenet广域数据集预训练模型进行finetune,提升MNIST数据集的分类效果

    1.1、迁移学习Fintune的几种策略              

        文章:迁移学习finetune的几种策略

当数据量比较少的时候,除了上述策略外:
    那可以先冻训练前面一部分参数fintune。然后再全训练整个网络fintune 

    1.2、模型效果 

网络更换过程:
    ① 使用1层全连接 nn.Linear() 进行模型训练,60epoch训练集acc 84%,预测集acc 81%,模型欠拟合,模型训练速度会受到
       初始值初始化影响严重,良好的初始化值acc超过86%,而不合适时甚至达不到80%
    ② 使用3层全连接 nn.Linear() 进行模型训练,60epoch训练集acc 91%,验证集acc 87%,模型欠拟合,存在和上诉模型相同
       的问题,且模型一个批次的迭代速度明显下降。
    ③ 使用conv2d搭建简单卷积神经网络,20epoch 训练集acc 97%,验证集acc 96%,网络简单且数据量较小指标不再上升,cnn
       相对于全连接acc提升明显,而且模型迭代一个批次速度快,收敛速度也快
    ④ 使用resnet50预训练模型finetune,由于数据量小,所以采取两种方案,第一种冻结resnet50除了最后一层输出层Fc以外
       所有的参数,修改FC的输出类别,仅仅训练FC层,100epoch训练集acc 83%,验证集 82%,模型欠拟合
    ⑤ 冻结resnet第1个bottleneck及其以前所有网络参数冻结,在预训练参数基础上训练后3个bottleneck以及Fc输
       出的参数,100 epoch 训练集acc 99.10%,验证集 98.330%,模型效果得到大幅度提升,但在使用微信截图手写数字pred
       ict的过程中,“3”类别预测的效果很差。
    ⑥ 为了解决“3”类别预测的效果很差,考虑到可能是样本分布不均衡或者原始数据“3”数据是高难度特征,所以在⑤步骤的加入
       以下改变:
            a. 将 ce 改为 focal_loss
            b. restnet50后三层bottleneck是在原参数基础上进行训练,FC是重新初始化的参数,因此这里采用分层设置学习
               率lr
            c. 模型训练随着epoch增大学习率lr应当逐渐减小,但考虑优化器在学习率较小时陷入局部震荡,需要学习率lr在高
               epoch次数时拥有“上升激活”的机会,所以我这里采用了学习率多项式衰减的策略。
        最终100epoch训练集acc 99.987%,验证集 99.330%,使用微信截图手写数字predict的过程中,“3”类别预测正确。


tip:MNIST 原始图片为 28*28很小的黑底且较粗白字手写数字,所以模型训练完成后我采用的也是尺寸小于80*80黑底较粗白
        字的截图手写数字进行predict。

二、前文

    2.1、pytorch 基本 API使用

参考文章:pytorch 基本 API使用

    2.2、pytorch CNN分类MNIST数据代码

参考文章:pytorch CNN分类MNIST数据代码

三、代码【只展示第六个练习的代码】

  3.1、代码结构

  1. MNIST数据准备和预处理
  2. 模型搭建
    1. 加载resnet50预训练模型及其对应的参数
    2. 根据需求更改预训练模型的一部分结构
    3. 设置冻结层
  3. 优化器
    1. 分层学习率
  4. 学习率调整器
    1. 带“热启动”多项式学习率衰减策略
    2. 预热 warm up + 退火余弦学习衰减策略
  5. Loss 构建
    1. 用focal loss 代替传统的 ce,用于解决数据不均衡与难学习特征样本
  6. trian、eval、保存模型断点续传
  7. predict

  3.2、代码解析

    3.2.1、使用python3的语法可以执行python2的代码 __future__

from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from __future__ import with_statement
"""
在开头加上from __future__ import print_function这句之后,即使在python2.X,使用print就得像python3.X那样加括号使用。
python2.X中print不需要括号,而在python3.X中则需要。
如果某个版本中出现了某个新的功能特性,而且这个特性和当前版本中使用的不兼容,也就是它在该版本中不是语言标准,
那么我如果想要使用的话就需要从future模块导入。
  其他例子: 
    from __future__ import division , 
    from __future__ import absolute_import , 
    from __future__ import with_statement 。等等 
  加上这些,如果你的python版本是python2.X,你也得按照python3.X那样使用这些函数
    ****************** 一般需要加入到代码最上端 *************************

"""

    3.2.2、MNIST数据准备和预处理

        这里需要注意的有以下几点:

  • MNIST 数据时单通道,而 resnet50输入是三通道的,因此需要将图片转化为3通道,transforms 内部用的 PIL对象进行操作的,所以自定的函数也应该基于PIL对象进行处理的。
  • resnet50 需要输入图片的维度为224,但新版的resnet50由于存在nn.AdaptiveAvgPool2d((1, 1))自动池化的影响,理论上任意大小的方形图片都可以输入,熟悉resnet50都知道该网络一共会将图片缩小32倍,所以我们最好输入的图片均大于 32*32,MNIST数据为28*28的图片,我的做法是先将图片resize至72*72【不能放大太大】,然后transforms.CenterCrop(64)中间截取为64*64的图片大小,这样一来最后一层自动池化层AdaptiveAvgPool2d为 2*2 ----> 1*1
  • transforms是图像的数据增强库,train过程的数据要用 transforms 进行数据增强,transforms会保证每一个epoch训练时同一张图片装换成不用的图片,eval 过程不会进行数据增强
from __future__ import print_function,division,absolute_import,with_statement
import os
import copy
import torch
from torch.cuda import check_error
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from collections import OrderedDict
from torchvision import datasets,models,transforms
from torch.utils.data import DataLoader,Dataset
from torch import optim

# MNIST 灰度图 扩展至 3通道,用于 torchvision.transforms 中
def gray2color(x):
    """
        每次传入的 x 是 仅仅一张图片的 PIL对象
        将MINIST灰度图x repeat为三通道,(H,W) ----> (H,W,3)
    """
    x = np.array(x)  # PIL 对象转化为 numpy
    x = np.repeat(np.expand_dims(x,axis=-1),repeats=3,axis=-1)
    x = Image.fromarray(x.astype('uint8')).convert("RGB")   # numpy对象必须转化为 PIL 格式的图片,后续才能继续 transforms 操作
    return x

if __name__ == '__main__':
    # 查看 MNIST 数据维度
    data = datasets.MNIST('./data', train=True, download=True)
    print("原始的MNIST图片的维度:", data.data.shape)  # datasets.MNIST 最开始的数据为(B,H,W) 没有通道这一维度

    """
        transforms 过程每次传入一张图片,相当于对batchsize的维度做了一次循环
        注意:transforms 内部默认用的是 PIL 的Image对象进行转换的,因此 需要将numpy或者cv2的BGR 格式转化为 PIL RGB 格式在进行接下的操作

    """
    
    # 加载并转化MNIST图片数据
        # train 的数据要用 transforms 进行数据增强,transforms会保证每一个epoch训练时同一张图片装换成不用的图片
    train_transforms = transforms.Compose([
        gray2color,  # 这里使用自定的函数,将 单通道的图片重复为三通道的图片
        transforms.Resize(72),
        transforms.CenterCrop(64),
        transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),
        transforms.RandomGrayscale(p=0.025),  # 0.025的概率进行灰度图化,但是仍然是三通道
        transforms.ToTensor(),  # 将(b,h,w,c)  --- (b,c,h,w)后 每一个像素除以255归一化
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]
    )
        # 验证集不进行上述数据增强
    eval_transforms = transforms.Compose([
        gray2color,  # 这里使用自定的函数,将 单通道的图片重复为三通道的图片
        transforms.Resize(72),
        transforms.CenterCrop(64),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]
    )

    data = datasets.MNIST('./data', train=True, transform=train_transforms, download=True)
    print("trasformes转化后一张图片的维度:", next(iter(data))[0].shape)  # 将单通道 MINIST 扩展到三通道后,进行一系列操作后

        # 创建 train_loader 与 eval_loader
    kwargs = {'num_workers': 0, 'pin_memory': True} if use_cuda else {}
    train_loader = DataLoader(datasets.MNIST('./data', train=True, transform=train_transforms, download=True),
                              batch_size=batch_size, shuffle=True, **kwargs)

    eval_loader = DataLoader(datasets.MNIST('./data', train=False, download=True, transform=eval_transforms),
                             batch_size=256, shuffle=False, **kwargs)
    print("dataloader训练集一个batch中图片维度:", next(iter(train_loader))[0].shape)
    print("dataloader验证集一个batch中图片维度:", next(iter(eval_loader))[0].shape)

    3.2.3、模型搭建

         (1)迁移学习预训练base模型加载

  •   手动加载预训练模型离线参数
  •   自定义修改预训练模型的输出层
def initiallize_model(model_name,num_classes,feature_extract):
    """
        ********** 初始化预训练模型的参数 ************
        可初始化的模型为:resnet、alexnet、vgg、squeezenet ...
        除了 inceptionv3 需要模型的输入要求图片为3通道 299*299,其余模型输入图片要求为3通道 244*244 
    """
    model = None
    input_size = 0
    use_pretrained = feature_extract

    if model_name == 'resnet':
        if use_pretrained:
            model = models.resnet50(pretrained=False)
            model.load_state_dict(torch.load("models/resnet50-19c8e357.pth"))
        else:
            model = models.resnet50(pretrained=False)
        
        # 重新定义可训练的层,重新定义的层默认参数训练,这里只定义最后一层,即 fc层
        input_features = model.fc.in_features  # 这里获取最后一层输入的特征数
        model.fc = nn.Linear(input_features,num_classes)

        input_size = 224  # 官方resnet网络 图片的输入尺寸,所以需要将图片预处理为 224 * 224 三通
    
    else:
        print('Invaild model name,exiting ....')
        exit()

    return model,input_size

         (2)基于base模型做定义模型

  •   利用model.parameters()、model.named_parameters()、model.named_children() 冻结模型前几层的参数
  • nn.Sequential()  与 OrderedDict() 封装序列模型的方法
# 模型搭建
class BaseResnet(nn.Module):
    """
        ********** 利用resnet预训练模型搭建模型 ************
    """
    def __init__(self,init_model):
        super(BaseResnet, self).__init__()
        base_model = init_model

        # TODO 这里我利用 .named_children() 将预训练模型前 5层的参数 进行冻结
        set_parameter_requires_grad(model=base_model,feature_extract=feature_extract)

        self.model = nn.Sequential(OrderedDict([('body',base_model),
                                            ('logsoftmax',nn.LogSoftmax(dim=1))]))  # 将模型的输出值做 logsoftmax 函数并进行分组

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

         (3)冻结模型部分层的参数

def set_parameter_requires_grad(model,feature_extract):
    """
        ********** 参数冻结函数 ************
        怎么样去冻结前n层,或者自定义组合网络加载预训练模型的参数,冻结以后的模型的 优化器 optimizer 怎么去除 requires_grad==False的参数,详情请看:
        https://zhuanlan.zhihu.com/p/34147880
        怎么样任意修改删除预训练模型的某一层或某些层,具体请看:
        https://blog.csdn.net/lavinia_chen007/article/details/114833608

    """
    if feature_extract:
        for name,child in list(model.named_children())[:5]:    # 将反应的层数进行冻结
            for n,param in child.named_parameters():
                param.requires_grad = False

         (4)查看模型非冻结带训练的层

def view_will_trained_params(model,model_name):
    """
        ********** 查看模型哪些层的参数参与训练,哪些层的参数被固定了 ************
    """
    train_params = []
    for name,param in model.named_parameters():
        if param.requires_grad == True:
            train_params.append((name,param.shape))
    print("\n{} 预训练模型将要参与训练的层为:\n".format(model_name),train_params,end='\n\n\n')  # 在 feature_extract == True,仅仅最后一层 fc 的requires_grad为True,即只有fc会被训练

    3.2.4、优化器optimizer设置分层学习率

  •   使用 id 获取内存地址进行不同层参数筛选
  •   有时候model的冻结参数requires_grad = False 还会参与训练,怎样用 filter过滤掉requries_grad = False 的参数后添加至optmizer?
  1.  使用 filter(lambda p:p.requires_grad,model.parameters())
# 优化器【********* 分层定制学习率 ***********】
"""
    ***************** 屏蔽部分不需要梯度的参数,制定分层学习率 *******************
    filter(lambda p:p.requires_grad,model.parameters())
    # 因为我们冻结了一部分参数,所以我需要剔除模型中不需要计算grad的参数
    # 使用分层学习率【 根据 参数的内存id 进行筛选 】
    详情看文章:https://blog.csdn.net/xuyunyunaixuexi/article/details/100799097?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-8.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-8.control
"""
    # 特定层的参数列表
special_layers = nn.ModuleList([model.model.body.fc , model.model.logsoftmax])
        # 获取特等层的参数列表的内存id列表
special_layers_ids = list(map(lambda x:id(x),special_layers.parameters()))
    # 基础层的参数列表
basic_params = filter(lambda x:id(x) not in special_layers_ids,model.parameters())
    # 这句话的意思是 basic_params 的 lr 为1e-4,其他默认的参数的 lr 为1e-3
optimizer = optim.SGD([{'params':filter(lambda p:p.requires_grad,special_layers.parameters())},
                    {'params':filter(lambda p:p.requires_grad,basic_params),'lr':1e-3}],
                    lr=1e-2,momentum=0.9)  

    3.2.5、 学习率衰减策略

         (1)可控制“热启动”多项式衰减的自定义类 

     多项式衰减的曲线看这篇:TensorFlow学习--学习率衰减/learning rate decay     

from numpy import ceil
from copy import deepcopy
class PolynomialLRDecay():
    """
    ******************* pytorch自定义多项式衰减 分层训练的学习率,并且可以控制是否热启动学习率 *************************8
    具体可看文章:https://blog.csdn.net/akadiao/article/details/79560731
    特点:
        ① 支持分层多学习率

    Args:
        optimizer (Optimizer): pytotch 优化器
        max_decay_steps: 从基础学习降低至最低学习率的最大迭代次数
        end_learning_rate: 所有层学习率可以降低到的最低值,必须传入list
        power: The power of the polynomial.
        cycle: 学习降低最低是否热启动,具体看上诉文章,默认开启

    """
    
    def __init__(self, optimizer, max_decay_steps, end_learning_rate=None, power=0.5,cycle=True):
        if max_decay_steps <= 1.:
            raise ValueError('max_decay_steps should be greater than 1.')
        self.optimizer = optimizer
        self.base_lrs = [group['lr'] for group in optimizer.param_groups]   # 基础的分层学习率,list
        self.max_decay_steps = max_decay_steps
        self.max_decay_steps_copy = max_decay_steps
        self.end_learning_rate = end_learning_rate
        self.power = power
        self.last_step = 0     # 上一次批次的索引,用与断点续传
        self.cycle = cycle     # 控制后期学习率是否提升
        
    def get_lr(self):
        if self.last_step > self.max_decay_steps:
            return deepcopy(self.end_learning_rate[:len(self.base_lrs)])

        return [(base_lr - self.end_learning_rate[idx]) * 
                ((1 - self.last_step / self.max_decay_steps) ** (self.power)) +
                self.end_learning_rate[idx] for idx,base_lr in enumerate(self.base_lrs)]
    
    def step(self, step=None):
        # step:当前训练的迭代次数,支持断点续传
        if step is None:
            step = self.last_step + 1
        self.last_step = step if step != 0 else 1

        # 如果后期重新升高学习率
        if self.cycle == True:
            # 根据倍数更新学习率衰减的最大epoch 的值
            self.max_decay_steps = self.max_decay_steps_copy * ceil(self.last_step / self.max_decay_steps_copy)
            if self.last_step <= self.max_decay_steps:
                decay_lrs = self.get_lr()

            else:
                decay_lrs = self.get_lr()
        
        # 后期不进行学习率提升,超过学习率衰减的最大epoch后将使用最低的学习率进行训练
        else:
            decay_lrs = self.get_lr()
        for param_group, lr in zip(self.optimizer.param_groups, decay_lrs):
                    param_group['lr'] = lr

if __name__ == "__main__":
    import torch.optim as optim
    import torch
    lr = 0.1
    optimizer = optim.SGD([{'params':torch.zeros(10),'lr':0.1},
                            {'params':torch.zeros(10),'lr':0.01}])

    # scheduler 对象
    scheduler = PolynomialLRDecay(optimizer,max_decay_steps=5,end_learning_rate=[0.01,0.001,0.001],power=0.5,cycle=True)
    print([i['lr'] for i in optimizer.param_groups])
    for epoch in range(1,50):
        # 学习率调整
        scheduler.step(11 + epoch)  # 11 模拟断点续传已训练的epoch
        print(11+epoch,[i['lr'] for i in optimizer.param_groups])

         (2)pytorch 预热 warm up  + 退火余弦 学习率衰减策略

    pytorch自带了一下学习率衰减策略,其中最常用的事warm up + “退火余弦”:

    模型训练技巧——warm up + CosineAnnealingLR

(一)、什么是Warmup?

Warmup是在ResNet论文中提到的一种学习率预热的方法,它在训练开始的时候先选择使用一个较小的学习率,训练了一
些epoches或者steps(比如4个epoches,10000steps),再修改为预先设置的学习率来进行训练。

(二)、为什么使用Warmup?
由于刚开始训练时,模型的权重(weights)是随机初始化的,此时若选择一个较大的学习率,可能带来模型的不稳定(振荡),
选择Warmup预热学习率的方式,可以使得开始训练的几个epoches或者一些steps内学习率较小,在预热的小学习率下,模
型可以慢慢趋于稳定,等模型相对稳定后再选择预先设置的学习率进行训练,使得模型收敛速度变得更快,模型效果更佳。
import math
class WarmupCosineLR():
    def __init__(self,optimizer,warmup_iter:int,lrs_min:list = [1e-5,],T_max:int = 10):
        """
            ******************* pytorch自定义学习率 预热warmup + Cosline 余弦衰减 **************************
            具体可看文章:https://blog.csdn.net/qq_36560894/article/details/114004799?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-13.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-13.control

            Args:
                optimizer (Optimizer): pytotch 优化器
                warmup_iter: 预热的最大epoch
                lrs_min: list, optimizer 学习率一一对应的最小值
                T_max:余弦半周期

            特点:
                ① 支持分层学习率多组学习率衰减
        """
        self.optimizer = optimizer
        self.warmup_iter = warmup_iter
        self.lrs_min = lrs_min
        self.T_max = T_max
        self.base_lrs = [i['lr'] for i in optimizer.param_groups]
        
    def get_lr(self):
        if self.iter < self.warmup_iter:
            return [i * self.iter *1. / self.warmup_iter for i in self.base_lrs]
        else:
            return [self.lrs_min[idx] + 0.5*(i-self.lrs_min[idx])*(1.0+math.cos((self.iter-self.warmup_iter)/(self.T_max-self.warmup_iter)*math.pi)) \
                    for idx,i in enumerate(self.base_lrs)]

    def step(self,iter:int):
        if iter == 0:
            iter = iter + 1
        self.iter = iter
        # 获取当前epoch学习率
        decay_lrs = self.get_lr()
        
        # 更新学习率
        for param_group, lr in zip(self.optimizer.param_groups, decay_lrs):
            param_group['lr'] = lr
            
if __name__ == '__main__':
    import matplotlib.pyplot as plt
    import numpy as np
    import torch
    import torch.optim as optim

    optimizer = optim.SGD([{'params':[torch.zeros(10)],'lr':0.01},{'params':[torch.zeros(5)],'lr':0.05}],lr=0.1,momentum=0.9)
    # scheduler 对象
    scheduler = WarmupCosineLR(optimizer,warmup_iter=5,lrs_min=[1e-5,1e-5],T_max=10)  
    print([i['lr'] for i in optimizer.param_groups])
    for iter in range(1,101):
        scheduler.step(5+iter)   # optimizer学习率调整
        print(5+iter,[i['lr'] for i in optimizer.param_groups])

    3.2.6、LOSS构建——Focal loss

  •   focal loss  = alpha *(1 - pt)** r  * ce_loss
  •   函数输入为 log类型的output预测结果,维度为[batch,class] 和 真实标签target,维度为[class,]
def focacl_loss(output,target,alpha=1.0 , gamma=2.0,*args,**kwargs):
    """
        ********** 给定模型前向传播的输出 log output [batch,class]与真实值target[class,],计算loss误差 ************
        1. 仅仅在训练的时候使用 focal_loss ,验证时不使用 focal_loss

    """
    ce_loss = F.nll_loss(input = output,target = target, reduction="none")  # ce_loss dim: [B,]
    pt = torch.exp(-ce_loss)                                                   # pt      dim: [B,]
    # 构建 focal_loss
    focalloss = (alpha * (1.0 - pt)**gamma * ce_loss).mean()
    return focalloss

    3.2.7、Train & Eval & 模型保存【断点续传】

  •   cuda 与 cpu 参数持久化的相互自由加载转化怎么做?
# model.state_dict() cuda 与 cpu 互通
"""
    为了保证 gpu/cpu 训练的模型参数可以相互加载,这里在load时使用 map_location=lambda storage, loc: storage 来控制,详情请看文章:
    https://blog.csdn.net/nospeakmoreact/article/details/89634039?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.withoutpai&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.withoutpai
"""
if device == 'cuda':
    ck_dict = torch.load(model,map_location=lambda storage, loc: storage.cuda())    # 使用 gpu 读取 模型参数
else:
    ck_dict = torch.load(model,map_location=lambda storage, loc: storage)           # 使用 cpu 读取模型参数

# optimizer.state_dict() cuda 与 cpu 互通
if device == 'cuda':
    """
        重载optimizer的参数时将所有的tensor都放到cuda上(optimizer保存时默认放在cpu上了),详情见:
        https://blog.csdn.net/weixin_41848012/article/details/105675735
    """
    for state in optimizer.state.values():
        for k, v in state.items():
            if torch.is_tensor(v):
                state[k] = v.cuda()
  • model.train()  model.eval() with torch.no_grad() with torch.set_grad_enabled(bool)区别?
关于 model.eval()  model.train()  with torch.no_grad() with torch.set_grad_enabled(bool)

  model.eval():eval主要是用来影响网络中的dropout层和batchnorm层的行为。在dropout层保留所有的神经网络单元,
    batchnorm层使用在训练阶段学习得到的mean和var值。另外eval不会影响网络参数的梯度的计算,只不过不回传更新
    参数而已。所以eval模式下通常会使用 with torch.no_grad():不让模型参数自动求导,用于节省时间和显存。
  model.train():train 这个就是训练模式,是网络的默认模式。在这个模式下,dropout层会按照设置好的失活概率进
    行失活,batchnorm会继续计算数据的均值和方差等参数并在每个batch size之间不断更新。
  with torch.no_grad():with torch.no_grad会影响网络的自动求导机制,也就是网络前向传播后不会进行求导和进
    行反向传播。另外他不会影响dropout层和batchnorm层。一般在eval模式下使用
  with torch.set_grad_enabled(bool): 与 torch.no_grad 相似,bool = False 会将在这个with包裹下的
    所有的计算出的 新的变量 的required_grad 置为false。但原有的变量required_grad 不会改变。这实际上也就
    是影响了网络的自动求导机制。与with torch.no_grad() 相似,不过接受一个bool类型的值。
                        
  具体看原文链接:https://blog.csdn.net/a250225/article/details/108589205
  • print(model.Modules)、print(model)、print(model.state_dict()) 区别?
  1. print(model) 打印 Model 类 __init__ 初始化函数中所有self.xxx 模型结构
  2. print(model.modules)  model 有几个分开的模块
  3. model.state_dict() 模型参数有序字典,可以使用 .keys() .values() .items() 获取相应的属性
  • model.parameters()、model.named_parameters()、model.named_children()
  1. model.parameters()  model 的参数的迭代器【从上到下】,其内容与 model.state_dict().values() 相同
  2. model.named_parameters()  model 包含key和values的参数迭代器【从上到下】,其内容与 model.state_dict().items() 相同
  3. model.named_children() 获取model 第一层的"子树模型结构",本文就是用 model.named_children() [:5] 获取了resnet50 前 5层的"子结构"并进行冻结参数
    """
        关于 model.eval()  model.train()  with torch.no_grad() with torch.set_grad_enabled(bool)

        model.eval():eval主要是用来影响网络中的dropout层和batchnorm层的行为。在dropout层保留所有的神经网络单元,batchnorm层使用在训练阶段学习得到的mean和var值。
                    另外eval不会影响网络参数的梯度的计算,只不过不回传更新参数而已。所以eval模式要比 with torch.no_grad更费时间和显存。
        model.train():train 这个就是训练模式,是网络的默认模式。在这个模式下,dropout层会按照设置好的失活概率进行失活,batchnorm会继续计算数据的均值和方差等参数并在每个batch size之间不断更新。
        with torch.no_grad():with torch.no_grad会影响网络的自动求导机制,也就是网络前向传播后不会进行求导和进行反向传播。另外他不会影响dropout层和batchnorm层。
                            一般在eval模式下使用
        with torch.set_grad_enabled(bool): 与 torch.no_grad 相似,bool = False 会将在这个with包裹下的所有的计算出的 新的变量 的required_grad 置为false。
                                        但原有的变量required_grad 不会改变。这实际上也就是影响了网络的自动求导机制。与with torch.no_grad() 相似,不过接受一个bool类型的值。
                        
        具体看原文链接:https://blog.csdn.net/a250225/article/details/108589205
    """

       模型训练 train 自定义代码块 

from sklearn.metrics import confusion_matrix,accuracy_score,recall_score,precision_score,f1_score

def focal_loss(output,target,alpha=1.0 , gamma=2.0,*args,**kwargs):
    """
        ********** 给定模型前向传播的输出[batch,class]与真实值target[class,],计算loss误差 ************
        1. 仅仅在训练的时候使用 focal_loss ,验证时不使用 focal_loss
        2. 默认情况下不进行聚合
    """
    ce_loss = F.cross_entropy(input = output,target = target, reduction="none")  #这里必须使用 none 模式, ce_loss dim: [B,]
    pt = torch.exp(-ce_loss)                                                   # pt      dim: [B,]
    # 构建 focal_loss
    focalloss = (alpha * (1.0 - pt)**gamma * ce_loss).mean()
    return focalloss

def cross_entropy(output,target,*args,**kwargs):
    """
        普通的交叉熵损失函数,默认情况下不进行聚合
    """
    ce_loss = F.cross_entropy(input = output,target = target, reduction="mean")  # ce_loss 是一个均值
    return ce_loss

class WarmupCosineLR():
    def __init__(self,optimizer,warmup_iter:int,lrs_min:list = [1e-5,],T_max:int = 10):
        """
            ******************* pytorch自定义学习率 预热warmup + Cosline 余弦衰减 **************************
            具体可看文章:https://blog.csdn.net/qq_36560894/article/details/114004799?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-13.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-13.control
            Args:
                optimizer (Optimizer): pytotch 优化器
                warmup_iter: 预热的最大epoch
                lrs_min: list, optimizer 学习率一一对应的最小值
                T_max:余弦半周期,该值必须比 warmup_iter 大
            特点:
                ① 支持分层学习率多组学习率衰减
        """
        self.optimizer = optimizer
        self.warmup_iter = warmup_iter
        self.lrs_min = lrs_min
        self.T_max = T_max
        self.base_lrs = [i['lr'] for i in optimizer.param_groups]
        
    def get_lr(self):
        if self.iter < self.warmup_iter:
            return [i * self.iter *1. / self.warmup_iter for i in self.base_lrs]
        else:
            return [self.lrs_min[idx] + 0.5*(i-self.lrs_min[idx])*(1.0+math.cos((self.iter-self.warmup_iter)/(self.T_max-self.warmup_iter)*math.pi)) \
                    for idx,i in enumerate(self.base_lrs)]
 
    def step(self,iter:int):
        if iter == 0:
            iter = iter + 1
        self.iter = iter
        # 获取当前epoch学习率
        decay_lrs = self.get_lr()
        
        # 更新学习率
        for param_group, lr in zip(self.optimizer.param_groups, decay_lrs):
            param_group['lr'] = lr

def get_score(target,predict):
    con_matrix = confusion_matrix(y_true=target,y_pred=predict)
    # 计算acc
    acc = accuracy_score(y_true=target,y_pred=predict)
    # 计算 macro recall
    recall = recall_score(y_true=target,y_pred=predict,average='macro')
    # 计算 macro precision
    precision = precision_score(y_true=target,y_pred=predict,average='macro')
    # 计算 macro F1
    F1 = f1_score(y_true=target,y_pred=predict,average='macro')
    return (acc,recall,precision,F1),con_matrix

def train_one_epoch(model,device,optimizer,loss_fun,metric_fun,train_loader,current_epoch,info_interval:int=None):
    """
        ********** 一个epoch模型训练 ************
        关于 model.eval()  model.train()  with torch.no_grad() with torch.set_grad_enabled(bool) 区别
        return:
            ① batch_losses:每个batch均值loss列表
            ② 整个epoch 的 acc,recall,precision,F1
    """
    print('Training ... ')
    model.train()
    model.to(device)
    LRs = [i['lr'] for i in optimizer.param_groups] # 获取当前epoch 优化器 optimizer 学习率组
    batch_losses = []
    batch_targets = []
    batch_predicts = []
    for idx, (input_x, target) in enumerate(train_loader):
        input_x, target = input_x.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(input_x)  # 前向传播
        loss = loss_fun(output, target, alpha=1.0, gamma=2.0)
        loss.backward()  # 反向传播计算梯度
        optimizer.step()  # 更新
        batch_losses.append(loss.item())
        # 计算score
        pre = torch.argmax(output, dim=1)
        pre = pre.cpu().numpy().reshape(-1).tolist()
        target = target.cpu().numpy().reshape(-1).tolist()
        (acc,recall,precision,F1),con_matrix = metric_fun(target=target,predict=pre)
        batch_targets.extend(target)
        batch_predicts.extend(pre)

        if info_interval is not None:
            if idx % info_interval == 0:
                print("Epoch:{}\t[{}\{}\t\t{:.2f}%]\tLoss:{:.8f}\tScores: < acc:{:.3f}%\t"\
                      "macro_recall:{:.3f}%\tmacro_precision:{:.3f}%\tmacro_F1:{:.3f}%\t >\t\tBatch input_x shape:{}".format(
                    current_epoch, idx * len(input_x),
                    len(train_loader.dataset), 100. * (idx  / len(train_loader)),loss.item(),
                    100. * acc,100. * recall,100. * precision,100. * F1,input_x.shape
                ))
    # 计算一个epoch的score
    (epoch_acc, epoch_recall, epoch_precision, epoch_F1), con_matrix = metric_fun(target=batch_targets, predict=batch_predicts)
    print("Epoch Info :\tLoss:{:.8f}\tScores: <\tacc:{:.3f}%\t "\
          "macro_recall:{:.3f}%\t macro_precision:{:.3f}%\t macro_F1:{:.3f}%\t>\tLRs:{}".format(
        np.mean(batch_losses),100. * epoch_acc,100. * epoch_recall,100. * epoch_precision,100. * epoch_F1,LRs
    ))
    return batch_losses,[epoch_acc, epoch_recall, epoch_precision, epoch_F1]

def eval_one_epoch(model,device,loss_fun,metric_fun,eval_loader):
    """
        ********** 一个epoch模型验证 ************
        关于 model.eval()  model.train()  with torch.no_grad() with torch.set_grad_enabled(bool) 区别
        return: batch_losses 每个batch均值loss列表,batch_scores 每个batch的 acc,recall,precision,F1
    """
    print('Evaling ... ')
    model.eval() # 开启与dropout、BN层,它不会阻止梯度的计算,只不过回传参数,因此,eval 模式使用 with torch.no_grad() 还是很有必要的,加快计算速度。
    model.to(device)
    batch_losses = []
    batch_targets = []
    batch_predicts = []
    with torch.no_grad():
        for idx, (input_x, target) in enumerate(eval_loader):
            input_x, target = input_x.to(device), target.to(device)
            output = model(input_x)  # 前向传播
            loss = loss_fun(output, target, alpha=1.0, gamma=2.0)
            batch_losses.append(loss.item())
            # 计算score
            pre = torch.argmax(output, dim=1)
            pre = pre.cpu().numpy().reshape(-1).tolist()
            target = target.cpu().numpy().reshape(-1).tolist()
            (acc, recall, precision, F1), con_matrix = metric_fun(target=target, predict=pre)
            batch_targets.extend(target)
            batch_predicts.extend(pre)
        # 计算一个epoch的score
        (epoch_acc, epoch_recall, epoch_precision, epoch_F1), con_matrix = metric_fun(target=batch_targets, predict=batch_predicts)
        print(
            "Epoch Info :\tLoss:{:.8f}\tScores: Scores: <\tacc:{:.3f}%\t "\
            "macro_recall:{:.3f}%\t macro_precision:{:.3f}%\t macro_F1:{:.3f}%\t>".format(
                np.mean(batch_losses), 100. * epoch_acc, 100. * epoch_recall,
                                       100. * epoch_precision, 100. * epoch_F1
            ))
    return batch_losses,[epoch_acc, epoch_recall, epoch_precision, epoch_F1]

def train(model,device,optimizer,scheduler_fun,loss_fun,epochs,metric_fun,info_interval,checkpoint,train_loader,eval_loader):
    """
        ********** 模型训练 ************
        return:
            ① train_losses,eval_losses: 2D list ,(epoch,batch_num)
            ② train_scores,eval_scores: 2D list,(epoch,4)acc,recall,precision,F1
    """

    # 判断加载已保留的最优的模型参数【支持断点续传】
    best_scores = [-0.000001,-0.000001,-0.000001,-0.000001] # 定义初始的acc,recall,precision,F1的值
    history_epoch,best_epoch = 0,0   # 定义历史训练模型epoch次数初始值、最优模型的epoch初始值
    best_params = copy.deepcopy(model.state_dict())  # 获取模型的最佳参数,OrderDict属于链表,对其更该引用的变量也会变动,因此这里要用到深拷贝
    best_optimizer = copy.deepcopy(optimizer.state_dict())
    LRs = [i['lr'] for i in optimizer.param_groups]
    if os.path.exists(checkpoint):
        """
            为了保证 gpu/cpu 训练的模型参数可以相互加载,这里在load时使用 map_location=lambda storage, loc: storage 来控制,详情请看文章:
            https://blog.csdn.net/nospeakmoreact/article/details/89634039?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.withoutpai&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.withoutpai
        """
        if torch.cuda.is_available():
            ck_dict = torch.load(checkpoint, map_location=lambda storage, loc: storage.cuda())  # 使用 gpu 读取 模型参数
        else:
            ck_dict = torch.load(checkpoint, map_location=lambda storage, loc: storage)  # 使用 cpu 读取模型参数
        best_scores = ck_dict['best_score']
        history_epoch,best_epoch = ck_dict['epochs'],ck_dict['best_epochs']
        model.load_state_dict(ck_dict['best_params'])
        # optimizer.load_state_dict(ck_dict['optimizer'])

        # if torch.cuda.is_available():
        #     """
        #         重载optimizer的参数时将所有的tensor都放到cuda上(optimizer保存时默认放在cpu上了),详情见:
        #         https://blog.csdn.net/weixin_41848012/article/details/105675735
        #     """
        #     for state in optimizer.state.values():
        #         for k, v in state.items():



        #             if torch.is_tensor(v):
        #                 state[k] = v.cuda()

        best_params = copy.deepcopy(model.state_dict())  # 获取模型的最佳参数,OrderDict属于链表,对其更该引用的变量也会变动,因此这里要用到深拷贝
        # best_optimizer = copy.deepcopy(optimizer.state_dict())
        LRs = [i['lr'] for i in optimizer.param_groups]

        print('From "{}" load history model params:\n\tTrained Epochs:{}\n\t'\
              'Best Model Epoch:{}\n\t各层学习率 LRs 为:{}\n\tBest Score:<\tacc:{:.3f}%\t'\
              ' macro_recall:{:.3f}%\t macro_precision:{:.3f}%\t macro_F1:{:.3f}%\t>\n'.format(
            checkpoint, history_epoch,best_epoch,LRs
            , 100. * best_scores[0],100. * best_scores[1]
            ,100. * best_scores[2],100. * best_scores[3]))
        # print(best_params)
        # print(best_optimizer)

    # Train
    train_losses =[]
    eval_losses = []
    train_scores = []
    eval_scores = []
    for epoch in range(1,epochs + 1):
        # 获得本次训练的 lr 学习率
        scheduler_fun.step(history_epoch + epoch)  # 这里需要使用历史的epoch,为了是LR变化符合 Warmup + cosine
        LRs = [i['lr'] for i in optimizer.param_groups]
        # train & eval
        train_batch_loss,train_score = train_one_epoch(model=model,
                                                                device=device,
                                                                optimizer=optimizer,
                                                                loss_fun=loss_fun,
                                                                metric_fun=metric_fun,
                                                                train_loader=train_loader,
                                                                current_epoch=history_epoch+epoch,
                                                                info_interval=info_interval)
        print()
        eval_batch_loss,eval_score = eval_one_epoch(model=model,
                                                             device=device,
                                                             loss_fun=loss_fun,
                                                             metric_fun=metric_fun,
                                                             eval_loader=eval_loader)
        train_losses.append(train_batch_loss)
        eval_losses.append(eval_batch_loss)
        train_scores.append(train_score)
        eval_scores.append(eval_score)

        # 保存模型[当验证集的 F1 值 大于最优F1时,模型进行保存
        if best_scores[3] < eval_score[3]:
            print('历史模型分值:{:.3f}%,更新分值{:.3f}%,优化器学习率:{},模型参数更新保存\n'.format(100.*best_scores[3],100.*eval_score[3],LRs))
            best_scores = eval_score
            best_params = copy.deepcopy(model.state_dict())
            best_optimizer = copy.deepcopy(optimizer.state_dict())
            best_epoch = history_epoch + epoch
        else:
            print("模型最优的epcoh为:{},模型验证集最高分值:{:.3f}%, model 效果未提升\n".format(best_epoch,100.* best_scores[3]))
        ck_dict = {
            "best_score":best_scores,
            "best_params":best_params,
            "optimizer":best_optimizer,
            'epochs':history_epoch + epoch,
            'best_epochs':best_epoch
        }
        torch.save(ck_dict,checkpoint)

    # 训练结束,将模型赋予最优的参数
    model.load_state_dict(best_params)
    return model,train_losses,eval_losses,train_scores,eval_scores

    3.2.8、截图手写数字 predict 预测

def img_preprocess(image_path):
    """
        ********** 将图片使用类似transforms转化tensor ************
        image_path 是图片路径的列表
    """
    imgs = []
    for path in image_path:
        img = Image.open(path)
        img = img.convert("RGB")

        if img.size[0] > img.size[1]:
            img.thumbnail((10000, 72))
        else:
            img.thumbnail((72, 10000))
        # Crop操作
        left_margin = (img.width - 64) / 2
        right_margin = left_margin + 64
        bottom_margin = (img.height - 64) / 2
        top_margin = bottom_margin + 64
        img = img.crop((left_margin, bottom_margin, right_margin, top_margin))

        # 归一化、正则化
        img = np.array(img) / 255
        mean = np.array([0.485, 0.456, 0.406])
        std = np.array([0.229, 0.224, 0.225])
        img = (img - mean) / std
        # 通道转化 [ H,W,C] ---> [C,H,W]
        img = img.transpose([2, 0, 1])
        imgs.append(img)

    return imgs
    
# 预测
def predict(model,device,checkpoint,img_tensor):
    """
        ********** 模型预测 ************
    """
    print('Predicting .....')
    if os.path.exists(checkpoint):
        if device == 'cuda':
            ck_dict = torch.load(checkpoint,map_location=lambda storage, loc: storage.cuda())    # 使用 gpu 读取 模型参数
        else:
            ck_dict = torch.load(checkpoint,map_location=lambda storage, loc: storage)           # 使用 cpu 读取模型参数
        best_acc = ck_dict['best_score']
        current_epoch = ck_dict['epochs']
        model.load_state_dict(ck_dict['best_params'])
        print('From "{}" load exist model params\tTrained Epochs:{}\tBest Score:{:.3f}%.....'.format(checkpoint,current_epoch,100.*best_acc))
    else:
        print('checkpoint文件不存在!')
        exit()
    model.to(device)
    model.eval()

    # 预测
    with torch.no_grad():
        img_tensor = img_tensor.float().to(device)
        log_output = model(img_tensor)
        pre_tensor = torch.argmax(log_output,dim=1)
    # CUDA 不能转化为numpy,需要使用 .cpu() 转化为 cpu模式
    pre_array = pre_tensor.cpu().numpy()
    pt = torch.max(torch.exp(log_output),dim=1)[0].cpu().numpy()
    print("预测结果为:{},置信度为:{}".format(pre_array,pt))
    
    return pre_array,pt

  3.2、代码

from __future__ import print_function, division, absolute_import, with_statement
import os
import copy
import torch
from torch.cuda import check_error
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from collections import OrderedDict
from torchvision import datasets, models, transforms
from torch.utils.data import DataLoader, Dataset
from torch import optim
from sklearn.metrics import confusion_matrix,accuracy_score,recall_score,precision_score,f1_score

# feature_extract = False 将会 fintune 整个网络,feature_extract == True, 冻结原始网络参数,只训练自定义更改网络层的参数
feature_extract = True
num_classes = 10
batch_size = 64  # 训练集所使用的的 batch_size
use_cuda = torch.cuda.is_available()
device = 'cuda' if use_cuda else 'cpu'
epoch = 500
info_interval = 40  # 多少个 batch 打印一次 log
checkpoint_dir = './models'
# 训练: method = 'train',预测:   method = 'predict'
method = 'train'
torch.manual_seed(10)


def checkdir(dir):
    if not os.path.exists(dir):
        os.makedirs(dir)

# MNIST 灰度图 扩展至 3通道,用于 torchvision.transforms 中
def gray2color(x):
    """
        每次传入的 x 是 仅仅一张图片的 PIL对象
        将MINIST灰度图x repeat为三通道,(H,W) ----> (H,W,3)
    """
    x = np.array(x)  # PIL 对象转化为 numpy
    x = np.repeat(np.expand_dims(x, axis=-1), repeats=3, axis=-1)
    x = Image.fromarray(x.astype('uint8')).convert("RGB")  # numpy对象必须转化为 PIL 格式的图片,后续才能继续 transforms 操作
    return x

def set_parameter_requires_grad(model, feature_extract):
    """
        ********** 参数冻结函数 ************
        怎么样去冻结前n层,或者自定义组合网络加载预训练模型的参数,冻结以后的模型的 优化器 optimizer 怎么去除 requires_grad==False的参数,详情请看:
        https://zhuanlan.zhihu.com/p/34147880
        怎么样任意修改删除预训练模型的某一层或某些层,具体请看:
        https://blog.csdn.net/lavinia_chen007/article/details/114833608

    """
    if feature_extract:
        for name, child in list(model.named_children())[:5]:  # 将反应的层数进行冻结
            for n, param in child.named_parameters():
                param.requires_grad = False

def initiallize_model(model_name, num_classes, feature_extract):
    """
        ********** 初始化预训练模型的参数 ************
        可初始化的模型为:resnet、alexnet、vgg、squeezenet ...
        除了 inceptionv3 需要模型的输入要求图片为3通道 299*299,其余模型输入图片要求为3通道 244*244 
    """
    model = None
    input_size = 0
    use_pretrained = feature_extract

    if model_name == 'resnet':
        if use_pretrained:
            model = models.resnet50(pretrained=False)
            model.load_state_dict(torch.load("models/resnet50-19c8e357.pth"))
        else:
            model = models.resnet50(pretrained=False)

        # 重新定义可训练的层,重新定义的层默认参数训练,这里只定义最后一层,即 fc层
        input_features = model.fc.in_features  # 这里获取最后一层输入的特征数
        model.fc = nn.Linear(input_features, num_classes)

        input_size = 224  # 官方resnet网络 图片的输入尺寸,所以需要将图片预处理为 224 * 224 三通

    else:
        print('Invaild model name,exiting ....')
        exit()

    return model, input_size

def view_will_trained_params(model,model_name):
    """
        ********** 查看模型哪些层的参数参与训练,哪些层的参数被固定了 ************
    """
    train_params = []
    for name,param in model.named_parameters():
        if param.requires_grad == True:
            train_params.append((name,param.shape))
    print("\n{} 模型将要参与训练的层为:\n".format(model_name),train_params,end='\n\n\n')

def focal_loss(output,target,alpha=1.0 , gamma=2.0,*args,**kwargs):
    """
        ********** 给定模型前向传播的输出[batch,class]与真实值target[class,],计算loss误差 ************
        1. 仅仅在训练的时候使用 focal_loss ,验证时不使用 focal_loss
        2. 默认情况下不进行聚合
    """
    ce_loss = F.cross_entropy(input = output,target = target, reduction="none")  #这里必须使用 none 模式, ce_loss dim: [B,]
    pt = torch.exp(-ce_loss)                                                   # pt      dim: [B,]
    # 构建 focal_loss
    focalloss = (alpha * (1.0 - pt)**gamma * ce_loss).mean()
    return focalloss

def cross_entropy(output,target,*args,**kwargs):
    """
        普通的交叉熵损失函数,默认情况下不进行聚合
    """
    ce_loss = F.cross_entropy(input = output,target = target, reduction="mean")  # ce_loss 是一个均值
    return ce_loss

from copy import deepcopy
from numpy import ceil
class PolynomialLRDecay():
    """
    ******************* pytorch自定义多项式衰减 分层训练的学习率,并且可以控制是否热启动学习率 *************************8
    具体可看文章:https://blog.csdn.net/akadiao/article/details/79560731
    其他pytorch的学习衰减可以看:https://blog.csdn.net/weixin_43183872/article/details/108061092?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-16.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-16.control

    Args:
        optimizer (Optimizer): pytotch 优化器
        max_decay_steps: 从基础学习降低至最低学习率的最大迭代次数
        end_learning_rate: 所有层学习率可以降低到的最低值,必须传入list
        power: The power of the polynomial.
        cycle: 学习降低最低是否热启动,具体看上诉文章,默认开启

    """

    def __init__(self, optimizer, max_decay_steps, end_learning_rate=None, power=0.5, cycle=True):
        if max_decay_steps <= 1.:
            raise ValueError('max_decay_steps should be greater than 1.')
        self.optimizer = optimizer
        self.base_lrs = [group['lr'] for group in optimizer.param_groups]  # 基础学习率,list
        self.max_decay_steps = max_decay_steps
        self.max_decay_steps_copy = max_decay_steps
        self.end_learning_rate = end_learning_rate  # 允许最低的分层学习率,list
        self.power = power
        self.last_step = 0  # 上一次批次的索引,用与断点续传
        self.cycle = cycle  # 控制后期学习率是否提升

    def get_lr(self):
        if self.last_step > self.max_decay_steps:
            return deepcopy(self.end_learning_rate[:len(self.base_lrs)])

        return [(base_lr - self.end_learning_rate[idx]) *
                ((1 - self.last_step / self.max_decay_steps) ** (self.power)) +
                self.end_learning_rate[idx] for idx, base_lr in enumerate(self.base_lrs)]

    def step(self, step=None):
        if step is None:
            step = self.last_step + 1
        self.last_step = step if step != 0 else 1

        # 如果后期重新升高学习率
        if self.cycle == True:
            # 根据倍数更新学习率衰减的最大epoch 的值
            self.max_decay_steps = self.max_decay_steps_copy * ceil(self.last_step / self.max_decay_steps_copy)
            if self.last_step <= self.max_decay_steps:
                decay_lrs = self.get_lr()

            else:
                decay_lrs = self.get_lr()

        # 后期不进行学习率提升,超过学习率衰减的最大epoch后将使用最低的学习率进行训练
        else:
            decay_lrs = self.get_lr()
        for param_group, lr in zip(self.optimizer.param_groups, decay_lrs):
            param_group['lr'] = lr
            
def get_score(target,predict):
    con_matrix = confusion_matrix(y_true=target,y_pred=predict)
    # 计算acc
    acc = accuracy_score(y_true=target,y_pred=predict)
    # 计算 macro recall
    recall = recall_score(y_true=target,y_pred=predict,average='macro')
    # 计算 macro precision
    precision = precision_score(y_true=target,y_pred=predict,average='macro')
    # 计算 macro F1
    F1 = f1_score(y_true=target,y_pred=predict,average='macro')
    return (acc,recall,precision,F1),con_matrix

# 模型搭建
class BaseResnet(nn.Module):
    """
        ********** 利用resnet预训练模型搭建模型 ************
    """

    def __init__(self, init_model):
        super(BaseResnet, self).__init__()
        base_model = init_model

        # TODO 这里我利用 .named_children() 将预训练模型前 5层的参数 进行冻结
        set_parameter_requires_grad(model=base_model, feature_extract=feature_extract)

        self.model = nn.Sequential(OrderedDict([('body', base_model),
                                                ('logsoftmax', nn.LogSoftmax(dim=1))]))  # 将模型的输出值做 logsoftmax 函数并进行分组

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

# 训练
def train_one_epoch(model,device,optimizer,loss_fun,metric_fun,train_loader,current_epoch,info_interval:int=None):
    """
        ********** 一个epoch模型训练 ************
        关于 model.eval()  model.train()  with torch.no_grad() with torch.set_grad_enabled(bool) 区别
        return:
            ① batch_losses:每个batch均值loss列表
            ② 整个epoch 的 acc,recall,precision,F1
    """
    print('Training ... ')
    model.train()
    model.to(device)
    LRs = [i['lr'] for i in optimizer.param_groups] # 获取当前epoch 优化器 optimizer 学习率组
    batch_losses = []
    batch_targets = []
    batch_predicts = []
    for idx, (input_x, target) in enumerate(train_loader):
        input_x, target = input_x.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(input_x)  # 前向传播
        loss = loss_fun(output, target, alpha=1.0, gamma=2.0)
        loss.backward()  # 反向传播计算梯度
        optimizer.step()  # 更新
        batch_losses.append(loss.item())
        # 计算score
        pre = torch.argmax(output, dim=1)
        pre = pre.cpu().numpy().reshape(-1).tolist()
        target = target.cpu().numpy().reshape(-1).tolist()
        (acc,recall,precision,F1),con_matrix = metric_fun(target=target,predict=pre)
        batch_targets.extend(target)
        batch_predicts.extend(pre)

        if info_interval is not None:
            if idx % info_interval == 0:
                print("Epoch:{}\t[{}\{}\t\t{:.2f}%]\tLoss:{:.8f}\tScores: < acc:{:.3f}%\t"\
                      "macro_recall:{:.3f}%\tmacro_precision:{:.3f}%\tmacro_F1:{:.3f}%\t >\t\tBatch input_x shape:{}".format(
                    current_epoch, idx * len(input_x),
                    len(train_loader.dataset), 100. * (idx  / len(train_loader)),loss.item(),
                    100. * acc,100. * recall,100. * precision,100. * F1,input_x.shape
                ))
    # 计算一个epoch的score
    (epoch_acc, epoch_recall, epoch_precision, epoch_F1), con_matrix = metric_fun(target=batch_targets, predict=batch_predicts)
    print("Epoch Info :\tLoss:{:.8f}\tScores: <\tacc:{:.3f}%\t "\
          "macro_recall:{:.3f}%\t macro_precision:{:.3f}%\t macro_F1:{:.3f}%\t>\tLRs:{}".format(
        np.mean(batch_losses),100. * epoch_acc,100. * epoch_recall,100. * epoch_precision,100. * epoch_F1,LRs
    ))
    return batch_losses,[epoch_acc, epoch_recall, epoch_precision, epoch_F1]

def eval_one_epoch(model,device,loss_fun,metric_fun,eval_loader):
    """
        ********** 一个epoch模型验证 ************
        关于 model.eval()  model.train()  with torch.no_grad() with torch.set_grad_enabled(bool) 区别
        return: batch_losses 每个batch均值loss列表,batch_scores 每个batch的 acc,recall,precision,F1
    """
    print('Evaling ... ')
    model.eval() # 开启与dropout、BN层,它不会阻止梯度的计算,只不过回传参数,因此,eval 模式使用 with torch.no_grad() 还是很有必要的,加快计算速度。
    model.to(device)
    batch_losses = []
    batch_targets = []
    batch_predicts = []
    with torch.no_grad():
        for idx, (input_x, target) in enumerate(eval_loader):
            input_x, target = input_x.to(device), target.to(device)
            output = model(input_x)  # 前向传播
            loss = loss_fun(output, target, alpha=1.0, gamma=2.0)
            batch_losses.append(loss.item())
            # 计算score
            pre = torch.argmax(output, dim=1)
            pre = pre.cpu().numpy().reshape(-1).tolist()
            target = target.cpu().numpy().reshape(-1).tolist()
            (acc, recall, precision, F1), con_matrix = metric_fun(target=target, predict=pre)
            batch_targets.extend(target)
            batch_predicts.extend(pre)
        # 计算一个epoch的score
        (epoch_acc, epoch_recall, epoch_precision, epoch_F1), con_matrix = metric_fun(target=batch_targets, predict=batch_predicts)
        print(
            "Epoch Info :\tLoss:{:.8f}\tScores: Scores: <\tacc:{:.3f}%\t "\
            "macro_recall:{:.3f}%\t macro_precision:{:.3f}%\t macro_F1:{:.3f}%\t>".format(
                np.mean(batch_losses), 100. * epoch_acc, 100. * epoch_recall,
                                       100. * epoch_precision, 100. * epoch_F1
            ))
    return batch_losses,[epoch_acc, epoch_recall, epoch_precision, epoch_F1]

def train(model,device,optimizer,scheduler_fun,loss_fun,epochs,metric_fun,info_interval,checkpoint,train_loader,eval_loader):
    """
        ********** 模型训练 ************
        return:
            ① train_losses,eval_losses: 2D list ,(epoch,batch_num)
            ② train_scores,eval_scores: 2D list,(epoch,4)acc,recall,precision,F1
    """

    # 判断加载已保留的最优的模型参数【支持断点续传】
    best_scores = [-0.000001,-0.000001,-0.000001,-0.000001] # 定义初始的acc,recall,precision,F1的值
    history_epoch,best_epoch = 0,0   # 定义历史训练模型epoch次数初始值、最优模型的epoch初始值
    best_params = copy.deepcopy(model.state_dict())  # 获取模型的最佳参数,OrderDict属于链表,对其更该引用的变量也会变动,因此这里要用到深拷贝
    best_optimizer = copy.deepcopy(optimizer.state_dict())
    LRs = [i['lr'] for i in optimizer.param_groups]
    if os.path.exists(checkpoint):
        """
            为了保证 gpu/cpu 训练的模型参数可以相互加载,这里在load时使用 map_location=lambda storage, loc: storage 来控制,详情请看文章:
            https://blog.csdn.net/nospeakmoreact/article/details/89634039?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.withoutpai&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.withoutpai
        """
        if torch.cuda.is_available():
            ck_dict = torch.load(checkpoint, map_location=lambda storage, loc: storage.cuda())  # 使用 gpu 读取 模型参数
        else:
            ck_dict = torch.load(checkpoint, map_location=lambda storage, loc: storage)  # 使用 cpu 读取模型参数
        best_scores = ck_dict['best_score']
        history_epoch,best_epoch = ck_dict['epochs'],ck_dict['best_epochs']
        model.load_state_dict(ck_dict['best_params'])
        # optimizer.load_state_dict(ck_dict['optimizer'])

        # if torch.cuda.is_available():
        #     """
        #         重载optimizer的参数时将所有的tensor都放到cuda上(optimizer保存时默认放在cpu上了),详情见:
        #         https://blog.csdn.net/weixin_41848012/article/details/105675735
        #     """
        #     for state in optimizer.state.values():
        #         for k, v in state.items():



        #             if torch.is_tensor(v):
        #                 state[k] = v.cuda()

        best_params = copy.deepcopy(model.state_dict())  # 获取模型的最佳参数,OrderDict属于链表,对其更该引用的变量也会变动,因此这里要用到深拷贝
        # best_optimizer = copy.deepcopy(optimizer.state_dict())
        LRs = [i['lr'] for i in optimizer.param_groups]

        print('From "{}" load history model params:\n\tTrained Epochs:{}\n\t'\
              'Best Model Epoch:{}\n\t各层学习率 LRs 为:{}\n\tBest Score:<\tacc:{:.3f}%\t'\
              ' macro_recall:{:.3f}%\t macro_precision:{:.3f}%\t macro_F1:{:.3f}%\t>\n'.format(
            checkpoint, history_epoch,best_epoch,LRs
            , 100. * best_scores[0],100. * best_scores[1]
            ,100. * best_scores[2],100. * best_scores[3]))
        # print(best_params)
        # print(best_optimizer)

    # Train
    train_losses =[]
    eval_losses = []
    train_scores = []
    eval_scores = []
    for epoch in range(1,epochs + 1):
        # 获得本次训练的 lr 学习率
        scheduler_fun.step(history_epoch + epoch)  # 这里需要使用历史的epoch,为了是LR变化符合 Warmup + cosine
        LRs = [i['lr'] for i in optimizer.param_groups]
        # train & eval
        train_batch_loss,train_score = train_one_epoch(model=model,
                                                                device=device,
                                                                optimizer=optimizer,
                                                                loss_fun=loss_fun,
                                                                metric_fun=metric_fun,
                                                                train_loader=train_loader,
                                                                current_epoch=history_epoch+epoch,
                                                                info_interval=info_interval)
        print()
        eval_batch_loss,eval_score = eval_one_epoch(model=model,
                                                             device=device,
                                                             loss_fun=loss_fun,
                                                             metric_fun=metric_fun,
                                                             eval_loader=eval_loader)
        train_losses.append(train_batch_loss)
        eval_losses.append(eval_batch_loss)
        train_scores.append(train_score)
        eval_scores.append(eval_score)

        # 保存模型[当验证集的 F1 值 大于最优F1时,模型进行保存
        if best_scores[3] < eval_score[3]:
            print('历史模型分值:{:.3f}%,更新分值{:.3f}%,优化器学习率:{},模型参数更新保存\n'.format(100.*best_scores[3],100.*eval_score[3],LRs))
            best_scores = eval_score
            best_params = copy.deepcopy(model.state_dict())
            best_optimizer = copy.deepcopy(optimizer.state_dict())
            best_epoch = history_epoch + epoch
        else:
            print("模型最优的epcoh为:{},模型验证集最高分值:{:.3f}%, model 效果未提升\n".format(best_epoch,100.* best_scores[3]))
        ck_dict = {
            "best_score":best_scores,
            "best_params":best_params,
            "optimizer":best_optimizer,
            'epochs':history_epoch + epoch,
            'best_epochs':best_epoch
        }
        torch.save(ck_dict,checkpoint)

    # 训练结束,将模型赋予最优的参数
    model.load_state_dict(best_params)
    return model,train_losses,eval_losses,train_scores,eval_scores


def img_preprocess(image_path):
    """
        ********** 将图片使用类似transforms转化tensor ************
        image_path 是图片路径的列表
    """
    imgs = []
    for path in image_path:
        img = Image.open(path)
        img = img.convert("RGB")

        if img.size[0] > img.size[1]:
            img.thumbnail((10000, 72))
        else:
            img.thumbnail((72, 10000))
        # Crop操作
        left_margin = (img.width - 64) / 2
        right_margin = left_margin + 64
        bottom_margin = (img.height - 64) / 2
        top_margin = bottom_margin + 64
        img = img.crop((left_margin, bottom_margin, right_margin, top_margin))

        # 归一化、正则化
        img = np.array(img) / 255
        mean = np.array([0.485, 0.456, 0.406])
        std = np.array([0.229, 0.224, 0.225])
        img = (img - mean) / std
        # 通道转化 [ H,W,C] ---> [C,H,W]
        img = img.transpose([2, 0, 1])
        imgs.append(img)

    return imgs


# 预测
def predict(model, device, checkpoint, img_tensor):
    """
        ********** 模型预测 ************
    """
    print('Predicting .....')
    if os.path.exists(checkpoint):
        if device == 'cuda':
            ck_dict = torch.load(checkpoint, map_location=lambda storage, loc: storage.cuda())  # 使用 gpu 读取 模型参数
        else:
            ck_dict = torch.load(checkpoint, map_location=lambda storage, loc: storage)  # 使用 cpu 读取模型参数
        best_acc = ck_dict['best_score']
        current_epoch = ck_dict['epochs']
        model.load_state_dict(ck_dict['best_params'])
        print('From "{}" load exist model params\tTrained Epochs:{}\tBest Score:{:.3f}%.....'.format(checkpoint,
                                                                                                     current_epoch,
                                                                                                     100. * best_acc))
    else:
        print('checkpoint文件不存在!')
        exit()
    model.to(device)
    model.eval()

    # 预测
    with torch.no_grad():
        img_tensor = img_tensor.float().to(device)
        log_output = model(img_tensor)
        pre_tensor = torch.argmax(log_output, dim=1)
    # CUDA 不能转化为numpy,需要使用 .cpu() 转化为 cpu模式
    pre_array = pre_tensor.cpu().numpy()
    pt = torch.max(torch.exp(log_output), dim=1)[0].cpu().numpy()
    print("预测结果为:{},置信度为:{}".format(pre_array, pt))

    return pre_array, pt


if __name__ == '__main__':
    # 初始化预训练模型,model_name = "resnet" "alexnet" "vgg" ....
    model_name = 'resnet'
    checkdir(checkpoint_dir)
    checkpoint = os.path.join(checkpoint_dir, '{}.pt'.format(model_name))
    model, input_size = initiallize_model(model_name=model_name, num_classes=num_classes,
                                          feature_extract=feature_extract)
    # 初始化基于预训练的迁移学习的模型,该模型具有以下特点:
    # 1. 模型除了重写层参与训练,其他层的参数在feature_extract=True时将requires_grad=False,因此不参与训练
    model = BaseResnet(init_model=model)
    # print(model.state_dict()) # 打印模型的参数字典,Orderdict 类型

    if method == "train":
        # 2. 加载 MINIST 数据
        data = datasets.MNIST('./data', train=True, download=True)
        print("原始的MNIST图片的维度:", data.data.shape)  # datasets.MNIST 最开始的数据为(B,H,W) 没有通道这一维度

        """
            transforms 过程每次传入一张图片,相当于对batchsize的维度做了一次循环
            注意:transforms 内部默认用的是 PIL 的Image对象进行转换的,因此 需要将numpy或者cv2的BGR 格式转化为 PIL RGB 格式在进行接下的操作

        """
        train_transforms = transforms.Compose([
            gray2color,  # 这里使用自定的函数,将 单通道的图片重复为三通道的图片
            transforms.Resize(72),
            transforms.CenterCrop(64),
            transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),
            transforms.RandomGrayscale(p=0.025),  # 0.025的概率进行灰度图化,但是仍然是三通道
            transforms.ToTensor(),  # 将(b,h,w,c)  --- (b,c,h,w)后 每一个像素除以255归一化
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ]
        )
        eval_transforms = transforms.Compose([
            gray2color,  # 这里使用自定的函数,将 单通道的图片重复为三通道的图片
            transforms.Resize(72),
            transforms.CenterCrop(64),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]
        )

        data = datasets.MNIST('./data', train=True, transform=train_transforms, download=True)
        print("trasformes转化后一张图片的维度:", next(iter(data))[0].shape)  # 将单通道 MINIST 扩展到三通道后,进行一系列操作后

        # 创建 train_loader 与 eval_loader
        kwargs = {'num_workers': 0, 'pin_memory': True} if use_cuda else {}
        train_loader = DataLoader(datasets.MNIST('./data', train=True, transform=train_transforms, download=True),
                                  batch_size=batch_size, shuffle=True, **kwargs)

        eval_loader = DataLoader(datasets.MNIST('./data', train=False, download=True, transform=eval_transforms),
                                 batch_size=256, shuffle=False, **kwargs)
        print("dataloader训练集一个batch中图片维度:", next(iter(train_loader))[0].shape)
        print("dataloader验证集一个batch中图片维度:", next(iter(eval_loader))[0].shape)

        # 3. 查看模型 允许参与训练的层有哪些,即 requires_grad = True 的层
        view_will_trained_params(model, model_name=model_name)

        # 优化器【********* 分层定制学习率 ***********】
        """
            ***************** 屏蔽部分不需要梯度的参数,制定分层学习率 *******************
            filter(lambda p:p.requires_grad,model.parameters())
            # 因为我们冻结了一部分参数,所以我需要剔除模型中不需要计算grad的参数
            # 使用分层学习率【 根据 参数的内存id 进行筛选 】
            详情看文章:https://blog.csdn.net/xuyunyunaixuexi/article/details/100799097?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-8.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-8.control
        """
        # 特定层的参数列表
        special_layers = nn.ModuleList([model.model.body.fc, model.model.logsoftmax])
        # 获取特等层的参数列表的内存id列表
        special_layers_ids = list(map(lambda x: id(x), special_layers.parameters()))
        # 基础层的参数列表
        basic_params = filter(lambda x: id(x) not in special_layers_ids, model.parameters())
        # 这句话的意思是 basic_params 的 lr 为1e-4,其他默认的参数的 lr 为1e-3
        optimizer = optim.SGD([{'params': filter(lambda p: p.requires_grad, special_layers.parameters())},
                               {'params': filter(lambda p: p.requires_grad, basic_params), 'lr': 1e-3}],
                              lr=1e-2, momentum=0.9)

        # 4. 学习率调整器
        scheduler = PolynomialLRDecay(optimizer=optimizer, max_decay_steps=80, end_learning_rate=[1e-4, 1e-5],
                                      power=0.5, cycle=True)
        """
            optim.lr_scheduler 学习率调整函数,为了保证第一个初始参数被使用,scheduler需要放在 optimizer.step()之后,

            scheduler = ...
            for epoch in range(100):
                train(...)
                validate(...)
                scheduler.step()

            具体看:https://blog.csdn.net/qyhaill/article/details/103043637
        """

        # 5. 训练
        train(model=model, device=device, optimizer=optimizer, scheduler_fun=scheduler, loss_fun=focacl_loss,
              train_loader=train_loader,
              eval_loader=eval_loader, epoch=epoch, checkpoint=checkpoint, info_interval=info_interval)

    elif method == 'predict':
        # 预测

        iamges = [os.path.join('./data', i) for i in
                  filter(lambda x: x.endswith('.png') or x.endswith('.jpg'), os.listdir('./data'))]
        print(iamges)
        img = img_preprocess(image_path=iamges)
        img_tensor = torch.tensor(img)
        predict(model=model, device=device, checkpoint=checkpoint, img_tensor=img_tensor)

    else:
        print('method 输入错误!')
        exit()

四、思考的问题

  • torchvision 中 models 经典模型的 pre_trained == true首次运行会下载预训练的模型参数很耗时,怎样离线下载预训练的模型参数并加载
  • 怎样冻结预训练模型的参数?
  • 怎样冻结预训练模型一部分参数?比如前8层的参数
使用:model.parameters()、model.named_parameters()、model.named_children()
  • 怎样删除预训练模型的一部分网络结构?

model.named_children()[:-3]  后3层的模型不要
  • 怎样不同学习率分层训练模型?
  • 怎样仅仅训练部分模型的参数?比如仅仅训练FC层的参数
  • 为什么将模型的一部分参数 requires_grad == False, 这些参数最终还是参与训练了
  • 怎样gpu训练的模型参数用cpu进行load?
  • load_state_dict 中 strict = False 是怎么用的?什么场景可以用到它?
  • resnet50一共下采样图片缩小了多少倍? 一定要求输入的图片是 224吗?如果图片不是224怎么办呢?

        32倍,由于新版nn.AdaptiveAvgPool2d((1, 1))的作用不会强制要求图片必须是224的大小,但建议图片大小要大于32,因为下采样会缩小32倍

        文章:pytorch笔记:04)resnet网络&解决输入图像大小问题

  • 预训练模型学习率选择的策略是什么?

        预训练模型fintune学习率选择较小,通常比正常训练的学习率小10倍甚至100倍,而迁移学习自写的网络的结构学习率通常较大,所以采取的策略是进行分层定制学习率,预训练模型越靠近输出层,学习率越大

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 PyTorch 进行监督学习,需要遵循以下步骤: 1. 加载数据集:首先需要加载数据集。可以使用 PyTorch 的 Dataset 和 DataLoader 类来加载数据集。可以使用 torchvision 库中的 ImageFolder 类来加载图像数据集。 2. 定义模型:在 PyTorch 中定义模型非常简单。可以使用现有的模型,如 ResNet50,也可以自己定义模型。 3. 定义损失函数:在监督学习中,需要定义损失函数来衡量模型的性能。可以使用 PyTorch 中提供的各种损失函数,如交叉熵损失函数。 4. 定义优化器:在训练模型时,需要定义优化器来更新模型参数。可以使用 PyTorch 中提供的各种优化器,如 Adam 优化器。 5. 训练模型:定义好模型、损失函数和优化器后,就可以开始训练模型了。使用 PyTorch 训练模型非常简单,只需要编写一个循环,每次迭代计算模型输出、损失和梯度,然后使用优化器更新模型参数。 以下是一个使用 ResNet50 进行监督学习的示例代码: ```python import torch import torch.nn as nn import torch.optim as optim import torchvision.datasets as datasets import torchvision.transforms as transforms import torchvision.models as models # 加载数据集 train_dataset = datasets.ImageFolder('/path/to/train', transform=transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor() ])) train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=32, shuffle=True) # 定义模型 model = models.resnet50(pretrained=True) model.fc = nn.Linear(2048, 10) # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # 训练模型 for epoch in range(10): for inputs, labels in train_loader: optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() print(f'Epoch {epoch+1}, Loss: {loss.item():.4f}') ``` 在上面的代码中,我们首先加载了数据集,并定义了 ResNet50 模型。然后,我们定义了交叉熵损失函数和 Adam 优化器。最后,我们使用一个循环来训练模型,每次迭代计算模型输出、损失和梯度,然后使用优化器更新模型参数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值