学习计划


Task01:构建自己的图像分类数据集

第一节配套的代码:(按照顺序学习)

1.1配置环境

tqdm库官方说明:https://github.com/tqdm/tqdm#documentation

简而言之,tqdm是 Python 进度条库,可以在 Python长循环中添加一个进度提示信息。

代码例子:

from tqdm import tqdm
from time import sleepfor char in tqdm(range(0,5)):sleep(2)'''
更简便的写法
'''
from tqdm import trange
for i in trange(0,5):sleep(2)

显示结果:

补充阅读:https://blog.csdn.net/wxd1233/article/details/118371404

opencv-python 处理图像

requests Python HTTP库

1.2图像采集

爬虫知识,略

1.3注意事项

  1. 删除无关图片(手动删)

  1. 类别均衡

  1. 多样化、代表性、一致性

  1. 删除多余文件夹、文件

文件夹:__MACOSX,.DS_Store,.ipynb_checkpoints;

文件:gif格式的、非三通道图片(三通道图可以是彩色图,可以是灰度模式的图像。三通道分别指RGB通道)

1.4数据集信息描述

图像尺寸、比例分布

#第一步,导入库
import os
import numpy as np
import pandas as pd
import cv2
from tqdm import tqdmimport matplotlib.pyplot as plt
%matplotlib inline# 第二步,指定数据集路径
dataset_path = 'fruit81_full'
os.chdir(dataset_path)
os.listdir()df = pd.DataFrame()
for fruit in tqdm(os.listdir()): # 遍历每个类别    os.chdir(fruit)for file in os.listdir(): # 遍历每张图像try:img = cv2.imread(file)df = df.append({'类别':fruit, '文件名':file, '图像宽':img.shape[1], '图像高':img.shape[0]}, ignore_index=True)except:print(os.path.join(fruit, file), '读取错误')os.chdir('../')
os.chdir('../')#查看结果
df
#可视化图像尺寸分布from scipy.stats import gaussian_kde
from matplotlib.colors import LogNormx = df['图像宽']
y = df['图像高']xy = np.vstack([x,y])
z = gaussian_kde(xy)(xy)# Sort the points by density, so that the densest points are plotted last
idx = z.argsort()
x, y, z = x[idx], y[idx], z[idx]plt.figure(figsize=(10,10))
# plt.figure(figsize=(12,12))
plt.scatter(x, y, c=z,  s=5, cmap='Spectral_r')
# plt.colorbar()
# plt.xticks([])
# plt.yticks([])plt.tick_params(labelsize=15)xy_max = max(max(df['图像宽']), max(df['图像高']))
plt.xlim(xmin=0, xmax=xy_max)
plt.ylim(ymin=0, ymax=xy_max)plt.ylabel('height', fontsize=25)
plt.xlabel('width', fontsize=25)plt.savefig('图像尺寸分布.pdf', dpi=120, bbox_inches='tight')plt.show()

1.5划分数据集

CV是图像数据集,无法用Pytorch自动划分。只能使用基础语法创建目录,移动文件。

主要思路:

将数据集统一命名为数字-->获得文件名列表full_dataset-->打乱列表random_dataset-->set train_rate = 0.8,train_dataset = random_dataset[:int(full_dataset)*0.8],test_dataset =random_dataset[int(full_dataset)*0.8:] -->移动文件。

第一步,导入所需要的库。

import os
import shutil
import random
import pandas as pd

第二步,获得full_dataset,并随即打乱

# 读取该类别的所有图像文件名
old_dir = os.path.join(dataset_path, fruit)
images_filename = os.listdir(old_dir)
random.shuffle(images_filename)

第三步,开始划分

# 划分训练集和测试集
testset_numer = int(len(images_filename) * test_frac) # 测试集图像个数
testset_images = images_filename[:testset_numer]      # 获取拟移动至 test 目录的测试集图像文件名
trainset_images = images_filename[testset_numer:]     # 获取拟移动至 train 目录的训练集图像文件名

第四步,开始移动

# 移动图像至 test 目录
for image in testset_images:old_img_path = os.path.join(dataset_path, fruit, image)         # 获取原始文件路径new_test_path = os.path.join(dataset_path, 'val', fruit, image) # 获取 test 目录的新文件路径shutil.move(old_img_path, new_test_path) # 移动文件# 移动图像至 train 目录
for image in trainset_images:old_img_path = os.path.join(dataset_path, fruit, image)           # 获取原始文件路径new_train_path = os.path.join(dataset_path, 'train', fruit, image) # 获取 train 目录的新文件路径shutil.move(old_img_path, new_train_path) # 移动文件

第五步,删除原有的文件目录

    # 删除旧文件夹assert len(os.listdir(old_dir)) == 0 # 确保旧文件夹中的所有图像都被移动走shutil.rmtree(old_dir) # 删除文件夹

第六步,df

# 工整地输出每一类别的数据个数
print('{:^18} {:^18} {:^18}'.format(fruit, len(trainset_images), len(testset_images)))# 保存到表格中
df = df.append({'class':fruit, 'trainset':len(trainset_images), 'testset':len(testset_images)}, ignore_index=True)

第七步,第二步到第六步加个遍历,因为数据集不止这一类。

for fruit in classes: # 遍历每个类别...

第八步

# 重命名数据集文件夹
shutil.move(dataset_path, dataset_name+'_split')# 数据集各类别数量统计表格,导出为 csv 文件
df['total'] = df['trainset'] + df['testset']
df.to_csv('数据量统计.csv', index=False)

Task02:预训练图像分类模型预测

task02的代码,按顺序进行学习

2.1配置环境

mmcv-full

MMCV 是一个面向计算机视觉的基础库,它支持了很多开源项目。

MMCV 提供了如下众多功能:

  • 通用的 IO 接口

  • 图像和视频处理

  • 图像和标注结果可视化

  • 常用小工具(进度条,计时器等)

  • 基于 PyTorch 的通用训练框架

  • 多种 CNN 网络结构

  • 高质量实现的常见 CUDA 算子

mmcv-full: 完整版,包含所有的特性以及丰富的开箱即用的 CUDA 算子。注意完整版本可能需要更长时间来编译。

mmcv: 精简版,不包含 CUDA 算子但包含其余所有特性和功能,类似 MMCV 1.0 之前的版本。如果你不需要使用 CUDA 算子的话,精简版可以作为一个考虑选项。

根据torch和cuda以及mmcv版本在官网中查找对应命令下载

https://mmcv.readthedocs.io/zh_CN/latest/get_started/installation.html

import torch
print(torch.__version__)
print(torch.version.cuda)
pip install mmcv-full==1.7.0 -f https://download.openmmlab.com/mmcv/dist/cu102/torch1.6/index.html

其他我在这个项目之前就安装过了,不赘述。

2.2预测单张图像

第一步,导入所需要的库

import osimport cv2import pandas as pd
import numpy as npimport torchimport matplotlib.pyplot as plt
%matplotlib inlinefrom torchvision import models
# 有 GPU 就用 GPU,没有就用 CPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

