【深度学习前沿应用】图像分类Fine-Tuning

时间:2022-10-08 07:18:22

【深度学习前沿应用】图像分类Fine-Tuning


作者简介:在校大学生一枚,华为云享专家,阿里云星级博主,腾云先锋(TDP)成员,云曦智划项目总负责人,全国高等学校计算机教学与产业实践资源建设专家委员会(TIPCC)志愿者,以及编程爱好者,期待和大家一起学习,一起进步~ . 博客主页ぃ灵彧が的学习日志 . 本文专栏机器学习 . 专栏寄语:若你决定灿烂,山无遮,海无拦 . 【深度学习前沿应用】图像分类Fine-Tuning

(文章目录)


前言

1. 什么是预训练-微调模式?

在计算机视觉领域,预训练-微调模式已经沿用了多年,即在大规模图片数据集预训练模型参数,然后将训练好的参数在新的小数据集任务上进行微调,从而产生泛化性能更好的模型。


2. 什么是ResNet?

ResNet为常用的预训练模型之一,其核心操作为卷积与残差连接。卷积层为3×3的滤波器,并遵循两个简单的设计规则:①对于相同的输出特征图尺寸,每层具有相同数量的滤波器;②如果特征图尺寸减半,则滤波器数量加倍,以保持每层的时间复杂度。直接用步长为2的卷积层进行下采样,网络以全局平均池化层和伴随softmax的1000维全连接层结束,其中,卷积层数为34,因此也称为ResNet34(如下图1所示)。

【深度学习前沿应用】图像分类Fine-Tuning

本小节将使用ResNet34预训练-微调框架,实现猫脸12分类。对于给定的猫脸,判断其所属类型。


一、数据加载及预处理

