【项目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 四个模型对 十二生肖数据集蝴蝶分类数据集 进行训练和评估。

本页面为代码的发布版,包含三个主要部分,分别是模型训练、模型离线评估和推理预测。自主学习请访问 学生版(部分关键代码)教学版(包含完整代码)。在本项目中,我们同样将使用函数式编程的方式,将代码模块化,并使用文件夹结构来管理代码和输出结果。为了便于管理代码和输出结果,我们将根据骨干模型和数据集设置不同的配置文件,其命名方式为 config_数据集名_模型名.yaml。例如,对于ResNet50模型在蝴蝶分类数据集上的配置文件命名为 config_Butterfly_resnet50.yaml。下面给出完整代码。

【任务一】 数据准备

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

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


【任务二】 模型训练

2.1 初始化训练参数

import os              # 系统库,用于获取系统信息和路径定义等
import yaml            # 导入yaml库,用于读取配置文件
import json            # 导入json库,用于读写配置文件
import sys             # 系统库,用于获取模块路径
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                                              # 导入百度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"                  # 解决多线程报错问题

#####################################################################################
# 1. 从本地磁盘读取 YAML 配置文件,获取全局配置参数
config_root_path = '../../Data/Projects/Project013Modularization/'
config_name = 'config_Zodiac_mobilenet_v2.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)

# 2. 本地环境配置
# 2.1 初始化模型名称,用于区分和管理当前任务
args['model_name'] = args['dataset_name'] + '_' + args['architecture']
if args['pretrained'] is False:
    args['model_name'] = args['model_name'] + '_without_pretrained'

# 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 数据集路径
dataset_root_path = args['dataset_root_path'] = os.path.join(args['dataset_root_path'], args['dataset_name']) # 根据数据集的名称重写数据集根路径

# 2.3.2 初始化结果路径(用于开发环节中的保存训练结果、微调和验证)
result_root_path = args['result_root_path'] = os.path.join(args['result_root_path'], args['project_name']) # 根据数据集的名称重写结果根路径
args['results_path']['final_models_path']  = os.path.join(result_root_path, 'final_models')                # 最终用于部署和推理的模型
args['results_path']['final_figures_path'] = os.path.join(result_root_path, 'final_figures')               # 训练过程中loss和accuracy曲线图
args['results_path']['checkpoint_models_path'] = os.path.join(result_root_path, 'checkpoint_models')       # 带模型状态的微调模型
args['results_path']['logs_path'] = os.path.join(result_root_path, 'logs')                                 # 训练过程日志

# 2.3.3 部署路径(用于推理和预测)
deployment_root_path = args['deployment_root_path'] = os.path.join(args['deployment_root_path'], args['project_name'])
args['deployments_path']['deployment_checkpoint_model_path'] = os.path.join(deployment_root_path, 'checkpoint_models')
args['deployments_path']['deployment_final_model_path'] = os.path.join(deployment_root_path, 'final_models')
args['deployments_path']['deployment_final_figures_path'] = os.path.join(deployment_root_path, 'final_figures')
args['deployments_path']['deployment_logs_path'] = os.path.join(deployment_root_path, 'logs')
args['deployments_path']['deployment_pretrained_model_path'] = os.path.join(deployment_root_path, 'pretrained_models')

# 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 = json.loads(open(os.path.join(args['dataset_root_path'] , 'dataset_info.json'), 'r', encoding='utf-8').read())  
fields_dataset = ['num_trainval', 'num_train', 'num_val', 'num_test', 'class_dim', 'label_dict']
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对象,配置日志输出路径和模型名称

# 3. 将生成的完整配置文件(按需求)保存成部署配置文件
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}

config_depoly_path = os.path.join(config_root_path, config_name.split('.')[0] + '_deploy.yaml')
with open(config_depoly_path, 'w') as f:
    yaml.dump(args_deploy, f, default_flow_style=False, sort_keys=False)

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

2.2 训练主函数

