深度学习模型可解释性初探

时间:2023-01-02 00:02:56

1. 可解释性是什么

0x1:广义可解释性

广义上的可解释性指:

在我们需要了解或解决一件事情的时候,我们可以获得我们所需要的足够的可以理解的信息。

比如我们在调试 bug 的时候,需要通过变量审查和日志信息定位到问题出在哪里。

比如在科学研究中面临一个新问题的研究时,我们需要查阅一些资料来了解这个新问题的基本概念和研究现状,以获得对研究方向的正确认识。

反过来理解,如果在一些情境中我们无法得到相应的足够的信息,那么这些事情对我们来说都是不可解释的。比如刘慈欣的短篇《朝闻道》中霍金提出的「宇宙的目的是什么」这个问题一下子把无所不知的排险者卡住了,因为再高等的文明都没办法理解和掌握造物主创造宇宙时的全部信息,这些终极问题对我们来说永远都是不可解释的。

0x2:在统计学习中的可解释性

在传统统计中,我们通过调查大量的数据来构造和验证假设。我们建立模型来构建规则,我们可以将其纳入我们的模型中。

例如,营销公司可以建立一个模型,将营销活动数据与财务数据相关联,以确定构成有效营销活动的是什么。这是一种自上而下的数据科学方法,可解释性是关键,因为它是所定义规则和过程的基石。由于相关性往往不等于因果关系,所以在进行决策和解释时,需要对模型进行很强的理解。

作为一名数据分析从业者,我们经常关心微调模型以获得最佳性能。

数据科学通常被定义为:'给出具有X标签的数据,并以最小误差找到模型'。

尽管训练高性能模型的能力对于数据科学家来说是一项关键技能,但能够从更大的角度来看是很重要的。

数据和机器学习模型的可解释性是在数据科学的 “有用性”中至关重要的方面之一,它确保模型与您想要解决的问题保持一致,能够正确地解释你的发现是数据科学过程的重要组成部分。

0x3:机器学习领域的可解释性

1. 决策树是目前业内公认可解释性最好的非线性机器学习算法

而具体到机器学习领域来说,以最用户友好的决策树模型为例,模型每作出一个决策都会通过一个决策序列来向我们展示模型的决策依据

比如男性&未婚&博士&秃头的条件对应「不感兴趣」这个决策。

而且决策树模型自带的基于信息理论的筛选变量标准也有助于帮助我们理解在模型决策产生的过程中哪些变量起到了显著的作用。

所以在一定程度上,我们认为决策树模型是一个具有比较好的可解释性的模型。

2. DNN深度学习模型的可解释性不是那么明显

再以用户最不友好的多层神经网络模型为例,模型产生决策的依据是什么呢?

大概是以比如 1/(e^-(2*1/(e^(-(2*x+y))+1) + 3*1/(e^(-(8*x+5*y))+1))+1) 是否大于 0.5 为标准(这已经是最简单的模型结构了),这一连串的非线性函数的叠加公式让人难以直接理解神经网络的「脑回路」,所以深度神经网络习惯性被大家认为是黑箱模型。

人脑是不擅长理解非线性关系,更不要说是有层层组合后的复杂非线性组合。

所以,一个很自然的思考对策是,需要找一种维度较低的低阶近似映射(即找到一种低维度的表征方法),使得在即不损失太多原高阶决策超曲面空间分布的前提下,找到一种低阶的决策曲面。这样,人类就可以从低阶的决策曲面中得以一窥原始高阶决策超曲面的尊容了。

笔者思考:按照这种思路,其实我们可以看到,t-SNE、PCA降维后可视化就是一种低阶近似映射

 

2. 我们为什么需要可解释性?

广义上来说我们对深度学习模型的可解释性的需求主要来源于对问题和任务了解得还不够充分

尽管高度的非线性赋予了多层神经网络极高的模型表示能力,配合一些合适的调参技术可以在很多问题上达到非常喜人的表现。

一个很难受的问题是,虽然我们造出了准确度极高的机器,但最后只能得到一堆看上去毫无意义的模型参数和拟合度非常高的判定结果。作为模型开发者,我们显然不能仅仅满足于模型效果好即可。

