Pytorch实现CNN模型的迁移学习——蜜蜂和蚂蚁图片分类项目

很多时候当训练一个新的图像分类任务时,一般不会完全从一个随机的模型开始训练,而是利用预训练的模型来加速训练的过程。经常使用在ImageNet上的预训练模型。

  • 这是一种transfer learning的方法。我们常用以下两种方法做迁移学习。
    • fine tuning: 从一个预训练模型开始,我们改变一些模型的架构,然后继续训练整个模型的参数。
    • feature extraction: 我们不再改变与训练模型的参数,而是只更新我们改变过的部分模型参数。我们之所以叫它feature extraction是因为我们把预训练的CNN模型当做一个特征提取模型,利用提取出来的特征做来完成我们的训练任务。
      以下是构建和训练迁移学习模型的基本步骤:
  • 初始化预训练模型
  • 把最后一层的输出层改变成我们想要分的类别总数
  • 定义一个optimizer来更新参数
  • 模型训练

一、项目介绍:

1、目标:本文尝试采用CNN实现图像蜜蜂和蚂蚁图像分类任务。
2、数据说明:使用hymenoptera_data数据集。包括两类图片, bees 和 ants, 这些数据都被处理成了可以使用ImageFolder来读取的格式。
输入数据维度:;输出数据维度
3、torchvision的datasets.ImageFolder参数说明:
(1)data_dir:数据的存储目录。设置成数据的根目录
(2)model_name:训练时使用的模型。设置成自己的训练模型,也可以使用封装好的模型。如,resnet, alexnet, vgg, squeezenet, densenet, inception等
(3)num_classes:表示数据集分类的类别数
(4)batch_size
(5)num_epochs
(6)feature_extract:表示训练时使用fine tuning还是feature extraction方法。如果feature_extract = False,整个模型都会被同时更新。如果feature_extract = True,只有模型的最后一层被更新。
4、网络框架:

二、设置参数

import numpy as np
import torchvision
from torchvision import datasets, transforms, models

import matplotlib.pyplot as plt
import time
import os
import copy
print("Torchvision Version: ",torchvision.__version__)

data_dir = './hymenoptera_data'
#模型可以选择[resnet, alexnet, vgg, squeezenet, densenet, inception]
model_name = 'resnet'
#数据的label分类个数
num_classes = 2
#训练的batch_size
batch_size=32
#训练的轮数
num_epochs = 15
#整个模型参数都参数训练,进行更新
feature_extract = True

二、代码实现

1、初始化模型
#定义需要更新的参数
def set_parameter_requires_grad(model,feature_extracting):
    if feature_extracting:
        for param in model.parameters():
            param.requires_grad = False
#使用resnet框架进行模型初始化           
def initialize_model(model_name,num_class,feature_extract,use_pretrained=True):
    if model_name == 'renet':
        model_ft = models.resnet18(pretrained=use_pretrained)  #使用resnet18最为初始化模型
        set_parameter_requires_grad(model_ft,feature_extract)  #模型中所有参数都更新
        num_ftrs = model_ft.fc.in_features
        model_ft.fc = nn.Linear(num_ftrs,num_classes)  #根据分类个数重新定义最后一层全连接层
        input_size = 224
        
    return model_ft,input_size
model_ft,input_size = initialize_model(model_name,num_classes,feature_extract,use_pretrained=True)
print(model_ft) 
2、导入数据集

根据模型输入的size,将数据预处理称为对应的格式

#定义图片的处理方式
data_transforms = {
    'train':transforms.Compose([
        transforms.RandomResizedCrop(input_size),
        transforms.RandomHorizontalFlip(),
        tranforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  #对三个通道分别从用不同的形式进行标准化
    ]),
    'val':transforms.Compose([
        transforms.Resize(input_size),
        transforms.CenterCrop(input_size),
        transforms.ToTensor(),
        transforms.Normalize([[0.485, 0.456, 0.406], [0.229, 0.224, 0.225]])
    ])
}

print("Initializing Datasets and Dataloaders...")

#生成训练集和测试集
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir,x),data_transforms[x]) for x in ['train','val']}
#生成训练集和测试集的迭代器
dataloaders_dict = {x: torch.utils.data.DataLoader(image_datasets[x],batch_size=batch_size,shuffle=True,num_workers=4) for x in ['train','val']}

#判断是否使用gpu
device = torch.device('cuda:0' if torch.cuda.is_availabel() else 'cpu')
3、观察原始数据
it = iter(dataloaders_dict["train"])
inputs, labels = next(it)
for inputs, labels in dataloaders_dict["train"]:
    print(labels.size())
    
