李宏毅的可解释模型——三个任务

1、问题

观看了李宏毅老师的机器学习进化课程之可解释的机器学习,课程中对主要是针对黑盒模型进行白盒模型转化的技巧和方法进行了简单介绍,详细细节可以参考《Interpretable Machine Learning》。像一些线性模型、树形模型是可解释的ML model,但是,深度学习一直被称为“黑盒子”,是end-to-end模型,即忽略内部计算,只关心输入端和输出端。然后就有不少人想要知道深度学习模型训练之后学到了什么,是否真的是人类设想的,学到了某些物体的轮廓和纹理,又是如何判别的,这些疑问可以从中间结果、或将模型转换为可解释的ML model等多种途径解决。

可解释性(非数学定义):可解释性是一个人能够理解决策原因的程度

可解释的机器学习是它捕获“从机器学习模型中提取与数据中包含的活模型学习的关系相关的知识”。

1.1、可解释模型的分类

机器学习的可解释性的方法可以根据各种标准进行分类

(1)Intrinsic or post hoc?

该标准区分了可解释性是通过限制机器学习模型的复杂性(Intrinsic)还是通过应用训练后分析模型的方法(Post hoc)来实现。Intrinsic是指由于结构简单而被认为是可解释的机器学习模型,如线性模型、树形模型,Post hoc是训练后解释方法的应用,如排列特征重要性。

(2)Model-specific or model-agnostic? 

模型是特定的还是不可知的,特定于模型(Model-specific)的解释工具仅限于特定的模型类。线性模型中回归权重的解释是特定于模型的解释,因为根据定义,内在可解释模型的解释总是特定于模型的。仅用于解释例如神经网络的工具是特定于模型的。与模型无关的工具可以用在任何机器学习模型上,并且在模型已经被训练(事后)之后被应用。这些不可知的方法(model-agnostic)通常通过分析特征输入和输出对来工作。根据定义,这些方法不能访问模型内部,如重量或结构信息。

(3)Local or global?

模型是局部还是整体的,即是解释单个预测还是整体模型。

2、任务

3、解析

3.1 任务一:计算梯度

我们都知道神经网络模型是分为forward和backward两个部分,前向传播(forward)是从输入端到输出端,通过各个网络层的隐节点产生输出。后向传播是定义一个损失函数,将损失函数的信息向后传播用以计算梯度,从而达到优化网络参数的目的。

3.1.1 前提

(1)stop_gradient

查看一个Tensor是否计算并传播梯度,如果stop_gradinet为true,则该Tensor不会计算梯度,并会阻绝Autograd的梯度传播,反之,则进行梯度计算和传播

(2)grad

查看一个Tensor的梯度,数据类型是numpy.ndarray。

(3)backward

调用backward,自动计算梯度,并将结果存在grad属性中。backward()会累积梯度,还提供了clear_grad()函数来清除当前Tensor的梯度。

(4)自动微分运行机制

飞浆的神经网络核心是自动微分(Tensorflow和Pytorch也有自动微分机制),飞桨的自动微分是通过trace的方式,记录前向OP的执行,并自动创建反向var和添加相应的反向OP,然后来实现反向梯度计算的。以a=wx+b为例,细节参考。

3.1.2 实现

(1)处理数据

import paddle
import os
import pandas
import cv2
import numpy as np
from  paddle.io  import Dataset,DataLoader
from paddlenlp.datasets import MapDatasettrain_path = 'data/food-11/training'
val_path = 'data/food-11/validation'
test_path = 'data/food-11/testing'#图片大小不一致
def  data_loader(path):filelist = os.listdir(path)data = []  for i in filelist[:1000]:img = cv2.imread(path+'/'+i)img = cv2.resize(img,(512,512))# 读入的图像数据格式是[H, W, C]# 使用转置操作将其变成[C, H, W]img = np.transpose(img, (2,0,1))img = np.array(img,dtype='float32')label = int(i.split('_')[0])data.append((img,label))return datatrain_data = data_loader(train_path)train_loader = paddle.io.DataLoader(MapDataset(train_data), return_list=True, shuffle=True, batch_size=5, drop_last=True)

(2)定义模型