实际上,对模型可解释性的探究就是在探寻模型work well的原理,了解到了原理,也许就不需要进行很多无用的模型调参工作,优化的方向也会更有方向性。

从模式识别的角度来看,模型本身也意味着知识(或者说模型本身存储了一些模式知识),我们希望知道模型究竟从数据中学到了哪些知识(以人类可以理解的方式表达的)从而产生了最终的决策。从中是不是可以帮助我们发现一些潜在的关联。

比如我想基于深度学习模型开发一个帮助医生判定病人风险的应用,除了最终的判定结果之外,我可能还需要了解模型产生这样的判定是基于病人哪些因素的考虑。

0x1:可解释性的不同动机

1. 判别并减轻偏差(Identify and mitigate bias)

偏差可能存在于任何数据集中,数据科学家需要确定并尝试修正偏差。

1. 数据集的规模可能有限,并且不能代表所有数据。
2. 或者数据捕获过程可能没有考虑到潜在的偏差。

在彻底进行数据分析后,或者分析模型预测与模型输入之间的关系时,偏差往往会变得明显。
请注意,解决偏差问题没有唯一的解决方案,但是可解释性的关键一步是意识并发现潜在的偏差。

1)特征工程中存在的偏差

虚拟一个简历筛选的场景,我们对面试者投递的简历进行了分词后,建立了word index table,但是一个很容易被忽视的问题是,对于一家IT公司的技术部门来说,男性求职者往往占了绝大多数的比例,因此像类似“技术简历”这种词有更大的可能性会出现在男性求职者的简历中。

当使用这些词向量进行训练模型时,招聘人员搜索“技术简介”将使女性履历保留在最下面。

但是另一方面我们也需要注意,如果这种偏差是我们有意而为之的,则这种bais就是可以接受的。例如我们的目标并不是平等地搜索求职者简历,而是尽可能发现有技术能力的求职者,或者我们的招聘更倾向于男性,则这种word table index编码方式就没有问题。

总之,数据挖掘项目中的偏差需要谨慎对待,它可能对我们是有益的也可能是有害的。

2)训练数据样本分布不均衡偏差

例如当您在小型数据集上训练目标检测模型时,通常情况下图像的宽度太有限。为了避免只适用于数据中噪音和不重要元素的模型,需要在不同环境,不同光照条件和不同角度下的各种物体图像。

在非图像领域,尤其是NLP文本检测领域,不同文本风格(代码风格)的样本数量常常是分布不均的,这点在WEBSHELL网站后门中非常常见,黑客们会倾向于使用某一种或某几种WEBSHELL样本,这种偏差会带来的最明显问题是,模型可能会忽视某些非常小众的黑样本。

2. 考虑问题的上下文(Accounting for the context of the problem)

在大多数问题中,您正在使用的数据集仅仅是您正试图解决的问题的粗略表示,而机器学习模型无法捕捉到真实任务的完整复杂性。纯理论上的无穷大N样本在实际工程中是非常难达到的。

这个时候,可解释模型可帮助您了解并解释模型中包含和未包含的因素。

3. 改进泛化能力和性能(Improving generalisation and performance)

高解释性模型通常有更好的泛化能力。

可解释性不是要了解所有数据点的模型的每个细节。必须将可靠的数据模型问题理解结合起来才能获得性能更好的解决方案。

0x2:在安全攻防AI 领域同样需要可解释性

笔者在行业内以及在技术圈子内,近几年不断看到很多效果很好的实际工程应用,深度学习模型在海量数据以及大规模深度结构下,取得相当好,甚至超过国际上老牌AV引擎的检测能力。从结果上看,我们似乎也不是那么需要可解释性,毕竟结果才是最重要的,世界上不能解释的事情太多了,也不见得每件事都能得到完美的可解释性。

但是笔者认为,依然存在一些场景下,我们会需要可解释性:

1. 找到模型检测效果下降的真正原因 - 对异常产生的原因进行追踪和定位

某天下午晴空万里,我们正在位子上写博客,突然一堆误报告警响起,你的深度模型遇到了一批误报。你紧急下线了该模型,平复了一下心情,接下来要做的是找到误报的原因。

