查看原文
其他

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

大邓 大邓和他的Python 2019-04-26

本文任务

我们接下来需要用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(33232)

#显示图片
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.50.50.5),
                             std = (0.50.50.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 = [1000000 ,00 ,0]

该output表示 input数据 经过该神经网络运算得到的 预测结果 显示的类别是 第一类

同理,理想情况下

output2 = [0100000 ,00 ,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(-116*5*5)
        x = self.classifier(x)
        return x

实例化神经网络LeNet

net = LeNet()
net

运行结果

LeNet(
  (feature_engineering): Sequential(
    (0): Conv2d(36, kernel_size=(55), stride=(11))
    (1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (2): Conv2d(616, kernel_size=(55), stride=(11))
    (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.19630.22600.21680.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(060)
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%,证明网络确实学习到了规律。

往期文章

《用Python做文本分析》视频教程   

大邓强力推荐-jupyter notebook使用小技巧  

10分钟理解深度学习中的~卷积~  

深度学习之 图解LSTM  

Pytorch实战:使用RNN网络对姓名进行分类 

100G Python学习资料(免费下载) 

100G 文本分析语料资源(免费下载)    

typing库:让你的代码阅读者再也不用猜猜猜  

Seaborn官方教程中文教程(一)

数据清洗 常用正则表达式大全

PySimpleGUI: 开发自己第一个软件

深度特征合成:自动生成机器学习中的特征

Python 3.7中dataclass的终极指南(一) 

Python 3.7中dataclass的终极指南(二) 

15个最好的数据科学领域Python库    

使用Pandas更好的做数据科学

[计算消费者的偏好]推荐系统与协同过滤、奇异值分解

机器学习: 识别图片中的数字

应用PCA降维加速模型训练

如何从文本中提取特征信息?

使用sklearn做自然语言处理-1 

使用sklearn做自然语言处理-2

机器学习|八大步骤解决90%的NLP问题    

Python圈中的符号计算库-Sympy

Python中处理日期时间库的使用方法 

【视频讲解】Scrapy递归抓取简书用户信息

美团商家信息采集神器 

用chardect库解决网页乱码问题 



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存