[Pytorch图像分类全流程实战]Task03:迁移学习微调

迁移学习是一种机器学习方法,就是把为任务A开发的模型作为初始点,重新使用在为任务B开发模型的过程中。

水果分类模型

在已经预训练好的模型的基础上,在自己的数据集上进行微调,复用预训练模型特征及权重来解决自己的问题

使用深度学习框架Pytorch,在lmageNet预训练图像分类模型(例如Resnet18)基础上,对自己的30类水果图像分类数据集进行迁移学习(transfer learning)微调(fine-tuning)训练,得到自己的图像分类模型。
在训练过程中,记录训练集和测试集的损失函数、准确率、Precision、Recall、f1-score等评估指标,使用wandb可视化面板监控。
为后续的新图像预测、测试集评估、可解释性分析、模型部署,奠定算法基础。

这里使用的云GPU还是老师推荐的:https://featurize.cn/?s=d7ce99f842414bfcaea5662a97581bd1

不过这个要规范归还,不然钱钱会不翼而飞

这个可以不用运行就有结果,时间比较紧的时候可以用,Kaggle那个需要自己运行且总是有点小麻烦,不用还是好用的

所用资料如下:

[Pytorch图像分类全流程实战]Task03:迁移学习微调_第1张图片

学习方法:先一遍代码再视频加入笔记

【A】安装配置环境

!pip install numpy pandas matplotlib seaborn plotly requests tqdm opencv-python pillow wandb -i https://pypi.tuna.tsinghua.edu.cn/simple

##下载安装Pytorch
!pip3 install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu113

##下载中文字体文件
!wget https://zihao-openmmlab.obs.cn-east-3.myhuaweicloud.com/20220716-mmclassification/dataset/SimHei.ttf --no-check-certificate

##创建目录
import os

# 存放结果文件
os.mkdir('output')

# 存放训练得到的模型权重
os.mkdir('checkpoints')

# 存放生成的图表
os.mkdir('图表')

##设置matplotlib中文字体
import matplotlib.pyplot as plt
%matplotlib inline

# # windows操作系统
# plt.rcParams['font.sans-serif']=['SimHei']  # 用来正常显示中文标签 
# plt.rcParams['axes.unicode_minus']=False  # 用来正常显示负号

# Mac操作系统,参考 https://www.ngui.cc/51cto/show-727683.html
# 下载 simhei.ttf 字体文件
# !wget https://zihao-openmmlab.obs.cn-east-3.myhuaweicloud.com/20220716-mmclassification/dataset/SimHei.ttf

# Linux操作系统,例如 云GPU平台:https://featurize.cn/?s=d7ce99f842414bfcaea5662a97581bd1
# 如果报错 Unable to establish SSL connection.,重新运行本代码块即可
!wget https://zihao-openmmlab.obs.cn-east-3.myhuaweicloud.com/20220716-mmclassification/dataset/SimHei.ttf -O /environment/miniconda3/lib/python3.7/site-packages/matplotlib/mpl-data/fonts/ttf/SimHei.ttf --no-check-certificate
!rm -rf /home/featurize/.cache/matplotlib

import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
matplotlib.rc("font",family='SimHei') # 中文字体
plt.rcParams['axes.unicode_minus']=False  # 用来正常显示负号

plt.plot([1,2,3], [100,500,300])
plt.title('matplotlib中文字体测试', fontsize=25)
plt.xlabel('X轴', fontsize=15)
plt.ylabel('Y轴', fontsize=15)
plt.show()

[Pytorch图像分类全流程实战]Task03:迁移学习微调_第2张图片
【B】准备图像分类数据集

 前面讲过构建自己的图像分类数据集:构建自己的图像分类数据集【两天搞定AI毕设】_哔哩哔哩_bilibili

##下载样例数据集
# 下载数据集压缩包
!wget https://zihao-openmmlab.obs.cn-east-3.myhuaweicloud.com/20220716-mmclassification/dataset/fruit30/fruit30_split.zip

# 解压
!unzip fruit30_split.zip >> /dev/null

# 删除压缩包
!rm fruit30_split.zip

##查看数据集目录结构
!sudo snap install tree

!tree fruit30_split -L 2

[Pytorch图像分类全流程实战]Task03:迁移学习微调_第3张图片
【c1】迁移学习微调训练-基础版

在自己的图像分类数据集上,使用ImageNet预训练图像分类模型初始化,改动分类层,迁移学习微调训练