第二步,查看有多少模型

dir(models)
['AlexNet','DenseNet','GoogLeNet','GoogLeNetOutputs','Inception3','InceptionOutputs','MNASNet','MobileNetV2','ResNet','ShuffleNetV2','SqueezeNet','VGG','_GoogLeNetOutputs','_InceptionOutputs','__builtins__','__cached__','__doc__','__file__','__loader__','__name__','__package__','__path__','__spec__','_utils','alexnet','densenet','densenet121','densenet161','densenet169','densenet201','detection','googlenet','inception','inception_v3','mnasnet','mnasnet0_5','mnasnet0_75','mnasnet1_0','mnasnet1_3','mobilenet','mobilenet_v2','quantization','resnet','resnet101','resnet152','resnet18','resnet34','resnet50','resnext101_32x8d','resnext50_32x4d','segmentation','shufflenet_v2_x0_5','shufflenet_v2_x1_0','shufflenet_v2_x1_5','shufflenet_v2_x2_0','shufflenetv2','squeezenet','squeezenet1_0','squeezenet1_1','utils','vgg','vgg11','vgg11_bn','vgg13','vgg13_bn','vgg16','vgg16_bn','vgg19','vgg19_bn','video','wide_resnet101_2','wide_resnet50_2']

第三步,载入预训练图像分类模型

model = models.resnet18(pretrained=True) # model = models.resnet152(pretrained=True) 效果更好,更精准model = model.eval()
model = model.to(device)#使用GPU处理

model.eval()的作用:模型中有BatchNormalization和Dropout,在预测时使用model.eval()后会将其关闭以免影响预测结果。(反正记得用就对了)

第四步,图像预处理

from torchvision import transforms

torchvision是PyTorch的计算机视觉工具包,包含了一些与CV相关的处理。其中transforms尝用作图像预处理的,如数据中心化、标准化、缩放、裁剪等。

https://zhuanlan.zhihu.com/p/200876072这篇文章介绍了使用transform22种图片预处理方法。

# 测试集图像预处理-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])])

第五步,开始预测

首先,载入测试图像

# 用 pillow 载入
from PIL import Imageimg_path = 'test_img/ca.jpeg'
img_pil = Image.open(img_path)
input_img = test_transform(img_pil) # 预处理
input_img = input_img.unsqueeze(0).to(device)#不知道这一步是为了什么
# 执行前向预测,得到所有类别的 logit 预测分数
pred_logits = model(input_img) 
import torch.nn.functional as F
pred_softmax = F.softmax(pred_logits, dim=1) # 对 logit 分数做 softmax 运算

预测结果分析:各类别置信度柱状图

plt.figure(figsize=(8,4))x = range(1000)
y = pred_softmax.cpu().detach().numpy()[0]ax = plt.bar(x, y, alpha=0.5, width=0.3, color='yellow', edgecolor='red', lw=3)
plt.ylim([0, 1.0]) # y轴取值范围
# plt.bar_label(ax, fmt='%.2f', fontsize=15) # 置信度数值plt.xlabel('Class', fontsize=20)
plt.ylabel('Confidence', fontsize=20)
plt.tick_params(labelsize=16) # 坐标文字大小
plt.title(img_path, fontsize=25)plt.show()

取出置信度最大的前5个

n = 5
top_n = torch.topk(pred_softmax, n)
pred_ids = top_n[1].cpu().detach().numpy().squeeze()
confs = top_n[0].cpu().detach().numpy().squeeze()

values是置信度,indices是对应的id

载入csv

df = pd.read_csv('imagenet_class_index.csv')
idx_to_labels = {}
for idx, row in df.iterrows():idx_to_labels[row['ID']] = [row['wordnet'], row['class']]
# 用 opencv 载入原图
img_bgr = cv2.imread(img_path)for i in range(n):class_name = idx_to_labels[pred_ids[i]][1] # 获取类别名称confidence = confs[i] * 100 # 获取置信度text = '{:<15} {:>.4f}'.format(class_name, confidence)print(text)

将结果写在图片上后是这样:

2.3预测视频文件

看一下导入的库,知道每个库的作用就行了。

import os
import time
import shutil
import tempfile
from tqdm import tqdmimport cv2
from PIL import Imageimport numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['axes.unicode_minus']=False  # 用来正常显示负号
plt.rcParams['font.sans-serif']=['SimHei']  # 用来正常显示中文标签
import gcimport torch
import torch.nn.functional as F
from torchvision import modelsimport mmcv# 有 GPU 就用 GPU,没有就用 CPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print('device:', device)

主要思路:

  1. 利用mmcv把视频的每一帧提取出来,放在临时文件夹里

  1. 循环运行【1.2】的代码

  1. 将第二步的输出结果合并成一个视频

这一块补充知识:https://blog.csdn.net/ViatorSun/article/details/108815413

是关于opencv和PIL格式的转换

2.4预测摄像头

准备工作

import osimport numpy as np
import pandas as pdimport cv2 # opencv-python
from PIL import Image, ImageFont, ImageDraw
from tqdm import tqdm # 进度条import matplotlib.pyplot as plt
%matplotlib inlineimport torch
import torch.nn.functional as F
from torchvision import models# 有 GPU 就用 GPU,没有就用 CPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print('device:', device)# 导入中文字体,指定字号
font = ImageFont.truetype('SimHei.ttf', 32)

载入预训练图片分类模型

model = models.resnet18(pretrained=True)
model = model.eval()
model = model.to(device)

载入ImageNet 1000图像分类标签

# 载入ImageNet 1000图像分类标签
df = pd.read_csv('imagenet_class_index.csv')
idx_to_labels = {}
for idx, row in df.iterrows():idx_to_labels[row['ID']] = row['Chinese']

图片预处理

from torchvision import transforms# 测试集图像预处理-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])])

获取摄像头一帧的图片,输出img_bgr(bgr格式)

# 导入opencv-python
import cv2
import time# 获取摄像头,传入0表示获取系统默认摄像头
cap = cv2.VideoCapture(1)# 打开cap
cap.open(0)time.sleep(1)success, img_bgr = cap.read()# 关闭摄像头
cap.release()# 关闭图像窗口
cv2.destroyAllWindows()

实现array到image的转化

img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) # BGR转RGBimg_pil = Image.fromarray(img_rgb)
#plt.imshow(img_rgb)

对单个图片的预处理

input_img = test_transform(img_pil).unsqueeze(0).to(device) # 预处理
pred_logits = model(input_img) # 执行前向预测,得到所有类别的 logit 预测分数
pred_softmax = F.softmax(pred_logits, dim=1) # 对 logit 分数做 softmax 运算n = 5
top_n = torch.topk(pred_softmax, n) # 取置信度最大的 n 个结果
pred_ids = top_n[1].cpu().detach().numpy().squeeze() # 解析出类别
confs = top_n[0].cpu().detach().numpy().squeeze() # 解析出置信度
draw = ImageDraw.Draw(img_pil)
# 在图像上写字
for i in range(len(confs)):pred_class = idx_to_labels[pred_ids[i]]text = '{:<15} {:>.3f}'.format(pred_class, confs[i])# 文字坐标,中文字符串,字体,rgba颜色draw.text((50, 100 + 50 * i), text, font=font, fill=(255, 0, 0, 1))
img = np.array(img_pil) # PIL 转 array

