最稳妥方式是用tf.keras.Sequential搭建CNN,严格遵循卷积块顺序、避免过早Flatten、正确匹配loss与标签格式、统一用SavedModel保存。

用 tf.keras.Sequential 搭建基础CNN结构最稳妥
TensorFlow 2.x 默认启用 Eager Execution,tf.keras.Sequential 是最直接、出错率最低的构建方式。它天然适配函数式 API 的底层逻辑,且调试时每层输出形状清晰可见。
常见错误是过早添加 Flatten 层——必须等所有卷积/池化完成、特征图尺寸稳定后再压平;否则会报 ValueError: Input 0 of layer flatten is incompatible with the layer。
实操建议:
- 卷积块按
Conv2D → BatchNormalization → ReLU → MaxPooling2D顺序组织,避免在池化后接 BN(无意义) - 最后全连接前用
GlobalAveragePooling2D()替代Flatten() + Dense(...),更鲁棒、参数更少 - 输入层不用显式定义——
model.build(input_shape=(None, 224, 224, 3))可延后触发,方便调试中间 shape
ImageDataGenerator 的 rescale 和 preprocessing_function 别混用
很多用户同时设置 rescale=1./255 和自定义 preprocessing_function(比如调用 tf.keras.applications.resnet50.preprocess_input),结果图像被重复归一化,模型完全不收敛。
立即学习“Python免费学习笔记(深入)”;
根本原因:二者作用域重叠——rescale 是像素级线性缩放,而 preprocessing_function 通常包含减均值、按通道缩放等更复杂的操作(如 ResNet 要求 [0, 255] 输入,而非 [0, 1])。
正确做法:
- 用预训练 backbone(如
ResNet50(weights='imagenet'))时,只设preprocessing_function=tf.keras.applications.resnet50.preprocess_input,rescale必须为None - 从头训练小网络时,用
rescale=1./255即可,无需额外预处理 - 验证集生成器必须和训练集使用**完全一致**的预处理逻辑,否则评估失真
编译时选错 loss 会导致 categorical_accuracy 始终为 0.0
这是新手高频踩坑点:标签用了 to_categorical(one-hot 编码),却选了 sparse_categorical_crossentropy;或反过来,标签是整数索引,却用 categorical_crossentropy。两者不匹配时,梯度爆炸或恒为零,准确率卡死。
判断依据很简单:看 y_train 的 shape。若为 (N, num_classes),必须用 categorical_crossentropy;若为 (N,) 且 dtype 是 int64,则用 sparse_categorical_crossentropy。
其他关键点:
-
optimizer优先用Adam(learning_rate=1e-3),比 SGD 更稳;调学习率别用lr_schedule类,先用ReduceLROnPlateau监控val_loss - 务必加
metrics=['accuracy'],否则model.evaluate()返回的 accuracy 是 0(TF 2.9+ 默认不自动挂 metrics) - 如果数据不平衡,
class_weight比改 loss 更直观有效
保存模型必须用 save_format='tf',别碰 HDF5
TensorFlow 2.x 默认保存格式是 SavedModel(即 save_format='tf'),它完整序列化计算图、变量、签名,支持跨平台部署和 tf.lite 转换。而 model.save('xxx.h5') 强制走 HDF5,会丢失自定义层、动态控制流,加载时报 Unknown layer 或 Failed to load model。
尤其当你用了 Lambda 层、tf.image 预处理、或自定义 call 方法时,HDF5 根本存不住逻辑。
安全做法:
- 统一用
model.save('my_cnn', save_format='tf')(注意路径不带扩展名) - 加载时用
tf.keras.models.load_model('my_cnn'),不要加compile=False除非你确定要重编译 - 想转 TFLite?必须从 SavedModel 加载后,再用
tf.lite.TFLiteConverter.from_saved_model,HDF5 不支持
真正麻烦的不是写几行代码,而是训练好模型后发现没法导出、没法上生产——SavedModel 是唯一能覆盖训练、推理、量化全链路的格式。


















