加拿大高等研究院CIFAR-10计算机视觉数据集(2009)Python版和LeNet-5识别样例

需要登录后才允许下载文件包。登录
合集:AI案例-CV-计算机服务业
数据集:加拿大高等研究院CIFAR发布的10个类别计算机视觉数据集
数据集价值:用于图像分类和机器学习任务。
解决方案:PyTorch框架、LeNet-5神经网络模型

一、问题描述

CIFAR-10是 Canadian Institute For Advanced Research(加拿大高等研究院)的缩写,它是一个由10个类别组成的计算机视觉数据集,用于图像分类和机器学习任务。CIFAR-10数据集的开发目的是为了推动图像分类技术的发展,特别是在深度学习和计算机视觉领域。它由Hinton的学生Alex Krizhevsky和Ilya Sutskever收集,旨在通过提供标注好的图像数据,训练深度学习模型以识别图片中的目标

二、数据集内容

CIFAR-10数据集包含60,000张32×32像素的彩色图像,分为10个类别,每个类别有6,000张图像。其中有50,000张训练图像和10,000张测试图像。该数据集被划分为五个训练批次和一个测试批次,每个批次包含10,000张图像。测试批次恰好包含每个类别随机选取的1,000张图像。训练批次包含剩余的图像,顺序随机,但某些训练批次可能包含某个类别的图像比其他类别多。总的来说,训练批次恰好包含每个类别的5,000张图像。

CIFAR-10数据集基本信息:
图像数量:60,000张
图像尺寸:32x32像素
颜色通道:3(RGB)
类别数量:10(飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船、卡车)
每个类别的图像数量:每个类别包含6,000张图像
训练集大小:50,000张
测试集大小:10,000张

CIFAR10数据集中一共有10类物体,标签值分别按照0~9来区分,分别是飞机( airplane )、汽车( automobile )、鸟( bird )、猫( cat )、鹿( deer )、狗( dog )、青蛙( frog )、马( horse )、船( ship )和卡车( truck )。

Classes:
1) 0: airplane
2) 1: automobile
3) 2: bird
4) 3: cat
5) 4: deer
6) 5: dog
7) 6: frog
8) 7: horse
9) 8: ship
10) 9: truck

图片样例:

数据结构

我将描述Python版本的数据集布局。Matlab版本的布局与之相同。

数据集文件列表:

batches.meta
data_batch_1
data_batch_2
data_batch_3
data_batch_4
data_batch_5
test_batch

如何加载 batches.meta file (Python)

该存档包含文件data_batch_1、data_batch_2、…、data_batch_5以及test_batch。这些文件都是使用cPickle生成的Python“pickle”对象。以下是一个python2例程,它可以打开这样的文件并返回一个字典:

# 在Python 2中,cPickle模块用于序列化和反序列化Python对象。它提供了高效的二进制数据编码和解码功能,可以将Python对象转换为字节流(即“pickle”),也可以将字节流转换回Python对象。
def unpickle(file):
   import cPickle
   with open(file, 'rb') as fo:
       dict = cPickle.load(fo)
   return dict

以及一个python3版本:

# 在Python 3中,cPickle模块已经被重命名为pickle,并且与Python 2中的pickle模块进行了合并。因此,在Python 3中,你应该使用pickle模块而不是cPickle。
def unpickle(file):
   import pickle
   with open(file, 'rb') as fo:
       dict = pickle.load(fo, encoding='bytes')
   return dict

以这种方式加载时,每个批次文件都包含一个具有以下元素的字典:

  • data — 一个10000×3072的uint8 numpy数组。数组的每一行存储一个32×32的彩色图像。前1024个条目包含红色通道的值,接下来的1024个绿色通道的值,最后的1024个蓝色通道的值。图像以行主序存储,因此数组的前32个条目是图像第一行的红色通道值。
  • labels — 一个包含0-9范围内数字的10000个数字列表。索引i处的数字表示数组data中第i个图像的标签。

数据集还包含另一个文件,称为batches.meta。它也包含一个Python字典对象。它具有以下条目:

  • label_names — 一个10元素的列表,为上述labels数组中的数字标签提供有意义的名称。例如,label_names[0] == “airplane”,label_names[1] == “automobile”,等等。

Example of how to read the file: batches.meta

metadata_path = './cifar-10-python/batches.meta' # change this path
metadata = unpickle(metadata_path)

致谢

