深度卷积神经网络模型由于其层数多,需要训练的参数多,导致从零开始训练很深的卷积神经网络非常困难,同时训练很深的网络通畅需要大量的数据集,这对于设备算力不够的使用者非常不友好。幸运的是Pytorch已经提供了使用ImageNet数据集与与训练好的流行的深度学习网络,我们可以针对自己的需求,对与训练好的网络进行微调,从而快速完成自己的任务。
下面将会基于与训练好的VGG16网络,对其网络结构进行微调,使用自己的分类数据集,训练一个图像分类器。使用的数据集来自kaggle数据集中的10类猴子数据库,数据地址为https://www.kaggle.com/slothkong/10-money-species。在该数据集中包含训练数据集合验证数据集,其中训练数据集中每类约140张RGB图像,验证数据集中每类30张图像。针对该数据集使用VGG16的卷积层和池化层的预训练好的权重,提取数据特征,然后定义新的全连接层,用于图像的分类。
首先导入所需要的库和模块。
# import numpy as np
# import pandas as pd
# from sklearn.metrics import accuracy_score,confusion_matrix,classification_report
# import matplotlib.pyplot as plt
# import seaborn as sns
# import hiddenlayer as hl
# import torch
import torch.nn as nn
# from torch.optim import SGD,Adam
# import torch.utils.data as Data
from torchvision import models
# from torchvision import transforms
# from torchvision.datasets import ImageFolder
对于已经训练好的VGG16网络,需要首先导入网络。
vgg16=models.vgg16(pretrained=True)
vgg=vgg16.features
# for param in vgg.parameters():
# param.requires_grad_(False)
在上面的程序中,使用models.vgg16(pretrained=True)导入网络,其中参数pretrained=True表示导入的网络是使用ImageNet数据集预训练好的网络(如果第一次使用该程序,需要一定时间从网络上下载模型)。在得到的VGG16网络中,使用vgg16.features获取VGG16网络的特征提取模块,即前面的卷积池化层,不包括全连接层。为了提升网络的训练速度,只是用VGG16提取图像的特征,需要将VGG16的特征提取层参数冻结,不更新其权重,通过for循环和param.requires_grad_(False)即可。
VGG特征提取层预处理结束后,可在VGG16特征提取层之后添加新的全连接层,用于图像分类,程序定义网络结构如下:
class MyVggModel(nn.Module):
def __init__(self):
super(MyVggModel,self).__init__()
self.vgg=vgg
self.classifier=nn.Sequential(
nn.Linear(25088,512),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(512,256),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(256,10),
nn.Softmax(dim=1)
)
def forward(self,x):
x=self.vgg(x)
x=x.view(x.size(0),-1)
output=self.classifier(x)
return output
在上面的程序中,定义了一个卷积神经网络类MyVggModel,在该网络中,包含两个大的结构,一个是self.vgg,使用预训练好的VGG16的特征提取并且其参数的权重已经冻结;另一个是self.classifier,由三个全连接层组成,并且神经元的个数分别为512,256,和10.在全连接层中使用ReLU函数作为激活函数,并通过nn.Dropout()层防止过拟合。在网络的前向传播函数中,有self.classifier得到输出。
可以通过下面的程序查看网络的详细结构。
Myvggc=MyVggModel()
print(Myvggc)
输出结果为:
在定义好卷积神经网络Myvggc后,下面需要对数据集进行准备。首先定义训练集和验证集的预处
理过程,程序如下:
train_data_transforms=transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
])
val_data_transforms=transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
])
上面的程序定义了对训练集的预处理过程train_data_transforms,从而对训练集进行数据增强,对验证集的预处理过程val_data_trabsforms与train_data_transforms会有一些差异,其不需要对图像进行随机翻转与随机裁剪操作。在对读入的单张图像进行预处理时,通过RandomResizedCrop()对图像进行随机裁剪,使用RandomHorizontalFlip()将图像依概率p=0.5水平翻转,通过Resize()充值图像分辨率,通过CenterCrop()将图像按照给定的尺寸从中心裁剪,通过Normalize()将他徐昂的像素值进行标准化处理等。
因为每类图像都分别保存在一个单独的文件夹中,所以可以使用ImageFolder()函数从文件中读取训练集和验证集,数据读取的程序如下:
train_data_dir="data/chap6/10-monkey-species/training"
train_data=ImageFolder(train_data_dir,transform=train_data_transforms)
train_data_loader=Data.DataLoader(train_data,batch_size=32,shuffle=True,num_workers=2)
val_data_dir="data/chap6/10-monkey-species/validation"
val_data=ImageFolder(val_data_dir,transform=val_data_transforms)
val_data_loader=Data.DataLoader(val_data,batch_size=32,shuffle=True,num_workers=2)
print("训练集样本数:",len(train_data.targets))
print("验证集样本数:",len(val_data.targets))
输出结果如下:
上面的程序在读取图像后,分别使用Data.DataLoader()函数,将训练集和测试集处理为数据加载起train_data_loader和val_data_loader,并且每个batch包含32张图像。从输出结果发现,训练集有1097个样本,验证集有272个样本。下面我们获取训练集的一个batch图像,然后将获取的32张图像进行可视化,观察数据中图像的内容。
for step,(b_x,b_y) in enumerate(train_data_loader):
if step>0:
break
mean=np.array([0.485,0.456,0.406])
std=np.array([0.229,0.224,0.225])
plt.figure(figsize=(12,6))
for ii in np.arange(len(b_y)):
plt.subplot(4,8,ii+1)
image=b_x[ii,:,:,:].numpy().transpose((1,2,0))
image=std*image+mean
image=np.clip(image,0,1)
plt.imshow(image)
plt.title(b_y[ii].data.numpy())
plt.axis("off")
plt.subplots_adjust(hspace=0.3)
plt.show()
上面的程序在获取了一个batch图像后,再可视化前,需要将图像每个通道的像素值乘以对应的标准差并加上对应的均值。最后的图像如下: