pytorch学习日记(一)——之CIFAR10图像训练测试实战

神经网络NN编程实现,往往需要以下几个步骤:

1)定义NN,初始化NN的参数(权重和偏置)

2)准备好输入数据集

3)让输入通过NN,得到输出

4)计算输出和理想输出的loss

5)采用随机梯度下降方法(SGD),后向传播更新NN的权重和偏置,更新规则:

weight = weight - learning_rate * gradient

下面,将根据这些步骤进行编程:

导入库

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
import torch
import torchvision
import torchvision.transforms as transforms
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np
  • 1. 定义NN,初始化NN的参数
class Net(nn.Module):
    # 定义Net的初始化函数,这个函数定义了该神经网络的基本结构
    def __init__(self):
        super(Net, self).__init__()  # 复制并使用Net的父类的初始化方法,即先运行nn.Module的初始化函数
        self.conv1 = nn.Conv2d(3, 6, 5)  # 定义conv1函数的是图像卷积函数:输入为图像(3个频道,即RGB图),输出为 6张特征图, 卷积核为5x5正方形
        self.conv2 = nn.Conv2d(6, 16, 5)  # 定义conv2函数的是图像卷积函数:输入为6张特征图,输出为16张特征图, 卷积核为5x5正方形
        self.fc1 = nn.Linear(16 * 5 * 5, 120)  # 定义fc1(fullconnect)全连接函数1为线性函数:y = Wx + b,并将16*5*5个节点连接到120个节点上。
        self.fc2 = nn.Linear(120, 84)  # 定义fc2(fullconnect)全连接函数2为线性函数:y = Wx + b,并将120个节点连接到84个节点上。
        self.fc3 = nn.Linear(84, 10)  # 定义fc3(fullconnect)全连接函数3为线性函数:y = Wx + b,并将84个节点连接到10个节点上。

    # 定义该神经网络的向前传播函数,该函数必须定义,一旦定义成功,向后传播函数也会自动生成(autograd)
    def forward(self, x):
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))  # 输入x经过卷积conv1之后,经过激活函数ReLU,使用2x2的窗口进行最大池化Max pooling,然后更新到x。
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)  # 输入x经过卷积conv2之后,经过激活函数ReLU,使用2x2的窗口进行最大池化Max pooling,然后更新到x。
        x = x.view(-1, self.num_flat_features(x))  # view函数将张量x变形成一维的向量形式,总特征数并不改变,为接下来的全连接作准备。
        x = F.relu(self.fc1(x))  # 输入x经过全连接1,再经过ReLU激活函数,然后更新x
        x = F.relu(self.fc2(x))  # 输入x经过全连接2,再经过ReLU激活函数,然后更新x
        x = self.fc3(x)  # 输入x经过全连接3,然后更新x
        return x

    # 使用num_flat_features函数计算张量x的总特征量(把每个数字都看出是一个特征,即特征总量),比如x是4*2*2的张量,那么它的特征总量就是16。
    def num_flat_features(self, x):
        size = x.size()[1:]  # 这里为什么要使用[1:],是因为pytorch只接受批输入,也就是说一次性输入好几张图片,那么输入数据张量的维度自然上升到了4维。
        # 【1:】让我们把注意力放在后3维上面,是因为 x.size() 会 return [nSamples, nChannels, Height, Width]。我们只需要展开后三项成为一个一维的 tensor。
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

 这里你可以用下面的代码打印出网络的结构看看:

net = Net()
# 以下代码是为了看一下我们需要训练的参数的数量
print(net)

params = list(net.parameters())
k = 0
for i in params:
     l = 1
     #i type is <class 'torch.nn.parameter.Parameter'>
     print("该层的结构:" + str(list(i.size())))
     for j in i.size():
         l *= j
     print("参数和:" + str(l))
     k = k + l

print("总参数和:" + str(k))

这里特别说下在__init__(self)里的 self.fc1 = nn.Linear(16 * 5 * 5, 120)  ,为什么输入是16*5*5?

1)有必要提下CIFAR10数据集:

CIFAR10,该数据集共有60000张彩色图像,这些图像是32*32×3(记住这个32*32很重要),分为10个类,每类6000张图。这里面有50000张用于训练,构成了5个训练批,每一批10000张图;另外10000用于测试,单独构成一批。测试批的数据里,取自10类中的每一类,每一类随机取1000张。抽剩下的就随机排列组成了训练批。注意一个训练批中的各类图像并不一定数量相同,总的来看训练批,每一类都有5000张图。

 下面这幅图就是列举了10各类,每一类展示了随机的10张图片:

 你可以打印出图片看看:

# matplotlib inline
def imshow(img):
    img = img / 2 + 0.5  # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()


# show some random training images
dataiter = iter(trainloader)
images, labels = dataiter.next()
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(5)))

2)有必要了解上面code定义的NN结构:

conv1->max_pool1->conv2->max_pool2->fc1->fc2->fc3

总体结构是这样的,当然relu只是激活函数,不作为一层。

上面我们提过输入是32*32*3的图像,我们先看看nn.Conv2d函数参数的意义:

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

可以看到,步长stride=1,默认为1,填充padding=0默认为0。好了,看下我们定义的self.conv1 = nn.Conv2d(3, 6, 5),即输入为图像(3个频道,即RGB图),输出为 6张特征图, 卷积核为5x5正方形。

那么通过conv1层输出的feature_map尺寸计算公式为:[ (原图片尺寸 -卷积核尺寸)/ 步长 ] + 1(牢记),不懂原理的看这篇博客https://blog.csdn.net/m0_37673307/article/details/81166266

因此,

  1. conv1层self.conv1 = nn.Conv2d(3, 6, 5):输入是32*32*3,计算(32-5)/ 1 + 1 = 28,那么通过conv1输出的结果是28*28*6;
  2. max_pool1层F.max_pool2d(F.relu(self.conv1(x)), (2, 2)):输入是28*28*6,窗口2*2,计算28 / 2 = 14,那么通过max_pool1层输出结果是14*14*6;
  3. conv2层 self.conv2 = nn.Conv2d(6, 16, 5):输入是14*14*6,计算(14 - 5)/ 1 + 1 = 10,那么通过conv2输出的结果是10*10*16;
  4. max_pool2层F.max_pool2d(F.relu(self.conv2(x)), 2):输入是10*10*16,窗口2*2,计算10 /  2 = 5,那么通过max_pool2层输出结果是5*5*16.

那么接下来就是fc1层的输入,也就是为啥是self.fc1 = nn.Linear(16 * 5 * 5, 120) 的原因了。

  •  2. 准备好输入数据集
# torchvision输出的是PILImage,值的范围是[0, 1].
# 我们将其转化为tensor数据,并归一化为[-1, 1]。
transform = transforms.Compose([transforms.ToTensor(),
                                transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
                                ])

# 训练集,将相对目录./data下的cifar-10-batches-py文件夹中的全部数据(50000张图片作为训练数据)加载到内存中,若download为True时,会自动从网上下载数据并解压
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)

# 将训练集的50000张图片划分成12500份,每份4张图,用于mini-batch输入。shffule=True在表示不同批次的数据遍历时,打乱顺序。num_workers=2表示使用两个子进程来加载数据
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=False, num_workers=2)

#测试集,将相对目录./data下的cifar-10-batches-py文件夹中的全部数据(10000张图片作为测试数据)加载到内存中,若download为True时,会自动从网上下载数据并解压
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)

# 将测试集的10000张图片划分成2500份,每份4张图,用于mini-batch输入。
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
                                         shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
  • 3.输入通过NN,得到输出,计算输出和理想输出的loss,采用随机梯度下降方法(SGD),后向传播更新NN的权重和偏置

这里三个步骤放在一起,因为pytorch进行了高度封装,一个函数就能做成一件事。

criterion = nn.CrossEntropyLoss()#叉熵损失函数
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)#使用SGD(随机梯度下降)优化,学习率为0.001,动量为0.9
for epoch in range(10):  # 遍历数据集10次

    running_loss = 0.0
    # enumerate(sequence, [start=0]),i序号,data是数据
    for i, data in enumerate(trainloader, 0):
        # get the inputs
        #data is list
        inputs, labels = data  # data的结构是:[4x3x32x32的张量,长度4的张量]
        # wrap them in Variable
        inputs, labels = Variable(inputs), Variable(labels)  # 把input数据从tensor转为variable
        # zero the parameter gradients
        optimizer.zero_grad()  # 将参数的grad值初始化为0

        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)  # 将output和labels使用叉熵计算损失
        loss.backward()  # 反向传播
        optimizer.step()  # 用SGD更新参数

        # 每2000批数据打印一次平均loss值
        running_loss += loss.item()  # loss本身为Variable类型,所以要使用data获取其Tensor,因为其为标量,所以取0  或使用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')

