【项目013】基于模块化编程的迁移学习(学生版)教学版 | 发布版 | 返回首页

作者:欧新宇(Xinyu OU)
当前版本:Release v1.0
开发平台:Paddle 3.0.0-beta1
运行环境:Intel Core i7-7700K CPU 4.2GHz, nVidia GeForce RTX 4080
本教案所涉及的数据集仅用于教学和交流使用,请勿用作商用。

最后更新:2025年3月8日


函数式编程作为一种编程范式,将计算机运算视作数学上的函数计算过程。它采用纯函数与不可变数据结构,有效规避了程序状态管理和易变对象的使用难题。此编程范式带来了诸多显著优势:它极大地提升了代码的可读性和可维护性,使得程序流程更加清晰,易于追踪与推理;模块化编程的特性简化了单元测试流程;不可变性原则不仅为并发代码的设计带来了便利,还显著增强了代码的可重用性和可组合性;此外,模块化编程还有助于加速开发进程,减少代码冗余,使程序结构更加简洁明了。正因如此,函数式编程在现代软件开发领域日益受到青睐,成为提升代码质量和开发效率不可或缺的重要工具。本项目将 使用 ResNet18, ResNet50, Mobilenetv2, VGG16 四个模型对 十二生肖数据集蝴蝶分类数据集 进行训练和评估。本项目的完整功能主要包含三个部分,分别是模型训练、模型评估和推理预测,这三个模块的完整代码请参考本项目的 发布版。本页面将这三个任务按照知识点进行分布拆解,构建了7个任务,每个任务包含若干个知识点。

【实验目的】

  1. 能够熟练使用文件夹结构来管理代码和输出结果;
  2. 能够熟练掌握函数化编程的方式,并在使用模块(外部文件)来组织代码,同时在每个模块中完成代码测试;
  3. 学会调用paddle.vision.models库实现内置模型(AlexNet, ResNet, VGG, MobileNet等)的调用,并能够实现预训练模型的载入、训练、验证和推理;
  4. 能够熟练掌握logging函数进行日志的输出和自动化保存,熟练保存运行结果图,并能够反复调用输出;
  5. 能够快速更改算法训练过程中的各种超参数,并学会对比和分析不同超参数对算法性能的影响。

【实验要求】

  1. 所有作业均在AIStudio上进行提交,提交时包含源代码和运行结果
  2. 基本学会模块化开发的基本方法,完成 datasets.py, getLogging.py, evaluate.py,train.py,...等模块的创建,并学会调用这些模块来完成模型训练、评估和推理。
  3. 补充完成下列AlexNet, ResNet50, ResNet18, Mobilenetv2, VGG16 等模型的结果统计表(至少完成三个模型的评估)
  4. 根据性能统计结果,进行分析和总结

下面我们将在ResNet50, ResNet18, Mobilenetv2, VGG16四个模型对 十二生肖数据集蝴蝶分类数据集 进行评估,所有模型设置batch_size=64。十二生肖数据集 包含样本12个类,其中训练验证集样本7840个,训练集样本7190个, 验证集样本650个, 测试集样本660个, 共计8500个;蝴蝶分类数据集 包含7个不同种类的蝴蝶,合计619个样本,我们将按照 7:1:2 的比例划分为训练集、验证集和测试集。

【任务一】 数据准备

数据集准备是整个人工智能项目的前提,通常在项目进行之前就需要完成。在实际工程中,数据集的准备还涉及各种类型的数据分析,也就是说通常都需要进行数据清洗、数据标注和数据列表生成等步骤。本项目所使用的 十二生肖数据集 包含样本12个类,其中训练验证集样本7840个,训练集样本7190个, 验证集样本650个, 测试集样本660个, 共计8500个;蝴蝶分类数据集 包含7个不同种类的蝴蝶,合计619个样本,我们将按照 7:1:2 的比例划分为训练集、验证集和测试集。关于数据准备和数据集的划分的相关知识,请参考 【项目003】数据准备(Data Preparation)第4.2节 数据准备

以下给出这两个数据集的下载地址和数据列表生成代码,有兴趣的同学可以阅读这个两个数据集生成列表的源代码,了解数据列表生成的细节。需要重新生成时,注意根据操作系统环境和实际的文件夹配置修改源代码中的路径。

1.1 数据集解压(仅执行一次)

  1. 使用如下命令完成数据集的解压
  2. /home/aistudio/data/data* 目录中的 generate_annotation.py 文件和 data_clean.py 分别拷贝到对应的数据集文件夹中

!unzip /home/aistudio/data/data71358/Butterfly.zip -d /home/aistudio/work/ExpDatasets/Butterfly
!unzip /home/aistudio/data/data71363/Zodiac.zip -d /home/aistudio/work/ExpDatasets/Zodiac

1.2 数据清洗(仅执行一次)

对于十二生肖数据集Zodiac需要进行数据清晰,排除无法读取的图像文件;蝴蝶分类数据集Butterfly不需要进行清洗,只需要在生成列表文件时排除临时文件('.DS_Store','.ipynb_checkpoints')即可。

注意:数据清洗时间较长(约1~2分钟),请耐心等待。运行前,请先修改文件中的数据集根路径。

# 本程序功能:
# 1. 删除MacOS自动生成的文件'.DS_Store'
# 2. 对图像坏样本,进行索引,并保存到bad.txt中

# !python "D:/WorkSpace/ExpDatasets/Zodiac/data_clean.py"
!python "/home/aistudio/work/ExpDatasets/Zodiac/data_clean.py"
libpng warning: iCCP: known incorrect sRGB profile
 当前清洗进度:1/36libpng warning: iCCP: known incorrect sRGB profile
 当前清洗进度:4/36Premature end of JPEG file
 当前清洗进度:6/36Corrupt JPEG data: 4 extraneous bytes before marker 0xdb
libpng warning: iCCP: known incorrect sRGB profile
 当前清洗进度:8/36libpng warning: iCCP: known incorrect sRGB profile
 当前清洗进度:36/36数据集清洗完成, 损坏文件9, 正常文件8499.

1.3 生成图像列表文件(仅执行一次)

在生成图像列表的过程中,应注意以下几点:

  1. 同时生成 train.txt, trainval.txt, val.txt, test.txt 四个数据子集列表文件
  2. 生成 dataset_info.json 数据集信息文件
  3. 四个列表文件中的图像路径应为正确的 绝对路径
# 1. Windows
!python "D:/WorkSpace/ExpDatasets/Zodiac/generate_annotation.py"
!python "D:/WorkSpace/ExpDatasets/Butterfly/generate_annotation.py"

# 2. AIStudio
# !python "/home/aistudio/work/ExpDatasets/Zodiac/generate_annotation.py"
# !python "/home/aistudio/work/ExpDatasets/Butterfly/generate_annotation.py"
图像列表已生成,其中训练验证集样本7840,训练集样本7190个,验证集样本650个,测试集样本660个,共计8500个,损坏文件9个。
图像列表已生成, 其中训练验证集样本490,训练集样本423, 验证集样本67, 测试集样本129, 共计619个。

【任务二】 全局参数设置

实验摘要: 对于一个项目而言,我们总是期望能够通过修改配置文件的设置而不是去修改源代码来完成本地化的设置。因此,全局参数的设置通常都是项目开发中的重要环节。一般来说,全局超参数的设置通常包括系统参数,训练参数,数据集参数,网络参数,可视化参数,日志参数等。但项目创立之初,我们可能并不一定能够确定所有的超参数,因此,在项目初期,我们通常只需要定义一些基本的参数,然后在项目的开发过程中,根据需要,逐步添加新的配置项。此外,对于一个主函数来说,除了这些全局参数的设置外,还需要定义一些基本的依赖导入和本地环境的配置。

实验目的:

  1. 学会使用配置文件定义全局参数
  2. 学会根据需要,逐步添加新的配置项(通常在整个项目结束后才能完成)
  3. 学会使用配置文件进行参数的修改
  4. 学会使用配置文件进行本地环境的配置

2.1 依赖库的导入

正如实验摘要所介绍的,依赖库的导入是每个项目、每个模块所必须的。对于各个功能模块来说,只需要导入其所需的库即可,不需要考虑其他模块的依赖;同样,对于主函数来说,也只需要导入其所需的库即可。以下为训练主函数的依赖库的导入。

import os              # 系统库,用于获取系统信息和路径定义等
import yaml            # 导入yaml库,用于读取配置文件
import json            # 导入json库,用于读写配置文件
import sys             # 系统库,用于获取模块路径
import warnings                                            # 导入警告库
warnings.filterwarnings('ignore')                          # 调用warnings库,忽略警告信息,请谨慎使用,可能会在调试时掩盖潜在的问题,因此建议在开发和调试阶段保持警告开启。
sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2')     # 定义自定义模块保存位置Windows
# sys.path.append('/home/aistudio/work/teaching')          # 定义自定义模块保存位置AIStudio
from utils.getSystemInfo import getSystemInfo              # 导入系统信息模块
from utils.train import train                              # 导入训练模块
from utils.evaluate import eval                            # 导入评估模块
from utils.datasets import Dataset                         # 导入数据集模块
from utils.getLogging import init_log_config               # 导入日志模块
from utils.getOptimizer import learning_rate_setting, optimizer_setting    # 导入优化器模块
from utils.getVisualization import draw_process            # 导入绘图模块
import paddle                                              # 导入百度paddle库,用于深度学习框架
from paddle.io import DataLoader                           # 导入百度Paddle的数据加载模块
from paddle.static import InputSpec                        # 导入百度Paddle的输入规格模块
import warnings                                            # 导入警告库
warnings.filterwarnings('ignore', category=UserWarning)    # 调用warnings库,忽略警告信息,请谨慎使用,可能会在调试时掩盖潜在的问题,因此建议在开发和调试阶段保持警告开启。
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"                  # 解决多线程报错问题

2.1 全局参数的载入

常用的外部配置文件格式有:json,yaml,ini等,其中,前两者都是深度学习框架中常用的格式。json格式使用括号、引号、冒号等字符来对内容进行可视化,具有较好的可读性,且更接近于python的字典数据类型,因此,经常被用来定义各种参数以及作为数据交换的主要格式,例如目标检测中的标签文件。但是,json格式不支持注释,这使得其可读性对于初学者来说相对较差。yaml格式默认支持注释,且支持各种丰富的数据类型,因此也经常被用来作为项目的配置文件使用。在本项目中,我们将使用 .yaml 格式文件来定义全局参数,然后使用 .json 格式文件来保存和调用一些数据交换的文件,例如训练日志。此外,全局配置文件 .yaml 导入后,将会转换为一个字典对象 args,这样,在后续的实验中,我们可以直接通过变量名 args 很方便地完成所有超参数的传递。

完整的全局参数配置文件可参考:config.yaml

以下代码展示了如何导入全局参数配置文件,并将其转换为字典对象。

# 1. 从本地磁盘读取 YAML 配置文件,获取全局配置参数
config_root_path = '../../Data/Projects/Project013Modularization/'
# config_root_path = '/home/aistudio/work/teaching/Configs'
config_name = 'config.yaml'
config_path = os.path.join(config_root_path, config_name)
with open(config_path, 'r', encoding='utf-8') as yaml_file:
    args = yaml.safe_load(yaml_file)