将上面的代码块进行合并,就可以定义一个处理帧的函数。

格式转化很容易搞混。上面提到的这篇博客(https://blog.csdn.net/ViatorSun/article/details/108815413)需要收藏,以备不时之需。

# 处理帧函数
def process_frame(img):# 记录该帧开始处理的时间start_time = time.time()img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR转RGBimg_pil = Image.fromarray(img_rgb) # array 转 PILinput_img = test_transform(img_pil).unsqueeze(0).to(device) # 预处理pred_logits = model(input_img) # 执行前向预测,得到所有类别的 logit 预测分数pred_softmax = F.softmax(pred_logits, dim=1) # 对 logit 分数做 softmax 运算top_n = torch.topk(pred_softmax, 5) # 取置信度最大的 n 个结果pred_ids = top_n[1].cpu().detach().numpy().squeeze() # 解析预测类别confs = top_n[0].cpu().detach().numpy().squeeze() # 解析置信度# 使用PIL绘制中文draw = ImageDraw.Draw(img_pil) # 在图像上写字for i in range(len(confs)):pred_class = idx_to_labels[pred_ids[i]]text = '{:<15} {:>.3f}'.format(pred_class, confs[i])# 文字坐标,中文字符串,字体,bgra颜色draw.text((50, 100 + 50 * i),  text, font=font, fill=(255, 0, 0, 1))img = np.array(img_pil) # PIL 转 arrayimg = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) # RGB转BGR# 记录该帧处理完毕的时间end_time = time.time()# 计算每秒处理图像帧数FPSFPS = 1/(end_time - start_time)  # 图片,添加的文字,左上角坐标,字体,字体大小,颜色,线宽,线型img = cv2.putText(img, 'FPS  '+str(int(FPS)), (50, 80), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 255), 4, cv2.LINE_AA)return img

最后一步,调用摄像头获取每帧(模板,以后直接套用)。

# 调用摄像头逐帧实时处理模板
# 不需修改任何代码,只需修改process_frame函数即可
# 同济子豪兄 2021-7-8# 导入opencv-python
import cv2
import time# 获取摄像头,传入0表示获取系统默认摄像头
cap = cv2.VideoCapture(1)# 打开cap
cap.open(0)# 无限循环,直到break被触发
while cap.isOpened():# 获取画面success, frame = cap.read()if not success:print('Error')break## !!!处理帧函数frame = process_frame(frame)# 展示处理后的三通道图像cv2.imshow('my_window',frame)if cv2.waitKey(1) in [ord('q'),27]: # 按键盘上的q或esc退出(在英文输入法下)break# 关闭摄像头
cap.release()# 关闭图像窗口
cv2.destroyAllWindows()

2.5总结

把每个代码之间的关系滤清了,逻辑是通了但是没搞懂底层。中间遇到一些问题,比如cuda failed,还有图片格式转化搞不清。

Task03:迁移学习训练自己的图像分类模型

学习代码如下,按顺序学习:

3.1配置环境,准备数据

前面几个Task的环境就够了

import time
import osimport numpy as np
from tqdm import tqdmimport torch
import torchvision
import torch.nn as nn
import torch.nn.functional as Fimport 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)

3.2迁移学习微调训练

载入数据集,这里有个注意事项,之前没有遇到过。通过os.chdir改变当前的工作目录,避开E:中的:符号,否则会报错。

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

得到train_dataset和test_dataset,通过ImageFolder的方法,可以方便的得到两个数据集的属性,例如类别和索引(X.classes,X.class_to_idx等)

定义数据加载器Dataloader

from torch.utils.data import DataLoader
BATCH_SIZE = 16#自定义修改# 训练集的数据加载器
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

斯坦福CS231N官方笔记https://cs231n.github.io/transfer-learning/

训练配置

model = model.to(device)# 交叉熵损失函数
criterion = nn.CrossEntropyLoss() # 训练轮次 Epoch
EPOCHS = 20

完整训练

# 遍历每个 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 = 0total = 0for images, labels in tqdm(test_loader):images = images.to(device)labels = labels.to(device)outputs = model(images)_, preds = torch.max(outputs, 1)total += labels.size(0)correct += (preds == labels).sum()print('测试集上的准确率为 {:.3f} %'.format(100 * torch.true_divide(correct,total)))

机器学习中一般可以将数据集分成三类:

在这里我们只分了两类。

最后保存模型

torch.save(model, 'fruit30_pytorch_20230126.pth')

3.3进阶后面再学习!

Task04:用训练得到的模型预测

和Task02的任务很像。

这里需要导入Task03的模型

model = torch.load('XXXX.pth')
model = model.eval().to(device)

载入后预测步骤与Task02一致。这一节的内容就是拿未知的数据去确认模型的使用效果。

Task05:在测试集上评估图像分类模型精度

5.1得到用于分析的测试结果

载入数据集,使用迁移学习得到的模型测试

model = torch.load('checkpoints/fruit30_pytorch_20220814.pth')
model = model.eval().to(device)
# 数据集文件夹路径
dataset_dir = 'fruit30_split'
test_path = os.path.join(dataset_dir, 'val')
from torchvision import datasets
# 载入测试集
test_dataset = datasets.ImageFolder(test_path, test_transform)
print('测试集图像数量', len(test_dataset))
print('类别个数', len(test_dataset.classes))
print('各类别名称', test_dataset.classes)
# 载入类别名称 和 ID索引号 的映射字典
idx_to_labels = np.load('idx_to_labels.npy', allow_pickle=True).item()
# 获得类别名称
classes = list(idx_to_labels.values())
print(classes)
img_paths = [each[0] for each in test_dataset.imgs]#将路径和类别的tuple转为列表的形式df = pd.DataFrame()#用 pd 展示
df['图像路径'] = img_paths
df['标注类别ID'] = test_dataset.targets
df['标注类别名称'] = [idx_to_labels[ID] for ID in test_dataset.targets]

得到如下的表格。

n = 3df_pred = pd.DataFrame()
for idx, row in tqdm(df.iterrows()):img_path = row['图像路径']img_pil = Image.open(img_path).convert('RGB')input_img = test_transform(img_pil).unsqueeze(0).to(device) # 预处理pred_logits = model(input_img) # 执行前向预测,得到所有类别的 logit 预测分数pred_softmax = F.softmax(pred_logits, dim=1) # 对 logit 分数做 softmax 运算pred_dict = {}top_n = torch.topk(pred_softmax, n) # 取置信度最大的 n 个结果pred_ids = top_n[1].cpu().detach().numpy().squeeze() # 解析出类别# top-n 预测结果for i in range(1, n+1):pred_dict['top-{}-预测ID'.format(i)] = pred_ids[i-1]pred_dict['top-{}-预测名称'.format(i)] = idx_to_labels[pred_ids[i-1]]pred_dict['top-n预测正确'] = row['标注类别ID'] in pred_ids# 每个类别的预测置信度for idx, each in enumerate(classes):pred_dict['{}-预测置信度'.format(each)] = pred_softmax[0][idx].cpu().detach().numpy()df_pred = df_pred.append(pred_dict, ignore_index=True)

