You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于多编码器自定义CLIP模型生成Grad-CAM热力图异常求助

自定义多编码器CLIP模型Grad-CAM热力图全为0的问题

我在为一款自定义CLIP模型生成Grad-CAM热力图时遇到异常:生成的热力图无高亮区域,打印结果显示Grad-CAM值全为0。该模型与标准CLIP结构不同,采用多编码器拼接后输入CLIP。

原代码

import tensorflow as tf
from tensorflow import keras
import numpy as np
import cv2
import matplotlib.pyplot as plt

# Assuming you have already trained the clip_model
# Create a new model for Grad-CAM generation
input_image = keras.Input(shape=(312, 312, 3))

global_activations = clip_model.global_vision_encoder(input_image)
local_activations = clip_model.local_vision_encoder(input_image)

# Create separate Grad-CAM models for impression and finding embeddings
impression_gradcam_model = keras.Model(
    inputs=[input_image, clip_model.impression_input],
    outputs=[global_activations, local_activations]
)

finding_gradcam_model = keras.Model(
    inputs=[input_image, clip_model.finding_input],
    outputs=[global_activations, local_activations]
)

# Function to preprocess the input image
def preprocess_image(image_path):
    image = tf.io.read_file(image_path)
    image = tf.image.decode_jpeg(image, channels=3)
    image = tf.image.resize(image, (312, 312))
    image = tf.expand_dims(image, axis=0)
    return image

# Example usage
image_path = r"C:\Users\zahid\Desktop\3130_IM-1472-1001.dcm.png"
impression = "1.No acute cardiopulmonary findings."
finding = "No focal consolidation. No visualized pneumothorax. No large pleural effusions. The heart size and cardiomediastinal silhouette are grossly unremarkable"

# Load and resize the input image
input_image_resized = cv2.resize(cv2.imread(image_path), (312, 312))

def compute_gradcam(model, inputs, activations):
    with tf.GradientTape() as tape:
        tape.watch(inputs[0])
        _, local_activations = model(inputs)
        class_output = tf.reduce_mean(local_activations, axis=[0])
    grads = tape.gradient(class_output, inputs[0])
    pooled_grads = tf.reduce_mean(grads, axis=[0, 1, 2])
    pooled_grads = tf.expand_dims(pooled_grads, axis=-1)
    activations = tf.cast(activations, pooled_grads.dtype)  # Cast activations to the same data type as pooled_grads
    gradcam = tf.reduce_sum(tf.multiply(activations, pooled_grads), axis=[0, 1])
    gradcam = tf.maximum(gradcam, 0)
    gradcam = (gradcam - tf.reduce_min(gradcam)) / (tf.reduce_max(gradcam) - tf.reduce_min(gradcam) + 1e-8)
    return gradcam

impression_inputs = [preprocess_image(image_path), tf.constant([impression], dtype=tf.string)]
_, impression_activations = impression_gradcam_model(impression_inputs)
impression_gradcam = compute_gradcam(impression_gradcam_model, impression_inputs, impression_activations)

finding_inputs = [preprocess_image(image_path), tf.constant([finding], dtype=tf.string)]
_, finding_activations = finding_gradcam_model(finding_inputs)
finding_gradcam = compute_gradcam(finding_gradcam_model, finding_inputs, finding_activations)

print("Impression Grad-CAM values:\n", impression_gradcam)
print("Finding Grad-CAM values:\n", finding_gradcam)

def normalize_gradcam(gradcam):
    gradcam_norm = (gradcam - tf.reduce_min(gradcam)) / (tf.reduce_max(gradcam) - tf.reduce_min(gradcam) + 1e-8)
    return gradcam_norm

impression_gradcam_norm = normalize_gradcam(impression_gradcam)
finding_gradcam_norm = normalize_gradcam(finding_gradcam)

print(f"Impression Grad-CAM heatmap values: min={tf.reduce_min(impression_gradcam_norm):.2f}, max={tf.reduce_max(impression_gradcam_norm):.2f}")
print(f"Finding Grad-CAM heatmap values: min={tf.reduce_min(finding_gradcam_norm):.2f}, max={tf.reduce_max(finding_gradcam_norm):.2f}")