# 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, mode='trainval')
dataset_train = Dataset(dataset_root_path, args=args, mode='train')
dataset_val = Dataset(dataset_root_path, args=args, mode='val')
trainval_reader = DataLoader(dataset_trainval, batch_size=args['batch_size'], shuffle=False)
train_reader = DataLoader(dataset_train, batch_size=args['batch_size'], shuffle=False)
val_reader = DataLoader(dataset_val, batch_size=args['batch_size'], shuffle=False)

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

#####################################################################################
# 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 = getattr(paddle.vision.models, args['architecture'])
network = model_builder(num_classes=args['class_dim'], pretrained=args['pretrained'])
model = paddle.Model(network, input_spec, label_spec) 

# 3.3 设置学习率、优化器、损失函数和评价指标
lr = learning_rate_setting(args=args)
optimizer = optimizer_setting(model, lr, args['learning_strategy'])
model.prepare(optimizer,
        paddle.nn.CrossEntropyLoss(),
        paddle.metric.Accuracy(topk=(1,5)))

#####################################################################################
# 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 = json.dumps(args, indent=4, ensure_ascii=False, sort_keys=False, separators=(',', ':'))   # 格式化字典格式的参数列表
logger.info(data)

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

# 4.4 启动训练过程
visualization_log = train(model, args=args, train_reader=train_reader, val_reader=val_reader, logger=logger)

# 4.5 输出训练过程图
draw_process(visualization_log, figure_path=args['results_path']['final_figures_path'], model_name=args['model_name'], isValid=isValid)
logger.info('Done.')
[2025-03-23 14:32:55,932] [    INFO] 3545894799.py:3 - 载入 Imagenet-mobilenet_v2 预训练模型完毕,开始微调训练(fine-tune)。
[2025-03-23 14:32:56,081] [    INFO] 3545894799.py:48 - 系统基本信息:
当前学习率策略为: Adam + CosineAnnealingDecay, 初始学习率为:0.01
[2025-03-23 14:32:57,124] [    INFO] 3545894799.py:50 - {
    "操作系统":"Windows-10-10.0.26100-SP0",
    "Paddle":"3.0.0-beta1",
    "CPU":"13th Gen Intel(R) Core(TM) i7-13700KF",
    "内存":"13.77G/31.85G (43.20%)",
    "GPU":"NVIDIA GeForce RTX 4080 SUPER 5.74G/15.99G (0.36%)",
    "CUDA/cuDNN":"12.3 / 9.0.0"
}
[2025-03-23 14:32:57,126] [    INFO] 3545894799.py:54 - {
    "project_name":"Project013Modularization",
    "dataset_name":"Zodiac",
    "architecture":"mobilenet_v2",
    "model_name":"Zodiac_mobilenet_v2",
    "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":7840,
    "num_train":7190,
    "num_val":650,
    "num_test":660,
    "total_epoch":50,
    "batch_size":64,
    "class_dim":12,
    "log_interval":10,
    "eval_interval":1,
    "pretrained":true,
    "useGPU":true,
    "dataset_root_path":"D:/workspace/ExpDatasets\\Zodiac",
    "result_root_path":"D:/Workspace/ExpResults\\Project013Modularization",
    "deployment_root_path":"D:/Workspace/ExpDeployments\\Project013Modularization",
    "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.01,
        "momentum":0.9,
        "Piecewise_boundaries":[
            20,
            40,
            45
        ],
        "Piecewise_values":[
            0.0001,
            1e-05,
            1e-06,
            1e-07
        ],
        "Exponential_gamma":0.9,
        "Polynomial_decay_steps":10,
        "verbose":false
    },
    "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"
    },
    "deployments_path":{
        "deployment_logs_path":"D:/Workspace/ExpDeployments\\Project013Modularization\\logs",
        "deployment_final_figures_path":"D:/Workspace/ExpDeployments\\Project013Modularization\\final_figures",
        "deployment_checkpoint_model_path":"D:/Workspace/ExpDeployments\\Project013Modularization\\checkpoint_models",
        "deployment_final_model_path":"D:/Workspace/ExpDeployments\\Project013Modularization\\final_models",
        "deployment_pretrained_model_path":"D:/Workspace/ExpDeployments\\Project013Modularization\\pretrained_models"
    },
    "label_dict":{
        "0":"dog",
        "1":"dragon",
        "2":"goat",
        "3":"horse",
        "4":"monkey",
        "5":"ox",
        "6":"pig",
        "7":"rabbit",
...
    }
}
[2025-03-23 14:32:57,126] [    INFO] 3545894799.py:57 - 模型参数信息:
[2025-03-23 14:32:57,139] [    INFO] 3545894799.py:58 - {'total_params': 2273356, 'trainable_params': 2239244}
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
-------------------------------------------------------------------------------
   Layer (type)         Input Shape          Output Shape         Param #    