print(json.dumps(args, indent=4, sort_keys=False))
{
    "project_name": "Project013Modularization",
    "dataset_name": "Butterfly",
    "architecture": "mobilenet_v2",
    "model_name": "None",
    "training_data": "train",
    "starting_time": "time.strftime(\"%Y%m%d%H%M\", time.localtime())",
    "input_size": [
        3,
        227,
        227
    ],
    "mean_value": [
        0.485,
        0.456,
        0.406
    ],
    "std_value": [
        0.229,
        0.224,
        0.225
    ],
    "num_trainval": -1,
    "num_train": -1,
    "num_val": -1,
    "num_test": -1,
    "class_dim": -1,
    "label_dict": {},
    "total_epoch": 10,
    "batch_size": 64,
    "log_interval": 10,
    "eval_interval": 1,
    "checkpointed": false,
    "checkpoint_train": false,
    "checkpoint_model": "Butterfly_Mobilenetv2",
    "pretrained": true,
    "pretrained_model": "API",
    "useGPU": false,
    "dataset_root_path": "D:/Workspace/ExpDatasets",
    "result_root_path": "D:/Workspace/ExpResults",
    "deployment_root_path": "D:/Workspace/ExpDeployments",
    "augmentation": {
        "augmentation_prob": 1,
        "rotate_angle": 15,
        "Vflip_prob": 0,
        "Hflip_prob": 0.5,
        "brightness": 0.2,
        "contrast": 0.2,
        "saturation": 0.2,
        "hue": 0.1
    },
    "learning_strategy": {
        "optimizer_strategy": "Momentum",
        "learning_rate_strategy": "CosineAnnealingDecay",
        "learning_rate": 0.001,
        "momentum": 0.9,
        "Piecewise_boundaries": [
            60,
            80,
            90
        ],
        "Piecewise_values": [
            0.01,
            0.001,
            0.0001,
            1e-05
        ],
        "Exponential_gamma": 0.9,
        "Polynomial_decay_steps": 10,
        "verbose": false
    },
    "results_path": {
        "checkpoint_models_path": "None",
        "final_figures_path": "None",
        "final_models_path": "None",
        "logs_path": "None"
    },
    "deployments_path": {
        "deployment_logs_path": "None",
        "deployment_final_figures_path": "None",
        "deployment_checkpoint_model_path": "None",
        "deployment_final_model_path": "None",
        "deployment_pretrained_model_path": "None"
    }
}

2.3 本地环境配置

在项目的初始化过程中,有一些参数需要通过读取其他配置文件来动态生成,例如,数据集的信息;也有一些参数需要通过组合多个超参数来生成,例如项目数据集路径、模型名称、输出结果路径、部署路径等。以下给出一些必要配置文件的修改建议:

# 2.1 初始化模型名称,用于区分和管理当前任务




# 2.2 定义设备工作模式 [GPU|CPU]
# 定义使用CPU还是GPU,使用CPU时use_cuda = False,使用GPU时use_cuda = True
def init_device(useGPU=args['useGPU']):
    paddle.set_device('gpu:0') if useGPU else paddle.set_device('cpu')
init_device()

# 2.3 初始化本地路径
# 2.3.1 数据集路径


# 2.3.2 初始化结果路径(用于开发环节中的保存训练结果、微调和验证)





# 2.3.3 部署路径(用于推理和预测)








# 2.3.4 检查结果路径和部署路径是否存在,若不存在,则创建
for path in {**args['results_path'], **args['deployments_path']}.values():
    if not os.path.exists(path):
        os.makedirs(path)

# 2.4 初始化数据集参数
dataset_info =  
fields_dataset = 
args.update({field: dataset_info[field] for field in fields_dataset})

# 2.5 初始化日志配置
logger = init_log_config(args['results_path']['logs_path'], args['model_name'])     # 初始化日志模块为logger对象,配置日志输出路径和模型名称

###################################################33
# 输出完整的训练参数 args
if __name__ == '__main__':
    print(json.dumps(args, indent=4, sort_keys=False))

2.4 部署环境配置

在项目结束后,通常要生成一份部署配置文件,用于部署环境中的参数配置。部署配置文件通常包含用户使用模型所需要的必要参数和路径,这些参数可以通过训练配置文件动态生成。以下给出一些必要配置文件的修改建议:

# 3. 将生成的完整配置文件(按需求)保存成部署配置文件
# 3.1 从训练配置文件中选取必须的参数,生成部署配置文件
fields_deploy = ['dataset_root_path', 'model_name', 'dataset_name', 'deployments_path', 'num_test', 'class_dim', 'label_dict']
args_deploy = {field: args[field] for field in fields_deploy}

# 3.2 将部署配置文件保存到本地
deploy_file = config_name.split('.')[0] + '_deploy.yaml'


【任务三】 数据获取和数据预处理

实验摘要: 不管是哪一类的人工智能项目,数据的获取和数据预处理都至关重要。本项目以图片分类为例,来讲解如何通过函数化编程实现数据集类的定义与数据预处理。

实验目的:

  1. 学会定义数据集类,实现从数据列表读取数据,并进行必要的数据预处理
  2. 学会调用数据集类,来实现不同数据集的载入
  3. 学会将数据集类保存成模块文件,方便后续调用
  4. 学会使用外部导入的方式来载入数据集类
  5. 能够在数据集类中定义简单的测试代码,用于评估功能的正确性
  6. 初步学会使用配置文件来初始化参数,并应用到数据集的本地化访问中

任务三全局配置文件:config03.yaml

3.1 数据集定义的基本介绍

在Paddle 2.0+ 中,对数据的获取包含两个步骤,一是创建数据集类,二是创建数据迭代读取器。

  1. 数据集类的定义可以是使用 paddle.io 来进行构造。数据集类的主要功能是从本地读取数据列表,并对列表中的样本进行数据预处理。全新的 paddle.vision.transforms 可以轻松的实现样本的多种预处理功能,而不用手动去编写数据预处理的函数,这大简化了代码的复杂性。对于用于训练集(train)和训练验证集(trainval),我们可以按照一定的比例对样本进行数据增广,并进行标准的数据预处理;而对于验证集(val)和测试集(test)则只需要进行标准的数据预处理即可。此外,我们还可以使用该类来从指定的数据列表中获取未经预处理的原始样本。
  2. paddle.io.DataLoader 是创建数据迭代读取器的有效工具包。它可以将读入的数据进行 batch 划分,并确定是否进行随机打乱和是否丢弃最后的冗余样本。 一般来说,对于训练样本(包括train和trainval),我们需要进行随机打乱,让每次输入到网络中的样本处于不同的组合形式,防止过拟合的产生;对于验证数据(val)和测试数据(test),由于每次测试都需要对所有样本进行一次完整的遍历,并计算最终的平均值,因此是否进行打乱,并不影响最终的结果。另一方面,由于在最终输出的loss和accuracy的平均值时,会事先进行一次批内的平均,因此如果最后一个batch的数据并不能构成完整的一批,即实际样本数量小于batch_size,会导致最终计算精度产生一定的偏差。所以,当样本数较多的时候,可以考虑在训练时丢弃最后一个batch的数据。但值得注意的是,验证集和测试集不能进行丢弃,否则会有一部分样本无法进行测试。

通常情况下数据集类是通用的,换句话说,我们只需要修改数据集的读取路径,就可以在不同的数据集上进行测试,这进一步体现了模块化设计的优势。在下面的代码中,我们将创建一个通用的数据集类 Dataset,然后再利用内置的 paddle.io.DataLoader 来创建十二生肖数据集 Zodiac 和蝴蝶分类数据集 Butterfly 的数据迭代读取器。值得注意的是,为了进一步体现模块化设计的重用性,在这里所创建的数据集类将保存到硬盘上,创建数据迭代处理器的时候我们将通过外部导入的方式来获取数据集类。这意味着你无需像其他项目案例(例如:【项目027】基于迁移学习的蝴蝶分类)一样,事先运行数据集类的定义函数。

数据集模块的调用方法

  1. 源代码:datasets.py

  2. 调用方法:

    sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2')   # 定义模块保存位置
    from utils.datasets import Dataset                       # 导入数据集模块
    

3.2 数据集类的定义

class Dataset(paddle.io.Dataset):
    """1. 数据集的定义"""
    # args: 数据集定义所使用的相关超参数
    # isTransforms=[0|1|2]: 定义Transforms类型,选择是否使用数据增广和数据归约
    # 0:仅使用必要的数据规约,包括尺度变换、数据格式和数据类型变换。用于不使用图像变换的特殊场景。
    # 1:使用完整的数据归约和数据增广。用于train和trainval
    # 2:仅使用数据规约,包括尺度变换、数据格式和数据类型变换、均值消除。用于test和val,以及部分train(原图送入模型)
    def __init__(self, dataset_root_path, args=None, mode='test', transforms_type=None):
        # 1.1 初始化内部变量,包括数据集路径、模式和是否使用数据预处理
        assert mode in ['train', 'val', 'test', 'trainval']
        self.data = []                                         # 定义数据序列,用于保存数据的路径和标签
        self.args = args                                       # 定义超参数列表
        self.augmentations = args['augmentation']              # 定义数据增广超参数
        self.transforms_type = transforms_type                       # 定义数据预处理类型

        # 1.2 根据模式,选择对应的数据预处理类型
        if mode in ['train', 'trainval']:
            self.transforms_type = 1
        elif mode in ['val', 'test'] and transforms_type != 0:
            self.transforms_type = 2

        # 1.3 读取数据列表文件,将每一行都按照路径和标签进行拆分成两个字段的序列,并将序列依次保存至data序列中
        # 1) 若列表信息长度为2,则表示包含路径和标签信息。
        # 2) 若列表信息长度为1,则表示只包含路径,不包含标签。一般正式的测试文件都只包含路径,不包含标签。
        with open(os.path.join(dataset_root_path, mode+'.txt'), encoding='utf-8') as f:
            for line in f.readlines():
                info = line.strip().split('\t')                # 拆分从列表文件中读取到数据信息
                image_path = info[0].strip()                   # 信息的[0]位置为路径
                image_label = None                             # 信息的[1]位置为标签
                if len(info) == 2:                             # 判断信息的长度,若包含标签则写入image_label
                    image_label = info[1].strip()
                elif len(info) == 1:                           # 判断信息的长度,若不包含标签,则用"-1"表示
                    image_label = -1
                self.data.append([image_path, image_label])    # 将路径和标签写入[data]容器

        # 1.4 对训练数据和验证、测试数据执行不同的数据预处理方法
        # 1) train和trainval:执行随机裁剪,并完成标准化预处理
        # 2) val和test:直接执行尺度缩放,并完成标准化预处理
        inputSize = self.args['input_size'][1:3] if len(self.args['input_size']) == 3 else self.args['input_size']  # 获取输入图片的尺寸,若输入尺寸为3维(彩色图),则取后两个维度,否则取整个尺寸(灰度图)

        prob = np.random.random()                              # 生成一个随机数,用于判断是否执行数据增广
        if self.transforms_type == 0:
            self.transforms = T.Compose([                      # 0) 输出原始数据,仅作必要数据规约
                T.Resize(inputSize),                           # 直接尺度缩放
                T.ToTensor(),                                  # 转换成Paddle规定的Tensor格式
            ])
        elif self.transforms_type == 1 and prob <= self.augmentations['augmentation_prob']:
            self.transforms = T.Compose([                      # 1) 训练数据预处理,包含数据增广(trainval, train)
                T.Resize((256, 256)),                          # 直接尺度缩放
                T.RandomResizedCrop(inputSize),                # 随机裁剪
                T.RandomHorizontalFlip(prob=self.augmentations['Hflip_prob']),              # 水平翻转
                T.RandomVerticalFlip(prob=self.augmentations['Vflip_prob']),              # 垂直翻转
                T.RandomRotation(self.augmentations['rotate_angle']),                       # 随机旋转
                T.ColorJitter(brightness=self.augmentations['brightness'],                  # 色彩扰动:亮度、对比度、饱和度和色度
                              contrast=self.augmentations['contrast'],
                              saturation=self.augmentations['saturation'],
                              hue=self.augmentations['hue']),
                T.ToTensor(),                                  # 转换成Paddle规定的Tensor格式
                T.Normalize(mean=self.args['mean_value'],      # Z-Score标准化
                            std=self.args['std_value'])
            ])
        else:
            self.transforms = T.Compose([                      # 2) 基本数据预处理,不含数据增广(val, test, 不参与增强的train)
                T.Resize(inputSize),                           # 直接尺度缩放
                T.ToTensor(),                                  # 转换成Paddle规定的Tensor格式
                T.Normalize(mean=self.args['mean_value'],      # Z-Score标准化
                            std=self.args['std_value'])
            ])

    # 2. 定义数据获取函数,返回单条数据(样本数据、对应的标签)
    def __getitem__(self, index):
        image_path, label = self.data[index]            # 根据索引,从列表中取出指定[index]图像,并将数据拆分成路径和列表
        img = cv2.imread(image_path, 1)                 # 使用cv2进行数据读取,0为灰度模式,1为彩色模式
        img = self.transforms(img)                      # 执行数据预处理
        label = np.array(label, dtype='int64')          # 将标签转换为64位整型

        return img, label

    # 3. 定义方法,返回获取数据集的样本总数
    def __len__(self):
        # 返回对象的data属性的长度
        return len(self.data)

