图像识别实战常用模块解读

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
#pip install torchvision
from torchvision import transforms, models, datasets
#https://pytorch.org/docs/stable/torchvision/index.html
import imageio
import time
import warnings
warnings.filterwarnings("ignore")
import random

import sys
import copy
import json
from PIL import Image

代码主要是导入在深度学习和图像处理任务中常用的 Python 库,下面对每一行代码进行详细解释:

1. 操作系统相关库

import os

os 是 Python 标准库中的模块,提供了与操作系统进行交互的功能,例如文件和目录操作、环境变量管理等。在深度学习项目里,常用来处理数据集路径、创建目录等。

2. 绘图相关库

import matplotlib.pyplot as plt
%matplotlib inline
  • matplotlib.pyplot 是 matplotlib 库的一个子模块,用于创建各种静态、动态的可视化图表,比如绘制训练过程中的损失曲线、准确率曲线等。
  • %matplotlib inline 是 Jupyter Notebook 中的魔法命令,作用是让绘制的图表直接显示在 Notebook 中。

3. 数值计算库

import numpy as np

numpy 是 Python 中用于科学计算的基础库,提供了多维数组对象和各种数学函数。在深度学习里,常用来处理数据预处理、初始化权重等操作。

4. 深度学习框架库

import torch
from torch import nn
import torch.optim as optim
import torchvision
from torchvision import transforms, models, datasets
  • torch 是 PyTorch 深度学习框架的核心库,提供了张量(Tensor)操作、自动求导等功能。
  • from torch import nn 从 torch 中导入 nn 模块,该模块提供了构建神经网络的各种组件,如层(LinearConv2d 等)、损失函数(CrossEntropyLoss 等)。
  • import torch.optim as optim 导入 torch 的优化器模块,包含了各种优化算法,如 SGDAdam 等,用于更新模型的参数。
  • torchvision 是 PyTorch 的一个扩展库,主要用于处理计算机视觉任务。
    • transforms 提供了一系列图像预处理的方法,如裁剪、缩放、归一化等。
    • models 包含了预训练的深度学习模型,如 ResNet、VGG 等。
    • datasets 提供了常用的计算机视觉数据集,如 MNIST、CIFAR-10 等。

5. 图像处理库

import imageio

imageio 是一个用于读取和写入多种图像数据格式的库,在深度学习中常用来加载和保存图像文件。

6. 时间处理库

import time

time 是 Python 标准库中的模块,用于处理时间相关的操作,例如记录训练时间、设置定时任务等。

7. 警告处理

import warnings
warnings.filterwarnings("ignore")

warnings 模块用于处理 Python 发出的警告信息。warnings.filterwarnings("ignore") 这行代码会忽略所有警告信息,避免在运行过程中被警告信息干扰。

8. 随机数生成库

import random

random 是 Python 标准库中的模块,用于生成随机数。在深度学习中,常用来初始化权重、随机打乱数据集等。

9. 系统相关库

import sys

sys 是 Python 标准库中的模块,提供了一些变量和函数,用于与 Python 解释器进行交互,例如获取命令行参数、退出程序等。

10. 浅拷贝和深拷贝库

import copy

copy 是 Python 标准库中的模块,提供了浅拷贝和深拷贝的功能。在深度学习中,常用来复制模型的参数或数据结构。

11. JSON 处理库

import json

json 是 Python 标准库中的模块,用于处理 JSON 数据的编码和解码。在深度学习中,常用来保存和加载模型的配置信息。

12. 图像处理库

from PIL import Image

PIL(Python Imaging Library)是 Python 中常用的图像处理库,Image 模块提供了打开、操作和保存多种图像文件格式的功能。

2

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

代码主要用于定义深度学习项目中数据集的路径,下面逐行解释:

1. 定义数据集根目录

data_dir = './flower_data/'
  • data_dir 是一个变量,用于存储数据集的根目录路径。
  • './flower_data/' 表示当前工作目录下的 flower_data 文件夹。./ 是相对路径的表示方式,代表当前目录。