##设置matplotlib中文字体
# # windows操作系统
# plt.rcParams['font.sans-serif']=['SimHei']  # 用来正常显示中文标签 
# plt.rcParams['axes.unicode_minus']=False  # 用来正常显示负号

# Mac操作系统,参考 https://www.ngui.cc/51cto/show-727683.html
# 下载 simhei.ttf 字体文件
# !wget https://zihao-openmmlab.obs.cn-east-3.myhuaweicloud.com/20220716-mmclassification/dataset/SimHei.ttf

# Linux操作系统,例如 云GPU平台:https://featurize.cn/?s=d7ce99f842414bfcaea5662a97581bd1
# 如果遇到 SSL 相关报错,重新运行本代码块即可
!wget https://zihao-openmmlab.obs.cn-east-3.myhuaweicloud.com/20220716-mmclassification/dataset/SimHei.ttf -O /environment/miniconda3/lib/python3.7/site-packages/matplotlib/mpl-data/fonts/ttf/SimHei.ttf
!rm -rf /home/featurize/.cache/matplotlib

import matplotlib
import matplotlib.pyplot as plt
matplotlib.rc("font",family='SimHei') # 中文字体
plt.rcParams['axes.unicode_minus']=False  # 用来正常显示负号

##导入工具包
import time
import os

import numpy as np
from tqdm import tqdm

import torch
import torchvision
import torch.nn as nn
import torch.nn.functional as F

import matplotlib.pyplot as plt
%matplotlib inline

# 忽略烦人的红色提示
import warnings
warnings.filterwarnings("ignore")

##获取计算硬件
# 有 GPU 就用 GPU,没有就用 CPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print('device', device)

##图像预处理
from torchvision import transforms

# 训练集图像预处理:缩放裁剪、图像增强、转 Tensor、归一化
train_transform = transforms.Compose([transforms.RandomResizedCrop(224),
                                      transforms.RandomHorizontalFlip(),
                                      transforms.ToTensor(),
                                      transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
                                     ])

# 测试集图像预处理-RCTN:缩放、裁剪、转 Tensor、归一化
test_transform = transforms.Compose([transforms.Resize(256),
                                     transforms.CenterCrop(224),
                                     transforms.ToTensor(),
                                     transforms.Normalize(
                                         mean=[0.485, 0.456, 0.406], 
                                         std=[0.229, 0.224, 0.225])
                                    ])

##载入图像分类数据集
# 数据集文件夹路径
dataset_dir = 'fruit30_split'

train_path = os.path.join(dataset_dir, 'train')
test_path = os.path.join(dataset_dir, 'val')
print('训练集路径', train_path)
print('测试集路径', test_path)

from torchvision import datasets

# 载入训练集
train_dataset = datasets.ImageFolder(train_path, train_transform)

# 载入测试集
test_dataset = datasets.ImageFolder(test_path, test_transform)

print('训练集图像数量', len(train_dataset))
print('类别个数', len(train_dataset.classes))
print('各类别名称', train_dataset.classes)

 

print('测试集图像数量', len(test_dataset))
print('类别个数', len(test_dataset.classes))
print('各类别名称', test_dataset.classes)

 [Pytorch图像分类全流程实战]Task03:迁移学习微调_第4张图片

##类别和索引号——对应
# 各类别名称
class_names = train_dataset.classes
n_class = len(class_names)

print(class_names)

 [Pytorch图像分类全流程实战]Task03:迁移学习微调_第5张图片

# 映射关系:类别 到 索引号
train_dataset.class_to_idx

 [Pytorch图像分类全流程实战]Task03:迁移学习微调_第6张图片

# 映射关系:索引号 到 类别
idx_to_labels = {y:x for x,y in train_dataset.class_to_idx.items()}

print(idx_to_labels)

 [Pytorch图像分类全流程实战]Task03:迁移学习微调_第7张图片

# 保存为本地的 npy 文件
np.save('idx_to_labels.npy', idx_to_labels)
np.save('labels_to_idx.npy', train_dataset.class_to_idx)

##定义数据加载器DataLoader
from torch.utils.data import DataLoader

BATCH_SIZE = 32

# 训练集的数据加载器
train_loader = DataLoader(train_dataset,
                          batch_size=BATCH_SIZE,
                          shuffle=True,
                          num_workers=4
                         )

