作者:欧新宇(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个任务,每个任务包含若干个知识点。
datasets.py
, getLogging.py
, evaluate.py
,train.py
,...等模块的创建,并学会调用这些模块来完成模型训练、评估和推理。下面我们将在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节 数据准备。
以下给出这两个数据集的下载地址和数据列表生成代码,有兴趣的同学可以阅读这个两个数据集生成列表的源代码,了解数据列表生成的细节。需要重新生成时,注意根据操作系统环境和实际的文件夹配置修改源代码中的路径。
/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
对于十二生肖数据集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.
在生成图像列表的过程中,应注意以下几点:
train.txt
, trainval.txt
, val.txt
, test.txt
四个数据子集列表文件dataset_info.json
数据集信息文件绝对路径
# 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个。
实验摘要: 对于一个项目而言,我们总是期望能够通过修改配置文件的设置而不是去修改源代码来完成本地化的设置。因此,全局参数的设置通常都是项目开发中的重要环节。一般来说,全局超参数的设置通常包括系统参数,训练参数,数据集参数,网络参数,可视化参数,日志参数等。但项目创立之初,我们可能并不一定能够确定所有的超参数,因此,在项目初期,我们通常只需要定义一些基本的参数,然后在项目的开发过程中,根据需要,逐步添加新的配置项。此外,对于一个主函数来说,除了这些全局参数的设置外,还需要定义一些基本的依赖导入和本地环境的配置。
实验目的:
正如实验摘要所介绍的,依赖库的导入是每个项目、每个模块所必须的。对于各个功能模块来说,只需要导入其所需的库即可,不需要考虑其他模块的依赖;同样,对于主函数来说,也只需要导入其所需的库即可。以下为训练主函数的依赖库的导入。
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" # 解决多线程报错问题
常用的外部配置文件格式有: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.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))
在项目结束后,通常要生成一份部署配置文件,用于部署环境中的参数配置。部署配置文件通常包含用户使用模型所需要的必要参数和路径,这些参数可以通过训练配置文件动态生成。以下给出一些必要配置文件的修改建议:
# 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'
实验摘要: 不管是哪一类的人工智能项目,数据的获取和数据预处理都至关重要。本项目以图片分类为例,来讲解如何通过函数化编程实现数据集类的定义与数据预处理。
实验目的:
任务三全局配置文件:config03.yaml
在Paddle 2.0+ 中,对数据的获取包含两个步骤,一是创建数据集类,二是创建数据迭代读取器。
paddle.io
来进行构造。数据集类的主要功能是从本地读取数据列表,并对列表中的样本进行数据预处理。全新的 paddle.vision.transforms
可以轻松的实现样本的多种预处理功能,而不用手动去编写数据预处理的函数,这大简化了代码的复杂性。对于用于训练集(train)和训练验证集(trainval),我们可以按照一定的比例对样本进行数据增广,并进行标准的数据预处理;而对于验证集(val)和测试集(test)则只需要进行标准的数据预处理即可。此外,我们还可以使用该类来从指定的数据列表中获取未经预处理的原始样本。paddle.io.DataLoader
是创建数据迭代读取器的有效工具包。它可以将读入的数据进行 batch 划分,并确定是否进行随机打乱和是否丢弃最后的冗余样本。 一般来说,对于训练样本(包括train和trainval),我们需要进行随机打乱,让每次输入到网络中的样本处于不同的组合形式,防止过拟合的产生;对于验证数据(val)和测试数据(test),由于每次测试都需要对所有样本进行一次完整的遍历,并计算最终的平均值,因此是否进行打乱,并不影响最终的结果。另一方面,由于在最终输出的loss和accuracy的平均值时,会事先进行一次批内的平均,因此如果最后一个batch的数据并不能构成完整的一批,即实际样本数量小于batch_size,会导致最终计算精度产生一定的偏差。所以,当样本数较多的时候,可以考虑在训练时丢弃最后一个batch的数据。但值得注意的是,验证集和测试集不能进行丢弃,否则会有一部分样本无法进行测试。通常情况下数据集类是通用的,换句话说,我们只需要修改数据集的读取路径,就可以在不同的数据集上进行测试,这进一步体现了模块化设计的优势。在下面的代码中,我们将创建一个通用的数据集类 Dataset
,然后再利用内置的 paddle.io.DataLoader
来创建十二生肖数据集 Zodiac
和蝴蝶分类数据集 Butterfly
的数据迭代读取器。值得注意的是,为了进一步体现模块化设计的重用性,在这里所创建的数据集类将保存到硬盘上,创建数据迭代处理器的时候我们将通过外部导入的方式来获取数据集类。这意味着你无需像其他项目案例(例如:【项目027】基于迁移学习的蝴蝶分类)一样,事先运行数据集类的定义函数。
数据集模块的调用方法
源代码:datasets.py
调用方法:
sys.path.append('D:/WorkSpace/DeepLearning/WebsiteV2') # 定义模块保存位置
from utils.datasets import Dataset # 导入数据集模块
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)
为了调用预先写好的数据集类模块,首先需要导入该模块。模块的调用通常需要指定数据集的路径和可能的配置参数。在下面的代码中,我们先使用 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]。不过,这并不影响图像的显示,因为图像数据已经经过归一化处理。
在实际应用中,为了便于测试编写的类是否能正常运行,我们可以将上述代码整合到一起,形成完整的数据集类。在将测试代码进行整合的时候,我们可以使用 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__":
# 测试代码
模块化的数据集类设计最好的体现之一就是可以通过更换超参数来轻松地更换数据集。在之前的代码中,我们已经实现了这一功能。因此,如果需要将数据集从 Zodiac
更换为 Butterfly
,只需要在全局参数定义中,将 args['dataset_name']
的值更改为 Butterfly
即可,其他部分的代码不需要做任何修改。此外,为了进一步简化主函数,我们还可以将全局参数定义的代码块移到配置文件(config03.yaml)中。执行主函数的时候,直接进行导入即可。具体代码如下:
实验摘要: 蝴蝶种类识别是一个多分类问题,我们通过卷积神经网络来完成。这部分通过PaddlePaddle手动构造一个Alexnet卷积神经的网络来实现识别功能。本实验主要实现训练前的一些准备工作,包括:全局参数定义,数据集载入,数据预处理,可视化函数定义,日志输出函数定义。
实验目的:
任务四全局配置文件:config04.yaml
深度学习的项目通常都比较复杂,既涉及到编程技巧也涉及到设备环境,因此,在训练过程中,我们通常需要记录一些信息,以便后续的调试和优化。logging 是一个专业的日志输出库,既可以用于在屏幕上打印运行结果(和print()函数一致),同时也可以实现将这些运行结果保存到指定的文件中,用于后续的研究。它的使用方法和 print()
函数类似,但功能更强大,主要包括记录运行信息、记录错误信息、记录警告信息、记录调试信息等。
源代码:getLogging.py
调用方法:
# 1. 初始化日志模块,配置日志输出路径和模型名称
logger = init_log_config(logs_path, model_name) # 初始化日志模块为logger对象,配置日志输出路径和模型名称
# 2. 使用logger对象输出信息
logger.info('This is a info message') # 调用logger对象,输出信息.format())
logging模块的使用包含两个步骤,一是定义日志初始化函数,二是调用日志输出函数。下面分别介绍这两个步骤。
为了更好地管理日志文件,我们通常会将日志输出到一个指定的文件夹中,并以模型名称和时间戳作为文件名。下面是一个初始化日志的函数示例。其中,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
文件中。完整代码可以在 此处 查看。
当我们需要调用logging来输出日志信息时,首先要指定模块保存的位置,然后使用 import
方法将模块载入到内存,接下来使用 init_log_config()
函数对logger进行初始化,之后就可以像使用 print()
函数一样来输出日志了。注意,在调用 init_log_config()
函数时,需要传入两个参数:logs_path
和 model_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。
在本小节中,我们将展示如何输出和保存系统信息。这里仅给出一个范例,包括操作系统、PaddlePaddle版本、CPU、内存及GPU的基本信息。实际应用中,可以根据需要保存和输出更多有关系统的信息。这里,我们仍然使用上一节定义的 logger
对象来输出和保存系统信息。
源代码:getSystemInfo.py
调用方法:
# 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"
}
定义训练过程中用到的可视化方法,用于输出训练过程中关键指标与迭代次数之间的关系图,这些关键指标包括训练集损失、训练集批准确率、验证集损失,验证集准确率等。值得注意的是,训练过程中可以每个epoch绘制一个数据点,也可以每个batch绘制一个数据点,也可以每个n个batch或n个epoch绘制一个数据点。为了代码编写的便捷性,我们使用最常用的标准来绘制曲线图,即训练数据按照批次batch进行呈现,验证数据按照周期epoch进行呈现。此外,可视化代码除实现训练完成后自动将训练进程绘制成曲线图外,还可将训练过程的数据和曲线图保存至指定文件夹,以备后续的分析总结使用,输出文件夹由超参数 final_figures_path
指定。
调用方法:
draw_process(visualization_log=visualization_log, figure_path=final_figures_path, model_name=args['model_name'])
特别注意:
figure_path
以及模型的名称 model_name
,而不需要指定具体的可视化日志 visualization_log
,因为 draw_process
函数会自动从训练过程中所生成的日志数据中进行获取相关数据。但是,当我们需要离线绘制可视化结果时,就必须要手动指定 visualization_log
参数,该参数通常是一个包含训练过程中各个步骤的日志数据的 JSON 文件路径,在训练过程中生成并保存在输出结果路径 final_figures_path
中。下面的演示代码中,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
实验摘要: 掌握基于模块化设计方法的模型训练。
实验目的:
任务五全局配置文件:config05.yaml
从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 输出模型摘要
学习率策略 和 优化方法 是两个密不可分的模块,学习率策略定义了学习率的变化方法,常见的包括固定学习率、分段学习率、余弦退火学习率、指数学习率和多项式学习率等;优化策略是如何进行学习率的学习和变化,常见的包括动量SGD、RMS、SGD和Adam等。
下面,我们给出一个优化方法调用的例子,这里我们以一个简单的单层神经网络模型为例。
源代码:getOptimizer.py
调用方法:
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.
验证函数有两个功能,一是在训练过程中实时地对验证集进行测试(在线测试),二是在训练结束后对测试集进行测试(离线测试)。
验证函数的具体流程包括:
在定义eval()函数的时候,我们需要为其指定两个必要参数:model
是需要进行测试或验证的模型,data_reader
是迭代的数据读取器,取值为前面所定义的 val_reader, test_reader,分别是验证集和测试集。此处验证集和测试集数据的测试过程是相同的,只是所使用的数据不同;此外,可选参数verbose用于定义是否在测试的时候输出过程
源代码:evaluate.py
调用方法:
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
的初始化定义,以方便后续的测试。
在Paddle 2.0+动态图模式下,动态图模式被作为默认进程,同时动态图守护进程 fluid.dygraph.guard(PLACE)
被取消。
训练部分 主函数的具体流程包括:
args
,而不是创建一个临时变量,这样既方便多个模块的调用,也便于日志文件的输出。shuffle
和末尾丢弃drop_last
的设置有所不同。train()
基于"周期-批次"两层循环进行训练,并返回训练日志,用于可视化输出checkpoint_model
模型和用于部署与预测的final_model
模型源代码:train.py
调用方法:
# 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 文件夹的最终文件 copy 到 ExpDeployments 用于进行部署和应用。
与在线测试不同的时候,离线测试通常需要调用本地已经训练好的模型来对数据进行预测,而不是通过网络请求。当然,这里通常指的也是对验证集的测试。但在一些比较特殊的情况下,也可以对测试集进行离线测试。整个测试过程除了载入已经训练好的模型参数外,其他部分与在线测试过程基本一致。
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
【结果分析】
需要注意的是此处的精度与训练过程中输出的测试精度是不相同的,因为训练过程中使用的是验证集, 而这里的离线测试使用的是测试集.
实验摘要: 模块化的设计方法有利于更好地调用模型,并进行推理和预测。
实验目的:
任务六全局配置文件:config06.yaml
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
在实际应用中,我们通常需要对一批图像进行预测,并输出预测结果。在本项目中,我们使用 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。
模型名称 | 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 中蝴蝶分类数据集的表格结构。
从实验结果可以得到以下几个结论:
1.
2.
模型名称 | 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 | 是 | - | - | - | - | - | - | - |
从实验结果可以得到以下几个结论:
1.
2.