Learning Multiple Layers of Features from Tiny Images, Alex Krizhevsky, 2009.

数据集使用许可协议

Creative Commons Attribution 4.0 License

三、样例

源码:CIFAR_10.ipynb

安装

通过conda安装python (Version 3.12.x)、torch、torchvision、matplotlib等开发包。请参考《安装深度学习框架PyTorch》安装PyTorch(包含torch、torchvision等包)。选择合适的CUDA版本进行安装,例如pytorch==2.4.1:

conda create -n pytorch241-gpu python=3.10
conda activate pytorch241-gpu
conda install pytorch==2.4.1 torchvision==0.19.1 torchaudio==2.4.1 pytorch-cuda=12.1 -c pytorch -c nvidia

导入开发库

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from matplotlib import pyplot as plt

1、数据集预处理

torchvision.datasets支持从CIFAR10官网下载到本地./data/CIFAR10 文件夹。

pipline_train = transforms.Compose([
  # 随机旋转图片
  transforms.RandomHorizontalFlip(),
  # 将图片尺寸resize到32x32
  transforms.Resize((32, 32)),
  # 将图片转化为Tensor格式
  transforms.ToTensor(),
  # 正则化(当模型出现过拟合的情况时,用来降低模型的复杂度)
  transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

pipline_test = transforms.Compose([
  # 将图片尺寸resize到32x32
  transforms.Resize((32, 32)),
  transforms.ToTensor(),
  transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

# 下载数据集,如果已下载数据则可以设置 download=False
train_set = datasets.CIFAR10(root="./data/CIFAR10", train=True, download=False, transform=pipline_train)
test_set = datasets.CIFAR10(root="./data/CIFAR10", train=False, download=False, transform=pipline_test)

# 加载数据集
trainloader = torch.utils.data.DataLoader(train_set, batch_size=64, shuffle=True)
testloader = torch.utils.data.DataLoader(test_set, batch_size=32, shuffle=False)

# 类别信息也是需要我们给定的
classes = ('plane', 'car', 'bird', 'cat','deer', 'dog', 'frog', 'horse', 'ship', 'truck')

2、搭建LeNet-5神经网络结构

LeNet-5是一种用于手写数字识别的卷积神经网络,由Yann LeCun和他的同事们在1998年提出。这段代码定义了一个名为LeNetRGB的PyTorch神经网络模型,它是基于经典的LeNet-5架构的变体,专门用于处理RGB(3通道)图像。下面是对代码中每一部分的详细解释:

__init__ 方法定义了网络的层结构:

  • self.conv1 = nn.Conv2d(3, 6, 5): 第一个卷积层,输入通道数为3(对应RGB图像),输出通道数为6,卷积核大小为5×5。
  • self.relu = nn.ReLU(): 定义ReLU激活函数,用于在卷积层后增加非线性。
  • self.maxpool1 = nn.MaxPool2d(2, 2): 第一个最大池化层,池化窗口大小为2×2,步长也为2。
  • self.conv2 = nn.Conv2d(6, 16, 5): 第二个卷积层,输入通道数为6(来自第一个卷积层的输出),输出通道数为16,卷积核大小为5×5。
  • self.maxpool2 = nn.MaxPool2d(2, 2): 第二个最大池化层,同样使用2×2的池化窗口和步长。
  • self.fc1 = nn.Linear(16*5*5, 120): 第一个全连接层,输入特征数为1655(来自第二个卷积层后的输出,经过池化后尺寸为5×5),输出特征数为120。
  • self.fc2 = nn.Linear(120, 84): 第二个全连接层,输入特征数为120,输出特征数为84。
  • self.fc3 = nn.Linear(84, 10): 输出层,输入特征数为84,输出特征数为10(对应10个类别)。

forward 方法定义了数据在网络中的前向传播过程:

  • 输入x首先通过第一个卷积层self.conv1
  • 然后应用ReLU激活函数self.relu
  • 接着通过第一个最大池化层self.maxpool1
  • 输入x继续通过第二个卷积层self.conv2
  • 再次应用ReLU激活函数(这里代码中没有显式写出,但在forward方法中使用了F.relu)。
  • 然后通过第二个最大池化层self.maxpool2
  • 接下来,输入x被展平(x.view(-1, 16*5*5)),以便能够输入到全连接层。
  • 输入x通过第一个全连接层self.fc1,并应用ReLU激活函数。
  • 输入x继续通过第二个全连接层self.fc2,并再次应用ReLU激活函数。
  • 最后,输入x通过输出层self.fc3
  • 输出结果通过F.log_softmax(x, dim=1)进行softmax操作,得到每个类别的概率分布。

注意:在forward方法中使用了F.reluF.log_softmax,这意味着需要从torch.nn.functional导入这些函数,即import torch.nn.functional as F

LeNet-5的提出是深度学习领域的一个重要里程碑,它不仅在手写数字识别任务上取得了显著的成功,还确立了现代卷积神经网络的基本结构。

class LeNetRGB(nn.Module):
   def __init__(self):
       super(LeNetRGB, self).__init__()
       self.conv1 = nn.Conv2d(3, 6, 5)   # 3表示输入是3通道
       self.relu = nn.ReLU()
       self.maxpool1 = nn.MaxPool2d(2, 2)
       self.conv2 = nn.Conv2d(6, 16, 5)
       self.maxpool2 = nn.MaxPool2d(2, 2)

       self.fc1 = nn.Linear(16*5*5, 120)
       self.fc2 = nn.Linear(120, 84)
       self.fc3 = nn.Linear(84, 10)

   def forward(self, x):
       x = self.conv1(x)
       x = self.relu(x)
       x = self.maxpool1(x)
       x = self.conv2(x)
       x = self.maxpool2(x)
       x = x.view(-1, 16*5*5)
       x = F.relu(self.fc1(x))
       x = F.relu(self.fc2(x))
       x = self.fc3(x)
       output = F.log_softmax(x, dim=1)
       return output

3、创建模型

# 创建模型,部署GPU/CPU
print(f"torch.cuda.is_available(): {torch.cuda.is_available()}")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = LeNetRGB().to(device)

# 定义优化器
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
# optimizer = optim.Adam(model.parameters(), lr=0.001)

4、定义训练过程

def train_runner(model, device, trainloader, optimizer, epoch):
  # 训练模型, 启用 BatchNormalization 和 Dropout, 将BatchNormalization和Dropout置为True
  model.train()
  total = 0
  correct = 0.0

  Loss = []
  Accuracy = []
   
  # enumerate迭代已加载的数据集,同时获取数据和数据下标
  for i, data in enumerate(trainloader, 0):
      inputs, labels = data
      # 把模型部署到device上
      inputs, labels = inputs.to(device), labels.to(device)
      # 初始化梯度
      optimizer.zero_grad()
      # 保存训练结果
      outputs = model(inputs)
      # 计算损失和
      # 多分类情况通常使用cross_entropy(交叉熵损失函数), 而对于二分类问题, 通常使用sigmod
      loss = F.cross_entropy(outputs, labels)
      # 获取最大概率的预测结果
      # dim=1表示返回每一行的最大值对应的列下标
      predict = outputs.argmax(dim=1)
      total += labels.size(0)
      correct += (predict == labels).sum().item()
      # 反向传播
      loss.backward()
      # 更新参数
      optimizer.step()
      if i % 1000 == 0:
          # loss.item()表示当前loss的数值
          print("Train Epoch{} \t Loss: {:.6f}, accuracy: {:.6f}%".format(epoch, loss.item(), 100*(correct/total)))
          Loss.append(loss.item())
          Accuracy.append(correct/total)
   
  return loss.item(), correct/total

5、定义测试过程

def test_runner(model, device, testloader):
  # 模型验证, 必须要写, 否则只要有输入数据, 即使不训练, 它也会改变权值
  # 因为调用eval()将不启用 BatchNormalization 和 Dropout, BatchNormalization和Dropout置为False
  model.eval()
   
  # 统计模型正确率, 设置初始值
  correct = 0.0
  test_loss = 0.0
  total = 0
   
  # torch.no_grad将不会计算梯度, 也不会进行反向传播
  with torch.no_grad():
      for data, label in testloader:
          data, label = data.to(device), label.to(device)
          output = model(data)
          test_loss += F.cross_entropy(output, label).item()
          predict = output.argmax(dim=1)
          # 计算正确数量
          total += label.size(0)
          correct += (predict == label).sum().item()
       
      # 计算损失值
      print("test_avarage_loss: {:.6f}, accuracy: {:.6f}%".format(test_loss/total, 100*(correct/total)))

5、运行LeNet-5神经网络

# 调用
epoch = 20
Loss = []
Accuracy = []

for epoch in range(1, epoch + 1):
   print("start_time", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
   loss, acc = train_runner(model, device, trainloader, optimizer, epoch)
   Loss.append(loss)
   Accuracy.append(acc)
   test_runner(model, device, testloader)
   print("end_time: ", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())), '\n')

print('Finished Training')
plt.subplot(2, 1, 1)
plt.plot(Loss)
plt.title('Loss')
plt.show()
plt.subplot(2, 1, 2)
plt.plot(Accuracy)
plt.title('Accuracy')
plt.show()

运行结果:

start_time 2024-12-06 09:09:42
Train Epoch1 Loss: 2.314643, accuracy: 15.625000%
test_avarage_loss: 0.046870, accuracy: 46.050000%
end_time: 2024-12-06 09:10:21

start_time 2024-12-06 09:10:21
Train Epoch2 Loss: 1.463546, accuracy: 46.875000%
test_avarage_loss: 0.039477, accuracy: 55.420000%
end_time: 2024-12-06 09:10:41

start_time 2024-12-06 09:10:41
Train Epoch3 Loss: 1.298694, accuracy: 50.000000%
test_avarage_loss: 0.037176, accuracy: 57.110000%
end_time: 2024-12-06 09:11:00

start_time 2024-12-06 09:11:00
Train Epoch4 Loss: 1.279239, accuracy: 51.562500%
test_avarage_loss: 0.036265, accuracy: 59.250000%
end_time: 2024-12-06 09:11:21

start_time 2024-12-06 09:11:21
Train Epoch5 Loss: 1.200579, accuracy: 62.500000%
test_avarage_loss: 0.032690, accuracy: 63.660000%
end_time: 2024-12-06 09:11:41
...
test_avarage_loss: 0.031859, accuracy: 66.090000%
end_time: 2024-12-06 09:16:36

Finished Training

执行后展示损失函数曲线图是用来衡量模型预测结果与实际标签之间差异的一个指标。损失越小,表示模型的预测结果越接近真实值。损失曲线图展示了在训练过程中,损失值随着训练步骤(或迭代次数)的变化情况。通过观察损失曲线,可以判断模型是否正在收敛,以及是否出现了过拟合或欠拟合的情况。

执行后展示准确率曲线图。准确率是衡量模型分类正确性的指标,即模型预测正确的样本数占总样本数的比例。准确率曲线图展示了在训练过程中,模型的准确率随着训练步骤的变化情况。通过观察准确率曲线,可以评估模型的性能以及是否达到了预期的分类效果。

6、预测

from PIL import Image
import numpy as np

if __name__ == '__main__':
   device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
   model = torch.load('./models/model-cifar10.pth')  # 加载模型
   model = model.to(device)
   model.eval()  # 把模型转为test模式
   
   # 读取要预测的图片
   img = Image.open("./images/test_cifar10.png").convert('RGB')  # 读取图像
   # img.show()
   plt.imshow(img)  # 显示图片
   plt.axis('off')  # 不显示坐标轴
   plt.show()
   
   # 导入图片,图片扩展后为[1,1,32,32]
   trans = transforms.Compose([
           # 将图片尺寸resize到32x32
           transforms.Resize((32, 32)),
           transforms.ToTensor(),
           transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
  ])
   img = trans(img)
   img = img.to(device)
   img = img.unsqueeze(0)  # 图片扩展多一维,因为保存模型是4维的[batch_size, 通道, 长, 宽],而普通图片只有三维[通道, 长, 宽]
   
   # 预测
   classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
   output = model(img)
   prob = F.softmax(output, dim=1)  # prob是10个分类的概率
   print("概率:", prob)
   value, predicted = torch.max(output.data, 1)
   predict = output.argmax(dim=1)
   print(predict.item())
   pred_class = classes[predicted.item()]
   print("预测类别:", pred_class)

运行结果

输入:./images/test_cifar10.png

执行结果:

概率: tensor([[9.9350e-01, 1.1108e-03, 6.4368e-04, 2.6529e-10, 9.7377e-04, 8.4973e-08,
        1.1790e-08, 5.0911e-05, 2.0891e-05, 3.6979e-03]], device='cuda:0',
      grad_fn=<SoftmaxBackward0>)
0
预测类别: plane

源码开源协议

MIT License : RedstoneWill

四、获取案例套装

需要登录后才允许下载文件包。登录 需要登录后才允许下载文件包。登录

发表评论