CSDN只作为查看网络结构,具体代码和结果展示请移步GitHub
CIFAR-10 是由 Hinton 的学生 Alex Krizhevsky 和 Ilya Sutskever 整理的一个用于识别普适物体的小型数据集。一共包含 10 个类别的 RGB 彩色图 片:飞机( arplane )、汽车( automobile )、鸟类( bird )、猫( cat )、鹿( deer )、狗( dog )、蛙类( frog )、马( horse )、船( ship )和卡车( truck )。图片的尺寸为 32×32 ,数据集中一共有 50000 张训练圄片和 10000 张测试图片。
与 MNIST 数据集中目比, CIFAR-10 具有以下不同点:
• CIFAR-10 是 3 通道的彩色 RGB 图像,而 MNIST 是灰度图像。
• CIFAR-10 的图片尺寸为 32×32, 而 MNIST 的图片尺寸为 28×28,比 MNIST 稍大。
• 相比于手写字符, CIFAR-10 含有的是现实世界中真实的物体,不仅噪声很大,而且物体的比例、 特征都不尽相同,这为识别带来很大困难。
首先使用torchvision
加载和归一化我们的训练数据和测试数据。
torchvision
这个东西,实现了常用的一些深度学习的相关的图像数据的加载功能,比如cifar10、Imagenet、Mnist等等的,保存在torchvision.datasets
模块中。torchvision.transforms
模块中torchvision.models
当中就包含了AlexNet,VGG,ResNet,SqueezeNet等模型。由于torchvision的datasets的输出是[0,1]的PILImage,所以我们先先归一化为[-1,1]的Tensor
首先定义了一个变换transform,利用的是上面提到的transforms模块中的Compose( )把多个变换组合在一起,可以看到这里面组合了ToTensor和Normalize这两个变换
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
前面的(0.5,0.5,0.5) 是 R G B 三个通道上的均值, 后面(0.5, 0.5, 0.5)是三个通道的标准差,注意通道顺序是 R G B ,用过opencv的同学应该知道openCV读出来的图像是 BRG顺序。这两个tuple数据是用来对RGB 图像做归一化的,如其名称 Normalize 所示这里都取0.5只是一个近似的操作,实际上其均值和方差并不是这么多,但是就这个示例而言 影响可不计。精确值是通过分别计算R,G,B三个通道的数据算出来的。
import torch
import torchvision
import torchvision.datasets as datasets
import torchvision.transforms as transforms
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# datasets.CIFAR10( )也是封装好了的,就在我前面提到的torchvision.datasets块中
trainset = datasets.CIFAR10(root='D:/CIFAR-10', train=True,download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128,shuffle=True, num_workers=2)
# 对于测试集的操作和训练集一样,我就不赘述了
testset = torchvision.datasets.CIFAR10(root='D:/CIFAR-10', train=False,download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=128,shuffle=False, num_workers=2)
# 类别信息也是需要我们给定的
classes = ('plane', 'car', 'bird', 'cat','deer', 'dog', 'frog', 'horse', 'ship', 'truck')
手写字体识别模型LeNet5诞生于1994年,是最早的卷积神经网络之一。LeNet5通过巧妙的设计,利用卷积、参数共享、池化等操作提取特征,避免了大量的计算成本,最后再使用全连接神经网络进行分类识别,这个网络也是最近大量神经网络架构的起点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OBSpCaRR-1587021133464)(attachment:image.png)]
LeNet-5 一些性质:
import torch
import torch.nn as nn
#若能使用cuda,则使用cuda
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#定义网络
class LeNet5(nn.Module):# nn.Module是所有神经网络的基类,我们自己定义任何神经网络,都要继承nn.Module
def __init__(self):
super(LeNet5,self).__init__()
self.conv1 = nn.Sequential(
# 卷积层1,3通道输入,6个卷积核,核大小5*5
# 经过该层图像大小变为32-5+1,28*28
nn.Conv2d(in_channels=3,out_channels=6,kernel_size=5,stride=1, padding=0),
#激活函数
nn.ReLU(),
# 经2*2最大池化,图像变为14*14
nn.MaxPool2d(kernel_size=2,stride=2,padding=0),
)
self.conv2 = nn.Sequential(
# 卷积层2,6输入通道,16个卷积核,核大小5*5
# 经过该层图像变为14-5+1,10*10
nn.Conv2d(in_channels=6,out_channels=16,kernel_size=5,stride=1, padding=0),
nn.ReLU(),
# 经2*2最大池化,图像变为5*5
nn.MaxPool2d(kernel_size=2,stride=2,padding=0),
)
self.fc = nn.Sequential(
# 接着三个全连接层
nn.Linear(16*5*5,120),
nn.ReLU(),
nn.Linear(120,84),
nn.ReLU(),
nn.Linear(84,10),
)
# 定义前向传播过程,输入为
def forward(self,x):
x = self.conv1(x)
x = self.conv2(x)
# nn.Linear()的输入输出都是维度为一的值,所以要把多维度的tensor展平成一维
x = x.view(x.size()[0],-1)
x = self.fc(x)
return x
net = LeNet5().cuda()
print("LeNet5 out: ", net)
pytorch将深度学习中常用的优化方法全部封装在torch.optim之中,所有的优化方法都是继承基类optim.Optimizier
损失函数是封装在神经网络工具箱nn中的,包含很多损失函数
import torch.optim as optim
#用到了神经网络工具箱 nn 中的交叉熵损失函数
criterion = nn.CrossEntropyLoss()
# 使用SGD(随机梯度下降)优化,学习率为0.001,动量为0.9
optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)
from torch.autograd import Variable
plotloss = []
plotauc = []
for epoch in range(50): # 指定训练一共要循环几个epoch
net.train()
sum_loss = 0.0
correct = 0.0
total = 0.0
# 这里我们遇到了第一步中出现的trailoader,代码传入数据,enumerate是python的内置函数,既获得索引也获得数据
for i, (images,labels) in enumerate(trainloader):
# data是从enumerate返回的data,包含数据和标签信息,分别赋值给inputs和labels
# data的结构是:[4x3x32x32的张量,长度4的张量],4是batch_size的数值
# 把input数据从tensor转为variable,variable才拥有梯度grad,输入模型训练都要转成Variable
if torch.cuda.is_available():
images=Variable(images).cuda()
labels=Variable(labels).cuda()
else:
images=Variable(images)
labels=Variable(labels)
# 将参数的grad值初始化为
optimizer.zero_grad()
# forward + backward + optimize
outputs = net(images)
# 将output和labels使用叉熵计算损失
loss = criterion(outputs, labels)
# 反向传播
loss.backward()
# 用SGD更新参数
optimizer.step()
# loss.item()转换为numpy
# loss本身为Variable类型,所以要使用loss.data[0]获取其Tensor,因为其为标量,所以取0
sum_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0) # 更新测试图片的数量
correct += (predicted == labels).sum() # 更新正确分类的图片的数量
# if i % 200 == 199:
print('[epoch:%d, iter:%d] Loss: %.03f | Acc: %.3f%% '
% (epoch + 1, (i + 1 + epoch * len(trainloader)), sum_loss / (i + 1), 100. * correct / total))
plotloss.append(sum_loss / (i + 1))
plotauc.append(100. * correct / total)
print('Finished Training')
plt.subplot(2,1,1)
plt.plot(plotloss)
plt.subplot(2,1,2)
plt.plot(plotauc)
# 定义2个存储每类中测试正确的个数的 列表,初始化为0
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
for data in testloader:
images, labels = data
images=Variable(images).cuda()
labels=Variable(labels).cuda()
outputs = net(images)
_, predicted = torch.max(outputs.data, 1)
#4组(batch_size)数据中,输出于label相同的,标记为1,否则为0
c = (predicted == labels).squeeze()
for i in range(16):
label = labels[i] # 对各个类的进行各自累加
class_correct[label] += c[i]
class_total[label] += 1
for i in range(10):
print('Accuracy of %5s : %2d %%' % (classes[i], 100 * class_correct[i] / class_total[i]))
pytorch保存模型的方式有两种:
(1)将整个网络都都保存下来
torch.save(model_object, 'model.pkl')
model = torch.load('model.pkl')
这种方式再重新加载的时候不需要自定义网络结构,保存时已经把网络结构保存了下来,但是,再读取模型的时候还需要将以前的网络结构复制进来且比较死板(pycharm中不用),另外不能调整网络结构。
(2)仅保存和加载模型参数(推荐使用这样的方法)
GPU上保存,CPU上加载:
torch.save(model.state_dict(), PATH)
device = torch.device('cpu')
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH, map_location=device))
GPU上保存,GPU上加载:
torch.save(model.state_dict(), PATH)
device = torch.device("cuda")
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.to(device)
往模型中输入数据的时候不要忘记在任意tensor上调用input = input.to(device)
这种方式再重新加载的时候需要自己定义网络,并且其中的参数名称与结构要与保存的模型中的一致(可以是部分网络,比如只使用VGG的前几层),相对灵活,便于对网络进行修改。
torch.save(net, 'D:/CIFAR-10/model/LeNet5-128.pth')
import torch
from PIL import Image
from torch.autograd import Variable
import torch.nn.functional as F
from torchvision import datasets, transforms
import numpy as np
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = torch.load('D:/CIFAR-10/model/LeNet5-128.pth') # 加载模型
model = model.to(device)
model.eval() # 把模型转为test模式
# 读取要预测的图片
img = Image.open("D:/CIFAR-10/bird1.png").convert('RGB') # 读取图像
trans = transforms.Compose([transforms.Scale((32,32)),
transforms.ToTensor(),
transforms.Normalize(mean=(0.5, 0.5, 0.5),
std=(0.5, 0.5, 0.5)),
])
img = trans(img)
img = img.to(device)
# 图片扩展多一维,因为输入到保存的模型中是4维的[batch_size,通道,长,宽],而普通图片只有三维,[通道,长,宽]
img = img.unsqueeze(0)
# 扩展后,为[1,1,28,28]
output = model(img)
prob = F.softmax(output,dim=1) #prob是10个分类的概率
print("概率",prob)
value, predicted = torch.max(output.data, 1)
print("类别",predicted.item())
print(value)
pred_class = classes[predicted.item()]
print("分类",pred_class)