import paddle
import numpy as np
from paddle.nn import Conv2D, MaxPool2D, Linear## 组网
import paddle.nn.functional as F# 定义 LeNet 网络结构
class LeNet(paddle.nn.Layer):def __init__(self, num_classes=1):super(LeNet, self).__init__()# 创建卷积和池化层# 创建第1个卷积层self.conv1 = Conv2D(in_channels=3, out_channels=32, kernel_size=128)self.max_pool1 = MaxPool2D(kernel_size=4, stride=2)# 尺寸的逻辑:池化层未改变通道数;当前通道数为6# 创建第2个卷积层self.conv2 = Conv2D(in_channels=32, out_channels=64, kernel_size=128)self.max_pool2 = MaxPool2D(kernel_size=4, stride=2)# 创建第3个卷积层self.conv3 = Conv2D(in_channels=64, out_channels=128, kernel_size=64)self.max_pool3 = MaxPool2D(kernel_size=4, stride=2)# 创建第4个卷积层self.conv4 = Conv2D(in_channels=128, out_channels=256, kernel_size=32)self.max_pool4 = MaxPool2D(kernel_size=4, stride=2)# 创建第5个卷积层self.conv5 = Conv2D(in_channels=256, out_channels=512, kernel_size=16)self.max_pool5 = MaxPool2D(kernel_size=4, stride=2)# 创建第6个卷积层self.conv6 = Conv2D(in_channels=512, out_channels=1024, kernel_size=8)self.max_pool6 = MaxPool2D(kernel_size=4, stride=2)# 尺寸的逻辑:输入层将数据拉平[B,C,H,W] -> [B,C*H*W]# 输入size是[28,28],经过三次卷积和两次池化之后,C*H*W等于120self.fc1 = Linear(in_features=1024, out_features=64)# 创建全连接层,第一个全连接层的输出神经元个数为64, 第二个全连接层输出神经元个数为分类标签的类别数self.fc2 = Linear(in_features=64, out_features=num_classes)# 网络的前向计算过程def forward(self, x):x = self.conv1(x)# 每个卷积层使用Sigmoid激活函数,后面跟着一个2x2的池化x = F.sigmoid(x)x = self.max_pool1(x)x = F.sigmoid(x)x = self.conv2(x)x = self.max_pool2(x)x = self.conv3(x)x = self.max_pool3(x)x = self.conv4(x)x = self.max_pool4(x)x = self.conv5(x)x = self.max_pool5(x)x = self.conv6(x)x = self.max_pool6(x)# 尺寸的逻辑:输入层将数据拉平[B,C,H,W] -> [B,C*H*W]x = paddle.reshape(x, [x.shape[0], -1])x = self.fc1(x)x = F.sigmoid(x)x = self.fc2(x)return xmodel_pre = paddle.Model(model) 

(3)训练,并保存梯度数据

def normalize(image):return (image - image.min()) / (image.max() - image.min())def compute_saliency_maps(x, y, model):# 计算梯度x.stop_gradient = False#模型训练y_pred = model(x)loss_func = paddle.nn.loss.CrossEntropyLoss()loss = loss_func(y_pred, y)#反向传播loss.backward()#获取反向传播的梯度saliencies = np.abs(x.grad)# saliencies: (batches, channels, height, weight)# 因为接下来我们要对每张图片画 saliency map,每张图片的 gradient scale 很可能有巨大落差# 可能第一张图片的 gradient 在 100 ~ 1000,但第二张图片的 gradient 在 0.001 ~ 0.0001# 如果我们用同样的色阶去画每一张 saliency 的话,第一张可能就全部都很亮,第二张就全部都很暗,# 如此就看不到有意义的结果,我们想看的是「单一张 saliency 内部的大小关係」,# 所以这边我们要对每张 saliency 各自做 normalize。手法有很多种,这边只採用最简单的saliencies = np.stack([normalize(item) for item in saliencies])return saliencies#这里遍历了所有数据,但是只存最后一组,
for images,labels in train_loader:# print(images,labels)saliencies = compute_saliency_maps(images, labels, model)

(4)绘图

import cv2
import matplotlib.pyplot as plt
import paddle
# 使用 matplotlib 画出来,batch_size=5,默认画5张
fig, axs = plt.subplots(2, 5, figsize=(15, 8))
index = 0
saliencies = paddle.to_tensor(saliencies)
for row, target in enumerate([images, saliencies]):for column, img in enumerate(target):img = img.numpy()axs[row][column].imshow(img[0])plt.show()
plt.close()

3.2 任务二:中间结果展示

CNN模型在卷积的过程中,我们认为不同的滤波器能学习到不同的图像纹理或边缘特征,但是模型训练是一体化,并不展示中间结果,因此,hook机制应运而生。

3.2.1 前提

(1)model的执行模式

模型的执行模式有两种,如果需要训练的话调用 train() ,如果只进行前向执行则调用 eval()

(2)Hook的应用

hook是一个作用于变量的自定义函数,在模型执行时调用。对于注册在层上的hook函数,可以分为pre_hookpost_hook两种。pre_hook可以对层的输入变量进行处理,用函数的返回值作为新的变量参与层的计算。post_hook则可以对层的输出变量进行处理,将层的输出进行进一步处理后,用函数的返回值作为层计算的输出。细节参考

        hook的实现步骤:

        1. 定义一个对feature进行处理的函数,比如叫hook_fun

        2. 注册hook:告诉模型,我将在哪些层使用hook_fun来处理feature

  • register_forward_pre_hook(pre_hook)
import paddle
import numpy as np# the forward_post_hook change the input of the layer: input = input * 2
def forward_pre_hook(layer, input):# user can use layer and input for information statistis tasks# change the inputinput_return = (input[0] * 2)return input_returnlinear = paddle.nn.Linear(13, 5)
# register the hook
forward_pre_hook_handle = linear.register_forward_pre_hook(forward_pre_hook)
value0 = np.arange(26).reshape(2, 13).astype("float32")
in0 = paddle.to_tensor(value0)
out0 = linear(in0)# remove the hook
forward_pre_hook_handle.remove()
value1 = value0 * 2
in1 = paddle.to_tensor(value1)
out1 = linear(in1)# hook change the linear's input to input * 2, so out0 is equal to out1.
assert (out0.numpy() == out1.numpy()).any()
  • register_forward_post_hook(post_hook)
import paddle
import numpy as np# the forward_post_hook change the output of the layer: output = output * 2
def forward_post_hook(layer, input, output):# user can use layer, input and output for information statistis tasks# change the outputreturn output * 2linear = paddle.nn.Linear(13, 5)
# register the hook
forward_post_hook_handle = linear.register_forward_post_hook(forward_post_hook)
value1 = np.arange(26).reshape(2, 13).astype("float32")
in1 = paddle.to_tensor(value1)
out0 = linear(in1)# remove the hook
forward_post_hook_handle.remove()
out1 = linear(in1)# hook change the linear's output to output * 2, so out0 is equal to out1 * 2.
assert (out0.numpy() == (out1.numpy()) * 2).any()

3.2.2 实现

模型是基于任务一,可以通过summary()打印网络的基础结构和参数信息,即

model_pre.summary()

模型结构是6层卷积池化层,2层全连接层,我们通过hook机制来输出中间结果,可以定一个全局变量,来记录中间结果值。

#定义hook函数
def hook(model,input,output):global llll = output#对模型第三层卷积层进行输出
hook_ll = model.conv3.register_forward_post_hook(hook)
#遍历数据,并代入到模型中
for i in train_loader:model(i[0])#待执行完,ll变量里边存储中间结果数据,是一个四维数组,
#第一维是train_loader里边的batch_size个数,即图片个数
#第二维是滤波器的个数
#第三维和第四维是当前卷积层卷积之后的output的宽高
print(ll[:,1,:,:])

画图:

import cv2
import matplotlib.pyplot as plt
import paddle
fig, axs = plt.subplots(1, 5, figsize=(15, 6))for i,img in enumerate(ll[:,1,:,:]) :# print(i,img)img = img.numpy()axs[i].imshow(img)plt.show()
plt.close()

3.3 任务三:LIME & SHAP 的应用

3.3.1 LIME & SHAP的工具使用

(1)LIME

LIME(Local Interpretable Model-AgnosticExplanations)算法是为了解释某个样本在模型中的信息,帮助理解模型。原理参考