===============================================================================
     Conv2D-53       [[1, 3, 227, 227]]   [1, 32, 114, 114]         864      
  BatchNorm2D-53    [[1, 32, 114, 114]]   [1, 32, 114, 114]         128      
     ReLU6-36       [[1, 32, 114, 114]]   [1, 32, 114, 114]          0       
     Conv2D-54      [[1, 32, 114, 114]]   [1, 32, 114, 114]         288      
  BatchNorm2D-54    [[1, 32, 114, 114]]   [1, 32, 114, 114]         128      
     ReLU6-37       [[1, 32, 114, 114]]   [1, 32, 114, 114]          0       
     Conv2D-55      [[1, 32, 114, 114]]   [1, 16, 114, 114]         512      
  BatchNorm2D-55    [[1, 16, 114, 114]]   [1, 16, 114, 114]         64       
InvertedResidual-18 [[1, 32, 114, 114]]   [1, 16, 114, 114]          0       
     Conv2D-56      [[1, 16, 114, 114]]   [1, 96, 114, 114]        1,536     
  BatchNorm2D-56    [[1, 96, 114, 114]]   [1, 96, 114, 114]         384      
     ReLU6-38       [[1, 96, 114, 114]]   [1, 96, 114, 114]          0       
     Conv2D-57      [[1, 96, 114, 114]]    [1, 96, 57, 57]          864      
  BatchNorm2D-57     [[1, 96, 57, 57]]     [1, 96, 57, 57]          384      
     ReLU6-39        [[1, 96, 57, 57]]     [1, 96, 57, 57]           0       
     Conv2D-58       [[1, 96, 57, 57]]     [1, 24, 57, 57]         2,304     
  BatchNorm2D-58     [[1, 24, 57, 57]]     [1, 24, 57, 57]          96       
InvertedResidual-19 [[1, 16, 114, 114]]    [1, 24, 57, 57]           0       
     Conv2D-59       [[1, 24, 57, 57]]     [1, 144, 57, 57]        3,456     
  BatchNorm2D-59     [[1, 144, 57, 57]]    [1, 144, 57, 57]         576      
     ReLU6-40        [[1, 144, 57, 57]]    [1, 144, 57, 57]          0       
     Conv2D-60       [[1, 144, 57, 57]]    [1, 144, 57, 57]        1,296     
  BatchNorm2D-60     [[1, 144, 57, 57]]    [1, 144, 57, 57]         576      
     ReLU6-41        [[1, 144, 57, 57]]    [1, 144, 57, 57]          0       
     Conv2D-61       [[1, 144, 57, 57]]    [1, 24, 57, 57]         3,456     
  BatchNorm2D-61     [[1, 24, 57, 57]]     [1, 24, 57, 57]          96       
