
本文详解在将预训练cnn(如xception)从二分类迁移到多分类时,因标签格式不匹配导致的“shapes (none, 1) and (none, 3) are incompatible”错误,并提供完整的数据预处理、特征提取与分类头适配方案。
本文详解在将预训练cnn(如xception)从二分类迁移到多分类时,因标签格式不匹配导致的“shapes (none, 1) and (none, 3) are incompatible”错误,并提供完整的数据预处理、特征提取与分类头适配方案。
当使用 image_dataset_from_directory 加载多分类数据集(如3类图像)时,Keras 默认以整数形式返回标签(例如 [0, 1, 2]),其 shape 为 (batch_size,) —— 这是稀疏整数标签(sparse labels)。而当你在模型末端使用 Dense(3, activation='softmax') 并指定 loss="categorical_crossentropy" 时,Keras 要求标签必须是one-hot 编码格式,即 shape 为 (batch_size, 3),值形如 [[1,0,0], [0,1,0], [0,0,1]]。二者形状不匹配,直接引发 ValueError: Shapes (None, 1) and (None, 3) are incompatible。
关键修复点在于:在特征提取阶段同步将整数标签转换为 one-hot 编码。需引入 keras.utils.to_categorical(注意:np_utils 已弃用,应使用 keras.utils)。
以下是修正后的完整流程:
import os
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.applications import Xception
from tensorflow.keras.utils import image_dataset_from_directory, to_categorical
# 1. 数据加载(保持不变)
new_base_dir = '/Users/.../img_dir'
train_dataset = image_dataset_from_directory(
os.path.join(new_base_dir, 'train'),
image_size=(180, 180),
batch_size=10,
label_mode='int' # 显式指定,确保返回整数标签
)
validation_dataset = image_dataset_from_directory(
os.path.join(new_base_dir, 'validation'),
image_size=(180, 180),
batch_size=10,
label_mode='int'
)
test_dataset = image_dataset_from_directory(
os.path.join(new_base_dir, 'test'),
image_size=(180, 180),
batch_size=10,
label_mode='int'
)
# 2. 构建并冻结预训练特征提取器
conv_base = Xception(
weights="imagenet",
include_top=False,
input_shape=(180, 180, 3)
)
conv_base.trainable = False # 冻结卷积基
# 3. 特征提取函数(核心修正:to_categorical)
def get_features_and_labels(dataset):
all_features = []
all_labels = []
for images, labels in dataset:
preprocessed_images = keras.applications.xception.preprocess_input(images)
features = conv_base.predict(preprocessed_images)
all_features.append(features)
all_labels.append(labels)
# ✅ 关键修改:将拼接后的整数标签转为 one-hot 编码
return np.concatenate(all_features), to_categorical(np.concatenate(all_labels), num_classes=3)
train_features, train_labels = get_features_and_labels(train_dataset)
val_features, val_labels = get_features_and_labels(validation_dataset)
test_features, test_labels = get_features_and_labels(test_dataset)
print(f"Train features shape: {train_features.shape}") # e.g., (3000, 6, 6, 2048)
print(f"Train labels shape: {train_labels.shape}") # ✅ now (3000, 3)接下来构建分类器(输入 shape 需与 Xception 输出一致):
# 4. 构建全连接分类头
inputs = keras.Input(shape=(6, 6, 2048)) # Xception with input (180,180,3) → output (6,6,2048)
x = layers.Flatten()(inputs)
x = layers.Dense(256, activation='relu')(x)
x = layers.Dropout(0.5)(x)
outputs = layers.Dense(3, activation='softmax')(x) # 3 classes → softmax
model = keras.Model(inputs, outputs)
model.compile(
loss="categorical_crossentropy", # ✅ 匹配 one-hot 标签
optimizer="rmsprop",
metrics=["accuracy"]
)
# 5. 训练
history = model.fit(
train_features, train_labels,
epochs=20,
validation_data=(val_features, val_labels),
verbose=1
)⚠️ 重要注意事项:
- 若后续需微调(fine-tuning),请在 conv_base.trainable = True 前先编译模型,并仅解冻顶层若干层,避免梯度爆炸;
- label_mode='int' 是显式安全写法(默认即为此模式),但明确声明可增强可读性;
- to_categorical() 的 num_classes 参数必须显式指定(如 num_classes=3),否则可能推断错误;
- 若改用 sparse_categorical_crossentropy 损失函数,则无需 one-hot 编码,但需保持 Dense(3) + softmax,且标签仍为整数——此时应将 loss 改为 "sparse_categorical_crossentropy",并确保 metrics 兼容("accuracy" 仍适用)。
通过统一标签表示形式与损失函数语义,即可彻底解决输入形状不兼容问题,顺利完成从二分类到多分类的迁移学习升级。

















