深度学习-图像分类篇 P2.1 pytorch官方demo

哔哩哔哩视频链接
up主附的代码链接

(一)预备知识

1、demo代码地址:
我们将以这个图片分类代码《Training a Classifier》为例:
在这里插入图片描述点进去之后我们看一下代码大概是这样,数据集我们选择的是Cifar 10:
在这里插入图片描述
2、回顾LeNet网络
在这里插入图片描述可以看到LeNet的网络结构主要是:卷积-下采样-卷积-下采样-3个全连接层。
而我们使用的cifar10数据集是彩色图片,有RGB三个通道,那么我们的input应该是3X32X32。
特别地,在Pytorch中的通道排序是[batch, channel, height, width],其中batch指一批图像的个数,如果我们将cifar10数据集按每批30个图片来分,那么batch=30,深度channel=3(因为有RGB三个通道),height=32,width=32。

(二)代码剖析–model.py

1、我们将模型代码、预测代码、训练代码放到一个文件夹下,先来看model.py:
在这里插入图片描述首先定义类LeNet,其继承与nn.Module类,实现两个方法:初始化函数init()和正向传播函数forward()。
(1)Conv2d()方法
Conv2d是定义卷积层的方法,当你想知道这个方法具体怎么用时,进入pytorch官网找到Docs后,在左边搜索栏中输入Conv2d进行检索(或者选中这个单词,然后ctrl+左键单击即可查看源码),点击torch.nn.Conv2d即可查看相关参数的定义。
在这里插入图片描述
in_channels代表的是输入特征矩阵的深度,比如第一个卷积层输入RGB图像,那么深度=3,即in_channels=3;
out_channels对应的是卷积核的个数,在之前的规律中,我们已知:使用几个卷积核,就会生成深度为多少维的特征矩阵,所以out_channels=特征核个数;
kernel_size=卷积核的大小;
stride=步距;
padding是补0处理,默认为0;bias是偏置,默认使用。
在这里插入图片描述
也就是说,第8行代码的意思是:该卷积层的channel=3,卷积核个数=16,卷积核的size=5X5。


在这里插入图片描述
如果将dilation默认值1代入到式子中去,可以得到我们之前得到的式子:
在这里插入图片描述
根据:
在这里插入图片描述
我们知道输入的是3X32X32大小的图片,那么W=32;
上面的Conv2d的输入,我们知道F=卷积核大小=5,P=padding=0,S=stride=1,那么N=28。
因为Conv2d的输入我们选择的是16个卷积核,所以output的channel=16,所以output(16,28,28),请注意,我们的input和output暂时忽略了batch,所以格式是[channel,height,width]。
(2)MaxPool2d()方法
Conv2d是定义下采样层的方法,类似地,我们ctrl+单击左键跳转到代码定义(如果有继承的父类,那再对父类ctrl+左键单击查看):
在这里插入图片描述
kernel_size=池化盒的大小,stride=步距(不写的话默认=kernel_size),padding=0。
在这里插入图片描述
也就是说,第9行代码的意思是:定义了一个池化盒大小为2,步距也为2的最大池化操作。
原output作为输入(16,28,28),通过这个操作后,宽度和高度都变成了原来的一半,因为池化层只改变特征矩阵的宽度和高度,不改变深度,所以channel不变,还是16。所以这时x的输出为(16,14,14)。
(3)conv2操作
在这里插入图片描述
定义第二个卷积层conv2,首先因为上一步输出的深度为16,所以这一步作为深度为16的输入。
这一步我们采用32个卷积核(channel=32),卷积核的size为5X5。
然后我们重新计算一下N,因为上一步输出的宽度和高度为14,所以W=14,F=5,P=0,S=1,那么N=10。
所以这一步输出(32,10,10)。
在这里插入图片描述
(4)pool2操作
在这里插入图片描述
同样地,定义一个池化盒的大小kernel_size=2,步距stride=2的最大池化操作。通过这个操作后,宽度、高度都缩减为原来的一半,那么输出为(32,5,5)。
在这里插入图片描述
(5)Linear()函数
接下来要进行全连接层的输入,要求是一个一维向量,但是我们这里是32X5X5的,需要先进行展平操作,(因为batch没有,所以这里写-1)
在这里插入图片描述
另外根据图中可以看出C5:layer=120,所以我们第二个参数是120。
在这里插入图片描述同样地,fc2的第一个参数就是上面的120,第二个参数根据图中F6:layer可以得知是64;fc3的第一个参数是64,第二个参数根据你要分的类数,因为我们最后要将它们分为10类,所以第二个参数是10。
在这里插入图片描述
(6)forward()函数
先将conv1使用relu激活函数输出新的x,然后将x放入下采样层,然后再使用relu激活函数输出新的x,这时进入第二个下采样层,接下来是要与全连接层进行拼接,所以先用view()方法进行展平,然后使用relu激活函数输出新的x进入第二个全连接层,再relu激活函数输出新的x进入第三个全连接层,最后输出。

