Grad-CAM:CNN看到了啥

前言

Grad-CAM论文传送门:arxiv.org/abs/1610.02...

Grad-CAM,全名为Gradient-weighted Class Activation Mapping,中文名叫梯度加权类激活映射。 是一种用于理解卷积神经网络决策的可视化技术,简单来说,它可以帮助我们"看到"神经网络在做出决策时,到底关注了图像的哪些部分,也就是卷积层的注意力部分呗

此项技术可以应用在图像分类,图像转文字,视觉问题回答等任何特定任务的网络

Grad-CAM无疑是一把解锁神经网络黑箱的钥匙。通过它,可以更直观地理解模型的内部工作机制,从而优化模型、提高性能(这句是水文章)

Grad-CAM 可以用来解释深度网络中任何卷积层,但我们只专注于解释最后一层卷积层

python 复制代码
import cv2
import numpy as np
import tensorflow as tf
from tensorflow import keras
from matplotlib import pyplot as plt

加载数据集

数据集来自Kaggle - Fruits Dataset (Images),该图像数据集展示了9种流行的水果,包括苹果、香蕉、樱桃、奇库(chickoo)、葡萄、奇异果、芒果、橙子和草莓。每种水果有40个图像,并具有不同的尺寸

在数据预处理中,将图像尺寸调整为(224, 224),且进行归一化处理

python 复制代码
path = '/kaggle/input/fruits-dataset-images/images'
batch_size = 64
target_size = (224, 224)

# 定义数据增强
data_generator = keras.preprocessing.image.ImageDataGenerator(
    rescale=1.0 / 255,
    validation_split=0.2,
)

# 读取、预处理练集数据
train_set = data_generator.flow_from_directory(
    directory=path,
    target_size=target_size,
    batch_size=batch_size,
    class_mode='categorical',
    color_mode='rgb',
    shuffle=True,
    subset='training'
)
# 读取、预处理验证集数据
valid_set = data_generator.flow_from_directory(
    directory=path,
    target_size=target_size,
    batch_size=batch_size,
    class_mode='categorical',
    color_mode='rgb',
    shuffle=True,
    subset='validation'
)

构建模型

采用以下方式,以提高模型学习能力与速度

  • 使用了卷积层堆叠
  • 使用了L2权重正则化
  • 在激活函数上,使用ELU代替ReLU

最后一层卷积层命名为conv,便于后期提取该层的输出。虽然在论文中指出relu的Grad-CAM效果更好,但奈不住elu的训练更快啊

python 复制代码
model = keras.Sequential([
    keras.layers.Conv2D(64, (5, 5), activation='elu'),
    keras.layers.Conv2D(64, (1, 1), activation='elu'),
    keras.layers.MaxPool2D(pool_size=(3, 3), strides=2),

    keras.layers.Conv2D(128, (3, 3), activation='elu'),
    keras.layers.Conv2D(128, (1, 1), activation='elu'),
    keras.layers.Conv2D(128, (1, 1), activation='elu'),
    keras.layers.MaxPool2D(pool_size=(3, 3), strides=2),

    keras.layers.Conv2D(256, (3, 3), activation='elu'),
    keras.layers.Conv2D(256, (1, 1), activation='elu'),
    keras.layers.Conv2D(256, (1, 1), activation='elu'),
    keras.layers.MaxPool2D(pool_size=(3, 3), strides=2),

    keras.layers.Conv2D(512, (3, 3), activation='elu'),
    keras.layers.Conv2D(512, (1, 1), activation='elu', name='conv'),
    keras.layers.MaxPool2D(pool_size=(3, 3), strides=1),

    keras.layers.GlobalAveragePooling2D(),
    keras.layers.Dropout(rate=0.5),
    keras.layers.Dense(512, activation='elu'),
    keras.layers.Dropout(rate=0.5),
    keras.layers.Dense(9, activation='softmax'),
])
model.build(input_shape=(None, 224, 224, 3))

编译模型

学习率使用了指数衰减(ExponentialDecay)方式,使模型在训练后期"学"得更加稳定

python 复制代码
initial_learning_rate = 0.001
lr_schedule = keras.optimizers.schedules.ExponentialDecay(initial_learning_rate,
                                                          decay_steps=30,
                                                          decay_rate=0.76,
                                                          staircase=True)
optimizer = keras.optimizers.Adam(learning_rate=lr_schedule)
loss = keras.losses.CategoricalCrossentropy()
model.compile(optimizer=optimizer,
              loss=loss,
              metrics=['accuracy'])

模型 Run!

Model 启动!!!

python 复制代码
model.fit(train_set, epochs=40, validation_data=valid_set)

40个epochs的训练结果如下图所示。虽然val_accuracy才80%,垃的一批,但没有出现最头疼的过拟合

Grad-CAM

上面水了好多好多,至此,终于到了真真正正的正文了

python 复制代码
# 获取图片、标签
images, labels = train_set.next()
# 随机选取 10 张图片
index = np.random.choice(images.shape[0], 10)
images = images[index]
# 提取最后一层卷积层
conv_layer = model.get_layer('conv')
# 获取卷积层与输出层的输出,便于后期的输出对卷积层求导
gard_model = keras.Model(inputs=model.inputs, outputs=[conv_layer.output, model.output])