def apply_colormap_and_overlay(gradcam_norm, input_image):
    gradcam_norm = tf.reshape(gradcam_norm, (1, 1))
    gradcam_norm_rgb = tf.image.grayscale_to_rgb(tf.expand_dims(gradcam_norm, axis=-1))
    gradcam_norm_resized = tf.image.resize(gradcam_norm_rgb, (input_image.shape[0], input_image.shape[1]))
    gradcam_heatmap = np.uint8(255 * gradcam_norm_resized.numpy())
    gradcam_overlay = cv2.addWeighted(input_image, 0.8, gradcam_heatmap, 0.5, 0)
    return gradcam_overlay


impression_gradcam_overlay = apply_colormap_and_overlay(impression_gradcam_norm.numpy(), input_image_resized)
finding_gradcam_overlay = apply_colormap_and_overlay(finding_gradcam_norm.numpy(), input_image_resized)

# Display the results
fig, axes = plt.subplots(1, 3, figsize=(15, 5))

axes[0].imshow(input_image_resized)
axes[0].set_title("Input Image")

axes[1].imshow(impression_gradcam_overlay)
axes[1].set_title("Impression Grad-CAM Heatmap")

axes[2].imshow(finding_gradcam_overlay)
axes[2].set_title("Finding Grad-CAM Heatmap")

plt.tight_layout()
plt.show()

原代码输出结果

Impression Grad-CAM values:
 tf.Tensor(0.0, shape=(), dtype=float32)
Finding Grad-CAM values:
 tf.Tensor(0.0, shape=(), dtype=float32)
Impression Grad-CAM heatmap values: min=0.00, max=0.00
Finding Grad-CAM heatmap values: min=0.00, max=0.00

异常热力图

热力图截图


问题原因与修复方案

1. Grad-CAM核心逻辑错误

原代码中,梯度计算的目标是local_activations的均值,而非CLIP模型的核心输出——图像与文本的匹配相似度得分。没有关联到任务目标的梯度自然为0。

  • 修复:将梯度计算目标替换为图像-文本的相似度得分,这才是模型训练的核心任务指标。

2. Grad-CAM模型定义不完整

原Grad-CAM模型仅输出中间激活层,未包含CLIP的文本编码、相似度计算完整链路,导致梯度无法传递到最终任务目标。

  • 修复:重新定义模型,让其接收图像+文本输入,同时输出相似度得分和目标激活层(local_activations)。

3. 可视化逻辑错误

原代码将Grad-CAM值强制reshape为(1,1),导致整个热力图为单一值,无法体现空间分布。

  • 修复:保留Grad-CAM的空间维度,将其resize到与输入图像一致的尺寸后再生成热力图。

修改后的完整代码

import tensorflow as tf
from tensorflow import keras
import numpy as np
import cv2
import matplotlib.pyplot as plt

# 基于已训练的clip_model构建完整Grad-CAM模型
def build_gradcam_model(clip_model):
    input_image = keras.Input(shape=(312, 312, 3))
    text_input = keras.Input(shape=(), dtype=tf.string)
    
    # 执行完整的图像编码流程
    global_feat = clip_model.global_vision_encoder(input_image)
    local_feat = clip_model.local_vision_encoder(input_image)
    image_embedding = tf.concat([global_feat, local_feat], axis=-1)
    
    # 执行完整的文本编码流程
    text_embedding = clip_model.text_encoder(text_input)
    
    # 计算图像-文本相似度(CLIP核心匹配得分)
    similarity = tf.matmul(
        tf.nn.l2_normalize(image_embedding, axis=-1),
        tf.nn.l2_normalize(text_embedding, axis=-1),
        transpose_b=True
    )
    similarity = tf.squeeze(similarity, axis=1)  # 去除冗余维度
    
    # 返回任务目标(相似度)和用于生成热力图的激活层
    return keras.Model(inputs=[input_image, text_input], outputs=[similarity, local_feat])