2. 定义训练集目录

train_dir = data_dir + '/train'
  • train_dir 是一个变量,用于存储训练集数据的目录路径。
  • 通过字符串拼接的方式,将数据集根目录 data_dir 和 '/train' 连接起来,得到训练集所在的完整路径。通常在深度学习项目里,训练集数据会存放在根目录下的 train 文件夹中。

3. 定义验证集目录

valid_dir = data_dir + '/valid'
  • valid_dir 是一个变量,用于存储验证集数据的目录路径。
  • 同样使用字符串拼接,将数据集根目录 data_dir 和 '/valid' 连接起来,得到验证集所在的完整路径。验证集数据一般存放在根目录下的 valid 文件夹中,用于在模型训练过程中评估模型的性能。

代码优化建议

可以使用 os.path.join() 函数来拼接路径,这样能提高代码的跨平台兼容性,因为不同操作系统的路径分隔符可能不同。示例如下:

import os

data_dir = os.path.join('.', 'flower_data')
train_dir = os.path.join(data_dir, 'train')
valid_dir = os.path.join(data_dir, 'valid')

3

data_transforms = {
    'train': 
        transforms.Compose([
        transforms.Resize([96, 96]),
        transforms.RandomRotation(45),#随机旋转,-45到45度之间随机选
        transforms.CenterCrop(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])#均值,标准差
    ]),
    'valid': 
        transforms.Compose([
        transforms.Resize([64, 64]),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

代码定义了一个名为 data_transforms 的字典,用于存储训练集和验证集的图像预处理转换操作。在深度学习中,图像预处理是非常重要的步骤,它可以提升模型的性能和泛化能力。下面详细解释代码的各个部分:

整体结构

data_transforms 是一个字典,包含两个键:'train' 和 'valid',分别对应训练集和验证集的图像预处理操作。每个键的值是一个 transforms.Compose 对象,它将多个图像转换操作按顺序组合在一起。

训练集预处理操作 ('train')

transforms.Compose([
    transforms.Resize([96, 96]),
    transforms.RandomRotation(45),
    transforms.CenterCrop(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),
    transforms.RandomGrayscale(p=0.025),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
  • transforms.Resize([96, 96]):将输入图像的大小调整为 96x96 像素。这是为后续的裁剪操作做准备。
  • transforms.RandomRotation(45):以 0 度为中心,在 -45 度到 45 度之间随机旋转图像。这增加了数据的多样性,有助于模型学习不同角度的图像特征。
  • transforms.CenterCrop(64):从调整大小后的图像中心裁剪出一个 64x64 像素的区域。
  • transforms.RandomHorizontalFlip(p=0.5):以 0.5 的概率对图像进行水平翻转。这可以让模型学习到图像的左右对称特征。
  • transforms.RandomVerticalFlip(p=0.5):以 0.5 的概率对图像进行垂直翻转。这有助于模型学习到图像的上下对称特征。
  • transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1):随机调整图像的亮度、对比度、饱和度和色相。这可以模拟不同光照条件下的图像,增强模型的泛化能力。
  • transforms.RandomGrayscale(p=0.025):以 0.025 的概率将图像转换为灰度图。这有助于模型学习到图像的灰度特征。
  • transforms.ToTensor():将 PIL 图像或 NumPy 数组转换为 PyTorch 张量,并将像素值从 0-255 归一化到 0-1 范围。
  • transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]):对张量进行归一化处理,使用 ImageNet 数据集的均值和标准差。这有助于模型更快地收敛。

验证集预处理操作 ('valid')

transforms.Compose([
    transforms.Resize([64, 64]),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
  • transforms.Resize([64, 64]):将输入图像的大小调整为 64x64 像素。
  • transforms.ToTensor():将 PIL 图像或 NumPy 数组转换为 PyTorch 张量,并将像素值归一化到 0-1 范围。
  • transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]):对张量进行归一化处理,使用 ImageNet 数据集的均值和标准差。

验证集的预处理操作相对简单,因为验证集主要用于评估模型的性能,不需要进行数据增强操作。

训练集使用了多种数据增强技术来增加数据的多样性,帮助模型学习到更丰富的特征,提高泛化能力。验证集只进行了必要的大小调整、张量转换和归一化操作,以保证评估结果的准确性。

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 x in ['train', 'valid']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'valid']}
class_names = image_datasets['train'].classes

