Press "Enter" to skip to content

PyTorch 实战:使用卷积神经网络对照片进行分类

本文任务

我们接下来需要用CIFAR-10数据集进行分类,步骤如下:

  1. 使用torchvision 加载并预处理CIFAR-10数据集

  2. 定义网络

  3. 定义损失函数和优化器

  4. 训练网络并更新网络参数

  5. 测试网络

对卷积不了解的同学建议先阅读
10分钟理解深度学习中的~卷积~
conv2d处理的数据是什么样的?
注意:文章末尾含有项目jupyter notebook实战教程下载可供大家课后实战操作

一、CIFAR-10数据加载及预处理

CIFAR-10 是一个常用的彩色图片数据集,它有 10 个类别,分别是 airplane、automobile、bird、cat、deer、dog、frog、horse、ship和 truck 。每张图片都是 3*32*32 ,也就是 三通道彩色图片,分辨率 32*32 。

import torchvision as tv
import torchvision.transforms as transforms
from torchvision.transforms import ToPILImage
import torch as t
#可以把Tensor转化为Image,方便可视化
show = ToPILImage()
#先伪造一个图片的Tensor,用ToPILImage显示
fake_img = t.randn(3, 32, 32)
#显示图片
show(fake_img)

第一次运行torchvision会自动下载CIFAR-10数据集,大约163M。这里我将数据直接放到项目 data文件夹 中。

cifar_dataset = tv.datasets.CIFAR10(root='data',
                                    train=True,
                                    download=True
                                  )
imgdata, label = cifar_dataset[90]
print('label: ', label)
print('imgdata的类型:',type(imgdata))
imgdata

运行结果

Files already downloaded and verified
label:  2
imgdata的类型: <class 'PIL.Image.Image'>

注意,数据集中的照片数据是以 PIL.Image.Image类 形式存储的,在我们加载数据时,要注意将其转化为 Tensor类 。

def dataloader(train):
    transformer = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize(mean=(0.5, 0.5, 0.5),
                             std = (0.5, 0.5, 0.5))
    ])
    cifar_dataset = tv.datasets.CIFAR10(root='data',  #下载的数据集所在的位置
                                        train=train,  #是否为训练集。
                                        download=True, #设置为True,不用再重新下载数据
                                        transform=transformer
                                  )
    loader = t.utils.data.DataLoader(
        cifar_dataset,
        batch_size=4,
        shuffle=True, #打乱顺序
        num_workers=2 #worker数为2
    )
    return loader