但是现在问题来了,你的模型有2000w的自有参数,层与层之间完全是非线性的组合,如何知道到底是哪些神经元导致了对这批样本的误报呢?这批导致误报的样本的共性是什么?其中的哪些特征模式被模型误认定为是一个黑模式了呢?

这一连串的问题,会驱动我们去更深入地探究我们训练出的深度学习模型的可解释性。

2. 找到迭代优化的最快方向

相信大家在进行模型开发的过程中,都会经历一个阶段,大量的翻阅google上的学术paper,尝试各种神经网络组合,会发现现在有非常多的神经网络结构被提出,论文里的实验结果也非常惊艳,这个时候,我们会抱着“侥幸心理”去进行大量尝试,虽然paper里的问题场景我们的项目可能并不一致。

最终通过数周的大量实验,我们“摸索”出了一种模型结构,在训练集和验证集上的表现都很好,都拿到了99.8/99.9的acc,但还是有少量的误报(笔者所在的行业是误报敏感型的,漏报相对还可以接受),这个时候我们会面临一个问题,接下来怎么优化?继续增大样本?还是继续翻阅paper,优化模型结构?是否不同的神经网络结构之间存在一些原理上的共性?

想要系统化、工程化的解决这个问题,就需要进行可解释性的研究。例如我们通过可解释性的可视化得到了模型的热点权重分布,即分析出模型对输入特征空间中的哪些特征维度更“看中”,对另外一些特征维度“相对轻视”,而这有可能恰恰就是导致我们误报的原因之一。

Relevant Link: 

https://www.leiphone.com/news/201805/Ti3mOdeRlWTplIlZ.html
https://cloud.tencent.com/developer/article/1096716
https://zhuanlan.zhihu.com/p/38151985
https://www.jiqizhixin.com/articles/2018-01-10

 

3. 有哪些可解释性方法

机器学习的目的是从数据中发现知识或解决问题,那么在这个过程中只要是能够提供给我们关于数据或模型的可以理解的信息,有助于我们更充分地发现知识、理解和解决问题的方法,那么都可以归类为可解释性方法。如果按照可解释性方法进行的过程进行划分的话,大概可以划分为三个大类:

1. 在建模之前的可解释性方法
2. 建立本身具备可解释性的模型
3. 在建模之后使用可解释性方法对模型作出解释

0x1:在建模之前的可解释性方法

这一类方法其实主要涉及一些数据预处理数据展示的方法。机器学习解决的是从数据中发现知识和规律的问题,如果我们对想要处理的数据特征所知甚少,指望对所要解决的问题本身有很好的理解是不现实的。因此,在建模之前的可解释性方法的关键在于帮助我们迅速而全面地了解数据分布的特征,从而帮助我们考虑在建模过程中可能面临的问题并选择一种最合理的模型来逼近问题所能达到的最优解。

1. 数据可视化

数据可视化方法就是一类非常重要的建模前可解释性方法。大多数时候,我们在真正要研究一个数据问题之前,通过建立一系列方方面面的可视化方法来建立我们对数据的直观理解是非常必须的,特别是当数据量非常大或者数据维度非常高的时候,比如一些时空高维数据,如果可以建立一些一些交互式的可视化方法将会极大地帮助我们从各个层次角度理解数据的分布。

这方面的相关讨论,可以参阅我的另一篇blog

2. 样本数据探索

还有一类比较重要的方法是探索性质的数据分析,可以帮助我们更好地理解数据的分布情况。比如一种称为 MMD-critic 方法中,可以帮助我们找到数据中一些具有代表性或者不具代表性的样本。

深度学习模型可解释性初探

3. 特征重要性探索

特征重要性是解释模型的一种基本方法

0x2:建立本身具备可解释性的模型

模型本身具备可解释性是最好也是最直接的一类可解释性方法,同样也是一类要求和限定很高的方法(深度神经网络在很多情况下就不具备可解释性),具备可解释性模型大概可以分为以下几种

1. 基于规则的方法(Rule-based)
2. 基于单个特征的方法(Per-feature-based)
3. 基于实例的方法(Case-based)
4. 稀疏性方法(Sparsity)
5. 单调性方法(Monotonicity)

