PyTorch深度学习实战——使用卷积神经网络执行图像分类

0. 前言

我们已经在《卷积神经网络详解》一节中介绍了传统神经网络在面对图像平移时的问题以及卷积神经网络 (Convolutional Neural Network, CNN) 的工作原理。CNN 的关键思想是通过卷积操作来提取输入数据中的特征,并使用池化操作进行降采样,以逐渐减少参数数量,从而减少计算量并提高模型的效率。在本节中,将介绍 CNN 在图像平移后如何解决错误预测的问题。

1. Fashion-MNIST 数据集图像分类

Fashion-MNIST 数据集的预处理部分与《使用PyTorch构建神经网络》一节中的代码相同,但当我们整形 (.view) 输入数据时,不是将输入展平为 28 x 28 = 784 维,而是将每个输入图像的形状重塑为 (1,28,28) (需要特别注意的是,在 PyTorch 中,首先要指定通道,然后是高度和宽度),因为卷积神经网络期望每个输入的形状为批大小 x 通道 x 高度 x 宽度

(1) 导入必要的库和数据集:

from torchvision import datasets
import torch
import matplotlib.pyplot as plt
import numpy as np
from torch.utils.data import Dataset, DataLoader
import torch
import torch.nn as nn
device = 'cuda' if torch.cuda.is_available() else 'cpu'

data_folder = './data/FMNIST'
fmnist = datasets.FashionMNIST(data_folder, download=True, train=True)

tr_images = fmnist.data
tr_targets = fmnist.targets

val_fmnist = datasets.FashionMNIST(data_folder, download=True, train=False)
val_images = val_fmnist.data
val_targets = val_fmnist.targets

(2) Fashion-MNIST 数据集类定义如下:

class FMNISTDataset(Dataset):
    def __init__(self, x, y):
        x = x.float()/255
        x = x.view(-1,1,28,28)
        self.x, self.y = x, y 
    def __getitem__(self, ix):
        x, y = self.x[ix], self.y[ix]        
        return x.to(device), y.to(device)
    def __len__(self): 
        return len(self.x)

(3) 定义 CNN 模型架构,并打印模型摘要:

from torch.optim import SGD, Adam
def get_model():
    model = nn.Sequential(
        nn.Conv2d(1, 64, kernel_size=3),
        nn.MaxPool2d(2),
        nn.ReLU(),
        nn.Conv2d(64, 128, kernel_size=3),
        nn.MaxPool2d(2),
        nn.ReLU(),
        nn.Flatten(),
        nn.Linear(3200, 256),
        nn.ReLU(),
        nn.Linear(256, 10)
    ).to(device)

    loss_fn = nn.CrossEntropyLoss()
    optimizer = Adam(model.parameters(), lr=1e-3)
    return model, loss_fn, optimizer

def train_batch(x, y, model, optimizer, loss_fn):
    prediction = model(x)
    batch_loss = loss_fn(prediction, y)
    batch_loss.backward()
    optimizer.step()
    optimizer.zero_grad()
    return batch_loss.item()

@torch.no_grad()
def accuracy(x, y, model):
    model.eval()
    prediction = model(x)
    max_values, argmaxes = prediction.max(-1)
    is_correct = argmaxes == y
    return is_correct.cpu().numpy().tolist()

def get_data():     
    train = FMNISTDataset(tr_images, tr_targets)     
    trn_dl = DataLoader(train, batch_size=32, shuffle=True)
    val = FMNISTDataset(val_images, val_targets)     
    val_dl = DataLoader(val, batch_size=len(val_images), shuffle=True)
    return trn_dl, val_dl

@torch.no_grad()
def val_loss(x, y, model, loss_fn):
    prediction = model(x)
    val_loss = loss_fn(prediction, y)
    return val_loss.item()

from torch import optim
trn_dl, val_dl = get_data()
model, loss_fn, optimizer = get_model()

from torchsummary import summary
model, loss_fn, optimizer = get_model()
print(summary(model, tuple([1,28,28])))

模型架构摘要信息如下:

----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1           [-1, 64, 26, 26]             640
         MaxPool2d-2           [-1, 64, 13, 13]               0
              ReLU-3           [-1, 64, 13, 13]               0
            Conv2d-4          [-1, 128, 11, 11]          73,856
         MaxPool2d-5            [-1, 128, 5, 5]               0
              ReLU-6            [-1, 128, 5, 5]               0
           Flatten-7                 [-1, 3200]               0
            Linear-8                  [-1, 256]         819,456
              ReLU-9                  [-1, 256]               0
           Linear-10                   [-1, 10]           2,570
================================================================
Total params: 896,522
Trainable params: 896,522
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.69
Params size (MB): 3.42
Estimated Total Size (MB): 4.11
----------------------------------------------------------------
  • 1 个网络层中:有 64 个卷积核大小为 3 的滤波器,因此有 64 x 3 x 3 个权重和 64 x 1 个偏置,共有 640 个参数
  • 4 个网络层中:有 128 个卷积核大小为 3 的滤波器,因此有 128 x 64 x3 x 3 个权重和 128 x 1 个偏置,共有 73,856 个参数
  • 8 个网络层:有 3,200 个节点的网络层连接到具有 256 个节点的另一网络层,因此有 3,200 x 256 个权重和 256 个偏置,共有 819,456 个参数
  • 10 个网络层:有 256 个节点的网络层连接到具有 10 个节点的另一网络层,因此共有 256 x 10 个权重和 10 个偏置,共有 2,570 个参数