# 构建两个任务的Grad-CAM模型
impression_gradcam_model = build_gradcam_model(clip_model)
finding_gradcam_model = build_gradcam_model(clip_model)

# 图像预处理(添加CLIP标准预处理)
def preprocess_image(image_path):
    image = tf.io.read_file(image_path)
    image = tf.image.decode_jpeg(image, channels=3)
    image = tf.image.resize(image, (312, 312))
    image = tf.expand_dims(image, axis=0)
    # 适配CLIP的图像预处理要求
    image = tf.keras.applications.clip.preprocess_input(image)
    return image

# 修正后的Grad-CAM计算函数
def compute_gradcam(model, inputs):
    image_input, text_input = inputs
    with tf.GradientTape() as tape:
        tape.watch([image_input, model.output[1]])
        similarity, local_activations = model([image_input, text_input])
        # 以相似度得分为目标,计算对激活层的梯度
        grads = tape.gradient(similarity, local_activations)
    
    # Grad-CAM核心计算:梯度空间均值作为权重
    pooled_grads = tf.reduce_mean(grads, axis=(1, 2))
    # 加权激活层并求和得到热力图
    gradcam = tf.reduce_sum(
        tf.multiply(pooled_grads[..., tf.newaxis, tf.newaxis], local_activations),
        axis=-1
    )
    # ReLU过滤负贡献
    gradcam = tf.maximum(gradcam, 0)
    # 归一化到[0,1]区间
    gradcam = (gradcam - tf.reduce_min(gradcam)) / (tf.reduce_max(gradcam) - tf.reduce_min(gradcam) + 1e-8)
    return tf.squeeze(gradcam)  # 去除batch维度

# 示例数据
image_path = r"C:\Users\zahid\Desktop\3130_IM-1472-1001.dcm.png"
impression = "1.No acute cardiopulmonary findings."
finding = "No focal consolidation. No visualized pneumothorax. No large pleural effusions. The heart size and cardiomediastinal silhouette are grossly unremarkable"

input_image_resized = cv2.resize(cv2.imread(image_path), (312, 312))
input_image_tensor = preprocess_image(image_path)

# 计算Grad-CAM
impression_gradcam = compute_gradcam(impression_gradcam_model, [input_image_tensor, tf.constant([impression])])
finding_gradcam = compute_gradcam(finding_gradcam_model, [input_image_tensor, tf.constant([finding])])

print("Impression Grad-CAM values:\n", impression_gradcam)
print("Finding Grad-CAM values:\n", finding_gradcam)

# 修正后的热力图叠加函数
def apply_colormap_and_overlay(gradcam, input_image):
    # 将Grad-CAM resize到图像尺寸
    gradcam_resized = cv2.resize(gradcam.numpy(), (input_image.shape[1], input_image.shape[0]))
    # 生成彩色热力图
    heatmap = cv2.applyColorMap(np.uint8(255 * gradcam_resized), cv2.COLORMAP_JET)
    # 叠加到原图
    overlay = cv2.addWeighted(input_image, 0.7, heatmap, 0.3, 0)
    return overlay

# 生成并展示结果
impression_overlay = apply_colormap_and_overlay(impression_gradcam, input_image_resized)
finding_overlay = apply_colormap_and_overlay(finding_gradcam, input_image_resized)

fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(cv2.cvtColor(input_image_resized, cv2.COLOR_BGR2RGB))
axes[0].set_title("输入图像")
axes[1].imshow(cv2.cvtColor(impression_overlay, cv2.COLOR_BGR2RGB))
axes[1].set_title("Impression Grad-CAM热力图")
axes[2].imshow(cv2.cvtColor(finding_overlay, cv2.COLOR_BGR2RGB))
axes[2].set_title("Finding Grad-CAM热力图")
plt.tight_layout()
plt.show()

内容的提问来源于stack exchange,提问作者Jacob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 16:35:54