那么由以上训练的模型,我们需要测试下预测效果怎样:

correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(Variable(images))
        # print outputs.data
        # print(outputs.data)
        # print(labels)
        value, predicted = torch.max(outputs.data, 1)  # outputs.data是一个4x10张量,将每一行的最大的那一列的值和序号各自组成一个一维张量返回,第一个是值的张量,第二个是序号的张量。
        #label.size(0) 是一个数
        total += labels.size(0)
        correct += (predicted == labels).sum()  # 两个一维张量逐行对比,相同的行记为1,不同的行记为0,再利用sum(),求总和,得到相同的个数。

print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total))

看看我的运行效果,训练了10次,测试准确度为57%,多训练几次,就能降低loss,但也会出现过拟合。

当然你也可以看看每一类的分类情况:

class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
    for data in testloader:
        images,labels = data
        outputs = net(images)
        _,predicted = torch.max(outputs,1)
        c = (predicted==labels).squeeze()
        for i in range(4):
            label = labels[i]
            class_correct[label] += c[i].item()
            class_total[label] += 1
for i in range(10):
    print('Accuracy of %5s : %2d %%' % (classes[i],100 * class_correct[i] / class_total[i]))

运行结果:

===========================================================================================

这里给出完整代码:

# coding=utf-8
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
import torch
import torchvision
import torchvision.transforms as transforms
import torch.optim as optim

# torchvision输出的是PILImage,值的范围是[0, 1].
# 我们将其转化为tensor数据,并归一化为[-1, 1]。
transform = transforms.Compose([transforms.ToTensor(),
                                transforms.Normalize(mean = (0.5, 0.5, 0.5), std = (0.5, 0.5, 0.5)),
                                ])

# 训练集,将相对目录./data下的cifar-10-batches-py文件夹中的全部数据(50000张图片作为训练数据)加载到内存中,若download为True时,会自动从网上下载数据并解压
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)

# 将训练集的50000张图片划分成12500份,每份4张图,用于mini-batch输入。shffule=True在表示不同批次的数据遍历时,打乱顺序。num_workers=2表示使用两个子进程来加载数据
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=False, num_workers=2)


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

# print(type(trainloader))
# print(len(trainset))
# print(len(trainloader))
# print(len(testloader))

# 下面是代码只是为了给小伙伴们显示一个图片例子,让大家有个直觉感受。
# functions to show an image
import matplotlib.pyplot as plt
import numpy as np


# matplotlib inline
def imshow(img):
    img = img / 2 + 0.5  # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()


# show some random training images
# dataiter = iter(trainloader)
# images, labels = dataiter.next()

# dataiter = iter(trainloader)
# try:
#     # show some random training images
#     while True:
#         images, labels = dataiter.next()
#         # print images
#         imshow(torchvision.utils.make_grid(images))
#         # print labels
#         print(' '.join('%5s' % classes[labels[j]] for j in range(5)))
# except:
#     print ('done')