<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> α k c = 1 Z ∑ i ∑ j ⏞ g l o b a l a v e r a g e p o o l i n g ∂ y c ∂ A i j k ⏟ g r a d i e n t s v i a b a c k p r o p \alpha_{k}^{c} = \overbrace{\frac{1}{Z} \sum_{i} \sum_{j}}^{\scriptsize global\ average\ pooling} \underbrace{\frac{\partial y^{c}}{\partial A_{ij}^{k}}}_{\scriptsize gradients\ via\ backprop} </math>αkc=Z1i∑j∑ global average poolinggradients via backprop ∂Aijk∂yc

输出层输出为 <math xmlns="http://www.w3.org/1998/Math/MathML"> y c y^{c} </math>yc,卷积层输出为 <math xmlns="http://www.w3.org/1998/Math/MathML"> A i j k A_{ij}^{k} </math>Aijk。先计算模型输出对卷积层的导数,再对导数进行全局平均池化处理,最终获得神经元重要性权重 <math xmlns="http://www.w3.org/1998/Math/MathML"> α k c \alpha_{k}^{c} </math>αkc

python 复制代码
with tf.GradientTape() as tape:
    conv_output, pred = gard_model(images)
    # 获取输出层的最大值
    pred = tf.reduce_max(pred, axis=-1)

# 输出层对卷积层求导
grad = tape.gradient(pred, conv_output)
# 对卷积层梯度求全局平均值,作为每个维度的权重
weights = keras.layers.GlobalAvgPool2D(keepdims=True)(grad)

<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> L G r a d − C A M c = R e L U ( ∑ k α k c A k ) ⏟ l i n e a r c o m b i n a t i o n L_{Grad-CAM}^{c}=\underbrace{ReLU \left( \sum_{k}\alpha_{k}^{c} A^{k} \right)}_{linear\ combination} </math>LGrad−CAMc=linear combination ReLU(k∑αkcAk)

前向传播神经元重要性权重与卷积层输出的加权组合,并使用ReLU去除负梯度,如果没有ReLU,注意力在定位上可能会效果欠佳

python 复制代码
# 去除负梯度
L = tf.nn.relu(weights * conv_output)
# 对512个维度求平均值
heatmaps = np.mean(L, axis=-1)
# 图像归一化
heatmaps = heatmaps / heatmaps.max()

显示注意力热力图

python 复制代码
# float32 转 uint8 格式,便于后期图片合并
images = np.uint8(255 * images)
heatmaps = np.uint8(255 * heatmaps)
heatmaps = 255 - heatmaps

fig, axes = plt.subplots(5, 6, dpi=166)
num = 0
for j in range(5):
    for i in [0, 3]:
        img = images[num]
        heatmap = heatmaps[num]

        axes[j][0 + i].imshow(img)
        axes[j][0 + i].axis('off')

        # 修改热力图尺寸 
        heatmap = cv2.resize(heatmap, (224, 224))
        # 灰度图渲染成热力图
        heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
        axes[j][1 + i].imshow(heatmap)
        axes[j][1 + i].axis('off')

        # 原图与热力图合并
        img_add_heatmap = cv2.addWeighted(img, 0.5, heatmap, 0.5, 0)
        axes[j][2 + i].imshow(img_add_heatmap)
        axes[j][2 + i].axis('off')
        num += 1

plt.subplots_adjust(wspace=0.01, hspace=0.01)
plt.show()

效果如下所示,在对于只有单个类别物体的图像中,Grad-CAM可以有不错的效果,但对于有一个图像中存在多个相同类别的物体嘛,就一言难尽咯......

相关推荐
qq_2739002335 分钟前
pytorch register_buffer介绍
人工智能·pytorch·python
龙的爹23332 小时前
论文翻译 | The Capacity for Moral Self-Correction in Large Language Models
人工智能·深度学习·算法·机器学习·语言模型·自然语言处理·prompt
python_知世3 小时前
2024年中国金融大模型产业发展洞察报告(附完整PDF下载)
人工智能·自然语言处理·金融·llm·计算机技术·大模型微调·大模型研究报告
Fanstay9853 小时前
人工智能技术的应用前景及其对生活和工作方式的影响
人工智能·生活
lunch( ̄︶ ̄)3 小时前
《AI 使生活更美好》
人工智能·生活
Hoper.J3 小时前
用两行命令快速搭建深度学习环境(Docker/torch2.5.1+cu118/命令行美化+插件),包含完整的 Docker 安装步骤
人工智能·深度学习·docker
Shaidou_Data3 小时前
信息技术引领未来:大数据治理的实践与挑战
大数据·人工智能·数据清洗·信息技术·数据治理技术
Elastic 中国社区官方博客3 小时前
开始使用 Elastic AI Assistant 进行可观察性和 Microsoft Azure OpenAI
大数据·人工智能·elasticsearch·microsoft·搜索引擎·全文检索·azure
qq_273900233 小时前
pytorch detach方法介绍
人工智能·pytorch·python
AI狂热爱好者4 小时前
A3超级计算机虚拟机,为大型语言模型LLM和AIGC提供强大算力支持
服务器·人工智能·ai·gpu算力