得到如下的图片。包含每一张图片各个类别的置信度以及前3的预测结果

将两表拼接

df = pd.concat([df, df_pred], axis=1)

导出表格

df.to_csv('测试集预测结果.csv', index=False)

补充学习:详细介绍ImageFolder的用法df = pd.read_csv('测试集预测结果.csv')

dataset=torchvision.datasets.ImageFolder(root, transform=None, target_transform=None, loader=<function default_loader>, is_valid_file=None)

参数详解:

root:图片存储的根目录,即各类别文件夹所在目录的上一级目录。

transform:对图片进行预处理的操作(函数),原始图片作为输入,返回一个转换后的图片。

target_transform:对图片类别进行预处理的操作,输入为 target,输出对其的转换。如果不传该参数,即对 target 不做任何转换,返回的顺序索引 0,1, 2…

loader:表示数据集加载方式,通常默认加载方式即可。

is_valid_file:获取图像文件的路径并检查该文件是否为有效文件的函数(用于检查损坏文件)

返回的dataset的属性

self.classes:用一个 list 保存类别名称

self.class_to_idx:类别对应的索引,与不做任何转换返回的 target 对应

self.imgs:保存(img-path, class) tuple的 list

5.2指标一:准确率

idx_to_labels = np.load('idx_to_labels.npy', allow_pickle=True).item()
# 获得类别名称
classes = list(idx_to_labels.values())
print(classes)

['哈密瓜', '圣女果', '山竹', '杨梅', '柚子', '柠檬', '桂圆', '梨', '椰子', '榴莲', '火龙果', '猕猴桃', '石榴', '砂糖橘', '胡萝卜', '脐橙', '芒果', '苦瓜', '苹果-红', '苹果-青', '草莓', '荔枝', '菠萝', '葡萄-白', '葡萄-红', '西瓜', '西红柿', '车厘子', '香蕉', '黄瓜']

df = pd.read_csv('测试集预测结果.csv')
sum(df['标注类别名称'] == df['top-1-预测名称']) / len(df)
sum(df['top-n预测正确']) / len(df)

5.3指标二:混淆矩阵

生成混淆矩阵

from sklearn.metrics import confusion_matrixconfusion_matrix_model = confusion_matrix(df['标注类别名称'], df['top-1-预测名称'])
confusion_matrix_model

可视化混淆矩阵

import itertools
def cnf_matrix_plotter(cm, classes, cmap=plt.cm.Blues):"""传入混淆矩阵和标签名称列表,绘制混淆矩阵"""plt.figure(figsize=(10, 10))plt.imshow(cm, interpolation='nearest', cmap=cmap)# plt.colorbar() # 色条tick_marks = np.arange(len(classes))plt.title('混淆矩阵', fontsize=30)plt.xlabel('预测类别', fontsize=25, c='r')plt.ylabel('真实类别', fontsize=25, c='r')plt.tick_params(labelsize=16) # 设置类别文字大小plt.xticks(tick_marks, classes, rotation=90) # 横轴文字旋转plt.yticks(tick_marks, classes)# 写数字threshold = cm.max() / 2.for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):plt.text(j, i, cm[i, j],horizontalalignment="center",color="white" if cm[i, j] > threshold else "black",fontsize=12)plt.tight_layout()plt.savefig('混淆矩阵.pdf', dpi=300) # 保存图像plt.show()cnf_matrix_plotter(confusion_matrix_model, classes, cmap='Blues')

找出错误分类的图片,分析其原因。df表中有路径。

可视化图像

for idx, row in wrong_df.iterrows():img_path = row['图像路径']img_bgr = cv2.imread(img_path)img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)plt.imshow(img_rgb)title_str = img_path + '\nTrue:' + row['标注类别名称'] + ' Pred:' + row['top-1-预测名称']plt.title(title_str)plt.show()

其他指标:

from sklearn.metrics import classification_report
print(classification_report(df['标注类别名称'], df['top-1-预测名称'], target_names=classes))

True Positive(TP):真正类。样本的真实类别是正类,并且模型识别的结果也是正类。

False Negative(FN):假负类。样本的真实类别是正类,但是模型将其识别为负类。

False Positive(FP):假正类。样本的真实类别是负类,但是模型将其识别为正类。

True Negative(TN):真负类。样本的真实类别是负类,并且模型将其识别为负类。

从混淆矩阵当中,可以得到更高级的分类指标:Accuracy(精确率),Precision(正确率或者准确率),Recall(召回率)

5.3.1 精确率(Accuracy)

精确率是最常用的分类性能指标。可以用来表示模型的精度,即模型识别正确的个数/样本的总个数。一般情况下,模型的精度越高,说明模型的效果越好。

Accuracy = (TP+TN)/(TP+FN+FP+TN)

5.3.2 正确率或者准确率(Precision)

又称为查准率,表示在模型识别为正类的样本中,真正为正类的样本所占的比例。一般情况下,查准率越高,说明模型的效果越好。

Precision = TP/(TP+FP)

5.3.3 召回率(Recall)

又称为查全率,召回率表现出在实际正样本中,分类器能预测出多少。

Recall(召回率) = Sensitivity(敏感指标,True Positive Rate,TPR)= 查全率

表示的是,模型正确识别出为正类的样本的数量占总的正类样本数量的比值。一般情况下,Recall越高,说明有更多的正类样本被模型预测正确,模型的效果越好。

Recall = TP/(TP+FN)

查准率和查全率是一对矛盾的指标。一般来说,查准率高时,查全率往往偏低;二查全率高时,查准率往往偏低。

5.3.4 Fβ-SCORE

β=1

F1_Score:数学定义为 F1分数(F1-Score),又称为平衡 F分数(Balanced Score),它被定义为正确率和召回率的调和平均数。F1-Score的值是从0到1的,1是最好,0是最差。

5.3.5macro avg和weighted avg

accuracy_list = []
for fruit in tqdm(classes):df_temp = df[df['标注类别名称']==fruit]accuracy = sum(df_temp['标注类别名称'] == df_temp['top-1-预测名称']) / len(df_temp)accuracy_list.append(accuracy)# 计算 宏平均准确率 和 加权平均准确率
acc_macro = np.mean(accuracy_list)
acc_weighted = sum(accuracy_list * df_report.iloc[:-2]['support'] / len(df))accuracy_list.append(acc_macro)
accuracy_list.append(acc_weighted)df_report['accuracy'] = accuracy_listdf_report.to_csv('各类别准确率评估指标.csv', index_label='类别')

5.4指标三:PR曲线和AP值

5.4.1PR

    以精确率为y轴,以召回率为x轴,我们就得到了PR曲线。仍然从精确率和召回率的定义可以理解,精确率越高,召回率越高,我们的模型和算法就越高效。也就是画出来的PR曲线越靠近右上越好。如图所示。

