目录
前言
不知道这个与可解释机器学习的关联有多大,因为在学习可解释性机器学习也学过这部分内容:CAM热力图系列算法、Deep Feature Factorization、Captum工具包、shap工具包、lime工具包。
感觉确实之前的课程讲的细致一些就可解释性上,不过也可能没啥可比性,之前可解释性机器学习是基于论文来实战
,这个课程是基于之前的实战过程来实战
。
CAM热力图系列算法
这次换个形式学,不想搞代码了,和之前的代码内容差不多
【A】安装配置环境
- 直接运行代码
- 下载安装Pytorch
- 下载安装mmcv-full
- 下载中文字体
- 下载ImageNet-1000类别信息
- 创建目录
安装torchcam
重启kernel
- 设置matplotlib中文字体(可测试)
强调部分对比于[Pytorch图像分类全流程实战]Task04:新图片、新视频预测
迁徙学习得到的30类水果图像分类模型
- 存放测试图片
- 存放训练得到的模型权重
- 下载阳历模型文件
- 下载 类别名称 和 ID索引号 的映射字典
- 下载测试图像文件 至 test_img 文件夹
- 下载测试视频文件 至 test_img 文件夹
而在[Pytorch图像分类全流程实战]Task03:迁移学习微调
中,强调部分为
- 存放结果文件
- 存放训练得到的模型权重
- 存放生成的图表
【B】 torchcam命令行
通过命令行方式使用torchcam算法库,对图像进行各种基于CAM的可解释性分析。
这里最重要的是掌握命令行操作
人工notebook,傻瓜操作。
-
导入工具包
import os import pandas as pd from PIL import Image
-
命令行基本用法
!python torch-cam/scripts/cam_example.py --help
-
ImageNet预训练图像分类模型
# ImageNet1000类别名称与ID号 df = pd.read_csv('imagenet_class_index.csv')
-
图中只有一个类别
# 类别-边牧犬 !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 Image.open('output/B1_border_collie.jpg')
-
图中有多个类别
# 类别-虎斑猫 !python torch-cam/scripts/cam_example.py \ --img test_img/cat_dog.jpg \ --savefig output/B2_cat_dog.jpg \ --arch resnet18 \ --class-idx 282 \ --rows 2 Image.open('output/B2_cat_dog.jpg')
# 类别-边牧犬 !python torch-cam/scripts/cam_example.py \ --img test_img/cat_dog.jpg \ --savefig output/B3_cat_dog.jpg \ --arch resnet18 \ --class-idx 232 \ --rows 2 Image.open('output/B3_cat_dog.jpg')
【C1】Pytorch预训练ImageNet图像分类-单张图像
通过Python API方式,使用torchcam算法库,对Pytorch预训练ImageNet-1000图像分类模型进行基于CAM的可解释性分析。
-
导入工具包
-
导入pillow中文字体
-
导入lmageNet预训练模型
-
导入可解释性分析方法
-
预处理
-
运行图像分类预测
img_path = 'test_img/cat_dog.jpg' img_pil = Image.open(img_path) input_tensor = test_transform(img_pil).unsqueeze(0).to(device) # 预处理 input_tensor.shape #torch.Size([1, 3, 224, 224]) pred_logits = model(input_tensor) pred_top1 = torch.topk(pred_logits, 1) pred_id = pred_top1[1].detach().cpu().numpy().squeeze().item() pred_id #282
-
生成可解释分析热力图
activation_map = cam_extractor(pred_id, pred_logits) activation_map = activation_map[0][0].detach().cpu().numpy() activation_map.shape #(7, 7)
-
可视化
plt.imshow(activation_map) plt.show()
from torchcam.utils import overlay_mask result = overlay_mask(img_pil, Image.fromarray(activation_map), alpha=0.7) print(result)
-
整理代码:设置类别、中文类别显示
需要载入lmageNet 1000图像分类标签:lmageNet 1000类别中文释义
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/cat_dog.jpg'
# 可视化热力图的类别ID,如果为 None,则为置信度最高的预测类别ID
show_class_id = 231
# 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_id
show_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))
print(result)
【C2】 Pytorch预训练lmageNet图像分类-视频文件
-
导入工具包
-
导入pillow中文字体
-
导入ImageNet预训练模型
-
载入ImageNet 1000图像分类标签
-
导入可解释性分析方法
-
预处理
-
图像分类预测函数
def pred_single_frame(img, show_class_id=None, Chinese=True): ''' 输入摄像头画面bgr-array和用于绘制热力图的类别ID,输出写字的热力图PIL-Image 如果不指定类别ID,则为置信度最高的预测类别ID ''' img_bgr = img img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR 转 RGB img_pil = Image.fromarray(img_rgb) # array 转 pil input_tensor = test_transform(img_pil).unsqueeze(0).to(device) # 预处理 pred_logits = model(input_tensor) # 执行前向预测,得到所有类别的 logit 预测分数 pred_top1 = torch.topk(pred_logits, 1) pred_id = pred_top1[1].detach().cpu().numpy().squeeze().item() # 可视化热力图的类别ID,如果为 None,则为置信度最高的预测类别ID if show_class_id: show_id = show_class_id else: show_id = pred_id show_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)) return result
-
视频预测
–输入输出视频路径
input_video = 'test_img/room_video.mp4'
–
创建临时文件夹
# 创建临时文件夹,存放每帧结果 temp_out_dir = time.strftime('%Y%m%d%H%M%S') os.mkdir(temp_out_dir) print('创建文件夹 {} 用于存放每帧预测结果'.format(temp_out_dir))
–
视频逐帧预测
# 读入待预测视频 imgs = mmcv.VideoReader(input_video) prog_bar = mmcv.ProgressBar(len(imgs)) # 对视频逐帧处理 for frame_id, img in enumerate(imgs): ## 处理单帧画面 img = pred_single_frame(img, show_class_id=None) # 将处理后的该帧画面图像文件,保存至 /tmp 目录下 img.save(f'{temp_out_dir}/{frame_id:06d}.jpg', "BMP") prog_bar.update() # 更新进度条 # 把每一帧串成视频文件 mmcv.frames2video(temp_out_dir, 'output/output_pred.mp4', fps=imgs.fps, fourcc='mp4v') shutil.rmtree(temp_out_dir) # 删除存放每帧画面的临时文件夹 print('删除临时文件夹', temp_out_dir)
【C3】 Pytorch预训练ImageNet图像分类-摄像头实时画面-
- 导入工具包
- 导入中文字体
- 导入ImageNet预训练模型
- 载入ImageNet 1000图像分类标签
- 导入可解释性分析方法
- 预处理
获取摄像头的一帧画面
# 导入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()
img_bgr.shape
#(720, 1280, 3)
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) # BGR转RGB
img_pil = Image.fromarray(img_rgb)
print(img_pil)
# 可视化热力图的类别ID,如果为 None,则为置信度最高的预测类别ID
show_class_id = 587
# show_class_id = None
# 是否显示中文类别
Chinese = True
# Chinese = False
# 前向预测
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_id
show_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))
print(result)
-
图像分类预测函数(这里函数名为process_frame)
-
调用摄像头获取每帧(模板)
# 调用摄像头逐帧实时处理模板 # 不需修改任何代码,只需修改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) frame = process_frame(frame, show_class_id=999) # 卫生纸 # 展示处理后的三通道图像 cv2.imshow('my_window',frame) if cv2.waitKey(1) in [ord('q'),27]: # 按键盘上的q或esc退出(在英文输入法下) break # 关闭摄像头 cap.release() # 关闭图像窗口 cv2.destroyAllWindows()
【D1】自己训练的水果分类模型-单张图像.
-
设置matplotlib中文字体
-
导入工具包
-
导入训练好的Pytorch模型
-
这些训练好的模型老师好像没给
model = torch.load('checkpoint/fruit30_pytorch_20220814.pth') model = model.eval().to(device)
-
导入可解释性分析方法
-
预处理
-
运行图像分类预测
-
生成可解释性分析热力图
-
可视化
-
载入类别名称和索引号映射字典
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() print(idx_to_labels)
{0: ‘哈密瓜’,
, 1: ‘圣女果’,
, 2: ‘山竹’,
, 3: ‘杨梅’,
, 4: ‘柚子’,
, 5: ‘柠檬’,
, 6: ‘桂圆’,
, 7: ‘梨’,
, 8: ‘椰子’,
, 9: ‘榴莲’,
, 10: ‘火龙果’,
, 11: ‘猕猴桃’,
, 12: ‘石榴’,
, 13: ‘砂糖橘’,
, 14: ‘胡萝卜’,
, 15: ‘脐橙’,
, 16: ‘芒果’,
, 17: ‘苦瓜’,
, 18: ‘苹果-红’,
, 19: ‘苹果-青’,
, 20: ‘草莓’,
, 21: ‘荔枝’,
, 22: ‘菠萝’,
, 23: ‘葡萄-白’,
, 24: ‘葡萄-红’,
, 25: ‘西瓜’,
, 26: ‘西红柿’,
, 27: ‘车厘子’,
, 28: ‘香蕉’,
, 29: ‘黄瓜’}print(labels_to_idx)
{‘哈密瓜’: 0,
, ‘圣女果’: 1,
, ‘山竹’: 2,
, ‘杨梅’: 3,
, ‘柚子’: 4,
, ‘柠檬’: 5,
, ‘桂圆’: 6,
, ‘梨’: 7,
, ‘椰子’: 8,
, ‘榴莲’: 9,
, ‘火龙果’: 10,
, ‘猕猴桃’: 11,
, ‘石榴’: 12,
, ‘砂糖橘’: 13,
, ‘胡萝卜’: 14,
, ‘脐橙’: 15,
, ‘芒果’: 16,
, ‘苦瓜’: 17,
, ‘苹果-红’: 18,
, ‘苹果-青’: 19,
, ‘草莓’: 20,
, ‘荔枝’: 21,
, ‘菠萝’: 22,
, ‘葡萄-白’: 23,
, ‘葡萄-红’: 24,
, ‘西瓜’: 25,
, ‘西红柿’: 26,
, ‘车厘子’: 27,
, ‘香蕉’: 28,
, ‘黄瓜’: 29} -
整理代码
【D2】自己训练的水果分类模型-视频文件
-
导入工具包
-
导入pillow中文字体
-
载入类别
idx_to_labels_cn = np.load('idx_to_labels.npy', allow_pickle=True).item()
-
导入训练好的模型
-
导入可解释性分析方法
-
预处理
-
图像分类预测函数
-
视频预测
– 输入输出视频路径
– 创建临时文件夹
– 视频逐帧预测
【D3】自己训练的水果分类模型-摄像头实时画面
- 导入工具包
- 导入中文字体
- 导入训练好的模型
- 载入类别
- 导入可解释性分析方法
- 预处理
- 获取摄像头的一帧画面
- 图像分类预测函数
- 调用摄像头获取每帧(模板)
Deep Feature Factorization
对单张图像,进行Deep Feature Factorization可解释性分析,展示Concept Discovery概念发现图。
【D1】基于DFF的图像子区域可解释性分…
- 导入工具包
- 预处理函数
- 载入模型
- 载入测试图像
- 预处理
img, rgb_img_float, input_tensor = get_image_from_path(img_path)
img.shape #(1560, 2340, 3)
input_tensor.shape #torch.Size([1, 3, 1560, 2340])
初始化DFF算法
classifier = model.fc
dff = DeepFeatureFactorization(model=model,
target_layer=model.layer4,
computation_on_concepts=classifier)
# concept个数(图块颜色个数)
n_components = 5
concepts, batch_explanations, concept_outputs = dff(input_tensor, n_components)
concepts.shape #(2048, 5)
图像中每个像素对应的concept热力图
# concept个数 x 高 x 宽
batch_explanations[0].shape #(5, 1560, 2340)
plt.imshow(batch_explanations[0][4])
plt.show()
- concept与类别的关系
```python
concept_outputs.shape #(5, 1000)
concept_outputs = torch.softmax(torch.from_numpy(concept_outputs), axis=-1).numpy()
concept_outputs.shape #(5, 1000)
```
每个concept展示前top_k个类别
# 每个概念展示几个类别
top_k = 2
concept_label_strings = create_labels(concept_outputs, top_k=top_k)
print(concept_label_strings)
[‘hip:0.09\nchime:0.08’,
, ‘sloth_bear:0.33\nskunk:0.13’,
, ‘wood_rabbit:0.05\nhare:0.04’,
, ‘Egyptian_cat:0.02\ntabby:0.02’,
, ‘wombat:0.09\nbadger:0.07’]
- 生成可视化效果
封装函数
这段并不能直接运行
def dff_show(img_path='test_img/cat_dog.jpg', n_components=5, top_k=2, hstack=False):
img, rgb_img_float, input_tensor = get_image_from_path(img_path)
dff = DeepFeatureFactorization(model=model,
target_layer=model.layer4,
computation_on_concepts=classifier)
concepts, batch_explanations, concept_outputs = dff(input_tensor, n_components)
concept_outputs = torch.softmax(torch.from_numpy(concept_outputs), axis=-1).numpy()
concept_label_strings = create_labels(concept_outputs, top_k=top_k)
visualization = show_factorization_on_image(rgb_img_float,
batch_explanations[0],
image_weight=0.3, # 原始图像透明度
concept_labels=concept_label_strings)
if hstack:
result = np.hstack((img, visualization))
else:
result = visualization
display(Image.fromarray(result))
dff_show()
dff_show(hstack=True)
dff_show(img_path='test_img/box_tabby.png', hstack=True)
dff_show(img_path='test_img/bear.jpeg', hstack=True)
dff_show(img_path='test_img/bear.jpeg', n_components=10, top_k=1, hstack=True)
dff_show(img_path='test_img/giraffe_zebra.jpg', n_components=5, top_k=2, hstack=True)
【D2】基于DFF的图像子区域可解释性分…
- 导入工具包
预处理函数
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])
])
def get_image_from_path(img_path):
'''
输入图像文件路径,输出 图像array、归一化图像array、预处理后的tensor
'''
img = np.array(Image.open(img_path))
rgb_img_float = np.float32(img) / 255
input_tensor = preprocess_image(rgb_img_float,
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
return img, rgb_img_float, input_tensor
def create_labels(concept_scores, top_k=2):
""" Create a list with the image-net category names of the top scoring categories"""
labels = {
0:'Hami Melon',
1:'Cherry Tomatoes',
2:'Shanzhu',
3:'Bayberry',
4:'Grapefruit',
5:'Lemon',
6:'Longan',
7:'Pears',
8:'Coconut',
9:'Durian',
10:'Dragon Fruit',
11:'Kiwi',
12:'Pomegranate',
13:'Sugar orange',
14:'Carrots',
15:'Navel orange',
16:'Mango',
17:'Balsam pear',
18:'Apple Red',
19:'Apple Green',
20:'Strawberries',
21:'Litchi',
22:'Pineapple',
23:'Grape White',
24:'Grape Red',
25:'Watermelon',
26:'Tomato',
27:'Cherts',
28:'Banana',
29:'Cucumber'
}
concept_categories = np.argsort(concept_scores, axis=1)[:, ::-1][:, :top_k]
concept_labels_topk = []
for concept_index in range(concept_categories.shape[0]):
categories = concept_categories[concept_index, :]
concept_labels = []
for category in categories:
score = concept_scores[concept_index, category]
label = f"{labels[category].split(',')[0]}:{score:.2f}"
concept_labels.append(label)
concept_labels_topk.append("\n".join(concept_labels))
return concept_labels_topk
- 载入模型
- 载入测试图像
- 预处理
- 初始化DFF算法
- 图像中每个像素对应的concept热力图
- concept与类别的关系
- 每个concept展示前top_k个类别
- 生成可视化效果
- 封装函数
Captum工具包
shap工具包
lime工具包
后面这三看之前的文章,或者明天按前面的方法再次整理
LIME、shap代码实战
总结
1.
CAM热力图算法
:对Pytorch预训练ImageNet图像分类模型,和自己训练得到的水果图像分类模型,通过各种CAM类激活热力图方法,进行可解释性分析和显著性分析。
使用torch-cam工具包、pytorch-grad-cam工具包,在单张图像、视频文件、摄像头实时画面上绘制CAM热力图,观察神经网络预测指定类别时的“脑回路”和“注意力”,剖析深度学习黑箱子,知其然也知其所以然。
2.
Deep Feature Factorization
:把卷积神经网络中间层输出,通过NMF非负矩阵分解,分解为两个矩阵。对应N个concept概念,得到channel与concept的对应关系、激活值与concept的对应关系。进而分析输入图像不同区域对应的concept概念,及这些concept概念伴随的类别语义。
在原始输入图像上,用不同颜色的色块代表不同concept概念对应的区域,即可对图像分类深度学习模型进行可解释性分析和显著性分析。
3.
Captum工具包
:对Pytorch预训练ImageNet图像分类模型,和自己训练得到的水果图像分类模型,通过遮挡、像素梯度等方法,进行可解释性分析和显著性分析。
使用Meta开源的Captum工具包,基于遮挡敏感性分析、Integrated Gradients、GradientShap、Feature Ablation等可解释性分析方法,观察神经网络预测指定类别时的“脑回路”和“注意力”,剖析深度学习黑箱子,知其然也知其所以然。
4.
shap工具包
:基于博弈论的Shapley值实现机器学习可解释性分析,计算每个数据、每个特征,对模型每类预测结果的Shapley值,反映边际贡献。对Pytorch预训练ImageNet图像分类模型、自己训练得到的30类水果图像分类模型,进行可解释性分析,可视化指定预测类别的shap值热力图。
5.
lime工具包
:基于LIME可解释性分析方法,对UCI葡萄酒质量二分类数据集、Pytorch预训练ImageNet图像分类模型、自己训练的30类水果图像分类模型进行可解释性分析。定量评估出某个样本、某个特征(图块),对模型预测为某个类别的贡献影响。