2.2 使用Pytorch构建一个分类器
学习目标
- 了解分类器的任务和数据样式
- 掌握如何使用Pytorch实现一个分类器
分类器任务和数据介绍
- 构建一个将不同图像进行分类的神经网络分类器,对输入的图片进行判断并完成分类
- 本案例采用CIFAR0数据集作为原始图片数据
CIFAR10数据集介绍
- 数据集中每张图片的尺寸是33232,代表彩色3通道
- CIFAR10数据集共有10种不同的分类,分别是:airplane automobile bird cat deer dog frog horse ship truck
训练分类器步骤
- 1:使用torchvision下载CIFAR10数据集
- 2:定义卷积神经网络
- 3:定义损失函数
- 4:在训练集上训练模型
- 5:在测试集上测试模型
1:使用torchvision下载CIFAR10数据集
- 导入torchvision包来辅助下载数据集
import torch
import torchvision
import torchvision.transforms as transforms
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
#下载数据集并对图片进行调整,因为torchvision数据集的输出是PILImage格式,数据域在[0,1]
#我们将其转换成标准数据域[-1,1]的张量格式
#transform 数据转换器
transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))])
trainset=torchvision.datasets.CIFAR10(root='./data',train=True,download=True,transform=transform)
# 下载下来的数据放在trainset里面
trainloader=torch.utils.data.DataLoader(trainset,batch_size=4,shuffle=True,num_workers=2)
# DataLoader数据迭代器 将数据封装成DataLoader
# num_workers:两个线程读取数据
# batch_size=4 批处理
testset=torchvision.datasets.CIFAR10(root='./data',train=False,download=True,transform=transform)
testloader=torch.utils.data.DataLoader(testset,batch_size=4,shuffle=False,num_workers=2)
classes=('airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
#输出
Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data\cifar-10-python.tar.gz
100.0%
Extracting ./data\cifar-10-python.tar.gz to ./data
Files already downloaded and verified
注意:
可能存在的两个问题:
- windows系统下运行上述代码,并且出现报错信息“BrokenPipeError”时,可以尝试将torch.utils.DataLoader()中的num_workers设置为0
- 下载CIFAR10数据集报错:urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certi
- 尽量使用linux系统学习深度学习
展示若干训练集的图片
在此处遇到问题可以访问我之前写的一篇文章
import torch
import torchvision
import torchvision.transforms as transforms
import ssl
import torch.utils.data as Data
ssl._create_default_https_context = ssl._create_unverified_context
#下载数据集并对图片进行调整,因为torchvision数据集的输出是PILImage格式,数据域在[0,1]
#我们将其转换成标准数据域[-1,1]的张量格式
#transform 数据转换器
transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))])
trainset=torchvision.datasets.CIFAR10(root='./data',train=True,download=True,transform=transform)
# 下载下来的数据放在trainset里面
trainloader=torch.utils.data.DataLoader(dataset=trainset,batch_size=4,shuffle=True,num_workers=2)
# DataLoader数据迭代器 将数据封装成DataLoader
# num_workers:两个线程读取数据
# batch_size=4 批处理
testset=torchvision.datasets.CIFAR10(root='./data',train=False,download=True,transform=transform)
testloader=torch.utils.data.DataLoader(dataset=testset,batch_size=4,shuffle=False,num_workers=2)
classes=('airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
#展示若干训练集的图片
#导入画图包和numpy
import matplotlib.pyplot as plt
import numpy as np
#构建展示图片的函数
def imShow(img):
img=img/2+0.5
#img是tensor类型的数据,tensor类型转换成numpy类型的数据
npimg=img.numpy()
plt.imshow(np.transpose(npimg,(1,2,0)))#维度转换成 1 2 0 这三个维度
plt.show()
#从数据迭代器中读取一张图片
if __name__ == '__main__':
try:
dataiter = iter(trainloader)
images, labels = dataiter.next()
# 展示图片
imShow(torchvision.utils.make_grid(images))
# 使用网格的形式展示图片
# 打印标签label
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
except Exception as e:
print(e)
#输出
dog horse cat cat
定义卷积神经网络
仿照2.1节中的类来构造此处的类,唯一的区别在于次数采用的三通道 3-channel
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.conv2=nn.Conv2d(6,16,5)
#定义池化层
self.pool = nn.MaxPool2d(2, 2)
#定义全连接层
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=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()
print(net)
3: 定义损失函数
采用交叉熵损失函数和随机梯度下降优化器
#交叉熵损失函数和随机梯度下降优化器
import torch.optim as optim
criterion=nn.CrossEntropyLoss()
optimizer=optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
4: 在训练集上训练模型
采用基于梯度下降的优化算法,都需要很多个轮次的迭代训练
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torch
import torchvision.transforms as transforms
transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))])
trainset=torchvision.datasets.CIFAR10(root='./data',train=True,download=True,transform=transform)
trainloader=torch.utils.data.DataLoader(trainset,batch_size=4,shuffle=True,num_workers=0)
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
#定义两个卷积层
self.conv1=nn.Conv2d(3,6,5)
self.conv2=nn.Conv2d(6,16,5)
#定义池化层
self.pool = nn.MaxPool2d(2, 2)
#定义全连接层
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=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()
print(net)
#交叉熵损失函数和随机梯度下降优化器
import torch.optim as optim
criterion=nn.CrossEntropyLoss()
optimizer=optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
for epoch in range(2):
running_loss=0.0
for i ,data in enumerate(trainloader,0):
#data 中包含输入图像张量inputs,标签张量labels
inputs,labels=data
#首先将优化器梯度归零
optimizer.zero_grad()
#输入图像张量进网络,得到输出张量outputs
outputs=net(inputs)
#利用网络的输出outputs和标签labels计算损失值
loss=criterion(outputs,labels)
#反向传播+参数更新,是标准的代码流程
loss.backward()
optimizer.step()
#打印轮次和损失值
running_loss+=loss.item()
if (i+1)%2000==0:
print('[%d , %5d] loss: %.3f'%(epoch+1,i+1,running_loss/2000))
running_loss=0.0
print("Finished Training")
- 输出结果
[1 , 2000] loss: 2.220
[1 , 4000] loss: 1.859
[1 , 6000] loss: 1.658
[1 , 8000] loss: 1.570
[1 , 10000] loss: 1.512
[1 , 12000] loss: 1.436
[2 , 2000] loss: 1.373
[2 , 4000] loss: 1.367
[2 , 6000] loss: 1.340
[2 , 8000] loss: 1.303
[2 , 10000] loss: 1.312
[2 , 12000] loss: 1.277
Finished Training
- 保存模型
#首先设定模型的保存路径
PATH='./cifar_net.pth'
torch.save(net.state_dict(),PATH)
5:在测试集上测试模型
- 第一步,展示测试集中的若干图片
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)))
【完整版本代码】
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torch
import numpy as np
import matplotlib.pyplot as plt
import torchvision.transforms as transforms
transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))])
trainset=torchvision.datasets.CIFAR10(root='./data',train=True,download=True,transform=transform)
trainloader=torch.utils.data.DataLoader(trainset,batch_size=4,shuffle=True,num_workers=0)
testset=torchvision.datasets.CIFAR10(root='./data',train=False,download=True,transform=transform)
testloader=torch.utils.data.DataLoader(testset,batch_size=4,shuffle=True,num_workers=0)
classes=('airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
def imShow(img):
img=img/2+0.5
#img是tensor类型的数据,tensor类型转换成numpy类型的数据
npimg=img.numpy()
plt.imshow(np.transpose(npimg,(1,2,0)))#维度转换成 1 2 0 这三个维度
plt.show()
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
#定义两个卷积层
self.conv1=nn.Conv2d(3,6,5)
self.conv2=nn.Conv2d(6,16,5)
#定义池化层
self.pool = nn.MaxPool2d(2, 2)
#定义全连接层
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=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()
print(net)
#交叉熵损失函数和随机梯度下降优化器
import torch.optim as optim
criterion=nn.CrossEntropyLoss()
optimizer=optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
# for epoch in range(2):
# running_loss=0.0
# for i ,data in enumerate(trainloader,0):
# #data 中包含输入图像张量inputs,标签张量labels
# inputs,labels=data
#
# #首先将优化器梯度归零
# optimizer.zero_grad()
#
# #输入图像张量进网络,得到输出张量outputs
# outputs=net(inputs)
#
# #利用网络的输出outputs和标签labels计算损失值
# loss=criterion(outputs,labels)
#
# #反向传播+参数更新,是标准的代码流程
# loss.backward()
# optimizer.step()
#
# #打印轮次和损失值
# running_loss+=loss.item()
# if (i+1)%2000==0:
# print('[%d , %5d] loss: %.3f'%(epoch+1,i+1,running_loss/2000))
# running_loss=0.0
# print("Finished Training")
#
# #保存模型
# #首先设定模型的保存路径
# PATH='./cifar_net.pth'
# torch.save(net.state_dict(),PATH)
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)))
- 第二步,加载模型并对测试图片进行预测
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torch
import numpy as np
import matplotlib.pyplot as plt
import torchvision.transforms as transforms
transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))])
trainset=torchvision.datasets.CIFAR10(root='./data',train=True,download=True,transform=transform)
trainloader=torch.utils.data.DataLoader(trainset,batch_size=4,shuffle=True,num_workers=0)
testset=torchvision.datasets.CIFAR10(root='./data',train=False,download=True,transform=transform)
testloader=torch.utils.data.DataLoader(testset,batch_size=4,shuffle=True,num_workers=0)
classes=('airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
def imShow(img):
img=img/2+0.5
#img是tensor类型的数据,tensor类型转换成numpy类型的数据
npimg=img.numpy()
plt.imshow(np.transpose(npimg,(1,2,0)))#维度转换成 1 2 0 这三个维度
plt.show()
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
#定义两个卷积层
self.conv1=nn.Conv2d(3,6,5)
self.conv2=nn.Conv2d(6,16,5)
#定义池化层
self.pool = nn.MaxPool2d(2, 2)
#定义全连接层
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=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()
# print(net)
#交叉熵损失函数和随机梯度下降优化器
import torch.optim as optim
criterion=nn.CrossEntropyLoss()
# optimizer=optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
# for epoch in range(2):
# running_loss=0.0
# for i ,data in enumerate(trainloader,0):
# #data 中包含输入图像张量inputs,标签张量labels
# inputs,labels=data
#
# #首先将优化器梯度归零
# optimizer.zero_grad()
#
# #输入图像张量进网络,得到输出张量outputs
# outputs=net(inputs)
#
# #利用网络的输出outputs和标签labels计算损失值
# loss=criterion(outputs,labels)
#
# #反向传播+参数更新,是标准的代码流程
# loss.backward()
# optimizer.step()
#
# #打印轮次和损失值
# running_loss+=loss.item()
# if (i+1)%2000==0:
# print('[%d , %5d] loss: %.3f'%(epoch+1,i+1,running_loss/2000))
# running_loss=0.0
# print("Finished Training")
#
# #保存模型
# #首先设定模型的保存路径
PATH='./cifar_net.pth'
# torch.save(net.state_dict(),PATH)
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)))
#第二步,加载模型并对测试图片进行预测
#首先实例化模型的类对象
net=Net()
#加载训练阶段保存好的模型的状态字典
net.load_state_dict(torch.load(PATH))
#利用模型对图片进行预测
outputs=net(images)
#共有10个类别,采用模型计算出的概率最大的作为预测的类别
_,predicted=torch.max(outputs,1)
print("predicted:",predicted)
#打印预测标签的结果
print("Predicted:",' '.join('%5s' % classes[predicted[j]] for j in range(4)))
- 输出
GroundTruth: ship frog bird automobile
predicted: tensor([0, 2, 8, 1])
Predicted: airplane bird ship automobile
- 训练好的模型在全部测试集上的表现
#在全部测试集上的表现
correct=0
total=0
with torch.no_grad():
for data in testloader:
images,labels=data
outputs=net(images)
_,predicted=torch.max(outputs.data,1)
total+=labels.size(0)
correct+=(predicted==labels).sum().item()
print("Accuracy of the network on the 10000 test images:%d %%"%(100*correct/total))
#输出
Accuracy of the network on the 10000 test images:56 %
- 分析:对于拥有10个类被的数据集,随机猜测的准确率是10%,模型达到了53%,说明模型学到了真实的东西。
上面计算的是模型在总体测试集上的准确率,为了更加细致多看一些模型在哪些类别上表现更好,在哪些类别上表现更差,可以对每个类别的准确率进行计算。
#在全部测试集上的表现
correct=0
total=0
class_correct=[0. for i in range(10)]
class_total=[0 for i in range(10)]
max_index=-1
min_index=1000
with torch.no_grad():
for data in testloader:
images,labels=data
outputs=net(images)
_,predicted=torch.max(outputs.data,1)
total+=labels.size(0)
correct+=(predicted==labels).sum().item()
for i in range(labels.size(0)):
class_correct[labels[i]]+=(predicted[i]==labels[i])
class_total[labels[i]]+=1
print("Accuracy of the network on the 10000 test images:%d %%"%(100*correct/total))
# print(class_total)
# print(class_correct)
#打印不同类别的准确度
for i in range(10):
print("Accuracy of ",classes[i], ":",100*class_correct[i].item()/class_total[i],"%")
#输出
Accuracy of the network on the 10000 test images:56 %
Accuracy of airplane : 59.4 %
Accuracy of automobile : 70.2 %
Accuracy of bird : 30.1 %
Accuracy of cat : 38.6 %
Accuracy of deer : 59.1 %
Accuracy of dog : 35.4 %
Accuracy of frog : 70.7 %
Accuracy of horse : 68.3 %
Accuracy of ship : 69.2 %
Accuracy of truck : 67.6 %
在GPU上训练模型
为了真正利用Pytorch中Tensor的优秀属性,加速模型的训练,可以将训练过程转移到GPU上进行。
首先,定义设备,如果CUDA是可用的则被定义成GPU,否则被定义成CPU
将模型放在GPU上训练分两步走:
1:将模型转移到GPU上
2:将数据转移到GPU上
device=torch.device('cuda:0' if torch.cuda.is_available() else "cpu")
print(device)
#当训练模型的时候,只需要将模型转移到GPU上满天星将输入的图片和标签页转移到GPU上即可
net.to(device)
#将输入的图片张量和标签张量转移到GPU上
inputs,labels=data[0].to(device),data[1].to(device)
【小节总结】
学习了分类器的任务和数据样式
- 将不同图像进行分类的神经网络分类器,对输入的图片进行判断并完成分类
- 采用CIFAR10数据集作为原始图片数据,CIFAR10数据集拥有10个类别的33232彩色图片
学习了训练分类器的步骤
- 使用torchvision下载CIFAR10数据集
- 定义卷积神经网络
- 定义损失函数
- 在训练集上训练模型
- 在测试集上测试模型
学习了在GPU上训练模型
- 首先需要定义设备,CPU和GPU二选一
device=torch.device('cuda:0' if torch.cuda.is_available() else "cpu")
- 然后将模型转移到GPU上去
net.to(device)
- 最后在迭代训练的过程中,每一步都将图片和标签张量转移到GPU上去
inputs,labels=data[0].to(device),data[1].to(device)