当Precision=Recall时,取得平衡点(BEP),平衡点越外侧时,此PR曲线对应的学习器效果更好,拥有相对更大的Precision与Recall,所以PR曲线更接近于外侧(或者将另一曲线围住)的学习器效果也是更好的,用上面的图来说,就是曲线C的效果最差,B较好,A是最佳的,A曲线与B曲线有交叉,则可计算重叠面积来比较,明显A的效果比B好。

5.4.2AP

分类任务的AP就是P—R曲线下的面积,我们需要做的就是根据不同的置信度阈值 (p_threshold),计算出这模型得到的对某个类别的(R,P),然后作出P—R曲线,并求解面积,就能得到分类模型对该类别的AP。

5.4.3代码实现

绘制某一类

specific_class = '荔枝'
# 二分类标注
y_test = (df['标注类别名称'] == specific_class)
# 二分类预测置信度
y_score = df['荔枝-预测置信度']from sklearn.metrics import precision_recall_curve
from sklearn.metrics import average_precision_score
precision, recall, thresholds = precision_recall_curve(y_test, y_score)
AP = average_precision_score(y_test, y_score, average='weighted')
plt.figure(figsize=(12, 8))
# 绘制 PR 曲线
plt.plot(recall, precision, linewidth=5, label=specific_class)# 随机二分类模型
# 阈值小,所有样本都被预测为正类,recall为1,precision为正样本百分比
# 阈值大,所有样本都被预测为负类,recall为0,precision波动较大
plt.plot([0, 0], [0, 1], ls="--", c='.3', linewidth=3, label='随机模型')
plt.plot([0, 1], [0.5, sum(y_test==1)/len(df)], ls="--", c='.3', linewidth=3)plt.xlim([-0.01, 1.0])
plt.ylim([0.0, 1.01])
plt.rcParams['font.size'] = 22
plt.title('{} PR曲线  AP:{:.3f}'.format(specific_class, AP))
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.legend()
plt.grid(True)
plt.savefig('{}-PR曲线.pdf'.format(specific_class), dpi=120, bbox_inches='tight')
plt.show()

绘制所有类

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_argplt.figure(figsize=(14, 10))
plt.xlim([-0.01, 1.0])
plt.ylim([0.0, 1.01])
# plt.plot([0, 1], [0, 1],ls="--", c='.3', linewidth=3, label='随机模型')
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.rcParams['font.size'] = 22
plt.grid(True)ap_list = []
for each_class in classes:y_test = list((df['标注类别名称'] == each_class))y_score = list(df['{}-预测置信度'.format(each_class)])precision, recall, thresholds = precision_recall_curve(y_test, y_score)AP = average_precision_score(y_test, y_score, average='weighted')plt.plot(recall, precision, **get_line_arg(), label=each_class)plt.legend()ap_list.append(AP)plt.legend(loc='best', fontsize=12)
plt.savefig('各类别PR曲线.pdf'.format(specific_class), dpi=120, bbox_inches='tight')
plt.show()
# 计算 AUC值 的 宏平均 和 加权平均
macro_avg_auc = np.mean(ap_list)
weighted_avg_auc = sum(ap_list * df_report.iloc[:-2]['support'] / len(df))
ap_list.append(macro_avg_auc)
ap_list.append(weighted_avg_auc)

最后将值加入df表格中

5.5指标四:ROC曲线和AUC值

5.5.1ROC

横轴FPR:1-TNR,1-Specificity,FPR越大,预测正类中实际负类越多。

纵轴TPR:Sensitivity(正类覆盖率),TPR越大,预测正类中实际正类越多。

理想目标:TPR=1,FPR=0,即图中(0,1)点,故ROC曲线越靠拢(0,1)点,越偏离45度对角线越好,Sensitivity、Specificity越大效果越好。

5.5.2AUC

AUC(Area under Curve):Roc曲线下的面积,介于0.1和1之间。Auc作为数值可以直观的评价分类器的好坏,值越大越好。

首先AUC值是一个概率值,当你随机挑选一个正样本以及负样本,当前的分类算法根据计算得到的Score值将这个正样本排在负样本前面的概率就是AUC值,AUC值越大,当前分类算法越有可能将正样本排在负样本前面,从而能够更好地分类。

5.5.3代码实现

specific_class = '荔枝'
# 二分类标注
y_test = (df['标注类别名称'] == specific_class)
# 二分类置信度
y_score = df['荔枝-预测置信度']
from sklearn.metrics import roc_curve, auc
fpr, tpr, threshold = roc_curve(y_test, y_score)
plt.figure(figsize=(12, 8))
plt.plot(fpr, tpr, linewidth=5, label=specific_class)
plt.plot([0, 1], [0, 1],ls="--", c='.3', linewidth=3, label='随机模型')
plt.xlim([-0.01, 1.0])
plt.ylim([0.0, 1.01])
plt.rcParams['font.size'] = 22
plt.title('{} ROC曲线  AUC:{:.3f}'.format(specific_class, auc(fpr, tpr)))
plt.xlabel('False Positive Rate (1 - Specificity)')
plt.ylabel('True Positive Rate (Sensitivity)')
plt.legend()
plt.grid(True)plt.savefig('{}-ROC曲线.pdf'.format(specific_class), dpi=120, bbox_inches='tight')
plt.show()# yticks = ax.yaxis.get_major_ticks()
# yticks[0].label1.set_visible(False)
auc(fpr, tpr)

绘制所有类别的ROC曲线

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_argplt.figure(figsize=(14, 10))
plt.xlim([-0.01, 1.0])
plt.ylim([0.0, 1.01])
plt.plot([0, 1], [0, 1],ls="--", c='.3', linewidth=3, label='随机模型')
plt.xlabel('False Positive Rate (1 - Specificity)')
plt.ylabel('True Positive Rate (Sensitivity)')
plt.rcParams['font.size'] = 22
plt.grid(True)auc_list = []
for each_class in classes:y_test = list((df['标注类别名称'] == each_class))y_score = list(df['{}-预测置信度'.format(each_class)])fpr, tpr, threshold = roc_curve(y_test, y_score)plt.plot(fpr, tpr, **get_line_arg(), label=each_class)plt.legend()auc_list.append(auc(fpr, tpr))plt.legend(loc='best', fontsize=12)
plt.savefig('各类别ROC曲线.pdf'.format(specific_class), dpi=120, bbox_inches='tight')
plt.show()
# 计算 AUC值 的 宏平均 和 加权平均
macro_avg_auc = np.mean(auc_list)
weighted_avg_auc = sum(auc_list * df_report.iloc[:-2]['support'] / len(df))auc_list.append(macro_avg_auc)
auc_list.append(weighted_avg_auc)

最后将值加入df表格中

5.6计算图像语义特征

抽取Pytorch训练得到的图像分类模型中间层的输出特征,作为输入图像的语义特征。计算测试集所有图像的语义特征,使用t-SNE和UMAP两种降维方法降维至二维和三维,可视化。分析不同类别的语义距离、异常数据、细粒度分类、高维数据结构。同济子豪兄:https://space.bilibili.com/1900783

