23.7 卷积滤波器可视化
优质
小牛编辑
174浏览
2023-12-01
该脚本可以在几分钟内在 CPU 上运行完。
结果示例:
from __future__ import print_function
import time
import numpy as np
from PIL import Image as pil_image
from keras.preprocessing.image import save_img
from keras import layers
from keras.applications import vgg16
from keras import backend as K
def normalize(x):
"""用于标准化张量的实用函数。
# 参数
x: 输入张量。
# 返回
标准化的输入张量。
"""
return x / (K.sqrt(K.mean(K.square(x))) + K.epsilon())
def deprocess_image(x):
"""用于将 float 数组转换为有效 uint8 图像的实用函数。
# 参数
x: 表示生成图像的 numpy 数组。
# 返回
经处理的 numpy 阵列,可用于 imshow 等。
"""
# 标准化张量: center 为 0., 保证 std 为 0.25
x -= x.mean()
x /= (x.std() + K.epsilon())
x *= 0.25
# 裁剪为 [0, 1]
x += 0.5
x = np.clip(x, 0, 1)
# 转换为 RGB 数组
x *= 255
if K.image_data_format() == 'channels_first':
x = x.transpose((1, 2, 0))
x = np.clip(x, 0, 255).astype('uint8')
return x
def process_image(x, former):
"""用于将 float 数组转换为有效 uint8 图像转换回 float 数组的实用函数。
`deprocess_image` 反向操作。
# 参数
x: numpy 数组,可用于 imshow 等。
former: 前身 numpy 数组,
需要确定前者的均值和方差。
# 返回
一个处理后的 numpy 数组,表示一幅生成图像。
"""
if K.image_data_format() == 'channels_first':
x = x.transpose((2, 0, 1))
return (x / 255 - 0.5) * 4 * former.std() + former.mean()
def visualize_layer(model,
layer_name,
step=1.,
epochs=15,
upscaling_steps=9,
upscaling_factor=1.2,
output_dim=(412, 412),
filter_range=(0, None)):
"""可视化某个模型中一个转换层的最相关过滤器。
# 参数
model: 包含 layer_name 的模型。
layer_name: 需要可视化的层的名称。
必须是模型的一部分。
step: 梯度提升步长。
epochs: 梯度提升迭代轮次。
upscaling_steps: upscaling 步数。
起始图像为 (80, 80)。
upscaling_factor: 将图像缓慢提升到 output_dim 的因子。
output_dim: [img_width, img_height] 输出图像维度。
filter_range: 元组 [lower, upper]
决定需要计算的过滤器数目。
如果第二个值为 `None`,
最后一个过滤器将被推断为上边界。
"""
def _generate_filter_image(input_img,
layer_output,
filter_index):
"""为一个特定的过滤器生成图像。
# 参数
input_img: 输入图像张量。
layer_output: 输出图像张量。
filter_index: 需要处理的过滤器数目。
假设可用。
# 返回
要么是 None,如果无法生成图像。
要么是图像(数组)本身以及最后的 loss 组成的元组。
"""
s_time = time.time()
# 构建一个损失函数,使所考虑的层的第 n 个过滤器的激活最大化
if K.image_data_format() == 'channels_first':
loss = K.mean(layer_output[:, filter_index, :, :])
else:
loss = K.mean(layer_output[:, :, :, filter_index])
# 计算这种损失的输入图像的梯度
grads = K.gradients(loss, input_img)[0]
# 标准化技巧:将梯度标准化
grads = normalize(grads)
# 此函数返回给定输入图片的损失和梯度
iterate = K.function([input_img], [loss, grads])
# 从带有一些随机噪音的灰色图像开始
intermediate_dim = tuple(
int(x / (upscaling_factor ** upscaling_steps)) for x in output_dim)
if K.image_data_format() == 'channels_first':
input_img_data = np.random.random(
(1, 3, intermediate_dim[0], intermediate_dim[1]))
else:
input_img_data = np.random.random(
(1, intermediate_dim[0], intermediate_dim[1], 3))
input_img_data = (input_img_data - 0.5) * 20 + 128
# 缓慢放大原始图像的尺寸可以防止可视化结构的主导高频现象发生
# (如果我们直接计算 412d-image 时该现象就会发生。)
# 作为每个后续维度的更好起点,因此它避免了较差的局部最小值
for up in reversed(range(upscaling_steps)):
# 执行 20 次梯度提升
for _ in range(epochs):
loss_value, grads_value = iterate([input_img_data])
input_img_data += grads_value * step
# s一些过滤器被卡在了 0,我们可以跳过它们
if loss_value <= K.epsilon():
return None
# 计算放大维度
intermediate_dim = tuple(
int(x / (upscaling_factor ** up)) for x in output_dim)
# 放大
img = deprocess_image(input_img_data[0])
img = np.array(pil_image.fromarray(img).resize(intermediate_dim,
pil_image.BICUBIC))
input_img_data = np.expand_dims(
process_image(img, input_img_data[0]), 0)
# 解码生成的输入图像
img = deprocess_image(input_img_data[0])
e_time = time.time()
print('Costs of filter {:3}: {:5.0f} ( {:4.2f}s )'.format(filter_index,
loss_value,
e_time - s_time))
return img, loss_value
def _draw_filters(filters, n=None):
"""在 nxn 网格中绘制最佳过滤器。
# 参数
filters: 每个已处理过滤器的生成图像及其相应的损失的列表。
n: 网格维度。
如果为 None,将使用最大可能的方格
"""
if n is None:
n = int(np.floor(np.sqrt(len(filters))))
# 假设损失最大的过滤器看起来更好看。
# 我们只保留顶部 n*n 过滤器。
filters.sort(key=lambda x: x[1], reverse=True)
filters = filters[:n * n]
# 构建一个有足够空间的黑色图像
# 例如,8 x 8 个过滤器,总尺寸为 412 x 412,每个过滤器 5px 间隔的图像
MARGIN = 5
width = n * output_dim[0] + (n - 1) * MARGIN
height = n * output_dim[1] + (n - 1) * MARGIN
stitched_filters = np.zeros((width, height, 3), dtype='uint8')
# 用我们保存的过滤器填充图像
for i in range(n):
for j in range(n):
img, _ = filters[i * n + j]
width_margin = (output_dim[0] + MARGIN) * i
height_margin = (output_dim[1] + MARGIN) * j
stitched_filters[
width_margin: width_margin + output_dim[0],
height_margin: height_margin + output_dim[1], :] = img
# 将结果保存到磁盘
save_img('vgg_{0:}_{1:}x{1:}.png'.format(layer_name, n), stitched_filters)
# 这是输入图像的占位符
assert len(model.inputs) == 1
input_img = model.inputs[0]
# 获取每个『关键』图层的符号输出(我们给它们唯一的名称)。
layer_dict = dict([(layer.name, layer) for layer in model.layers[1:]])
output_layer = layer_dict[layer_name]
assert isinstance(output_layer, layers.Conv2D)
# 计算要处理的过滤范围
filter_lower = filter_range[0]
filter_upper = (filter_range[1]
if filter_range[1] is not None
else len(output_layer.get_weights()[1]))
assert(filter_lower >= 0
and filter_upper <= len(output_layer.get_weights()[1])
and filter_upper > filter_lower)
print('Compute filters {:} to {:}'.format(filter_lower, filter_upper))
# 迭代每个过滤器并生成其相应的图像
processed_filters = []
for f in range(filter_lower, filter_upper):
img_loss = _generate_filter_image(input_img, output_layer.output, f)
if img_loss is not None:
processed_filters.append(img_loss)
print('{} filter processed.'.format(len(processed_filters)))
# Finally draw and store the best filters to disk
_draw_filters(processed_filters)
if __name__ == '__main__':
# 我们想要可视化的图层的名称
# (see model definition at keras/applications/vgg16.py)
LAYER_NAME = 'block5_conv1'
# 构建 ImageNet 权重预训练的 VGG16 网络
vgg = vgg16.VGG16(weights='imagenet', include_top=False)
print('Model loaded.')
vgg.summary()
# 调用示例函数
visualize_layer(vgg, LAYER_NAME)