# 测试集的数据加载器
test_loader = DataLoader(test_dataset,
                         batch_size=BATCH_SIZE,
                         shuffle=False,
                         num_workers=4
                        )

##查看一个batch的图像和标注
# DataLoader 是 python生成器,每次调用返回一个 batch 的数据
images, labels = next(iter(train_loader))

##可视化一个batch的图像和标注
# 将数据集中的Tensor张量转为numpy的array数据类型
images = images.numpy()

plt.hist(images[5].flatten(), bins=50)
plt.show()

 [Pytorch图像分类全流程实战]Task03:迁移学习微调_第8张图片

# batch 中经过预处理的图像
idx = 2
plt.imshow(images[idx].transpose((1,2,0))) # 转为(224, 224, 3)
plt.title('label:'+str(labels[idx].item()))

 [Pytorch图像分类全流程实战]Task03:迁移学习微调_第9张图片

# 原始图像
idx = 2
mean = np.array([0.485, 0.456, 0.406])
std = np.array([0.229, 0.224, 0.225])
plt.imshow(np.clip(images[idx].transpose((1,2,0)) * std + mean, 0, 1))
plt.title('label:'+ pred_classname)
plt.show()

 [Pytorch图像分类全流程实战]Task03:迁移学习微调_第10张图片

以上为在train loader中调取了一个batch的数据和样本

真实训练中就是一口一口喂

##导入训练需使用的工具包
from torchvision import models
import torch.optim as optim

选择迁移学习训练方式
斯坦福CS231N【迁移学习】中文精讲: 【子豪兄】精讲CS231N斯坦福计算机视觉公开课(2020最新)_哔哩哔哩_bilibili
斯坦福CS231N【迁移学习】官方笔记:CS231n Convolutional Neural Networks for Visual Recognition

不同的迁移学习策略取决于自己数据集的数据分布和ImageNet大数据集的数据分布的差异

##选择一:只微调训练模型最后一层(全连接分类层)
model = models.resnet18(pretrained=True) # 载入预训练模型

# 修改全连接层,使得全连接层的输出与当前数据集类别数对应
# 新建的层默认 requires_grad=True
model.fc = nn.Linear(model.fc.in_features, n_class)

# 只微调训练最后一层全连接层的参数,其它层冻结
optimizer = optim.Adam(model.fc.parameters())

##选择二:微调训练所有层
# model = models.resnet18(pretrained=True) # 载入预训练模型

# model.fc = nn.Linear(model.fc.in_features, n_class)

# optimizer = optim.Adam(model.parameters())

##选择三:随机初始化模型全部权重,从头训练所有层
# model = models.resnet18(pretrained=False) # 只载入模型结构,不载入预训练权重参数

# model.fc = nn.Linear(model.fc.in_features, n_class)

# optimizer = optim.Adam(model.parameters())

##训练配置
model = model.to(device)

# 交叉熵损失函数
criterion = nn.CrossEntropyLoss() 

# 训练轮次 Epoch
EPOCHS = 20

##模拟—个batch的训练
# 获得一个 batch 的数据和标注
images, labels = next(iter(train_loader))
images = images.to(device)
labels = labels.to(device)

# 输入模型,执行前向预测
outputs = model(images)

# 获得当前 batch 所有图像的预测类别 logit 分数
outputs.shape

# 由 logit,计算当前 batch 中,每个样本的平均交叉熵损失函数值
loss = criterion(outputs, labels)

# 反向传播“三部曲”
optimizer.zero_grad() # 清除梯度
loss.backward() # 反向传播
optimizer.step() # 优化更新

# 获得当前 batch 所有图像的预测类别
_, preds = torch.max(outputs, 1)

##运行完整训练
# 遍历每个 EPOCH
for epoch in tqdm(range(EPOCHS)):

    model.train()

    for images, labels in train_loader:  # 获得一个 batch 的数据和标注
        images = images.to(device)
        labels = labels.to(device)

        outputs = model(images)
        loss = criterion(outputs, labels) # 计算当前 batch 中,每个样本的平均交叉熵损失函数值
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

##在测试集上初步测试
model.eval()
with torch.no_grad():
    correct = 0
    total = 0
    for images, labels in tqdm(test_loader):
        images = images.to(device)
        labels = labels.to(device)
        outputs = model(images)
        _, preds = torch.max(outputs, 1) #获取batch置信度最高的类别
        total += labels.size(0)
        correct += (preds == labels).sum()

    print('测试集上的准确率为 {:.3f} %'.format(100 * correct / total))

