一、蝴蝶识别分类任务概述
这是飞桨领航团的第四次作业,将完成蝴蝶图像的识别与分类。
本项目将利用人工智能技术来对蝴蝶图像进行分类,需要能对蝴蝶的类别、属性进行细粒度的识别分类。
二、首先查看一下图像的总数据量
1、在查看图像数据前将data文件下的数据集解压,首先把当前路径转换到data目录,可以使用命令!cd data。用&&可以连接两个命令。用\号可以换行写代码。
【需要注意的是,每次重新打开该项目,data文件夹下除了挂载的数据集,其他文件都会被清空。因此,如果把数据保存在data目录中,每次重新启动项目时,都需要解压缩一下。如果想省事持久化保存,可以把数据保存在work目录下】
利用unzip命令,把压缩包解压到当前路径。unzip的-q参数代表执行时不显示任何信息。unzip的-o参数代表不必先询问用户,unzip执行后覆盖原有的文件。两个参数合起来,可以写为-qo。
用rm命令可以把一些文件夹给删掉,比如,__MACOSX文件夹
代码如下:
!cd data &&\
unzip -qo data73998/Butterfly20_test.zip &&\
unzip -qo data73998/Butterfly20.zip &&\
rm -r __MACOSX
二、导入相关的模块
import paddle
import matplotlib.pyplot as plt
import PIL.Image as Image
import numpy as np
import matplotlib.pyplot as plt
import cv2
import os
import glob
import random
import time
import pandas as pd
print(paddle.__version__)
1、查看图像数据量
data_path='/home/aistudio/data/Butterfly20/*/*.jpg'
# data_path='/home/aistudio/data/Butterfly20_test/*.jpg'
but_files =glob.glob(data_path)
print(f'图片数据为{len(but_files)}')
【我这里也看了一下测试集的图片数量】
2、查看任意类别的蝴蝶图像
#随机显示一个样品的图片
index=random.choice(but_files) # 随机获取一个地址
print(index) # 查看地址
name=index.split('/')[-2] # 获取标签
img = Image.open(index) # 打开图片
img =cv2.imread(index) # 图片处理
print(img.shape) # 输出图片形状
img =img[:,:,::-1]
print(f'该样本标签为:{name}')
plt.figure(figsize=(8,10),dpi=50) # 修改显示的图像大小
plt.axis('off')
plt.imshow(img) #根据数组绘制图像
三、数据准备
数据准备过程包括以下两个重点步骤:
一是建立样本数据读取路径与样本标签之间的关系。
二是构造读取器与数据预处理。可以写个自定义数据读取器,它继承于PaddlePaddle2.0的dataset类,在__getitem__方法中把自定义的预处理方法加载进去。
1、先查看一下数据的类型
### 查看数据类型
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)
for i in label_dic:
print(i)
可以看到有20个类别
001.Atrophaneura_horishanus
002.Atrophaneura_varuna
003.Byasa_alcinous
004.Byasa_dasarada
005.Byasa_polyeuctes
006.Graphium_agamemnon
007.Graphium_cloanthus
008.Graphium_sarpedon
009.Iphiclides_podalirius
010.Lamproptera_curius
011.Lamproptera_meges
012.Losaria_coon
013.Meandrusa_payeni
014.Meandrusa_sciron
015.Pachliopta_aristolochiae
016.Papilio_alcmenor
017.Papilio_arcturus
018.Papilio_bianor
019.Papilio_dialis
020.Papilio_hermosanus
2、创建训练集与测试集
butterfly_path = 'data/Butterfly20/'
if(os.path.exists('./train_set.txt')): # 判断有误文件
os.remove('./train_set.txt') # 删除文件
if(os.path.exists('./validation_set.txt')):
os.remove('./validation_set.txt')
data_list = []
for i in os.listdir(butterfly_path):
if i not in '.DS_Store':
for j in os.listdir(os.path.join(butterfly_path, i)):
data_list.append(f'{os.path.join(butterfly_path, i, j)}\t{label_dic[i]}\n')
random.shuffle(data_list) # 乱序
print(data_list[0])
data_len = len(data_list)
print(len(data_list))
count = 0
for data in data_list:
if count <= data_len*0.2:
with open('./validation_set.txt', 'a')as f:# validation_set.txt
f.write(data)
count += 1
else:
with open('./train_set.txt', 'a')as tf: # 80%写入训练集
tf.write(data)
count += 1
这里划分训练集与测试集是按照8:2进行划分的,其他的数据集也可以按照这个比例,在后面的课程中也是按照这个比例划分的。其中将顺序打乱一下。打印结果是1866张
结果如下:
data/Butterfly20/003.Byasa_alcinous/088.jpg 2
1866
3、使用Paddle.dataset对数据进行自定义读取
#以下代码用于构造读取器与数据预处理
#首先需要导入相关的模块
import paddle
from paddle.vision.transforms import Compose,CenterCrop, Resize,Normalize,RandomRotation,RandomHorizontalFlip,Transpose,ToTensor
import cv2
import numpy as np
from PIL import Image
from paddle.io import Dataset
# 自定义数据读取器
class Reader(Dataset):
def __init__(self, mode='train_set'):
"""
初始化函数
"""
self.data = []
with open(f'{mode}_set.txt') as f:
for line in f.readlines():
info = line.strip().split('\t')
if len(info) > 0:
self.data.append([info[0].strip(), info[1].strip()])
def __getitem__(self, index):
"""
读取图片,对图片进行归一化处理,返回图片和 标签
"""
image_file, label = self.data[index] # 获取数据
img = Image.open(image_file) # 读取图片
img = img.convert('RGB')
img = img.resize((224, 224), Image.ANTIALIAS) # 图片大小样式归一化
img = np.array(img).astype('float32') # 转换成数组类型浮点型32位
img = img.transpose((2, 0, 1)) #读出来的图像是rgb,rgb,rbg..., 转置为 rrr...,ggg...,bbb... 这里要注意看一下
img = img/255.0 # 数据缩放到0-1的范围
# img = img.shape()
# print(img(0:3,:,:))
return img, np.array(label, dtype='int64')
def __len__(self):
"""
获取样本总数
"""
return len(self.data)
# 训练的数据提供器
train_dataset = Reader(mode='train')
# 测试的数据提供器
eval_dataset = Reader(mode='validation')
# 查看训练和测试数据的大小
print('train大小:', train_dataset.__len__())
print('eval大小:', eval_dataset.__len__())
# 查看图片数据、大小及标签
for data, label in eval_dataset:
print(data)
print(np.array(data).shape)
print(label)
break
【transpose((2,0,1))】注意一下,懂?
打印结果:
train大小: 1492
eval大小: 374
【注意!!!这里有一个小细节,应该是看你仔细不仔细,原来的代码中train和eval弄反了】
四、创建模型
为简便,这里直接采用残差网络resnet152,并且采用预训练模式(pretrained = True)。为什么要采用预训练模型呢?因为通常模型参数采用随机初始化,而预训练模型参数初始值是一个比较确定的值。这个参数初始值是经历了大量任务训练而得来的,比如用CIFAR图像识别任务来训练模型,得到的参数。虽然蝴蝶识别任务和CIFAR图像识别任务是不同的,但可能存在某些机器视觉上的共性。用预训练模型可能能够较快地得到比较好的准确度。主要还是为了比较快的获得较好的准确度。
import paddle
from paddle.vision.transforms import Compose,CenterCrop, Resize,Normalize,RandomRotation,RandomHorizontalFlip,Transpose,ToTensor
import cv2
import numpy as np
from PIL import Image
from paddle.io import Dataset
class MyCNN(paddle.nn.Layer):
def __init__(self):
super(MyCNN,self).__init__()
self.layer = paddle.vision.models.resnet152(pretrained = True)##
self.fc1 = paddle.nn.Linear(1000,512)
self.fc2 = paddle.nn.Linear(512,20)
self.flatten = paddle.nn.Flatten()# 展平
self.relu = paddle.nn.ReLU()
self.dropout = paddle.nn.Dropout(0.2)
def forward(self,x):
x=self.layer(x)
x=self.flatten(x)
x=self.fc1(x)
x=self.relu(x)
x=self.dropout(x)
x=self.fc2(x)
return x
【这里注意一下paddle.vision下的API用法】
五、实例化模型可视化以及训练
#定义输入
input_def = paddle.static.InputSpec(shape=[-1,3,224,224], dtype="float32", name="img")
label_def = paddle.static.InputSpec(shape=[-1,1], dtype="int64", name="label")
#实例化网络对象并定义优化器等训练逻辑
model = MyCNN()
model = paddle.Model(model,inputs=input_def,labels=label_def) #用Paddle.Model()对模型进行封装
# model.summary((1,3, 224, 224))
这里实例化模型,需要用Paddle.Model()对模型进行封装,model = paddle.Model(model,inputs=input_def,labels=label_def)。
# 训练可视化VisualDL工具的回调函数
visualdl = paddle.callbacks.VisualDL(log_dir='visualdl_log')
训练可视化
# 配置优化器、损失函数、评估指标
model.prepare(paddle.optimizer.Adam(learning_rate=0.0001, parameters=model.parameters()),
paddle.nn.CrossEntropyLoss(),
paddle.metric.Accuracy())
优化器定义,采用的是Adam,将学习率设置为0.0001,采用交叉熵损失
# 启动模型全流程训练
model.fit(train_dataset, # 训练数据集
eval_dataset, # 评估数据集
epochs=55, # 训练的总轮次
batch_size=64, # 训练使用的批大小
verbose=1, # 日志展示形式
callbacks=[visualdl]) # 设置可视化
启动训练过程
六、模型评估
result = model.evaluate(eval_dataset, verbose=1)
print(result)
结果显示;{'loss': [0.0], 'acc': 0.8796791443850267},有87.97%的正确率
七、保存模型和预测
如果是要参加建模比赛,通常赛事组织方会提供待预测的数据集,我们需要利用自己构建的模型,来对待预测数据集合中的数据标签进行预测。也就是说,我们其实并不知道到其真实标签是什么,只有比赛的组织方知道真实标签,我们的模型预测结果越接近真实结果,那么分数也就越高
def load_image(file):
# 打开图片
im = Image.open(file)
# 将图片调整为跟训练数据一样的大小
im = im.convert('RGB')
im = im.resize((224, 224), Image.ANTIALIAS)
# 建立图片矩阵 类型为float32
im = np.array(im).astype(np.float32)
# 矩阵转置
im = im.transpose((2, 0, 1))
# 将像素值从[0-255]转换为[0-1]
im = im / 255.0
# print(im)
im = np.expand_dims(im, axis=0)
# 保持和之前输入image维度一致
print('im_shape的维度:',im.shape)
return im
from PIL import Image
# site = 255 # 读取图片位置
model_state_dict = paddle.load('./butterfly.pdparams') # 读取模型
model = MyCNN() # 实例化模型
model.set_state_dict(model_state_dict)
model.eval()
img = load_image(index)
print(paddle.to_tensor(img).shape)
# print(paddle.reshape(paddle.to_tensor(img), (1, 3, 224, 224)))
ceshi = model(paddle.reshape(paddle.to_tensor(img), (1, 3, 224, 224))) # 测试
print('预测的结果为:', list(label_dic.keys())[np.argmax(ceshi.numpy())]) # 获取值
Image.open(index) # 显示图片
通过本次学习,熟悉了建模的一般步骤,包括问题定义,数据准备,模型选择开发,模型训练调优,模型评估测试及部署上线。
附:
卷积神经网络的基本结构:输入、卷积层、池化层(重复卷积与池化)、全连接层、softmax、输出分类
卷积核:卷积前边长 a 卷积核边长 k 步长为stride
卷积后边长: (a-k)/stride+ 1 (步长为1,且不加边缘补齐的情况下)
pooling:池化层 分为最大池化和平均池化,是一种下采样的应用方式通常我们使用最大池化 max pooling,池化的作用是增大感受野、维持神经网络规模。
softmax:通常分类问题将softmax作为输出层的激活函数
加油啊!!!!
第一次写,没啥经验,若有错误,谢谢指正
执行报内存溢出,直接停了,
内存溢出可以改小batch_size或者选用小模型。
在执行,看着应该不会报内存溢出了,改小了batch_size
这个和电脑配置有关系吗?这不是在线上使用的AIstudio吗
和电脑配置有关,和模型大小也有关。线上也不是无限内存呀,给你的资源都是有限的。
那么多矩阵参数全部要放到显卡里面,如果batch_size太大,就回放不下,报内存错误了。
赞
我没有遇到过唉,但是我的确实跑通了
学到了
厉害啊有点小懂了
蝴蝶分类这也太难了吧。蝴蝶的类别那么多,模型还能分的那么准。百度飞桨YYDS了。
666
太牛啦!