Yukyukuon / blog

博客的文章
1 stars 0 forks source link

Deep Learning with Python 5-2 #27

Open Yukyukuon opened 1 year ago

Yukyukuon commented 1 year ago

书第5.4 卷积神经网络的可视化

卷积神经网络的可视化

卷积神经网络学到的表示非常适合可视化,很大程度上是因为它们是视觉概念的表示。 可视化的方法:

可视化中间激活

可视化中间激活,是指对于给定输入,展示网络中各个卷积层和池化层输出的特征图(层的输出通常被称为该层的激活,即激活函数的输出)。 三个维度对特征图进行可视化:宽度、高度和深度(通道)

# 导入训练好的模型
import keras
from keras.models import load_model
model = load_model('../model/cats_and_dogs_small_2.h5')

# 输入图片
img_path = "../data/cats_and_dogs_small/test/cats/cat.1500.jpg"
# 图像处理为4D张量
from keras.preprocessing import image
import numpy as np
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)
img_tensor /= 255.  # 训练模型的输入数据都用这种方法预处理

# 显示测试图像
import matplotlib.pyplot as plt
plt.imshow(img_tensor[0])
plt.show()

卷积通道可视化

# 用一个输入张量和一个输出张量列表将模型实例化
from keras import models
layer_outputs = [layer.output for layer in model.layers[:8]]  # 提取前8层的输出
activation_model = models.Model(inputs=model.input, outputs=layer_outputs)  # 给定模型输入,可以返回这些输出

# 返回8 个Numpy数组组成的列表,每个层激活对应一个Numpy 数组
activations = activation_model.predict(img_tensor)

# 将第4 个通道可视化
import matplotlib.pyplot as plt
plt.matshow(first_layer_activation[0, :, :, 3], cmap='viridis')
plt.show()

将每个中间激活的所有通道可视化:

import keras

# 将前8层的层名记录下来,画图时用
layer_names = []
for layer in model.layers[:8]:
    layer_names.append(layer.name)

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]  # 特征图的形状为(1, size, size, n_features)

    n_cols = n_features // imags_per_row  # 将滤镜平铺
    display_grid = np.zeros((size * n_cols, images_per_row * size))

    #将每个过滤器平铺到一个大的水平网格中
    for col in range(n_cols):
        for row in range(imags_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()

6_1

深度神经网络可以有效地作为信息蒸馏管道(information distillation pipeline),输入原始数据,反复对其进行变换,将无关信息过滤掉(比如图像的具体外观),并放大和细化有用的信息(比如图像的类别)。

可视化卷积神经网络的过滤器

在输入空间中进行梯度上升显示每个过滤器所响应的视觉模式。
需要构建一个损失函数,其目的是让某个卷积层的某个过滤器的值最大化;然后,我们要使用随机梯度下降来调节输入图像的值,以便让这个激活值最大化。
对于在ImageNet 上预训练的VGG16网络,其block3_conv1 层第0 个过滤器:

# 为过滤器的可视化定义损失张量
from tensorflow.keras.applications import VGG16
from tensorflow.keras import backend as K
import tensorflow as tf
tf.compat.v1.disable_eager_execution()

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])

# 使用Keras的backend 模块内置的gradients 函数自动做微分
grads = K.gradients(loss, model.input)[0]

# 梯度张量除以其L2 范数(张量中所有值的平方的平均值的平方根)来标准化
grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)

# iterate 是一个函数,它将一个Numpy 张量(表示为长度为1 的张量列表)
# 转换为两个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

得到的图像张量是形状为(1, 150, 150, 3) 的浮点数张量,其取值可能不是[0, 255] 区间内的整数。因此,你需要对这个张量进行后处理,将其转换为可显示的图像:

def deprocess_image(x):
    x -= x.mean()
    x /= (x.std() + 1e-5)
    x *= 0.1

    x += 0.5
    x = np.clip(x, 0, 1)

    x *= 255
    x = np.clip(x, 0, 255).astype('uint8')
    return x/255

生成过滤器可视化的函数:

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))

生成某一层中所有过滤器响应模式组成的网格:

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):
        for j in range(8):
            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)

6_2

卷积神经网络中每一层都学习一组过滤器,以便将其输入表示为过滤器的组合

可视化类激活的热力图

类激活图(CAM,class activation map)可视化,它是指对输入图像生成类激活的热力图。类激活热力图是与特定输出类别相关的二维分数网格,对任何输入图像的每个位置都要进行计算,它表示每个位置对该类别的重要程度。
此处实现的方法是Grad-CAM: visual explanations from deep networks via gradient based localization。给定一张输入图像,对于一个卷积层的输出特征图,用类别相对于通道的梯度对这个特征图中的每个通道进行加权。

预处理图片:

from keras.applications.vgg16 import VGG16
model = VGG16(weights='imagenet')
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input, decode_predictions
import numpy as np
img_path = '../data/images/iceland-1979445__480.jpg'

# 变换形状为 (224, 224, 3) 的float32 格式的Numpy 数组
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)

# CNN需要4维输入添加一个维度,将数组转换为(1, 224, 224, 3) 形状的批量
x = np.expand_dims(x, axis=0)

# 对批量进行预处理(按通道将颜色标准化)
x = preprocess_input(x)

可查看对于图片的预测

preds = model.predict(x)  
print('Predicted:', decode_predictions(preds, top=3)[0])  

# 查看最大预测所在位置
np.argmax(preds[0])  # 这里返回最大预测在279

应用Grad-CAM 算法:

african_elephant_output = model.output[:, 279]  #显示最大预测的热力图
last_conv_layer = model.get_layer('block5_conv3')   # 取出最后一层卷积层

# 279最大预测类别对于最后一层卷积层输出特征图梯度
grads = K.gradients(african_elephant_output, last_conv_layer.output)[0]

# 形状为(512,) 的向量,每个元素是特定特征图通道的梯度平均大小
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])

# 对最后一个卷积层的output的512个滤镜做加权平均
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)

将热力图与原始图像叠加:

import cv2
img = cv2.imread(img_path)

# 将热力图的大小调整为原始图像相同尺寸
heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))

# 将热力图转换为RGB格式
heatmap = np.uint8(255 * heatmap)

# 将热力图应用于原始图像
heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)

superimposed_img = heatmap * 0.4 + img
cv2.imwrite('../data/images/ele.jpg', superimposed_img)