classes=('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
#训练集和测试集的加载器
trainloader = dataloader(train=True)
testloader = dataloader(train=False)

运行结果

Files already downloaded and verified
Files already downloaded and verified

DataLoader是一个可迭代的对象,它将dataset返回的每一条数据样本拼接成一个batch,并提供多线程加速优化和数据打乱等操作。当程序对 cirfar_dataset 的所有数据遍历完一遍, 对Dataloader也完成了一次迭代。

dataiter = iter(trainloader)
#返回四张照片及其label
images, labels = dataiter.next()
#打印多张照片
show(tv.utils.make_grid(images))

#显示images中的第三张照片
show(images[2])

二、定义网络

最早的卷积神经网络LeNet为例,学习卷积神经网络。

2.1 第一个convolutions层

图中显示是单通道照片,但是由于我们的数据集中的照片是三通道照片。所以
该层输入的是 三通道图片 ,图片长宽均为32,那么通过kernel_size=5的卷积核卷积后的尺寸为(32-5+1)=28
同时要注意,第一个convolution中,图片由 三通道变为6通道 , 所以在此卷积过程中,in_channels=3, out_channels=6

nn.Conv2d(in_channels=3,
          out_channels=6,
          kernel_size=5)

2.2 第一subsampling层

该层输入数据是6通道,输出还为6通道,但是图片的长宽从28变为14,我们可以使用池化层来实现尺寸缩小一倍。这里我们使用MaxPool2d(2, 2)

nn.MaxPool2d(kernel_size=2,
             stride=2)

2.3 第二个convolutions层

该层输入的是6通道数据,输出为16通道数据,且图片长宽从14变为10。这里我们使用

nn.Conv2d(in_channels=6,
          out_channels=16,
          kernel_size=5)

2.4 全连接层作用

在此之前的卷积层和池化层都属于特征工程层,用于从数据中抽取特征。而之后的多个全连接层,功能类似于机器学习中的模型,用于学习特征数据中的规律,并输出预测结果。

2.5 第一全连接层full connection

第二个convolutions层输出的 数据形状为 (16, 5, 5) 的数组 ,是一个三维数据。
而在全连接层中,我们需要将其 展平为一个一维数据(样子类似于列表,长度为16\*5\*5)

nn.Linear(in_features=16*5*5,
          out_features=120) #根据图中,该输出为120

2.6 第二全连接层

该层的输入是一维数组,长度为120,输出为一维数组,长度为84.

nn.Linear(in_features=120,
          out_features=84) #根据图中,该输出为84

2.7 第三全连接层

该层的输入是一维数组,长度为84,输出为一维数组,长度为10,该层网络定义如下

nn.Linear(in_features=84,
          out_features=10) #根据图中,该输出为10

注意:
这里的长度10的列表,可以看做输出的label序列。例如理想情况下

output = [1, 0, 0, 0, 0, 0, 0 ,0, 0 ,0]

该output表示 input数据 经过该神经网络运算得到的 预测结果 显示的 类别是 第一类
同理,理想情况下

output2 = [0, 1, 0, 0, 0, 0, 0 ,0, 0 ,0]

该output2表示 input数据 经过该神经网络运算得到的 预测结果 显示的 类别是 第二类
根据前面对LeNet网络的解读,现在我们用pytorch来定义LeNet网络结构

import torch
import torch.nn as nn
class LeNet(nn.Module):
    def __init__(self):
        #Net继承nn.Module类,这里初始化调用Module中的一些方法和属性
        nn.Module.__init__(self)
        #定义特征工程网络层,用于从输入数据中进行抽象提取特征
        self.feature_engineering = nn.Sequential(
            nn.Conv2d(in_channels=3,
                      out_channels=6,
                      kernel_size=5),
            #kernel_size=2, stride=2,正好可以将图片长宽尺寸缩小为原来的一半
            nn.MaxPool2d(kernel_size=2,
                         stride=2),
            nn.Conv2d(in_channels=6,
                      out_channels=16,
                      kernel_size=5),
            nn.MaxPool2d(kernel_size=2,
                        stride=2)
        )
        #分类器层,将self.feature_engineering中的输出的数据进行拟合
        self.classifier = nn.Sequential(
            nn.Linear(in_features=16*5*5,
                      out_features=120),
            nn.Linear(in_features=120,
                      out_features=84),
            nn.Linear(in_features=84,
                      out_features=10),
        )
    def forward(self, x):
        #在Net中改写nn.Module中的forward方法。
        #这里定义的forward不是调用,我们可以理解成数据流的方向,给net输入数据inpput会按照forward提示的流程进行处理和操作并输出数据
        x = self.feature_engineering(x)
        x = x.view(-1, 16*5*5)
        x = self.classifier(x)
        return x

实例化神经网络LeNet

net = LeNet()
net

运行结果

LeNet(
  (feature_engineering): Sequential(
    (0): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
    (1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
    (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (classifier): Sequential(
    (0): Linear(in_features=400, out_features=120, bias=True)
    (1): Linear(in_features=120, out_features=84, bias=True)
    (2): Linear(in_features=84, out_features=10, bias=True)
  )
)

我们随机传入一批照片(batch_size=4) ,将其输入给net,看输出的结果是什么情况。
注意:
pytorch中输入的数据必须是batch数据(批数据)

dataiter = iter(trainloader)
#返回四张照片及其label
images, labels = dataiter.next()
outputs = net(images)
outputs

运行结果

tensor([[ 0.1963,  0.0203,  0.0887, -0.0789, -0.0027, -0.0429, -0.1119,  0.0080,
          0.0007, -0.0901],
        [ 0.2260,  0.0246,  0.0498, -0.0188,  0.0207, -0.0541, -0.0943,  0.0431,
         -0.0204, -0.1023],
        [ 0.2168,  0.0280,  0.0463, -0.0055, -0.0017, -0.0504, -0.0897,  0.0385,
         -0.0229, -0.1030],
        [ 0.2025,  0.0579,  0.0527, -0.0038, -0.0300, -0.0474, -0.0952,  0.0698,
         -0.0145, -0.0620]], grad_fn=<ThAddmmBackward>)

t.max(input, dim)

  • input:传入的tensor

  • dim: tensor的方向。dim=1表示按照行方向计算最大值

t.max(outputs, dim=1)

运行结果

(tensor([0.1963, 0.2260, 0.2168, 0.2025], grad_fn=<MaxBackward0>),
 tensor([0, 0, 0, 0]))

上述的操作,找到了outputs中四个最大的值,及其对应的index(该index可以理解为label)

三、定义损失函数和优化器

神经网络强大之处就在于 反向传播 ,通过比较 预测结果 与 真实结果 , 修整 网络参数 。
这里的 比较 就是 损失函数 ,而 修整网络参数 就是 优化器 。
这样充分利用了每个训练数据,使得网络的拟合和预测能力大大提高。

from torch import optim
#定义交叉熵损失函数
criterion = nn.CrossEntropyLoss()
#随机梯度下降SGD优化器
optimizer = optim.SGD(params = net.parameters(),
                      lr = 0.001)

四、训练网络

所有网络的训练的流程都是类似的,不断执行(轮):

  • 给网络输入数据

  • 前向传播+反向传播

  • 更新网络参数

遍历完一遍数据集称为一个epoch,这里我们进行 2个epoch 轮次的训练。

epochs = 10
average_loss_series = []
for epoch in range(epochs):
    running_loss = 0.0
    for i, data in enumerate(trainloader):
        inputs, labels = data
        #inputs, labels = Variable(inputs), Variable(labels)
        #梯度清零
        optimizer.zero_grad()
        #forward+backward
        outputs = net(inputs)
        #对比预测结果和labels,计算loss
        loss = criterion(outputs, labels)
        #反向传播
        loss.backward()
        #更新参数
        optimizer.step()
        #打印log  
        running_loss += loss.item()
        if i % 2000 == 1999: #每2000个batch打印一次训练状态
            average_loss = running_loss/2000
            print("[{0},{1}] loss:  {2}".format(epoch+1, i+1, average_loss))
            average_loss_series.append(average_loss)
            running_loss = 0.0

运行结果

[1,2000] loss:  2.284719424366951
[1,4000] loss:  2.1300598658323286
[1,6000] loss:  2.0143098856806754
[1,8000] loss:  1.9478365245759488
[1,10000] loss:  1.9135449583530426
[1,12000] loss:  1.8653237966001033
[2,2000] loss:  1.8014366626143457
[2,4000] loss:  1.737443323969841
[2,6000] loss:  1.6933535016775132
[2,8000] loss:  1.6476907352507115
[2,10000] loss:  1.6234023304879666
[2,12000] loss:  1.5863604183495044
[3,2000] loss:  1.5544855180978776
[3,4000] loss:  1.539060534775257
[3,6000] loss:  1.5500386973917484
[3,8000] loss:  1.5407403408288955
[3,10000] loss:  1.493699783280492
[3,12000] loss:  1.4957395897060632
[4,2000] loss:  1.4730096785128117
[4,4000] loss:  1.4749664356559515
[4,6000] loss:  1.4479290856420994
[4,8000] loss:  1.445657522082329
[4,10000] loss:  1.4586472637057304
[4,12000] loss:  1.4320134285390378
[5,2000] loss:  1.406113230422139
[5,4000] loss:  1.4196837954670192
[5,6000] loss:  1.3951636335104705
[5,8000] loss:  1.3933502195328473
[5,10000] loss:  1.3908299638181925
[5,12000] loss:  1.3908768535405398
[6,2000] loss:  1.3397984126955271
[6,4000] loss:  1.3737898395806551
[6,6000] loss:  1.360704499706626
[6,8000] loss:  1.3652801268100738
[6,10000] loss:  1.334371616870165
[6,12000] loss:  1.312294240474701
[7,2000] loss:  1.3097571679353714
[7,4000] loss:  1.3236577164530754
[7,6000] loss:  1.310647354334593
[7,8000] loss:  1.3016219032108785
[7,10000] loss:  1.2931814943552018
[7,12000] loss:  1.2910259604007006
[8,2000] loss:  1.2796987656354903
[8,4000] loss:  1.2650054657310248
[8,6000] loss:  1.2713083022236824
[8,8000] loss:  1.258927255064249
[8,10000] loss:  1.275728213787079
[8,12000] loss:  1.2612977192252874
[9,2000] loss:  1.2273035216629504
[9,4000] loss:  1.25000972096622
[9,6000] loss:  1.2236297953873874
[9,8000] loss:  1.2251979489773512
[9,10000] loss:  1.2623697004914283
[9,12000] loss:  1.2501848887503146
[10,2000] loss:  1.2257770787626505
[10,4000] loss:  1.2277075409144163
[10,6000] loss:  1.2050671626776457
[10,8000] loss:  1.2159633481949568
[10,10000] loss:  1.210464821562171
[10,12000] loss:  1.2225491935014725

五、测试网络

5.1 打印误差曲线

%matplotlib inline
import matplotlib.pyplot as plt
x = range(0, 60)
plt.figure()
plt.plot(x, average_loss_series)

5.2 查看训练的准确率

我们使用测试集检验训练的神经网络的性能。

def correct_rate(net, testloader):
    correct = 0
    total = 0
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = t.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted==labels).sum()
    return 100*correct/total
correct = correct_rate(net, testloader)
print('10000张测试集中准确率为: {}%'.format(correct))

运行结果

10000张测试集中准确率为: 57%

数据集一共有10种照片,且每种照片数量相等。所以理论上,我们猜测对每一张照片的概率为10%。
而通过我们神经网络LeNet预测的准确率达到 57% ,证明网络确实学习到了规律。

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注