本文讲述如何使用自己已经训练好的VGG16模型来实现对图像的自动分类处理。本节中的内容将使用CPU主机而非GPU主机来实现。
主要学习内容有:
ImageNet数据集
Image数据集是斯坦福大学从互联网上收集大量图片后,并对其进行分类整理而形成的图像数据集合。在ILSVRC(ImageNet Large Scale Visual Recognition Challenge)竞赛中井场使用到这一数据集。
在PyTorch中可以轻松地使用ImageNet数据集中的ILSVRC2012数据集(分类数:1000种;训练用数据:120张;验证数据集:5万张;测试集:10万张),以及各种已经训练过的神经网络连接参数和各种已完成学习的模型。
VGG16模型
VGG16模型是在2014年的ILSVRC竞赛中人类人去排名第二的卷积神经网络模型。VGG16模型是由牛静大大学的VGG团队设计的16层神经网络模型,因此也成为VGG16模型。此外,还有层数为11、13、19的VGG模型版本。
本算法运行时使用的Python版本为3.6.2,使用的PyTorch版本时1.0.2。
conda create -n pytorch-bk1 python=3.6.2 -y
pip install torch-1.0.1-cp36-cp36m-win_amd64.whl --force-reinstall
pip install torchvision==0.2.2 --trusted-host pypi.douban.com
注意:如果之前已经安装过PyTorch或者安装不完全,可以使用后缀--force-reinstall
进行强制安装。
安装成功后,测试结果如下:
在开始运行编写的代码之前,必须先创建本文所涉及程序所使用的文件夹,并下载相关的文件。
程序的下载路径为:https://wwm.lanzouv.com/in8il0cmd1wb
下载完文件夹1_image_classification后,打开文件make_folders_and_data_downloads.ipynb并执行,程序就会自动生成下图所示的文件结构。
文件夹说明:
# 导入软件包
import numpy as np
import json
from PIL import Image
import matplotlib.pyplot as plt
%matplotlib inline
import torch
import torchvision
from torchvision import models, transforms
# 确认 PyTorch 的版本号
print("PyTorch Version: ",torch.__version__)
print("Torchvision Version: ",torchvision.__version__)
输出结果为:
PyTorch Version: 1.0.1
Torchvision Version: 0.2.2
利用已经训练好的VGG16模型,对文件夹data中金毛巡回犬的照片进行分类处理。
首先,使用ImageNet载入已经训练好参数的VGG16模型。第一次执行这段代码时,由于需要从网络下载参数数据,因此执行时间会稍微长一些。
#VGG-16已完成训练模型的载入
# 第一次执行时,由于需要从网络下载学习好的参数数据,因此执行时间会稍微长一些
#生成VGG-16模型的实例
use_pretrained = True # 使用已经训练好的参数
net = models.vgg16(pretrained=use_pretrained)
net.eval() # 设置为推测模式
# 输出模型的网络结构
print(net)
输出执行结果:
从输出结果中可以看出,VGG16模型的网络结构是由名为feature和classifier的两个模块组成的。在这个两个模块中,又分别包含卷积神经网络和全连接层。
可以看到,VGG16的名字虽然是16,但实际上是由38层网络组成的,而不是16层。这是因为16层指的只是其中的卷积神经网络层和全连接层的数量(其中不包括ReLU激活函数、池化层和Dropout层)。下图为VGG16模型的网络结构。
网络输入的图像的尺寸是颜色通道数为3的RGB格式,图像的高度和宽度均为224像素(batch_num,3,224,224)。图像尺寸前面的batch_num表示每个小批次处理的数量。上图中并没有显示最小批处理数量。
输入的图像首先是两次通过由3×3大小的卷积过滤器(64通道)和ReLU激活函数搭配而成的组合的处理,之后通过一个2×2大小的最大池化,这样就可以得到了一个112×112尺寸的一半大小的图像。在总共经过了5次这样卷积层、ReLU激活函数和最大池化的组合处理后,最终通过位于features模块中最后位置的最大池化处理后,数据的尺寸就变为了(512,7,7)。此外,使用PyTorch处理的数据对象被称为张量。
输入数据在通过features模块后,紧接着被传入classifier模块。位于开头的全连接层的输入参数数据为25088,输出参数数量为4096.这里的25088是通过classifier模块的输入图像的总参数量512×7×7=25088计算得到的。
在全连接层之后,接着会通过ReLU层和Dropout层,然后会再次通过一个全连接、 ReLU层和Dropout层的组合,最后通过一个神经数量为1000的分类类目的数量,用于表示输入图像属于1000个分类类别中的哪一个。
现在已经成功地加载了训练好的VGG16模型,接下来编写在图片被输入VGG16前的预处理部分的代码。在将图片输入VGG模型之前,必须先对数据进行预处理。
预处理就是将图片的尺寸转换为224×224,并对颜色信息进行标准化数据处理。对颜色信息进行标准化,就是对每个RGB值用平均值(0.485,0.456,0.406)和标准差(0.229,0.224,0.225)进行归一化处理。这种归一化的条件数据是从ILSVRC2012数据集中的监督数据中计算得到的。先前加载的已经训练好的VGG16模型正是用这一归一化条件对图像进行了预处理之后再进行训练而得到的模型。因此,也需要对输入的图片进行同样的预处理操作。
接下来,将编程实现图片的预处理类的代码。首先,创建了一个BaseTransform类,并尝试执行代码。
具体实现的代码如下所示。需要注意的是,PyTorch与Pillow(PIL)对图像像素的处理顺序是不同的。在PyTorch中,图像是按照颜色通道、高度、宽度的顺序来处理的,而Pillow(PIL)中是按照图像的高度、宽度、颜色通道的顺序处理的。因此,PyTorch中输出的张量的顺序是通过image_transformed=img_transformed.numpy().transpose((1,2,0))这一语句进行转换的。
此外,__call__()
这一函数是Python中的通用函数。该函数是在调用类的实例时,没有指定具体方法是被调用的函数。在生成BaseTransform的实例之后,如果不指定函数名而直接调用实例的变量名,__call__()
函数内的代码就会被执行。
# 对输入图片进行预处理的类
class BaseTransform():
"""
调整图片的尺寸,并对颜色进行规范化。
Attributes
----------
resize : int
指定调整尺寸后图片的大小
mean : (R, G, B)
各个颜色通道的平均值
std : (R, G, B)
各个颜色通道的标准偏差
"""
def __init__(self, resize, mean, std):
self.base_transform = transforms.Compose([
transforms.Resize(resize), #将较短边的长度作为resize的大小
transforms.CenterCrop(resize), #从图片中央截取resize × resize大小的区域
transforms.ToTensor(), #转换为Torch张量
transforms.Normalize(mean, std) #颜色信息的正规化
])
def __call__(self, img):
return self.base_transform(img)
#确认图像预处理的结果
# 1. 读取图片
image_file_path = './data/goldenretriever-3724972_640.jpg'
img = Image.open(image_file_path) # [高度][宽度][颜色RGB]
# 2. 显示处理前的图片示
plt.imshow(img)
plt.show()
# 3. 同时显示预处理前后的图片
resize = 224
mean = (0.485, 0.456, 0.406)
std = (0.229, 0.224, 0.225)
transform = BaseTransform(resize, mean, std)
img_transformed = transform(img) # torch.Size([3, 224, 224])
# 将 ( 颜色、高度、宽度 ) 转换为 ( 高度、宽度、颜色 ),并将取值范围限制在0~1
img_transformed = img_transformed.numpy().transpose((1, 2, 0))
img_transformed = np.clip(img_transformed, 0, 1)
plt.imshow(img_transformed)
plt.show()
下图所示,为图片预处理的输出结果。图像的尺寸被调整为224,颜色信息也进行了归一化处理。
下面,需要实现将VGG16模型1000维的输出结果转化为分类标签的ILSVRCRPredictor类。在之前下载的JSON文件imagenet_class_index.json中已经事先先保存了ILSVRC的分类标签列表,直接使用即可。
首先对需要实现的功能进行简要说明。从VGG16模型输出的数据被保存在大小为torch.Size([1,1000])的PyTorch张量中,这里需要将其转换为Numpy型变量。因此,首先调用.detach(),将输出结果从网络中分离出来;然后,对被detach的张量进行.numpy()调用,将其转换为Numpy型变量,并用np.argmax()获取最大索引值。所有这些操作都是在这一行代码中完成的maxid=np.argmax(out.detach().numpy())
。之后,从字典类型变量ILSVRC_class_index中获取maxid所对应的标签名。
#根据ILSVRC数据,从模型的输出结果计算出分类标签
ILSVRC_class_index = json.load(open('./data/imagenet_class_index.json', 'r'))
ILSVRC_class_index
# 根据输出结果对标签进行预测的后处理类
class ILSVRCPredictor():
"""
根据ILSVRC数据,从模型的输出结果计算出分类标签
Attributes
----------
class_index : dictionary
将类的index与标签名关联起来的字典型变量
"""
def __init__(self, class_index):
self.class_index = class_index
def predict_max(self, out):
"""
获得概率最大的ILSVRC分类标签名
Parameters
----------
out : torch.Size([1, 1000])
从Net中输出结果
Returns
-------
predicted_label_name : str
预测概率最高的分类标签的名称
"""
maxid = np.argmax(out.detach().numpy())
predicted_label_name = self.class_index[str(maxid)][1]
return predicted_label_name
以上,已经成功创建了图像的预处理类BaseTransform和网络输出的后处理类ILSVRCPredictor。训练完毕的VGG模型结构如下图所示。
输入图片经过BaseTranform的转换后,被作为VGG16模型的输入数据进行输入。模型输出的1000维的数据又经过ILSVRCPredictor的处理,被转换为预测概率最高的分类标签名,并作为最终的输出结果返回。
接下来实现这一连串的处理,并利用已经训练好的VGG模型对图片进行预测。
具体地代码实现如下所示。在将图片输入PyTorch网络中时,需要以最小批次的形式传递,因此这里使用了unsqueeze_(0),将小批次的维度追加到输入数据中。
# 载入ILSVRC的标签信息,并生成字典型变量
ILSVRC_class_index = json.load(open('./data/imagenet_class_index.json', 'r'))
# 生成ILSVRCPredictor的实例
predictor = ILSVRCPredictor(ILSVRC_class_index)
# 读取输入的图像
image_file_path = './data/goldenretriever-3724972_640.jpg'
img = Image.open(image_file_path) # [ 高度 ][ 宽度 ][ 颜色RGB]
# 完成预处理后,添加批次尺寸的维度
transform = BaseTransform(resize, mean, std) #创建预处理类
img_transformed = transform(img) # torch.Size([3, 224, 224])
inputs = img_transformed.unsqueeze_(0) # torch.Size([1, 3, 224, 224])
# 输入数据到模型中,并将模型的输出转换为标签
out = net(inputs) # torch.Size([1, 1000])
result = predictor.predict_max(out)
# 输出预测结果
print("输入图像的预测结果:", result)
输出结果为:
输入图像的预测结果: golden_retriever
执行上述代码,就可以得到golden_retriever这一输出结果,程序准确无误地将图片归类到金毛巡回犬的分类中。
以上就是本次学习内容,完成实现了用ImageNet将已经训练好的VGG16模型载入,并准确地将手头上未知图片(金毛巡回犬的照片)归类到ImageNet的分类中的程序代码。