构建第一个卷积神经网络模型的实践指南

技术专业 · 08-17

说明

本文根据《# A Hands-on Guide to Build Your First Convolutional Neural Network Model》翻译整理。

 概述

本文将简要讨论 CNN,这是一种专门为图像相关任务设计的神经网络的特殊变体。本文将主要围绕CNN的实施部分进行介绍。我们已尽最大努力使本文具有交互性和简单性。希望你喜欢它。祝您学习愉快!!

 

 介绍

卷积神经网络由 Yann LeCun 和 Yoshua Bengio 于 1995 年引入,后来被证明在图像领域显示出卓越的结果。那么,与普通神经网络相比,当应用于图像域时,是什么让它们与众不同呢?我将用一个简单的例子来解释其中一个原因。考虑到您的任务是对手写数字的图像进行分类,下面给出了一些来自训练集的样本。

如果你正确地观察,你会发现所有的数字都出现在各自图像的中心。如果测试图像是相似的类型,则使用这些图像训练普通神经网络模型可能会产生良好的结果。但是,如果测试图像如下所示,该怎么办?

 
在这里,数字 9 出现在图像的一角。如果我们使用一个简单的神经网络模型来对这个图像进行分类,我们的模型可能会突然失败。但是,如果将相同的测试图像提供给CNN模型,则它很有可能正确分类。性能更好的原因是它在图像中查找空间特征。对于上述情况本身,即使数字 9 位于帧的左上角,经过训练的 CNN 模型也会捕获图像中的特征,并且很可能预测该数字是数字 9。普通的神经网络无法做到这种魔术。现在让我们简要讨论一下,讨论一下CNN的主要组成部分。

CNN模型架构中的主要组件

 
这是一个简单的 CNN 模型,用于对图像是否包含猫进行分类。因此,CNN的主要组成部分是:

 1. 卷积层

 2. 池化层

 3.全连接层

 卷积层