##保存模型
torch.save(model, 'checkpoints/fruit30_pytorch_20220814.pth')

参考文档

Transfer Learning for Computer Vision Tutorial — PyTorch Tutorials 1.13.1+cu117 documentation

 Pytorch预训练图像分类模型识别预测【两天搞定AI毕设】_哔哩哔哩_bilibili

【子豪兄】玩转MNIST数据集_哔哩哔哩_bilibili

【子豪兄Pytorch】二十分钟搭建神经网络分类Fashion-MNIST数据集时尚物品_哔哩哔哩_bilibili

【c2]迁移学习微调训练-进阶版

在训练过程中加入了一些模型的可视化和模型的监控与记录日志

##导入工具包
import time
import os
from tqdm import tqdm

import pandas as pd
import numpy as np

import torch
import torchvision
import torch.nn as nn
import torch.nn.functional as F

import matplotlib.pyplot as plt
%matplotlib inline

# 忽略烦人的红色提示
import warnings
warnings.filterwarnings("ignore")

# 获取计算硬件
# 有 GPU 就用 GPU,没有就用 CPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print('device', device)

##图像预处理
from torchvision import transforms

# 训练集图像预处理:缩放裁剪、图像增强、转 Tensor、归一化
train_transform = transforms.Compose([transforms.RandomResizedCrop(224),
                                      transforms.RandomHorizontalFlip(),
                                      transforms.ToTensor(),
                                      transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
                                     ])

# 测试集图像预处理-RCTN:缩放、裁剪、转 Tensor、归一化
test_transform = transforms.Compose([transforms.Resize(256),
                                     transforms.CenterCrop(224),
                                     transforms.ToTensor(),
                                     transforms.Normalize(
                                         mean=[0.485, 0.456, 0.406], 
                                         std=[0.229, 0.224, 0.225])
                                    ])

##载入图像分类数据集
# 数据集文件夹路径
dataset_dir = 'fruit30_split'

train_path = os.path.join(dataset_dir, 'train')
test_path = os.path.join(dataset_dir, 'val')
print('训练集路径', train_path)
print('测试集路径', test_path)

from torchvision import datasets
# 载入训练集
train_dataset = datasets.ImageFolder(train_path, train_transform)
# 载入测试集
test_dataset = datasets.ImageFolder(test_path, test_transform)

print('训练集图像数量', len(train_dataset))
print('类别个数', len(train_dataset.classes))
print('各类别名称', train_dataset.classes)
print('测试集图像数量', len(test_dataset))
print('类别个数', len(test_dataset.classes))
print('各类别名称', test_dataset.classes)

[Pytorch图像分类全流程实战]Task03:迁移学习微调_第11张图片

##类别和索引号映射字典
# 各类别名称
class_names = train_dataset.classes
n_class = len(class_names)
# 映射关系:类别 到 索引号
train_dataset.class_to_idx
# 映射关系:索引号 到 类别
idx_to_labels = {y:x for x,y in train_dataset.class_to_idx.items()}

print(idx_to_labels)

[Pytorch图像分类全流程实战]Task03:迁移学习微调_第12张图片

##定义数据加载器DataLoader
from torch.utils.data import DataLoader

BATCH_SIZE = 32

# 训练集的数据加载器
train_loader = DataLoader(train_dataset,
                          batch_size=BATCH_SIZE,
                          shuffle=True,
                          num_workers=4
                         )

# 测试集的数据加载器
test_loader = DataLoader(test_dataset,
                         batch_size=BATCH_SIZE,
                         shuffle=False,
                         num_workers=4
                        )

##导入训练需使用的工具包
from torchvision import models
import torch.optim as optim
from torch.optim import lr_scheduler

##选择迁移学习训练方式

##选择一:只微调训练模型最后一层(全连接分类层)
model = models.resnet18(pretrained=True) # 载入预训练模型

# 修改全连接层,使得全连接层的输出与当前数据集类别数对应
# 新建的层默认 requires_grad=True
model.fc = nn.Linear(model.fc.in_features, n_class)

# 只微调训练最后一层全连接层的参数,其它层冻结
optimizer = optim.Adam(model.fc.parameters())

##选择二:微调训练所有层
# model = models.resnet18(pretrained=True) # 载入预训练模型

