Python K折交叉验证训练模型时Pandas iloc索引越界报错
问题根因
该iloc越界报错和DataFrame数据缺失无关,是两个开发高频疏漏叠加导致:
- 第一,KFold切分折内数据后未重置索引:使用
df.iloc[train_idx]切分得到的训练/验证子集,会保留原全量DataFrame的全局索引标签,而非从0开始的连续位置索引。iloc是按行位置序号而非索引标签取值,当生成器内从0开始递增的迭代序号超过子集实际行数对应的最大位置时,就会触发越界。典型触发场景:全量数据集共1000行,索引范围0999,某折训练集切出900个样本,对应原索引100999。未重置索引的train_df实际行位置范围是0~899,当迭代序号到900时,
iloc[900]超出最大位置阈值直接报错,和子集内是否存够样本无关。 - 第二,每epoch迭代步数计算错误:自定义生成器的
__len__方法返回值偏大,或手动给model.fit传入的steps_per_epoch大于折内数据集实际可生成的最大批次数,导致生成器迭代到超出样本总量的序号,触发越界。
修复步骤
按以下顺序修改即可彻底解决:
- 切分折内数据后强制重置连续索引
每次K折切分完成后,对训练、验证子集做索引重置,丢弃原全局索引:
from sklearn.model_selection import KFold import numpy as np import pandas as pd import tensorflow as tf from tensorflow.keras.applications import ResNet50 kf = KFold(n_splits=5, shuffle=True, random_state=42) for train_idx, val_idx in kf.split(df): # 核心:切分后立刻重置索引,drop=True丢弃原索引列 train_df = df.iloc[train_idx].reset_index(drop=True) val_df = df.iloc[val_idx].reset_index(drop=True) # 再将子集传入对应数据生成器 train_gen = DataGenerator(train_df, batch_size=32) val_gen = DataGenerator(val_df, batch_size=32) # 模型训练逻辑
- 正确实现自定义Sequence生成器的长度计算逻辑
不要硬编码样本量、不要引用全量数据集长度计算批次数,基于当前传入的子集长度动态计算:
class DataGenerator(tf.keras.utils.Sequence): def __init__(self, df, batch_size=32, img_size=(224,224), shuffle=True): self.df = df self.batch_size = batch_size self.img_size = img_size self.shuffle = shuffle self.n_samples = len(self.df) self.on_epoch_end() def __len__(self): # 向上取整计算总批次数,覆盖最后一个不足batch_size的批次 return int(np.ceil(self.n_samples / self.batch_size)) def __getitem__(self, index): # 按位置取当前批次的样本索引,不会出现越界 batch_indices = self.indexes[index*self.batch_size : (index+1)*self.batch_size] batch_data = self.df.iloc[batch_indices] # 此处补全图像读取、预处理、标签转换的原有逻辑即可 X = [] y = [] for _, row in batch_data.iterrows(): img = tf.keras.utils.load_img(row['img_path'], target_size=self.img_size) img = tf.keras.utils.img_to_array(img) X.append(img) y.append(row['label']) return np.array(X), np.array(y) def on_epoch_end(self): self.indexes = np.arange(self.n_samples) if self.shuffle: np.random.shuffle(self.indexes)
- 训练时不要手动硬编码迭代步数
调用model.fit时,直接传入继承了Sequence的生成器实例即可,无需手动指定steps_per_epoch、validation_steps参数,TensorFlow会自动调用生成器的__len__方法获取正确的迭代步数,从根源避免步数算多导致的越界:
model = ResNet50(input_shape=(224,224,3), weights=None, classes=num_classes) model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) model.fit( train_gen, validation_data=val_gen, epochs=10, # 不要手动传steps_per_epoch、validation_steps )
额外排查项
如果完成上述修改后仍触发同类报错,检查两个细节:
- 如果生成器内有样本过滤逻辑(比如自动剔除读取失败的损坏图像),过滤完成后必须同步更新
self.n_samples和self.indexes的长度,不要保留已剔除样本的索引 - 不要为了绕开越界把
iloc换成loc,该修改会导致位置索引和标签索引错位,最终出现标签和图像不匹配的问题,训练得到的模型完全不可用。
内容的提问来源于stack exchange,提问作者Jimut123
相关产品推荐
相关产品推荐