print(len(dataloaders_dict["train"].dataset.imgs),len(dataloaders_dict["train"].dataset))
4、模型训练
torch.manual_seed(53113
use_cuda = torch.cuda.is_available()
device = torch.device("cuda" if use_cuda else "cpu")
                  
def train_model(model,dataloaders,criterion,optimizer,num_epochs=5):
    since = time.time()
    val_acc_history = []
    best_model_wts = copy.deepcopy(model.state_dict())   #保存最佳参数;copy.deepcopy一旦复制出来了,就应该是独立的了,原始变量改变其不会变,数值一样,但对象不同的。
    best_acc = 0.  #保存最高的准确率
    for epoch in range(num_epochs):
        print(f'Epoch {epoch}/{num_epochs-1}')
        print("-"*10)
        
        for phase in ['train','val']:
            running_loss = 0.
            running_corrects = 0.
            if phase == 'train':
                model.train()
            else:
                model.eval()
                
            for inputs, labels in dataloaders[phase]:   
                inputs = inputs.to(device)
                labels = labels.to(device)
                
                with torch.autograd.set_grad_enabled(phase=='train'):   #当phase=='train'为True是,可以进行自动求梯度计算。
                    outputs = model(inputs)   #将数据输入到模型中进行预测
                    loss = criterion(outputs,labels)  #损失计算
                    
                _,preds = torch.max(outputs,1)   #在所有输出中选取最大的最为输出概率
                if phase == 'train':
                    optimizer.zero_grad()   #每一次梯度计算后,所有梯度设置为0
                    loss.backward()    #反向求导
                    optimizer.step()   #参数优化
                    
                running_loss += loss.item() * inputs.size(0)   #损失*batch,等于每一批的总损失;每一批的总损失相加为所有样本的损失和
                running_corrects += torch.sum(preds.view(-1) == labels.view(-1))  #每批的预测正确的样本数量;每批预测正确的样本数量相加为所有样本中预测正确的样本数量
            
            epoch_loss = running_loss / len(dataloaders[phase].dataset)  #样本总损失 / 样本总个数 = 平均损失
            epoch_acc = running_corrects / len(dataloaders[phase].dataset) #样本预测正确的个数 / 样本总个数 = 平均准确率
                
            print(f'{phase} Loss:{epoch_loss} Acc:{epoch_acc}')  #取平均准确率最为当前epoch的准确率,与之前的进行比较,保存最高的
            if phase == 'val' and epoch_acc > best_acc:
                  best_acc = epoch_acc  #每个epoch更新一次准确率
                  best_model_wts = copy.deepcopy(model.state_dict())
            if phase == 'val':
                  val_acc_history.append(epoch_acc)  #将每个epoch的准确率添加到列表中
 
            
    time_elapsed = time.time() - since
    print(f'Training compete in {time_elapsed // 60}m {time_elapsed % 60}s')   #time_elapsed // 60表示相除后取商。
    print(f'Best val Acc: {best_acc}')
    
    model.load_state_dict(best_model_wts)
    return model,val_acc_history   #返回模型和评估集,每个epoch的最佳acc    

(1)对参数进行训练的训练集、测试集结果

model_ft = model_ft.to(device)

params_to_update = model_ft.parameters()  #获取模型所有的参数进行更新
print('Params to learn:')
if feature_extract:  #如果feature_extract为True,表示对参数进行更新
    params_to_update = []
    for name,param in model_ft.named_parameters():
        if param.requirs_grad == True:
            params_to_update.append(param)
            print('\t',name)
            
else:
    for name,param in model_ft.named_parameters():
        if param.requires_grad == True:
            print('\t',name)

训练结果:
Training compete in 0.0m 14.700076580047607s
Best val Acc: 0.954248366013072
(2)不对参数训练的训练集和测试集结果

scratch_model,_ = initlialize_model(model_name,num_classes,feature_extract=False,use_pretrained=False)
scratch_model = scratch_model.to(device)
scratch_optimizer = optim.SGD(scratch_model.parameters(), lr=0.001, momentum=0.9)
scratch_criterion = nn.CrossEntropyLoss()
-,scratch_hist = train_model(scratch_model, dataloaders_dict, scratch_criterion, scratch_optimizer, num_epochs=num_epochs)

训练结果:
Training compete in 0.0m 18.418847799301147s
Best val Acc: 0.7450980392156863
(3)是否对参数训练,效果对比

plt.title("Validation Accuracy vs. Number of Training Epochs")
plt.xlabel("Training Epochs")
plt.ylabel("Validation Accuracy")
plt.plot(range(1,num_epochs+1),ohist,label="Pretrained")
plt.plot(range(1,num_epochs+1),scratch_hist,label="Scratch")
plt.ylim((0,1.))
plt.xticks(np.arange(1, num_epochs+1, 1.0))
plt.legend()
plt.show()

在这里插入图片描述

参考文献:七月在线Pytorch课程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章