LIME:为什么我应该相信你?解释任何模型的预测结果

LIME:为什么我应该相信你?解释任何模型的预测结果

  • 概述
    • 简介
    • 案例解释
    • LIME原理解释
      • Step 1 围绕待解释样本随机生成样本
      • Step 2 对随机样本标准化,以及利用模型对标准化数据进行预测
      • Step 3 特征选择
    • Step 4 特征贡献值确定
    • 总结

Github 项目链接:https://github.com/marcotcr/lime
参考链接:
LIME - Local Interpretable Model-Agnostic Explanations
LIME:一种解释机器学习模型的方法
LIME:模型预测结果是否值得信任?
论文参考链接:https://arxiv.org/pdf/1602.04938v1.pdf
责编:Adam(投稿请联系[email protected]

概述

机器学习在互联网大公司中已经被广泛应用。在不久的未来,也必将被大部分互联网公司应用。所以,当部署一个最新模型的时候,利用它在实际业务中进行预测的同时,理解它预测的底层缘由,从而评估它预测的可信度是至关重要的。

简介

机器学习在近几年计算机科学进步中起到了决定性的作用,不幸的的是,人,这个重要的角色经常在这个领域被忽视掉了。不管人是否是直接用机器学习分类器作为一个工具,又或者是部署到其它的产品中。一个重要的问题是:如果用户不相信这个模型或预测,用户肯定不会用它。值得注意的是,虽然相信模型和相信预测有一定的关联性,但是它们是两个不同的相信的定义。(1)相信预测,比如:用户会基于相信的预测,而采取一些行动。(2)相信模型,比如:用户是否相信这个模型表现合理。相较于模型对于用户是黑盒的状态而言,两种相信都直接受用户对模型行为了解的程度影响。首先针对于第一种相信预测,LIME为每个单独的预测结果提供了解释。其次,LIME选择了多个预测结果和预测解释来解决相信模型的问题。
LIME, 是一种可以为任何分类器和回归器提供合理解释的算法。其主要实现逻辑是:通过一个可解释性的模型来进行局部拟合这个模型。

案例解释

为了让用户能够有效相信和利用机器学习,LIME提供了一个可视化的HTML格式展示界面给用户,让用户能够对模型预测结果和各大特征之间有一个量化的理解。何谓量化理解?通俗的讲,既然你模型认定它属于某类,那么模型是通过这个案例的哪些特征来认定它属于这个类别的呢?并且,这些特征给模型提供了多大的确信度让模型更加确信的认定它属于这类的呢?举个例子:模型对猫和狗图片的分类问题,模型判定这张图片是猫,模型是如何判定的呢?模型可能是通过观察图片中动物的毛色,动物的眼睛,动物的头型等等这些特征,从而最后判定这张图片是猫不是狗。那么,这些特征分别对模型判定图片为猫的确信度是增加了还是减少了呢。这就是特征贡献值的作用【量化】,当特征贡献值>0的时候,我们就认为它对模型认为这张图片为猫的确信度增加了,至于增加的程度,就是通过特征贡献值来衡量。反之,如果贡献值<0,这个特征就是让模型认定这张图片为猫的确信度减少了,至于减少多少,这个当然也就是特征贡献值的绝对值来衡量了。接下来我通过一个demo案例来解释模型的预测结果和各大特征之间的量化理解:LIME:为什么我应该相信你?解释任何模型的预测结果_第1张图片
上图为测试集合中,随机生成的一个测试案例的预测结果和LIME对该预测结果的解释。这个测试案例被预测为山鸢尾。LIME提供的解释是:模型判定它为山鸢尾时,观察了这个案例【这儿是花】四个方面的特征:花瓣宽度,花瓣长度,花萼宽度以及花萼长度。并且这些特征都对这个模型判定这个案例为山鸢尾起到了正向贡献【即:增加了模型判定它为山鸢尾的确信度】。其中,花瓣宽度,花瓣长度起到的贡献最大,分别为0.46和0.42。

LIME原理解释

LIME是如何判定这个某个案例属于某一类的呢?接下来,我们通过代码来一步步深入了解它实现的逻辑:

Step 1 围绕待解释样本随机生成样本

下图中,黄框1中的代码,LIME随机生成一个以标准差为1,均值为0,shape为[num_samples, num_features]的ndarray格式数据。其中,num_samples默认为5000,也可以外部函数调用指定。num_features是要解释样本的特征数目。然后,接下来,黄框2将这些数据做了一个反标准化操作。什么意思呢?通俗的解释一下,LIME在原始数据【如果sample_around_instance=True, 就是待解释的样本数据;否则,为训练样本的(均值,标准差)数据】周围随机生成了num_samples个符合标准高斯分布的数据。黄框3中,将待解释样本数据替代随机生成的样本数据中的第一个样本数据。
LIME:为什么我应该相信你?解释任何模型的预测结果_第2张图片

Step 2 对随机样本标准化,以及利用模型对标准化数据进行预测

对基于Step1随机生成的num_samples-1个样本和1个待解释样本进行基于训练样本数据的均值和标准差对数据进行标准化操作【注意:step1中的黄框2不仅仅基于所有训练样本的均值和标准差做的反标准化的操作,还有基于待解释样本的反标准化操作。所以,这儿好像是有点问题,这也是本人提pr的缘由。如您有更好的见解,望收到您的邮件,或底下留言,谢谢.】。然后,挨个计算新产生样本和带解释样本的欧式距离,得到结果向量D。最后,利用模型对标准化样本数据进行预测, 得到预测结果yss【shape为[num_samples, num_labels]】。通俗的理解,每个样本对应每个类别都有一个预测值。由于本人研究期间只想获取待解释样本的所属类的特征贡献解释,所以,接下来,本人默认都用待解释样本所属类对应预测值进行展开。

scaled_data = (data - self.scaler.mean_) / self.scaler.scale_

distances = sklearn.metrics.pairwise_distances(
        scaled_data,
        scaled_data[0].reshape(1, -1),
        metric=distance_metric
).ravel()

yss = predict_fn(inverse)

Step 3 特征选择

利用Step 2生成的距离向量D,通过核函数f(x)【这儿我贴出来的核函数是lime_tabular.py对应的核函数】, 挨个计算每个样本相对于待解释样本的权重值Wi【原始样本相对于原始样本距离为0,权重为1】,其中Wi∈(0, 1],得到权重矩阵W。然后,利用Step 2产生的对应类别预测值和权重W进行指定个数【记作n】的特征选择。

if kernel_width is None:
            kernel_width = np.sqrt(training_data.shape[1]) * .75
        kernel_width = float(kernel_width)

def kernel_fn(d):
    return np.sqrt(np.exp(-(d ** 2) / kernel_width ** 2))
weights = self.kernel_fn(distances)
labels_column = neighborhood_labels[:, label]
used_features = self.feature_selection(neighborhood_data,
                                       labels_column,
                                       weights,
                                       num_features,
                                       feature_selection)
  • 特征选择

特征选择方法默认为auto,根据以下代码我们可以知道auto的选择思想是根据特征的个数来决定是用贪心算法的forward_selection或者用highest_weights来进行,如果用none的话,那就直接选择所有的特征

forward_selection

  1. 假设特征集合为list=[]
  2. 遍历所有特征,利用num_samples个新生成样本和list中的特征+一个特征进行训练,得到一个岭回归模型记作M
  3. 计算M的拟合系数R2 Score,比较获取所有特征所对应的R2 Score,保留其中R2 Score最大所对应的特征f1至list中。
  4. 重复2.3步骤,直至获取到想要特征数目

highest_weights 类似PCA算法,利用num_samples个新生成样本集中所有特征进行训练,得到一个岭回归模型记作M1,然后获取M1中系数最大的前n个特征。

lasso_path 通过构建lasso回归对特征进行稀疏化处理,获取特征值为非0的特征。

注意:无论是forward_selection, 还是highest_weights,在进行训练岭回归模型时,由于会对数据进行标准化处理,所以会利用权重矩阵W来计算加权平均值,进而利用标准化后的数据进行训练岭回归模型。

def feature_selection(self, data, labels, weights, num_features, method):
       """Selects features for the model. see explain_instance_with_data to
          understand the parameters."""
       if method == 'none':
           return np.array(range(data.shape[1]))
       elif method == 'forward_selection':
           return self.forward_selection(data, labels, weights, num_features)
       elif method == 'highest_weights':
           clf = Ridge(alpha=0, fit_intercept=True,
                       random_state=self.random_state)
           clf.fit(data, labels, sample_weight=weights)
           feature_weights = sorted(zip(range(data.shape[0]),
                                        clf.coef_ * data[0]),
                                    key=lambda x: np.abs(x[1]),
                                    reverse=True)
           return np.array([x[0] for x in feature_weights[:num_features]])
       elif method == 'lasso_path':
           weighted_data = ((data - np.average(data, axis=0, weights=weights))
                            * np.sqrt(weights[:, np.newaxis]))
           weighted_labels = ((labels - np.average(labels, weights=weights))
                              * np.sqrt(weights))
           nonzero = range(weighted_data.shape[1])
           _, coefs = self.generate_lars_path(weighted_data,
                                              weighted_labels)
           for i in range(len(coefs.T) - 1, 0, -1):
               nonzero = coefs.T[i].nonzero()[0]
               if len(nonzero) <= num_features:
                   break
           used_features = nonzero
           return used_features
       elif method == 'auto':
           if num_features <= 6:
               n_method = 'forward_selection'
           else:
               n_method = 'highest_weights'
           return self.feature_selection(data, labels, weights,
                                         num_features, n_method)

Step 4 特征贡献值确定

利用Step 3得到的特征,再次进行训练新生成数据得到一个岭回归模型记作M_final。最后,M_final中的系数即为每个特征所对应的特征贡献值。

if model_regressor is None:
	model_regressor = Ridge(alpha=1, fit_intercept=True,
	                        random_state=self.random_state)
easy_model = model_regressor
easy_model.fit(neighborhood_data[:, used_features],
           labels_column, sample_weight=weights)
prediction_score = easy_model.score(
neighborhood_data[:, used_features],
labels_column, sample_weight=weights)

local_pred = easy_model.predict(neighborhood_data[0, used_features].reshape(1, -1))

if self.verbose:
	print('Intercept', easy_model.intercept_)
	print('Prediction_local', local_pred,)
	print('Right:', neighborhood_labels[0, label])
return (easy_model.intercept_,
    sorted(zip(used_features, easy_model.coef_),
           key=lambda x: np.abs(x[1]), reverse=True),
    prediction_score, local_pred)

总结

相较于晦涩难懂的论文,代码更加易懂。虽然LIME在我个人实践中特征和特征值整体表现效果还是稳定的,但LIME通过随机生成的样本来对单个样本进行解释导致每回所获的得可解释性特征和特征值都会有些许差异。这点个人还是保留疑惑。

注:如有不正之处,欢迎留言评论或发邮件指正,谢谢

你可能感兴趣的:(AI,机器学习)