ResNet50生成Grad-CAM热图遇sequential_2未调用错误求助
皮肤病变分类ResNet50模型Grad-CAM热图生成报错解决方案
问题背景
使用Keras训练了皮肤病变分类的ResNet50模型,保存为ResNet50_skin_lesion_final_v2.keras,模型结构包含ResNet50基础层、全局平均池化层、全连接层、Dropout层及8分类输出层。尝试基于ResNet50的conv5_block3_out层生成Grad-CAM热图时,出现以下错误:
AttributeError: The layer sequential_2 has never been called and thus has no defined input.
相关代码
import numpy as np import tensorflow as tf import matplotlib.pyplot as plt from tensorflow.keras.preprocessing import image from tensorflow.keras.models import load_model # Load trained model (ResNet50_skin_lesion_final_v2) model = load_model('ResNet50_skin_lesion_final_v2.keras') # Function to preprocess the input image def preprocess_input_image(img_path, target_size=(224, 224)): img = image.load_img(img_path, target_size=target_size) img_array = image.img_to_array(img) img_array = np.expand_dims(img_array, axis=0) img_array /= 255.0 # Rescale the image like the training data return img_array # Function to generate Grad-CAM heatmap def generate_gradcam_heatmap(model, img_array, last_conv_layer_name, pred_index=None): # Forward pass to ensure the model input is called predictions = model.predict(img_array) if pred_index is None: pred_index = tf.argmax(predictions[0]) # Get the ResNet50 base model base_model = model.get_layer('resnet50') # Build a model that outputs the activations of the last conv layer and the model output grad_model = tf.keras.models.Model( inputs=model.input, outputs=[base_model.get_layer(last_conv_layer_name).output, model.output] ) with tf.GradientTape() as tape: # Watch the gradients for the convolutional layer conv_outputs, predictions = grad_model(img_array) class_output = predictions[:, pred_index] # Compute the gradient of the class output with respect to the feature map grads = tape.gradient(class_output, conv_outputs) # Take the mean of the gradients across the channels pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2)) # Multiply the output feature map by the pooled gradients conv_outputs = conv_outputs[0] conv_outputs *= pooled_grads # Generate the heatmap heatmap = tf.reduce_mean(conv_outputs, axis=-1) heatmap = np.maximum(heatmap, 0) / np.max(heatmap) # Normalize between 0 and 1 return heatmap.numpy() # Function to display the Grad-CAM heatmap on top of the original image def display_gradcam(img_path, heatmap, alpha=0.4): # Load the original image img = image.load_img(img_path) img = image.img_to_array(img) # Resize the heatmap to match the original image size heatmap = np.uint8(255 * heatmap) heatmap = np.expand_dims(heatmap, axis=-1) heatmap = tf.image.resize(heatmap, (img.shape[0], img.shape[1])).numpy() # Create an RGB heatmap heatmap = np.uint8(plt.cm.jet(heatmap[..., 0]) * 255) # Superimpose the heatmap on the original image superimposed_img = heatmap * alpha + img # Display the image plt.figure(figsize=(8, 8)) plt.imshow(superimposed_img.astype('uint8')) plt.axis('off') plt.show() # Example usage img_path = '/mnt/c/Users/arjay/Downloads/Ubuntu/ISIC_2019_Skin_Lesion_Data/ISIC_2019_Training_Input/manual_test/ISIC_0027665.jpg' img_array = preprocess_input_image(img_path) # Generate the Grad-CAM heatmap from the last conv layer of ResNet50 heatmap = generate_gradcam_heatmap(model, img_array, last_conv_layer_name='conv5_block3_out') # Display the Grad-CAM heatmap on the original image display_gradcam(img_path, heatmap)
错误栈
AttributeError Traceback (most recent call last) Cell In[128], line 82 79 img_array = preprocess_input_image(img_path) 81 # Generate the Grad-CAM heatmap from the last conv layer of ResNet50 ---> 82 heatmap = generate_gradcam_heatmap(model, img_array, last_conv_layer_name='conv5_block3_out') 84 # Display the Grad-CAM heatmap on the original image 85 display_gradcam(img_path, heatmap) Cell In[128], line 30 26 base_model = model.get_layer('resnet50') 28 # Build a model that outputs the activations of the last conv layer and the model output 29 grad_model = tf.keras.models.Model( ---> 30 inputs=model.input, 31 outputs=[base_model.get_layer(last_conv_layer_name).output, model.output] 32 ) 34 with tf.GradientTape() as tape: 35 # Watch the gradients for the convolutional layer 36 conv_outputs, predictions = grad_model(img_array) File ~/projects/tf217/tf217/lib/python3.12/site-packages/keras/src/ops/operation.py:254, in Operation.input(self) 244 @property 245 def input(self): 246 """Retrieves the input tensor(s) of a symbolic operation. 247 ... 292 f"{node_index}, but the operation has only " 293 f"{len(self._inbound_nodes)} inbound nodes." 294 ) AttributeError: The layer sequential_2 has never been called and thus has no defined input.
疑问解答
1. 为何错误提及sequential_2,而我加载的是ResNet50模型?
你的整体模型是一个Sequential容器(自动命名为sequential_2),其中封装了ResNet50基础层和后续的分类层(全局平均池化、全连接等)。加载模型后,直接访问model.input时,由于Sequential层的输入节点尚未通过实际输入数据完成初始化(即使调用了predict,部分情况下内部层的符号输入张量仍未被正确注册),导致报错。本质是模型加载后,容器层的输入张量没有被显式定义,构建grad_model时无法直接引用model.input。
2. 如何正确输入数据以生成Grad-CAM热图?
核心是确保构建grad_model前,模型的输入输出节点已完全初始化。以下是两种可靠的修正方案:
方案一:显式创建输入张量替代model.input
在构建grad_model时,用tf.keras.Input根据模型的输入形状创建输入张量,避免依赖未初始化的model.input。
方案二:加载模型后先执行一次前向传播
在调用generate_gradcam_heatmap前,先让模型处理一次输入数据(比如model(img_array)),确保所有层的输入节点被初始化。
修正后的完整代码
方案一:显式定义输入张量
import numpy as np import tensorflow as tf import matplotlib.pyplot as plt from tensorflow.keras.preprocessing import image from tensorflow.keras.models import load_model # Load trained model model = load_model('ResNet50_skin_lesion_final_v2.keras') def preprocess_input_image(img_path, target_size=(224, 224)): img = image.load_img(img_path, target_size=target_size) img_array = image.img_to_array(img) img_array = np.expand_dims(img_array, axis=0) img_array /= 255.0 return img_array def generate_gradcam_heatmap(model, img_array, last_conv_layer_name, pred_index=None): # 先执行前向传播获取预测结果 predictions = model.predict(img_array, verbose=0) if pred_index is None: pred_index = tf.argmax(predictions[0]) base_model = model.get_layer('resnet50') # 显式创建输入张量,替代model.input input_tensor = tf.keras.Input(shape=model.input_shape[1:]) x = input_tensor conv_output = None # 重建模型的前向传播路径,记录目标卷积层输出 for layer in model.layers: x = layer(x) if layer.name == last_conv_layer_name: conv_output = x final_output = x # 构建grad_model grad_model = tf.keras.models.Model( inputs=input_tensor, outputs=[conv_output, final_output] ) with tf.GradientTape() as tape: conv_outputs, predictions = grad_model(img_array) class_output = predictions[:, pred_index] grads = tape.gradient(class_output, conv_outputs) pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2)) conv_outputs = conv_outputs[0] conv_outputs *= pooled_grads heatmap = tf.reduce_mean(conv_outputs, axis=-1) heatmap = np.maximum(heatmap, 0) / np.max(heatmap) return heatmap.numpy() def display_gradcam(img_path, heatmap, alpha=0.4): img = image.load_img(img_path) img = image.img_to_array(img) heatmap = np.uint8(255 * heatmap) heatmap = np.expand_dims(heatmap, axis=-1) heatmap = tf.image.resize(heatmap, (img.shape[0], img.shape[1])).numpy() heatmap = np.uint8(plt.cm.jet(heatmap[..., 0]) * 255) superimposed_img = heatmap * alpha + img plt.figure(figsize=(8, 8)) plt.imshow(superimposed_img.astype('uint8')) plt.axis('off') plt.show() # 示例调用 img_path = '/mnt/c/Users/arjay/Downloads/Ubuntu/ISIC_2019_Skin_Lesion_Data/ISIC_2019_Training_Input/manual_test/ISIC_0027665.jpg' img_array = preprocess_input_image(img_path) heatmap = generate_gradcam_heatmap(model, img_array, last_conv_layer_name='conv5_block3_out') display_gradcam(img_path, heatmap)
方案二:提前初始化模型输入节点
修改generate_gradcam_heatmap函数,增加显式前向传播步骤:
def generate_gradcam_heatmap(model, img_array, last_conv_layer_name, pred_index=None): # 显式执行一次前向传播,初始化模型输入节点 _ = model(img_array) predictions = model.predict(img_array, verbose=0) if pred_index is None: pred_index = tf.argmax(predictions[0]) base_model = model.get_layer('resnet50') grad_model = tf.keras.models.Model( inputs=model.input, outputs=[base_model.get_layer(last_conv_layer_name).output, model.output] ) # 后续代码与原函数一致...
内容的提问来源于stack exchange,提问作者Arjay Alba
相关产品推荐
相关产品推荐

