PaddlePaddle2.0搭建VGG-16模型实现蝴蝶分类

  • Post author:
  • Post category:其他




PaddlePaddle2.0利用VGG-16预训练模型实现蝴蝶分类

本项目是百度AI Studio上图像分类课程的一个内容,之前使用ResNet101网络完成,现在用VGG-16网络实现同样的任务。数据集可以在AI Studio上搜索Butterfly20下载,如果下载不到,可用下面网盘链接。

Butterfly20数据集下载链接:

链接:

https://pan.baidu.com/s/19Fqsg_rUAQi9nvf3vLhI9w


提取码:qdkx

这次使用其他网络完成同一任务,所以程序大体变化不大,数据预处理、训练测试、模型保存等都变化不大,所以更详细的分步解释见上一篇文章:


https://blog.csdn.net/weixin_43848436/article/details/114669737



一、数据解压

将数据集文件夹解压。

# 解压数据集文件夹
!cd data &&\
unzip -qo data73998/Butterfly20_test.zip &&\
unzip -qo data73998/Butterfly20.zip &&\
rm -r __MACOSX



二、导入相关的库

# 引入所需的库
import os
import zipfile
import random
import json
import paddle
import sys
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
from paddle.io import Dataset



三、数据预处理

包括两部分:数据集标签对应关系建立、数据预处理以及数据读取器的建立(分配训练集和测试集)。

#以下代码用于建立样本数据读取路径与样本标签之间的关系
import os
import random

data_list = [] #用个列表保存每个样本的读取路径、标签

#由于属种名称本身是字符串,而输入模型的是数字。需要构造一个字典,把某个数字代表该属种名称。键是属种名称,值是整数。
label_list=[]
with open("/home/aistudio/data/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/data/Butterfly20")
class_list.remove('.DS_Store') #删掉列表中名为.DS_Store的元素,因为.DS_Store并没有样本。

for each in class_list:
    for f in os.listdir("/home/aistudio/data/Butterfly20/"+each):
        data_list.append(["/home/aistudio/data/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)))
#以下代码用于构造读取器与数据预处理
#首先需要导入相关的模块
import paddle
from paddle.vision.transforms import Compose, ColorJitter, Resize,Transpose, Normalize
import cv2
import numpy as np
from PIL import Image
from paddle.io import Dataset

#自定义的数据预处理函数,输入原始图像,输出处理后的图像,可以借用paddle.vision.transforms的数据处理功能
def preprocess(img):
    transform = Compose([
        Resize(size=(224, 224)), #把数据长宽像素调成224*224
        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(len(train_dataset)) #1866*0.8=1492.8
#print(train_dataset[1136][0])
print(train_dataset[1136][0].shape)
print(train_dataset[1136][1])
print(data_list[1136])



四、网络定义

分为两部分:网络块的定义、网络传播过程的定义。

# 定义网络块
class ConvPool(paddle.nn.Layer):
    '''卷积+池化'''
    def __init__(self,
                 num_channels,#1
                 num_filters, #2
                 filter_size,#3
                 pool_size,#4
                 pool_stride,#5
                 groups,#6
                 conv_stride=1, 
                 conv_padding=1,
                 ):
        super(ConvPool, self).__init__()  

        self._conv2d_list = []

        for i in range(groups):
            conv2d = self.add_sublayer(   #添加子层实例
                'bb_%d' % i,
                paddle.nn.Conv2D(         # layer
                in_channels=num_channels, #通道数
                out_channels=num_filters,   #卷积核个数
                kernel_size=filter_size,   #卷积核大小
                stride=conv_stride,        #步长
                padding = conv_padding,    #padding
                )
            )
            num_channels = num_filters
       
            self._conv2d_list.append(conv2d)

        self._pool2d = paddle.nn.MaxPool2D(
            kernel_size=pool_size,           #池化核大小
            stride=pool_stride               #池化步长
            )
        # print(self._conv2d_list)
    def forward(self, inputs):
        x = inputs
        for conv in self._conv2d_list:
            x = conv(x)
            x = paddle.nn.functional.relu(x)
        x = self._pool2d(x)
        return x
# 定义VGG网络结构
class VGGNet(paddle.nn.Layer):
  
    def __init__(self):
       super(VGGNet, self).__init__() 
       self.convpool01 = ConvPool(3,64,3,2,2,1)
       self.convpool02 = ConvPool(64,128,3,2,2,2)
       self.convpool03 = ConvPool(128,256,3,2,2,4)
       self.convpool04 = ConvPool(256,512,3,2,2,3)
       self.convpool05 = ConvPool(512,512,3,2,2,3)
       self.pool_5_shape = 512*7*7
       self.fc01 = paddle.nn.Linear(self.pool_5_shape,4096)
       self.fc02 = paddle.nn.Linear(4096,4096)
       # self.fc03 = paddle.nn.Linear(4096,train_parameters['class_dim'])
       self.fc03 = paddle.nn.Linear(4096,20)


    def forward(self, inputs, label=None):
        out = self.convpool01(inputs)
        out = self.convpool02(out)
        out = self.convpool03(out)
        out = self.convpool04(out)
        out = self.convpool05(out)
        out = paddle.reshape(out,shape=[-1,512*7*7])
        out = self.fc01(out)
        out = self.fc02(out)
        out = self.fc03(out)
        if label is not None:
          acc = paddle.metric.accuracy(input=out,label=label)
          return out,acc
        else:
          return out

网络具体结构可用API查看:

model = paddle.Model(VGGNet())
model.summary((1, 3, 224, 224))

在这里插入图片描述

上图中,线性层的部分其实就是全连接的过程,输入输出数据之间通过全连接进行计算。参数量计算具体如下:

在这里插入图片描述



五、模型训练

#定义输入
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 = VGGNet()
model = paddle.Model(model,inputs=input_define,labels=label_define) #用Paddle.Model()对模型进行封装
optimizer = paddle.optimizer.Adam(learning_rate=0.0001, parameters=model.parameters())
#上述优化器中的学习率(learning_rate)参数很重要。要是训练过程中得到的准确率呈震荡状态,忽大忽小,可以试试进一步把学习率调低。

model.prepare(optimizer=optimizer, #指定优化器
              loss=paddle.nn.CrossEntropyLoss(), #指定损失函数
              metrics=paddle.metric.Accuracy()) #指定评估方法

model.fit(train_data=train_dataset,     #训练数据集
          eval_data=eval_dataset,         #测试数据集
          batch_size=64,                  #一个批次的样本数量
          epochs=30,                      #迭代轮次
          save_dir="/home/aistudio/lup", #把模型参数、优化器参数保存至自定义的文件夹
          save_freq=20,                    #设定每隔多少个epoch保存模型参数及优化器参数
          log_freq=100                     #打印日志的频率
)

在这里插入图片描述



六、模型预测

验证集合里面有200张图片,运用训练好的模型对这部分图片进行预测,然后将结果保存下来,格式为.txt文件。用于评价模型的表现能力。

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(VGGNet(),inputs=input_define)

#读取刚刚训练好的参数
model.load('/home/aistudio/lup/final')

#准备模型
model.prepare()

#得到待预测数据集中每个图像的读取路径
infer_list=[]
with open("/home/aistudio/data/testpath.txt") as file_pred:
    for line in file_pred:
        infer_list.append("/home/aistudio/data/"+line.strip())

#模型预测结果通常是个数,需要获得其对应的文字标签。这里需要建立一个字典。
def get_label_dict2():
    label_list2=[]
    with open("/home/aistudio/data/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))



版权声明:本文为weixin_43848436原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。