2、写一段代码测试一下:
input1是随机生成的batch=32,深度channel=3,宽度=高度=32的图像集,调用LeNet,我们右键Debug运行并将断点打在如下位置:
在这里插入图片描述
在下方我们选择“调试器”,然后可以看到x下的变量shape如我们输入的一样,
在这里插入图片描述
按下下一步执行键:
在这里插入图片描述在这里插入图片描述
接着下一步:
在这里插入图片描述在这里插入图片描述在这里插入图片描述
而控制台输出的是:
在这里插入图片描述
与我们的设想一致!

(二)代码剖析–train.py

train.py要调用model.py中训练好的LeNet()网络
1、先下载Cifar10数据集到train_set,root表示下载到当前目录下的data文件夹中去;train=true表示需要cifar10这个数据集;download第一次下载数据时改为true,下完以后改为false;transform是对图片进行预处理操作。
可以看到transform使用了两个预处理方法即ToTensor()和Normalize(),使用ctrl+左键单击进入方法源码。
可以看到对ToTensor()的解释是:可以将图片或一个numpy从H x W x C(每个维度像素值为[0, 255])变成C x H x W(每个维度像素值为[0.0, 1.0]),转变成一个Tensor。
可以看到对Normalize()的解释是:使用均值和标准差来归一化。

    transform = transforms.Compose(
        [transforms.ToTensor(),
         transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

    # 50000张训练图片
    # 第一次使用时要将download设置为True才会自动去下载数据集
    train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
                                             download=True, transform=transform)

这时可以直接运行试试,现在就会开始下载cifar10数据集:
在这里插入图片描述
2、(1)将上面下载的图片集train_set分成每个batch为36的批次,shuffle=true表示分批时可以打乱。

train_loader = torch.utils.data.DataLoader(train_set, batch_size=36,
                                               shuffle=True, num_workers=0)

(2)同样的方法,我们下载10000张验证图片,使用iter()方法将它转换成迭代器,然后使用next()方法将迭代器中的图片、label取出来。

 # 10000张验证图片
    # 第一次使用时要将download设置为True才会自动去下载数据集
    val_set = torchvision.datasets.CIFAR10(root='./data', train=False,
                                           download=False, transform=transform)
    val_loader = torch.utils.data.DataLoader(val_set, batch_size=5000,
                                              shuffle=False, num_workers=0)
    val_data_iter = iter(val_loader)
    val_image, val_label = val_data_iter.next()

(3)因为是cifar10,也就是说这里有10类数据,我们定义一个classes元组,将10个种类都写出来:

classes = ('plane', 'car', 'bird', 'cat',
       'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

3、然后来测试一下这个数据集图片及标签
在这里插入图片描述
(1)首先根据官方给的例子,导入matplotlib包和numpy包,然后将以下代码复制,更改标签名、图像名:

    def imshow(img):
        img = img / 2 + 0.5    #反归一化
        npimg = img.numpy() #改成numpy格式
        plt.imshow(np.transpose(npimg,(1,2,0))) #因为前面的ToTensor()方法将图片从H x W x C(每个维度像素值为[0, 255])变成C x H x W,
        #(channel,width,height)下标是(0,1,2),现在要改成本来的序列H x W x C,就是(1,2,0)
        plt.show()
    
    #下面的是调用函数代码,打印标签和图像的    
    print(''.join('%5s' %classes[val_label[j]] for j in range(4)))
    imshow(torchvision.utils.make_grid(val_image))

(2)此外,我们还要将var_loader的batchsize改为4,以便显示。

val_loader = torch.utils.data.DataLoader(val_set, batch_size=4,
                                             shuffle=False, num_workers=0)

(3)识别成功,识别四张图片分别是cat、ship、ship、plane
在这里插入图片描述
4、定义网络、损失函数、优化器
删掉第3步的代码,接下来实例化LeNet网络,并用CrossEntropyLoss()方法定义损失函数(这个方法中包括了softmax和loss函数),使用Adam优化器,并定义学习率lr=0.001。

    net = LeNet()
    loss_function = nn.CrossEntropyLoss()
    optimizer = optim.Adam(net.parameters(), lr=0.001)

5、接下来进入迭代训练的过程。
(1)range(5)意味着我们每轮要迭代5次,定义runnning_loss用来累积损失。然后再进入一个循环遍历训练集样本,不仅返回每一批训练完的数据data,还会返回这一批数据对应的步数step。
(2)在内部的这层循环中,我们将data分为输入的图像inputs和对应的标签labels;利用zero_grad()函数将所有历史梯度清零,每计算一个batch,就需要调用一次optimizer.zero_grad()函数清除历史梯度。
在这里插入图片描述
(3)然后将输入的图片inputs放到网络中正向传播,返回预测标签放到outputs中。然后用预测值outputs和真实值labels放到损失函数中累计损失loss,然后用loss进行反向传播。最后用optimizer.step()进行参数更新。
(4)然后每500步进行一次信息打印,在这个500步的训练中,我们将训练集传入net(),计算输出的预测标签predict_y与真实标签val_label相等的数量,计算准确率。打印信息的时候就要打印迭代轮数、第多少步、平均损失、准确率。

 #接下来进入迭代训练的过程,每一轮训练有5次迭代
    for epoch in range(5):  # loop over the dataset multiple times

        running_loss = 0.0 #用来累计损失的
        for step, data in enumerate(train_loader, start=0):
            #(1) get the inputs; data is a list of [inputs, labels] 将数据分别取出图像放到inputs中,取出标签放到labels
            inputs, labels = data

            # (2)zero the parameter gradients 清除历史梯度
            optimizer.zero_grad()
            # (3)forward + backward + optimize 将inputs传入定义的网络中输出预测标签outputs,loss用来累计真实标签与预测标签的误差
            outputs = net(inputs)
            loss = loss_function(outputs, labels)
            loss.backward()
            optimizer.step()#参数优化

            # (4)print statistics
            running_loss += loss.item() #将每次产生的loss累计到running_loss中
            if step % 500 == 499:    # print every 500 mini-batches每隔500步打印一次训练的信息
                with torch.no_grad(): #with是一个上下文管理器 no_grad()函数可以保证不用每次都算每个节点的损失梯度
                    outputs = net(val_image)  # [batch, 10]将测试集的图片传入网络输出预测标签outputs
                    predict_y = torch.max(outputs, dim=1)[1] #找出 输出预测标签 与 第1维中输出的最大的index(第0维是batch,第1维是对应的10个节点也就是10种种类)
                    # 预测对了的标签的总数(因为是tensor格式所以要用item()方法转为数字)除以测试样本的数目就得到了准确率accuracy
                    accuracy = (predict_y == val_label).sum().item() / val_label.size(0)

                    #打印训练得到的参数,epoch表示训练到第几轮了;step表示在某一轮的第几步;然后是500步的平均误差;测试样本的准确率;
                    print('[%d, %5d] train_loss: %.3f  test_accuracy: %.3f' %
                          (epoch + 1, step + 1, running_loss / 500, accuracy))
                    running_loss = 0.0 #然后将误差清零,进行下一个500步的计算

    print('Finished Training')

6、训练结束,将网络中所有参数进行保存,保存路径为save_path。

save_path = './Lenet.pth'
    torch.save(net.state_dict(), save_path)

启动训练过程,训练完成:
在这里插入图片描述并生成Lenet.pth:
在这里插入图片描述

(二)代码剖析–predict.py

这个文件就是用来调用模型权重预测的。
1、在网上下载了一张飞机的图片,命名为1.jpg,放在项目文件夹下。
2、(1)导入相应的包之后,用Resize()方法将下载好的图片缩放处理成32X32的,然后转为向量并归一化。

transform = transforms.Compose(
    [transforms.Resize((32, 32)),
     transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

(2)实例化网络net,然后用load_state_dict()方法导入’Lenet.pth’文件;
通过PIL模块载入1.jpg这张图像,使用transform()预处理,将[H,W,C]的图片处理成[C,H,W]的tensor,再用unsqueeze()方法加上N这个维度。

net = LeNet()
net.load_state_dict(torch.load('Lenet.pth'))

im = Image.open('1.jpg')
im = transform(im)  # [C, H, W]
im = torch.unsqueeze(im, dim=0)  # [N, C, H, W]

(3)将处理好的张量im传入网络,找到最大值输出中对应的index,根据index找到对应的种类名并输出。

with torch.no_grad():
    outputs = net(im)
    predict = torch.max(outputs, dim=1)[1].data.numpy() #找到最大值输出中对应的index
print(classes[int(predict)]) #根据index找到对应的种类名

预测结果为plane:
在这里插入图片描述

(4)现在稍作修改,我们使用softmax函数将10个节点处输出的预测值打印出来看看:

with torch.no_grad():
    outputs = net(im) #将处理好的张量im传入网络
    # predict = torch.max(outputs, dim=1)[1].data.numpy() #找到最大值输出中对应的index
    predict = torch.softmax(outputs,dim=1)
print(predict)

在这里插入图片描述
在这里插入图片描述
可以看到,我们输出第一个class的概率是97.805%,对应的概率最高,也就是最可能是“plane”。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值