基于ResNet50V2的Grad-CAM无法生成热力图,求问题排查
我训练了一个用于3分类任务的图像分类模型,参考Keras官网的Grad-CAM教程操作,步骤如下:
加载模型
RN50V2 = tf.keras.models.load_model('/content/drive/MyDrive/Grad_CAM_Test/ResNet50V2_/RN50V2.h5')
通过RN50V2.summary()确定最后卷积层名称为last_conv_layer_name = 'conv5_block3_3_conv',随后加载测试集:
batch_size = 32 test_dataset = tf.keras.utils.image_dataset_from_directory( '/content/drive/MyDrive/Test_150', image_size = (224, 224), batch_size = batch_size, shuffle = False )
遍历测试数据集批次
images_batch = [] # 存储测试集图像的列表 labels_batch = [] # 存储测试集标签的列表 for dataset in test_dataset.unbatch(): image_batchh, label_batchh = dataset images = image_batchh.numpy() # 将图像张量转为numpy数组 labels = label_batchh.numpy() # 将标签张量转为numpy数组 images_batch.append(images) labels_batch.append(labels)
准备测试图像
将测试集中的第一张图像转换为模型输入格式:
array_of_img = keras.preprocessing.image.img_to_array(images_batch[0]) # 转为数组 array_of_img = np.expand_dims(array_of_img, axis=0) # 形状变为(1,224,224,3) print(array_of_img)
Grad-CAM算法实现
def make_gradcam_heatmap(img_array, model, last_conv_layer_name, pred_index=None): # 创建映射输入到最后卷积层激活和输出预测的模型 grad_model = tf.keras.models.Model( [RN50V2.inputs], [RN50V2.get_layer('conv5_block3_3_conv').output, RN50V2.output] ) # 计算输入图像对应最高预测类别的梯度,相对于最后卷积层的激活 with tf.GradientTape() as tape: last_conv_layer_output, preds = grad_model(img_array) if pred_index is None: pred_index = tf.argmax(preds[0]) class_channel = preds[:, pred_index] # 输出神经元相对于最后卷积层特征图的梯度 grads = tape.gradient(class_channel, last_conv_layer_output) # 计算每个特征图通道的梯度均值 pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2)) # 加权求和特征图通道得到热力图 last_conv_layer_output = last_conv_layer_output[0] heatmap = last_conv_layer_output @ pooled_grads[..., tf.newaxis] heatmap = tf.squeeze(heatmap) # 归一化热力图到0-1区间 heatmap = tf.maximum(heatmap, 0) / tf.math.reduce_max(heatmap) return heatmap.numpy()
测试Grad-CAM
# 准备输入图像 img_array = array_of_img # 加载模型 model = RN50V2 # 移除最后一层的softmax激活 model.layers[-1].activation = None # 打印最高预测类别 preds = model.predict(img_array) # 生成热力图 heatmap = make_gradcam_heatmap(img_array, RN50V2, 'conv5_block3_3_conv') # 显示热力图 plt.matshow(heatmap) plt.show()
测试后无法看到最后卷积层的热力图,请问哪里操作出错了?
问题排查与解决建议
Grad-CAM函数硬编码模型实例
你在make_gradcam_heatmap函数里创建grad_model时,直接使用了全局的RN50V2,而非函数参数传入的model。这会导致函数无法复用,且如果后续对传入的model做了修改(比如移除softmax),函数内部依然使用原始模型,梯度计算会出现偏差。修改方式:grad_model = tf.keras.models.Model( [model.inputs], [model.get_layer(last_conv_layer_name).output, model.output] )图像预处理与训练流程不匹配
ResNet50V2训练时通常需要特定预处理(比如将像素值归一化到[0,1],或使用ImageNet均值方差标准化)。你直接将图像转成数组就输入模型,若预处理和训练时不一致,模型预测结果会异常,进而导致热力图无有效信息。检查训练代码中的预处理步骤,比如训练时如果用了:preprocess_input = tf.keras.applications.resnet_v2.preprocess_input测试时需对
array_of_img做同样处理:array_of_img = preprocess_input(array_of_img)移除softmax的操作无效
直接修改model.layers[-1].activation = None不会自动更新模型计算图,因为模型已经编译过。实际上Grad-CAM不需要移除softmax——softmax是单调递增函数,不会改变梯度的相对大小,这一步完全可以省略。检查热力图数值是否异常
在显示热力图前,打印heatmap的数值范围和内容:print(heatmap.min(), heatmap.max()) print(heatmap)如果数值全为0或接近0,说明梯度计算出现问题,大概率是模型输入预处理错误,或是最后卷积层选择有误。
确认GradientTape正确追踪张量
确保last_conv_layer_output在tf.GradientTape作用域内是可追踪的张量。若模型加载时设置了run_eagerly=False(默认),通常不会有问题,但如果有自定义层,需检查层的可追踪性。
内容的提问来源于stack exchange,提问作者Jaturong

