『行远见大』炼丹注意事项——以蝴蝶图像分类为例

P粉084495128
发布: 2025-07-30 10:33:59
原创
490人浏览过
该项目是飞桨图像分类训练营大作业,以蝴蝶图像分类为例,探究炼丹中数据增强与调参对精度的影响。包括数据集加载预处理(解压、建立路径与标签关系、自定义读取器,用Resize等增强)、构建ResNet152预训练模型,以Adam优化器训练,及预测流程,旨在分析提升分类精度的方法。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

『行远见大』炼丹注意事项——以蝴蝶图像分类为例 - php中文网

『行远见大』炼丹注意事项——以蝴蝶图像分类为例

本项目是『飞桨领航团图像分类零基础训练营』课程布置的大作业,项目旨在分析图像分类炼丹过程中如何做数据增强以及调参来增加精度。

『飞桨领航团图像分类零基础训练营』是由飞桨深度学习学院于2021/02/25开设的课程。

加载数据集

解压缩数据步骤:

第一步,把当前路径转换到data目录,可以使用命令!cd data。在AI studio nootbook中可以使用Linux命令,需要在命令的最前面加上英文的感叹号(!)。用&&可以连接两个命令。用\号可以换行写代码。需要注意的是,每次重新打开该项目,data文件夹下除了挂载的数据集,其他文件都会被清空。因此,如果把数据保存在data目录中,每次重新启动项目时,都需要解压缩一下。如果想省事持久化保存,可以把数据保存在work目录下。

实际上,!加某命令的模式,等价于python中的get_ipython().system('某命令')模式。

第二步,利用unzip命令,把压缩包解压到当前路径。unzip的-q参数代表执行时不显示任何信息。unzip的-o参数代表不必先询问用户,unzip执行后覆盖原有的文件。两个参数合起来,可以写为-qo。

第三步,用rm命令可以把一些文件夹给删掉(rm -r __MACOSX),比如,__MACOSX文件夹

In [1]
!ls /home/aistudio/data/data66509/Butterfly20.zip!ls /home/aistudio/data/data66509/Butterfly20_test.zip!unzip -q /home/aistudio/data/data66509/Butterfly20_test.zip -d work
!unzip -q /home/aistudio/data/data66509/Butterfly20.zip -d work
登录后复制
   

准备数据

数据准备过程包括以下两个重点步骤:

一是建立样本数据读取路径与样本标签之间的关系。

二是构造读取器与数据预处理。可以写个自定义数据读取器,它继承于PaddlePaddle2.0的dataset类,在__getitem__方法中把自定义的预处理方法加载进去。

In [3]
# 以下代码用于建立样本数据读取路径与样本标签之间的关系import osimport randomimport matplotlib.pyplot as pltimport PIL.Image as Image

data_list = [] # 用个列表保存每个样本的读取路径、标签# 由于属种名称本身是字符串,而输入模型的是数字。需要构造一个字典,把某个数字代表该属种名称。键是属种名称,值是整数。label_list=[]with open("/home/aistudio/work/species.txt") as f:    for line in f:
        a,b = line.strip("\n").split(" ")
        label_list.append([b, int(a)-1])
label_dic = dict(label_list)# 获取Butterfly20目录下的所有子目录名称,保存进一个列表之中class_list = os.listdir("/home/aistudio/work/Butterfly20")
class_list.remove('.DS_Store') # 删掉列表中名为.DS_Store的元素,因为.DS_Store并没有样本。for each in class_list:    for f in os.listdir("/home/aistudio/work/Butterfly20/"+each):
        data_list.append(["/home/aistudio/work/Butterfly20/"+each+'/'+f,label_dic[each]])# 按文件顺序读取,可能造成很多属种图片存在序列相关,用random.shuffle方法把样本顺序彻底打乱。random.shuffle(data_list)# 打印前十个,可以看出data_list列表中的每个元素是[样本读取路径, 样本标签]。print(data_list[0:10])# 打印样本数量,一共有1866个样本。print("样本数量是:{}".format(len(data_list)))
登录后复制
       
[['/home/aistudio/work/Butterfly20/011.Lamproptera_meges/011.jpg', 10], ['/home/aistudio/work/Butterfly20/016.Papilio_alcmenor/012.jpg', 15], ['/home/aistudio/work/Butterfly20/001.Atrophaneura_horishanus/152.jpg', 0], ['/home/aistudio/work/Butterfly20/013.Meandrusa_payeni/007.jpg', 12], ['/home/aistudio/work/Butterfly20/017.Papilio_arcturus/055.jpg', 16], ['/home/aistudio/work/Butterfly20/003.Byasa_alcinous/101.jpg', 2], ['/home/aistudio/work/Butterfly20/010.Lamproptera_curius/030.jpg', 9], ['/home/aistudio/work/Butterfly20/001.Atrophaneura_horishanus/129.jpg', 0], ['/home/aistudio/work/Butterfly20/007.Graphium_cloanthus/020.jpg', 6], ['/home/aistudio/work/Butterfly20/019.Papilio_dialis/043.jpg', 18]]
样本数量是:1866
登录后复制
       
In [4]
# 以下代码用于构造读取器与数据预处理# 首先需要导入相关的模块import paddlefrom paddle.vision.transforms import Compose, ColorJitter, Resize,Transpose, Normalizeimport cv2import numpy as npfrom PIL import Imagefrom paddle.io import Dataset# 使用Paddle2.0自带的数据增强方法import paddle.vision.transforms as T# 自定义的数据预处理函数,输入原始图像,输出处理后的图像,可以借用paddle.vision.transforms的数据处理功能def preprocess(img):
    transform = Compose([
        Resize(size=(224, 224)),                                                             # 把数据长宽像素调成224*224
        T.ColorJitter(0.125,0.4,0.4,0.08),                                                   # 保持亮度、对比度、饱和度、色调等在测试集上一致
        # T.BrightnessTransform(0.4),                                                        # 只对亮度调整做调整
        T.RandomHorizontalFlip(0.5),                                                         # 水平翻转
        T.RandomRotation(15),                                                                # 随机反转角度范围
        T.RandomVerticalFlip(0.5),                                                           # 垂直翻转
        T.RandomRotation(15),                                                                # 随机反转角度范围
        Normalize(mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], data_format='HWC'), # 标准化
        Transpose()                                                                          # 原始数据形状维度是HWC格式,经过Transpose,转换为CHW格式
        ])      
    img = transform(img).astype("float32")    return img# 自定义数据读取器class Reader(Dataset):
    def __init__(self, data, is_val=False):
        super().__init__()        # 在初始化阶段,把数据集划分训练集和测试集。由于在读取前样本已经被打乱顺序,取20%的样本作为测试集,80%的样本作为训练集。
        self.samples = data[-int(len(data)*0.2):] if is_val else data[:-int(len(data)*0.2)]    def __getitem__(self, idx):
        # 处理图像
        img_path = self.samples[idx][0]          # 得到某样本的路径
        img = Image.open(img_path)        if img.mode != 'RGB':
            img = img.convert('RGB')
        img = preprocess(img)                    # 数据预处理--这里仅包括简单数据预处理,没有用到数据增强

        # 处理标签
        label = self.samples[idx][1]             # 得到某样本的标签
        label = np.array([label], dtype="int64") # 把标签数据类型转成int64
        return img, label    def __len__(self):
        # 返回每个Epoch中图片数量
        return len(self.samples)# 生成训练数据集实例train_dataset = Reader(data_list, is_val=False)# 生成测试数据集实例eval_dataset = Reader(data_list, is_val=True)# 打印一个训练样本# print(train_dataset[1136][0])print(train_dataset[1136][0].shape)print(train_dataset[1136][1])# 查看划分后的样本量print('训练集样本量: {},验证集样本量: {}'.format(len(train_dataset), len(eval_dataset)))