步骤:

  1. 导入训练好的模型

  1. 载入测试集图像分类结果

  1. 抽取模型中间层输出结果作为语义特征

  1. 计算每张图像的语义特征

  1. 保存

Task06:可解释性分析、显著性分析

6.1如何理解可解释性

6.1.1为什么要学习可解释机器学习?

  • 研究AI的脑回路,就是研究AI的本质

  • 可解释分析是机器学习和数据挖掘的通用研究方法

  • 和所有AI方向交叉融合:数据挖掘、计算机视觉、自然语言处理、强化学习、知识图谱、联邦学习

  • 包括但不限于:大模型、弱监督、缺陷异常检测、细粒度分类、决策AI和强化学习、图神经网络、AI纠偏、AI4Science、Machine Teaching、对抗样本、可信计算、联邦学习

6.1.2为什么要模型可解释性?

  • 模型的决策对场景影响越大,模型对它行为的解释就越重要

  • 可解释性便于人类理解模型的决策,模型必须“说服” 我们,这样它们才能达到预期的⽬标

  • 我们需要模型可解释性来找出问题的隐患,如员工离职预测,我们需要通过可解释性来找出离职背后的真正原因,以采取相应的措施

6.1.3个人理解

关于可解释性在机器学习的理论部分掌握不是很多。我大致的理解就是,可解释性就是让模型的运作原理展示给人类。模型在分析图片的时候,通过可解释性告诉我们它着重分析的是哪块地方。知己知彼,方能百战不殆!

6.2使用工具包torch-cam和预训练模型制作图片的热力图

CAM,class activation map来自于论文《Learning Deep Features for Discriminative Localization》类激活热力图

补充阅读:https://www.jianshu.com/p/fd2f09dc3cc9

安装:

!git clone https://github.com/frgfm/torch-cam.git
!pip install -e torch-cam/.

两种调用torch cam的方法,方法一,命令行下:

!python torch-cam/scripts/cam_example.py --help

使用方法:

usage: cam_example.py [-h] [--arch ARCH] [--img IMG] [--class-idx CLASS_IDX]

[--device DEVICE] [--savefig SAVEFIG] [--method METHOD]

[--target TARGET] [--alpha ALPHA] [--rows ROWS]

[--noblock]

Saliency Map comparison

optional arguments:

-h, --help show this help message and exit

--arch ARCH Name of the architecture (default: resnet18)

--img IMG The image to extract CAM from (default:

https://www.woopets.fr/assets/races/000/066/big-

portrait/border-collie.jpg)

--class-idx CLASS_IDX

Index of the class to inspect (default: 232)

--device DEVICE Default device to perform computation on (default:

None)

--savefig SAVEFIG Path to save figure (default: None)

--method METHOD CAM method to use (default: None)

--target TARGET the target layer (default: None)

--alpha ALPHA Transparency of the heatmap (default: 0.5)

--rows ROWS Number of rows for the layout (default: 1)

--noblock Disables blocking visualization (default: False)

# 类别-边牧犬
!python torch-cam/scripts/cam_example.py \--img test_img/border-collie.jpg \--savefig output/B1_border_collie.jpg \--arch resnet18 \--class-idx 232 \--rows 2

代码解释:第一行调用torchcam,第二行是需要识别的照片,第三行是结果保存位置,第四行使用resnet18模型,第五行边牧犬的id,第六行代表布局,即得到的结果有几行热力图。

Image.open('output/B1_border_collie.jpg')

方法二:

导入工具包

import matplotlib.pyplot as plt
%matplotlib inlinefrom PIL import Imageimport torch
# 有 GPU 就用 GPU,没有就用 CPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print('device', device)from PIL import ImageFont, ImageDraw
# 导入中文字体,指定字体大小
font = ImageFont.truetype('SimHei.ttf', 50)

导入ImageNet预训练模型

from torchvision.models import resnet18
model = resnet18(pretrained=True).eval().to(device)

导入可解释性分析方法

from torchcam.methods import SmoothGradCAMpp
# CAM GradCAM GradCAMpp ISCAM LayerCAM SSCAM ScoreCAM SmoothGradCAMpp XGradCAMcam_extractor = SmoothGradCAMpp(model)

图片预处理

from torchvision import transforms
# 测试集图像预处理-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])])

使用单张图像预测,得到预测id

img_path = 'test_img/pangxie.jpg'
img_pil = Image.open(img_path)
input_tensor = test_transform(img_pil).unsqueeze(0).to(device) # 预处理
pred_logits = model(input_tensor)
pred_top1 = torch.topk(pred_logits, 1)
pred_id = pred_top1[1].detach().cpu().numpy().squeeze().item()

生成可解释性分析热力图

activation_map = cam_extractor(pred_id, pred_logits)
activation_map = activation_map[0][0].detach().cpu().numpy()
plt.imshow(activation_map)
plt.show()
from torchcam.utils import overlay_maskresult = overlay_mask(img_pil, Image.fromarray(activation_map), alpha=0.7)

将以上代码整合,并设置中文标签显示:

import pandas as pd
df = pd.read_csv('imagenet_class_index.csv')
idx_to_labels = {}
idx_to_labels_cn = {}
for idx, row in df.iterrows():idx_to_labels[row['ID']] = row['class']idx_to_labels_cn[row['ID']] = row['Chinese']img_path = 'test_img/pangxie.jpg'# 可视化热力图的类别ID,如果为 None,则为置信度最高的预测类别ID
show_class_id = None
# show_class_id = None# 是否显示中文类别
Chinese = True
# Chinese = False# 前向预测
img_pil = Image.open(img_path)
input_tensor = test_transform(img_pil).unsqueeze(0).to(device) # 预处理
pred_logits = model(input_tensor)
pred_top1 = torch.topk(pred_logits, 1)
pred_id = pred_top1[1].detach().cpu().numpy().squeeze().item()# 可视化热力图的类别ID,如果不指定,则为置信度最高的预测类别ID
if show_class_id:show_id = show_class_id
else:show_id = pred_idshow_class_id = pred_id# 生成可解释性分析热力图
activation_map = cam_extractor(show_id, pred_logits)
activation_map = activation_map[0][0].detach().cpu().numpy()
result = overlay_mask(img_pil, Image.fromarray(activation_map), alpha=0.7)# 在图像上写字
draw = ImageDraw.Draw(result)if Chinese:# 在图像上写中文text_pred = 'Pred Class: {}'.format(idx_to_labels_cn[pred_id])text_show = 'Show Class: {}'.format(idx_to_labels_cn[show_class_id])
else:# 在图像上写英文text_pred = 'Pred Class: {}'.format(idx_to_labels[pred_id])text_show = 'Show Class: {}'.format(idx_to_labels[show_class_id])
# 文字坐标,中文字符串,字体,rgba颜色
draw.text((50, 100), text_pred, font=font, fill=(255, 0, 0, 1))
draw.text((50, 200), text_show, font=font, fill=(255, 0, 0, 1))

