使用K折交叉验证训练DenseNet121预训练模型时遭遇TypeError问题求助
我最近在做乳腺癌图像分类任务,用预训练的DenseNet121搭建了模型,数据集也分成了训练、测试和验证集。现在想加入K折交叉验证提升模型可靠性,用了sklearn的cross_validation工具,但运行代码时碰到了下面这个TypeError,试了好几种方法都没解决,有没有大佬能指点一下?
我的模型代码:
import tensorflow as tf from tensorflow.keras.layers import Flatten, Dense, Input from tensorflow.keras.models import Model from sklearn import cross_validation # 构建预训练模型 in_model = tf.keras.applications.DenseNet121( input_shape=(224,224,3), include_top=False, weights='imagenet', classes = 2 ) in_model.trainable = False # 搭建自定义头部 inputs = tf.keras.Input(shape=(224,224,3)) x = in_model(inputs) flat = Flatten()(x) dense_1 = Dense(1024,activation = 'relu')(flat) dense_2 = Dense(1024,activation = 'relu')(dense_1) prediction = Dense(2,activation = 'softmax')(dense_2) in_pred = Model(inputs = inputs,outputs = prediction) # 编译模型 in_pred.compile( optimizer = tf.keras.optimizers.Adagrad(learning_rate=0.0002), loss=tf.keras.losses.CategoricalCrossentropy(from_logits = False), metrics=['accuracy'] ) # 尝试K折交叉验证 model_result=cross_validation(in_pred, train_data, train_labels, 5)
报错信息:
TypeError: Cannot clone object '<keras.engine.functional.Functional object at 0x000001F82E17E3A0>'
(type <class 'keras.engine.functional.Functional'>):
it does not seem to be a scikit-learn estimator as it does not implement a 'get_params' method.
问题原因
sklearn的交叉验证工具是为sklearn自身的estimator类设计的,而我们用Keras搭建的模型属于Keras的Functional模型,并没有实现sklearn要求的get_params()和set_params()方法,所以直接传入会报错。
解决办法
这里有两种常用的解决思路:
方法一:用KerasClassifier包装Keras模型
tensorflow提供了tf.keras.wrappers.scikit_learn.KerasClassifier(回归任务用KerasRegressor),可以把Keras模型包装成sklearn兼容的estimator,这样就能直接用sklearn的交叉验证工具了。
修改后的代码示例:
from tensorflow.keras.wrappers.scikit_learn import KerasClassifier from sklearn.model_selection import cross_val_score # 先把模型构建逻辑封装成函数 def build_model(): in_model = tf.keras.applications.DenseNet121( input_shape=(224,224,3), include_top=False, weights='imagenet', classes = 2 ) in_model.trainable = False inputs = tf.keras.Input(shape=(224,224,3)) x = in_model(inputs) flat = Flatten()(x) dense_1 = Dense(1024,activation = 'relu')(flat) dense_2 = Dense(1024,activation = 'relu')(dense_1) prediction = Dense(2,activation = 'softmax')(dense_2) model = Model(inputs = inputs,outputs = prediction) model.compile( optimizer = tf.keras.optimizers.Adagrad(learning_rate=0.0002), loss=tf.keras.losses.CategoricalCrossentropy(from_logits = False), metrics=['accuracy'] ) return model # 包装成sklearn estimator estimator = KerasClassifier(build_fn=build_model, epochs=3, batch_size=32) # 执行K折交叉验证 model_result = cross_val_score(estimator, train_data, train_labels, cv=5) print(f"5折交叉验证准确率结果: {model_result}") print(f"平均准确率: {model_result.mean():.4f}")
方法二:手动实现K折交叉验证
如果不想依赖sklearn的包装器,也可以手动拆分数据集,循环训练每一轮的模型,这种方式更灵活,适合自定义训练流程(比如加入早停、模型保存等)。
示例代码:
from sklearn.model_selection import KFold import numpy as np # 初始化K折拆分器(shuffle=True保证数据打乱) kf = KFold(n_splits=5, shuffle=True, random_state=42) fold_scores = [] # 循环每一轮折 for fold, (train_idx, val_idx) in enumerate(kf.split(train_data)): print(f"===== 第 {fold+1} 折训练 =====") # 拆分当前折的训练和验证数据 X_train_fold, X_val_fold = train_data[train_idx], train_data[val_idx] y_train_fold, y_val_fold = train_labels[train_idx], train_labels[val_idx] # 每折都重新构建模型,避免参数复用 def build_model(): in_model = tf.keras.applications.DenseNet121( input_shape=(224,224,3), include_top=False, weights='imagenet', classes = 2 ) in_model.trainable = False inputs = tf.keras.Input(shape=(224,224,3)) x = in_model(inputs) flat = Flatten()(x) dense_1 = Dense(1024,activation = 'relu')(flat) dense_2 = Dense(1024,activation = 'relu')(dense_1) prediction = Dense(2,activation = 'softmax')(dense_2) model = Model(inputs = inputs,outputs = prediction) model.compile( optimizer = tf.keras.optimizers.Adagrad(learning_rate=0.0002), loss=tf.keras.losses.CategoricalCrossentropy(from_logits = False), metrics=['accuracy'] ) return model model = build_model() # 训练当前折的模型 history = model.fit( X_train_fold, y_train_fold, epochs=3, batch_size=32, validation_data=(X_val_fold, y_val_fold), verbose=1 ) # 记录当前折的最终验证准确率 fold_scores.append(history.history['val_accuracy'][-1]) # 输出所有折的准确率统计 print(f"\n5折交叉验证结果: {[round(score,4) for score in fold_scores]}") print(f"平均准确率: {np.mean(fold_scores):.4f} ± {np.std(fold_scores):.4f}")
备注:内容来源于stack exchange,提问作者Eda

