34

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

 5 years ago
source link: https://mp.weixin.qq.com/s/wp8nZ_b9zsUdwqzm6ys-ng?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

AFR773N.jpg!web

本文任务

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

em6RZ3Z.png!web

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

二、定义网络

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

7B3QfeN.jpg!web

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)

ZnAv22f.png!web

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库解决网页乱码问题

bQN3U3N.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK