1、什么是图像分类
一张图片胜过千言万语。我们不断地攫取视觉内容,解释它的含义,并且存储它们以备后用。但是,对于计算机要解释一张图片的内容是很难的,因为计算机看到的图片是一个大的数字矩阵,它对图像传递的思想、知识和意义一无所知。
为了理解图像的内容,我们必须应用图像分类(image classification),这是使用计算机视觉和机器学习算法从图像中抽取意义的任务。这个操作可以简单的为一张图像分配一个标签,如猫、狗还是大象,或者也可以高级到解释图像的内容并且返回一个人类可读的句子。
图像分类,核心是从给定的分类集合中给图像分配一个标签的任务。实际上,这意味着我们的任务是分析一个输入图像并返回一个将图像分类的标签。标签总是来自预定义的可能类别集。
示例:我们假定一个可能的类别集categories = {dog, cat, panda},之后我们提供一张图片(下图)给分类系统:
这里的目标是根据输入图像,从类别集中分配一个类别,这里为dog。
我们的分类系统也可以根据概率给图像分配多个标签,如dog:95%,cat:4%,panda:1%。
2、CIFAR10数据集
CIFAR-10是一个更接近普适物体的彩色图像数据集。CIFAR-10 是由Hinton 的学生Alex Krizhevsky 和Ilya Sutskever 整理的一个用于识别普适物体的小型数据集。一共包含10 个类别的RGB 彩色图片:飞机( airplane )、汽车( automobile )、鸟类( bird )、猫( cat )、鹿( deer )、狗( dog )、蛙类( frog )、马( horse )、船( ship )和卡车( truck )。在CIFAR-10里面的图片数据大小是3x32x32,即三通道彩色图,图片大小是32x32像素。每个类别有6000个图像,数据集中一共有50000 张训练图片和10000 张测试图片。
2.1 使用torchvision加载CIFAR10数据集
torchvision数据集加载完后的输出是范围在[0, 1]之间的PILImage。我们将其标准化为范围在[-1, 1]之间的张量。
import torch
import torchvision
from torchvision import transforms
transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))])
train_set = torchvision.datasets.CIFAR10(root='./cv/cifar10', transform=transform, train=True, download=True)
train_loader = torch.utils.data.DataLoader(train_set, batch_size=32, shuffle=True, num_workers=2)
test_set = torchvision.datasets.CIFAR10(root='./cv/cifar10', transform=transform, train=False, download=True)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=32, shuffle=False, num_workers=2)
classes = ('plane', 'car', 'bird', 'cat','deer', 'dog', 'frog', 'horse', 'ship', 'truck')
输出
Files already downloaded and verified
Files already downloaded and verified
对于没有下载过CIFAR10数据的,需要耐心等待数据下载完成。
2.2 查看图片
import matplotlib.pyplot as plt
import numpy as np
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
# 随机获取训练图片
dataiter = iter(train_loader)
images, labels = dataiter.next()
# 显示图片
imshow(torchvision.utils.make_grid(images[:4]))
# 打印图片标签
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
输出
3、LeNet卷积神经网络
LeNet 诞生于 1994 年,是最早的卷积神经网络之一,并且推动了深度学习领域的发展。自从 1988 年开始,在许多次成功的迭代后,这项由 Yann LeCun 完成的开拓性成果被命名为 LeNet5。
LeNet网络结构如下图所示:
- 输入层:为32 * 32的单通道图片(这里我们用的数据集为CIFAR10,需将通道数改为3)
- conv1:经过卷积核为(5 * 5),数量为6,得到28 * 28 * 6的特征层;在经过(2 * 2)步长为2的maxplool,得到14 * 14 * 6的feature map
- conv2:经过卷积核为(5 *5 ),数量为16. 得到10 * 10 * 16的feature map;再经过(2 * 2)步长为2的maxpool,得到5 * 5 * 16的feature map
- fc1: 将5 * 5 * 16的feature map展开为400的向量,再与120的神经元进行全连接
- fc2: 120神经元再与84的神经元全连接
- fc3: 最后84 * 10得到十分类的输出层,通过softmax得到各类别的概率值
3.1、pytorch实现Lenet5卷积网络
import torch.nn as nn
import torch.nn.functional as F
class LeNte5(nn.Module):
def __init__(self):
super(LeNte5,self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
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.view(-1, 16*5*5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = LeNte5()
print(net)
网络结构如下:
LeNte5(
(conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
(pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(fc1): Linear(in_features=400, out_features=120, bias=True)
(fc2): Linear(in_features=120, out_features=84, bias=True)
(fc3): Linear(in_features=84, out_features=10, bias=True)
)
3.2 定义损失函数和优化器
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
3.3 训练网络
for epoch in range(20):
running_loss = 0.0
total = 0.0
for i, data in enumerate(train_loader, 0):
inputs, labels = data
optimizer.zero_grad()
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
total += labels.size(0)
print('epoch: %d,loss: %.3f' % (epoch + 1, running_loss / total))
print('Finished Training')
输出:
epoch: 1,loss: 0.070
epoch: 2,loss: 0.060
epoch: 3,loss: 0.051
epoch: 4,loss: 0.046
epoch: 5,loss: 0.044
epoch: 6,loss: 0.042
epoch: 7,loss: 0.040
epoch: 8,loss: 0.038
epoch: 9,loss: 0.036
epoch: 10,loss: 0.035
epoch: 11,loss: 0.034
epoch: 12,loss: 0.033
epoch: 13,loss: 0.032
epoch: 14,loss: 0.031
epoch: 15,loss: 0.030
epoch: 16,loss: 0.029
epoch: 17,loss: 0.028
epoch: 18,loss: 0.027
epoch: 19,loss: 0.027
epoch: 20,loss: 0.026
Finished Training
经过20轮训练,通过测试集查看一下网络是否学到了识别图像呢?
testiter = iter(test_loader)
imgs, labels = testiter.next()
pred_labels = net(imgs) # 预测
_, pred_labels = torch.max(pred_labels, 1) # 预测结果
imshow(torchvision.utils.make_grid(imgs[:8]))
print('预测值:' + ' '.join('%5s' % classes[pred_labels[j]] for j in range(8)))
print('实际值:' + ' '.join('%5s' % classes[labels[j]] for j in range(8)))
预测结果如下:
可以看到预测的8张图片和真实的图片标签完全一致。说明网络确实学会了对这些图像进行分类。
最后查看一下网络到底学到了多少东西(识别的准去率是怎样的)。
correct = 0
total = 0
with torch.no_grad():
for data in test_loader:
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: 66 %
经过20轮训练Lenet-5在一万张图片的识别准确率达到了66%。