pytorch学习笔记(三)

209 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

上一篇博客主要内容介绍了如何利用pytorch构建一个简单的网络结构,其中包括网络结构的定义,以及定义一个forward函数完成网络的前向传播,也就是将整个网络结构从输入到输出,进行串联起来,这个是最简单的网络结构,随着后面的不断学习,网络结构也会逐渐复杂,有时为了能够在深层次的网络结构中获取浅层次的网络特征,也会出现网络结构并联的情况,也就是concat操作。

本博客利用CIFA10数据集作为例子,在学会构建网络结构的基础上,通过定义损失函数,梯度的反向传播、优化等训练自己的网络。

CIFA10与MNIST数据集类似,其中都有十个类别,因此对于昨天定义的简单的网络结构,对其进行微调后仍然可以胜任今天的任务。CIFA10与MNIST也可以直接通过torchvision中的datasets包进行下载和使用。

首先导入本实验所需要的所有包

import matplotlib.pyplot as plt
import torch.nn as nn
import torchvision.utils
from torchvision import datasets
import numpy as np
import torch.utils.data as Data
from torchvision import transforms
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable

然后下载CIFA10的数据集,如果已经下载过CIFA10的数据集,可以将参数download设置为False.

# 设置一些变化的格式。将数据转化为tensor格式,将数据按照下面的格式进行标准化,mean表示均值,std表示方差
transform = transforms.Compose(
    [
        transforms.ToTensor(),
        transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))
    ]
)
# 下载数据集
traindata = datasets.CIFAR10(
    root="./data/CIFA10",
    train=True,
    download=True,
    transform=transform
)

testdata = datasets.CIFAR10(
    root="./data/CIFA10",
    train=False,
    download=True,
    transform=transform
)
# 加载数据集
train = Data.DataLoader(dataset=traindata, batch_size=32, shuffle=True, num_workers=1)
test = Data.DataLoader(dataset=testdata, batch_size=32, shuffle=True, num_workers=1)

# CIFA10的类别标签
classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# 查看数据集的一些照片
def showImage():
    def imgshow(img):
        img = img.permute(1, 2, 0)
        img = img / 2 + 0.5
        img_array = np.array(img)
        plt.imshow(img_array)
        plt.show()
    image = iter(train)
    images, labels = image.next()
    imgshow(torchvision.utils.make_grid(images))

image.png

上面显示的是一个batch的图像数据,一共32张,其数量大小可以由Dataloader中的batch_size控制。

然后就是定义我们的网络模型,网络模型是(1)卷积后跟一个relu然后最大池化;(2)卷积后跟一个relu,然后最大池化;(3)经历一个全连接然后跟一个relu;(4)经历一个全连接然后跟一个relu;(5)最后一个全连接,将类别输出(10类)

class Net(nn.Module):
    def __init__(self, in_channels, middle_channels, out_channels):
        '''
        定义卷积、全连接层等几个操作,无顺序可言
        :param in_channels:
        :param middle_channels:
        :param out_channels:
        '''
        super(Net, self).__init__()
        self.relu = nn.ReLU(inplace=True)
        self.conv1 = nn.Conv2d(in_channels, middle_channels, 5)
        # self.bn1 = nn.BatchNorm2d(middle_channels)
        self.conv2 = nn.Conv2d(middle_channels, out_channels, 5)
        # self.bn2 = nn.BatchNorm2d(out_channels)

        self.fc1 = nn.Linear(out_channels*5*5, 120)
        self.fc2 = nn.Linear(120, 60)
        self.fc3 = nn.Linear(60, 10)

    def forward(self, x):
        '''
        定义网络的结构顺序,完成前向的传播,这里的顺序就是整个网络结构的顺序
        :param x:
        :return:
        '''
        # 卷积层后跟一个池化层
        x = F.max_pool2d(self.relu(self.conv1(x)), (2, 2))
        x = F.max_pool2d(self.relu(self.conv2(x)), 2)
        # view中给定第一个参数为-1表示,让系统自动计算前面的维度大小
        yy = self.feature_size(x)
        x = x.view(-1, yy)
        x = self.relu(self.fc1(x))
        x = self.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def feature_size(self, x):
        '''
        计算特征图的数量
        :param x:
        :return:
        '''
        numer = x.size()[1:]
        num_feature = 1
        for num in numer:
            num_feature *= num
        return num_feature

最后,就是通过实例化模型,然后将输入输入到模型中,根据输出进行一个交叉熵损失计算,利用随机梯度下降进行参数的更新。代码如下

if __name__ == "__main__":
    showImage()
    in_channels = 3
    middle_channels = 6
    out_channels = 16
    model = Net(in_channels, middle_channels, out_channels).cuda(device=0)
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(model.parameters(), lr=1e-3, momentum=0.7)
    epoch = 3
    for i in range(epoch):
        loss_all = 0.0
        for j, data in enumerate(train, 0):
            inputs, labels = data
            inputs, labels = Variable(inputs.cuda(0)), Variable(labels.cuda(0))
            # 首先需要把梯度置为0
            optimizer.zero_grad()

            outputs = model(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()

            loss_all += loss.item()
            if j == 0 or j % 100 == 0:
                print("epoch: {}, loss: {}".format(j, loss_all / 100))
                loss_all = 0

对于训练的epoch,可以自己进行设置,具体做法就是将最外层的for循环数值增大即可。网络训练的结果如下。

image.png

可以看到随着训练迭代次数的增加,损失函数在下降,但是明显没有收敛,说明我们设置的epoch数值太小。可以增大epoch来达到网络训练过程中最终的收敛。本次训练,我们仅使用了一个gpu进行训练,在后面的博客中我将继续更新对于数据集的一些操作,包括如何使用多个gpu进行训练等。