3.3 数据类的调用

为了调用预先写好的数据集类模块,首先需要导入该模块。模块的调用通常需要指定数据集的路径和可能的配置参数。在下面的代码中,我们先使用 sys.path.append 方法将模块所在的目录添加到 Python 的搜索路径中,然后使用 import 方法将该类导入到 Python 环境中。在语句 from utils.datasets import Dataset 中,utils 是模块所在的目录,datasets 是模块的名称,通常是一个 .py 文件,Dataset 是模块中的类名。最后,我们还需要创建了一个 Dataset 类的实例,并传入数据集的路径和配置参数。下面代码中的全局参数的定义和初始化,可以根据实际情况整理后放入一个配置文件或直接在代码中定义。

import warnings                          # 导入警告库
warnings.filterwarnings('ignore')        # 调用warnings库,忽略警告信息
import os
import sys
import yaml
sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2')
# sys.path.append('/home/aistudio/work/teaching')
from utils.datasets import Dataset
from paddle.io import DataLoader
import matplotlib.pyplot as plt
import random

# 1. 从本地磁盘读取 YAML 配置文件,获取全局配置参数
config_root_path = '../../Data/Projects/Project013Modularization/'
# config_root_path = '/home/aistudio/work/teaching/Configs'
config_name = 'config03.yaml'
config_path = os.path.join(config_root_path, config_name)
with open(config_path, 'r', encoding='utf-8') as yaml_file:
    args = yaml.safe_load(yaml_file)

dataset_root_path = os.path.join(args['dataset_root_path'], args['dataset_name']) # 根据数据集的名称拼接当前任务数据集的根目录路径

# 2. 使用数据集类获取批量数据






# 3. 创建读取器






########################################################
# 4. 测试数据读取器
# 在下面的测试代码中,我们分别给出了训练集和测试集中各8幅图片的样例
# 显然,训练集的样本是经过数据增广的,而测试集的样本没有经过数据增广
print(f"trainval: {len(dataset_trainval)}, train: {len(dataset_train)}, val: {len(dataset_val)}, test: {len(dataset_test)}")
test_data_list = {'train': train_reader, 'test': test_reader}
for show in test_data_list:
    for i, (image, label) in enumerate(test_data_list[show]):
        print('测试数据集 batch_{} 的图像形态:{}, 标签形态:{}'.format(i, image.shape, label.shape))
        indices = random.sample(range(0, len(image)), 8)
        imgs = image[indices] # 假设图像存储在'image'键中
        
        plt.figure(figsize=(16, 2))
        plt.title(f"{show}ing data")
        plt.axis('off')
        for j in range(8):        
            ax = plt.subplot(1, 8, j+1)
            img = imgs[j].transpose((1, 2, 0)) # 如果图像是CHW格式,转为HWC
            img = img.numpy()
            img = img * args['std_value'] + args['mean_value']
            plt.axis('off')
            plt.imshow(img) # 如果图像是CHW格式,转为HWC
        break    

在上面代码中,系统给出了若干警告 [Warning],提示数值范围超过了显示范围,这是因为图像数据经过预处理后,已经违背了图像的默认数值要求[0..255],或[0..1]。不过,这并不影响图像的显示,因为图像数据已经经过归一化处理。

3.4 带测试的数据集类

在实际应用中,为了便于测试编写的类是否能正常运行,我们可以将上述代码整合到一起,形成完整的数据集类。在将测试代码进行整合的时候,我们可以使用 if __name__ == "__main__": 语句来确保测试代码只在直接运行该文件时执行,而不是在导入模块的时候执行。完整代码(datasets.py)的基本结构如下:

###############################################
# 1. 定义数据集类
class Dataset(paddle.io.Dataset):
    def __init__():
        ...

###############################################
# 2. 数据集类的测试
import sys
sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2')
from utils.datasets import Dataset

if __name__ == "__main__":
    # 测试代码

3.5 更换数据集

模块化的数据集类设计最好的体现之一就是可以通过更换超参数来轻松地更换数据集。在之前的代码中,我们已经实现了这一功能。因此,如果需要将数据集从 Zodiac 更换为 Butterfly,只需要在全局参数定义中,将 args['dataset_name'] 的值更改为 Butterfly 即可,其他部分的代码不需要做任何修改。此外,为了进一步简化主函数,我们还可以将全局参数定义的代码块移到配置文件(config03.yaml)中。执行主函数的时候,直接进行导入即可。具体代码如下:


【任务四】 基本函数的定义

实验摘要: 蝴蝶种类识别是一个多分类问题,我们通过卷积神经网络来完成。这部分通过PaddlePaddle手动构造一个Alexnet卷积神经的网络来实现识别功能。本实验主要实现训练前的一些准备工作,包括:全局参数定义,数据集载入,数据预处理,可视化函数定义,日志输出函数定义。

实验目的:

  1. 学会使用配置文件定义全局参数
  2. 学会设置和载入数据集
  3. 学会对输入样本进行基本的预处理
  4. 学会定义可视化函数,可视化训练过程,同时输出可视化结果图和数据
  5. 学会使用logging定义日志输出函数,用于训练过程中的日志保持

任务四全局配置文件:config04.yaml

4.1 定义日志输出函数

深度学习的项目通常都比较复杂,既涉及到编程技巧也涉及到设备环境,因此,在训练过程中,我们通常需要记录一些信息,以便后续的调试和优化。logging 是一个专业的日志输出库,既可以用于在屏幕上打印运行结果(和print()函数一致),同时也可以实现将这些运行结果保存到指定的文件中,用于后续的研究。它的使用方法和 print() 函数类似,但功能更强大,主要包括记录运行信息、记录错误信息、记录警告信息、记录调试信息等。

  1. 源代码:getLogging.py

  2. 调用方法:

     # 1. 初始化日志模块,配置日志输出路径和模型名称
     logger = init_log_config(logs_path, model_name)    # 初始化日志模块为logger对象,配置日志输出路径和模型名称
    
     # 2. 使用logger对象输出信息
     logger.info('This is a info message')              # 调用logger对象,输出信息.format())
    

logging模块的使用包含两个步骤,一是定义日志初始化函数,二是调用日志输出函数。下面分别介绍这两个步骤。

4.1.1 定义日志初始化函数

为了更好地管理日志文件,我们通常会将日志输出到一个指定的文件夹中,并以模型名称和时间戳作为文件名。下面是一个初始化日志的函数示例。其中,current_time 变量用于记录当前时间,配合 log_name 变量,可以生成唯一的日志文件名。logger 变量用于记录日志,包括屏幕输出和文件输出。logger 变量中,sh 变量用于屏幕输出,fh 变量用于文件输出,formatter 变量用于定义日志格式,logger.setLevel() 函数用于设置日志级别,logger.addHandler() 函数用于添加日志处理器。具体代码如下:

def init_log_config(logs_path=os.getcwd(), model_name='final_model'):
    # sh: 打印到屏幕控制台
    # fh: 打印到文件
    current_time = time.strftime("%Y%m%d%H%M%S", time.localtime())
    logger = logging.getLogger(model_name)
    if logger.handlers:
        logger.handlers.clear()
    logger.setLevel(logging.INFO)
    os.makedirs(logs_path, exist_ok=True)
    log_name = os.path.join(logs_path, model_name + '_' + current_time + '.log')
    # sh = logging.StreamHandler()
    fh = logging.FileHandler(log_name, mode='w', encoding='utf8')
    formatter = logging.Formatter("%(asctime)s - %(levelname)s: %(message)s")
    # sh.setFormatter(formatter)
    fh.setFormatter(formatter)
    # logger.addHandler(sh)
    logger.addHandler(fh)
    return logger

根据【任务三】中介绍的模块化设计原则,我们将日志初始化函数封装在 getLogging.py 文件中。完整代码可以在 此处 查看。

4.1.2 日志输出函数的调用

当我们需要调用logging来输出日志信息时,首先要指定模块保存的位置,然后使用 import 方法将模块载入到内存,接下来使用 init_log_config() 函数对logger进行初始化,之后就可以像使用 print() 函数一样来输出日志了。注意,在调用 init_log_config() 函数时,需要传入两个参数:logs_pathmodel_name。其中,logs_path 表示日志保存的路径,model_name 表示模型名称。 此外,需要特别注意的是,初始化函数 init_log_config() 通常需要在每次获取日志前都调用调用一次,否则会导致所有日志都输出到同一个文件中。

此处,建议将日志路径定义到输出结果文件中;同时,将日志的名称按照 “模型名称+模型生成时间” 的就够进行命名,以便于后续的日志管理。

import os
import yaml
import sys
sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2')                   # 定义模块保存位置
# sys.path.append('/home/aistudio/work/teaching')                   # 定义模块保存位置
from utils.getLogging import init_log_config                             # 导入日志模块

# 1. 从本地磁盘读取 YAML 配置文件,获取全局配置参数








# 2. 初始化日志模块,配置日志输出路径和模型名称


# 3. 使用logger对象输出信息


[2025-03-21 18:04:20,471] [    INFO] 3853597872.py:21 - 模型名称: Zodiac_mobilenet_v2。
[2025-03-21 18:04:20,473] [    INFO] 3853597872.py:22 - 输出路径:D:/Workspace/ExpResults\Project013Modularization\logs。

4.2 输出和保存系统信息

在本小节中,我们将展示如何输出和保存系统信息。这里仅给出一个范例,包括操作系统、PaddlePaddle版本、CPU、内存及GPU的基本信息。实际应用中,可以根据需要保存和输出更多有关系统的信息。这里,我们仍然使用上一节定义的 logger 对象来输出和保存系统信息。

  1. 源代码:getSystemInfo.py

  2. 调用方法:

     # 1. 初始化日志模块,配置日志输出路径和模型名称
     logger = init_log_config(logs_path, model_name)     # 初始化日志模块为logger对象,配置日志输出路径和模型名称
    
     # 2. 调用系统信息获取模块,获取系统信息,并使用json.dumps()方法将系统信息进行格式化
     system_info = json.dumps(getSystemInfo(), indent=4, ensure_ascii=False, sort_keys=False, separators=(',', ':'))
    
     # 3. 使用logger对象输出信息
     logger.info('系统基本信息:')
     logger.info(system_info)
    

