pytorch实战——训练一个简单的分类器(CIFAR10 数据集)

步骤

  • 数据处理(读取、归一化、加载)
  • 创建网络模型(卷积&池化&全连接、前向传播、损失函数、优化器)
  • 训练网络(epoch、batch_size、打印信息)
  • 测试模型性能(加载测试数据、部分预测、整体准确率)

文件目录结构
├── data
│ └── cifar-10-batches-py
│ ├── batches.meta
│ ├── data_batch_1
│ ├── data_batch_2
│ ├── data_batch_3
│ ├── data_batch_4
│ ├── data_batch_5
│ ├── readme.html
│ └── test_batch
├── data.py
├── model.py
├── train.py
└── validate.py

数据处理(data.py)

import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np

# torchvision.transforms.Compose()类的主要作用是串联多个图片变换的操作
# 即其内是对图片的一系列操作
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])


def getData():
    trainset = torchvision.datasets.CIFAR10(root='data/',
                                            train=True,
                                            download=False,
                                            transform=transform)

    trainloader = torch.utils.data.DataLoader(trainset,
                                              batch_size=4,
                                              shuffle=True,
                                              num_workers=4)
    # 使用cpu,所以batch_size设的不大
    # num_workers 通俗可理解为开几个cpu线程处理数据

    testset = torchvision.datasets.CIFAR10(root='data/',
                                           train=False,
                                           download=False,
                                           transform=transform)

    testloader = torch.utils.data.DataLoader(testset,
                                             batch_size=4,
                                             shuffle=False,
                                             num_workers=2)
    return trainloader, testloader


# 下面是数据展示
trainloader, testloader = getData()

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


def imshow(img):
    img = img / 2 + 0.5
    # unnormalize(反归一化),因为前面数据进行load时使用的
    # transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
    # 是先减0.5再除以0.5,现在反过来
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    # plt.imshow在现实的时候输入的是(imagesize,imagesize,channels),
    # 此处imshow(img)中,参数img的格式为(channels,imagesize,imagesize),
    # 所以要转换一下
    plt.show()


# 获取随机数据
dataiter = iter(testloader)
images, labels = dataiter.next()
# images 返回维度为[4, 3, 32, 32]的tensor,labels返回维度为[4]的tensor

# print(images.shape)
# print(labels.shape)

#print(sum(1 for _ in dataiter))

# 显示图像标签
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

# 展示图像
imshow(torchvision.utils.make_grid(images))

关于transforms.Compose()类用法详解,参见 transforms.Compose()类详解

为什么要进行图片归一化(-1, 1),因为数据如果分布在(0,1)之间,可能实际的bias,就是神经网络的输入b会比较大,而模型初始化时b=0的,这样会导致神经网络收敛比较慢,经过Normalize后,可以加快模型的收敛速度。对RGB图片而言,数据范围是[0-255]的,需要先经过ToTensor除以255归一化到[0,1]之后,再通过Normalize计算过后,将数据归一化到[-1,1]。transforms.Normalize()用法参见 transforms.Normalize()。
torchvision.utils.make_grid()可将若干幅图像拼成一幅图像。

创建网络模型(model.py)

import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True))

  • in_channel: 输入数据的通道数,例RGB图片通道数为3;
  • out_channel: 输出数据的通道数,根据模型进行调整;
  • kennel_size: 卷积核大小,可以是int,或tuple;kennel_size=2,意味着卷积大小2, kennel_size=(2,3),意味着卷积在第一维度大小为2,在第二维度大小为3在这里插入代码片
  • stride:步长,默认为1,与kennel_size类似,stride=2,意味在所有维度步长为2, stride=(2,3),意味着在第一维度步长为2,意味着在第二维度步长为3;
  • padding:默认零填充

nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)

  • kernel_size(int or tuple): max pooling的窗口大小,

  • stride(int or tuple, optional): max pooling的窗口移动的步长。默认值是kernel_size

  • padding(int or tuple, optional):输入的每一条边补充0的层数

  • dilation(int or tuple, optional): 一个控制窗口中元素步幅的参数

  • return_indices: 如果等于True,会返回输出最大值的序号,对于上采样操作会有帮助

  • ceil_mode: 如果等于True,计算输出信号大小的时候,会使用向上取整,代替默认的向下取整的操作


nn.Linear(in_features, out_features, bias=True)

  • in_features指的是输入的二维张量的大小,即输入的 [batch_size, size] 中的size。

  • out_features指的是输出的二维张量的大小,即输出的二维张量的形状为 [batch_size,output_size],当然,它也代表了该全连接层的神经元个数。


训练网络(train.py)

import torch.optim as optim
import torch.nn as nn
import torch
from data import getData
from model import Net, net

## 开启GPU训练阀门
# device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# net.to(device)

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

trainloader, testloader = getData()

for epoch in range(2):  # 多批次循环

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # 获取输入
        inputs, labels = data
		# inputs, labels = inputs.to(device), labels.to(device)
		# 若 GPU 训练要用
		
        # 梯度置0
        optimizer.zero_grad()

        # 正向传播,反向传播,优化
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        # 打印状态信息
        running_loss += loss.item()
        if i % 2000 == 1999:  # 每2000批次打印一次
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0

print('Finished Training')

# torch.save(net, 'cifarModel')

交叉熵损失 nn.CrossEntropyLoss()
optimizer.zero_grad()将梯度置0,即在每一次反向传播之前都需将梯度置0,这样才能正确的更新参数(不置0的话,梯度会叠加上次计算过的梯度,导致计算不准)。optimizer.step()进行梯度更新。


测试模型性能(validata.py)

import torch
import torchvision

from data import getData, imshow

net = torch.load("cifarModel")

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

_, testloader = getData()

dataiter = iter(testloader)
images, labels = dataiter.next()

# 显示图片
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))

outputs = net(images)

_, predicted = torch.max(outputs, 1)

print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))

correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        # images, labels = images.to(device), labels.to(device)
        # outputs = net(images.cuda()) # GPU 上训练的则用此句
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print('acc on the 10000 images: %d %%' % (100 * correct / total))

这里验证模型,是加载的训练好的保存的模型。torch.max() 是对softmax归一化后的数据返回每行最大值(one-hot)及其索引,来确认分类结果。


小结

  • 注意数据集的加载及读取方法(预处理)
  • 网络模型的搭建(几层卷积层,卷积核大小怎么设置,线性层kernel怎么设置)
  • GPU训练(测试时也要加载到GPU)
  • 注意数据转换(cuda、tensor、numpy .etc)

参考

  • https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html?highlight=cifar

你可能感兴趣的:(deepLearning,pytorch,pytorch,分类)