「这是我参与2022首次更文挑战的第33天,活动详情查看:2022首次更文挑战」。
图像分类是根据图像的语义信息将不同类别图像区分开来,是计算机视觉中重要的基本问题
猫狗分类属于图像分类中的粗粒度分类问题
# 使用2.0.0以上PaddlePaddle框架
import paddle
print(paddle.__version__)
2.0.1
首先导入必要的包
zipfile------------->python的模块,解压缩zip文件
os------------->python的模块,可使用该模块对操作系统进行操作
paddle--->PaddlePaddle深度学习框架
os------------->python的模块,可使用该模块对操作系统进行操作
numpy---------->python第三方库,用于进行科学计算
PIL------------> Python Image Library,python第三方图像处理库
matplotlib----->python的绘图库 pyplot:matplotlib的绘图框架
os------------->提供了丰富的方法来处理文件和目录
sys------------->供对解释器使用或维护的一些变量的访问,以及与解释器强烈交互的函数。
pickle---------->模块实现了基本的数据序列和反序列化
warnings.filterwarnings("ignore")---------->忽略所有警告
cpu_count---------->获取计算机cpu核数
# 导入需要的包
import warnings
warnings.filterwarnings("ignore")
import tarfile
import paddle
import numpy as np
from PIL import Image
import sys
import pickle
from multiprocessing import cpu_count
import matplotlib.pyplot as plt
import os
from paddle.nn import MaxPool2D,Conv2D,BatchNorm
from paddle.nn import Linear
print("本教程基于Paddle的版本号为:"+paddle.__version__)
本教程基于Paddle的版本号为:2.0.1
'''
参数配置
'''
train_parameters = {
"input_size": [1, 28, 28], #输入图片的shape
"class_dim": 2, #分类数
"src_path":"data/data9154/cifar-10-python.tar.gz", #原始数据集路径
"target_path":"/home/aistudio/data/", #要解压的路径
"num_epochs": 10, #训练轮数
"train_batch_size": 100, #训练时每个批次的大小
"learning_strategy": { #优化函数相关的配置
"lr": 0.001 #超参数学习率
},
'skip_steps': 5, #每N个批次打印一次结果
'save_steps': 5, #每N个批次保存一次模型参数
"checkpoints": "/home/aistudio/checkpoints" #保存的路径
}
Step1:准备数据
- (1)解压原始数据集
- (2)构造dataset、dataloader
数据集介绍
我们使用CIFAR10数据集。CIFAR10数据集包含60,000张32x32的彩色图片,10个类别,每个类包含6,000张。其中50,000张图片作为训练集,10000张作为验证集。这次我们只对其中的猫和狗两类进行预测。
PaddlePaddle已经内置了若干种常用的数据集,使用CIFAR10特别简单
from paddle.vision.datasets import Cifar10
1.1解压原始数据集
#解压原始数据集函数
def untar_data(src_path,target_path):
'''
解压原始数据集,将src_path路径下的tar包解压至target_path目录下
'''
if(not os.path.isdir(target_path + "cifar-10-batches-py")):
tar = tarfile.open(src_path)
tar.extractall(path=target_path)
tar.close()
print('数据集解压完成')
else:
print('文件已存在')
#参数初始化
src_path=train_parameters['src_path']
target_path=train_parameters['target_path']
#解压原始数据到指定路径
untar_data(src_path,target_path)
文件已存在
1.2构造dataset、dataloader
train_dataset和eval_dataset
自定义读取器处理训练集和测试集
paddle.reader.shuffle()表示每次缓存BUF_SIZE个数据项,并进行打乱
paddle.batch()表示每BATCH_SIZE组成一个batch
def unpickle(file):
# data:a 10000x3072 numpy array of uint8s. Each row of the array stores a 32x32 colour image.
# The first 1024 entries contain the red channel values, the next 1024 the green,
# and the final 1024 the blue. The image is stored in row-major order,
# so that the first 32 entries of the array are the red channel values of the first row of the image.
# labels:a list of 10000 numbers in the range 0-9.
# The number at index i indicates the label of the ith image in the array data.
fo = open(file, 'rb')
dict = pickle.load(fo,encoding = 'bytes')
train_labels = dict[b'labels']
train_array = dict[b'data']
train_array=train_array.tolist()
fo.close()
data_len=len(train_labels)
for i in range(data_len-1,-1,-1):
if train_labels[i]==3:
train_labels[i]=0
elif train_labels[i]==5:
train_labels[i]=1
else:
train_labels.pop(i)
train_array.pop(i)
train_array=np.array(train_array)
return train_labels, train_array
import paddle.vision.transforms as T
from paddle.vision.transforms import Compose, Normalize, Resize, Grayscale
from PIL import Image
'''
自定义dataset数据集
'''
from paddle.io import Dataset
class MyDataset(paddle.io.Dataset):
"""
步骤一:继承paddle.io.Dataset类
"""
def __init__(self, mode='train'):
"""
步骤二:实现构造函数,定义数据集大小
"""
super(MyDataset, self).__init__()
# 保存标签数据
self.data = []
# 保存图像数据
self.img_datas = []
# 临时变量
xs=[]
ys=[]
temp_labels=[]
temp_datas=[]
# transform定义,转灰度图,缩放到28*28尺寸,归一化
mean = [127.5]
std = [127.5]
self.transforms = Compose([Resize((28,28)), Grayscale(), Normalize(mean, std, 'CHW')])
if mode == 'train':
#批量读入训练数据
for i in range(1,6):
temp_label,temp_data=unpickle(target_path +"cifar-10-batches-py/data_batch_%d" % (i,))
ys.append(temp_label)
xs.append(temp_data)
temp_labels=np.concatenate(ys)
temp_datas=np.concatenate(xs)
else:
##批量读入测试数据
temp_labels,temp_datas=unpickle(target_path +"cifar-10-batches-py/test_batch")
temp_labels=np.array(temp_labels)
temp_datas=np.array(temp_datas)
# 转为3*32*32图像数据
temp_datas = temp_datas.reshape((-1,3,32,32))
self.data=temp_labels
self.img_datas = temp_datas
def __getitem__(self, index):
"""
步骤三:实现__getitem__方法,定义指定index时如何获取数据,并返回单条数据(训练数据,对应的标签)
"""
#返回单一数据和标签
data_image = self.img_datas[index]
# 从numpy载入Image
data_image = Image.fromarray(data_image, 'RGB')
# 取图像并应用transform进行resize、灰度、normalize
t_data_image = self.transforms(data_image)
# 取标签
label = self.data[index]
return t_data_image, np.array(label, dtype='int64')
def __len__(self):
"""
步骤四:实现__len__方法,返回数据集总数目
"""
#返回数据总数
return len(self.data)
# 测试定义的数据集
train_dataset = MyDataset(mode='train')
eval_dataset = MyDataset(mode='val')
print('=============train_dataset =============')
#输出数据集的形状和标签
print('train_dataset.__getitem__(1)[0].shape',train_dataset.__getitem__(1)[0].shape)
print('train_dataset.__getitem__(1)[1]', train_dataset.__getitem__(1)[1])
#输出数据集的长度
print('train_dataset.__len__()',train_dataset.__len__())
print('=============eval_dataset =============')
#输出数据集的长度
print('eval_dataset.__getitem__(1)[0].shape',eval_dataset.__getitem__(1)[0].shape)
print('eval_dataset.__getitem__(1)[1]', eval_dataset.__getitem__(1)[1])
print('eval_dataset.__len__()',eval_dataset.__len__())
=============train_dataset =============
train_dataset.__getitem__(1)[0].shape (1, 28, 28)
train_dataset.__getitem__(1)[1] 0
train_dataset.__len__() 10000
=============eval_dataset =============
eval_dataset.__getitem__(1)[0].shape (1, 28, 28)
eval_dataset.__getitem__(1)[1] 0
eval_dataset.__len__() 2000
#训练数据DataLoad加载
train_loader = paddle.io.DataLoader(train_dataset,
batch_size=train_parameters['train_batch_size'],
shuffle=True
)
#测试数据DataLoad加载
eval_loader = paddle.io.DataLoader(eval_dataset,
batch_size=train_parameters['train_batch_size'],
shuffle=False
)
Step2.网络配置
(1)网络搭建
*** CNN网络模型
在CNN模型中,卷积神经网络能够更好的利用图像的结构信息。下面PaddlePaddle内置的一个较简单的卷积神经网络Lenet。
LeNet-5是卷积神经网络模型的早期代表,它由LeCun在1998年提出。该模型采用顺序结构,主要包括7层(2个卷积层、2个池化层和3个全连接层),卷积层和池化层交替排列。
import paddle
import paddle.nn as nn
class LeNet(nn.Layer):
# Lenet定义
def __init__(self, num_classes=10):
# 分类数,默认10
super(LeNet, self).__init__()
self.num_classes = num_classes
self.features = nn.Sequential(
nn.Conv2D(
1, 6, 3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2D(2, 2),
nn.Conv2D(
6, 16, 5, stride=1, padding=0),
nn.ReLU(),
nn.MaxPool2D(2, 2))
if num_classes > 0:
self.fc = nn.Sequential(
nn.Linear(400, 120),
nn.Linear(120, 84), nn.Linear(84, num_classes))
def forward(self, inputs):
x = self.features(inputs)
if self.num_classes > 0:
x = paddle.flatten(x, 1)
x = self.fc(x)
return x
# 定义网络
network=LeNet(num_classes=train_parameters['class_dim'])
# 装配模型
model=paddle.Model(network)
# 打印网络结构
model.summary((1, 1, 28 , 28))
---------------------------------------------------------------------------
Layer (type) Input Shape Output Shape Param #
===========================================================================
Conv2D-1 [[1, 1, 28, 28]] [1, 6, 28, 28] 60
ReLU-1 [[1, 6, 28, 28]] [1, 6, 28, 28] 0
MaxPool2D-1 [[1, 6, 28, 28]] [1, 6, 14, 14] 0
Conv2D-2 [[1, 6, 14, 14]] [1, 16, 10, 10] 2,416
ReLU-2 [[1, 16, 10, 10]] [1, 16, 10, 10] 0
MaxPool2D-2 [[1, 16, 10, 10]] [1, 16, 5, 5] 0
Linear-1 [[1, 400]] [1, 120] 48,120
Linear-2 [[1, 120]] [1, 84] 10,164
Linear-3 [[1, 84]] [1, 2] 170
===========================================================================
Total params: 60,930
Trainable params: 60,930
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.11
Params size (MB): 0.23
Estimated Total Size (MB): 0.35
---------------------------------------------------------------------------
{'total_params': 60930, 'trainable_params': 60930}
Step3.模型训练 and Step4.模型评估
使用 paddle.optimizer.Adam 优化器来进行优化
使用 F.cross_entropy 来计算损失值
# 绘制损失函数图
def draw_process(title,color,iters,data,label):
plt.title(title, fontsize=24)
plt.xlabel("iter", fontsize=20)
plt.ylabel(label, fontsize=20)
plt.plot(iters, data,color=color,label=label)
plt.legend()
plt.grid()
plt.show()
# 模型训练
# 初始化LeNet模型
model=LeNet(num_classes=train_parameters['class_dim'])
# 训练模式
model.train()
# 交叉熵
cross_entropy = paddle.nn.CrossEntropyLoss()
# 优化器
optimizer = paddle.optimizer.Adam(learning_rate=train_parameters['learning_strategy']['lr'],
parameters=model.parameters())
# 绘制loss、acc曲线图变量
steps = 0
Iters, total_loss, total_acc = [], [], []
# 开始训练
for epo in range(train_parameters['num_epochs']):
for _, data in enumerate(train_loader()):
steps += 1
x_data = data[0]
x_data = paddle.to_tensor (x_data)
y_data = paddle.to_tensor(data[1])
y_data = paddle.unsqueeze(y_data, 1)
predicts = model(x_data)
# 计算交叉熵
loss = cross_entropy(predicts, y_data)
# 计算精确度
acc = paddle.metric.accuracy(predicts, y_data)
# 反向传播
loss.backward()
optimizer.step()
# 梯度清零
optimizer.clear_grad()
if steps % train_parameters["skip_steps"] == 0:
Iters.append(steps)
total_loss.append(loss.numpy()[0])
total_acc.append(acc.numpy()[0])
#打印中间过程
print('epo: {}, step: {}, loss is: {}, acc is: {}'\
.format(epo, steps, loss.numpy(), acc.numpy()))
#保存模型参数
if steps % train_parameters["save_steps"] == 0:
save_path = train_parameters["checkpoints"]+"/"+"save_dir_" + str(steps) + '.pdparams'
print('save model to: ' + save_path)
paddle.save(model.state_dict(),save_path)
paddle.save(model.state_dict(),train_parameters["checkpoints"]+"/"+"save_dir_final.pdparams")
draw_process("trainning loss","red",Iters,total_loss,"trainning loss")
draw_process("trainning acc","green",Iters,total_acc,"trainning acc")
模型验证
训练完成后,需要验证模型的效果,此时,加载测试数据集,然后用训练好的模对测试集进行预测,计算损失与精度。
'''
模型评估
'''
model__state_dict = paddle.load(train_parameters["checkpoints"]+"/"+"save_dir_final.pdparams")
model_eval = LeNet( num_classes=train_parameters['class_dim'])
model_eval.set_state_dict(model__state_dict)
model_eval.eval()
accs = []
for _, data in enumerate(eval_loader()):
x_data = data[0]
y_data = paddle.to_tensor(data[1])
y_data = paddle.unsqueeze(y_data, 1)
predicts = model_eval(x_data)
# 计算acc
acc = paddle.metric.accuracy(predicts, y_data)
accs.append(acc.numpy()[0])
print('模型在验证集上的准确率为:',np.mean(accs))
模型在验证集上的准确率为: 0.5365
Step5.模型预测
# 图片预处理
def load_image(file):
'''
预测图片预处理
'''
#打开图片
im = Image.open(file)
#将图片调整为跟训练数据一样的大小 28*28,设定ANTIALIAS,即抗锯齿.resize是缩放
im = im.resize((28, 28), Image.ANTIALIAS)
# 转灰度图
im = im.convert('1')
#建立图片矩阵 类型为float32
im = np.array(im).astype(np.float32)
#矩阵转置
#将像素值从【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
'''
模型预测
'''
# 载入模型
model__state_dict = paddle.load(train_parameters["checkpoints"]+"/"+"save_dir_final.pdparams")
model_eval = LeNet( num_classes=train_parameters['class_dim'])
model_eval.set_state_dict(model__state_dict)
#训练模式
model.eval()
#展示预测图片
infer_path='/home/aistudio/data/data7940/dog.png'
img = Image.open(infer_path)
plt.imshow(img) #根据数组绘制图像
plt.show() #显示图像
#对预测图片进行预处理
infer_img = load_image(infer_path)
infer_img = infer_img.reshape(1,28,28)
# infer_img = infer_img.toGra
#定义标签列表
label_list = [ "cat", "dog"]
data = infer_img
dy_x_data = np.array(data).astype('float32')
dy_x_data=dy_x_data[np.newaxis,:, : ,:]
img = paddle.to_tensor(dy_x_data)
out = model(img)
lab = np.argmax(out.numpy()) #argmax():返回最大数的索引
print(label_list[lab])
im_shape的维度: (1, 28, 28)
dog