系统信息获取的示例代码如下:

try:
    import pynvml
    import psutil
    import paddle
    import platform
    import json
    if platform.system() != "Linux":
        import wmi
except ModuleNotFoundError:
    import os
    os.system('pip install psutil')
    os.system('pip install pynvml')
    import pynvml
    import psutil
    import paddle
    import platform
    if platform.system() != "Linux": 
        os.system('pip install wmi')
    import wmi


#############################################################
# 1. 定义函数,用于获取系统信息
def getSystemInfo():
    # 1.1 获取CPU信息
    if platform.system() == "Linux":
        with open('/proc/cpuinfo') as f:
            for line in f:
                if line.startswith('model name'):
                    CPUName = line.split(':', 1)[1].strip()
    else: 
        c = wmi.WMI()
        for cpu in c.Win32_Processor():
            CPUName = cpu.Name

    # 1.2 获取内存信息
    memoryInfo = psutil.virtual_memory()
    memoryTotal = memoryInfo.total/(1024*1024*1024)
    memoryUsed = memoryInfo.used/(1024*1024*1024)
    memoryPercent = memoryInfo.percent

    system_info = {
        '操作系统': platform.platform(),
        'Paddle': paddle.__version__,
        'CPU': CPUName,
        '内存': f"{memoryUsed:.2f}G/{memoryTotal:.2f}G ({memoryPercent:.2f}%)",
    }

    # 13. 获取GPU信息
    try:
        pynvml.nvmlInit()
        handle = pynvml.nvmlDeviceGetHandleByIndex(0)
        GPUInfo = pynvml.nvmlDeviceGetMemoryInfo(handle)
        GPUName = pynvml.nvmlDeviceGetName(handle)
        GPUTotal = GPUInfo.total/(1024*1024*1024)
        GPUUsed = GPUInfo.used/(1024*1024*1024)

以上代码同样保存到本地文件中,完整获取系统信息的代码可访问 getSystemInfo.py 查看。与 4.1 类似,我们同样使用 logger 对象来显示和保存系统信息。

import os
import yaml
import json
import sys
sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2')                   # 定义模块保存位置
# sys.path.append('/home/aistudio/work/teaching')                   # 定义模块保存位置
from utils.getLogging import init_log_config                             # 导入日志模块
from utils.getSystemInfo import getSystemInfo                             # 导入获取系统信息模块

# 1. 从本地磁盘读取 YAML 配置文件,获取全局配置参数








# 2. 初始化日志模块,配置日志输出路径和模型名称


# 3. 使用logger对象输出信息
logger.info('项目名称: {}'.format(args['project_name']))
system_info = json.dumps(getSystemInfo(), indent=4, ensure_ascii=False, sort_keys=False, separators=(',', ':'))
logger.info('系统基本信息:')
logger.info(system_info)
[2025-03-21 18:05:00,220] [    INFO] 184325566.py:23 - 项目名称: Project013Modularization
[2025-03-21 18:05:01,311] [    INFO] 184325566.py:25 - 系统基本信息:
[2025-03-21 18:05:01,311] [    INFO] 184325566.py:26 - {
    "操作系统":"Windows-10-10.0.26100-SP0",
    "Paddle":"3.0.0-beta1",
    "CPU":"13th Gen Intel(R) Core(TM) i7-13700KF",
    "内存":"14.59G/31.85G (45.80%)",
    "GPU":"NVIDIA GeForce RTX 4080 SUPER 1.59G/15.99G (0.10%)",
    "CUDA/cuDNN":"12.3 / 9.0.0"
}

4.3 定义过程可视化函数

定义训练过程中用到的可视化方法,用于输出训练过程中关键指标与迭代次数之间的关系图,这些关键指标包括训练集损失、训练集批准确率、验证集损失,验证集准确率等。值得注意的是,训练过程中可以每个epoch绘制一个数据点,也可以每个batch绘制一个数据点,也可以每个n个batch或n个epoch绘制一个数据点。为了代码编写的便捷性,我们使用最常用的标准来绘制曲线图,即训练数据按照批次batch进行呈现,验证数据按照周期epoch进行呈现。此外,可视化代码除实现训练完成后自动将训练进程绘制成曲线图外,还可将训练过程的数据和曲线图保存至指定文件夹,以备后续的分析总结使用,输出文件夹由超参数 final_figures_path 指定。

  1. 源代码:getVisualization.py

  2. 调用方法:

    draw_process(visualization_log=visualization_log, figure_path=final_figures_path, model_name=args['model_name'])
    

特别注意:

  1. 在以上绘制可视化过程的函数调用代码中,通常只需要指定图片的保存路径 figure_path 以及模型的名称 model_name,而不需要指定具体的可视化日志 visualization_log,因为 draw_process 函数会自动从训练过程中所生成的日志数据中进行获取相关数据。但是,当我们需要离线绘制可视化结果时,就必须要手动指定 visualization_log 参数,该参数通常是一个包含训练过程中各个步骤的日志数据的 JSON 文件路径,在训练过程中生成并保存在输出结果路径 final_figures_path 中。
  2. 在使用训练集train进行训练的时候,因为需要按一定的周期进行验证,因此,通常需要同时输出train和val的日志;而在使用训练验证集train+val进行训练的时候,由于训练过程已经使用了val数据,因此再对val进行性能测试已经不具备参考意义了,因此,此时也无需再输出val的日志。

下面的演示代码中,try 部分直接调用训练中生成的日志进行过程可视化,except 部分则手动指定了日志文件路径,以便离线绘制可视化结果。

import os
import yaml
import json
import sys
sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2')              # 定义模块保存位置
# sys.path.append('/home/aistudio/work/teaching')                   # 定义模块保存位置
from utils.getVisualization import draw_process                     # 导入获取系统信息模块
os.environ["KMP_DUPLICATE_LIB_OK"] = "True"                         # 解决多线程报错问题

# 1. 从本地磁盘读取 YAML 配置文件,获取全局配置参数,并初始化本地路径和参数
config_path = '../../Data/Projects/Project013Modularization/config04.yaml'
# config_path = '/home/aistudio/work/teaching/Configs/config04.yaml'
with open(config_path, 'r', encoding='utf-8') as yaml_file:
    args = yaml.safe_load(yaml_file)

args['model_name'] = args['dataset_name'] + '_' + args['architecture']
args['results_path']['final_figures_path'] = os.path.join(args['results_root_path'], args['project_name'], 'final_figures') 
final_figures_path = args['results_path']['final_figures_path']

# 2. 从本地磁盘读取 JSON 文件,获取可视化日志,并绘制可视化结果
try:
    visualization_log = {}        
    draw_process(visualization_log=visualization_log, figure_path=final_figures_path, model_name=args['model_name'], isValid=False, isShow=True)
except:
    print('数据不存在,以下图例为测试数据。')
    log_file_path = 'D:/Workspace/DeepLearning/WebsiteV2/Data/Projects/Project013Modularization/training_log.json'
    log_file = json.loads(open(log_file_path, 'r', encoding='utf-8').read())
    draw_process(visualization_log=log_file, figure_path=final_figures_path, model_name='debug', show_top5=False, isShow=True)
数据不存在,以下图例为测试数据。
日志图已保存至:D:/Workspace/ExpResults\Project013Modularization\final_figures\training_log_debug.png

【任务五】 模型训练与评估

实验摘要: 掌握基于模块化设计方法的模型训练。

实验目的:

  1. 学会基于模块化的方法创建并调用网络模型
  2. 深刻理解训练集、验证集、训练验证集及测试集在模型训练中的作用
  3. 学会在线测试和离线测试两种测试方法,并理解两者的区别以及使用什么样的数据集完成测试
  4. 学会定义多种优化方法,并在全局参数中进行定义选择
  5. 学会通过配置文件更换数据集和模型

任务五全局配置文件:config05.yaml

5.1 配置网络模型

从Alexnet开始,包括VGG,GoogLeNet,Resnet等模型都是层次较深的模型,如果按照逐层的方式进行设计,代码会变得非常繁琐。因此,我们可以考虑将相同结构的模型进行汇总和合成,例如Alexnet中,卷积层+激活+池化层 就是一个完整的结构体。

当然,如果使用的是一些经典的网络结构,我们可以直接从Paddle的模型库中进行下载,并启用预训练模型,载入Imagenet预训练参数。在实际应用中,预训练(迁移学习)是非常有效的提高性能和缩减训练时间的方法。在载入Paddle模型库的时候,我们不需要手动设计模型,只需要按照下面的方法来直接调用即可。当然,如果是自己设计的模型,还是需要手动进行模型类的创建,详细设计方法请参考 [项目008] 卷积神经网络的结构设计与实现

在飞桨中,Paddle.vision.models 类内置了很多标准模型库,包括LeNet, alexnet, mobilenet_v1, mobilenet_v2, resnet18(34, 50, 101, 152), vgg16(19), googlenet等,更多的模型请参考:paddle.vision

在下面的代码中,我们没有直接定义模型获取函数,而是利用 yaml 文件中对模型相关参数的定义来动态加载模型,这样可以方便地对模型进行修改和扩展。具体实现方法如下:

# 3. 设置输入样本的维度
input_spec = InputSpec(shape=[None] + args['input_size'], dtype='float32', name='image')
label_spec = InputSpec(shape=[None, 1], dtype='int64', name='label')

# 4. 载入官方标准模型,若不存在则会自动进行下载,pretrained=True|False控制是否使用Imagenet预训练参数
# 使用getattr函数动态获取模型类,例如paddle.vision.models.args['architecture'],args['architecture']为模型名称,例如mobilenet_v2
model = getattr(paddle.vision.models, args['architecture'])
network = model(num_classes=args['class_dim'], pretrained=args['pretrained'])

# 5. 初始化模型并输出基本信息
model = paddle.Model(network, input_spec, label_spec) 

完整测试代码如下:

import os
import yaml
import sys
sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2')                   # 定义模块保存位置
# sys.path.append('/home/aistudio/work/teaching')                   # 定义模块保存位置
import paddle
from paddle.static import InputSpec
from utils.getLogging import init_log_config                             # 导入日志模块

# 1. 从本地磁盘读取 YAML 配置文件,获取全局配置参数,并初始化本地路径和参数








# 2. 初始化日志模块,配置日志输出路径和模型名称
logger = init_log_config(logs_path=args['results_path']['logs_path'], model_name=args['model_name'])     # 初始化日志模块为logger对象,配置日志输出路径和模型名称

# 3. 设置输入样本的维度



# 4. 载入官方标准模型,若不存在则会自动进行下载,pretrained=True|False控制是否使用Imagenet预训练参数
# 使用getattr函数动态获取模型类,例如paddle.vision.models.args['architecture'],args['architecture']为模型名称,例如mobilenet_v2



# 5. 初始化模型并输出基本信息
model = paddle.Model(network, input_spec, label_spec) 
logger.info('模型参数信息:')
logger.info(model.summary()) # 显示神经网络的具体信息
[2025-03-21 18:06:25,311] [    INFO] 692531925.py:33 - 模型参数信息:
[2025-03-21 18:06:25,383] [    INFO] 692531925.py:34 - {'total_params': 11189703, 'trainable_params': 11180103}
-------------------------------------------------------------------------------
   Layer (type)         Input Shape          Output Shape         Param #    
