我们接下来需要用CIFAR-10数据集进行分类,步骤如下:使用torchvision 加载并预处理CIFAR-10数据集

定义网络

定义损失函数和优化器

训练网络并更新网络参数

测试网络

注意:文章末尾含有项目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)

13importtorchvisionastv
importtorchvision.transformsastransforms
fromtorchvision.transformsimportToPILImage
importtorchast
#可以把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

9cifar_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的类型:


3Filesalreadydownloadedandverified
label:2
imgdata的类型:
注意,数据集中的照片数据是以 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)
27
28defdataloader(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
)
returnloader
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
1
2Filesalreadydownloadedandverified
Filesalreadydownloadedandverified

DataLoader是一个可迭代的对象,它将dataset返回的每一条数据样本拼接成一个batch,并提供多线程加速优化和数据打乱等操作。当程序对 cirfar_dataset 的所有数据遍历完一遍, 对Dataloader也完成了一次迭代。

dataiter = iter(trainloader)
#返回四张照片及其label
images, labels = dataiter.next()
#打印多张照片
show(tv.utils.make_grid(images))
7dataiter=iter(trainloader)
#返回四张照片及其label
images,labels=dataiter.next()
#打印多张照片
show(tv.utils.make_grid(images))

二、定义网络

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


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)
3nn.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)
1
2nn.MaxPool2d(kernel_size=2,
stride=2)

2.3 第二个convolutions层

该层输入的是6通道数据,输出为16通道数据,且图片长宽从14变为10。这里我们使用

nn.Conv2d(in_channels=6,
out_channels=16,
kernel_size=5)
1
2
3nn.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
1
2nn.Linear(in_features=16*5*5,
out_features=120)#根据图中,该输出为120
2.6 第二全连接层
该层的输入是一维数组,长度为120,输出为一维数组,长度为84.
nn.Linear(in_features=120,
out_features=84) #根据图中,该输出为84
1
2nn.Linear(in_features=120,
out_features=84)#根据图中,该输出为84
2.7 第三全连接层
该层的输入是一维数组,长度为84,输出为一维数组,长度为10,该层网络定义如下
nn.Linear(in_features=84,
out_features=10) #根据图中,该输出为10
1
2nn.Linear(in_features=84,
out_features=10)#根据图中,该输出为10
注意:
这里的长度10的列表,可以看做输出的label序列。例如理想情况下
output = [1, 0, 0, 0, 0, 0, 0 ,0, 0 ,0]
1output=[1,0,0,0,0,0,0,0,0,0]
该output表示 input数据 经过该神经网络运算得到的 预测结果 显示的类别是 第一类
同理,理想情况下
output2 = [0, 1, 0, 0, 0, 0, 0 ,0, 0 ,0]
1output2=[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


53importtorch
importtorch.nnasnn
classLeNet(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),
)
defforward(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)
returnx
实例化神经网络LeNet
net = LeNet()
net
1
2net=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)
)

13LeNet(
(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)
)
)
13LeNet(
(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,看输出的结果是什么情况。

注意:

8dataiter=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=)

8tensor([[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=)
t.max(input, dim)input:传入的tensor
dim: tensor的方向。dim=1表示按照行方向计算最大值
t.max(outputs, dim=1)
1t.max(outputs,dim=1)
运行结果
(tensor([0.1963, 0.2260, 0.2168, 0.2025], grad_fn=),
tensor([0, 0, 0, 0]))
1
2(tensor([0.1963,0.2260,0.2168,0.2025],grad_fn=),
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)


8fromtorchimportoptim
#定义交叉熵损失函数
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


34epochs=10
average_loss_series=[]
forepochinrange(epochs):
running_loss=0.0
fori,datainenumerate(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()
ifi%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


60[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)

7%matplotlibinline
importmatplotlib.pyplotasplt
x=range(0,60)
plt.figure()
plt.plot(x,average_loss_series)


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))


15defcorrect_rate(net,testloader):
correct=0
total=0
fordataintestloader:
images,labels=data
outputs=net(images)
_,predicted=t.max(outputs.data,1)
total+=labels.size(0)
correct+=(predicted==labels).sum()
return100*correct/total
correct=correct_rate(net,testloader)

print('10000张测试集中准确率为: {}%'.format(correct))

运行结果

10000张测试集中准确率为: 57%

110000张测试集中准确率为:57%

数据集一共有10种照片,且每种照片数量相等。所以理论上,我们猜测对每一张照片的概率为10%。

而通过我们神经网络LeNet预测的准确率达到 57%,证明网络确实学习到了规律。