代码主要完成了深度学习图像分类任务中数据加载和预处理的关键步骤,下面逐行解释:

1. 定义批量大小

batch_size = 128

这行代码定义了一个变量 batch_size,并将其值设置为 128。在深度学习训练过程中,batch_size 表示每次输入模型进行训练或验证的数据样本数量。使用批量数据可以提高训练效率,同时利用 GPU 的并行计算能力。

2. 创建图像数据集对象

image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'valid']}
  • 这是一个字典推导式,目的是创建一个包含训练集和验证集的 ImageFolder 对象的字典 image_datasets
  • datasets.ImageFolder 是 torchvision.datasets 中的一个类,用于从文件夹结构中加载图像数据集。文件夹结构要求每个类别对应一个子文件夹,子文件夹的名称即为类别名称。
  • os.path.join(data_dir, x):使用 os.path.join 函数将数据集根目录 data_dir 和子目录名('train' 或 'valid')拼接成完整的路径。
  • data_transforms[x]:根据当前是训练集还是验证集,从 data_transforms 字典中获取对应的图像预处理转换操作。

3. 创建数据加载器对象

dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) for x in ['train', 'valid']}
  • 同样是字典推导式,创建一个包含训练集和验证集的数据加载器对象的字典 dataloaders
  • torch.utils.data.DataLoader 是 PyTorch 提供的一个数据加载工具类,用于批量加载数据,支持多线程、数据打乱等功能。
  • image_datasets[x]:指定要加载的数据集。
  • batch_size=batch_size:设置每次加载的数据样本数量为之前定义的 batch_size
  • shuffle=True:表示在每个训练周期开始时打乱数据顺序,这样可以增加数据的随机性,有助于模型学习到更丰富的特征。

4. 计算数据集大小

dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'valid']}
  • 这也是一个字典推导式,创建一个包含训练集和验证集大小的字典 dataset_sizes
  • len(image_datasets[x]):获取每个数据集的样本数量。

5. 获取类别名称

class_names = image_datasets['train'].classes
  • 从训练集的 ImageFolder 对象中获取所有类别的名称列表 class_names。这些名称对应于数据文件夹中每个子文件夹的名称。

这段代码完成了从文件夹加载图像数据、定义数据预处理操作、创建数据加载器、计算数据集大小以及获取类别名称等任务,为后续的模型训练和验证提供了必要的数据准备。

5

image_datasets

整体结构

image_datasets 是一个字典,包含两个键值对:

  • 键 'train' 对应训练集的 ImageFolder 对象。
  • 键 'valid' 对应验证集的 ImageFolder 对象。
字典元素分析
  • datasets.ImageFolder:这是 torchvision.datasets 模块中的一个类,用于从文件夹结构中加载图像数据集。它假设数据集中每个类别对应一个子文件夹,子文件夹的名称就是类别名称。
  • os.path.join(data_dir, x):使用 os.path.join 函数将数据集根目录 data_dir 和子目录名('train' 或 'valid')拼接成完整的路径。
  • data_transforms[x]:根据当前是训练集还是验证集,从 data_transforms 字典中获取对应的图像预处理转换操作。这些操作会在加载图像时自动应用,对图像进行如缩放、裁剪、归一化等处理。

6

dataloaders

dataloaders 是一个字典,其作用是存储训练集和验证集的数据加载器对象。下面详细解释:

1. dataloaders 的创建

通常使用如下代码创建 dataloaders

dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) for x in ['train', 'valid']}
  • 字典推导式:通过字典推导式生成 dataloaders 字典,字典包含两个键值对,键分别为 'train' 和 'valid'
  • torch.utils.data.DataLoader:这是 PyTorch 提供的一个类,用于批量加载数据。它可以将数据集 image_datasets 按照指定的 batch_size 进行分批,并且支持多线程加载、数据打乱等功能。
  • image_datasets[x]image_datasets 是另一个字典,包含训练集和验证集的 torchvision.datasets.ImageFolder 对象,x 取值为 'train' 或 'valid'
  • batch_size:指定每次加载的数据样本数量。
  • shuffle=True:表示在每个训练周期开始时打乱数据顺序,增加数据的随机性,有助于模型学习到更丰富的特征。

2. dataloaders 的结构

dataloaders 字典有两个键值对:

  • dataloaders['train']:训练集的数据加载器,在模型训练阶段使用,用于批量加载训练数据。
  • dataloaders['valid']:验证集的数据加载器,在模型验证阶段使用,用于批量加载验证数据。

7

dataset_sizes

dataset_sizes 是一个存储训练集和验证集样本数量的字典,在深度学习训练和验证过程里,可用于计算迭代步数、统计数据量等操作。

创建 dataset_sizes 的代码

dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'valid']}

代码解释

  • 字典推导式:这是一种简洁创建字典的方式。for x in ['train', 'valid'] 会让 x 依次取 'train' 和 'valid' 这两个值。
  • image_datasets[x]image_datasets 是一个字典,包含了训练集和验证集的 torchvision.datasets.ImageFolder 对象。x 作为键,分别对应训练集和验证集的数据集对象。
  • len(image_datasets[x])len() 函数用于获取数据集对象中的样本数量。对于 torchvision.datasets.ImageFolder 类型的对象,len() 会返回该数据集中图像样本的总数。

dataset_sizes 结构

最终 dataset_sizes 会是一个包含两个键值对的字典:

  • dataset_sizes['train']:存储训练集的样本数量。
  • dataset_sizes['valid']:存储验证集的样本数量。

示例使用

在训练模型时,可能会用 dataset_sizes 来计算训练的总步数或者验证的总步数,示例如下:

# 计算训练一个 epoch 需要的步数
train_steps = dataset_sizes['train'] // batch_size
if dataset_sizes['train'] % batch_size != 0:
    train_steps += 1

# 计算验证需要的步数
valid_steps = dataset_sizes['valid'] // batch_size
if dataset_sizes['valid'] % batch_size != 0:
    valid_steps += 1

8

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

代码主要功能是从 cat_to_name.json 文件中读取数据,并将其解析为 Python 字典。下面详细解释代码的每一部分:

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

  • open() 是 Python 内置函数,用于打开文件。这里它接收两个参数:
    • 第一个参数 'cat_to_name.json' 是要打开的文件的名称。如果该文件和当前执行的 Python 脚本在同一目录下,直接写文件名即可;否则需要提供完整的文件路径。
    • 第二个参数 'r' 表示以只读模式打开文件。这意味着程序只能从文件中读取数据,不能对文件进行修改。
  • with...as 是 Python 的上下文管理器语法。使用上下文管理器可以确保文件在使用完毕后自动关闭,避免手动调用 close() 方法可能导致的资源泄漏问题。f 是打开文件对象的别名,后续可以通过 f 来操作这个文件。

2. cat_to_name = json.load(f)

  • json 是 Python 标准库中的模块,用于处理 JSON(JavaScript Object Notation)数据。JSON 是一种轻量级的数据交换格式,常用于前后端数据传输和配置文件存储。
  • json.load(f) 是 json 模块的一个函数,它的作用是从文件对象 f 中读取 JSON 格式的数据,并将其解析为 Python 中的字典或列表等数据结构。这里将解析后的数据赋值给变量 cat_to_name

代码的整体作用是读取 cat_to_name.json 文件中的 JSON 数据,并将其转换为 Python 字典 cat_to_name,方便后续在代码中使用这些数据。例如,这个字典可能存储了类别编号和类别名称的映射关系,在图像分类任务中可以用来将模型输出的类别编号转换为对应的类别名称。

9

cat_to_name

cat_to_name 单独出现时,在不同场景下有不同作用:

  • 交互式环境:在 Jupyter Notebook 或者 Python 交互式解释器里,直接输入 cat_to_name 并执行,会打印出这个字典的内容。这样能帮助开发者查看字典里存储的键值对,确认数据是否正确加载。
  • 代码逻辑:在完整的代码逻辑里,cat_to_name 可用于根据键获取对应的值。例如,在图像分类任务中,模型输出的可能是类别编号,而 cat_to_name 字典存储了类别编号和类别名称的映射关系,就可以用这个字典把编号转换为具体的类别名称。

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

model_name = 'resnet'  #可选的比较多 ['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")

代码主要完成了模型相关参数的设置以及检查 GPU 是否可用并确定训练设备。下面是各部分的详细解释:

1. 模型名称和特征提取标志设置

model_name = 'resnet'  #可选的比较多 ['resnet', 'alexnet', 'vgg', 'squeezenet', 'densenet', 'inception']
#是否用训练好的特征来做
feature_extract = True
  • model_name:指定要使用的深度学习模型名称,这里选择了 resnet。注释中列出了其他可选的模型名称,如 alexnetvgg 等。
  • feature_extract:一个布尔变量,用于指示是否使用预训练模型的特征。设置为 True 表示使用预训练模型提取的特征进行训练。

2. 检查 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 ...')
  • torch.cuda.is_available():这是 PyTorch 提供的函数,用于检查当前环境中是否有可用的 CUDA 设备(即 GPU)。
  • train_on_gpu:存储检查结果的布尔变量。
  • 根据 train_on_gpu 的值,程序会输出相应的信息,告知用户训练将在 CPU 还是 GPU 上进行。

3. 确定训练设备

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
  • torch.device:PyTorch 中用于指定计算设备的类。如果 CUDA 可用,device 会被设置为第一个 GPU 设备(cuda:0);否则,会被设置为 CPU。后续在代码中可以使用这个 device 变量将模型和数据移动到指定设备上进行计算。

11

def set_parameter_requires_grad(model, feature_extracting):
    if feature_extracting:
        for param in model.parameters():
            param.requires_grad = False

代码定义了一个名为 set_parameter_requires_grad 的函数,其主要功能是根据 feature_extracting 参数的值,决定是否冻结深度学习模型的参数。下面详细解释代码各部分:

函数定义

def set_parameter_requires_grad(model, feature_extracting):
  • set_parameter_requires_grad 是函数名,该函数接收两个参数:
    • model:一个 PyTorch 的模型对象,例如 resnetalexnet 等预训练模型。
    • feature_extracting:一个布尔类型的参数,用于控制是否冻结模型的参数。

条件判断与参数冻结

    if feature_extracting:
        for param in model.parameters():
            param.requires_grad = False
  • if feature_extracting::判断 feature_extracting 是否为 True。如果为 True,则执行后续的参数冻结操作。
  • for param in model.parameters()::遍历模型的所有参数。model.parameters() 是 PyTorch 模型对象的一个方法,它返回一个包含模型所有可训练参数的迭代器。
  • param.requires_grad = False:将每个参数的 requires_grad 属性设置为 False。在 PyTorch 中,requires_grad 属性控制该参数是否需要在反向传播过程中计算梯度。当设置为 False 时,该参数在训练过程中不会被更新,即参数被冻结。

12 

def initialize_model(model_name, num_classes, feature_extract, use_pretrained=True):
    
    model_ft = models.resnet152(pretrained=use_pretrained)
    set_parameter_requires_grad(model_ft, feature_extract)
    
    num_ftrs = model_ft.fc.in_features
    model_ft.fc = nn.Linear(num_ftrs, 102)#类别数自己根据自己任务来
                            
    input_size = 64#输入大小根据自己配置来

    return model_ft, input_size

