在彩色图像数据集上的应用#

import torchvision
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np

CIFAR-10 数据集介绍#

在本次应用中,我们将使用 CIFAR-10 数据集。该数据集包含 \(60,000\) 张彩色图像,每张图像的大小为 \(3 \times 32 \times 32\),分为 \(10\) 个类别。 类别包括:飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船和卡车。 我们将使用 torchvision 加载该数据集:

classes = ('plane', 'car', 'bird', 'cat','deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# Transformation des données, normalisation et transformation en tensor pytorch
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

# Téléchargement et chargement du dataset
dataset = torchvision.datasets.CIFAR10(root='./../data', train=True,download=True, transform=transform)
testdataset = torchvision.datasets.CIFAR10(root='./../data', train=False,download=True, transform=transform)
print("taille d'une image : ",dataset[0][0].shape)

#Création des dataloaders pour le train, validation et test
train_dataset, val_dataset=torch.utils.data.random_split(dataset, [0.8,0.2])
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16,shuffle=True, num_workers=2)
val_loader= torch.utils.data.DataLoader(val_dataset, batch_size=16,shuffle=True, num_workers=2)
test_loader = torch.utils.data.DataLoader(testdataset, batch_size=16,shuffle=False, num_workers=2)
Files already downloaded and verified
Files already downloaded and verified
taille d'une image :  torch.Size([3, 32, 32])

我们可以可视化图像及其对应的类别:

def imshow_with_labels(img, labels):
  img = img / 2 + 0.5  # Dénormaliser
  npimg = img.numpy()
  plt.imshow(np.transpose(npimg, (1, 2, 0)))
  plt.xticks([])  # Supprimer les graduations sur l'axe des x
  plt.yticks([])  # Supprimer les graduations sur l'axe des y
  for i in range(len(labels)):
    plt.text(i * 35, -2, classes[labels[i]], color='black', fontsize=8, ha='left')

# Récupération d'un batch d'images
images, labels = next(iter(train_loader))

# Affichage des images avec leurs classes
imshow_with_labels(torchvision.utils.make_grid(images[:8]), labels[:8])
plt.show()
../_images/b633fc4d448fcdb191125aca39c0fb7af50fcfc1bb8279cd726e4e10cac224ae.png

设计用于该问题的卷积神经网络#

在卷积的相关课程中,我们学习了 步长(stride) 参数,它表示卷积操作的步幅。当 步长 设置为 \(2\) 时,图像的分辨率在操作后会减半(前提是 填充(padding) 能够补偿滤波器的大小)。因此,可以通过增加 步长 来替代 池化(pooling) 操作。 使用 步长\(1\) 的卷积层后接一个 池化 层,其输出维度与使用 步长\(2\) 的单独卷积层的输出维度相同。

在本次应用中,我们将用增加卷积层的 步长 来替代 池化 层。 更多详情,请参阅此 博客文章论文

class cnn(nn.Module):
  def __init__(self, *args, **kwargs) -> None:
    super().__init__(*args, **kwargs)
    self.conv1=nn.Conv2d(3,8,kernel_size=3,stride=2,padding=1)
    self.conv2=nn.Conv2d(8,16,kernel_size=3,stride=2,padding=1)
    self.conv3=nn.Conv2d(16,32,kernel_size=3,stride=2,padding=1)
    self.fc=nn.Linear(4*4*32,10)
  
  def forward(self,x):
    x=F.relu(self.conv1(x))
    x=F.relu(self.conv2(x))
    x=F.relu(self.conv3(x))
    x=x.view(-1,4*4*32)
    output=self.fc(x)
    return output