本实验数据集来源于网络开源数据集(https://aistudio.baidu.com/aistudio/datasetdetail/10954),该数据集中包含12类猫图片,总计数据量为2160,部分图片展示如下图1所示。

【深度学习前沿应用】图像分类Fine-Tuning


(一)、数据加载及预处理

首先将该数据集挂载到当前项目中,然后读取数据文件,将数据按照8:2划分为训练集与验证集

  1. 导入相关包
import os
import time
import os.path as osp
import zipfile

import numpy as np
import paddle
import paddle.nn as nn
import pandas as pd
import paddle.nn.functional as F
from PIL import Image
from paddle.io import Dataset, DataLoader
from paddle.optimizer import Adam
from paddle.vision import Compose, ToTensor, Resize
from paddle.vision.models import resnet34
from paddle.metric import Accuracy
from sklearn.model_selection import StratifiedShuffleSplit

  1. 将train划分为训练集和验证集
info = pd.read_csv(osp.join('./data', 'train_list.txt'), sep='\t', header=None)
images, labels = info.iloc[:, 0], info.iloc[:, 1]
split = StratifiedShuffleSplit(test_size=0.2)
train_idx, valid_idx = next(split.split(images, labels))
info_tr = info.iloc[train_idx, :]
info_va = info.iloc[valid_idx, :]
info_tr.to_csv('data/train.csv', header=False, index=False)
info_va.to_csv('data/valid.csv', header=False, index=False)

(二)、数据集封装

class CatDataset(Dataset):
    train_file = 'cat_12_train.zip'
    test_file = 'cat_12_test.zip'
    train_label = 'train_list.txt'

    def __init__(self, root, mode, transform=None):
        super(CatDataset, self).__init__()
        self.root = root
        self.mode = mode
        self.transform = transform

        if not osp.isfile(osp.join(root, self.train_file)) or \
                not osp.isfile(osp.join(root, self.train_label)) or \
                not osp.isfile(osp.join(root, self.test_file)):
            raise ValueError('wrong data path')
        if not osp.isdir(osp.join(self.root, 'cat_12_train')):
            with zipfile.ZipFile(osp.join(root, self.train_file)) as f:
                f.extractall(root)
            with zipfile.ZipFile(osp.join(root, self.test_file)) as f:
                f.extractall(root)
        if mode == 'train':
            info = pd.read_csv(osp.join(root, 'train_list.txt'), sep='\t', header=None)
            self.images = info.iloc[:, 0].to_list()
            self.labels = paddle.to_tensor(
                info.iloc[:, 1].to_list()
            )
        elif mode == 'train_':
            info = pd.read_csv(osp.join(root, 'train.csv'), header=None)
            self.images = info.iloc[:, 0].to_list()
            self.labels = paddle.to_tensor(
                info.iloc[:, 1].to_list()
            )
            pass
        elif mode == 'valid_':
            info = pd.read_csv(osp.join(root, 'valid.csv'), header=None)
            self.images = info.iloc[:, 0].to_list()
            self.labels = paddle.to_tensor(
                info.iloc[:, 1].to_list()
            )
        else:
            images = os.listdir(os.path.join(root, 'cat_12_test'))
            self.images = ['cat_12_test/'+image for image in images]
            self.labels = None

    def __getitem__(self, idx):
        image = Image.open(osp.join(self.root, self.images[idx]))
        if image.mode != 'RGB':
            image = image.convert('RGB')
        if self.transform is not None:
            image = self.transform(image)
       
        if self.mode == 'test':
            return image,
        else:
            label = self.labels[idx]
            return image, label

    def __len__(self):
        return len(self.images)

(三)、样本分类与统计

paddle.set_device('gpu' if paddle.is_compiled_with_cuda() else 'cpu')
transform = Compose([
    Resize([224, 224]),
    ToTensor()
])


train_ds = CatDataset('./data', 'train_', transform)
valid_ds = CatDataset('./data', 'valid_', transform)
train_dl = DataLoader(train_ds, batch_size=64, shuffle=True)
valid_dl = DataLoader(valid_ds, batch_size=64, shuffle=False)
print('训练集样本数:',train_ds.__len__())
print('验证集样本数:',valid_ds.__len__())


二、预训练模型加载

paddle,vision是飞桨在视觉领域的高层API,内部封装了常用的数据集以及常用预测训练模型,如LeNet、VGG系列、ResNet系列及MobileNet系列等。本实验使用resnet34为例,演示如何进行图像分类的微调。

准备好数据集之后,加载预训练模型,调用net=resnet34(pretrained=True),设置参数pretrained为True,便可使用预训练好的参数,否则,需要从头开始训练参数(首次加载预训练参数时需要从相关专业网络中下载):


加载预训练模型,并设置类别数目为12(猫的分类)

net = resnet34(pretrained=True, num_classes=12)

三、模型微调

加载好预训练的模型之后,定义模型的优化器、评价指标等,输入领域数据,执行微调:

(一)、定义优化器

optimizer = Adam(
    parameters=net.parameters(),
    learning_rate=1e-5
)

(二)、定义损失函数

loss_fn = nn.CrossEntropyLoss()

(三)、定义准确率评价指标

metric_fn = Accuracy()

(四)、微调20轮

for epoch in range(20):
    t0 = time.time()
    net.train()
    for data, label in train_dl:
        logit = net(data)
        loss = loss_fn(logit, label.astype('int64'))
        optimizer.clear_grad()
        loss.backward()
        optimizer.step() 
        
    # 验证  
    net.eval()
    loss_tr = 0.
    for data, label in train_dl:
        logit = net(data)
        label = label.astype('int64')
        loss_tr += loss_fn(logit, label).cpu().numpy()[0]
    loss_tr /= len(train_dl)

    loss_va = 0.
    for data, label in valid_dl:
        label = label.astype('int64')
        logit = net(data)
        loss_va += loss_fn(logit, label).cpu().numpy()[0]
        metric_fn.update(
            metric_fn.compute(logit, label)
        )
    loss_va /= len(valid_dl)
    acc_va = metric_fn.accumulate()
    metric_fn.reset()
    t = time.time() - t0
    print('[Epoch {:3d} {:.2f}s] train loss({:.4f}); valid loss({:.4f}), acc({:.2f})'
            .format(epoch, t, loss_tr, loss_va, acc_va))

训练过程部分输出如下图2所示: 【深度学习前沿应用】图像分类Fine-Tuning


四、模型预测

import matplotlib.image as mpimg
import matplotlib.pyplot as plt
def show_image(file_name): 
    img = mpimg.imread('data/'+file_name)
    plt.figure(figsize=(10,10))
    plt.imshow(img)  
    plt.show()


test_ds = CatDataset('./data', mode='test', transform=transform)
test_dl = DataLoader(test_ds, batch_size=32, shuffle=False)
test_pred = []
with paddle.no_grad():
    for data, in test_dl:
        logit = net(data)
        pred = paddle.argmax(
            F.softmax(logit, axis=-1),
            axis=-1
        )
        test_pred.append(pred.cpu().numpy())
test_pred = np.concatenate(test_pred, axis=0) 

for image, pred in zip(test_ds.images, test_pred.astype(np.int)):
    img = mpimg.imread('data/'+image)
    plt.figure(figsize=(10,10))
    plt.imshow(img)  
    plt.show()
    print('图片路径:%s, 图片预测类型:%d\n' % (image.split('/')[1], pred))

预测结果部分输出如下图3、4、5、6所示

【深度学习前沿应用】图像分类Fine-Tuning

【深度学习前沿应用】图像分类Fine-Tuning

【深度学习前沿应用】图像分类Fine-Tuning【深度学习前沿应用】图像分类Fine-Tuning


总结

本系列文章内容为根据清华社出版的《机器学习实践》所作的相关笔记和感悟,其中代码均为基于百度飞桨开发,若有任何侵权和不妥之处,请私信于我,定积极配合处理,看到必回!!!

最后,引用本次活动的一句话,来作为文章的结语~( ̄▽ ̄~)~:

【**学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。**】

【深度学习前沿应用】图像分类Fine-Tuning