代码定义了一个名为 initialize_model 的函数,其主要功能是初始化一个预训练的深度学习模型,并根据需求调整模型结构,最后返回初始化好的模型和输入图像的大小。下面是对代码各部分的详细解释:

函数定义与参数

def initialize_model(model_name, num_classes, feature_extract, use_pretrained=True):
  • initialize_model 是函数名,用于初始化模型。
  • model_name:传入的模型名称,但在当前函数实现中未使用该参数。
  • num_classes:分类任务的类别数量,同样在当前实现中未使用。
  • feature_extract:布尔类型参数,用于控制是否冻结模型的参数。若为 True,则冻结模型参数。
  • use_pretrained:布尔类型参数,默认值为 True,表示是否使用预训练的模型。

模型初始化与参数冻结

    model_ft = models.resnet152(pretrained=use_pretrained)
    set_parameter_requires_grad(model_ft, feature_extract)
  • models.resnet152(pretrained=use_pretrained):使用 torchvision.models 模块加载预训练的 ResNet-152 模型。pretrained 参数根据 use_pretrained 的值决定是否加载预训练权重。
  • set_parameter_requires_grad(model_ft, feature_extract):调用 set_parameter_requires_grad 函数,根据 feature_extract 的值决定是否冻结 model_ft 模型的参数。该函数在当前代码片段中未给出定义,但推测其功能是设置模型参数的 requires_grad 属性。

调整全连接层

    num_ftrs = model_ft.fc.in_features
    model_ft.fc = nn.Linear(num_ftrs, 102)#类别数自己根据自己任务来
  • num_ftrs = model_ft.fc.in_features:获取原 ResNet-152 模型最后全连接层的输入特征数量。
  • model_ft.fc = nn.Linear(num_ftrs, 102):将原模型的最后全连接层替换为一个新的全连接层,输入特征数量为 num_ftrs,输出特征数量为 102,即分类任务的类别数。这里的 102 是硬编码的,实际使用时应根据具体任务调整。

设置输入图像大小

    input_size = 64#输入大小根据自己配置来
  • input_size = 64:设置输入图像的大小为 64x64。这个值是硬编码的,实际使用时应根据模型和任务需求进行调整。

返回结果

    return model_ft, input_size

13

model_ft
  • 函数返回初始化好的模型 model_ft 和输入图像的大小 input_size

代码主要完成了模型初始化、设备分配、模型保存文件名设置以及确定需要训练的参数等操作。下面是对代码各部分的详细解释:

1. 模型初始化Apply

model_ft, input_size = initialize_model(model_name, 102, feature_extract, use_pretrained=True)
  • 调用 initialize_model 函数,传入模型名称 model_name、分类类别数 102、特征提取标志 feature_extract 以及是否使用预训练模型标志 use_pretrained=True
  • 该函数返回初始化好的模型 model_ft 和输入图像的大小 input_size

2. 模型设备分配Apply

#GPU还是CPU计算
model_ft = model_ft.to(device)
  • model_ft.to(device) 方法将模型 model_ft 移动到之前定义好的设备 device 上。device 可以是 CPU 或者 GPU,具体取决于 torch.cuda.is_available() 的返回结果。

3. 模型保存文件名设置

# 模型保存
filename='best.pt'
  • 定义一个字符串变量 filename,用于指定后续保存模型时使用的文件名。这里将文件名设置为 best.pt.pt 是 PyTorch 常用的模型保存文件扩展名。

4. 确定需要训练的参数

# 是否训练所有层
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)
            print("\t",name)