登录后复制
       
(3, 224, 224)
[10]
训练集样本量: 1493,验证集样本量: 373
登录后复制
       

ColorJitter

class paddle.vision.transforms.ColorJitter(brightness=0, contrast=0, saturation=0, hue=0, keys=None) 随机调整图像的亮度,对比度,饱和度和色调。

参数

brightness(float) - 亮度调整范围大小,会从给定参数后的均匀分布[max(0,1 - brightness), 1 + brightness]中随机选择进行实际调整,不能是负数。

contrast(float) - 对比度调整范围大小,,会从给定参数后的均匀分布[max(0,1 - contrast), 1 + contrast]中随机选择进行实际调整,不能是负数。

saturation(float) - 饱和度调整范围大小,,会从给定参数后的均匀分布[max(0,1 - saturation), 1 + saturation]中随机选择进行实际调整,不能是负数。

hue(float) - 色调调整范围大小,,会从给定参数后的均匀分布[-hue, hue]中随机选择进行实际调整,参数值需要在0到0.5之间。

keys (list[str]|tuple[str], optional) - 与 BaseTransform 定义一致。默认值: None。

RandomHorizontalFlip/RandomVerticalFlip

class paddle.vision.transforms.RandomHorizontalFlip(prob=0.5, keys=None) 基于概率来执行图片的水平翻转;

class paddle.vision.transforms.RandomVerticalFlip(prob=0.5, keys=None) 基于概率来执行图片的垂直翻转。

参数

prob (float) - 图片执行水平/垂直翻转的概率,默认值为0.5。

keys (list[str]|tuple[str], optional) - 与 BaseTransform 定义一致。默认值: None。

RandomRotate

class paddle.vision.transforms.RandomRotation(degrees, interpolation='nearest', expand=False, center=None, fill=0, keys=None)按指定角度范围随机旋转图像。

参数

degrees (sequence|float|int) - 旋转的角度度数范围。 如果度数是数字而不是像(min,max)这样的序列,则会根据degrees参数值生成度数范围(-degrees,+degrees)。

interpolation (str, optional): 插值的方法。 如果这个参数没有设定或者输入图像为单通道,则该参数会根据使用的后端,被设置为 PIL.Image.NEAREST 或者 cv2.INTER_NEAREST。

先见AI
先见AI

数据为基,先见未见

先见AI 95
查看详情 先见AI

当使用 pil 作为后端时, 支持的插值方法如下: "nearest": Image.NEAREST, "bilinear": Image.BILINEAR, "bicubic": Image.BICUBIC

当使用 cv2 作为后端时, 支持的插值方法如下: "nearest": cv2.INTER_NEAREST, "bilinear": cv2.INTER_LINEAR, "bicubic": cv2.INTER_CUBIC

expand (bool,可选) - 是否要对旋转后的图片进行大小扩展,默认值: False。

当参数值为True时,会对图像大小进行扩展,让其能够足以容纳整个旋转后的图像。 当参数值为False时,会按照原图像大小保留旋转后的图像。这个扩展操作的前提是围绕中心旋转且没有平移。

center (2-tuple,可选) - 旋转的中心点坐标,原点是图片左上角,默认值是图像的中心点。

fill (int,可选) - 对图像扩展时填充的值。默认值:0。

keys (list[str]|tuple[str], optional) - 与 BaseTransform 定义一致。默认值: None。

建立模型

这里选用 ResNet 残差网络

为简便,这里直接使用残差网络ResNet,并且采用预训练模式。为什么要采用预训练模型呢?因为通常模型参数采用随机初始化,而预训练模型参数初始值是一个比较确定的值。这个参数初始值是经历了大量任务训练而得来的,比如用CIFAR图像识别任务来训练模型,得到的参数。虽然蝴蝶识别任务和CIFAR图像识别任务是不同的,但可能存在某些机器视觉上的共性。用预训练模型可能能够较快地得到比较好的准确度。