# model.fc = nn.Linear(model.fc.in_features, n_class)

# optimizer = optim.Adam(model.parameters())

##选择三:随机初始化模型全部权重,从头训练所有层
# model = models.resnet18(pretrained=False) # 只载入模型结构,不载入预训练权重参数

# model.fc = nn.Linear(model.fc.in_features, n_class)

# optimizer = optim.Adam(model.parameters())

##训练配置
model = model.to(device)

# 交叉熵损失函数
criterion = nn.CrossEntropyLoss() 

# 训练轮次 Epoch
EPOCHS = 30

# 学习率降低策略
lr_scheduler = lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5) #每隔5个EPOCHS学习率降低一半

##函数:在训练集上训练
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
from sklearn.metrics import accuracy_score
from sklearn.metrics import f1_score
from sklearn.metrics import roc_auc_score

def train_one_batch(images, labels):
    '''
    运行一个 batch 的训练,返回当前 batch 的训练日志
    '''
    
    # 获得一个 batch 的数据和标注
    images = images.to(device)
    labels = labels.to(device)
    
    outputs = model(images) # 输入模型,执行前向预测
    loss = criterion(outputs, labels) # 计算当前 batch 中,每个样本的平均交叉熵损失函数值
    
    # 优化更新权重
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    # 获取当前 batch 的标签类别和预测类别
    _, preds = torch.max(outputs, 1) # 获得当前 batch 所有图像的预测类别
    preds = preds.cpu().numpy()
    loss = loss.detach().cpu().numpy()
    outputs = outputs.detach().cpu().numpy()
    labels = labels.detach().cpu().numpy()
    
    log_train = {}
    log_train['epoch'] = epoch
    log_train['batch'] = batch_idx
    # 计算分类评估指标
    log_train['train_loss'] = loss
    log_train['train_accuracy'] = accuracy_score(labels, preds)
    # log_train['train_precision'] = precision_score(labels, preds, average='macro')
    # log_train['train_recall'] = recall_score(labels, preds, average='macro')
    # log_train['train_f1-score'] = f1_score(labels, preds, average='macro')
    
    return log_train

##函数:在整个测试集上评估
def evaluate_testset():
    '''
    在整个测试集上评估,返回分类评估指标日志
    '''

    loss_list = []
    labels_list = []
    preds_list = []
    
    with torch.no_grad():
        for images, labels in test_loader: # 生成一个 batch 的数据和标注
            images = images.to(device)
            labels = labels.to(device)
            outputs = model(images) # 输入模型,执行前向预测

            # 获取整个测试集的标签类别和预测类别
            _, preds = torch.max(outputs, 1) # 获得当前 batch 所有图像的预测类别
            preds = preds.cpu().numpy()
            loss = criterion(outputs, labels) # 由 logit,计算当前 batch 中,每个样本的平均交叉熵损失函数值
            loss = loss.detach().cpu().numpy()
            outputs = outputs.detach().cpu().numpy()
            labels = labels.detach().cpu().numpy()

            loss_list.append(loss)
            labels_list.extend(labels)
            preds_list.extend(preds)
        
    log_test = {}
    log_test['epoch'] = epoch
    
    # 计算分类评估指标
    log_test['test_loss'] = np.mean(loss)
    log_test['test_accuracy'] = accuracy_score(labels_list, preds_list)
    log_test['test_precision'] = precision_score(labels_list, preds_list, average='macro')
    log_test['test_recall'] = recall_score(labels_list, preds_list, average='macro')
    log_test['test_f1-score'] = f1_score(labels_list, preds_list, average='macro')
    
    return log_test

##训练开始之前,记录日志
epoch = 0
batch_idx = 0
best_test_accuracy = 0


# 训练日志-训练集
df_train_log = pd.DataFrame()
log_train = {}
log_train['epoch'] = 0
log_train['batch'] = 0
images, labels = next(iter(train_loader))
log_train.update(train_one_batch(images, labels))
df_train_log = df_train_log.append(log_train, ignore_index=True)

print(df_train_log)

# 训练日志-测试集
df_test_log = pd.DataFrame()
log_test = {}
log_test['epoch'] = 0
log_test.update(evaluate_testset())
df_test_log = df_test_log.append(log_test, ignore_index=True)

print(df_test_log)

开始训练,使用wandb监控记录模型训练过程中的指标

登录wandb

1. 安装wandb: pip install wandb
2. 登录wandb:在命令行中运行wandb login

