5.4卷积神经网络可视化
三种可视化方法:
- 可视化卷积神经网络的中间输出(中间激活)
- 可视化卷积神经网络的过滤器
- 可视化图像中类激活的热力图
可视化中间激活是指对于给定的输入,展示网络中各个卷积层和池化层输出的特征图(层的输出通常被称为该层的激活,即激活函数的输出)。下面在三个维度进行可视化:宽度、高度和深度(通道)。每个通道对应相对独立的特征,所以将这些特征图可视化的正确方法是将每个通道的内容分别绘制成二维图像。
# 加载5.2中保存的模型
from keras.models import load_model
model = load_model('cats_and_dogs_small_2.h5')
model.summary()
结果如下:
#预处理单张图像
img_path = r'D:\ALL_code\Anaconda\Deep_Learning_Study\chapter5\kaggle_original_data\cats_and_dogs_small\test\cats\cat.1700.jpg'
from keras.preprocessing import image
import numpy as np
#将图像预处理为一个4D张量
img = image.load_img(img_path, target_size=(150, 150))
img_tensor = image.img_to_array(img) #将图片变成数字
img_tensor = np.expand_dims(img_tensor, axis=0) #expand是扩展张量维度,加一个第0轴
img_tensor /= 255. #训练模型的输入数据都用这种方法
#其形状为(1, 150, 150, 3)
print(img_tensor.shape)
输出:(1, 150, 150, 3)
如果想看看img_tensor的具体样子,可以实用下面的代码,注意#是必须的
img_tensor # #可以调出查看img_tensor的具体样子
#显示测试图像
import matplotlib.pyplot as plt
plt.imshow(img_tensor[0])
plt.show()
图像批量作为输入,并输出所有卷积层和池化层的激活。使用keras的model模型,需要一个输入张量和一个输出张量,得到的类是个keras模型。
#用一个输入张量和一个输出张量列表将模型实例化
from keras import models
#提取前8层的模型输入,flatten前的8层
layer_outputs = [layer.output for layer in model.layers[:8]]
#创建一个模型,给定模型输入,可以返回输出
activation_model = models.Model(inputs=model.input, outputs=layer_outputs)
#以预测模式运行模型
activations = activation_model.predict(img_tensor)
#例如对于输入猫的图像,第一个卷积层的激活如下
first_layer_activation = activations[0]
print(first_layer_activation.shape)
(1, 148, 148, 32)
#将第4通道可视化
import matplotlib.pyplot as plt
plt.matshow(first_layer_activation[0, :, :, 4], cmap='viridis')
#将第7通道可视化
plt.matshow(first_layer_activation[0, :, :,7], cmap='viridis')
#将每个中间激活的所有通道可视化
layer_names = []
for layer in model.layers[:8]:
layer_names.append(layer.name)
#每一列画16个图
images_per_row = 16
for layer_name, layer_activation in zip(layer_names, activations):
n_features = layer_activation.shape[-1]#表示每一层特征图中特征个数
size = layer_activation.shape[1]
#在这个矩阵中将激活通道铺平
n_cols = n_features // images_per_row
display_grid = np.zeros((size * n_cols, images_per_row *size))
#将每个过滤器平铺到一个大的水平网格中
for col in range(n_cols):
for row in range(images_per_row):
channel_image = layer_activation[0,
:, :,
col * images_per_row + row]
channel_image -= channel_image.mean()
channel_image /= channel_image.std()
channel_image *= 64
channel_image += 128
channel_image = np.clip(channel_image, 0, 255).astype('uint8')
display_grid[col * size : (col + 1) *size,
row * size : (row + 1) *size] = channel_image
scale = 1./size
plt.figure(figsize=(scale * display_grid.shape[1],
scale * display_grid.shape[0]))
plt.title(layer_name)
plt.grid(False)
plt.imshow(display_grid, aspect='auto', cmap='viridis')
plt.show()
可视化卷积神经网络的过滤器
输入空间中进行梯度上升实现:从空白输入图像开始,将梯度下降应用于卷积神经网络输入图像的值,其目的是让某个过滤器的响应最大化。
构建一个损失函数,其目的是让某个卷积层的某个过滤器的最值最大化;然后,使用随机梯度下降来调节输入图像的值,以便让这个激活值最大化。
# 为过滤器的可视化定义损失张量
from keras.applications import VGG16
from keras import backend as K#后端运算命名为K
model = VGG16(weights='imagenet',
include_top=False)
layer_name = 'block3_conv1'
filter_index = 0
layer_output = model.get_layer(layer_name).output
loss = K.mean(layer_output[:, :, :, filter_index])
#获取损失相对于输入的梯度
grads = K.gradients(loss, model.input)[0]
#梯度标准化技巧
grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)
# 给定numpy输入值,得到Numpy输出值
iterate = K.function([model.input], [loss, grads])
import numpy as np
loss_value, grads_value = iterate([np.zeros((1, 150, 150, 3))])
#通过随机梯度下降让损失最大化
input_img_data = np.random.random((1, 150, 150, 3)) * 20 + 128.
step = 1.#每次梯度更新的步长
for i in range(40):
#计算损失值和梯度值
loss_value, grads_value = iterate([input_img_data])
#沿着让损失最大化的方向调节输入图像
input_img_data += grads_value * step
# 将张量转换为有效图像的实用函数
def deprocess_image(x):
#对张量进行标准化,使均值变为0,标准差变成0.1
x -= x.mean()
x /= (x.std() + 1e-5)
x *= 0.1
#将x裁剪成0~1区间内
x += 0.5
x = np.clip(x, 0, 1)
#将x转化为RGB数组
x *= 255
x = np.clip(x, 0, 255).astype('uint8')
return x
#生成过滤器可视化函数
def generate_pattern(layer_name, filter_index, size=150):
layer_output = model.get_layer(layer_name).output
loss = K.mean(layer_output[:, :, :, filter_index])
grads = K.gradients(loss, model.input)[0]
grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)
iterate = K.function([model.input], [loss, grads])
input_img_data = np.random.random((1, size, size, 3)) * 20 +128.
step = 1.
for i in range(40):
loss_value, grads_value = iterate([input_img_data])
input_img_data += grads_value * step
img = input_img_data[0]
return deprocess_image(img)
plt.imshow(generate_pattern('block3_conv1', 0).astype(np.uint8))
# 生成某一层中所有过滤器响应模式组成的网络
for layer_name in ['block1_conv1', 'block2_conv1', 'block3_conv1', 'block4_conv1']:
size = 64
margin = 5
results = np.zeros((8*size + 7 * margin,8*size +7*margin,3)) # 空图像,用于保存结果
for i in range(8):#遍历results的行
for j in range(8): #遍历results的列
filter_img = generate_pattern(layer_name,i+(j*8),size = size)
horizontal_start = i* size + i *margin
horizontal_end = horizontal_start + size
vertical_start = j * size + j * margin
vertical_end = vertical_start + size
results[horizontal_start:horizontal_end,
vertical_start:vertical_end,:] = filter_img
plt.figure(figsize = (20,20))
plt.imshow(results.astype(np.uint8))
plt.show()
如果在imshow()中不加入astype(np.uint8)则不会显示有颜色的图案,会显示白色图案。如下图所示:
如下是上面代码的输出图像:
这个图像是block3_conv1层第0个通道的的过滤器响应,图像是波尔卡点。
下面是所有过滤器的模式图:
block1_conv1层的过滤器模式
block2_conv1层的过滤器模式图
block3_conv1层的过滤器模式图
block4_conv1层的过滤器模式图
模型第一层(block1_conv1)的过滤器对应简单的方向边缘和颜色(有些是彩色边缘)。
block2_conv1层的过滤器对应边缘和颜色组合而成的简单纹理。
更高层的过滤器类似于自然图像中的纹理:羽毛、眼睛、树叶等。
可视化类激活的热力图:
有助于了解一张图片的哪一部分让卷积神经网络做出了最终的分类决策。还可以定位图像中的特定目标。这种通用技术叫做类激活图可视化,是指对输入图像生成类激活的热力图。
from keras.applications.vgg16 import VGG16
#注意:网络中包括了密集连接分类器。在前面所有例子中,我们都舍弃了这个分类器
model=VGG16(weights='imagenet')
# 为VGG16模型预处理一张输入图像
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input, decode_predictions
import numpy as np
img_path = r'D:\ALL_code\Anaconda\Deep_Learning_Study\chapter5\creative_commons_elephant.jpg'
#大小为224*224的Python图像库图像
img = image.load_img(img_path, target_size=(224, 224))
#形状为(224, 224, 3)的float32格式Numpy数组
x = image.img_to_array(img)
#添加一个维度,将数组转换为(1, 224, 224, 3)形状批量
x = np.expand_dims(x, axis=0)
#对批量进行预处理(按通道进行颜色标准化)具体方法:
#将每个通道的RGB变成BGR再减去BGR的平均数
x = preprocess_input(x)
preds = model.predict(x)
print('Predicted: ', decode_predictions(preds, top=3)[0])
Predicted: [(‘n02504458’, ‘African_elephant’, 0.90942144), (‘n01871265’, ‘tusker’, 0.08618243), (‘n02504013’, ‘Indian_elephant’, 0.0043545929)]
对这个图像预测的前三个类别分别是:
- 非洲象:92.5%的概率
- 长牙动物:7%的概率
- 印度象:0.4%的概率
网络认为预测向量中最大激活的元素对应是“非洲象”类别的元素,索引编号386
np.argmax(preds[0])
Out: 386
#使用Grad-CAM算法
african_elephant_output = model.output[:, 386]
last_conv_layer = model.get_layer('block5_conv3')
grads = K.gradients(african_elephant_output, last_conv_layer.output)[0]
pooled_grads = K.mean(grads, axis=(0, 1, 2))
iterate = K.function([model.input],
[pooled_grads, last_conv_layer.output[0]])
pooled_grads_value, conv_layer_output_value = iterate([x])
for i in range(512):
conv_layer_output_value[:, :, i] *= pooled_grads_value[i]
heatmap = np.mean(conv_layer_output_value, axis=-1)
heatmap = np.maximum(heatmap, 0)
heatmap /= np.max(heatmap)
plt.matshow(heatmap)
plt.show()
热处理后的图像:
#热力图与原始图像叠加
import cv2
img = cv2.imread(img_path)
heatmap = cv2.resize(heapmap, (img.shape[1], img.shape[0]))
heatmap = np.uint8(255 * heatmap)
heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
superimposed_img = heatmap * 0.4 + img
cv2.imwrite(r'D:\ALL_code\Anaconda\Deep_Learning_Study\chapter5')# 将图像保存到本地硬盘
这种可视化回答了两个重要问题:
(1)网络为什么会认为这张图像包含一头非洲象
(2)非洲象在图中什么位置。
这个我发现一个特别好的视频讲解:https://www.bilibili.com/video/BV1fK4y147rp?p=32&share_source=copy_web,可以关注一下。