InvertedResidual-20  [[1, 24, 57, 57]]     [1, 24, 57, 57]           0       
     Conv2D-62       [[1, 24, 57, 57]]     [1, 144, 57, 57]        3,456     
  BatchNorm2D-62     [[1, 144, 57, 57]]    [1, 144, 57, 57]         576      
     ReLU6-42        [[1, 144, 57, 57]]    [1, 144, 57, 57]          0       
     Conv2D-63       [[1, 144, 57, 57]]    [1, 144, 29, 29]        1,296     
  BatchNorm2D-63     [[1, 144, 29, 29]]    [1, 144, 29, 29]         576      
     ReLU6-43        [[1, 144, 29, 29]]    [1, 144, 29, 29]          0       
     Conv2D-64       [[1, 144, 29, 29]]    [1, 32, 29, 29]         4,608     
  BatchNorm2D-64     [[1, 32, 29, 29]]     [1, 32, 29, 29]          128      
InvertedResidual-21  [[1, 24, 57, 57]]     [1, 32, 29, 29]           0       
     Conv2D-65       [[1, 32, 29, 29]]     [1, 192, 29, 29]        6,144     
  BatchNorm2D-65     [[1, 192, 29, 29]]    [1, 192, 29, 29]         768      
     ReLU6-44        [[1, 192, 29, 29]]    [1, 192, 29, 29]          0       
     Conv2D-66       [[1, 192, 29, 29]]    [1, 192, 29, 29]        1,728     
  BatchNorm2D-66     [[1, 192, 29, 29]]    [1, 192, 29, 29]         768      
     ReLU6-45        [[1, 192, 29, 29]]    [1, 192, 29, 29]          0       
     Conv2D-67       [[1, 192, 29, 29]]    [1, 32, 29, 29]         6,144     
  BatchNorm2D-67     [[1, 32, 29, 29]]     [1, 32, 29, 29]          128      
InvertedResidual-22  [[1, 32, 29, 29]]     [1, 32, 29, 29]           0       
     Conv2D-68       [[1, 32, 29, 29]]     [1, 192, 29, 29]        6,144     
  BatchNorm2D-68     [[1, 192, 29, 29]]    [1, 192, 29, 29]         768      
     ReLU6-46        [[1, 192, 29, 29]]    [1, 192, 29, 29]          0       
     Conv2D-69       [[1, 192, 29, 29]]    [1, 192, 29, 29]        1,728     
  BatchNorm2D-69     [[1, 192, 29, 29]]    [1, 192, 29, 29]         768      
     ReLU6-47        [[1, 192, 29, 29]]    [1, 192, 29, 29]          0       
     Conv2D-70       [[1, 192, 29, 29]]    [1, 32, 29, 29]         6,144     
  BatchNorm2D-70     [[1, 32, 29, 29]]     [1, 32, 29, 29]          128      
InvertedResidual-23  [[1, 32, 29, 29]]     [1, 32, 29, 29]           0       
     Conv2D-71       [[1, 32, 29, 29]]     [1, 192, 29, 29]        6,144     
  BatchNorm2D-71     [[1, 192, 29, 29]]    [1, 192, 29, 29]         768      
     ReLU6-48        [[1, 192, 29, 29]]    [1, 192, 29, 29]          0       
     Conv2D-72       [[1, 192, 29, 29]]    [1, 192, 15, 15]        1,728     
  BatchNorm2D-72     [[1, 192, 15, 15]]    [1, 192, 15, 15]         768      
     ReLU6-49        [[1, 192, 15, 15]]    [1, 192, 15, 15]          0       
     Conv2D-73       [[1, 192, 15, 15]]    [1, 64, 15, 15]        12,288     
  BatchNorm2D-73     [[1, 64, 15, 15]]     [1, 64, 15, 15]          256      