model = cnn() # Couches d'entrée de taille 2, deux couches cachées de 16 neurones et un neurone de sortie
print(model)
print("Nombre de paramètres", sum(p.numel() for p in model.parameters()))
cnn(
  (conv1): Conv2d(3, 8, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
  (conv2): Conv2d(8, 16, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
  (conv3): Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
  (fc): Linear(in_features=512, out_features=10, bias=True)
)
Nombre de paramètres 11162

模型训练#

criterion = nn.CrossEntropyLoss()
epochs=5
learning_rate=0.001
optimizer=torch.optim.Adam(model.parameters(),lr=learning_rate)
for i in range(epochs):
  loss_train=0
  for images, labels in train_loader:
    preds=model(images)
    loss=criterion(preds,labels)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    loss_train+=loss   
  if i % 1 == 0:
    print(f"step {i} train loss {loss_train/len(train_loader)}")
  loss_val=0    
  for images, labels in val_loader:
    with torch.no_grad(): # permet de ne pas calculer les gradients
      preds=model(images)
      loss=criterion(preds,labels)
      loss_val+=loss 
  if i % 1 == 0:
    print(f"step {i} val loss {loss_val/len(val_loader)}")
step 0 train loss 1.5988761186599731
step 0 val loss 1.4532517194747925
step 1 train loss 1.3778905868530273
step 1 val loss 1.3579093217849731
step 2 train loss 1.2898519039154053
step 2 val loss 1.2919617891311646
step 3 train loss 1.2295998334884644
step 3 val loss 1.256637692451477
step 4 train loss 1.186734914779663
step 4 val loss 1.240902304649353

现在我们可以验证模型在测试数据上的准确率:

correct = 0
total = 0
for images,labels in test_loader: 
  with torch.no_grad():
    preds=model(images)
    
    _, predicted = torch.max(preds.data, 1)
    total += labels.size(0)
    correct += (predicted == labels).sum().item()     
test_acc = 100 * correct / total
print("Précision du modèle en phase de test : ",test_acc)
Précision du modèle en phase de test :  55.92

使用 GPU#

如果您的电脑配备了 GPU,或能够使用带有 GPU 的云服务,则可以加速模型的训练和推理过程。 以下是在 PyTorch 中使用 GPU 的方法:

model = cnn().to('cuda') # Ajouter le .to('cuda') pour charger le modèle sur GPU
criterion = nn.CrossEntropyLoss()
epochs=5
learning_rate=0.001
optimizer=torch.optim.Adam(model.parameters(),lr=learning_rate)
for i in range(epochs):
  loss_train=0
  for images, labels in train_loader:
    images=images.to('cuda') # Ajouter le .to('cuda') pour charger les images sur GPU
    labels=labels.to('cuda') # Ajouter le .to('cuda') pour charger les labels sur GPU
    preds=model(images)
    loss=criterion(preds,labels)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    loss_train+=loss   
  if i % 1 == 0:
    print(f"step {i} train loss {loss_train/len(train_loader)}")
  loss_val=0    
  for images, labels in val_loader:
    with torch.no_grad():
      images=images.to('cuda')
      labels=labels.to('cuda')
      preds=model(images)
      loss=criterion(preds,labels)
      loss_val+=loss 
  if i % 1 == 0:
    print(f"step {i} val loss {loss_val/len(val_loader)}")
/home/aquilae/anaconda3/envs/dev/lib/python3.11/site-packages/torch/autograd/graph.py:744: UserWarning: Plan failed with a cudnnException: CUDNN_BACKEND_EXECUTION_PLAN_DESCRIPTOR: cudnnFinalize Descriptor Failed cudnn_status: CUDNN_STATUS_NOT_SUPPORTED (Triggered internally at ../aten/src/ATen/native/cudnn/Conv_v8.cpp:919.)
  return Variable._execution_engine.run_backward(  # Calls into the C++ engine to run the backward pass
step 0 train loss 1.6380540132522583
step 0 val loss 1.4465171098709106
step 1 train loss 1.369913101196289
step 1 val loss 1.3735681772232056
step 2 train loss 1.2817057371139526
step 2 val loss 1.2942956686019897
step 3 train loss 1.2238909006118774
step 3 val loss 1.2601954936981201
step 4 train loss 1.186323881149292
step 4 val loss 1.2583365440368652
correct = 0
total = 0
for images,labels in test_loader: 
  images=images.to('cuda')
  labels=labels.to('cuda')
  with torch.no_grad():
    preds=model(images)
    
    _, predicted = torch.max(preds.data, 1)
    total += labels.size(0)
    correct += (predicted == labels).sum().item()     
test_acc = 100 * correct / total
print("Précision du modèle en phase de test : ",test_acc)
Précision du modèle en phase de test :  55.76

根据您的 GPU 类型,您可能已经注意到模型的训练和推理速度有所提升。对于性能强大的 GPU 以及深度且可并行化的模型,这种速度差异会更加显著。

实践练习#

为了提升实践能力,您可以尝试改进模型在 CIFAR-10 数据集上的性能。以下是一些建议:

  • 增加网络层数

  • 调整每层的滤波器数量

  • 添加 Dropout

  • 使用 Batch Normalization

  • 增加训练轮数(epochs)

  • 调整学习率(learning rate)

尝试将模型的准确率提升至至少 \(70\%\)