在PaddlePaddle2.0中,使用预训练模型只需要设定模型参数pretained=True。值得注意的是,预训练模型得出的结果类别是1000维度,要用个线性变换,把类别转化为20维度。

In [5]
# 定义模型class MyNet(paddle.nn.Layer):
    def __init__(self):
        super(MyNet,self).__init__()        # self.layer=paddle.vision.models.resnet50(pretrained=True)     # 陆平老师上课选择的模型
        # self.layer=paddle.vision.models.resnet152(pretrained=True)    # 讲义中推荐的模型
        self.layer=paddle.vision.models.resnet152(pretrained=True)      # 耗时长但精度相对而言高的模型
        self.dropout=paddle.nn.Dropout(p=0.5)
        self.fc = paddle.nn.Linear(1000, 20)    # 网络的前向计算过程
    def forward(self,x):
        x=self.layer(x)
        x=self.dropout(x)
        x=self.fc(x)        return xprint("本项目基于Paddle的版本号为:"+ paddle.__version__)
登录后复制
       
本项目基于Paddle的版本号为:2.0.0
登录后复制
       

应用高阶API训练模型

一是定义输入数据形状大小和数据类型。

二是实例化模型。如果要用高阶API,需要用Paddle.Model()对模型进行封装,如model = paddle.Model(model,inputs=input_define,labels=label_define)。

三是定义优化器。这个使用Adam优化器,学习率设置为0.0001,优化器中的学习率(learning_rate)参数很重要。要是训练过程中得到的准确率呈震荡状态,忽大忽小,可以试试进一步把学习率调低。

四是准备模型。这里用到高阶API,model.prepare()。

五是训练模型。这里用到高阶API,model.fit()。参数意义详见下述代码注释。

In [6]
# 定义输入input_define = paddle.static.InputSpec(shape=[-1,3,224,224], dtype="float32", name="img")
label_define = paddle.static.InputSpec(shape=[-1,1], dtype="int64", name="label")# 实例化网络对象并定义优化器等训练逻辑model = MyNet()
model = paddle.Model(model,inputs=input_define,labels=label_define)                      # 用Paddle.Model()对模型进行封装optimizer = paddle.optimizer.Adam(learning_rate=0.00005, parameters=model.parameters())# 上述优化器中的学习率(learning_rate)参数很重要。要是训练过程中得到的准确率呈震荡状态,忽大忽小,可以试试进一步把学习率调低
登录后复制
       
100%|██████████| 355826/355826 [00:04<00:00, 71284.76it/s]
登录后复制
       
In [7]
# 使用Adam优化器, 学习率为0.00005, 使用交叉熵损失函数, top1与top5精准度配置Accuracy的评估指标model.prepare(optimizer=optimizer,                           # 指定优化器
              loss=paddle.nn.CrossEntropyLoss(),             # 指定损失函数
              metrics=paddle.metric.Accuracy(topk=(1,5)))    # 指定评估方法# 回调函数使用callback = paddle.callbacks.VisualDL(log_dir='./log_mynet_152')

model.fit(train_data=train_dataset,         # 训练数据集
          eval_data=eval_dataset,           # 测试数据集
          batch_size=128,                   # 一个批次的样本数量(常见的有32、64、128、256)
          epochs=50,                        # 迭代轮次(常见的有20、50、100、200)
          save_dir="/home/aistudio/lup",    # 把模型参数、优化器参数保存至自定义的文件夹
          save_freq=20,                     # 设定每隔多少个epoch保存模型参数及优化器参数
          log_freq=100,                     # 打印日志的频率
          verbose=1,                        # 日志展示模式
          shuffle=True,                     # 是否打乱数据集顺序
          callbacks=callback)               # 回调函数使用# 如果运行报错,请退出草稿版,项目栏处切换version1.0版本再运行!