InvertedResidual-24  [[1, 32, 29, 29]]     [1, 64, 15, 15]           0       
     Conv2D-74       [[1, 64, 15, 15]]     [1, 384, 15, 15]       24,576     
  BatchNorm2D-74     [[1, 384, 15, 15]]    [1, 384, 15, 15]        1,536     
     ReLU6-50        [[1, 384, 15, 15]]    [1, 384, 15, 15]          0       
     Conv2D-75       [[1, 384, 15, 15]]    [1, 384, 15, 15]        3,456     
  BatchNorm2D-75     [[1, 384, 15, 15]]    [1, 384, 15, 15]        1,536     
     ReLU6-51        [[1, 384, 15, 15]]    [1, 384, 15, 15]          0       
     Conv2D-76       [[1, 384, 15, 15]]    [1, 64, 15, 15]        24,576     
  BatchNorm2D-76     [[1, 64, 15, 15]]     [1, 64, 15, 15]          256      
InvertedResidual-25  [[1, 64, 15, 15]]     [1, 64, 15, 15]           0       
     Conv2D-77       [[1, 64, 15, 15]]     [1, 384, 15, 15]       24,576     
  BatchNorm2D-77     [[1, 384, 15, 15]]    [1, 384, 15, 15]        1,536     
     ReLU6-52        [[1, 384, 15, 15]]    [1, 384, 15, 15]          0       
     Conv2D-78       [[1, 384, 15, 15]]    [1, 384, 15, 15]        3,456     
  BatchNorm2D-78     [[1, 384, 15, 15]]    [1, 384, 15, 15]        1,536     
     ReLU6-53        [[1, 384, 15, 15]]    [1, 384, 15, 15]          0       
     Conv2D-79       [[1, 384, 15, 15]]    [1, 64, 15, 15]        24,576     
  BatchNorm2D-79     [[1, 64, 15, 15]]     [1, 64, 15, 15]          256      
InvertedResidual-26  [[1, 64, 15, 15]]     [1, 64, 15, 15]           0       
     Conv2D-80       [[1, 64, 15, 15]]     [1, 384, 15, 15]       24,576     
  BatchNorm2D-80     [[1, 384, 15, 15]]    [1, 384, 15, 15]        1,536     
     ReLU6-54        [[1, 384, 15, 15]]    [1, 384, 15, 15]          0       
     Conv2D-81       [[1, 384, 15, 15]]    [1, 384, 15, 15]        3,456     
  BatchNorm2D-81     [[1, 384, 15, 15]]    [1, 384, 15, 15]        1,536     
     ReLU6-55        [[1, 384, 15, 15]]    [1, 384, 15, 15]          0       
     Conv2D-82       [[1, 384, 15, 15]]    [1, 64, 15, 15]        24,576     
  BatchNorm2D-82     [[1, 64, 15, 15]]     [1, 64, 15, 15]          256      
InvertedResidual-27  [[1, 64, 15, 15]]     [1, 64, 15, 15]           0       
     Conv2D-83       [[1, 64, 15, 15]]     [1, 384, 15, 15]       24,576     
  BatchNorm2D-83     [[1, 384, 15, 15]]    [1, 384, 15, 15]        1,536     
...
Estimated Total Size (MB): 174.34
-------------------------------------------------------------------------------

启动训练...
[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: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.

【任务三】 模型的离线评估

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 配置文件,获取全局配置参数,并初始化本地路径和参数
config_path = '../../Data/Projects/Project013Modularization/config_Butterfly_resnet18.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']
if args['pretrained'] is False:
    args['model_name'] = args['model_name'] + '_without_pretrained'
dataset_root_path = os.path.join(args['dataset_root_path'], args['dataset_name']) # 根据数据集的名称拼接当前任务数据集的根目录路径
checkpoint_models_path = os.path.join(args['results_root_path'], args['project_name'], 'checkpoint_models', args['model_name'] + '_0-01_final')

# 2. 使用数据集类获取批量数据
dataset_val = Dataset(dataset_root_path, args=args, mode='val')
val_reader = DataLoader(dataset_val, batch_size=args['batch_size'], shuffle=False, drop_last=False)
dataset_test = Dataset(dataset_root_path, args=args, mode='test')
test_reader = DataLoader(dataset_test, batch_size=args['batch_size'], shuffle=False, drop_last=False)