(4) 训练模型完成后,可视化训练和测试数据集的准确率和损失变化:

train_losses, train_accuracies = [], []
val_losses, val_accuracies = [], []
for epoch in range(10):
    print(epoch)
    train_epoch_losses, train_epoch_accuracies = [], []
    for ix, batch in enumerate(iter(trn_dl)):
        x, y = batch
        batch_loss = train_batch(x, y, model, optimizer, loss_fn)
        train_epoch_losses.append(batch_loss)        
    train_epoch_loss = np.array(train_epoch_losses).mean()

    for ix, batch in enumerate(iter(trn_dl)):
        x, y = batch
        is_correct = accuracy(x, y, model)
        train_epoch_accuracies.extend(is_correct)
    train_epoch_accuracy = np.mean(train_epoch_accuracies)

    for ix, batch in enumerate(iter(val_dl)):
        x, y = batch
        val_is_correct = accuracy(x, y, model)
        validation_loss = val_loss(x, y, model, loss_fn)
    val_epoch_accuracy = np.mean(val_is_correct)

    train_losses.append(train_epoch_loss)
    train_accuracies.append(train_epoch_accuracy)
    val_losses.append(validation_loss)
    val_accuracies.append(val_epoch_accuracy)

epochs = np.arange(10)+1
import matplotlib.ticker as mtick
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
plt.subplot(121)
plt.plot(epochs, train_losses, 'bo', label='Training loss')
plt.plot(epochs, val_losses, 'r', label='Validation loss')
plt.gca().xaxis.set_major_locator(mticker.MultipleLocator(1))
plt.title('Training and validation loss with CNN')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.grid('off')
plt.subplot(122)
plt.plot(epochs, train_accuracies, 'bo', label='Training accuracy')
plt.plot(epochs, val_accuracies, 'r', label='Validation accuracy')
plt.gca().xaxis.set_major_locator(mticker.MultipleLocator(1))
plt.title('Training and validation accuracy with CNN')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.gca().set_yticklabels(['{:.0f}%'.format(x*100) for x in plt.gca().get_yticks()]) 
plt.legend()
plt.grid('off')
plt.show()

PyTorch深度学习实战——使用卷积神经网络执行图像分类-LMLPHP

在上图中,可以看到验证数据集在前 5epoch 内的准确率可以达到 92% 左右,即时没有使用额外的正则化技术,也已经比使用了增强技术的全连接网络的准确率更好。

2. 模型测试

接下来,利用训练完成的 CNN 网络预测平移图像的类别:

preds = []
ix = 24150
for px in range(-5,6):
    img = tr_images[ix]/255.
    img = img.view(28, 28)
    plt.subplot(1, 11, px+6)
    img2 = np.roll(img, px, axis=1)
    img3 = torch.Tensor(img2).view(-1,1,28,28).to(device)
    np_output = model(img3).cpu().detach().numpy()
    pred = np.exp(np_output)/np.sum(np.exp(np_output))
    preds.append(pred)
    plt.imshow(img2)
    plt.title(fmnist.classes[pred[0].argmax()])

plt.show()

在以上代码中,对图像 (img3) 进行整形,使其形状转换为 (-1,1,28,28),以便将图像输入到 CNN 模型中。

可视化平移图像的类别概率:

import seaborn as sns
fig, ax = plt.subplots(1,1, figsize=(12,10))
plt.title('Probability of each class for various translations')
sns.heatmap(np.array(preds).reshape(11,10), annot=True, ax=ax, fmt='.2f', xticklabels=fmnist.classes, yticklabels=[str(i)+str(' pixels') for i in range(-5,6)], cmap='gray')
plt.show()

PyTorch深度学习实战——使用卷积神经网络执行图像分类-LMLPHP

在上图中可以看出,即使图像平移了 4 个像素,也可以得到正确的预测结果,而全连接网络中,当图像被平移 4 个像素时,输出了完全错误的预测结果。而当图像平移 5 个像素时,CNN 预测 “Trouser” 的概率会大幅降低。因此虽然 CNN 有助于解决图像平移的问题,但它们并不能完全解决该问题,在之后的学习中,我们将学习如何利用数据增强和 CNN 来解决此问题。

相关链接

PyTorch深度学习实战(1)——神经网络与模型训练过程详解
PyTorch深度学习实战(2)——PyTorch基础
PyTorch深度学习实战(3)——使用PyTorch构建神经网络
PyTorch深度学习实战(4)——常用激活函数和损失函数详解
PyTorch深度学习实战(5)——计算机视觉基础
PyTorch深度学习实战(6)——神经网络性能优化技术
PyTorch深度学习实战(7)——批大小对神经网络训练的影响
PyTorch深度学习实战(8)——批归一化
PyTorch深度学习实战(9)——学习率优化
PyTorch深度学习实战(10)——过拟合及其解决方法
PyTorch深度学习实战(11)——卷积神经网络

08-17 13:31