pytorch实现迁移训练-resnet18训练花朵识别模型【深度学习】

pytorch实现迁移训练-resnet18训练花朵识别模型【深度学习】_第1张图片

完整训练花朵数据集思路!

    • 一、训练模型三大步骤:
      • 1、数据预处理部分
      • 2、网络模块设置
      • 3、网络模型的保存与测试
    • 二、模型训练具体步骤
      • 1、导入所需要的模块包
      • 2、设置数据集文件路径
      • 3、数据预处理模块
      • 4、获取数据集
        • (1)`.classes`
        • (2) `datasets.ImageFolder`
      • 5、读取标签对应的实际名字
      • 6、加载models中提供的模型,并且直接用训练的好权重当做初始化参数
      • 7、把模型输出层改成自己的
      • 8、设置哪些层需要进行训练(目前只设置全连接进行训练)
        • `parameters`与`named_parameters`
      • 9、优化器设置
      • 10、模型训练模块(重点)
        • 只训练全连接层结果:
        • (1)model.state_dict()
        • (2)copy.deepcopy()
        • (2)_,preds=torch.max(outputs,1)
        • (4)loss.item() * inputs.size(0)
      • 11、继续训练其他网络层
        • 训练所有网络层结果:
      • 12、加载训练好的模型(不在完整训练过程里面)
      • 13、测试数据预处理
      • 14、获取概率最大的结果
      • 15、图片处理模块
        • (1)tensor.to('cpu').clone().detach()
        • (2)image.clip(0, 1)
      • 16、绘制图片
      • 识别结果

一、训练模型三大步骤:

1、数据预处理部分

数据增强:使用torchvision中的transform模块自带的图像处理功能

  • Resize(128x128):固定设置图片大小为128
  • RandomRotation(45):随机旋转度数-45~45之间
  • CenterCrop(64):从中心开始裁剪,从原图裁剪64x64
  • RandomHorizontalFlip(p=0.5):随机水平翻转,设置概率为0.5
  • RandomVerticalFlip(p=0.5):设置随机垂直翻转概率为0.5
  • ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1):参数1为亮度,参数2为对比度,参数3为饱和度,参数4为色相
  • RandomGrayscale(p=0.025):概率转换成灰度图
  • Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]):前面是均值,后面是标准差,分别对应的是R G B三个通道

数据标准化——transforms.Normalize

功能:逐channel的对图像进行标准化(均值变为0,标准差变为1),可以加快模型的收敛

  • output=(input-mean)/std
  • mean:各通道的均值
  • std:各通道的标准差
  • inplace:是否原地操作

2、网络模块设置

  • 加载预训练模型,torchvision中有很多经典网络架构,调用起来十分方便,并且可以用人家训练好的权重参数来继续训练,也就是所谓的迁移学习
  • 需要注意的是别人训练好的任务跟咱们的可不是完全一样,需要把最后的输出层改一改,一般也就是最后的全连接层,改成咱们自己的任务所需要的输出
  • 训练时可以全部重头训练,也可以只训练最后咱们任务的层,因为前几层都是做特征提取的,本质任务目标是一致的

3、网络模型的保存与测试

模型保存的时候可以带有选择性,例如在验证集中如果当前效果好则保存

二、模型训练具体步骤

1、导入所需要的模块包

import os
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import torch
from torch import nn
import torch.optim as optim
import torchvision
from torchvision import transforms, models, datasets
import imageio
'''用于读取、写入和处理各种图像和视频格式。它提供了一个简单而灵活的接口,
可以轻松地读取和写入各种图像和视频格式,包括JPEG、PNG、GIF、BMP、TIFF、AVI、MPEG等常见格式,
还支持许多科学和医学图像格式'''
import time
import warnings
warnings.filterwarnings("ignore")
#函数将警告信息忽略掉,这样在程序运行过程中就不会显示任何警告信息了。
import random
import copy
import json
from PIL import Image

2、设置数据集文件路径

data_dir='./flower_data/'
train_dir='/train'
valid_dir='/valid'

3、数据预处理模块