以iris数据集为数据源的LIME实例:

import lime 
import sklearn
import numpy as np
import sklearn.ensemble
import sklearn.metrics
import matplotlib.pyplot as plt 
from sklearn.datasets import load_iris
from sklearn.ensemble import GradientBoostingClassifier
from lime.lime_tabular import LimeTabularExplainer
#读取数据
# categories = ['alt.atheism', 'soc.religion.christian']
# newsgroups_train = fetch_20newsgroups(subset='train', categories=categories)
# newsgroups_test = fetch_20newsgroups(subset='test', categories=categories)
# class_names = ['atheism', 'christian']# data = pd.DataFrame(newsgroups_train.data)
data = load_iris()feature_names = data.feature_names
class_names = data.target_names
#利用GBDT分类模型区分是否违约x =data.data
y = data.targetgbdt = GradientBoostingClassifier()
gbdt = gbdt.fit(x[:140],y[:140])
#直接将训练数据作为预测数据
pred = gbdt.score(x[140:],y[140:])#中文字体显示  
plt.rc('font', family='SimHei', size=13)#建立解释器
explainer = LimeTabularExplainer(x, feature_names=feature_names, class_names=class_names)
#解释第81个样本的规则
exp = explainer.explain_instance(x[81], gbdt.predict_proba)
#画图
fig = exp.as_pyplot_figure()
#画分析图
exp.show_in_notebook(show_table=True, show_all=False)

(2)SHAP

SHAP(SHapley Additive exPlanation)是另一种可解释方法的模型 。具体实现细节就不深究了。但是,提供了多种模型的解释器:

细节参考

(1)特征重要性

使用XGboost模型去训练iris数据集,并通过特征重要性排序,来解释各项特征对模型的影响力。

import xgboost as xgb
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt; plt.style.use('seaborn')import sklearn
import numpy as np
import sklearn.ensemble
import sklearn.metrics
from sklearn.datasets import load_iris
from sklearn.ensemble import GradientBoostingClassifier
#读取数据
# categories = ['alt.atheism', 'soc.religion.christian']
# newsgroups_train = fetch_20newsgroups(subset='train', categories=categories)
# newsgroups_test = fetch_20newsgroups(subset='test', categories=categories)
# class_names = ['atheism', 'christian']# data = pd.DataFrame(newsgroups_train.data)
data = load_iris()feature_names = data.feature_names
class_names = data.target_names
#利用GBDT分类模型区分是否违约x =data.data
y = data.target# 训练xgboost回归模型
model = xgb.XGBRegressor(max_depth=4, learning_rate=0.05, n_estimators=150)
model.fit(x, y)# 获取feature importance 
plt.figure(figsize=(15, 5))
plt.bar(range(len(feature_names)), model.feature_importances_)
plt.xticks(range(len(feature_names)), feature_names, rotation=-45, fontsize=14)
plt.title('Feature importance', fontsize=14)

(2)通过shap进行分析,计算shap_values值

import shap
# model是在第1节中训练的模型
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(x)
print(shap_values.shape)

获取单个样本的shape值

j = 30
f_explainer = pd.DataFrame()
f_explainer['feature'] = feature_names
f_explainer['feature_value'] = x[j]
f_explainer['shap_value'] = shap_values[j]
print(f_explainer)

确定模型的基线:

#基线值就是训练集的目标变量的拟合值的均值
#一个样本中各特征SHAP值的和加上基线值应该等于该样本的预测值
y_base = explainer.expected_value
print(y_base)

绘图

shap.initjs()
shap.force_plot(explainer.expected_value, shap_values[j], x[j])

#对特征总体进行分析
shap.summary_plot(shap_values, x)

#对特征总体进行分析,绘制柱形图
shap.summary_plot(shap_values,x , plot_type="bar")

#某个特征对shap_value的影响
shap.dependence_plot('Feature 2', shap_values, x, interaction_index=None, show=False)

 多变量之间的分析

#多个变量的交互分析
shap_interaction_values = shap.TreeExplainer(model).shap_interaction_values(x)
shap.summary_plot(shap_interaction_values, x, max_display=4)#两个变量交互下变量对目标值的影响
shap.dependence_plot('Feature 2', shap_values,x , interaction_index='Feature 1', show=False)