3. 按提示复制粘则贴APl Key至命令行中

##创建wandb可视化项目
import wandb

wandb.init(project='fruit30', name=time.strftime('%m%d%H%M%S'))

[Pytorch图像分类全流程实战]Task03:迁移学习微调_第13张图片 

##运行训练
for epoch in range(1, EPOCHS+1):
    
    print(f'Epoch {epoch}/{EPOCHS}')
    
    ## 训练阶段
    model.train()
    for images, labels in tqdm(train_loader): # 获得一个 batch 的数据和标注
        batch_idx += 1
        log_train = train_one_batch(images, labels)
        df_train_log = df_train_log.append(log_train, ignore_index=True)
        wandb.log(log_train)
        
    lr_scheduler.step()

    ## 测试阶段
    model.eval()
    log_test = evaluate_testset()
    df_test_log = df_test_log.append(log_test, ignore_index=True)
    wandb.log(log_test)
    
    # 保存最新的最佳模型文件
    if log_test['test_accuracy'] > best_test_accuracy: 
        # 删除旧的最佳模型文件(如有)
        old_best_checkpoint_path = 'checkpoints/best-{:.3f}.pth'.format(best_test_accuracy)
        if os.path.exists(old_best_checkpoint_path):
            os.remove(old_best_checkpoint_path)
        # 保存新的最佳模型文件
        new_best_checkpoint_path = 'checkpoints/best-{:.3f}.pth'.format(log_test['test_accuracy'])
        torch.save(model, new_best_checkpoint_path)
        print('保存新的最佳模型', 'checkpoints/best-{:.3f}.pth'.format(best_test_accuracy))
        best_test_accuracy = log_test['test_accuracy']

df_train_log.to_csv('训练日志-训练集.csv', index=False)
df_test_log.to_csv('训练日志-测试集.csv', index=False)

##在测试集上评价
# 载入最佳模型作为当前模型
model = torch.load('checkpoints/best-{:.3f}.pth'.format(best_test_accuracy))

model.eval()
print(evaluate_testset())

【D】可视化训练日志

##导入工具包
import pandas as pd

import matplotlib.pyplot as plt
%matplotlib inline

##设置matplotlib中文字体
# # windows操作系统
# plt.rcParams['font.sans-serif']=['SimHei']  # 用来正常显示中文标签 
# plt.rcParams['axes.unicode_minus']=False  # 用来正常显示负号

# Mac操作系统,参考 https://www.ngui.cc/51cto/show-727683.html
# 下载 simhei.ttf 字体文件
# !wget https://zihao-openmmlab.obs.cn-east-3.myhuaweicloud.com/20220716-mmclassification/dataset/SimHei.ttf

# Linux操作系统,例如 云GPU平台:https://featurize.cn/?s=d7ce99f842414bfcaea5662a97581bd1
# 如果遇到 SSL 相关报错,重新运行本代码块即可
!wget https://zihao-openmmlab.obs.cn-east-3.myhuaweicloud.com/20220716-mmclassification/dataset/SimHei.ttf -O /environment/miniconda3/lib/python3.7/site-packages/matplotlib/mpl-data/fonts/ttf/SimHei.ttf
!rm -rf /home/featurize/.cache/matplotlib

import matplotlib
import matplotlib.pyplot as plt
matplotlib.rc("font",family='SimHei') # 中文字体

##载入训练日志表格
df_train = pd.read_csv('训练日志-训练集.csv')
df_test = pd.read_csv('训练日志-测试集.csv')

print(df_train)

 

epoch batch train_loss train_accuracy
0 0.0 0.0 3.568267 0.000000
1 1.0 1.0 3.721788 0.031250
2 1.0 2.0 3.540271 0.062500
3 1.0 3.0 3.493849 0.062500
4 1.0 4.0 3.758318 0.062500
... ... ... ... ...
4106 30.0 4106.0 0.284847 0.937500
4107 30.0 4107.0 0.463139 0.781250
4108 30.0 4108.0 0.230596 0.937500
4109 30.0 4109.0 0.198852 0.937500
4110 30.0 4110.0 0.357690 0.956522

4111 rows × 4 columns

