“你的神经网络是如何生成这个结果的?”这个问题也曾让许多数据科学家陷入了困境。其实,让我们去解释一个层数较少的简单神经网络工作原理并不难,但是当我们将计算机视觉项目中的神经网络层数增加到1000层时,它的可解释性就非常差了。
现实情况是,我们的用户或者终端需要可解释性——他们想知道我们的模型是如何得到最终的结果的。但是,深度神经网络的工作原理没有办法通过文字被清楚地描述出来,这个时候深度神经网络就被打上了“黑盒子”的标签,那我们该如何摆脱这个标签呢?
我们可以通过可视化来解决这个问题!当一个神经网络通过可视化的方式展示出来时,他的可解释性将会得到极大的提升,可视化可以将神经网络模型中处理数据的过程清晰展现。尤其当我们处理基于成千上万数据的卷积神经网络(CNN)时更是如此。
在本文中,我们将介绍多种用于可视化卷积神经网络的技术。 此外,我们还可以从这些可视化中加深观察信息,以调整我们的CNN模型。
内容表
1.我们为什么要使用可视化解码神经网络?
2.设置模型的体系结构
3.访问CNN的每个层
4.滤波器—可视化CNN的构成模块
5.最大化激活—可视化模型所期望的内容
6.遮挡贴图—可视化输入中的重要内容
7.显著性贴图—可视化输入特征的贡献
8.分类激活映射
9.可视化分层输出—可视化过程
我们为什么要使用可视化解码神经网络?
除可视化之外,有很多方法可以帮助我们去理解神经网络是如何工作的,那么这篇文章为什么要转向可视化这种非常规途径呢?
我们通过一个例子来回答这个问题。假设我们正在做一个对动物图片进行分类的项目,如分类雪豹与阿拉伯豹。从图片来看,我们可以使用图像背景来区分这两种动物。
两种动物的栖息地可以形成鲜明的对比。大多数雪豹图像的背景里都会有雪,而大多数阿拉伯豹图片里都会有一片茫茫沙漠。
问题来了——模型可以通过分类雪与沙漠的图像从而去分类雪豹与阿拉伯豹。那么,我们如何确保我们的模型正确地学习了这两种不同类型豹子的不同特征呢?可视化会给我们答案。
可视化有助于我们了解哪些特征正在指导模型对图像进行分类的决策。
将模型可视化有许多种方法,在本文中,我们将展示其中的一些。
设置模型的体系结构
实践是最好的学习方式之一。因此,我们立刻开始研究模型的代码。
在本文中,我们在ImageNet数据集上使用VGG16架构模型和预训练权重。首先我们先将模型程序导入并开始理解其架构。
我们将使用Keras中的'model.summary()'函数来可视化模型体系结构。在我们进入模型构建部分之前,这是非常重要的一步。我们需要确保输入和输出形状与我们的问题陈述相匹配,因此我们先可视化模型摘要。
#importing required modules
from keras.applications import VGG16
#loading the saved model
#we are using the complete architecture thus include_top=True
model = VGG16(weights='imagenet',include_top=True)
#show the summary of model
model.summary()
以下是上述代码生成的模型摘要:
我们有了模型的详细架构以及每层的可训练参数的数量。上面的输出可以多花一点时间去浏览,这样才能了解我们现有的数据情况。
我们仅训练模型层的一个子集(特征提取)是很重要的。 我们可以生成模型摘要,并确保不可训练参数的数量与我们不想训练的层匹配。
此外,我们可以使用可训练参数的总数来检查我们的GPU是否能够为训练模型分配足够的内存。 这对于我们大多数在个人电脑上工作的人来说,是一个比较熟悉的挑战!
访问CNN的每个层
既然我们知道如何获得模型的整体架构,让我们深入探索并尝试探索每个独立的层。
实际上,访问Keras模型的各个层并提取与每个层相关的参数非常容易。 这包括图层权重和其他信息,如滤波器的数量。
现在,我们将创建将图层名称映射到其相应特征和图层权重的字典:
#creating a mapping of layer name ot layer details
#we will create a dictionary layers_info which maps a layer name to its charcteristics
layers_info = {}
for i in model.layers:
layers_info[i.name] = i.get_config()
#here the layer_weights dictionary will map every layer_name to its corresponding weights
layer_weights = {}
for i in model.layers:
layer_weights[i.name] = i.get_weights()
print(layers_info['block5_conv1'])
上面的代码给出了以下输出,它由block5_conv1层的不同参数组成:
{'name': 'block5_conv1',
'trainable': True,
'filters': 512,
'kernel_size': (3, 3),
'strides': (1, 1),
'padding': 'same',
'data_format': 'channels_last',
'dilation_rate': (1, 1),
'activation': 'relu',
'use_bias': True,
'kernel_initializer': {'class_name': 'VarianceScaling',
'config': {'scale': 1.0,
'mode': 'fan_avg',
'distribution': 'uniform',
'seed': None}},
'bias_initializer': {'class_name': 'Zeros', 'config': {}},
'kernel_regularizer': None,
'bias_regularizer': None,
'activity_regularizer': None,
'kernel_constraint': None,
'bias_constraint': None}
不知你有没有注意到图层'block5_conv1'的可训练参数是否为真? 这意味着我们可以通过进一步训练模型来更新图层权重。
滤波器—可视化CNN的构成模块
滤波器是任何卷积神经网络的基本构建模块。不同的滤波器从图像中提取不同类型的特征。下面的GIF图非常清楚地说明了这一点:
如图所示,每个卷积层都由多个滤波器组成。查看我们在上一节中生成的输出 - 'block5_conv1'层由512个滤波器组成。这是对应的,是吧?
让我们绘制每个VGG16块的第一个卷积层的第一个滤波器:
layers = model.layers
layer_ids = [1,4,7,11,15]
#plot the filters
fig,ax = plt.subplots(nrows=1,ncols=5)
for i in range(5):
ax[i].imshow(layers[layer_ids[i]].get_weights()[0][:,:,:,0][:,:,0],cmap='gray')
ax[i].set_title('block'+str(i+1))
ax[i].set_xticks([])
ax[i].set_yticks([])
我们可以在上面的输出中看到不同层的滤波器。由于VGG16仅使用3×3滤波器,因此所有滤波器都具有相同的形状。
最大化激活—可视化模型所期望的内容
我们使用下面的图片来理解最大化激活的概念:
你认为对模型去识别大象来说哪些特征是重要的?下面是我想到的一些主要的特征:
那就是我们在本能情况下如何识别大象的,对吧?现在,当我们看看当我们尝试优化任意分类大象图像的模型时,我们在过程中得到了什么。
我们知道CNN中的每个卷积层都在前一层的输出中寻找相似的模式。当输入由它正在寻找的模式组成时,卷积层的激活被最大化。
在最大化激活技术中,我们更新每层的输入,以便将最大化激活的损失降到最低。
我们是如何做到这一点的?我们通过计算相对于输入的激活损失函数的梯度,然后相应得更新输入:
下面是这个操作的代码:
#importing the required modules
from vis.visualization import visualize_activation
from vis.utils import utils
from keras import activations
from keras import applications
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['figure.figsize'] = (18,6)
#creating a VGG16 model using fully connected layers also because then we can
#visualize the patterns for individual category
from keras.applications import VGG16
model = VGG16(weights='imagenet',include_top=True)
#finding out the layer index using layer name
#the find_layer_idx function accepts the model and name of layer as parameters and return the index of respective layer
layer_idx = utils.find_layer_idx(model,'predictions')
#changing the activation of the layer to linear
model.layers[layer_idx].activation = activations.linear
#applying modifications to the model
model = utils.apply_modifications(model)
#Indian elephant
img3 = visualize_activation(model,layer_idx,filter_indices=385,max_iter=5000,verbose=True)
plt.imshow(img3)
我们的模型使用随机输入对印度象进行分类,生成了以下输出:
从上面的图像中,我们可以观察到该模型需要像牙齿,大眼睛和象牙这样的结构。现在,这些信息对于我们检查数据集的完整性非常重要。 因为印度象通常存在于长满树木或长草的栖息地中,所以模型可能会侧重于背景中的栖息地特征,而这是不对的。
可视化输入中的重要内容—Occlusion Maps(遮挡贴图)
激活最大化用于可视化在图像中的模型期望的输出。在另一方面,遮挡部分图,可以帮助我们找到对模型来说那个部分是重要的。
现在,为了了解遮挡图后的模型是如何工作的,我们想根据制造商对汽车进行分类的模型,如丰田,奥迪等:
你能知道是那家生产商制造了上面这辆车吗?很大程度上是不能的,因为放置公司标志的部分在图片里被遮挡了。我们是以分类为目的,所以被遮挡的这部分对我们来说很重要。
类似地,因为这样的遮挡图的出现,我们遮挡图片的某些部分,然后计算出它属于哪一类的概率。如果概率降低,则意味着图像的被遮挡部分对于该类是重要的。否则,这就是不重要的。
这里,我们根据图像每个部分的像素值做概率分配,然后将它标准化并生成热图:
import numpy as np
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Activation, Conv2D, MaxPooling2D
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping, ModelCheckpoint
from keras.preprocessing.image import ImageDataGenerator
from keras.activations import relu
%matplotlib inline
import matplotlib.pyplot as plt
def iter_occlusion(image, size=8):
occlusion = np.full((size * 5, size * 5, 1), [0.5], np.float32)
occlusion_center = np.full((size, size, 1), [0.5], np.float32)
occlusion_padding = size * 2
# print('padding...')
image_padded = np.pad(image, (
(occlusion_padding, occlusion_padding), (occlusion_padding, occlusion_padding), (0, 0) ), 'constant', constant_values = 0.0)
for y in range(occlusion_padding, image.shape[0] + occlusion_padding, size):
for x in range(occlusion_padding, image.shape[1] + occlusion_padding, size):
tmp = image_padded.copy()
tmp[y - occlusion_padding:y + occlusion_center.shape[0] + occlusion_padding, x - occlusion_padding:x + occlusion_center.shape[1] + occlusion_padding]
= occlusion
tmp[y:y + occlusion_center.shape[0], x:x + occlusion_center.shape[1]] = occlusion_center
yield x - occlusion_padding, y - occlusion_padding,
tmp[occlusion_padding:tmp.shape[0] - occlusion_padding, occlusion_padding:tmp.shape[1] - occlusion_padding]
上面的代码定义了一个函数iter_occlusion,它返回一个具有不同被遮挡部分的图像。
现在,让我们导入图像并绘制它:
from keras.preprocessing.image import load_img
# load an image from file
image = load_img('car.jpeg', target_size=(224, 224))
plt.imshow(image)
plt.title('ORIGINAL IMAGE')
现在,我们将进行下面三个步骤:
from keras.preprocessing.image import img_to_array
from keras.applications.vgg16 import preprocess_input
# convert the image pixels to a numpy array
image = img_to_array(image)
# reshape data for the model
image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2]))
# prepare the image for the VGG model
image = preprocess_input(image)
# predict the probability across all output classes
yhat = model.predict(image)
temp = image[0]
print(temp.shape)
heatmap = np.zeros((224,224))
correct_class = np.argmax(yhat)
for n,(x,y,image) in enumerate(iter_occlusion(temp,14)):
heatmap[x:x+14,y:y+14] = model.predict(image.reshape((1, image.shape[0], image.shape[1], image.shape[2])))[0][correct_class]
print(x,y,n,' - ',image.shape)
heatmap1 = heatmap/heatmap.max()
plt.imshow(heatmap)
这非常有意思。 我们现在将使用标准化的热图概率创建一个掩模并绘制它:
import http://skimage.io as io
#creating mask from the standardised heatmap probabilities
mask = heatmap1 < 0.85
mask1 = mask *256
mask = mask.astype(int)
io.imshow(mask,cmap='gray')
最后,我们将遮挡码强加在输入图像上并绘制:
import cv2
#read the image
image = cv2.imread('car.jpeg')
image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
#resize image to appropriate dimensions
image = cv2.resize(image,(224,224))
mask = mask.astype('uint8')
#apply the mask to the image
final = cv2.bitwise_and(image,image,mask = mask)
final = cv2.cvtColor(final,cv2.COLOR_BGR2RGB)
#plot the final image
plt.imshow(final)
你能猜到为什么我们只看到图像的某些部分吗? 这其实是正确的 - 只有输入图像中对其输出类概率有重大贡献的那些部分才是可见的。 简而言之,这就是被遮挡图的全部内容。
可视化输入特征的贡献—显著性贴图
显著性图是另一种基于梯度的可视化技术。
显着图计算每个像素值对模型输出的影响。 这涉及计算输出相对于输入图像的每个像素的梯度。这告诉我们如何根据输入图像像素的微小变化输出类别变化。梯度的所有正值意味着像素值的微小变化将增加输出值:
这些梯度与图像形状相同(梯度是根据每个像素值计算的),为我们提供了直观的重点。让我们看看如何为任何图像生成显著性图。 首先,我们将使用以下代码段读取输入图像。
现在,我们将使用VGG16模型为图像生成显著性图:
# Utility to search for layer index by name.
# Alternatively we can specify this as -1 since it corresponds to the last layer.
layer_idx = utils.find_layer_idx(model, 'predictions')
# Swap softmax with linear
model.layers[layer_idx].activation = activations.linear
model = utils.apply_modifications(model)
#generating saliency map with unguided backprop
grads1 = visualize_saliency(model, layer_idx,filter_indices=None,seed_input=image)
#plotting the unguided saliency map
plt.imshow(grads1,cmap='jet')
我们看到该模型更侧重于狗的面部部分。现在,让我们看看反向传播的结果:
#generating saliency map with guided backprop
grads2 = visualize_saliency(model, layer_idx,filter_indices=None,seed_input=image,backprop_modifier='guided')
#plotting the saliency map as heatmap
plt.imshow(grads2,cmap='jet')
引导反向传播将所有负梯度截断为0,这意味着仅更新对分类概率具有正影响的像素。
分类激活图(梯度加权)
分类激活图也是一种神经网络可视化技术,它基于根据激活图的梯度或它们对输出的贡献来权衡激活图这样的想法。
以下摘自Grad-CAM论文给出了该技术的要点:
梯度加权分类激活映射(Grad-CAM),使用任何目标概念的梯度(比如“狗”或甚至是标题的对数),流入最终的卷积层以生成粗略的定位图,突出显示重要区域中的重要区域。用于预测概念的图像。
从本质上来说,我们采用最后一层卷积层的特征映射,并使用相对于特征映射的输出的梯度对每个滤波器进行加权(乘)。 Grad-CAM涉及以下步骤:
1.获取最终卷积层的输出要素图。对于VGG16,此功能图的形状为14x14x512; 2.计算输出相对于要素图的梯度 3.将全局平均池化应用于梯度 4.将要素图与相应的池化梯度相乘 我们可以在下面看到输入图像及其对应的分类激活图:
现在,我们来给上面的图像生成分类激活图:
可视化过程—分层输出可视化
CNN的起始层通常寻找像边缘这样的低级特征。随着我们的深入,功能也会发生变化。
可视化模型的不同层的输出有助于我们看到在相应层突出显示图像的是哪些特征。此步骤对于针对我们的问题微调架构特别重要。为什么?因为我们可以看到哪些图层提供了哪种特征,然后决定我们要在模型中使用哪些图层。
例如,可视化图层输出可以帮助我们比较神经样式转移问题中不同层的性能。
让我们看看如何在VGG16模型的不同层获得输出:
#importing required libraries and functions
from keras.models import Model
#defining names of layers from which we will take the output
layer_names = ['block1_conv1','block2_conv1','block3_conv1','block4_conv2']
outputs = []
image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2]))
#extracting the output and appending to outputs
for layer_name in layer_names:
intermediate_layer_model = Model(inputs=model.input,outputs=model.get_layer(layer_name).output)
intermediate_output = intermediate_layer_model.predict(image)
outputs.append(intermediate_output)
#plotting the outputs
fig,ax = plt.subplots(nrows=4,ncols=5,figsize=(20,20))
for i in range(4):
for z in range(5):
ax[i][z].imshow(outputs[i][0,:,:,z])
ax[i][z].set_title(layer_names[i])
ax[i][z].set_xticks([])
ax[i][z].set_yticks([])
plt.savefig('layerwise_output.jpg')
上图显示了VGG16的每一层从图像中提取的不同特征(模块5除外)。我们可以看到起始层对应于边缘等低级特征,而后面的层则看到汽车的车顶,排气等特征。
结语
可视化永远不会让我感到惊讶。有多种方法可以理解技术的工作原理,但可视化可以使它变得更加有趣。以下是您应该查看的几个资源:
如果您对本文有任何疑问或反馈,请与我们联系。 我很乐意参与讨论!
数据分析咨询请扫描二维码
若不方便扫码,搜微信号:CDAshujufenxi
在数据科学的广阔领域中,统计分析与数据挖掘占据了重要位置。尽管它们常常被视为有关联的领域,但两者在理论基础、目标、方法及 ...
2025-02-05在数据分析的世界里,“对比”是一种简单且有效的方法。这就像两个女孩子穿同一款式的衣服,效果不一样。 很多人都听过“货比三 ...
2025-02-05当我们只有非常少量的已标记数据,同时有大量未标记数据点时,可以使用半监督学习算法来处理。在sklearn中,基于图算法的半监督 ...
2025-02-05考虑一种棘手的情况:训练数据中大部分样本没有标签。此时,我们可以考虑使用半监督学习方法来处理。半监督学习能够利用这些额 ...
2025-02-04一、数学函数 1、取整 =INT(数字) 2、求余数 =MOD(除数,被除数) 3、四舍五入 =ROUND(数字,保留小数位数) 4、取绝对值 =AB ...
2025-02-03作者:CDA持证人 余治国 一般各平台出薪资报告,都会哀嚎遍野。举个例子,去年某招聘平台发布《中国女性职场现状调查报告》, ...
2025-02-02真正的数据分析大神是什么样的呢?有人认为他们能轻松驾驭各种分析工具,能够从海量数据中找到潜在关联,或者一眼识别报告中的数 ...
2025-02-01现今社会,“转行”似乎成无数职场人无法回避的话题。但行业就像座围城:外行人看光鲜,内行人看心酸。数据分析这个行业,近几年 ...
2025-01-31本人基本情况: 学校及专业:厦门大学经济学院应用统计 实习经历:快手数据分析、字节数据分析、百度数据分析 Offer情况:北京 ...
2025-01-3001专家简介 徐杨老师,CDA数据科学研究院教研副总监,主要负责CDA认证项目以及机器学习/人工智能类课程的研发与授课,负责过中 ...
2025-01-29持证人简介 郭畅,CDA数据分析师二级持证人,安徽大学毕业,目前就职于徽商银行总行大数据部,两年工作经验,主要参与两项跨部 ...
2025-01-282025年刚开启,知乎上就出现了一个热帖: 2024年突然出现的经济下行,使各行各业都感觉到压力山大。有人说,大环境越来越不好了 ...
2025-01-27在数据分析的世界里,“对比”是一种简单且有效的方法。这就像两个女孩子穿同一款式的衣服,效果不一样。 很多人都听过“货比三 ...
2025-01-26数据指标体系 “数据为王”相信大家都听说过。当前,数据信息不再仅仅是传递的媒介,它成为了驱动经济发展的新燃料。对于企业而 ...
2025-01-26在职场中,当你遇到问题的时候,如果感到无从下手,或者抓不到重点,可能是因为你掌握的思维模型不够多。 一个好用的思维模型, ...
2025-01-25俗话说的好“文不如表,表不如图”,图的信息传达效率很高,是数据汇报、数据展示的重要手段。好的数据展示不仅需要有图,还要选 ...
2025-01-24数据分析报告至关重要 一份高质量的数据分析报告不仅能够揭示数据背后的真相,还能为企业决策者提供有价值的洞察和建议。 年薪70 ...
2025-01-24又到一年年终时,各位打工人也迎来了展示成果的关键时刻 —— 年终述职。一份出色的年终述职报告,不仅能全面呈现你的工作价值, ...
2025-01-23“用户旅程分析”概念 用户旅程图又叫做用户体验地图,它是用于描述用户在与产品或服务互动的过程中所经历的各个阶段、触点和情 ...
2025-01-22在竞争激烈的商业世界中,竞品分析对于企业的发展至关重要。今天,我们就来详细聊聊数据分析师写竞品分析的那些事儿。 一、明确 ...
2025-01-22