===============================================================================
     Conv2D-1        [[1, 3, 227, 227]]   [1, 64, 114, 114]        9,408     
   BatchNorm2D-1    [[1, 64, 114, 114]]   [1, 64, 114, 114]         256      
      ReLU-1        [[1, 64, 114, 114]]   [1, 64, 114, 114]          0       
    MaxPool2D-1     [[1, 64, 114, 114]]    [1, 64, 57, 57]           0       
     Conv2D-2        [[1, 64, 57, 57]]     [1, 64, 57, 57]        36,864     
   BatchNorm2D-2     [[1, 64, 57, 57]]     [1, 64, 57, 57]          256      
      ReLU-2         [[1, 64, 57, 57]]     [1, 64, 57, 57]           0       
     Conv2D-3        [[1, 64, 57, 57]]     [1, 64, 57, 57]        36,864     
   BatchNorm2D-3     [[1, 64, 57, 57]]     [1, 64, 57, 57]          256      
   BasicBlock-1      [[1, 64, 57, 57]]     [1, 64, 57, 57]           0       
     Conv2D-4        [[1, 64, 57, 57]]     [1, 64, 57, 57]        36,864     
   BatchNorm2D-4     [[1, 64, 57, 57]]     [1, 64, 57, 57]          256      
      ReLU-3         [[1, 64, 57, 57]]     [1, 64, 57, 57]           0       
     Conv2D-5        [[1, 64, 57, 57]]     [1, 64, 57, 57]        36,864     
   BatchNorm2D-5     [[1, 64, 57, 57]]     [1, 64, 57, 57]          256      
   BasicBlock-2      [[1, 64, 57, 57]]     [1, 64, 57, 57]           0       
     Conv2D-7        [[1, 64, 57, 57]]     [1, 128, 29, 29]       73,728     
   BatchNorm2D-7     [[1, 128, 29, 29]]    [1, 128, 29, 29]         512      
      ReLU-4         [[1, 128, 29, 29]]    [1, 128, 29, 29]          0       
     Conv2D-8        [[1, 128, 29, 29]]    [1, 128, 29, 29]       147,456    
   BatchNorm2D-8     [[1, 128, 29, 29]]    [1, 128, 29, 29]         512      
     Conv2D-6        [[1, 64, 57, 57]]     [1, 128, 29, 29]        8,192     
   BatchNorm2D-6     [[1, 128, 29, 29]]    [1, 128, 29, 29]         512      
   BasicBlock-3      [[1, 64, 57, 57]]     [1, 128, 29, 29]          0       
     Conv2D-9        [[1, 128, 29, 29]]    [1, 128, 29, 29]       147,456    
   BatchNorm2D-9     [[1, 128, 29, 29]]    [1, 128, 29, 29]         512      
      ReLU-5         [[1, 128, 29, 29]]    [1, 128, 29, 29]          0       
     Conv2D-10       [[1, 128, 29, 29]]    [1, 128, 29, 29]       147,456    
  BatchNorm2D-10     [[1, 128, 29, 29]]    [1, 128, 29, 29]         512      
   BasicBlock-4      [[1, 128, 29, 29]]    [1, 128, 29, 29]          0       
     Conv2D-12       [[1, 128, 29, 29]]    [1, 256, 15, 15]       294,912    
  BatchNorm2D-12     [[1, 256, 15, 15]]    [1, 256, 15, 15]        1,024     
      ReLU-6         [[1, 256, 15, 15]]    [1, 256, 15, 15]          0       
     Conv2D-13       [[1, 256, 15, 15]]    [1, 256, 15, 15]       589,824    
  BatchNorm2D-13     [[1, 256, 15, 15]]    [1, 256, 15, 15]        1,024     
     Conv2D-11       [[1, 128, 29, 29]]    [1, 256, 15, 15]       32,768     
  BatchNorm2D-11     [[1, 256, 15, 15]]    [1, 256, 15, 15]        1,024     
   BasicBlock-5      [[1, 128, 29, 29]]    [1, 256, 15, 15]          0       
     Conv2D-14       [[1, 256, 15, 15]]    [1, 256, 15, 15]       589,824    
  BatchNorm2D-14     [[1, 256, 15, 15]]    [1, 256, 15, 15]        1,024     
      ReLU-7         [[1, 256, 15, 15]]    [1, 256, 15, 15]          0       
     Conv2D-15       [[1, 256, 15, 15]]    [1, 256, 15, 15]       589,824    
  BatchNorm2D-15     [[1, 256, 15, 15]]    [1, 256, 15, 15]        1,024     
   BasicBlock-6      [[1, 256, 15, 15]]    [1, 256, 15, 15]          0       
     Conv2D-17       [[1, 256, 15, 15]]     [1, 512, 8, 8]       1,179,648   
  BatchNorm2D-17      [[1, 512, 8, 8]]      [1, 512, 8, 8]         2,048     
      ReLU-8          [[1, 512, 8, 8]]      [1, 512, 8, 8]           0       
     Conv2D-18        [[1, 512, 8, 8]]      [1, 512, 8, 8]       2,359,296   
  BatchNorm2D-18      [[1, 512, 8, 8]]      [1, 512, 8, 8]         2,048     
     Conv2D-16       [[1, 256, 15, 15]]     [1, 512, 8, 8]        131,072    
  BatchNorm2D-16      [[1, 512, 8, 8]]      [1, 512, 8, 8]         2,048     
   BasicBlock-7      [[1, 256, 15, 15]]     [1, 512, 8, 8]           0       
     Conv2D-19        [[1, 512, 8, 8]]      [1, 512, 8, 8]       2,359,296   
  BatchNorm2D-19      [[1, 512, 8, 8]]      [1, 512, 8, 8]         2,048     
      ReLU-9          [[1, 512, 8, 8]]      [1, 512, 8, 8]           0       
     Conv2D-20        [[1, 512, 8, 8]]      [1, 512, 8, 8]       2,359,296   
  BatchNorm2D-20      [[1, 512, 8, 8]]      [1, 512, 8, 8]         2,048     
   BasicBlock-8       [[1, 512, 8, 8]]      [1, 512, 8, 8]           0       
AdaptiveAvgPool2D-1   [[1, 512, 8, 8]]      [1, 512, 1, 1]           0       
     Linear-1            [[1, 512]]             [1, 7]             3,591     
===============================================================================
Total params: 11,189,703
Trainable params: 11,180,103
Non-trainable params: 9,600
-------------------------------------------------------------------------------
Input size (MB): 0.59
Forward/backward pass size (MB): 60.81
Params size (MB): 42.69
Estimated Total Size (MB): 104.09
-------------------------------------------------------------------------------

【注意】 在以上代码中,语句 logger.info(model.summary()) 只能在控制台输出 model.summary() 的信息,而无法将该日志信息输出到logger的本地日志中。这主要是因为 model.summary() 并不是直接返回一个字符串,而是打印到标注输出(stdout)。因此,如果想要跟预期一样把日志输出到日志文件中,需要使用 io.String()来捕获输出到缓存,然年后再将其传递给日志记录器logger。以下给出一个示例。

import io

summary_buffer = io.StringIO()            # 创建一个 StringIO 对象用于捕获输出
original_stdout = sys.stdout              # 保存原始的 stdout
sys.stdout = summary_buffer               # 将 stdout 重定向到 summary_buffer
model.summary()                           # 调用Paddle的 summary 方法
sys.stdout = original_stdout              # 恢复原始的 stdout
summary_text = summary_buffer.getvalue()  # 获取捕获的输出内容
summary_buffer.close()                    # 关闭 StringIO 对象
logger.info(summary_text)                 # 使用 logger.info 输出模型摘要

5.2 定义优化方法

学习率策略优化方法 是两个密不可分的模块,学习率策略定义了学习率的变化方法,常见的包括固定学习率、分段学习率、余弦退火学习率、指数学习率和多项式学习率等;优化策略是如何进行学习率的学习和变化,常见的包括动量SGD、RMS、SGD和Adam等。

下面,我们给出一个优化方法调用的例子,这里我们以一个简单的单层神经网络模型为例。

  1. 源代码:getOptimizer.py

  2. 调用方法:

    sys.path.append(r'D:\WorkSpace\DeepLearning\WebsiteV2   # 定义模块保存位置
    from utils.getOptimizer import learning_rate_setting, optimizer_setting     # 导入优化器模块
    
    lr = learning_rate_setting(args=args)                                       # 调用学习率函数配置学习率
    optimizer = optimizer_setting(model, lr, argsO=args['learning_strategy'])   # 调用优化器函数配置优化器
    
import yaml
import sys
sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2')                   # 定义模块保存位置
# sys.path.append('/home/aistudio/work/teaching')                   # 定义模块保存位置
from utils.getOptimizer import learning_rate_setting, optimizer_setting
import paddle

# 1. 从本地磁盘读取 YAML 配置文件,获取全局配置参数,并初始化本地路径和参数
config_path = '../../Data/Projects/Project013Modularization/config05.yaml'
# config_path = '/home/aistudio/work/teaching/Configs/config05.yaml'
with open(config_path, 'r', encoding='utf-8') as yaml_file:
    args = yaml.safe_load(yaml_file)

# 2. 以下代码为优化器测试代码,仅供演示和测试,本项目的运行无需事先执行。
if __name__ == '__main__':
    linear = 
    lr = 
    optimizer = 
    if args['learning_strategy']['optimizer_strategy'] == 'fixed':
        print(f"learning = {args['learning_strategy']['learning_rate']}")
    else:
        for epoch in range(args['total_epoch']):               # 模拟训练过程,输出10个epoch,每个epoch包含10个batch
            for batch_id in range(10):
                x = paddle.uniform([10, 10])
                out = linear(x)
                loss = paddle.mean(out)
                loss.backward()
                optimizer.step()
                optimizer.clear_gradients()
                # lr.step()                   # 按照batch进行学习率更新
            lr.step()                         # 按照epoch进行学习率更新
当前学习率策略为: Adam + CosineAnnealingDecay, 初始学习率为:0.001
Epoch 0: CosineAnnealingDecay set learning rate to 0.001.
Epoch 1: CosineAnnealingDecay set learning rate to 0.0009890738003669028.
Epoch 2: CosineAnnealingDecay set learning rate to 0.0009567727288213003.
Epoch 3: CosineAnnealingDecay set learning rate to 0.0009045084971874737.
Epoch 4: CosineAnnealingDecay set learning rate to 0.0008345653031794292.
Epoch 5: CosineAnnealingDecay set learning rate to 0.00075.

5.3 定义验证函数

验证函数有两个功能,一是在训练过程中实时地对验证集进行测试(在线测试),二是在训练结束后对测试集进行测试(离线测试)。

验证函数的具体流程包括:

  1. 初始化输出变量,包括top1精度,top5精度和损失
  2. 基于批次batch的结构进行循环测试,具体包括:
    1). 定义输入层(image,label),图像输入维度 [batch, channel, Width, Height] (-1,imgChannel,imgSize,imgSize),标签输入维度 [batch, 1] (-1,1)
    2). 定义输出层:在paddle2.0+中,我们使用model.eval_batch([image],[label])进行评估验证,该函数可以直接输出精度和损失,但在运行前需要使用model.prepare()进行配置。值得注意的,在计算测试集精度的时候,需要对每个批次的精度/损失求取平均值。