1. 基于规则的方法(Rule-based)

基于规则的方法比如我们提到的非常经典的决策树模型。这类模型中任何的一个决策链都可以对应到一个逻辑规则表示。在决策树中,可解释性通过一连串的 if-else 逻辑组合来表达,

但当规则表示过多或者原始的特征本身就不是特别好解释的时候,基于规则的方法有时候也不太适用。

2. 基于单个特征的方法(Per-feature-based)

基于单个特征的方法主要是一些非常经典的线性模型,比如线性回归、逻辑回归、广义线性回归、广义加性模型等,这类模型可以说是现在可解释性最高的方法。线性回归方程中的 w 和 b 本身就有非常强的可解释性。

线性回归可谓是一种非常经典的数学模型,在计量经济学中,大半本书都在讨论线性模型,包括经济学及相关领域的论文其实大多数也都是使用线性回归作为方法来进行研究。这种非常经典的模型全世界每秒都会被用到大概 800 多万次。

为什么大家这么青睐这个模型呢?除了模型的结构比较简单之外,更重要的是线性回归模型及其一些变种拥有非常 solid 的统计学基础,统计学可以说是最看重可解释性的一门学科了,上百年来无数数学家统计学家探讨了在各种不同情况下的模型的参数估计、参数修正、假设检验、边界条件等等问题,目的就是为了使得在各种不同情况下都能使模型具有有非常好的可解释性。

3. 基于实例的方法(Case-based)

基于实例的方法主要是通过一些代表性的样本来解释聚类/分类结果的方法。通过观察代表性的样本,我们可以直观得获得其对应族类的样本宏观特征。

比如下图所展示的贝叶斯实例模型(Bayesian Case Model,BCM),我们将样本分成三个组团,可以分别找出每个组团中具有的代表性样例重要的子空间

对于下面第一类聚类来说:

深度学习模型可解释性初探

绿脸是具有代表性的样本;而绿色、方块是具有代表性的特征子空间(包含2个特征维度)。

由此,我们可以得到初步判断,第一类的宏观特征是一堆绿脸方块。
笔者思考:社区发现的聚类中心、Kmeans聚类的聚类质心本质上也可以提供一种基于实例的数据可视化视角。
关于社区发现的相关讨论,可以参阅我的另一篇blog

4. 稀疏性方法(Sparsity)

基于稀疏性的方法主要是利用信息的稀疏性特质,将模型尽可能地简化表示。

比如如下图的一种图稀疏性的 LDA 方法,根据层次性的单词信息形成了层次性的主题表达,这样一些小的主题就可以被更泛化的主题所概括,从而可以使我们更容易理解特定主题所代表的含义。

深度学习模型可解释性初探

5. 单调性方法(Monotonicity)

在很多机器学习问题中,有一些输入和输出之间存在正相关/负相关关系,如果在模型训练中我们可以找出这种单调性的关系就可以让模型具有更高的可解释性。

比如医生对患特定疾病的概率的估计主要由一些跟该疾病相关联的高风险因素决定,找出单调性关系就可以帮助我们识别这些高风险因素。
但是同时另一方面,很多情况下,输入数据的特征空间是高维特征,例如笔者所在的安全领域,抽象后的向量特征常常高达上万,面对这种高维数据,寻找input-ouput之间的线性相关,是非常困难的。

0x3:在建模之后使用可解释性方法对模型作出解释

建模后的可解释性方法主要是针对具有黑箱性质的深度学习模型而言的,主要分为以下几类的工作:

1. 隐层分析方法
2. 模拟/代理模型
3. 敏感性分析方法

Relevant Link:

https://www.leiphone.com/news/201805/Ti3mOdeRlWTplIlZ.html
https://new.qq.com/omn/20180117/20180117A0T4JE.html 

 

4. Lime - Explaining the predictions of any machine learning classifier

Lime可通过可视化的方式向我们展示机器学习决策器是根据哪些“因素”进行了综合决策的。

0x1:Lime的原理

