本文任务
我们接下来需要用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)
第一次运行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
运行结果
Files already downloaded and verified label: 2 imgdata的类型: <class 'PIL.Image.Image'>
注意,数据集中的照片数据是以 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)
运行结果
Files already downloaded and verified Files already downloaded and verified
DataLoader是一个可迭代的对象,它将dataset返回的每一条数据样本拼接成一个batch,并提供多线程加速优化和数据打乱等操作。当程序对 cirfar_dataset
的所有数据遍历完一遍, 对Dataloader也完成了一次迭代。
dataiter = iter(trainloader) #返回四张照片及其label images, labels = dataiter.next() #打印多张照片 show(tv.utils.make_grid(images))
#显示images中的第三张照片
show(images[2])
二、定义网络
最早的卷积神经网络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)
2.2 第一subsampling层
该层输入数据是6通道,输出还为6通道,但是图片的长宽从28变为14,我们可以使用池化层来实现尺寸缩小一倍。这里我们使用MaxPool2d(2, 2)
nn.MaxPool2d(kernel_size=2, stride=2)
2.3 第二个convolutions层
该层输入的是6通道数据,输出为16通道数据,且图片长宽从14变为10。这里我们使用
nn.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
2.6 第二全连接层
该层的输入是一维数组,长度为120,输出为一维数组,长度为84.
nn.Linear(in_features=120, out_features=84) #根据图中,该输出为84
2.7 第三全连接层
该层的输入是一维数组,长度为84,输出为一维数组,长度为10,该层网络定义如下
nn.Linear(in_features=84, out_features=10) #根据图中,该输出为10
注意:
这里的长度10的列表,可以看做输出的label序列。例如理想情况下
output = [1, 0, 0, 0, 0, 0, 0 ,0, 0 ,0]
该output表示 input数据
经过该神经网络运算得到的 预测结果
显示的 类别是 第一类
同理,理想情况下
output2 = [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
实例化神经网络LeNet
net = 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) ) )
我们随机传入一批照片(batch_size=4) ,将其输入给net,看输出的结果是什么情况。
注意:
pytorch中输入的数据必须是batch数据(批数据)
dataiter = 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=<ThAddmmBackward>)
t.max(input, dim)
-
input:传入的tensor
-
dim: tensor的方向。dim=1表示按照行方向计算最大值
t.max(outputs, dim=1)
运行结果
(tensor([0.1963, 0.2260, 0.2168, 0.2025], grad_fn=<MaxBackward0>), 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)
四、训练网络
所有网络的训练的流程都是类似的,不断执行(轮):
-
给网络输入数据
-
前向传播+反向传播
-
更新网络参数
遍历完一遍数据集称为一个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
运行结果
[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)
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))
运行结果
10000张测试集中准确率为: 57%
数据集一共有10种照片,且每种照片数量相等。所以理论上,我们猜测对每一张照片的概率为10%。
而通过我们神经网络LeNet预测的准确率达到 57%
,证明网络确实学习到了规律。
往期文章
以上所述就是小编给大家介绍的《PyTorch 实战:使用卷积神经网络对照片进行分类》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。