登录后复制
   

炼丹注意事项

我这里拿batch_size=128和epochs=50为例,在20轮后acc_top1的精度在0.88和0.91之间震荡,效果还算是理想。

但如果要使acc_top1精度稳定在0.9以上,可以从调整learning_rate、batch_size、epoch的参数下手。

年轻人,拿好这本《炼丹注意事项》,前路漫漫,开启炼(爆)丹(肝)之旅事不宜迟!

应用已经训练好的模型进行预测

如果是要参加建模比赛,通常赛事组织方会提供待预测的数据集,我们需要利用自己构建的模型,来对待预测数据集合中的数据标签进行预测。也就是说,我们其实并不知道到其真实标签是什么,只有比赛的组织方知道真实标签,我们的模型预测结果越接近真实结果,那么分数也就越高。

预测流程分为以下几个步骤:

一是构建数据读取器。因为预测数据集没有标签,该读取器写法和训练数据读取器不一样,建议重新写一个类,继承于Dataset基类。

二是实例化模型。如果要用高阶API,需要用Paddle.Model()对模型进行封装,如paddle.Model(MyNet(),inputs=input_define),由于是预测模型,所以仅设定输入数据格式就好了。

三是读取刚刚训练好的参数。这个保存在/home/aistudio/work目录之下,如果指定的是final则是最后一轮训练后的结果。可以指定其他轮次的结果,比如model.load('/home/aistudio/work/30'),这里用到了高阶API,model.load()

四是准备模型。这里用到高阶API,model.prepare()。

五是读取待预测集合中的数据,利用已经训练好的模型进行预测。

六是结果保存。

In [9]
class InferDataset(Dataset):
    def __init__(self, img_path=None):
        """
        数据读取Reader(推理)
        :param img_path: 推理单张图片
        """
        super().__init__()        if img_path:
            self.img_paths = [img_path]        else:            raise Exception("请指定需要预测对应图片路径")    def __getitem__(self, index):
        # 获取图像路径
        img_path = self.img_paths[index]        # 使用Pillow来读取图像数据并转成Numpy格式
        img = Image.open(img_path)        if img.mode != 'RGB': 
            img = img.convert('RGB') 
        img = preprocess(img) #数据预处理--这里仅包括简单数据预处理,没有用到数据增强
        return img    def __len__(self):
        return len(self.img_paths)# 实例化推理模型model = paddle.Model(MyNet(),inputs=input_define)# 读取刚刚训练好的参数model.load('/home/aistudio/lup/final')# 准备模型model.prepare()# 得到待预测数据集中每个图像的读取路径infer_list=[]with open("/home/aistudio/work/testpath.txt") as file_pred:    for line in file_pred:
        infer_list.append("/home/aistudio/work/"+line.strip())# 模型预测结果通常是个数,需要获得其对应的文字标签。这里需要建立一个字典。def get_label_dict2():
    label_list2=[]    with open("/home/aistudio/work/species.txt") as filess:        for line in filess:
            a,b = line.strip("\n").split(" ")
            label_list2.append([int(a)-1, b])
    label_dic2 = dict(label_list2)    return label_dic2

label_dict2 = get_label_dict2()# print(label_dict2)# 利用训练好的模型进行预测results=[]for infer_path in infer_list:
    infer_data = InferDataset(infer_path)
    result = model.predict(test_data=infer_data)[0]  # 关键代码,实现预测功能
    result = paddle.to_tensor(result)
    result = np.argmax(result.numpy())               # 获得最大值所在的序号
    results.append("{}".format(label_dict2[result])) # 查找该序号所对应的标签名字# 把结果保存起来with open("work/result.txt", "w") as f:    for r in results:
        f.write("{}\n".format(r))
登录后复制
   

以上就是『行远见大』炼丹注意事项——以蝴蝶图像分类为例的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号