卷积层帮助我们提取图像中存在的特征。这种提取是在过滤器的帮助下实现的。请注意以下操作。

 
在这里,我们可以看到一个窗口在整个图像上滑动,其中图像表示为网格(这就是计算机查看图像的方式,其中网格充满了数字!现在让我们看看计算是如何在卷积运算中进行的。

 
假设输入特征图是我们的图像,卷积滤波器是我们要滑过的窗口。现在让我们观察卷积运算的一个实例。

 
当卷积滤波器叠加在图像上时,相应的元素会相乘。然后,将相乘的值相加,得到一个值,该值将填充到输出特征图中。此操作将继续进行,直到我们将窗口滑过输入特征图,从而填充输出特征图。

 池化层

使用池化层背后的想法是减小特征图的维度。对于下面给出的表示,我们使用了 2*2 的最大池化层。每次窗口滑过图像时,我们都会取窗口内存在的最大值。

最后,在 max pool 操作之后,我们可以看到输入的维度,即 44 已缩小到 22。

 全连接层

如前所述,该层位于 CNN 模型架构的尾部。全连接层的输入是使用卷积滤波器提取的丰富特征。然后将其向前传播到输出层,在那里我们得到输入图像属于不同类别的概率。预测的输出是模型预测的概率最高的类。

 代码实现

在这里,我们将时尚MNIST作为我们的问题数据集。该数据集包含 T 恤、裤子、套头衫、连衣裙、外套、凉鞋、衬衫、运动鞋、包和踝靴。任务是在训练模型后将给定图像分类为上述类。

我们将在 Google Colab 中实现代码,因为它们在固定的时间段内提供免费 GPU 资源的使用。如果您不熟悉 Colab 环境和 GPU,请查看此博客以获得更好的想法。下面给出的是我们将要构建的 CNN 的架构。

步骤 1:导入必要的库

import os
import torch
import torchvision
import tarfile
from torchvision import transforms
from torch.utils.data import random_split
from torch.utils.data.dataloader import DataLoader
import torch.nn as nn
from torch.nn import functional as F
from itertools import chain

步骤 -2:下载训练和测试数据集

train_set = torchvision.datasets.FashionMNIST("/usr", download=True, transform=

                                            transforms.Compose([transforms.ToTensor()]))

test_set = torchvision.datasets.FashionMNIST("./data", download=True, train=False, transform=

                                           transforms.Compose([transforms.ToTensor()]))

步骤 3:将训练集拆分为训练和验证 

train_size = 48000
val_size = 60000 - train_size
train_ds,val_ds = random_split(train_set,[train_size,val_size])

步骤 4:使用 Dataloader 将数据集加载到内存中

train_dl = DataLoader(train_ds,batch_size=20,shuffle=True)
val_dl = DataLoader(val_ds,batch_size=20,shuffle=True)
classes = train_set.classes

现在让我们可视化加载的数据,

for imgs,labels in train_dl:
for img in imgs:

arr_ = np.squeeze(img) 
plt.show()
break

break

步骤 -5:定义体系结构

import torch.nn as nn
import torch.nn.functional as F

define the CNN architecture

class Net(nn.Module):

def __init__(self):
    super(Net, self).__init__()
    #convolutional layer-1
    self.conv1 = nn.Conv2d(1,6,5, padding=0)
    #convolutional layer-2
    self.conv2 = nn.Conv2d(6,10,5,padding=0)
    # max pooling layer
    self.pool = nn.MaxPool2d(2, 2)
    # Fully connected layer 1
    self.ff1 = nn.Linear(4*4*10,56)
    # Fully connected layer 2
    self.ff2 = nn.Linear(56,10)

def forward(self, x):
    # adding sequence of convolutional and max pooling layers
    #input dim-28*28*1
    x = self.conv1(x)
    # After convolution operation, output dim - 24*24*6
    x = self.pool(x)
    # After Max pool operation output dim - 12*12*6
    x = self.conv2(x)
    # After convolution operation  output dim - 8*8*10
    x = self.pool(x)
    # max pool output dim 4*4*10
    x = x.view(-1,4*4*10) # Reshaping the values to a shape appropriate to the input of fully connected layer
    x = F.relu(self.ff1(x)) # Applying Relu to the output of first layer
    x = F.sigmoid(self.ff2(x)) # Applying sigmoid to the output of second layer
    return x

create a complete CNN

model_scratch = Net()
print(model)

move tensors to GPU if CUDA is available

if use_cuda:

model_scratch.cuda()

第 6 步 – 定义损失函数

Loss function

import torch.nn as nn
import torch.optim as optim
criterion_scratch = nn.CrossEntropyLoss()
def get_optimizer_scratch(model):

optimizer = optim.SGD(model.parameters(),lr = 0.04)
return optimizer

第 7 步 – 实施训练和验证算法

Implementing the training algorithm

def train(n_epochs, loaders, model, optimizer, criterion, use_cuda, save_path):

"""returns trained model"""
# initialize tracker for minimum validation loss
valid_loss_min = np.Inf 
for epoch in range(1, n_epochs+1):
    # initialize variables to monitor training and validation loss
    train_loss = 0.0
    valid_loss = 0.0
    # train phase #
    # setting the module to training mode
    model.train()
    for batch_idx, (data, target) in enumerate(loaders['train']):
        # move to GPU
        if use_cuda:
            data, target = data.cuda(), target.cuda()
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
        train_loss = train_loss + ((1 / (batch_idx + 1)) * (loss.data.item() - train_loss))
    # validate the model #
    # set the model to evaluation mode
    model.eval()
    for batch_idx, (data, target) in enumerate(loaders['valid']):
        # move to GPU
        if use_cuda:
            data, target = data.cuda(), target.cuda()
        output = model(data)
        loss = criterion(output, target)
        valid_loss = valid_loss + ((1 / (batch_idx + 1)) * (loss.data.item() - valid_loss))

print training/validation statistics

    print('Epoch: {} tTraining Loss: {:.6f} tValidation Loss: {:.6f}'.format(
        epoch, 
        train_loss,
        valid_loss
        ))

If the valiation loss has decreased, then saving the model

    if valid_loss <= valid_loss_min:
        print('Validation loss decreased ({:.6f} --> {:.6f}).  Saving model ...'.format(
        valid_loss_min,
        valid_loss))
        torch.save(model.state_dict(), save_path)
        valid_loss_min = valid_loss 
return model

第 8 步:培训和评估阶段

num_epochs = 15
model_scratch = train(num_epochs, loaders_scratch, model_scratch, get_optimizer_scratch(model_scratch),

                  criterion_scratch, use_cuda, 'model_scratch.pt')

请注意,当每次验证损失减少时,我们都会保存模型的状态。

 第 9 步:测试阶段

def test(loaders, model, criterion, use_cuda):

monitor test loss and accuracy

test_loss = 0.
correct = 0.
total = 0.

set the module to evaluation mode

model.eval()
for batch_idx, (data, target) in enumerate(loaders['test']):
      # move to GPU
      if use_cuda:
        data, target = data.cuda(), target.cuda()
      # forward pass: compute predicted outputs by passing inputs to the model
      output = model(data)
      # calculate the loss
      loss = criterion(output, target)
      # update average test loss 
      test_loss = test_loss + ((1 / (batch_idx + 1)) * (loss.data.item() - test_loss))
      # convert output probabilities to predicted class
      pred = output.data.max(1, keepdim=True)[1]
      # compare predictions to true label
    correct += np.sum(np.squeeze(pred.eq(target.data.view_as(pred)),axis=1).cpu().numpy())
    total += data.size(0)

print('Test Loss: {:.6f}n'.format(test_loss))
print('nTest Accuracy: %2d%% (%2d/%2d)' % (

    100. * correct / total, correct, total))

load the model that got the best validation accuracy

model_scratch.load_state_dict(torch.load('model_scratch.pt'))
test(loaders_scratch, model_scratch, criterion_scratch, use_cuda)

第 10 步:使用样品进行测试

为使用单个图像测试模型而定义的函数

def predict_image(img, model):

# Convert to a batch of 1
xb = img.unsqueeze(0)
# Get predictions from model
yb = model(xb)
# Pick index with highest probability
_, preds  = torch.max(yb, dim=1)
# printing the image
plt.imshow(img.squeeze( ))
#returning the class label related to the image
return train_set.classes[preds[0].item()]

img,label = test_set[9]
predict_image(img,model_scratch)

 结论

在这里,我们简要讨论了卷积神经网络中的主要操作及其架构。还实现了一个简单的卷积神经网络模型,以更好地了解实际用例。你可以在我的 GitHub 仓库中找到已实现的代码。此外,您可以通过增强数据集来提高已实现模型的性能,在架构的全连接层中使用正则化技术,例如批量归一化和丢弃。另外,请记住,也可以使用预训练的 CNN 模型,这些模型是使用大型数据集进行训练的。通过使用这些最先进的模型,您肯定会获得给定问题的最佳度量分数。

 引用

  1. https://www.youtube.com/watch?v=EHuACSjijbI – 木星
  2. https://www.youtube.com/watch?v=2-Ol7ZB0MmU&t=1503s-A 卷积神经网络和图像识别的友好介绍

 关于作者

我叫Adwait Dathan,目前正在攻读人工智能和数据科学硕士学位。请随时通过LinkedIn与我联系

Theme Jasmine by Kent Liao