3.3.2 实现

(1)LIME

from lime.lime_image import LimeImageExplainer#处理数据
images=[]
labels = []
for image,label in train_loader:for i in image:i = i.numpy()images.append(i)for j in label:j = j.numpy()[0]# images[i]=imagelabels.append(j)def predict(images):images =  np.transpose(np.array(images), (0,3,1,2))images = paddle.to_tensor(images)output = model(images)return output.detach().numpy()#此处要注意顺序,model的data格式是[batch_size,channels,height,weight]
# lime的data顺序是[batch_size,height,weight,channels]
lime_ex = LimeImageExplainer().explain_instance(image= np.transpose(np.array(images), (0,2,3,1))[0],classifier_fn=predict,labels=labels[0])lime_img, mask = lime_ex.get_image_and_mask(label=int(labels[0]))import matplotlib.pyplot as plt
plt.imshow(lime_img)
plt.show()

(2)SHAP

 SHAP的DeepExplainer解释器,仅支持pytorch和tensorflow框架,需要用该框架定义model。鉴于下载数据集的麻烦,忽略。

参考:

李宏毅课程-机器学习进阶-作业1-卷积神经网络的可解释性 - 飞桨AI Studio - 人工智能学习与实训社区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/466624.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

柔性数组和环形队列之间的故事

之前的文章,讲解了柔性数组,有很多人留言,提到一些问题。刚好,之前发关于环形队列的文章有些问题,这次刚好拿出来一起说一下,并用柔性数组实现一个环形队列。柔性数组的上一篇文章环形队列C语言实现文章1、…

STM32——时钟系统

STM32——时钟系统 宗旨:技术的学习是有限的,分享的精神是无限的。 一、时钟树 普通的MCU,一般只要配置好GPIO 的寄存器,就可以使用了。STM32为了实现低功耗,设计了非常复杂的时钟系统,必须开启外设时钟才…

目标检测发展路程(一)——Two stage

目标检测是计算机视觉领域中非常重要的一个研究方向,它是将图像或者视频中目标与其他不感兴趣的部分进行区分,判断是否存在目标,确定目标位置,识别目标种类的任务,即定位分类。传统的目标检测模型有VJ.Det[1,2],HOG.De…

都2021年了,c/c++开发竟然还能继续吃香??

年后就迎来了金三银四,你准备好2021年的跳槽涨薪计划了吗?今天我就来给大家分享,c/c作为老牌开发常青树,还能与java/python/go较较劲的岗位和技术在哪里!同时,给大家整理了2021年系统全面技术学习资料。文末…

目标检测模型——One stage(YOLO v5的模型解析及应用)

1. 简介 目标检测分为Two stage和One stage,只使用一个网络同时产生候选区域并预测出物体的类别和位置,所以它们通常被叫做单阶段检测算法(One stage)。本篇文章只讲One stage模型,常见的模型有YOLO,SSD。 目标检测发…

腾讯回应QQ读取用户浏览器历史记录

腾讯QQ官方认证账号在知乎回应“QQ扫描读取所有浏览器的历史记录”表示,PC QQ存在读取浏览器历史用以判断用户登录安全风险的情况,读取的数据用于在PC QQ的本地客户端中判断是否恶意登录。所有相关数据不会上传至云端,不会储存,也…

OCR系列——总体概述

最近参加了百度Paddle的动手学OCR课程,特此做一个学习总结。 1. 简介 OCR(Optical Character Recognition,光学字符识别)是计算机视觉重要方向,传统的OCR一般面向扫描文档类对象,现在的OCR是指场景文字识…

STM32——系统滴答定时器

STM32——系统滴答定时器 宗旨:技术的学习是有限的,分享的精神是无限的。 一、SysTick【内核中】 【风格:先描述一下库对寄存器的封装,再举例实现某些功能】 SysTick定时器被捆绑在NVIC中,用于产生SysTick异常&#…

你会用while(1)还是for(;;)写循环代码?

