加载预训练VGG16生成混淆矩阵报错:Flatten层'list'无shape属性
加载已训练VGG16模型生成混淆矩阵时出现AttributeError的解决方案
问题描述
使用tensorflow.keras.applications中的VGG16模型在15类图像数据集上训练100个epoch后,将最优模型保存为best_model2.h5。尝试直接加载该模型生成混淆矩阵时,持续触发以下错误:
AttributeError: Exception encountered when calling Flatten.call(). 'list' object has no attribute 'shape' Arguments received by Flatten.call(): • args=(['<KerasTensor shape=(None, 7, 7, 512), dtype=float32, sparse=False, name=keras_tensor_450>'],) • kwargs=<class 'inspect._empty'>
训练代码
import tensorflow as tf from tensorflow.keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.applications import VGG16 from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Flatten, Dropout from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import ModelCheckpoint print("Set up directories for training and validation data...") train_dir = '/scratch/user/nabarunkar/Disaster Dataset/train' validation_dir = '/scratch/user/nabarunkar/Disaster Dataset/validation' print("Image preprocessing for data augmentation...") train_datagen = ImageDataGenerator( rescale=1./255, rotation_range=20, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, fill_mode='nearest' ) validation_datagen = ImageDataGenerator(rescale=1./255) print("Load images from directories...") train_generator = train_datagen.flow_from_directory( train_dir, target_size=(224, 224), # VGG16 expects 224x224 input batch_size=32, class_mode='categorical' # Ensure this is set to 'categorical' ) validation_generator = validation_datagen.flow_from_directory( validation_dir, target_size=(224, 224), batch_size=32, class_mode='categorical' # Ensure this is set to 'categorical' ) print("Build the VGG16 model...") base_model = VGG16(weights='imagenet', include_top=False, input_shape=(224, 224, 3)) print("Freeze the base model...") base_model.trainable = False print("Add custom classification layers on top...") model = Sequential([ base_model, Flatten(), Dense(256, activation='relu'), Dropout(0.5), Dense(15, activation='softmax') # 15 categories ]) print("Compile the model...") model.compile(optimizer=Adam(learning_rate=0.0001), loss='categorical_crossentropy', metrics=['accuracy']) print("Callbacks...") checkpoint = ModelCheckpoint( 'best_model2.h5', # Path to save the best model monitor='val_loss', # Metric to monitor save_best_only=True, # Only save the model if the monitored metric improves mode='min', # Save when the metric is minimized (for loss) verbose=1 # Print messages when saving the model ) print("Train the model...") history = model.fit( train_generator, epochs=100, validation_data=validation_generator, callbacks=[checkpoint] ) print("Evaluate the model on the validation set...") val_loss, val_accuracy = model.evaluate(validation_generator) print(f'Validation accuracy: {val_accuracy:.2f}')
测试代码
import numpy as np from tensorflow.keras.models import load_model from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns import matplotlib.pyplot as plt import re def extract_numbers(class_name): """Extract the numerical part from class names like 'cat01building'""" numbers = re.findall(r'\d+', class_name) return numbers[0] if numbers else class_name def generate_confusion_matrix(model_path, validation_generator, class_names): # Load the saved model model = load_model('./best_model2.h5') # Get predictions # Reset the generator to ensure we start from the beginning validation_generator.reset() # Get predictions for the validation set predictions = model.predict(validation_generator) predicted_classes = np.argmax(predictions, axis=1) # Get true labels true_classes = validation_generator.classes # Generate confusion matrix cm = confusion_matrix(true_classes, predicted_classes) # Simplify class names to just numbers simplified_labels = [extract_numbers(name) for name in class_names] # Plot confusion matrix plt.figure(figsize=(12, 10)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=simplified_labels, yticklabels=simplified_labels) plt.title('Confusion Matrix') plt.ylabel('True Label') plt.xlabel('Predicted Label') plt.xticks(rotation=45) plt.yticks(rotation=45) plt.tight_layout() plt.show() # Print classification report print("\nClassification Report:") print(classification_report(true_classes, predicted_classes, target_names=class_names)) train_dir = '/content/dataset/train' validation_dir = '/content/dataset/validation' train_datagen = ImageDataGenerator( rescale=1./255, rotation_range=20, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, fill_mode='nearest' ) validation_datagen = ImageDataGenerator(rescale=1./255) # Load images from directories train_generator = train_datagen.flow_from_directory( train_dir, target_size=(224, 224), # VGG16 expects 224x224 input batch_size=32, class_mode='categorical' # Ensure this is set to 'categorical' ) validation_generator = validation_datagen.flow_from_directory( validation_dir, target_size=(224, 224), batch_size=32, class_mode='categorical' # Ensure this is set to 'categorical' ) class_names = ['cat07', 'cat08', 'cat09', 'cat10', 'cat11', 'cat12', 'cat13', 'cat14', 'cat15', 'cat16', 'cat17', 'cat18', 'cat19', 'cat20', 'cat21', 'cat22', 'cat23'] # Generate confusion matrix generate_confusion_matrix('best_model2.h5', validation_generator, class_names)
错误原因分析
- 模型加载兼容性问题:Keras保存包含预训练模型(如VGG16)的Sequential模型时,偶尔会出现层输入解析异常,导致Flatten层无法正确识别输入张量,误将其当作列表处理。
- 类别数量不匹配:训练时模型输出为15类(
Dense(15, activation='softmax')),但测试代码中class_names定义了17个类别,不仅会导致后续指标计算出错,还可能间接干扰模型推理逻辑。
解决方案
1. 加载模型时指定自定义对象(优先推荐)
在加载模型时显式声明自定义对象,确保VGG16和Flatten层能被正确解析:
修改测试代码中的模型加载部分:
from tensorflow.keras.applications import VGG16 from tensorflow.keras.layers import Flatten model = load_model('./best_model2.h5', custom_objects={'VGG16': VGG16, 'Flatten': Flatten})
2. 修正类别数量
将测试代码中的class_names调整为15个,与训练时的类别数量严格一致:
# 替换原class_names,确保数量为15个,与训练数据集类别对应 class_names = ['cat07', 'cat08', 'cat09', 'cat10', 'cat11', 'cat12', 'cat13', 'cat14', 'cat15', 'cat16', 'cat17', 'cat18', 'cat19', 'cat20', 'cat21']
3. 改用SavedModel格式保存/加载(若方案1无效)
如果是模型保存格式导致的兼容性问题,可在训练代码末尾添加额外保存逻辑:
# 保存为SavedModel格式,兼容性更强 model.save('best_model_savedmodel')
然后在测试代码中加载该格式模型:
import tensorflow as tf model = tf.keras.models.load_model('best_model_savedmodel')
完整修正后的测试代码
import numpy as np import tensorflow as tf from tensorflow.keras.models import load_model from tensorflow.keras.applications import VGG16 from tensorflow.keras.layers import Flatten from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns import matplotlib.pyplot as plt import re def extract_numbers(class_name): """提取类名中的数字部分,比如从'cat01building'中提取'01'""" numbers = re.findall(r'\d+', class_name) return numbers[0] if numbers else class_name def generate_confusion_matrix(model_path, validation_generator, class_names): # 加载模型时指定自定义对象 model = load_model(model_path, custom_objects={'VGG16': VGG16, 'Flatten': Flatten}) validation_generator.reset() predictions = model.predict(validation_generator) predicted_classes = np.argmax(predictions, axis=1) true_classes = validation_generator.classes cm = confusion_matrix(true_classes, predicted_classes) simplified_labels = [extract_numbers(name) for name in class_names] plt.figure(figsize=(12, 10)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=simplified_labels, yticklabels=simplified_labels) plt.title('混淆矩阵') plt.ylabel('真实标签') plt.xlabel('预测标签') plt.xticks(rotation=45) plt.yticks(rotation=45) plt.tight_layout() plt.show() print("\n分类报告:") print(classification_report(true_classes, predicted_classes, target_names=class_names)) # 数据集路径(根据实际环境修改) train_dir = '/content/dataset/train' validation_dir = '/content/dataset/validation' train_datagen = ImageDataGenerator( rescale=1./255, rotation_range=20, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, fill_mode='nearest' ) validation_datagen = ImageDataGenerator(rescale=1./255) train_generator = train_datagen.flow_from_directory( train_dir, target_size=(224, 224), batch_size=32, class_mode='categorical' ) validation_generator = validation_datagen.flow_from_directory( validation_dir, target_size=(224, 224), batch_size=32, class_mode='categorical' ) # 修正为15个类别,与训练时一致 class_names = ['cat07', 'cat08', 'cat09', 'cat10', 'cat11', 'cat12', 'cat13', 'cat14', 'cat15', 'cat16', 'cat17', 'cat18', 'cat19', 'cat20', 'cat21'] generate_confusion_matrix('best_model2.h5', validation_generator, class_names)
内容的提问来源于stack exchange,提问作者Rick
相关产品推荐
相关产品推荐