# 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) 
# TODO: 载入预先训练好的模型参数
model.load(checkpoint_models_path)                                 # 载入调优模型的参数
model.prepare(loss=paddle.nn.CrossEntropyLoss(),                        # 设置loss
                metrics=paddle.metric.Accuracy(topk=(1,5)))               # 设置评价指标

# 6. 执行评估函数,并输出验证集样本的损失和精度
print('开始评估...')
avg_loss, avg_acc_top1, avg_acc_top5 = eval(model, val_reader, verbose=1)
print('\r[验证集] 损失: {:.5f}, top1精度:{:.5f}, top5精度为:{:.5f}\n'.format(avg_loss, avg_acc_top1, avg_acc_top5), end='')
avg_loss, avg_acc_top1, avg_acc_top5 = eval(model, test_reader, verbose=1)
print('\r[测试集] 损失: {:.5f}, top1精度:{:.5f}, top5精度为:{:.5f}\n'.format(avg_loss, avg_acc_top1, avg_acc_top5), end='')
开始评估...
[验证集] 损失: 0.00119, top1精度:1.00000, top5精度为:1.00000
[测试集] 损失: 0.00197, top1精度:1.00000, top5精度为:1.00000

【结果分析】

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


【任务四】 推理和预测

4.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 配置文件,获取全局配置参数,并初始化本地路径和参数
config_path = '../../Data/Projects/Project013Modularization/config_Butterfly_resnet18_deploy.yaml'
with open(config_path, 'r', encoding='utf-8') as yaml_file:
    args = yaml.safe_load(yaml_file)

# 2. 从数据集的dataset_info文件中,获取图像标签信息
json_dataset_info = os.path.join(args['dataset_root_path'], args['dataset_name'], 'dataset_info.json')
with open(json_dataset_info, 'r') as f_info:
    dataset_info = json.load(f_info)

# 3. 本地化路径,包括模型路径,待预测样本路径
model_name = args['model_name']
deployment_model_path  = args['deployments_path']['deployment_final_model_path'] # 定义最终模型路径
img_path = 'D:/WorkSpace/DeepLearning/WebsiteV2/Data/Projects/Project013Modularization/Zodiac/images/dog2.jpg'

# 4. 载入待预测样本及预先训练好的模型
image = cv2.imread(img_path, 1)
model = paddle.jit.load(deployment_model_path)

# 5. 调用predict函数进行预测,并根据预测的类别ID,从dataset_info中获取对应的标签名称
pred_id = predict(model, image) 
label_name_pred = dataset_info['label_dict'][str(pred_id)]

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

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

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

6.2 批量预测

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

import os
import cv2
import json
import yaml
import sys
import codecs
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 配置文件,获取全局配置参数,并初始化本地路径和参数
config_path = '../../Data/Projects/Project013Modularization/config06_deploy.yaml'
# config_path = '/home/aistudio/work/teaching/Configs/config06_deploy.yaml'
with open(config_path, 'r', encoding='utf-8') as yaml_file:
    args = yaml.safe_load(yaml_file)

# 2. 初始化本地化路径,包括模型路径,待预测样本路径
deployment_model_path  = os.path.join(args['deployments_path']['deployment_final_model_path'], args['model_name'] + '_final')     # 定义最终模型路径
img_root_path = 'D:/WorkSpace/DeepLearning/WebsiteV2/Data/Projects/Project013Modularization/Butterfly/images'
result_path = 'D:/WorkSpace/DeepLearning/WebsiteV2/Data/Projects/Project013Modularization/Butterfly/results_pred.txt'
# img_root_path = '/home/aistudio/work/teaching/Data/Butterfly/images'
# result_path = '/home/aistudio/work/ExpResults/Project013Modularization/results_pred.txt'

model = paddle.jit.load(deployment_model_path)