torchcam methods里的其余方法:

CAM GradCAM GradCAMpp ISCAM LayerCAM SSCAM ScoreCAM SmoothGradCAMpp XGradCAM(试了几个,发现没有区别!)

6.3使用自己的模型制作视频的热力图

import matplotlib.pyplot as plt
%matplotlib inlinematplotlib.rc("font",family='SimHei') # 中文字体import matplotlib.pyplot as plt
%matplotlib inlineimport numpy as np
from PIL import Imageimport torch
# 有 GPU 就用 GPU,没有就用 CPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')model = torch.load('checkpoint/fruit30_pytorch_20220814.pth')
model = model.eval().to(device)from torchcam.methods import GradCAMpp
# CAM GradCAM GradCAMpp ISCAM LayerCAM SSCAM ScoreCAM SmoothGradCAMpp XGradCAMcam_extractor = GradCAMpp(model)from torchvision import transforms
# 测试集图像预处理-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])])#进行图像预测
img_path = 'test_img/test_fruits.jpg'
img_pil = Image.open(img_path)
input_tensor = test_transform(img_pil).unsqueeze(0).to(device) # 预处理
pred_logits = model(input_tensor)
pred_id = torch.topk(pred_logits, 1)[1].detach().cpu().numpy().squeeze().item()#生成热力图
activation_map = cam_extractor(pred_id, pred_logits)
activation_map = activation_map[0][0].detach().cpu().numpy()
plt.imshow(activation_map)
plt.show()from torchcam.utils import overlay_maskresult = overlay_mask(img_pil, Image.fromarray(activation_map), alpha=0.7)#写入标签
idx_to_labels = np.load('idx_to_labels.npy', allow_pickle=True).item()
labels_to_idx = np.load('labels_to_idx.npy', allow_pickle=True).item()# 可视化热力图的类别,如果不指定,则为置信度最高的预测类别
show_class = '猕猴桃'
if show_class:class_id = labels_to_idx[show_class]show_id = class_id
else:show_id = pred_idplt.imshow(result)
plt.axis('off')plt.title('{}\nPred:{} Show:{}'.format(img_path, idx_to_labels[pred_id], show_class))
plt.show()

Task07:部署模型

7.1概念解释

Open Neural Network Exchange(ONNX,开放神经网络交换)格式,是一个用于表示深度学习模型的标准,可使模型在不同框架之间进行转移。

ONNX是一种针对机器学习所设计的开放式的文件格式,用于存储训练好的模型。它使得不同的人工智能框架(如Pytorch, MXNet)可以采用相同格式存储模型数据并交互。 ONNX的规范及代码主要由微软,亚马逊 ,Facebook 和 IBM 等公司共同开发,以开放源代码的方式托管在Github上。

目前官方支持加载ONNX模型并进行推理的深度学习框架有: Caffe2, PyTorch, MXNet,ML.NET,TensorRT 和 Microsoft CNTK,并且 TensorFlow 也非官方的支持ONNX。

假设一个场景:现在某组织因为主要开发用TensorFlow为基础的框架,现在有一个深度算法,需要将其部署在移动设备上,以观测变现。传统地我们需要用caffe2重新将模型写好,然后再训练参数;试想下这将是一个多么耗时耗力的过程。

此时,ONNX便应运而生,Caffe2,PyTorch,Microsoft Cognitive Toolkit,Apache MXNet等主流框架都对ONNX有着不同程度的支持。这就便于了我们的算法及模型在不同的框架之间的迁移。

• Pytorch -> ONNX -> TensorRT
• Pytorch -> ONNX -> TVM
• TF –> ONNX –> ncnn