直观上说,Lime采用了局部线性近似来对目标模型进行模拟。虽然目标模型本身可能在全局上非常复杂,但是我们却可以较容易地针对一个特征的局部实例进行近似模拟。

我们将目标模型视作一个黑盒,通过不断的渐进试探以学习到一个低维稀疏的线性模型,作为一个解释。

下图是一个复杂模型的决策函数,蓝色/粉色背景表示两个决策面,很显然,这是非线性的。

图中亮红色的叉叉就是Lime得到的解释实例,我们围绕解释实例 X 进行采样,采样的权重标准就是和 X 的近似程度(在这里就是size),我们随后得到一个线性模型,即虚线。

显然,这是一个局部近似的线性模型,不是全局的。

深度学习模型可解释性初探

0x2:通过Lime解释随机森林的决策因素

# -*- coding: utf-8 -*-

import lime
import sklearn
import numpy as np
import sklearn
import sklearn.ensemble
import sklearn.metrics

# For this tutorial, we'll be using the 20 newsgroups dataset. In particular, for simplicity, we'll use a 2-class subset: atheism and christianity.
from sklearn.datasets import fetch_20newsgroups
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']

# Let's use the tfidf vectorizer, commonly used for text.
vectorizer = sklearn.feature_extraction.text.TfidfVectorizer(lowercase=False)
train_vectors = vectorizer.fit_transform(newsgroups_train.data)
test_vectors = vectorizer.transform(newsgroups_test.data)

# Now, let's say we want to use random forests for classification. It's usually hard to understand what random forests are doing, especially with many trees.
rf = sklearn.ensemble.RandomForestClassifier(n_estimators=500)
rf.fit(train_vectors, newsgroups_train.target)

pred = rf.predict(test_vectors)
res = sklearn.metrics.f1_score(newsgroups_test.target, pred, average='binary')

print res

# Explaining predictions using lime
from lime import lime_text
from sklearn.pipeline import make_pipeline
c = make_pipeline(vectorizer, rf)

print(c.predict_proba([newsgroups_test.data[0]]))

# Now we create an explainer object. We pass the class_names a an argument for prettier display.
from lime.lime_text import LimeTextExplainer
explainer = LimeTextExplainer(class_names=class_names)

# We then generate an explanation with at most 6 features for an arbitrary document in the test set.
idx = 83
exp = explainer.explain_instance(newsgroups_test.data[idx], c.predict_proba, num_features=6)
print('Document id: %d' % idx)
print('Probability(christian) =', c.predict_proba([newsgroups_test.data[idx]])[0,1])
print('True class: %s' % class_names[newsgroups_test.target[idx]])

# The classifier got this example right (it predicted atheism).
# The explanation is presented below as a list of weighted features.
print exp.as_list()

# These weighted features are a linear model, which approximates the behaviour of the random forest classifier in the vicinity of the test example.
# Roughly, if we remove 'Posting' and 'Host' from the document , the prediction should move towards the opposite class (Christianity) by about 0.27 (the sum of the weights for both features).
# Let's see if this is the case.
print('Original prediction:', rf.predict_proba(test_vectors[idx])[0,1])
tmp = test_vectors[idx].copy()
tmp[0,vectorizer.vocabulary_['Posting']] = 0
tmp[0,vectorizer.vocabulary_['Host']] = 0
print('Prediction removing some features:', rf.predict_proba(tmp)[0,1])
print('Difference:', rf.predict_proba(tmp)[0,1] - rf.predict_proba(test_vectors[idx])[0,1])

# The explanations can be returned as a matplotlib barplot:
fig = exp.as_pyplot_figure()

# The explanations can also be exported as an html page (which we can render here in this notebook), using D3.js to render graphs.
exp.show_in_notebook(text=False)

# Alternatively, we can save the fully contained html page to a file:
exp.save_to_file('./oi.html')

# Finally, we can also include a visualization of the original document, with the words in the explanations highlighted. Notice how the words that affect the classifier the most are all in the email header.
exp.show_in_notebook(text=True)

Relevant Link:

https://github.com/marcotcr/lime
https://www.oreilly.com/learning/introduction-to-local-interpretable-model-agnostic-explanations-lime