img_list = os.listdir(img_root_path)
with codecs.open(result_path, 'a', 'utf-8') as f:
    for img_path in img_list:
        image = cv2.imread(os.path.join(img_root_path, img_path), 1)
        pred_id = predict(model, image) 
        label_name_pred = args['label_dict'][str(pred_id)]
        f.write(f"{img_path} {label_name_pred}\n")
        print(img_path, label_name_pred)
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 0.001 3/10 0.48769 0.87231 0.02458 0.48485 9.06/1177.53 11,172,042/11,191,242
Zodiac_Resnet18 ResNet18 0.01 7/10 0.83385 0.97538 0.01093 0.81364 10.20/1389.07 11,172,042/11,191,242
Zodiac_Resnet18_withoutPretrained ResNet18 0.01 50/50 0.60769 0.90769 0.02072 0.60909 12.28/7220.98 11,172,042/11,191,242
Zodiac_Resnet50 ResNet50 0.01 8/10 0.94615 0.99846 0.00343 0.94545 11.67/1455.60 23,479,500/23,585,740
Zodiac_Resnet50_withoutPretrained ResNet50 0.01 45/50 0.54154 0.89538 0.04756 0.55152 11.24/7868.96 23,479,500/23,585,740
Zodiac_VGG16 VGG16 0.001 8/10 0.89385 0.98615 0.00581 0.86515 16.69/2243.58 134,309,708/134,309,708
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 0.001 9/10 0.92769 0.99692 0.00425 0.91212 10.18/1294.77 2,205,132/2,273,356
Zodiac_Mobilenetv2_withoutPretrained Mobilenetv2 0.001 44/50 0.52615 0.87077 0.03536 0.51667 10.44/5838.82s 2,205,132/2,273,356

7.1.2 实验结果分析

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

  1. 基于Imagenet预训练模型的微调训练(finetune)比从头训练收敛更快,且更容易获得较好的性能
  2. VGG16和Alexnet模型因为参数较多,且没有类似残差结构的辅助,需要更低的学习率来进行训练
  3. 参数的数量训练时间有较大的影响
  4. 从损失loss和精确度来看,没有使用Imagenet预训练参数的模型的ResNet18还没有完全收敛。也证明了迁移学习在计算机视觉任务中的作用明显
  5. 同一种模型的参数数量是固定的,主要由其卷积层、全连接层等内部结构决定
  6. 由于任务并不复杂,因此四种模型的top5精度都比较高,说明CNN模型的判别能力还是很不错的
  7. 总体来看,ResNet50的性能是最好的,要优于ResNet18, VGG16和Mobilenet v2。一方面体现了残差网络的强大性能,另一方面也说明更深的模型对于识别精度是有积极意义的(Resnet50是50层,而Resnet18只有18层,VGG16也只有16层)。此外,Mobilenetv2也具有相当不错的性能,并且参数量只有ResNet50模型的10%,使其更适合部署在移动设备上。

值得注意的是,由于数据集比较简单,参数的初始化以及随机梯度下降算法对于训练的过程影响比较大。即每次训练的过程(甚至结果)可能会有一定的波动,但较好的超参数设置仍然有助于获得较好的性能。

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 0.0001 22/50 156.79 57,032,519 0.97015 1.00000 0.93798
Butterfly_Resnet18_withoutPretrained ResNet18 0.0001 28/50 98.96 11,189,703 0.49254 0.91045 0.42636
Butterfly_ResNet18_0.01 ResNet18 0.01 8/50 94.84 11,189,703 0.25373 0.80597 0.23256
Butterfly_ResNet18_0.001 ResNet18 0.001 8/50 96.38 11,189,703 0.68657 0.97015 0.64341
Butterfly_ResNet18_0.0001 ResNet18 0.0001 9/50 95.31 11,189,703 0.82090 1.00000 0.78295
Butterfly_ResNet18_0.00001 ResNet18 0.00001 47/50 112.57 11,189,703 0.88060 1.00000 0.73643
Butterfly_Resnet50_withoutPretrained ResNet50 0.0001 29/50 130.14 23,575,495 0.43284 0.88060 0.28682
Butterfly_ResNet50 ResNet50 0.0001 14/50 136.09 23,575,495 0.94030 0.98507 0.96124
Butterfly_vgg16_withoutPretrained VGG16 0.0001 49/50 324.84 134,289,223 0.59701 0.95522 0.48062
Butterfly_vgg16 VGG16 0.0001 34/50 362.43 134,289,223 0.95522 1.00000 0.96124
Butterfly_mobilenet_v2_withoutPretrained Mobilenetv2 0.0001 39/50 101.48 2,266,951 0.49254 0.94030 0.37984
Butterfly_mobilenet_v2 Mobilenetv2 0.0001 11/50 96.57 2,266,951 0.92537 0.97015 0.91473

