迁移学习中如何向预训练CNN模型添加新卷积层?
迁移学习新增卷积层实现方案
核心结论
- 迁移学习场景下完全可以在预训练模型基础上新增卷积层,你遇到的报错和功能可行性无关,纯粹是代码写法问题。
报错原因
你第一次运行触发ValueError: Input 0 of layer "conv2d_11" is incompatible with the layer: expected min_ndim=4, found ndim=2的原因非常明确:
你取的截断点是
base_model.layers[-3].output,对应原预训练模型里的Dense(256, activation='relu')层,这层在Flatten操作之后,输出是形状为(None, 256)的二维张量,而Conv2D卷积层要求输入必须是包含批次、高度、宽度、通道数的四维张量,维度不匹配自然报错。
另外你第一版代码还有两处笔误:第一个MaxPool2D行末尾多打了多余的s,还有两处把MaxPool2D错写成了xPool2D,就算层位置取对这两个问题也会导致运行失败。
正确实现方法
核心原则很简单:接新卷积层的截断位置,必须选在Flatten层之前、输出四维特征图的卷积/池化层,不能选Flatten之后的全连接层。两种常用实现方式如下:
方式1:Sequential顺序堆叠(已验证可用)
直接把预训练模型的卷积特征提取部分截断拿出来,冻结权重后顺序堆叠新的卷积层、池化层,最后接适配新任务分类数的全连接头即可。
import tensorflow as tf from tensorflow.keras.models import load_model, Sequential from tensorflow.keras.layers import Conv2D, MaxPool2D, Flatten, Dense, Dropout import os # 基础配置 input_shape = (224, 224, 3) num_classes = 2000 # 加载本地预训练模型 model_dir = r'D:\00_twm_cnn_model\02_prt\02_outputs' os.chdir(model_dir) base_model = load_model(r'Model_CNN_VGG-16_chi_prt_100_2022-06-15_1.h5') new_model = Sequential() # 截断预训练模型:去掉原模型最后6层(分类头全连接、Dropout、Flatten层),保留全部卷积特征提取部分 for layer in base_model.layers[0:-6]: # 冻结预训练层权重,初始训练阶段不更新 layer.trainable = False new_model.add(layer) # 堆叠新增的卷积块 new_model.add(Conv2D(512, kernel_size=(3,3), activation='relu', padding='same', name='conv2d_10')) new_model.add(Conv2D(512, kernel_size=(3,3), activation='relu', padding='same', name='conv2d_11')) new_model.add(Conv2D(512, kernel_size=(3,3), activation='relu', padding='same', name='conv2d_12')) new_model.add(MaxPool2D(pool_size=(2,2), strides=(2,2), name='max_pooling2d_4')) new_model.add(Conv2D(512, kernel_size=(3,3), activation='relu', padding='same', name='conv2d_13')) new_model.add(Conv2D(512, kernel_size=(3,3), activation='relu', padding='same', name='conv2d_14')) new_model.add(Conv2D(512, kernel_size=(3,3), activation='relu', padding='same', name='conv2d_15')) new_model.add(MaxPool2D(pool_size=(2,2), strides=(2,2), name='max_pooling2d_5')) # 堆叠新的分类头 new_model.add(Flatten()) new_model.add(Dense(128, activation='relu')) new_model.add(Dropout(0.1)) new_model.add(Dense(256, activation='relu')) new_model.add(Dropout(0.1)) new_model.add(Dense(num_classes, activation='softmax')) # 编译即可开始训练 new_model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) new_model.summary()
方式2:函数式API灵活构建
如果需要做多分支、残差连接等复杂结构,可以用函数式API实现,截断逻辑和顺序模型完全一致。
# 加载预训练模型后先冻结全部预训练层 for layer in base_model.layers: layer.trainable = False # 截断点选原模型最后一个MaxPool2D层(Flatten的上一层,输出为四维特征图) x = base_model.get_layer('max_pooling2d_3').output # 接自定义卷积结构 x = Conv2D(256, kernel_size=(3,3), activation='relu', padding='same', kernel_initializer='glorot_normal')(x) x = MaxPool2D(pool_size=(2,2), strides=(2,2))(x) x = Conv2D(512, kernel_size=(3,3), activation='relu', padding='same')(x) x = Conv2D(512, kernel_size=(3,3), activation='relu', padding='same')(x) x = Conv2D(512, kernel_size=(3,3), activation='relu', padding='same')(x) x = MaxPool2D(pool_size=(2,2), strides=(2,2))(x) # 接分类头 x = Flatten()(x) x = Dense(1024, activation='relu')(x) output = Dense(num_classes, activation='softmax')(x) # 构建最终模型 new_model = tf.keras.Model(inputs=base_model.inputs, outputs=output)
实践注意事项
- 截断模型前一定要先打印
base_model.summary()核对每一层的输出形状,确保接卷积层的位置输出维度符合要求,不要凭索引瞎猜层位置。 - 训练初期建议先冻结全部预训练层权重,等新增的卷积层和分类头权重收敛后,再根据验证集效果解冻部分顶层卷积层,用小学习率微调,精度会更高。
- 堆叠新卷积层和池化层时要注意特征图尺寸变化,如果池化步长太大导致特征图高宽被压缩到0,也会触发维度报错,可以适当减少池化层数或者调整卷积填充参数。
内容的提问来源于stack exchange,提问作者Jake Ma
相关产品推荐
相关产品推荐