class Net(nn.Module):
    # 定义Net的初始化函数,这个函数定义了该神经网络的基本结构
    def __init__(self):
        super(Net, self).__init__()  # 复制并使用Net的父类的初始化方法,即先运行nn.Module的初始化函数
        self.conv1 = nn.Conv2d(3, 6, 5)  # 定义conv1函数的是图像卷积函数:输入为图像(1个频道,即灰度图),输出为 6张特征图, 卷积核为5x5正方形
        self.conv2 = nn.Conv2d(6, 16, 5)  # 定义conv2函数的是图像卷积函数:输入为6张特征图,输出为16张特征图, 卷积核为5x5正方形
        self.fc1 = nn.Linear(16 * 5 * 5, 120)  # 定义fc1(fullconnect)全连接函数1为线性函数:y = Wx + b,并将16*5*5个节点连接到120个节点上。
        self.fc2 = nn.Linear(120, 84)  # 定义fc2(fullconnect)全连接函数2为线性函数:y = Wx + b,并将120个节点连接到84个节点上。
        self.fc3 = nn.Linear(84, 10)  # 定义fc3(fullconnect)全连接函数3为线性函数:y = Wx + b,并将84个节点连接到10个节点上。

    # 定义该神经网络的向前传播函数,该函数必须定义,一旦定义成功,向后传播函数也会自动生成(autograd)
    def forward(self, x):
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))  # 输入x经过卷积conv1之后,经过激活函数ReLU,使用2x2的窗口进行最大池化Max pooling,然后更新到x。
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)  # 输入x经过卷积conv2之后,经过激活函数ReLU,使用2x2的窗口进行最大池化Max pooling,然后更新到x。
        x = x.view(-1, self.num_flat_features(x))  # view函数将张量x变形成一维的向量形式,总特征数并不改变,为接下来的全连接作准备。
        x = F.relu(self.fc1(x))  # 输入x经过全连接1,再经过ReLU激活函数,然后更新x
        x = F.relu(self.fc2(x))  # 输入x经过全连接2,再经过ReLU激活函数,然后更新x
        x = self.fc3(x)  # 输入x经过全连接3,然后更新x
        return x

    # 使用num_flat_features函数计算张量x的总特征量(把每个数字都看出是一个特征,即特征总量),比如x是4*2*2的张量,那么它的特征总量就是16。
    def num_flat_features(self, x):
        size = x.size()[1:]  # 这里为什么要使用[1:],是因为pytorch只接受批输入,也就是说一次性输入好几张图片,那么输入数据张量的维度自然上升到了4维。
        # 【1:】让我们把注意力放在后3维上面,是因为 x.size() 会 return [nSamples, nChannels, Height, Width]。我们只需要展开后三项成为一个一维的 tensor。
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
# # 以下代码是为了看一下我们需要训练的参数的数量
# # print(net)
# #net.parameters() type is generator
# params = list(net.parameters())
# k = 0
# for i in params:
#     l = 1
#     #i type is <class 'torch.nn.parameter.Parameter'>
#     print("该层的结构:" + str(list(i.size())))
#     for j in i.size():
#         l *= j
#     print("参数和:" + str(l))
#     k = k + l
#
# print("总参数和:" + str(k))

#====================================================================================

criterion = nn.CrossEntropyLoss()#叉熵损失函数
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)#使用SGD(随机梯度下降)优化,学习率为0.001,动量为0.9
for epoch in range(10):  # 遍历数据集两次

    running_loss = 0.0
    # enumerate(sequence, [start=0]),i序号,data是数据
    for i, data in enumerate(trainloader, 0):
        # get the inputs
        #data is list
        # print(data[0].size())
        # print(i)
        inputs, labels = data  # data的结构是:[4x3x32x32的张量,长度4的张量]
        # wrap them in Variable
        inputs, labels = Variable(inputs), Variable(labels)  # 把input数据从tensor转为variable
        # print(input.grad_fn)
        # zero the parameter gradients
        optimizer.zero_grad()  # 将参数的grad值初始化为0

        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)  # 将output和labels使用叉熵计算损失
        loss.backward()  # 反向传播
        optimizer.step()  # 用SGD更新参数

        # 每2000批数据打印一次平均loss值
        running_loss += loss.item()  # loss本身为Variable类型,所以要使用data获取其Tensor,因为其为标量,所以取0  或使用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')

#测试集,将相对目录./data下的cifar-10-batches-py文件夹中的全部数据(10000张图片作为测试数据)加载到内存中,若download为True时,会自动从网上下载数据并解压
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)

# 将测试集的10000张图片划分成2500份,每份4张图,用于mini-batch输入。
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
                                         shuffle=False, num_workers=2)

# dataiter = iter(testloader)
# images, labels = dataiter.next()
#
# # print images
# imshow(torchvision.utils.make_grid(images))
# print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))
#
correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(Variable(images))
        # print outputs.data
        # print(outputs.data)
        # print(labels)
        value, predicted = torch.max(outputs.data, 1)  # outputs.data是一个4x10张量,将每一行的最大的那一列的值和序号各自组成一个一维张量返回,第一个是值的张量,第二个是序号的张量。
        #label.size(0) 是一个数
        total += labels.size(0)
        correct += (predicted == labels).sum()  # 两个一维张量逐行对比,相同的行记为1,不同的行记为0,再利用sum(),求总和,得到相同的个数。

print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total))

class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
    for data in testloader:
        images,labels = data
        outputs = net(images)
        _,predicted = torch.max(outputs,1)
        c = (predicted==labels).squeeze()
        for i in range(4):
            label = labels[i]
            class_correct[label] += c[i].item()
            class_total[label] += 1
for i in range(10):
    print('Accuracy of %5s : %2d %%' % (classes[i],100 * class_correct[i] / class_total[i]))

猜你喜欢

转载自blog.csdn.net/m0_37673307/article/details/81268222