7.2.2 实验结果分析

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

  1. 基于Imagenet预训练的模型在进行微调训练(finetune)时比从头训练收敛更快,且更容易获得较好的性能
  2. Alexnet模型和VGG模型由于参数较多,且没有类似残差结构的辅助,所以训练难度较大,需要更多的周期来进行训练
  3. 从测试准确度来看,VGG16具有更好的性能,这和它较多的参数有一定关系,但同样参数较多的AlexNet的性能却不如VGG16,这表明参数数量并不是决定模型性能的唯一因素,模型的结构设计同样重要(当然,VGG确实也比AlexNet具有更多参数);另一方面,参数只有AlexNet一半的ResNet50的性能却比AlexNet要好,这进一步证明模型结构设计对模型性能的影响比参数数量要更大。
  4. 参数的数量对训练时间有较大的影响,但由于系统的运行存在较多的不确定性,所以总训练时间并不是固定的。在实际中我们可以计算最优模型获得的时间,以及最优模型获得时的,每个批次/周期的平均时间。
  5. 初始学习率对于模型的最终性能也具有较大影响,初始学习率过大,可能导致模型在训练过程中出现震荡,无法收敛;初始学习率过小,可能导致模型收敛速度过慢,训练时间过长,且有陷入局部最优的风险。
  6. 从损失loss和精确度来看,没有使用Imagenet预训练参数的模型的ResNet18可能还没有完全收敛。这也证明了迁移学习在计算机视觉任务中的作用是非常明显的
  7. 同一种模型的参数数量是固定的,主要由其卷积层、全连接层等内部结构决定
  8. 由于任务并不复杂,因此三种模型的top5精度都比较高,说明CNN模型的判别能力还是很不错的
  9. 从训练过程来看几个模型在使用预训练参数后,虽然损失loss下降速度都明显加快,但在达到一定程度后,loss反而会上升,精确度也有一定的程度的下降,这说明固定学习率上模型已经无法再进行收敛,动态学习率或者阶梯递减学习率应该能获得更稳定的收敛效果(有兴趣的同学可以进行尝试)。
  10. 总体来看,几个模型的性能差距不大,这主要是因为数据集比较简单。但总体上,使用预训练模型进行微调训练(finetune)的ResNet50和vgg16性能最好,因为在相似性能下,这两个模型的参数数量较多,但他们的训练时间也是最长的。此外,Mobilenetv2也具有相当不错的性能,并且参数量只有ResNet50模型的10%,AlexNet的1/30,使其更适合部署在移动设备上。远低于AlexNet,特别是MobileNet_v2的参数只有AlexNet的1/30,因此建议在实际应用中,若关注模型大小,可以优先考虑使用该预训练模型。

值得注意的是,由于数据集比较简单,参数的初始化以及随机梯度下降算法对于训练的过程影响比较大,并且以上实验都使用了相同的优化算法Adma。因此,每次训练的过程(甚至结果)可能会有一定的波动,但较好的超参数设置仍然有助于获得较好的性能。

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

【任务二】 模型训练

【任务四】 推理和预测

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