在定义eval()函数的时候,我们需要为其指定两个必要参数:model 是需要进行测试或验证的模型,data_reader 是迭代的数据读取器,取值为前面所定义的 val_reader, test_reader,分别是验证集和测试集。此处验证集和测试集数据的测试过程是相同的,只是所使用的数据不同;此外,可选参数verbose用于定义是否在测试的时候输出过程

  1. 源代码:evaluate.py

  2. 调用方法:

    sys.path.append(r'D:\WorkSpace\DeepLearning\WebsiteV2   # 定义模块保存位置
    from utils.evaluate import eval                         # 导入优化器模块
    
    avg_loss, avg_acc_top1, avg_acc_top5 = eval(model, val_reader) # 调用方法
    
import os
import yaml
import sys
sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2')                   # 定义模块保存位置
# sys.path.append('/home/aistudio/work/teaching')                   # 定义模块保存位置
from utils.evaluate import *
from utils.datasets import Dataset
import paddle
from paddle.io import DataLoader
from paddle.static import InputSpec
import warnings
warnings.filterwarnings("ignore")    # 调用warnings库,忽略警告信息,请谨慎使用,可能会在调试时掩盖潜在的问题,因此建议在开发和调试阶段保持警告开启。

# 1. 从本地磁盘读取 YAML 配置文件,获取全局配置参数,并初始化本地路径和参数
config_path = 
# config_path = '/home/aistudio/work/teaching/Configs/config05.yaml'
with open(  ) as yaml_file:


dataset_root_path = 

# 2. 使用数据集类获取批量数据



# 3. 设置输入样本的维度



# 4. 载入官方标准模型,若不存在则会自动进行下载,pretrained=True|False控制是否使用Imagenet预训练参数
# 使用getattr函数动态获取模型类,例如paddle.vision.models.args['architecture'],args['architecture']为模型名称,例如mobilenet_v2



# 5. 初始化模型并输出基本信息




# 6. 执行评估函数,并输出验证集样本的损失和精度
print('开始评估...')


开始评估...
[验证集] 损失: 4.51603, top1精度:0.00000, top5精度为:0.77612

注意,对于以上验证函数的测试,只需要执行第6步,当前的 1-5 步只是为了实现模型 model 和验证数据集 val_reader 的初始化定义,以方便后续的测试。

5.4 模型训练及在线测试

在Paddle 2.0+动态图模式下,动态图模式被作为默认进程,同时动态图守护进程 fluid.dygraph.guard(PLACE) 被取消。

训练部分 主函数的具体流程包括:

  1. 进行超参数的本地化配置,包括各种本地路径、项目任务属性的超参数的设置(部分属性可以直接写入yaml,但有的信息需要进行拼接的通常可以在主函数中生成)。注意,使用trainval进行训练的时候,不需要进行验证,因为验证集已经加入到训练集中,验证的损失和精度都不具备参考意义。在进行超参数初始化的时候,建议直接修改参数字典变量 args,而不是创建一个临时变量,这样既方便多个模块的调用,也便于日志文件的输出。
  2. 创建数据集的迭代读取器。注意对于不同数据子集,随机打乱shuffle和末尾丢弃drop_last的设置有所不同。
  3. 定义输入层样本的规约(input_spec, label_spec): 图像输入维度 [batch, channel, Width, Height] (None, imgChannel, imgSize, imgSize),标签输入维度 [batch, 1] (None, 1)
  4. 实例化网络模型: model = Paddle.Model(network,input_spec,label_spec)
  5. 定义学习率策略和优化算法
  6. 定义输出层,即模型准备函数:model.prepare()
  7. 调用训练函数 train() 基于"周期-批次"两层循环进行训练,并返回训练日志,用于可视化输出
  8. 在训练主函数的执行过程中,可以记录并输出一些提示信息,以及日志信息,便于后续的分析和统计;同时训练过程的结果也应进行输出,包括定期输出模型。此处,我们分别保存用于调优和恢复训练的checkpoint_model模型和用于部署与预测的final_model模型
  1. 源代码:train.py

  2. 调用方法:

    # 1. 导入模块
    sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2   # 定义模块保存位置
    from utils.train import train                          # 导入训练模块
    
    # 2. 方法调用
    visualization_log =  train(model, args=args, train_reader=train_reader, val_reader=val_reader, logger=logger) 
    
import os
import yaml
import sys
import json
sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2')                   # 定义模块保存位置
# sys.path.append('/home/aistudio/work/teaching')                   # 定义模块保存位置
from utils.getSystemInfo import getSystemInfo 
from utils.train import train
from utils.evaluate import eval
from utils.datasets import Dataset
from utils.getLogging import init_log_config                             # 导入日志模块
from utils.getOptimizer import learning_rate_setting, optimizer_setting
from utils.getVisualization import draw_process                             # 导入获取系统信息模块
import paddle
from paddle.io import DataLoader
from paddle.static import InputSpec
import warnings
warnings.filterwarnings("ignore")    # 调用warnings库,忽略警告信息,请谨慎使用,可能会在调试时掩盖潜在的问题,因此建议在开发和调试阶段保持警告开启。
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"        # 解决多线程报错问题

#####################################################################################
# 1. 从本地磁盘读取 YAML 配置文件,获取全局配置参数,并初始化本地路径和参数
# 1.1 从本地磁盘读取 YAML 配置文件,获取全局配置参数
config_path = 

# 1.2 初始化本地路径









# 初始化日志模块为logger对象,配置日志输出路径和模型名称
logger =      

# 1.3 初始化训练参数
# 1.3.1 设置是否使用预训练模型
if args['pretrained'] == True:
    logger.info('载入 Imagenet-{} 预训练模型完毕,开始微调训练(fine-tune)。'.format(args['architecture']))
elif args['pretrained'] == False:
    logger.info('载入 {} 模型完毕,从初始状态开始训练。'.format(args['architecture']))


#####################################################################################
# 2. 获取训练所用的数据子集的数据,并初始化数据样本的维度
# 2.1 实例化各个数据使用数据集类获取批量数据
dataset_trainval = Dataset(dataset_root_path, args=args, 






# 2.2 设置输入样本的维度



#####################################################################################
# 3. 初始化训练模型
# 3.1 自动设置训练数据来源,注意使用trainval时只进行训练,不进行验证
if args['training_data'] == 'trainval':
    train_reader = trainval_reader
    isValid = False
elif args['training_data'] == 'train':
    train_reader = train_reader
    isValid = True

# 3.2 初始化模型
# 载入官方标准模型,若不存在则会自动进行下载,pretrained=True|False控制是否使用Imagenet预训练参数
# 使用getattr函数动态获取模型类,例如paddle.vision.models.args['architecture'],args['architecture']为模型名称,例如mobilenet_v2
model_builder = 
network = 
model = 


# 3.3 设置学习率、优化器、损失函数和评价指标
lr = 
optimizer = 




#####################################################################################
# 4. 开始模型训练 
# 4.1 输出系统硬件信息
logger.info('系统基本信息:')
system_info = json.dumps(getSystemInfo(), indent=4, ensure_ascii=False, sort_keys=False, separators=(',', ':'))
logger.info(system_info)

# 4.2 输出训练的超参数信息
data = 
logger.info(data)

# 4.3 输出训练的超参数信息
logger.info('模型参数信息:')
                            # 是否显示神经网络的具体信息

# 4.4 启动训练过程
visualization_log =  

# 4.5 输出训练过程图
draw_process( )
logger.info('Done.')
[2025-03-21 18:17:50,203] [    INFO] 2069346315.py:42 - 载入 Imagenet-resnet18 预训练模型完毕,开始微调训练(fine-tune)。
[2025-03-21 18:17:50,647] [    INFO] 2069346315.py:88 - 系统基本信息:
当前学习率策略为: Adam + CosineAnnealingDecay, 初始学习率为:0.001
Epoch 0: CosineAnnealingDecay set learning rate to 0.001.
[2025-03-21 18:17:51,702] [    INFO] 2069346315.py:90 - {
    "操作系统":"Windows-10-10.0.26100-SP0",
    "Paddle":"3.0.0-beta1",
    "CPU":"13th Gen Intel(R) Core(TM) i7-13700KF",
    "内存":"14.31G/31.85G (44.90%)",
    "GPU":"NVIDIA GeForce RTX 4080 SUPER 2.00G/15.99G (0.12%)",
    "CUDA/cuDNN":"12.3 / 9.0.0"
}
[2025-03-21 18:17:51,702] [    INFO] 2069346315.py:94 - {
    "project_name":"Project013Modularization",
    "dataset_name":"Butterfly",
    "architecture":"resnet18",
    "model_name":"Butterfly_resnet18",
    "training_data":"train",
    "input_size":[
        3,
        227,
        227
    ],
    "mean_value":[
        0.485,
        0.456,
        0.406
    ],
    "std_value":[
        0.229,
        0.224,
        0.225
    ],
    "num_trainval":490,
    "num_train":423,
    "num_val":67,
    "num_test":129,
    "class_dim":7,
    "total_epoch":5,
    "batch_size":64,
    "log_interval":1,
    "eval_interval":1,
    "pretrained":true,
    "use_gpu":true,
    "dataset_root_path":"D:/workspace/ExpDatasets",
    "results_root_path":"D:/Workspace/ExpResults",
    "augmentation":{
        "augmentation_prob":1,
        "rotate_angle":15,
        "Vflip_prob":0,
        "Hflip_prob":0.5,
        "brightness":0.2,
        "contrast":0.2,
        "saturation":0.2,
        "hue":0.1
    },
    "learning_strategy":{
        "optimizer_strategy":"Adam",
        "learning_rate_strategy":"CosineAnnealingDecay",
        "learning_rate":0.001,
        "momentum":0.9,
        "Piecewise_boundaries":[
            60,
            80,
            90
        ],
        "Piecewise_values":[
            0.01,
            0.001,
            0.0001,
            1e-05
        ],
        "Exponential_gamma":0.9,
        "Polynomial_decay_steps":10,
        "verbose":true
    },
    "results_path":{
        "logs_path":"D:/Workspace/ExpResults\\Project013Modularization\\logs",
        "final_figures_path":"D:/Workspace/ExpResults\\Project013Modularization\\final_figures",
        "checkpoint_models_path":"D:/Workspace/ExpResults\\Project013Modularization\\checkpoint_models",
        "final_models_path":"D:/Workspace/ExpResults\\Project013Modularization\\final_models"
    }
}
[2025-03-21 18:17:51,703] [    INFO] 2069346315.py:97 - 模型参数信息:
[2025-03-21 18:17:51,834] [    INFO] 2069346315.py:98 - {'total_params': 11189703, 'trainable_params': 11180103}
-------------------------------------------------------------------------------
   Layer (type)         Input Shape          Output Shape         Param #    
===============================================================================
     Conv2D-1        [[1, 3, 227, 227]]   [1, 64, 114, 114]        9,408     
   BatchNorm2D-1    [[1, 64, 114, 114]]   [1, 64, 114, 114]         256      
      ReLU-1        [[1, 64, 114, 114]]   [1, 64, 114, 114]          0       
    MaxPool2D-1     [[1, 64, 114, 114]]    [1, 64, 57, 57]           0       
     Conv2D-2        [[1, 64, 57, 57]]     [1, 64, 57, 57]        36,864     
   BatchNorm2D-2     [[1, 64, 57, 57]]     [1, 64, 57, 57]          256      
      ReLU-2         [[1, 64, 57, 57]]     [1, 64, 57, 57]           0       
     Conv2D-3        [[1, 64, 57, 57]]     [1, 64, 57, 57]        36,864     
   BatchNorm2D-3     [[1, 64, 57, 57]]     [1, 64, 57, 57]          256      
   BasicBlock-1      [[1, 64, 57, 57]]     [1, 64, 57, 57]           0       
     Conv2D-4        [[1, 64, 57, 57]]     [1, 64, 57, 57]        36,864     
   BatchNorm2D-4     [[1, 64, 57, 57]]     [1, 64, 57, 57]          256      
      ReLU-3         [[1, 64, 57, 57]]     [1, 64, 57, 57]           0       
     Conv2D-5        [[1, 64, 57, 57]]     [1, 64, 57, 57]        36,864     
   BatchNorm2D-5     [[1, 64, 57, 57]]     [1, 64, 57, 57]          256      
   BasicBlock-2      [[1, 64, 57, 57]]     [1, 64, 57, 57]           0       
     Conv2D-7        [[1, 64, 57, 57]]     [1, 128, 29, 29]       73,728     
   BatchNorm2D-7     [[1, 128, 29, 29]]    [1, 128, 29, 29]         512      
      ReLU-4         [[1, 128, 29, 29]]    [1, 128, 29, 29]          0       
     Conv2D-8        [[1, 128, 29, 29]]    [1, 128, 29, 29]       147,456    
   BatchNorm2D-8     [[1, 128, 29, 29]]    [1, 128, 29, 29]         512      
     Conv2D-6        [[1, 64, 57, 57]]     [1, 128, 29, 29]        8,192     
   BatchNorm2D-6     [[1, 128, 29, 29]]    [1, 128, 29, 29]         512      
   BasicBlock-3      [[1, 64, 57, 57]]     [1, 128, 29, 29]          0       
     Conv2D-9        [[1, 128, 29, 29]]    [1, 128, 29, 29]       147,456    
   BatchNorm2D-9     [[1, 128, 29, 29]]    [1, 128, 29, 29]         512      
      ReLU-5         [[1, 128, 29, 29]]    [1, 128, 29, 29]          0       
     Conv2D-10       [[1, 128, 29, 29]]    [1, 128, 29, 29]       147,456    
  BatchNorm2D-10     [[1, 128, 29, 29]]    [1, 128, 29, 29]         512      
   BasicBlock-4      [[1, 128, 29, 29]]    [1, 128, 29, 29]          0       
     Conv2D-12       [[1, 128, 29, 29]]    [1, 256, 15, 15]       294,912    
  BatchNorm2D-12     [[1, 256, 15, 15]]    [1, 256, 15, 15]        1,024     
      ReLU-6         [[1, 256, 15, 15]]    [1, 256, 15, 15]          0       
     Conv2D-13       [[1, 256, 15, 15]]    [1, 256, 15, 15]       589,824    
  BatchNorm2D-13     [[1, 256, 15, 15]]    [1, 256, 15, 15]        1,024     
     Conv2D-11       [[1, 128, 29, 29]]    [1, 256, 15, 15]       32,768     
  BatchNorm2D-11     [[1, 256, 15, 15]]    [1, 256, 15, 15]        1,024     
   BasicBlock-5      [[1, 128, 29, 29]]    [1, 256, 15, 15]          0       
     Conv2D-14       [[1, 256, 15, 15]]    [1, 256, 15, 15]       589,824    
  BatchNorm2D-14     [[1, 256, 15, 15]]    [1, 256, 15, 15]        1,024     
      ReLU-7         [[1, 256, 15, 15]]    [1, 256, 15, 15]          0       
     Conv2D-15       [[1, 256, 15, 15]]    [1, 256, 15, 15]       589,824    
  BatchNorm2D-15     [[1, 256, 15, 15]]    [1, 256, 15, 15]        1,024     
   BasicBlock-6      [[1, 256, 15, 15]]    [1, 256, 15, 15]          0       
     Conv2D-17       [[1, 256, 15, 15]]     [1, 512, 8, 8]       1,179,648   
  BatchNorm2D-17      [[1, 512, 8, 8]]      [1, 512, 8, 8]         2,048     
      ReLU-8          [[1, 512, 8, 8]]      [1, 512, 8, 8]           0       
     Conv2D-18        [[1, 512, 8, 8]]      [1, 512, 8, 8]       2,359,296   
  BatchNorm2D-18      [[1, 512, 8, 8]]      [1, 512, 8, 8]         2,048     
     Conv2D-16       [[1, 256, 15, 15]]     [1, 512, 8, 8]        131,072    
  BatchNorm2D-16      [[1, 512, 8, 8]]      [1, 512, 8, 8]         2,048     
   BasicBlock-7      [[1, 256, 15, 15]]     [1, 512, 8, 8]           0       
     Conv2D-19        [[1, 512, 8, 8]]      [1, 512, 8, 8]       2,359,296   
  BatchNorm2D-19      [[1, 512, 8, 8]]      [1, 512, 8, 8]         2,048     
      ReLU-9          [[1, 512, 8, 8]]      [1, 512, 8, 8]           0       
     Conv2D-20        [[1, 512, 8, 8]]      [1, 512, 8, 8]       2,359,296   
  BatchNorm2D-20      [[1, 512, 8, 8]]      [1, 512, 8, 8]         2,048     
   BasicBlock-8       [[1, 512, 8, 8]]      [1, 512, 8, 8]           0       
AdaptiveAvgPool2D-1   [[1, 512, 8, 8]]      [1, 512, 1, 1]           0       
     Linear-1            [[1, 512]]             [1, 7]             3,591     
===============================================================================
Total params: 11,189,703
Trainable params: 11,180,103
Non-trainable params: 9,600
-------------------------------------------------------------------------------
Input size (MB): 0.59
Forward/backward pass size (MB): 60.81
Params size (MB): 42.69
Estimated Total Size (MB): 104.09
-------------------------------------------------------------------------------

启动训练...
[2025-03-21 18:17:52,261] [    INFO] train.py:54 - Epoch:1/5, batch:1, train_loss:[1.46126], acc_top1:[0.32812], acc_top5:[1.00000](0.43s)
[2025-03-21 18:17:52,420] [    INFO] train.py:54 - Epoch:1/5, batch:2, train_loss:[1.21415], acc_top1:[0.54688], acc_top5:[0.98438](0.16s)
[2025-03-21 18:17:52,651] [    INFO] train.py:54 - Epoch:1/5, batch:3, train_loss:[0.66362], acc_top1:[0.70312], acc_top5:[1.00000](0.23s)
[2025-03-21 18:17:52,881] [    INFO] train.py:54 - Epoch:1/5, batch:4, train_loss:[0.25195], acc_top1:[0.90625], acc_top5:[1.00000](0.23s)
[2025-03-21 18:17:53,117] [    INFO] train.py:54 - Epoch:1/5, batch:5, train_loss:[0.11261], acc_top1:[0.96875], acc_top5:[1.00000](0.24s)
[2025-03-21 18:17:53,356] [    INFO] train.py:54 - Epoch:1/5, batch:6, train_loss:[0.06333], acc_top1:[0.98438], acc_top5:[1.00000](0.24s)
[2025-03-21 18:17:53,502] [    INFO] train.py:54 - Epoch:1/5, batch:7, train_loss:[0.02077], acc_top1:[1.00000], acc_top5:[1.00000](0.15s)
[2025-03-21 18:17:53,643] [    INFO] train.py:65 - [validation] Epoch:1/5, val_loss:[0.00001], val_top1:[1.00000], val_top5:[1.00000]
[2025-03-21 18:17:54,450] [    INFO] train.py:88 - 已保存当前测试模型(epoch=1)为最优模型:Butterfly_resnet18_final
[2025-03-21 18:17:54,450] [    INFO] train.py:89 - 最优top1测试精度:1.00000 (epoch=1)
[2025-03-21 18:17:54,732] [    INFO] train.py:54 - Epoch:2/5, batch:8, train_loss:[0.00620], acc_top1:[1.00000], acc_top5:[1.00000](1.23s)
[2025-03-21 18:17:54,976] [    INFO] train.py:54 - Epoch:2/5, batch:9, train_loss:[0.00498], acc_top1:[1.00000], acc_top5:[1.00000](0.24s)
[2025-03-21 18:17:55,196] [    INFO] train.py:54 - Epoch:2/5, batch:10, train_loss:[0.00295], acc_top1:[1.00000], acc_top5:[1.00000](0.22s)
[2025-03-21 18:17:55,434] [    INFO] train.py:54 - Epoch:2/5, batch:11, train_loss:[0.00154], acc_top1:[1.00000], acc_top5:[1.00000](0.24s)
[2025-03-21 18:17:55,687] [    INFO] train.py:54 - Epoch:2/5, batch:12, train_loss:[0.00110], acc_top1:[1.00000], acc_top5:[1.00000](0.25s)
[2025-03-21 18:17:55,957] [    INFO] train.py:54 - Epoch:2/5, batch:13, train_loss:[0.00094], acc_top1:[1.00000], acc_top5:[1.00000](0.27s)
[2025-03-21 18:17:56,102] [    INFO] train.py:54 - Epoch:2/5, batch:14, train_loss:[0.00071], acc_top1:[1.00000], acc_top5:[1.00000](0.15s)
[2025-03-21 18:17:56,237] [    INFO] train.py:65 - [validation] Epoch:2/5, val_loss:[0.00005], val_top1:[1.00000], val_top5:[1.00000]
[2025-03-21 18:17:56,239] [    INFO] train.py:89 - 最优top1测试精度:1.00000 (epoch=1)
[2025-03-21 18:17:56,506] [    INFO] train.py:54 - Epoch:3/5, batch:15, train_loss:[0.00027], acc_top1:[1.00000], acc_top5:[1.00000](0.40s)
[2025-03-21 18:17:56,745] [    INFO] train.py:54 - Epoch:3/5, batch:16, train_loss:[0.00043], acc_top1:[1.00000], acc_top5:[1.00000](0.24s)
[2025-03-21 18:17:56,967] [    INFO] train.py:54 - Epoch:3/5, batch:17, train_loss:[0.00022], acc_top1:[1.00000], acc_top5:[1.00000](0.22s)
[2025-03-21 18:17:57,196] [    INFO] train.py:54 - Epoch:3/5, batch:18, train_loss:[0.00016], acc_top1:[1.00000], acc_top5:[1.00000](0.23s)
[2025-03-21 18:17:57,434] [    INFO] train.py:54 - Epoch:3/5, batch:19, train_loss:[0.00012], acc_top1:[1.00000], acc_top5:[1.00000](0.24s)
[2025-03-21 18:17:57,673] [    INFO] train.py:54 - Epoch:3/5, batch:20, train_loss:[0.00018], acc_top1:[1.00000], acc_top5:[1.00000](0.24s)
[2025-03-21 18:17:57,814] [    INFO] train.py:54 - Epoch:3/5, batch:21, train_loss:[0.00014], acc_top1:[1.00000], acc_top5:[1.00000](0.14s)
[2025-03-21 18:17:57,956] [    INFO] train.py:65 - [validation] Epoch:3/5, val_loss:[0.00006], val_top1:[1.00000], val_top5:[1.00000]
[2025-03-21 18:17:57,957] [    INFO] train.py:89 - 最优top1测试精度:1.00000 (epoch=1)
[2025-03-21 18:17:58,217] [    INFO] train.py:54 - Epoch:4/5, batch:22, train_loss:[0.00016], acc_top1:[1.00000], acc_top5:[1.00000](0.40s)
[2025-03-21 18:17:58,466] [    INFO] train.py:54 - Epoch:4/5, batch:23, train_loss:[0.00009], acc_top1:[1.00000], acc_top5:[1.00000](0.25s)
[2025-03-21 18:17:58,689] [    INFO] train.py:54 - Epoch:4/5, batch:24, train_loss:[0.00008], acc_top1:[1.00000], acc_top5:[1.00000](0.22s)
[2025-03-21 18:17:58,929] [    INFO] train.py:54 - Epoch:4/5, batch:25, train_loss:[0.00006], acc_top1:[1.00000], acc_top5:[1.00000](0.24s)
[2025-03-21 18:17:59,169] [    INFO] train.py:54 - Epoch:4/5, batch:26, train_loss:[0.00007], acc_top1:[1.00000], acc_top5:[1.00000](0.24s)
[2025-03-21 18:17:59,408] [    INFO] train.py:54 - Epoch:4/5, batch:27, train_loss:[0.00009], acc_top1:[1.00000], acc_top5:[1.00000](0.24s)
[2025-03-21 18:17:59,551] [    INFO] train.py:54 - Epoch:4/5, batch:28, train_loss:[0.00008], acc_top1:[1.00000], acc_top5:[1.00000](0.14s)
[2025-03-21 18:17:59,687] [    INFO] train.py:65 - [validation] Epoch:4/5, val_loss:[0.00007], val_top1:[1.00000], val_top5:[1.00000]
[2025-03-21 18:17:59,687] [    INFO] train.py:89 - 最优top1测试精度:1.00000 (epoch=1)
[2025-03-21 18:17:59,945] [    INFO] train.py:54 - Epoch:5/5, batch:29, train_loss:[0.00006], acc_top1:[1.00000], acc_top5:[1.00000](0.39s)
[2025-03-21 18:18:00,185] [    INFO] train.py:54 - Epoch:5/5, batch:30, train_loss:[0.00006], acc_top1:[1.00000], acc_top5:[1.00000](0.24s)
[2025-03-21 18:18:00,404] [    INFO] train.py:54 - Epoch:5/5, batch:31, train_loss:[0.00005], acc_top1:[1.00000], acc_top5:[1.00000](0.22s)
[2025-03-21 18:18:00,632] [    INFO] train.py:54 - Epoch:5/5, batch:32, train_loss:[0.00004], acc_top1:[1.00000], acc_top5:[1.00000](0.23s)
[2025-03-21 18:18:00,874] [    INFO] train.py:54 - Epoch:5/5, batch:33, train_loss:[0.00004], acc_top1:[1.00000], acc_top5:[1.00000](0.24s)
[2025-03-21 18:18:01,107] [    INFO] train.py:54 - Epoch:5/5, batch:34, train_loss:[0.00005], acc_top1:[1.00000], acc_top5:[1.00000](0.23s)
[2025-03-21 18:18:01,250] [    INFO] train.py:54 - Epoch:5/5, batch:35, train_loss:[0.00004], acc_top1:[1.00000], acc_top5:[1.00000](0.14s)
[2025-03-21 18:18:01,386] [    INFO] train.py:65 - [validation] Epoch:5/5, val_loss:[0.00004], val_top1:[1.00000], val_top5:[1.00000]
[2025-03-21 18:18:01,387] [    INFO] train.py:89 - 最优top1测试精度:1.00000 (epoch=1)
[2025-03-21 18:18:01,387] [    INFO] train.py:102 - 训练完成,总耗时9.55s, 已将其保存为:Butterfly_resnet18_final
训练完成,最终性能accuracy=1.00000(epoch=1), 总耗时9.55s
日志图已保存至:D:/Workspace/ExpResults\Project013Modularization\final_figures\training_log_Butterfly_resnet18.png
[2025-03-21 18:18:01,575] [    INFO] 2069346315.py:105 - Done.

有一点值得注意,在上面打印的训练日志中,我们同时输出了训练损失train_loss和验证损失val_loss,但是这两个损失的度量是有区别的,训练损失是仅仅只是针对当前批次(batch)的,而验证损失是针对整个验证集的。
训练完成后,建议将 ExpResults 文件夹的最终文件 copyExpDeployments 用于进行部署和应用。

5.5 离线测试

与在线测试不同的时候,离线测试通常需要调用本地已经训练好的模型来对数据进行预测,而不是通过网络请求。当然,这里通常指的也是对验证集的测试。但在一些比较特殊的情况下,也可以对测试集进行离线测试。整个测试过程除了载入已经训练好的模型参数外,其他部分与在线测试过程基本一致。

import os
import yaml
import sys
sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2')                   # 定义模块保存位置
# sys.path.append('/home/aistudio/work/teaching')                   # 定义模块保存位置
from utils.evaluate import eval
from utils.datasets import Dataset
import paddle
from paddle.io import DataLoader
from paddle.static import InputSpec
import warnings
warnings.filterwarnings("ignore")    # 调用warnings库,忽略警告信息,请谨慎使用,可能会在调试时掩盖潜在的问题,因此建议在开发和调试阶段保持警告开启。

# 1. 从本地磁盘读取 YAML 配置文件,获取全局配置参数,并初始化本地路径和参数









# 2. 使用数据集类获取批量数据





# 3. 设置输入样本的维度



# 4. 载入官方标准模型,若不存在则会自动进行下载,pretrained=True|False控制是否使用Imagenet预训练参数
# 使用getattr函数动态获取模型类,例如paddle.vision.models.args['architecture'],args['architecture']为模型名称,例如mobilenet_v2



# 5. 初始化模型并输出基本信息






# 6. 执行评估函数,并输出验证集样本的损失和精度
print('开始评估...')

print('\r[验证集] 损失:)

print('\r[测试集] 损失:)
开始评估...
[验证集] 损失: 0.00119, top1精度:1.00000, top5精度为:1.00000
[测试集] 损失: 0.00197, top1精度:1.00000, top5精度为:1.00000

【结果分析】

需要注意的是此处的精度与训练过程中输出的测试精度是不相同的,因为训练过程中使用的是验证集, 而这里的离线测试使用的是测试集.


【任务六】 模型的推理与预测

实验摘要: 模块化的设计方法有利于更好地调用模型,并进行推理和预测。

实验目的:

  1. 掌握模型推理与预测的方法
  2. 学会创建预测函数,并在主程序中调用该函数进行预测

任务六全局配置文件:config06.yaml

6.1 预测单张样本

import os
import cv2
import json
import yaml
import sys
import paddle
sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2')                   # 定义模块保存位置
# sys.path.append('/home/aistudio/work/teaching')                   # 定义模块保存位置
from utils.predict import predict
import matplotlib.pyplot as plt                  # 载入python的第三方图像处理库
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"        # 解决多线程报错问题

# 1. 从本地磁盘读取 YAML 配置文件,获取全局配置参数,并初始化本地路径和参数





# 2. 本地化路径,包括模型路径,待预测样本路径




# 3. 载入待预测样本及预先训练好的模型



# 4. 调用predict函数进行预测,并根据预测的类别ID,从dataset_info中获取对应的标签名称


# 5. 输出结果
# 5.1 输出预测结果
print(f"待测样本的预测类别为:{label_name_pred}")

# 5.2 显示待预测样本
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.imshow(image_rgb)
plt.show()

待测样本的预测类别为:machaon

6.2 批量预测

在实际应用中,我们通常需要对一批图像进行预测,并输出预测结果。在本项目中,我们使用 predict() 函数迭代地对一批图像进行预测,并输出预测结果。待预测图像的路径存储在 img_root_path 文件夹中,预测结果以 'results.txt' 命名,并保存在根目录中。

adm010.jpg admiral
adm017.jpg admiral
adm018.jpg admiral
mch009.jpg machaon
mch016.jpg machaon
mch023.jpg monarch_open
mnc017.jpg monarch_closed
mnc024.jpg monarch_closed
mnc030.jpg monarch_closed
pea016.jpg peacock
pea024.jpg peacock
swa004.jpg black_swallowtail
swa011.jpg zebra
swa018.jpg black_swallowtail
zeb019.jpg zebra
zeb023.jpg zebra
zeb026.jpg zebra

【任务七】输出实验结果并给出结果分析

在本项目中,我们分别使用了AlexNet, ResNet50, ResNet18, Mobilenetv2, VGG16五个模型对 十二生肖数据集(Zodiac)蝴蝶分类数据集(Butterfly) 进行训练,并获取相关的输出评估指标,所有模型的批次大小均设置为 batch_size=64。

7.1 十二生肖数据集(Zodiac)

7.1.1 实验结果

模型名称 Baseline模型 ImageNet预训练 learning_rate best_epoch top-1 acc top-5 acc loss test_top1_acc 单batch时间/总训练时间(s) 可训练参数/总参数
Zodiac_Alexnet Alexnet - - - - - - - -
Zodiac_Resnet18 ResNet18 - - - - - - - -
Zodiac_Resnet18_withoutPretrained ResNet18 - - - - - - - -
Zodiac_Resnet50 ResNet50 - - - - - - - -
Zodiac_Resnet50_withoutPretrained ResNet50 - - - - - - - -
Zodiac_VGG16 VGG16 - - - - - - - -
Zodiac_VGG16_withoutPretrained VGG16 0.001 45/50 0.55231 0.90000 0.03685 0.86429 14.75/8727.87 134,309,708/134,309,708
Zodiac_Mobilenetv2 Mobilenetv2 - - - - - - - -
Zodiac_Mobilenetv2_withoutPretrained Mobilenetv2 - - - - - - - -

PS: 以上表格,也可使用 7.1.2 中蝴蝶分类数据集的表格结构。

7.1.2 实验结果分析

从实验结果可以得到以下几个结论:
1.
2.

7.2 蝴蝶分类数据集(Butterfly)

7.2.1 实验结果

模型名称 Baseline ImageNet预训练 学习率 best_epoch 训练时间(s) 总参数量 val_top1_acc val_top5_acc test_top1_acc
Butterfly_Alexnet_withoutPretrained Alexnet 0.0001 40/50 117.39 57,032,519 0.34328 0.85075 0.29457
Butterfly_Alexnet Alexnet - - - - - - -
Butterfly_Resnet18_withoutPretrained ResNet18 - - - - - - -
Butterfly_ResNet18_0.01 ResNet18 - - - - - - -
Butterfly_ResNet18_0.001 ResNet18 - - - - - - -
Butterfly_ResNet18_0.0001 ResNet18 - - - - - - -
Butterfly_ResNet18_0.00001 ResNet18 - - - - - - -
Butterfly_Resnet50_withoutPretrained ResNet50 - - - - - - -
Butterfly_ResNet50 ResNet50 - - - - - - -
Butterfly_vgg16_withoutPretrained VGG16 - - - - - - -
Butterfly_vgg16 VGG16 - - - - - - -
Butterfly_mobilenet_v2_withoutPretrained Mobilenetv2 - - - - - - -
Butterfly_mobilenet_v2 Mobilenetv2 - - - - - - -

7.2.2 实验结果分析

从实验结果可以得到以下几个结论:
1.
2.

【项目013】基于模块化编程的迁移学习(学生版)教学版 | 发布版 | 返回首页

【任务一】 数据准备

【任务二】 全局参数设置

【任务三】 数据获取和数据预处理

【任务四】 基本函数的定义

【任务五】 模型训练与评估

【任务六】 模型的推理与预测

【任务七】输出实验结果并给出结果分析