LIME算法:图像分类解释器(代码实现)

在上一篇博客LIME算法:模型的可解释性(代码实现)中,我整理了LIME算法的原理及在文本分类模型中的应用。在这篇笔记中,我记录了LIME算法在图像分类模型中的应用及过程中遇到的问题和解决方法。

一、算法简介

LIME算法是Marco Tulio Ribeiro2016年发表的论文《“Why Should I Trust You?” Explaining the Predictions of Any Classifier》中介绍的局部可解释性模型算法。该算法主要是用在文本类与图像类的模型中。
LIME算法:图像分类解释器(代码实现)
在日常测试图像分类模型时,常常会得到一些莫名其妙的预测结果。我拿我家小猫的照片做测试,得出的预测结果竟然有“纸箱、安全带”这样的分类:
LIME算法:图像分类解释器(代码实现)
我忍不住想知道,我家小猫到底哪部分长得像安全带?而要得到这个答案,就可以利用LIME解释器来帮助解释。

二、LIME解释器代码实现

要实现LIME解释器在图像分类模型中的应用,首先要有一个已经建模完成的图像分类模型,这里参考lime算法的GitHub实例,基于keras框架下载Google Inception net-v3深度神经网络模型。

#加载需要的包
import os
import keras
from keras.applications import inception_v3 as inc_net
from keras.preprocessing import image
from keras.applications.imagenet_utils import decode_predictions
from skimage.io import imread
import matplotlib.pyplot as plt
import numpy as np
print('Notebook run using keras:', keras.__version__)

#下载Google Inception net-v3深度神经网络模型
inet_model = inc_net.InceptionV3()

对待分类图像做数据预处理

def transform_img_fn(path_list):
    out = []
    for img_path in path_list:
        img = image.load_img(img_path, target_size=(299, 299))
        x = image.img_to_array(img)
        x = np.expand_dims(x, axis=0)
        x = inc_net.preprocess_input(x)
        out.append(x)
    return np.vstack(out)

读取图像,输出预测结果。

images = transform_img_fn([os.path.join('./','cat.jpg')])#加载图像后直接进行数据处理
plt.imshow(images[0] / 2 + 0.5)
preds = inet_model.predict(images)
for x in decode_predictions(preds)[0]:
    print(x)#输出预测结果

可以看到输出的预测结果TOP5,分别为埃及猫、猞猁、纸箱、窗口屏幕、安全带。
LIME算法:图像分类解释器(代码实现)

预测结果的最大概率”埃及猫“和实际猫咪是吻合的,但是为什么会预测出纸箱、安全带之类的结果,就让人一头雾水了。这样的情况通过LIME算法是可以得到解答的。

LIME模型的原理是,把原始图片转成可解释的特征表示,通过可解释的特征表示对样本进行扰动,得到N个扰动后的样本。然后再将这N个样本还原到原始特征空间,并把预测值作为真实值,用可解释的特征数据表示建立简单的数据表示,观察哪些超像素的系数较大。这部分的原理解释在上一篇博客中已经进行了详细的解释,这里主要关注代码的实现。

#加载lime包
import lime
from lime import lime_image

建立解释器,explain_instance的参数包括:

image:代解释图像
classifier_fn:分类器
labels:可解释标签
hide_color:隐藏颜色
top_labels:预测概率最高的K个标签生成解释
num_features:说明中出现的最大功能数
num_samples:学习线性模型的领域大小
batch_size:批处理大小
distance_metric:距离度量
model_regressor:模型回归器,默认为岭回归
segmentation_fn:分段,将图像分为多少个大小
random_seed:随机整数,用作分割算法的随机种子

explainer = lime_image.LimeImageExplainer()
x=images[0].astype(np.double) #lime要求numpy array
explanation = explainer.explain_instance(x, inet_model.predict, top_labels=5, hide_color=0, num_samples=1000)

解释器进度条跑完说明解释器运行完成。

LIME算法:图像分类解释器(代码实现)

对图像分类结果进行解释,首先看“埃及猫”的解释。

from skimage.segmentation import mark_boundaries
temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=True, num_features=5, hide_rest=True)
plt.imshow(mark_boundaries(temp / 2 + 0.5, mask))

显示出的图像是对分类结果最有力的部分。

LIME算法:图像分类解释器(代码实现)

可以调整参数hide_rest让图像的其他部分取消隐藏。

temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=True, num_features=5, hide_rest=False)
plt.imshow(mark_boundaries(temp / 2 + 0.5, mask))

LIME算法:图像分类解释器(代码实现)

还可以看到赞成部分和反对部分,赞成为绿色区域,反对为红色区域。

temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=True, num_features=5, hide_rest=False)
plt.imshow(mark_boundaries(temp / 2 + 0.5, mask))

LIME算法:图像分类解释器(代码实现)

以上是对预测结果为“埃及猫”的解释,还可以看看对预测结果为“安全带”的解释。

temp, mask = explanation.get_image_and_mask(explanation.top_labels[4], positive_only=True, num_features=5, hide_rest=True)
plt.imshow(mark_boundaries(temp / 2 + 0.5, mask))

LIME算法:图像分类解释器(代码实现)

可以看到模型对于图像中“不太像猫“的部分作出了”安全带“的分类。

由此,通过LIME算法可以对某张图像分类结果进行可视化的解释,使我们能更直观的看到模型的分类效果。

三、LIME算法的应用条件

在基于keras框架实现LIME算法之前,我是用pytorch自己建了神经网络图像分类模型来试用LIME算法。发现LIME算法中的skimage只能识别的tensor通道顺序为:NHWC,其中:
N:batch
H:height
W:width
C:channel

images = transform_img_fn([os.path.join('./','cat.jpg')])
images.shape

输出结果为:(1,299,299,3)
而pytorch中的tensor通道顺序为:NCHW,即为(1,3,299,299)。
所以如果是在pytorch框架下实现lime解释器,则考虑通道顺序问题,可以先对分类器进行封装。

def batch_model(images):
    model.eval()
    images=torch.FloatTensor(images).permute(0, 3, 1, 2)
    #通过permute改变通道顺序适用于pytorch,也就是(batches, channels, height, width)
    return model(images)

四、参考资料

1、https://github.com/marcotcr/lime

上一篇:LIME算法:模型的可解释性(代码实现)


下一篇:雍禾植发帝之java设计模式学习(一)策略模式