data_transforms = {
    'train': 
    #compose里面内容按照顺序执行
        transforms.Compose([
        transforms.Resize([96, 96]), #用cpu跑的时候需要将图像大小设置小一点;
        transforms.RandomRotation(45),#随机旋转,-45到45度之间随机选
        transforms.CenterCrop(64),#从中心开始裁剪,从原图随机裁剪64x64 这个应该是最小了 尽量不要低于64
        transforms.RandomHorizontalFlip(p=0.5),#随机水平翻转 选择一个概率概率
        transforms.RandomVerticalFlip(p=0.5),#随机垂直翻转
        transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),#参数1为亮度,参数2为对比度,参数3为饱和度,参数4为色相
        transforms.RandomGrayscale(p=0.025),#概率转换成灰度率,3通道就是R=G=B
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])#前面是均值,后面是标准差,分别对应的是R G B三个通道
    ]),
    'valid': 
        transforms.Compose([
        transforms.Resize([64, 64]),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

我们预处理的图片并不是将处理之后的图片保存到本地,而且通过dataloaders处理之后,直接传入到模型训练当中

4、获取数据集

batch_size = 128
image_datasets={x: datasets.ImageFolder(os.path.join(data_dir,x),data_transforms[x]) for x in ['train','valid']}##获取训练集和测试集数据,并进行预处理
dataloaders={x: torch.utils.data.DataLoader(image_datasets[x],batch_size=batch_size,shuffle=True) for in ['train','valid']}
#用dataloader设置验证集和训练集
dataset_sizes={x: len(image_datasets[x]) for in ['train','valid']} ##获取训练集和验证集总数
class_names=image_datasets['train'].classes # 获取子文件夹名,只有使用datasets.ImageFolder加载数据集才能使用classes

(1).classes

image_datasets['train'].classes是在使用PyTorch中的torchvision.datasets.ImageFolder类加载图像数据集时,用于获取所有类别名称的属性。具体来说,torchvision.datasets.ImageFolder类会将数据集中每个子目录视为一个类别,并将该目录下所有图像文件视为该类别的样本。在加载数据集时,我们需要将数据集的根目录和各个类别的子目录传递给ImageFolder类,然后可以使用ImageFolder类的实例对象获取所有类别名称的列表。这个列表可以用于后续的模型训练和预测,方便我们对不同类别的样本进行分类。

案例

import torchvision.datasets as datasets
# 加载数据集
train_dataset = datasets.ImageFolder(root="./data/train")
# 获取所有类别名称
classes = train_dataset.classes
print(classes)

在上述示例中,我们首先使用ImageFolder类加载了训练集数据,然后使用classes属性获取了所有类别名称的列表,并打印出来。注意,如果数据集的子目录名称不是按照字母表顺序排列的,那么classes属性返回的列表也不会按照字母表顺序排列,而是按照子目录的创建顺序排列。如果需要按照字母表顺序排列,可以使用Python的sorted()函数对classes列表进行排序。

(2) datasets.ImageFolder

datasets.ImageFolder是PyTorch中一个用于加载图像数据集的类。它可以帮助我们快速地加载和预处理图像数据集,并将其转换为PyTorch中的Dataset对象,方便我们在训练神经网络时进行批量读取和处理。datasets.ImageFolder的基本用法非常简单,只需要指定数据集的根目录和各个类别的子目录即可自动扫描所有图像文件并将其转换为Dataset对象。具体来说,datasets.ImageFolder会将每个子目录视为一个类别,并将该目录下所有图像文件视为该类别的样本。在转换为Dataset对象后,我们可以使用DataLoader类进行批量读取和处理。

案例:

import torch
import torchvision.datasets as datasets
import torchvision.transforms as transforms
# 定义数据预处理
transform = transforms.Compose([
    transforms.Resize((224, 224)),  # 调整图像大小为224x224
    transforms.ToTensor(),  # 将图像转换为Tensor格式
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 像素值归一化
])
# 加载数据集
train_dataset = datasets.ImageFolder(root="./data/train", transform=transform)
val_dataset = datasets.ImageFolder(root="./data/val", transform=transform)
# 创建数据加载器
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=32, shuffle=False)

在上述示例中,我们首先定义了数据预处理的变换,包括调整图像大小、转换为Tensor格式、像素值归一化等。然后使用datasets.ImageFolder类分别加载了训练集和验证集,其中root参数指定了数据集的根目录,transform参数指定了数据预处理的变换。最后使用DataLoader类创建了训练集和验证集的数据加载器,可以方便地进行批量读取和处理。

5、读取标签对应的实际名字

with open('cat_to_name.json','r') as f:
  cat_to_name=json.load(f)

json.load(f)是Python中用于从文件中加载JSON数据的函数。JSON是一种轻量级的数据交换格式,常用于Web应用程序中的数据传输。在Python中,我们可以使用json标准库中的load()函数将JSON数据加载为Python中的数据类型,如字典、列表等。而json.load(f)函数则是将JSON数据从文件中加载到Python中。

6、加载models中提供的模型,并且直接用训练的好权重当做初始化参数

(迁移学习)

model_name='resnet18' #可选的比较多 ['resnet', 'alexnet', 'vgg', 'squeezenet', 'densenet', 'inception']
feature_extract=True #都用人家特征,咱先不更新,将别人的模型冻住,只有输出层进行更新

#是否使用GPU来进行训练
train_on_gpu=torch.cuda.is_available()

if not train_on_gpu:
  print('CUDA is not available.  Training on CPU ...')
else:
  print('CUDA is available!  Training on GPU ...')

device=torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

#将网络中的每一层都冻结
def set_parameter_requires_grad(model,feature_extracting)
  if feature_extracting:
    for param in model.parameters():
      param.requires_grad=False

feature_extract = True是在使用迁移学习(transfer learning)进行图像分类模型训练时,一种常用的模型微调(fine-tuning)策略。在迁移学习中,我们通常使用预先训练好的模型来提取图像特征,然后将这些特征输入到一个新的分类器中进行训练。而在模型微调中,我们可以选择冻结(freeze)预训练模型的所有层,只训练新的分类器层,也可以解冻(unfreeze)预训练模型的部分或全部层,与新的分类器层一起进行训练。feature_extract = True就是指冻结预训练模型的所有层,只训练新的分类器层,即只更新新的分类器层的权重参数,不更新预训练模型的权重参数。这种策略可以降低模型训练的复杂度和计算量,加快模型训练的速度,同时可以避免过拟合(overfitting)的风险。

7、把模型输出层改成自己的

注意:当我重新设置全连接层的输出,虽然之前的梯度都设置为false了,但是新添加全连接层的梯度为True

def initialize_model(model_name,num_classes,feature_extract,use_pretrained):
  mode_ft=models.model_name(pretrained=use_pretrained)#pretrained=True:下载resnet18模型
  # 注意:resnet 只有18层 50层 101层 152层 没有其他的
  set_parameter_requires_grad(model_ft,feature_extract)#调用函数,将每一层的参数都设置位false
  num_ftrs = model_ft.fc.in_features #拿出最后一层的输入512
  model_ft.fc = nn.Linear(num_ftrs, num_classes) #类别数自己根据自己任务来
  
  input_size=64 # 输入大小根据自己配置来
  return model_ft,input_size

resnet模型最后一层:AdaptiveAvgPool2d全局平均池化,output_size=(1, 1)将每一维度求一个平均值,经过平均池化之后,我们得到的是一个512维的向量
pytorch实现迁移训练-resnet18训练花朵识别模型【深度学习】_第2张图片

8、设置哪些层需要进行训练(目前只设置全连接进行训练)

model.input_size=initialize_model(model_name,102,feature_extract,True)

#设置模型使用gpu还是cpu训练
model_ft=model_ft.to(device)

#设置模型保存的名字
filename='best.pt'

#是否训练所有网络层
params_to_update=model_ft.parameters() #将模型当中每一层的参数先储存下来
print("Params to learn:")
if feature_extract:
  params_to_update=[]
  for name,param in model_ft.named_parameters():
    if param.requires_grad==True:
      params_to_update.append(param)  #将有梯度的参数保存到列表list当中 此时params_to_update只保存了最后一层的参数
      print(name)
else:
    for name,param in model_ft.named_parameters():
        if param.requires_grad == True:
            print("\t",name)

parametersnamed_parameters

在PyTorch中,model_ft.parameters()model_ft.named_parameters()都是用于获取模型中需要更新的参数的方法,但它们返回的对象类型不同。 model_ft.parameters()方法返回一个Python迭代器,包含了模型中需要更新的所有参数。每个参数都是一个torch.Tensor类型的对象,可以通过梯度下降等优化算法进行更新。这个方法通常用于获取所有需要更新的参数,例如在定义优化器时使用。在迁移学习中,我们常常使用预训练模型中的部分或全部层作为特征提取器,只更新新的分类器层的参数。这时,我们可以通过设置requires_grad属性,决定哪些参数需要更新,哪些参数不需要更新。 model_ft.named_parameters()方法返回一个Python迭代器,包含了模型中需要更新的所有参数以及它们的名称。每个参数都是一个元组(name, parameter),其中name是参数的名称,parameter是一个torch.Tensor类型的对象,可以通过梯度下降等优化算法进行更新。这个方法通常用于获取所有需要更新的参数以及它们的名称,例如在保存和加载模型时使用。在加载预训练模型时,我们通常需要根据参数名称匹配预训练模型中的参数,来恢复预训练模型的权重参数。

代码案例:

import torch
import torchvision.models as models
# 加载预训练模型
model_ft = models.resnet18(pretrained=True)
# 冻结所有层,只训练新的分类器层
for param in model_ft.parameters():
    param.requires_grad = False
model_ft.fc.requires_grad = True  # 解冻新的分类器层
# 获取需要更新的参数列表和名称
params_to_update = []
for name, param in model_ft.named_parameters():
    if param.requires_grad == True:
        params_to_update.append(param)
        print(name)  # 输出需要更新的参数名称
# 定义优化器
optimizer = torch.optim.SGD(params_to_update, lr=0.001, momentum=0.9)

在上述示例中,我们首先使用models.resnet18()函数加载了预训练的ResNet-18模型,然后使用for循环冻结了所有层的权重参数,只解冻了新的分类器层的权重参数。接着,我们使用model_ft.named_parameters()方法获取了所有需要更新的参数以及它们的名称,并将需要更新的参数添加到了一个列表中。同时,我们输出了需要更新的参数名称。最后,我们使用torch.optim.SGD()函数定义了优化器,并传递需要更新的参数列表params_to_update给优化器。

9、优化器设置

optimizer_ft=optim.Adam(param_to_update,lr=le-2) #要训练啥参数,你来定 这里设置只更新fc最后一层
schduler=optim.lr_scheduler.StepLR(optimizer_ft,step_size=10,gamma=0.1) # 定义学习率衰减 学习率每10个epoch衰减成原来的1/10
criterion=nn.CrossEntropyLoss() #设置交叉熵损失函数

optim.lr_scheduler.StepLR是PyTorch中的学习率调整器(learning rate scheduler)之一。学习率调整器用于在训练过程中动态地调整模型的学习率,以提高训练效果。

10、模型训练模块(重点)

def train_model(model,dataloaders,criterion,optimizer,num_epochs,filename)
  #记录训练开始时间
  since=time.time()
  
  #记录最好的一次精度
  best_acc=0
  
  #模型放到你的CPU或者GPU
  model.to(device)
  #训练过程中打印一堆损失和指标
  train_acc_history=[] #训练集的准确率
  val_acc_history=[]
  
  #训练损失
  train_losses=[]
  val_losses=[]
  
  #取当前的学习率
  LRs=[optimizer.param_groups[0]['lr']]
  
  #将最好的模型参数保存下来:model.state_dict()取出模型参数
  best_model_wts=copy.deepcopy(model.state_dict())
  
  #一个一个epoch来遍历
  for epoch in range(num_epochs):
  print('Epoch {}/{}'.format(epoch, num_epochs - 1))
  print('-' * 10)
    #训练和验证
    for phase in ['train','valid']:
      if phase == 'train':
        model.train()
      else:
        model.val()
      #初始化损失和预测正确的个数
      running_loss=0.0
      running_corrects=0
      
      #将所有数据都遍历一遍
      for inputs,labels in dataloaders[phase]:
        #把数据取出来的数据放到你的CPU或GPU
        inputs=inputs.to(device)
        labels=labels.to(device)
        
        #梯度清零
        optimizer.zero_grad()
        
        #只有训练的时候计算和更新梯度
        outputs=model(inputs)  #outputs=102
        loss=criterion(outputs,labels) #计算损失
        _,preds=torch.max(outputs,1) #取出我们的预测最大值的索引 用来计算后面的准确率
        
        # 训练阶段更新权重
        if phase=='train':
          loss.backward() #损失反向传播
          optimizer.step() #参数的更新
        
        #计算损失
        running_loss+=loss.item()*input.size(0)  #inputs:torch.Size([128, 3, 64, 64])
        #0表示batch那个维度,计算当前batch的一个损失
        running_corrects+=torch.sum(preds==labels.data)#预测结果最大索引的和真实值是否一致,保存预测正确的值
      epoch_loss=running_loss/len(dataloaders[phase].dataset)  # 算平均损失
      epoch_acc=running_corrects.double()/len(dataloaders[phase].dataset)
      
      time_elapsed = time.time() - since#一个epoch我浪费了多少时间
      
      print('Time elapsed {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
      print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
      
      # 得到最好那次的模型
      if phase=='vaild' and epoch_acc>best_acc:
        best_acc=epoch_acc
        best_model_wts=copy.deepcopy(model.state_dict()) #将精度最好的模型参数拷贝下来
        state={#将精度最好模型参数,精度,优化器参数保存
          'state_dict':model.state_dict(),
          'best_acc':best_acc,
          'optimizer':optimizer.state_dict(),
        }
        torch.save(state, filename)
        
        #保存每一轮验证集和训练集的精度以及损失
      if phase=='valid':
        val_acc_history.append(epoch_acc)
        valid_losses.append(epoch_loss)
      if phase=='train':
        train_acc_history.append(epoch_acc)
        train_losses.append(epoch_loss)
    print('Optimizer learning rate : {:.7f}'.format(optimizer.param_groups[0]['lr']))
    LRs.append(optimizer.param_groups[0]['lr']) #保存当前学习率
    scheduler.step()#学习率衰减 一个step增加一次,当10次step之后(也就是10次epoch),会进行一次学习率衰减
    
  time_elapsed=time.time()-since #计算所有epoch所花费的时间
  print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
  print('Best val Acc: {:4f}'.format(best_acc))
  
  model.load_state_dict(best_model_wts)# 训练完后用最好的一次当做模型最终的结果,等着一会测试
  return model,val_acc_history,train_acc_history,valid_losses,train_losses,LRs

model_ft,val_acc_history,train_acc_hitory,valid_losses,train_losses,LRs=train_model(model_ft,dataloader,criterion,optimizer_ft,20,filename)

只训练全连接层结果:

可以看出训练集精度比验证集高,说明发生了过拟合;而且只训练全连接层的化,精度达到一定程度后将不在提高;
pytorch实现迁移训练-resnet18训练花朵识别模型【深度学习】_第3张图片

(1)model.state_dict()

model.state_dict()是PyTorch中用于返回模型的参数(权重和偏置)的字典形式。模型的参数包括了模型各个层的权重和偏置,这些参数在训练过程中会不断更新。 model.state_dict()返回的是一个字典,字典的键是每个层的名称,值是对应层的参数。可以将这个字典保存下来,以便后续恢复该模型的参数或在不同的设备上加载模型。

代码案例:

import torch
import torch.nn as nn
# 创建一个简单的模型
class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.fc = nn.Linear(10, 1)
    def forward(self, x):
        return self.fc(x)
model = SimpleModel()
# 保存模型的参数
torch.save(model.state_dict(), 'model_params.pth')
# 加载模型的参数
new_model = SimpleModel()
new_model.load_state_dict(torch.load('model_params.pth'))

在上述示例中,我们首先创建了一个简单的模型SimpleModel,并保存了它的参数到文件"model_params.pth"中。然后,我们创建了一个新的模型new_model,并使用load_state_dict()方法加载之前保存的参数。 这样,我们就可以使用model.state_dict()来获取模型的参数,并使用load_state_dict()来加载模型的参数,方便地保存和加载模型的状态。

(2)copy.deepcopy()

copy.deepcopy()是Python中用于创建一个对象的深拷贝的函数。它可以创建一个原对象的完全独立的副本,包括对象本身及其所有嵌套的对象。 深拷贝与浅拷贝的区别在于,浅拷贝只会复制对象本身,而不会复制对象内部的嵌套对象,而深拷贝则会递归地复制对象及其嵌套对象。

代码案例:

import copy
# 原对象
original_list = [1, 2, [3, 4]]
# 深拷贝
copied_list = copy.deepcopy(original_list)
# 修改原对象
original_list[2][0] = 5
# 打印结果
print(original_list)  # [1, 2, [5, 4]]
print(copied_list)  # [1, 2, [3, 4]]

在上述示例中,我们首先创建了一个原对象original_list,它包含了一个嵌套的列表。然后,我们使用copy.deepcopy()对原对象进行深拷贝,得到一个独立的副本copied_list。 接着,我们修改了原对象的嵌套列表中的一个元素。可以看到,原对象和副本对象的值不同,说明深拷贝创建了原对象及其嵌套对象的完全独立的副本。

(2)_,preds=torch.max(outputs,1)

_, preds = torch.max(outputs, 1)是一个常见的PyTorch操作,用于从模型的输出中获取预测结果。 在这个操作中,outputs是经过模型前向传播后的输出结果,通常是一个张量。torch.max()函数会返回张量中每行或每列的最大值以及对应的索引。 具体地,torch.max(outputs, 1)的第一个参数是要操作的张量,第二个参数1表示在每行上进行操作。这意味着函数会返回每行的最大值和对应的索引。返回的结果是一个元组,其中第一个元素是最大值,第二个元素是对应的索引。 在这个操作中,我们使用了一个下划线_来表示我们不关心最大值,只关心索引。因此,通过_, preds = torch.max(outputs, 1),我们将最大值丢弃,只保留了预测的索引,赋值给preds变量。 这样,preds就是模型输出中每个样本的预测类别索引。我们可以进一步使用这些预测索引进行后续的评估、计算损失等操作。

(4)loss.item() * inputs.size(0)

loss.item() * inputs.size(0)用于计算一个batch中的总损失值。在这个操作中,loss.item()返回了一个标量值,表示一个batch中的平均损失。inputs.size(0)返回了一个整数值,表示一个batch中样本的数量。 乘法运算 loss.item() * inputs.size(0) 将平均损失乘以样本数量,得到了整个batch的总损失值。这是因为平均损失只是单个样本的损失的统计量,要得到整个batch的损失值,需要将平均损失乘以样本数量。 通过计算整个batch的总损失值,可以更好地理解模型在整个batch上的性能,并与其他batch进行比较。这在训练过程中、模型选择或性能评估中都是常见的操作。 例如,如果loss.item()为0.5,inputs.size(0)为64(表示一个batch中有64个样本),那么loss.item() * inputs.size(0)的结果将为32,表示整个batch的总损失值为32。

11、继续训练其他网络层

上面的训练结果是只对全连接层进行训练,其他网络层都是冻结状态,下面开始训练所有网络结构

for param in model_ft.parameters():
  param.requires_grad=True #将每一层网络梯度都设置为True
  
#继续训练所有的参数,学习率降低一点
optimizer=optim.Adam(model_ft.paramters(),lr=1e-3) #1e-3表示科学计数法中的小数1乘以10的负3次方,即0.001。
scheduler=optim.lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1) #7轮之后进行衰减

# 损失函数
criterion = nn.CrossEntropyLoss()

# 加载之前训练好的权重参数
checkpoint=torch.load(filename)
best_acc = checkpoint['best_acc']
model_ft.load_state_dict(checkpoint['state_dict']) #用我们训练好的权重参数,代替原来模型的权重参数


#所有网络层开始训练
model_ft,val_acc_history,train_acc_history,valid_losses,train_losses, LRs =train_model(model_ft, dataloaders, criterion, optimizer, 
num_epochs=10,filename)

训练所有网络层结果:

最好的一次精度达到了60%
pytorch实现迁移训练-resnet18训练花朵识别模型【深度学习】_第4张图片

12、加载训练好的模型(不在完整训练过程里面)

如果训练中断,可以继续加载模型进行训练,注意:两次训练的网络结构必须一样,同时每一层结构都不能发生改变

model_ft, input_size = initialize_model(model_name, 102, feature_extract, use_pretrained=True)

# GPU模式
model_ft = model_ft.to(device)

# 保存文件的名字
filename='best.pt'

# 加载模型
checkpoint = torch.load(filename)
best_acc = checkpoint['best_acc'] #取出之前保存的最好的准确率
model_ft.load_state_dict(checkpoint['state_dict'])

13、测试数据预处理

  • 测试数据处理方法需要跟训练时一直才可以
  • crop操作的目的是保证输入的大小是一致的
  • 标准化操作也是必须的,用跟训练数据相同的mean和std,但是需要注意一点训练数据是在0-1上进行标准化,所以测试数据也需要先归一化
  • 最后一点,PyTorch中颜色通道是第一个维度,跟很多工具包都不一样,需要转换

Python中,iter()是一个内置函数,用于创建一个迭代器对象。在深度学习中,iter()函数常用于将数据加载器(dataloader)对象转换为一个可迭代的迭代器对象。 在iter(dataloaders['valid'])中,dataloaders['valid']是一个验证数据加载器对象。通过调用iter()函数,可以将验证数据加载器对象转换为一个迭代器对象。 迭代器对象可以通过next()函数逐个返回数据集中的元素。每次调用next()函数时,迭代器会返回下一个元素,直到遍历完所有元素为止。迭代器对象通常用于循环中,逐个处理数据集中的元素。 在深度学习中,使用迭代器对象可以方便地遍历数据集中的批次(batches),并将它们输入到模型中进行训练或验证。通过iter(dataloaders['valid'])将验证数据加载器对象转换为迭代器对象后,可以使用循环来逐个访问验证数据集中的批次,并进行相应的操作。

dataiter=iter(dataloaders['valid']) #转换成可迭代对象,因为验证集我们设置的batch_size是128,所以每一次迭代取128个数据
images, labels = dataiter.next()# 所以每次next都会取一个batch_size 128个图片
model_ft.eval()

# 判断我们有没有GPU,验证也可以使用gpu验证
if train_on_gpu:
    output = model_ft(images.cuda())
else:
    output = model_ft(images)

model_ft(images.cuda())是将输入数据images传递给已经移动到GPU上的模型model_ft进行推理(inference)的操作。 在这个操作中,images是一个张量(tensor),表示输入的图像数据。.cuda()是一个PyTorch方法,用于将张量移动到GPU上进行计算。通过调用.cuda()方法,images张量被移动到GPU上。 model_ft是一个已经定义好的模型对象,通常是一个神经网络模型。通过将images.cuda()作为输入传递给model_ft,可以将移动到GPU上的图像数据输入到模型中进行推理。

14、获取概率最大的结果

_, preds_tensor = torch.max(output, 1)
preds = np.squeeze(preds_tensor.numpy()) if not train_on_gpu else np.squeeze(preds_tensor.cpu().numpy())

np.squeeze是NumPy库中的一个函数,用于去除数组中维度为1的维度,从而减少数组的维度。 具体而言,np.squeeze(a, axis=None)函数的作用是将数组 a 中维度为1的维度去除。参数 axis 可选,用于指定要去除的维度。当 axisNone 时(默认情况下),np.squeeze 将会去除所有维度为1的维度;当 axis 为整数或整数元组时,np.squeeze 将只去除指定的维度。

代码案例:

import numpy as np
a = np.array([[[1], [2], [3]]])  # shape: (1, 3, 1)
b = np.squeeze(a)  # shape: (3,)
c = np.squeeze(a, axis=0)  # shape: (3, 1)
print(b)
print(c)

#输出结果
[1 2 3]
[[1]
 [2]
 [3]]

在上述示例中,数组 a 的维度为 (1, 3, 1),使用 np.squeeze 函数去除维度为1的维度后,得到的数组 b 的维度为 (3,);当指定 axis=0 时,得到的数组 c 的维度为 (3, 1)

15、图片处理模块

def im_convert(tensor):
'''展示数据'''
  image=tensor.to('cpu').clone().detach()
  image = image.numpy().squeeze() #转换为numpy类型
  image = image.transpose(1,2,0)# torch得到的数据一般是(c,h,w)格式,但是很多任务里面,第一个值并不是维度,这个操作我们可以把维度放到最后面
  image = image * np.array((0.229, 0.224, 0.225)) + np.array((0.485, 0.456, 0.406))
  image = image.clip(0, 1)
  return image

(1)tensor.to(‘cpu’).clone().detach()

是对一个PyTorch张量进行操作的代码片段。它的作用是将张量移动到CPU上,创建它的副本,并且将副本从计算图中分离(detach)。 具体而言,这个操作包含了以下几个步骤:

  1. tensor.to("cpu")将张量移动到CPU上进行计算。在PyTorch中,可以通过调用.to()方法并传递"cpu"作为参数,将张量从GPU上移动到CPU上。
  2. clone()创建了张量的副本。副本是原始张量的一个完全独立的拷贝,两者之间没有任何关联。
  3. detach()将副本从计算图中分离。在PyTorch中,张量默认是连接到计算图中的,这意味着它们可以追踪其计算历史并进行自动微分。调用detach()方法可以将张量从计算图中分离,使其成为一个独立的张量,不再与原始计算图相关联。 通过tensor.to("cpu").clone().detach()操作,可以获得一个在CPU上的张量副本,该副本不再与原始计算图相关联,可以进行后续的计算或操作,而不会对原始张量造成影响。

(2)image.clip(0, 1)

是对一个图像数组进行操作的代码片段。它的作用是将图像数组中的像素值限制在指定的范围内,即将小于0的像素值设为0,大于1的像素值设为1。 具体而言,image.clip(0, 1)会对图像数组中的每个像素值进行处理,如果像素值小于0,则将其设为0;如果像素值大于1,则将其设为1。这样可以确保图像中的像素值在合理的范围内,通常是0到1之间。 这个操作常用于图像处理中,特别是在对图像进行预处理或后处理时,用于保证图像的像素值不超过指定的范围。将像素值限制在0到1之间可以避免因为像素值过大或过小而导致的图像质量问题,同时也可确保图像的像素值符合预期的数据范围。

16、绘制图片

fig=plt.figure(figsize=(20,20))
columns =4
rows = 2
for idx in range (columns*rows):
    ax = fig.add_subplot(rows, columns, idx+1, xticks=[], yticks=[])
    plt.imshow(im_convert(images[idx]))
    ax.set_title("{} ({})".format(cat_to_name[str(preds[idx])], cat_to_name[str(labels[idx].item())]),
                 color=("green" if cat_to_name[str(preds[idx])]==cat_to_name[str(labels[idx].item())] else "red"))
plt.show()

plt.figure(figsize=(20, 20))是用于创建一个具有指定尺寸的图像窗口的代码。它使用了Matplotlib库中的plt.figure()函数,并通过figsize参数指定了图像窗口的尺寸。

ax = fig.add_subplot(rows, columns, idx+1, xticks=[], yticks=[])是用于在图像窗口中添加子图的代码。它使用了Matplotlib库中的add_subplot()方法,并通过参数指定子图的位置和属性。 具体而言,rows表示图像窗口中子图的行数,columns表示图像窗口中子图的列数,idx+1表示子图的索引(从1开始计数)。 xticks=[]yticks=[]表示在子图中不显示x轴和y轴的刻度。

识别结果

红色title表示识别错误
pytorch实现迁移训练-resnet18训练花朵识别模型【深度学习】_第5张图片

你可能感兴趣的:(Pytorch,深度学习,pytorch,人工智能)