else:
    for name,param in model_ft.named_parameters():
        if param.requires_grad == True:
            print("\t",name)
  • params_to_update = model_ft.parameters():初始时,将模型的所有参数都作为需要更新的参数。
  • print("Params to learn:"):输出提示信息,表明接下来要打印需要训练的参数。
  • if feature_extract::如果 feature_extract 为 True,表示只使用预训练模型的特征,需要冻结部分层,此时会遍历模型的所有命名参数,将那些 requires_grad 属性为 True 的参数添加到 params_to_update 列表中,并打印这些参数的名称。
  • else::如果 feature_extract 为 False,表示要训练模型的所有可训练层,此时会遍历模型的所有命名参数,打印出那些 requires_grad 属性为 True 的参数名称,但不修改 params_to_update 列表。

 14

# 优化器设置
optimizer_ft = optim.Adam(params_to_update, lr=1e-2)#要训练啥参数,你来定
scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=10, gamma=0.1)#学习率每7个epoch衰减成原来的1/10
criterion = nn.CrossEntropyLoss()

代码主要完成了深度学习训练过程中优化器、学习率调度器和损失函数的设置,下面详细解释每一行代码:

1. 优化器设置

optimizer_ft = optim.Adam(params_to_update, lr=1e-2)
  • optim.Adam:这是 PyTorch 中的 Adam 优化器类,Adam 是一种结合了 AdaGrad 和 RMSProp 优点的自适应学习率优化算法,在很多深度学习任务中都有良好的表现。
  • params_to_update:传入需要训练更新的模型参数。结合前面的代码,这些参数是根据 feature_extract 标志筛选出来的需要训练的参数。
  • lr=1e-2:设置优化器的学习率为 0.01。学习率控制了参数更新的步长,是一个重要的超参数。

2. 学习率调度器设置

scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=10, gamma=0.1)#学习率每7个epoch衰减成原来的1/10
  • optim.lr_scheduler.StepLR:这是 PyTorch 中的步长学习率调度器类,用于在训练过程中定期调整学习率。
  • optimizer_ft:传入要调整学习率的优化器。
  • step_size=10:表示每隔 10 个训练周期(epoch)调整一次学习率。
  • gamma=0.1:表示每次调整学习率时,将当前学习率乘以 0.1,即衰减为原来的十分之一。不过注释里提到每 7 个 epoch 衰减,和代码中的 step_size=10 不一致,需要注意修正。

3. 损失函数设置

criterion = nn.CrossEntropyLoss()
  • nn.CrossEntropyLoss:这是 PyTorch 中的交叉熵损失函数类,常用于多分类任务。交叉熵损失函数可以衡量模型预测的概率分布与真实标签之间的差异,训练过程中会通过最小化这个损失来优化模型参数。

15

函数定义与参数

def train_model(model, dataloaders, criterion, optimizer, num_epochs=25,filename='best.pt'):
  • model:待训练的深度学习模型。
  • dataloaders:一个字典,包含训练集和验证集的数据加载器,键为 'train' 和 'valid'
  • criterion:损失函数,用于计算模型预测结果与真实标签之间的损失。
  • optimizer:优化器,用于更新模型的参数。
  • num_epochs:训练的轮数,默认值为 25。
  • filename:保存最佳模型的文件名,默认值为 'best.pt'

初始化变量

    since = time.time()
    best_acc = 0
    model.to(device)
    val_acc_history = []
    train_acc_history = []
    train_losses = []
    valid_losses = []
    LRs = [optimizer.param_groups[0]['lr']]
    best_model_wts = copy.deepcopy(model.state_dict())
  • since:记录训练开始的时间,用于计算训练耗时。
  • best_acc:记录验证集上的最佳准确率。
  • model.to(device):将模型移动到指定的设备(CPU 或 GPU)上。
  • val_acc_historytrain_acc_historytrain_lossesvalid_losses:分别用于记录验证集准确率、训练集准确率、训练集损失和验证集损失的历史数据。
  • LRs:记录学习率的变化历史。
  • best_model_wts:深拷贝当前模型的参数,用于保存最佳模型的参数。

