迁移学习模型K折交叉验证遇TypeError报错,求解决方案
迁移学习模型K折交叉验证报错解决
问题背景
在图像分类任务中,基于迁移学习搭建的模型单独运行正常,但执行K折交叉验证时出现报错。使用tf.keras.utils.image_dataset_from_directory加载数据并拆分图像和标签,相关代码及报错信息如下:
K折交叉验证代码
# Model configuration batch_size = 32 img_width, img_height, img_num_channels = 32, 32, 3 loss_function = sparse_categorical_crossentropy no_classes = 5 no_epochs = 10 optimizer = Adam() verbosity = 1 num_folds = 10 # Define the K-fold Cross Validator kfold = KFold(n_splits=num_folds, shuffle=True) # K-fold Cross Validation model evaluation fold_no = 1 for train, test in kfold.split(inputs, targets): # Load Model base_model = keras.applications.vgg19.VGG19( include_top=False, weights='imagenet', input_shape=(224,224,3) ) # Freeze base_model base_model.trainable = False # inputs = keras.Input(shape=(224,224,3)) x = data_augmentation(inputs) #apply data augmentation # Preprocessing x = tf.keras.applications.vgg19.preprocess_input(x) # The base model contains batchnorm layers. We want to keep them in inference mode # when we unfreeze the base model for fine-tuning, so we make sure that the # base_model is running in inference mode here. x = base_model(x, training=False) x = keras.layers.GlobalAveragePooling2D()(x) x = keras.layers.Dropout(0.2)(x) # Regularize with dropout outputs = keras.layers.Dense(5, activation="softmax")(x) model = keras.Model(inputs, outputs) model.compile( loss=loss_function, optimizer=optimizer, metrics=['accuracy']) # Generate a print print('------------------------------------------------------------------------') print(f'Training for fold {fold_no} ...') hist = model.fit(inputs[train], targets[train], batch_size=batch_size, epochs=no_epochs) base_model.trainable = True model.compile( optimizer=keras.optimizers.Adam(learning_rate=0.00001), # Low learning rate loss=loss_function, metrics=['accuracy']) hist_2 = model.fit(inputs[train], targets[train], batch_size=batch_size, epochs=no_epochs) # Generate generalization metrics scores = model.evaluate(inputs[test], targets[test], verbose=0) print(f'Score for fold {fold_no}: {model.metrics_names[0]} of {scores[0]}; {model.metrics_names[1]} of {scores[1]*100}%') acc_per_fold.append(scores[1] * 100) loss_per_fold.append(scores[0]) # Increase fold number fold_no = fold_no + 1
首次报错信息
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) /usr/local/lib/python3.8/dist-packages/sklearn/utils/validation.py in _num_samples(x) 277 try: --> 278 return len(x) 279 except TypeError as type_error: 6 frames /usr/local/lib/python3.8/dist-packages/keras/engine/keras_tensor.py in __len__(self) 220 def __len__(self): --> 221 raise TypeError('Keras symbolic inputs/outputs do not ' 222 'implement `__len__`. You may be ' TypeError: Keras symbolic inputs/outputs do not implement `__len__`. You may be trying to pass Keras symbolic inputs/outputs to a TF API that does not register dispatching, preventing Keras from automatically converting the API call to a lambda layer in the Functional Model. This error will also get raised if you try asserting a symbolic input/output directly. The above exception was the direct cause of the following exception: TypeError Traceback (most recent call last) <ipython-input-37-6292815144e5> in <module> 1 # K-fold Cross Validation model evaluation 2 fold_no = 1 ----> 3 for train, test in kfold.split(inputs, targets): 4 5 # Load Model /usr/local/lib/python3.8/dist-packages/sklearn/model_selection/_split.py in split(self, X, y, groups) 328 The testing set indices for that split. 329 """ --> 330 X, y, groups = indexable(X, y, groups) 331 n_samples = _num_samples(X) 332 if self.n_splits > n_samples: /usr/local/lib/python3.8/dist-packages/sklearn/utils/validation.py in indexable(*iterables) 376 377 result = [_make_indexable(X) for X in iterables] --> 378 check_consistent_length(*result) 379 return result 380 /usr/local/lib/python3.8/dist-packages/sklearn/utils/validation.py in check_consistent_length(*arrays) 327 """ 328 --> 329 lengths = [_num_samples(X) for X in arrays if X is not None] 330 uniques = np.unique(lengths) 331 if len(uniques) > 1: /usr/local/lib/python3.8/dist-packages/sklearn/utils/validation.py in <listcomp>(.0) 327 """ 328 --> 329 lengths = [_num_samples(X) for X in arrays if X is not None] 330 uniques = np.unique(lengths) 331 if len(uniques) > 1: /usr/local/lib/python3.8/dist-packages/sklearn/utils/validation.py in _num_samples(x) 278 return len(x) 279 except TypeError as type_error: --> 280 raise TypeError(message) from type_error 281 282 TypeError: Expected sequence or array-like, got <class 'keras.engine.keras_tensor.KerasTensor'>
修正数据加载后新报错信息
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg19/vgg19_weights_tf_dim_ordering_tf_kernels_notop.h5 80134624/80134624 [==============================] - 4s 0us/step ------------------------------------------------------------------------ Training for fold 1 ... --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-17-6292815144e5> in <module> 38 print(f'Training for fold {fold_no} ...') 39 --> 40 hist = model.fit(inputs[train], targets[train], 41 batch_size=batch_size, 42 epochs=no_epochs) 2 frames /usr/local/lib/python3.8/dist-packages/keras/utils/traceback_utils.py in error_handler(*args, **kwargs) 65 except Exception as e: # pylint: disable=broad-except 66 filtered_tb = _process_traceback_frames(e.__traceback__) --> 67 raise e.with_traceback(filtered_tb) from None 68 finally: 69 del filtered_tb TypeError: Exception encountered when calling layer "tf.__operators__.getitem_1" (type SlicingOpLambda). Only integers, slices (`:`), ellipsis (`...`), tf.newaxis (`None`) and scalar tf.int32/tf.int64 tensors are valid indices, got array([ 0, 1, 2, ..., 2872, 2873, 2874]) Call arguments received by layer "tf.__operators__.getitem_1" (type SlicingOpLambda): • tensor=tf.Tensor(shape=(None, 224, 224, 3), dtype=float32) • slice_spec=array([ 0, 1, 2, ..., 2872, 2873, 2874]) • var=None
数据加载代码
train_ds = tf.keras.utils.image_dataset_from_directory( directory ='/gdrive/My Drive/Flies_dt/224x224', validation_split=0.4, subset="training", seed=123, image_size=(224, 224), batch_size=32) val_ds = tf.keras.utils.image_dataset_from_directory( directory ='/gdrive/My Drive/Flies_dt/224x224', validation_split=0.4, subset="validation", seed=123, image_size=(224, 224), batch_size=32) train_images = np.concatenate(list(train_ds.map(lambda x, y:x))) train_labels = np.concatenate(list(train_ds.map(lambda x, y:y))) val_images = np.concatenate(list(val_ds.map(lambda x, y:x))) val_labels = np.concatenate(list(val_ds.map(lambda x, y:y))) inputs = np.concatenate((train_images, val_images), axis=0) targets = np.concatenate((train_labels, val_labels), axis=0)
报错原因及解决步骤
1. 首次报错:变量名冲突
- 原因:循环内部重新定义
inputs = keras.Input(shape=(224,224,3)),覆盖了外部存储训练数据的numpy数组inputs,导致kfold.split()接收的是Keras张量而非数组,触发类型错误。 - 解决:修改模型输入层的变量名,避免与数据变量冲突,例如改为
input_layer:# 替换原代码中的inputs定义 input_layer = keras.Input(shape=(224,224,3)) x = data_augmentation(input_layer) # 应用数据增强 # 后续代码中所有对应的inputs都替换为input_layer model = keras.Model(input_layer, outputs)
2. 第二次报错:索引逻辑混淆
- 原因:变量名冲突修正后,
model.fit中使用的inputs本应是numpy数组,但由于之前的变量覆盖问题,代码逻辑仍将Keras张量作为数据传入,导致切片索引不兼容。 - 解决:确保循环内模型输入层变量与数据变量完全隔离,训练时直接使用numpy数组切片,或转换为TensorFlow数据集(大场景下更高效):
# 方式1:直接使用numpy数组切片 hist = model.fit(inputs[train_idx], targets[train_idx], batch_size=batch_size, epochs=no_epochs) # 方式2:转换为TF数据集(内存友好) train_ds_fold = tf.data.Dataset.from_tensor_slices((inputs[train_idx], targets[train_idx])).batch(batch_size) hist = model.fit(train_ds_fold, epochs=no_epochs)
3. 其他关键优化
- 初始化存储列表:在循环前必须初始化
acc_per_fold和loss_per_fold,避免列表未定义报错:acc_per_fold = [] loss_per_fold = [] - 模型复用优化:将模型构建封装为函数,避免每个fold重复加载VGG19权重,提升效率:
def build_model(): base_model = keras.applications.vgg19.VGG19( include_top=False, weights='imagenet', input_shape=(224,224,3) ) base_model.trainable = False input_layer = keras.Input(shape=(224,224,3)) x = data_augmentation(input_layer) x = tf.keras.applications.vgg19.preprocess_input(x) x = base_model(x, training=False) x = keras.layers.GlobalAveragePooling2D()(x) x = keras.layers.Dropout(0.2)(x) outputs = keras.layers.Dense(5, activation="softmax")(x) return keras.Model(input_layer, outputs)
修正后的完整代码示例
import numpy as np import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers from sklearn.model_selection import KFold from tensorflow.keras.optimizers import Adam from tensorflow.keras.losses import sparse_categorical_crossentropy # 数据加载 train_ds = tf.keras.utils.image_dataset_from_directory( directory ='/gdrive/My Drive/Flies_dt/224x224', validation_split=0.4, subset="training", seed=123, image_size=(224, 224), batch_size=32) val_ds = tf.keras.utils.image_dataset_from_directory( directory ='/gdrive/My Drive/Flies_dt/224x224', validation_split=0.4, subset="validation", seed=123, image_size=(224, 224), batch_size=32) train_images = np.concatenate(list(train_ds.map(lambda x, y:x))) train_labels = np.concatenate(list(train_ds.map(lambda x, y:y))) val_images = np.concatenate(list(val_ds.map(lambda x, y:x))) val_labels = np.concatenate(list(val_ds.map(lambda x, y:y))) inputs = np.concatenate((train_images, val_images), axis=0) targets = np.concatenate((train_labels, val_labels), axis=0) # 数据增强(根据实际需求调整) data_augmentation = keras.Sequential( [ layers.RandomFlip("horizontal"), layers.RandomRotation(0.1), ] ) # 模型配置 batch_size = 32 no_classes = 5 no_epochs = 10 optimizer = Adam() verbosity = 1 num_folds = 10 # 初始化结果存储列表 acc_per_fold = [] loss_per_fold = [] # 定义K折验证器 kfold = KFold(n_splits=num_folds, shuffle=True, random_state=123) # 模型构建函数 def build_model(): base_model = keras.applications.vgg19.VGG19( include_top=False, weights='imagenet', input_shape=(224,224,3) ) base_model.trainable = False input_layer = keras.Input(shape=(224,224,3)) x = data_augmentation(input_layer) x = tf.keras.applications.vgg19.preprocess_input(x) x = base_model(x, training=False) x = keras.layers.GlobalAveragePooling2D()(x) x = keras.layers.Dropout(0.2)(x) outputs = keras.layers.Dense(5, activation="softmax")(x) return keras.Model(input_layer, outputs) # K折交叉验证主循环 fold_no = 1 for train_idx, test_idx in kfold.split(inputs, targets): # 构建新模型 model = build_model() # 编译冻结阶段模型 model.compile( loss=sparse_categorical_crossentropy, optimizer=optimizer, metrics=['accuracy']) # 打印fold信息 print('------------------------------------------------------------------------') print(f'Training for fold {fold_no} ...') # 训练冻结阶段 hist = model.fit(inputs[train_idx], targets[train_idx], batch_size=batch_size, epochs=no_epochs, verbose=verbosity) # 解冻基础模型 model.get_layer('vgg19').trainable = True # 编译微调阶段模型 model.compile( optimizer=keras.optimizers.Adam(learning_rate=1e-5), loss=sparse_categorical_crossentropy, metrics=['accuracy']) # 训练微调阶段 hist_2 = model.fit(inputs[train_idx], targets[train_idx], batch_size=batch_size, epochs=no_epochs, verbose=verbosity) # 评估模型 scores = model.evaluate(inputs[test_idx], targets[test_idx], verbose=0) print(f'Score for fold {fold_no}: {model.metrics_names
相关产品推荐
相关产品推荐