(参考资料:https://blog.csdn.net/qq_29258377/article/details/122907981,

https://blog.csdn.net/m0_46579823/article/details/127889230)

7.2代码实现

安装所需要的库

pip install onnx
pip install onnxruntime

导入库

import torch
from torchvision import models# 有 GPU 就用 GPU,没有就用 CPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

7.2.1pytorch模型转为onnx模型

参数说明:
model(torch.nn.Module) - 要导出的模型
args(参数集)- 模型的输入,满足输入层的shape正确即可
path - 类文件对象(必须实现文件描述符的返回)或包含文件名的字符串。二进制 Protobuf 将写入此文件。输出的onnx模型的位置。
verbose (bool, default False) - 如果指定,将输出导出跟踪的调试描述。
opset_version - 默认是9。值必须等于_onnx_main_opset或在_onnx_stable_opsets之内。具体可在torch/onnx/symbolic_helper.py中找到。opset的版本,低版本不支持upsample等操作
input_names(字符串列表,默认为空列表)- 按顺序为图中的输入节点分配名称。
output_names(字符串列表,默认为空列表)- 按顺序为图中的输出节点分配名称。
补充阅读 https://blog.csdn.net/jiong9412/article/details/125383053

https://learn.microsoft.com/zh-cn/windows/ai/windows-ml/tutorials/pytorch-analysis-convert-model这个链接里是这样解释的:

#Function to Convert to ONNX
def convert(): # set the model to inference mode model.eval() # Let's create a dummy input tensor  dummy_input = torch.randn(1, 3, 32, 32, requires_grad=True)  # Export the model   torch.onnx.export(model,         # model being run dummy_input,       # model input (or a tuple for multiple inputs) "Network.onnx",       # where to save the model  export_params=True,  # store the trained parameter weights inside the model file opset_version=11,    # the ONNX version to export the model to do_constant_folding=True,  # whether to execute constant folding for optimization input_names = ['input'],   # the model's input names output_names = ['output'], # the model's output names dynamic_axes={'input' : {0 : 'batch_size'},    # variable length axes 'output' : {0 : 'batch_size'}}) print(" ") print('Model has been converted to ONNX')

导入自己训练好的代码

model = torch.load('checkpoints/fruit30_pytorch_20220814.pth')
model = model.eval().to(device)

pytorch模型转为onnx模型

x = torch.randn(1, 3, 256, 256).to(device)#定义一个输入with torch.no_grad():torch.onnx.export(model,                   # 要转换的模型x,                       # 模型的任意一组输入'fruit30_resnet18.onnx', # 导出的 ONNX 文件名opset_version=11,        # ONNX 算子集版本input_names=['input'],   # 输入 Tensor 的名称(自己起名字)output_names=['output']  # 输出 Tensor 的名称(自己起名字)) 

验证转化成功

import onnx# 读取 ONNX 模型
onnx_model = onnx.load('fruit30_resnet18.onnx')# 检查模型格式是否正确
onnx.checker.check_model(onnx_model)print('无报错,onnx模型载入成功')

使用https://netron.app对ONNX可视化

7.2.2推理引擎部署模型

应用场景:

以下代码在需要部署的硬件上运行

只需把onnx模型文件发到部署硬件上,并安装 ONNX Runtime 环境,用几行代码就可以运行模型了。

import onnxruntime
import numpy as np
import torch

载入 onnx 模型,获取 ONNX Runtime 推理器

ort_session = onnxruntime.InferenceSession('fruit30_resnet18.onnx')
#图像预处理
from torchvision import transforms# 测试集图像预处理-RCTN:缩放裁剪、转 Tensor、归一化
test_transform = transforms.Compose([transforms.Resize(256),transforms.CenterCrop(256),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])img_path = 'test_img/watermelon1.jpg'
# 用 pillow 载入
from PIL import Image
img_pil = Image.open(img_path)input_img = test_transform(img_pil)
input_tensor = input_img.unsqueeze(0).numpy()# ONNX Runtime 输入
ort_inputs = {'input': input_tensor}# ONNX Runtime 输出
pred_logits = ort_session.run(['output'], ort_inputs)[0]
pred_logits = torch.tensor(pred_logits)import torch.nn.functional as F
pred_softmax = F.softmax(pred_logits, dim=1) # 对 logit 分数做 softmax 运算idx_to_labels = np.load('idx_to_labels.npy', allow_pickle=True).item()import matplotlib
matplotlib.rc("font",family='SimHei') # 中文字体import matplotlib.pyplot as plt
%matplotlib inlineplt.figure(figsize=(22, 10))x = idx_to_labels.values()
y = pred_softmax.cpu().detach().numpy()[0] * 100
width = 0.45 # 柱状图宽度ax = plt.bar(x, y, width)plt.bar_label(ax, fmt='%.2f', fontsize=15) # 置信度数值
plt.tick_params(labelsize=20) # 设置坐标文字大小plt.title(img_path, fontsize=30)
plt.xticks(rotation=45) # 横轴文字旋转
plt.xlabel('类别', fontsize=20)
plt.ylabel('置信度', fontsize=20)
plt.show()

参考资料和阅读资料

(按照引用顺序排序)

主要学习up主同济子豪兄https://space.bilibili.com/1900783

pytorch中主要编码格式https://blog.csdn.net/ViatorSun/article/details/108815413

5.4和5.5参考https://blog.csdn.net/xys430381_1/article/details/90770520

6.1参考https://blog.csdn.net/weixin_42137700/article/details/107627737

6.1参考:https://www.jianshu.com/p/fd2f09dc3cc9

7.1https://blog.csdn.net/qq_29258377/article/details/122907981,https://blog.csdn.net/m0_46579823/article/details/127889230

7.2https://blog.csdn.net/jiong9412/article/details/125383053

Pytorch图像分类实战笔记Task01-Task07|Datawhale组队学习相关推荐

  1. 七、图像分类模型的部署(Datawhale组队学习)

    文章目录 前言 ONNX简介 应用场景 部署ImageNet预训练图像分类模型 导出ONNX模型 推理引擎ONNX Runtime部署-预测单张图像 前期准备 ONNX Runtime预测 推理引擎O ...

  2. 五、在测试集上评估图像分类算法精度(Datawhale组队学习)

    文章目录 配置环境 准备图像分类数据集和模型文件 测试集图像分类预测结果 表格A-测试集图像路径及标注 表格B-测试集每张图像的图像分类预测结果,以及各类别置信度 可视化测试集中被误判的图像 测试集总 ...

  3. Datawhale组队学习-金融时序数据挖掘实践-Task01数据探索与分析

    Datawhale组队学习-金融时序数据挖掘实践-Task01数据探索与分析   在二手车交易价格预测之后,本菜鸟又加入了金融时序数据挖掘实践的学习.两个项目都是结构化数据,都着重于对数据本身的探索. ...

  4. 第8期Datawhale组队学习计划

    第8期Datawhale组队学习计划马上就要开始啦 这次共组织15个组队学习,涵盖了AI领域从理论知识到动手实践的内容 按照下面给出的最完备学习路线分类,难度系数分为低.中.高三档,可以按照需要参加 ...

  5. 第7期 Datawhale 组队学习计划

    马上就要开始啦 这次共组织15个组队学习 涵盖了AI领域从理论知识到动手实践的内容 按照下面给出的最完备学习路线分类 难度系数分为低.中.高三档 可以按照需要参加 - 学习路线 - 基础知识 01 统 ...

  6. Datawhale组队学习周报(第032周)

    希望开设的开源内容 目前Datawhale的开源内容分为两种:第一种是已经囊括在我们的学习路线图内的Datawhale精品课,第二种是暂未囊括在我们的学习路线图内的Datawhale打磨课.我们根据您 ...

  7. Datawhale组队学习周报(第021周)

    本文总结了本周(07月05日~07月11日)Datawhale组队学习的运行情况,我们一直秉承"与学习者一起成长的理念",希望这个活动能够让更多的学习者受益. 第 25 期组队学习 ...

  8. Datawhale组队学习周报(第022周)

    文章目录 已经结营的开源内容 即将结营的开源内容 正在进行的开源内容 八月排期的开源内容 每周号外 Whalepaper 招新公告 本文总结了本周(07月12日~07月18日)Datawhale组队学 ...

  9. Datawhale组队学习周报(第047周)

    本周报总结了从 2021年01月03日至2022年01月09日,Datawhale组队学习的运行情况,我们一直秉承"与学习者一起成长的理念",希望这个活动能够让更多的学习者受益. ...

最新文章

  1. HTTP 错误 500.21 - Internal Server Error PageHandlerFactory-Integrated
  2. mysql的存储过程基本使用
  3. eclipse 工程目录下 删除不掉目录或者文件的解决办法
  4. MMM for MySQL FAQ
  5. 转 java中static{}语句块详解
  6. 《机器学习实战》完整读书笔记
  7. [iOS] 使用xib做为应用程序入口 with Code
  8. Linux运维日常命令
  9. 常用心理学实验刺激呈现软件介绍
  10. HTTP1.0、HTTP2.0、HTTP 3.0及HTTPS简要介绍
  11. Redis生成自增流水号,每日清零
  12. 上海始终未出现真正“富人区”
  13. Linux用户层和内核文件读写
  14. android地图定位到海洋,如何利用卫星导航技术进行高精度海洋测绘定位
  15. Blos查看计算机硬盘,bios查看硬盘损坏
  16. artifactIdsolace-spring-boot-starter/artifactId
  17. DebugView工具使用方法
  18. JAVA中如何精确取到时间间隔
  19. 基于Multisim的声光控制路灯电路设计
  20. QQ宠物客户端2005III新年新版新亮相(转)

热门文章

  1. 手把手教你如何获取全网可访问的所有网站网址和网站信息
  2. Java爬虫Jsoup简易使用
  3. 30.6 撤销用户权限
  4. linux 批处理教程,最全的批处理入门教程,菜鸟必看
  5. 并非从0开始的c++ day14
  6. java中 byte 取值范围_java中为什么byte的取值范围是-128到+127
  7. android sdk库 离线安装,android sdk 离线安装
  8. C/C++注册表【3】子键枚举,备份,恢复
  9. 51单片机过时了?为什么还有人学51呢?陈老师为你解答
  10. 2022 年 Java 面试总结,200 多页真题笔记和详解(含核心考点及 6 家大厂)