print(df_test)
epoch test_loss test_accuracy test_precision test_recall test_f1-score
0 0.0 3.224075 0.035250 0.017963 0.034687 0.016466
1 1.0 0.614413 0.731911 0.773937 0.729183 0.726178
2 2.0 0.309934 0.791280 0.805776 0.789615 0.786697
3 3.0 0.343570 0.826531 0.838070 0.824748 0.824690
4 4.0 0.472587 0.837662 0.847906 0.837459 0.836966
5 5.0 0.417531 0.832096 0.843639 0.829974 0.831244
6 6.0 0.263817 0.851577 0.858356 0.850947 0.850593
7 7.0 0.210574 0.843228 0.855338 0.841779 0.843126
8 8.0 0.204230 0.867347 0.874752 0.866211 0.866007
9 9.0 0.232938 0.850649 0.860808 0.849349 0.847877
10 10.0 0.322416 0.849722 0.865295 0.848496 0.846039
11 11.0 0.172115 0.866419 0.877599 0.864662 0.864998
12 12.0 0.285754 0.861781 0.875341 0.860255 0.860449
13 13.0 0.218701 0.869202 0.875127 0.868420 0.868948
14 14.0 0.282180 0.864564 0.869911 0.862939 0.863354
15 15.0 0.246851 0.872913 0.880009 0.871480 0.871678
16 16.0 0.165407 0.867347 0.874303 0.867302 0.867304
17 17.0 0.061076 0.859926 0.868373 0.859063 0.857848
18 18.0 0.343297 0.863636 0.873884 0.862973 0.863741
19 19.0 0.172381 0.871058 0.879093 0.869936 0.871374
20 20.0 0.240845 0.867347 0.873157 0.866652 0.866904
21 21.0 0.196387 0.871985 0.883942 0.870201 0.871514
22 22.0 0.414227 0.876623 0.882219 0.875006 0.875602
23 23.0 0.190263 0.877551 0.882674 0.877371 0.877009
24 24.0 0.149703 0.865492 0.876651 0.864660 0.866338
25 25.0 0.049876 0.868275 0.879614 0.867451 0.867966
26 26.0 0.125152 0.872913 0.876978 0.872943 0.871724
27 27.0 0.081084 0.872913 0.879330 0.873042 0.872056
28 28.0 0.129047 0.888683 0.895575 0.888689 0.888649
29 29.0 0.106969 0.869202 0.880197 0.866905 0.868556
30 30.0 0.246746 0.873840 0.880458 0.872479 0.872441
##训练集损失函数
plt.figure(figsize=(16, 8))

x = df_train['batch']
y = df_train['train_loss']

plt.plot(x, y, label='训练集')

plt.tick_params(labelsize=20)
plt.xlabel('batch', fontsize=20)
plt.ylabel('loss', fontsize=20)
plt.title('训练集损失函数', fontsize=25)
plt.savefig('图表/训练集损失函数.pdf', dpi=120, bbox_inches='tight')

plt.show()

[Pytorch图像分类全流程实战]Task03:迁移学习微调_第14张图片

##训练集准确率
plt.figure(figsize=(16, 8))

x = df_train['batch']
y = df_train['train_accuracy']

plt.plot(x, y, label='训练集')

plt.tick_params(labelsize=20)
plt.xlabel('batch', fontsize=20)
plt.ylabel('loss', fontsize=20)
plt.title('训练集准确率', fontsize=25)
plt.savefig('图表/训练集准确率.pdf', dpi=120, bbox_inches='tight')

plt.show()

[Pytorch图像分类全流程实战]Task03:迁移学习微调_第15张图片

##测试集损失函数
plt.figure(figsize=(16, 8))

x = df_test['epoch']
y = df_test['test_loss']

plt.plot(x, y, label='测试集')

plt.tick_params(labelsize=20)
plt.xlabel('epoch', fontsize=20)
plt.ylabel('loss', fontsize=20)
plt.title('测试集损失函数', fontsize=25)
plt.savefig('图表/测试集损失函数.pdf', dpi=120, bbox_inches='tight')

plt.show()

[Pytorch图像分类全流程实战]Task03:迁移学习微调_第16张图片

 