训练循环

    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.eval()   # 验证模式

            running_loss = 0.0
            running_corrects = 0

            for inputs, labels in dataloaders[phase]:
                inputs = inputs.to(device)
                labels = labels.to(device)

                optimizer.zero_grad()  # 梯度清零

                outputs = model(inputs)
                loss = criterion(outputs, labels)
                _, preds = torch.max(outputs, 1)

                if phase == 'train':
                    loss.backward()  # 反向传播
                    optimizer.step()  # 更新参数

                running_loss += loss.item() * inputs.size(0)
                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
            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))
  • 外层 for 循环遍历指定的训练轮数。
  • 内层 for 循环分别在训练和验证阶段进行操作:
    • 训练阶段:将模型设置为训练模式,计算梯度并更新参数。
    • 验证阶段:将模型设置为评估模式,不计算梯度。
    • 遍历数据加载器中的每个批次,将输入数据和标签移动到指定设备上,进行前向传播计算损失和预测结果。
    • 训练阶段进行反向传播和参数更新。
    • 计算每个 epoch 的平均损失和准确率,并打印训练耗时、损失和准确率。

保存最佳模型

            if phase == 'valid' 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()
  • 打印当前的学习率,并记录到 LRs 列表中。
  • 调用学习率调度器的 step 方法,调整学习率。

训练结束Apply

    time_elapsed = time.time() - since
    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_history, valid_losses, train_losses, LRs  = train_model(model_ft, dataloaders, criterion, optimizer_ft, num_epochs=20)

代码调用了 train_model 函数来训练深度学习模型,并接收该函数的返回值。下面详细解释代码各部分:

函数调用

train_model(model_ft, dataloaders, criterion, optimizer_ft, num_epochs=20)
  • train_model:这是一个自定义的函数,从之前的解释可知,该函数的作用是对深度学习模型进行训练和验证,同时记录训练过程中的各项指标。
  • 参数说明
    • model_ft:待训练的深度学习模型对象,在之前的代码里通过 initialize_model 函数初始化得到。
    • dataloaders:一个字典,包含训练集和验证集的数据加载器,键通常为 'train' 和 'valid',用于加载训练和验证所需的数据。
    • criterion:损失函数,用于计算模型预测结果与真实标签之间的损失,例如前面代码里使用的 nn.CrossEntropyLoss()
    • optimizer_ft:优化器,用于更新模型的参数,例如前面代码里使用的 optim.Adam 优化器。
    • num_epochs=20:指定训练的轮数,这里设置为 20 轮,意味着模型会对整个训练数据集迭代 20 次。

接收返回值

model_ft, val_acc_history, train_acc_history, valid_losses, train_losses, LRs  = 

train_model 函数会返回多个值,这里将这些返回值分别赋值给对应的变量:

  • model_ft:训练好的模型对象,经过 20 轮训练后,模型的参数已经得到了更新。
  • val_acc_history:一个列表,记录了每一轮验证集上的准确率。
  • train_acc_history:一个列表,记录了每一轮训练集上的准确率。
  • valid_losses:一个列表,记录了每一轮验证集上的损失值。
  • train_losses:一个列表,记录了每一轮训练集上的损失值。
  • LRs:一个列表,记录了每一轮训练时优化器的学习率。

 16

for param in model_ft.parameters():
    param.requires_grad = True

# 再继续训练所有的参数,学习率调小一点
optimizer = optim.Adam(model_ft.parameters(), lr=1e-3)
scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)

# 损失函数
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=20,)
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'])
# 得到一个batch的测试数据
dataiter = iter(dataloaders['valid'])
images, labels = next(dataiter)

model_ft.eval()

if train_on_gpu:
    output = model_ft(images.cuda())
else:
    output = model_ft(images)
output.shape
_, preds_tensor = torch.max(output, 1)

preds = np.squeeze(preds_tensor.numpy()) if not train_on_gpu else np.squeeze(preds_tensor.cpu().numpy())
preds
def im_convert(tensor):
    """ 展示数据"""
    
    image = tensor.to("cpu").clone().detach()
    image = image.numpy().squeeze()
    image = image.transpose(1,2,0)
    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
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()