看代码看到for(;;)&#xff0c;然后觉得为什么不写成while(1)呢&#xff0c;所以就做了下面的测试。网上有解释&#xff0c;因为while需要做一次判断&#xff0c;理论上执行会花费的时间更久&#xff0c;for(;;)只是执行了两次空语句&#xff0c;执行会更快for.c#include <s…

OCR系列——文本检测任务

1. 简介 文本检测任务是找出图像或视频中的文字位置。不同于目标检测任务&#xff0c;目标检测不仅要解决定位问题&#xff0c;还要解决目标分类问题。 目标检测和文本检测同属于“定位”问题。但是文本检测无需对目标分类&#xff0c;并且文本形状复杂多样。 当前所说的文本…

关于ORACLE 语句中,IN 超过1000个的解决方法

在ORACLE SELECT 语句中 IN 的数据如果超过 1000&#xff0c;就会出错&#xff0c;解决方法也很简单&#xff0c;以C#代码为例&#xff1a; 1、先写一个方法&#xff0c;接收2个参数 参数1&#xff1a;接收 IN里面的数据&#xff0c;如&#xff1a;a1,a2,...a2000 &#xff1b;…

9个提高代码运行效率的小技巧你知道几个?

我们写程序的目的就是使它在任何情况下都可以稳定工作。一个运行的很快但是结果错误的程序并没有任何用处。在程序开发和优化的过程中&#xff0c;我们必须考虑代码使用的方式&#xff0c;以及影响它的关键因素。通常&#xff0c;我们必须在程序的简洁性与它的运行速度之间做出…

STM32——按键

STM32——按键 宗旨&#xff1a;技术的学习是有限的&#xff0c;分享的精神是无限的。 一、GPIO工作模式 1、当I/O端口配置为输入时&#xff1a; 输出缓冲器被禁止 施密特触发输入被激活 根据输入配置(上拉&#xff0c;下拉或浮动)的不同&#xff0c;弱上拉和下拉电阻被连接 …

深度学习——模型的压缩和加速

1. 简介 随着深度学习发展&#xff0c;越来越多的模型被发现和应用&#xff0c;模型的体量也越来越大&#xff0c;出现了模型过于庞大和参数冗余的问题。同时&#xff0c;移动端对模型的需求也是越轻量越好&#xff0c;因此&#xff0c;模型压缩和加速技术应运而生。 模型压缩…

干货,记一次解决录音杂音问题

最近在项目上遇到一个问题&#xff0c;也不能说是最近项目上的问题了&#xff0c;是之前一直存在的问题&#xff0c;但是对项目没什么影响&#xff0c;所以我就不怎么理会&#xff0c;直到最近&#xff0c;同事说这个杂音已经影响到了项目的开发&#xff0c;所以今天花了一天时…

3.5.2 冒泡排序类

那么&#xff0c;我们就以冒泡排序为例&#xff0c;把它改造成一个类。首先&#xff0c;单击菜单&#xff0c;“项目”&#xff0d;“添加类”&#xff0c;添加一个BubbleSort.cs类文件。IDE自动为我们创建如下代码&#xff1a; usingSystem;usingSystem.Collections.Generic;u…

STM32——串口通信

STM32——串口通信 宗旨&#xff1a;技术的学习是有限的&#xff0c;分享的精神是无限的。 一、异步串口通信协议 STM32 的串口非常强大&#xff0c;它不仅支持最基本的通用串口同步、异步通信&#xff0c;还具有 LIN 总线功能&#xff08;局域互联网&#xff09;、IRDA 功能&…

操作系统——死锁(银行家算法)

1、概述 1.1 死锁 死锁是多个进程因竞争资源而造成的一种僵局&#xff08;互相等待&#xff09;&#xff0c;若无外力作用&#xff0c;这些进程都将无法向前推进。 1.2 死锁产生的原因和条件 原因&#xff1a;&#xff08;1&#xff09;竞争资源&#xff1b;&#xff08;2&…

操作系统——内存管理

1、内存基本概念 1.1 主要功能 内存空间的分配与回收&#xff1b;地址转换内存保护&#xff1a;使用上下限寄存器或者重定位寄存器和界地址寄存器内存扩充&#xff1a;交换和覆盖内容共享 2、内存的分配与回收 2.1 连续分配方式 连续分配方式是指为一个用户程序分配一个连续…