##测试集评估指标
from matplotlib import colors as mcolors
import random
random.seed(124)
colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k', 'tab:blue', 'tab:orange', 'tab:green', 'tab:red', 'tab:purple', 'tab:brown', 'tab:pink', 'tab:gray', 'tab:olive', 'tab:cyan', 'black', 'indianred', 'brown', 'firebrick', 'maroon', 'darkred', 'red', 'sienna', 'chocolate', 'yellow', 'olivedrab', 'yellowgreen', 'darkolivegreen', 'forestgreen', 'limegreen', 'darkgreen', 'green', 'lime', 'seagreen', 'mediumseagreen', 'darkslategray', 'darkslategrey', 'teal', 'darkcyan', 'dodgerblue', 'navy', 'darkblue', 'mediumblue', 'blue', 'slateblue', 'darkslateblue', 'mediumslateblue', 'mediumpurple', 'rebeccapurple', 'blueviolet', 'indigo', 'darkorchid', 'darkviolet', 'mediumorchid', 'purple', 'darkmagenta', 'fuchsia', 'magenta', 'orchid', 'mediumvioletred', 'deeppink', 'hotpink']
markers = [".",",","o","v","^","<",">","1","2","3","4","8","s","p","P","*","h","H","+","x","X","D","d","|","_",0,1,2,3,4,5,6,7,8,9,10,11]
linestyle = ['--', '-.', '-']
def get_line_arg():
    '''
    随机产生一种绘图线型
    '''
    line_arg = {}
    line_arg['color'] = random.choice(colors)
    # line_arg['marker'] = random.choice(markers)
    line_arg['linestyle'] = random.choice(linestyle)
    line_arg['linewidth'] = random.randint(1, 4)
    # line_arg['markersize'] = random.randint(3, 5)
    return line_arg

metrics = ['test_accuracy', 'test_precision', 'test_recall', 'test_f1-score']

plt.figure(figsize=(16, 8))

x = df_test['epoch']
for y in metrics:
    plt.plot(x, df_test[y], label=y, **get_line_arg())

plt.tick_params(labelsize=20)
plt.ylim([0, 1])
plt.xlabel('epoch', fontsize=20)
plt.ylabel(y, fontsize=20)
plt.title('测试集分类评估指标', fontsize=25)
plt.savefig('图表/测试集分类评估指标.pdf', dpi=120, bbox_inches='tight')

plt.legend(fontsize=20)

plt.show()

[Pytorch图像分类全流程实战]Task03:迁移学习微调_第17张图片

【E】总结与扩展

注意事项

严禁把测试集图像用于训练(反向传播更新权重)

抛开baseline基准模型谈性能(速度、精度),都是耍流氓

测试集上的准确率越高,模型就一定越好吗?

常用数据集中存在大量的错标、漏标:https://mp.weixin.qq.com/s/4NbIA4wsNdX-N2uMOUmPLA

创新点展望

更换不同预训练图像分类模型

分别尝试三种不同的迁移学习训练配置:只微调训练模型最后一层(全连接分类层)、微调训练所有层、随机初始化模型全部权重,从头训练所有层

更换不同的优化器、学习率

扩展阅读

同济子豪兄的论文精读视频:Docs

开源图像分类算法库 MMClassificaiton:https://github.com/open-mmlab/mmclassification

**机器学习分类评估指标**

手绘笔记讲解:手绘图解分类器评估指标_哔哩哔哩_bilibili

混淆矩阵:
混淆矩阵详解_哔哩哔哩_bilibili

混淆矩阵交互演示1_哔哩哔哩_bilibili

ROC曲线:
ROC曲线详解_哔哩哔哩_bilibili

ROC曲线交互演示1_哔哩哔哩_bilibili

ROC曲线交互演示2_哔哩哔哩_bilibili

F1-score:F1-Score详解_哔哩哔哩_bilibili

F-beta-score:正负样本不均衡时的分类评估指标_哔哩哔哩_bilibili

训练好图像分类模型之后,做什么?

在新图像、视频、摄像头实时画面预测

在测试集上评估:混淆矩阵、ROC曲线、PR曲线、语义特征降维可视化

可解释性分析:CAM热力图

模型TensorRT部署:智能手机、开发板、浏览器、服务器

转ONNX并可视化模型结构

MMClassification图像分类、MMDeploy模型部署

开发图像分类APP和微信小程序

 总结:本节使用深度学习框架Pytorch,在lmageNet预训练图像分类模型基础上,对自己的30类水果图像分类数据集进行迁移学习微调训练(两个函数),得到自己的图像分类模型。
在训练过程中,记录训练集和测试集的损失函数、准确率、Precision、Recall、f1-score等评估指标,使用wandb可视化面板监控。

 

你可能感兴趣的:(pytorch,分类,迁移学习)