TensorFlow图像模型训练报错:ValueError序列长度为0请求检索元素0
问题描述
使用TensorFlow在图像数据集上训练模型,已反复确认路径无误,但持续报错:
ValueError: Asked to retrieve element 0, but the Sequence has length 0
以下是使用的代码:
import tensorflow as tf import os import xml.etree.ElementTree as ET from PIL import Image # 定义训练和验证数据文件夹路径 train_folder = 'C:/Users/UnitechSolutions/Desktop/python projects/1st assignment/tensorflow/dataset/train' val_folder = 'C:/Users/UnitechSolutions/Desktop/python projects/1st assignment/tensorflow/dataset/val' # 设置变量 saved_model_path = 'saved_model.h5' image_size = (224, 224) batch_size = 32 epochs = 20 num_classes = 21 # 定义类别 classes = ['7up 1.5 liters', '7up 250ml', '7up 500ml', '7up Diet 500ml', '7up Diet TIncan_250ml', '7up Tincan 250ml', 'Dew 1.5Liters', 'Dew 250ml', 'Dew 500ml', 'Dew Tincan 250ml', 'Empty', 'Mirinda 1.5 liters', 'Mirinda 250ml', 'Mirinda 500ml', 'Mirinda Tincan_250ml', 'Pepsi 1.5 Liters', 'Pepsi 250ml', 'Pepsi 500ml', 'Pepsi Tincan 250ml', 'Sting Red 250ml', 'Sting Red 500ml'] num_classes = len(classes) # 预训练权重路径 pretrained_weights_path = 'C:/Users/UnitechSolutions/Desktop/python projects/1st assignment/tensorflow/' # 保存模型路径 saved_model_path = 'C:/Users/UnitechSolutions/Desktop/python projects/1st assignment/tensorflow/' # 解析XML标注的函数 def parse_annotation(annotation_path): tree = ET.parse(annotation_path) root = tree.getroot() # 获取图像尺寸 size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) # 获取每个目标的边界框 boxes = [] labels = [] for obj in root.findall('object'): label = obj.find('name').text if label not in classes: continue label_index = classes.index(label) bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) boxes.append([xmin / width, ymin / height, xmax / width, ymax / height]) labels.append(label_index) return boxes, labels len(train_folder) len(val_folder) # 预处理图像的函数 def preprocess_image(image_path): image = Image.open(image_path) image = image.resize(image_size) image = tf.keras.preprocessing.image.img_to_array(image) image = tf.keras.applications.resnet50.preprocess_input(image) return image # 加载数据的函数 def load_data(data_folder): images = [] boxes_list = [] labels_list = [] for filename in os.listdir(data_folder): if filename.endswith('.jpg'): image_path = os.path.join(data_folder, filename) annotation_path = os.path.join(data_folder, filename[:-4] + '.xml') boxes, labels = parse_annotation(annotation_path) images.append(preprocess_image(image_path)) boxes_list.append(boxes) labels_list.append(labels) return images, boxes_list, labels_list # 加载训练和验证数据 train_images, train_boxes_list, train_labels_list = load_data(train_folder) val_images, val_boxes_list, val_labels_list = load_data(val_folder) # 定义模型 base_model = tf.keras.applications.ResNet50( include_top=False, weights='imagenet', input_shape=image_size + (3,) ) x = base_model.output x = tf.keras.layers.GlobalAveragePooling2D()(x) x = tf.keras.layers.Dense(1024, activation='relu')(x) x = tf.keras.layers.Dense(1024, activation='relu')(x) predictions = tf.keras.layers.Dense(num_classes, activation='softmax')(x) model = tf.keras.models.Model(inputs=base_model.input, outputs=predictions) # 编译模型 model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001), loss='sparse_categorical_crossentropy', metrics=['accuracy']) # 定义回调函数 checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(filepath=saved_model_path, save_best_only=True) early_stopping_callback = tf.keras.callbacks.EarlyStopping(patience=5) # 定义数据生成器 train_data_generator = tf.keras.preprocessing.image.ImageDataGenerator( rescale=1./255, horizontal_flip=True, zoom_range=0.1 ) val_data_generator = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255) # 从目录生成数据 train_flow_from_directory = train_data_generator.flow_from_directory( train_folder, target_size=image_size, batch_size=batch_size, classes=None, class_mode='sparse', shuffle=True, seed=42 ) val_flow_from_directory = val_data_generator.flow_from_directory( val_folder, target_size=image_size, batch_size=batch_size, classes=None, class_mode='sparse', shuffle=True, seed=42 ) # 训练模型 history = model.fit( train_flow_from_directory, epochs=epochs, validation_data=val_flow_from_directory, callbacks=[checkpoint_callback, early_stopping_callback] )
问题分析与解决方法
核心错误原因
报错本质是数据生成器没有加载到任何数据,根源在于**flow_from_directory的使用不符合要求**:
该函数要求数据集目录必须遵循「根目录/类别子目录/图像文件」的结构,比如:
train/ ├── 7up 1.5 liters/ │ ├── img1.jpg │ ├── img2.jpg ├── 7up 250ml/ │ ├── img3.jpg │ ...
但你的代码直接将存放图像和XML的train_folder/val_folder传给它,没有按类别分文件夹,导致生成器无法识别有效数据。
此外还有几个次要问题:
- 同时实现了手动加载数据的
load_data函数和flow_from_directory,但训练仅用了后者,手动加载代码完全冗余。 saved_model_path被重复赋值为文件夹路径,而ModelCheckpoint需要具体文件路径(如xxx.h5),会导致模型保存失败。parse_annotation函数未做异常处理,若XML缺失size或bndbox节点会直接报错。
修复步骤
方案一:调整目录结构适配flow_from_directory
- 按类别为
train/val目录创建子文件夹,将同类图像移入对应子文件夹(XML可保留原位置或同步移动,flow_from_directory无需XML,会自动按文件夹名标注类别)。 - 修正
saved_model_path:saved_model_path = 'C:/Users/UnitechSolutions/Desktop/python projects/1st assignment/tensorflow/saved_model.h5' - 删除冗余的
load_data、preprocess_image、parse_annotation及数据加载代码,flow_from_directory会自动处理图像加载与预处理。
方案二:放弃flow_from_directory,使用手动加载的数据训练
若不想调整目录结构,可基于自己写的load_data函数训练:
- 将手动加载的数据转换为TensorFlow数据集格式(假设单图像单目标,若为多目标需改用目标检测模型):
# 转换训练数据 train_images = tf.convert_to_tensor(train_images, dtype=tf.float32) train_labels = tf.convert_to_tensor([label[0] for label in train_labels_list], dtype=tf.int32) train_dataset = tf.data.Dataset.from_tensor_slices((train_images, train_labels)).shuffle(1000).batch(batch_size) # 转换验证数据 val_images = tf.convert_to_tensor(val_images, dtype=tf.float32) val_labels = tf.convert_to_tensor([label[0] for label in val_labels_list], dtype=tf.int32) val_dataset = tf.data.Dataset.from_tensor_slices((val_images, val_labels)).batch(batch_size) - 修改
model.fit的输入:history = model.fit( train_dataset, epochs=epochs, validation_data=val_dataset, callbacks=[checkpoint_callback, early_stopping_callback] ) - 同样修正
saved_model_path的路径问题。
额外注意事项
- 若你的任务是目标检测(需预测边界框+类别),当前的分类模型完全不适用,需改用YOLO、Faster R-CNN等专门的检测模型架构。
- 为避免
parse_annotation函数报错,可添加异常处理:def parse_annotation(annotation_path): try: tree = ET.parse(annotation_path) root = tree.getroot() size = root.find('size') if size is None: return [], [] width = int(size.find('width').text) height = int(size.find('height').text) boxes = [] labels = [] for obj in root.findall('object'): label = obj.find('name').text if label not in classes: continue label_index = classes.index(label) bbox = obj.find('bndbox') if bbox is None: continue xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) boxes.append([xmin / width, ymin / height, xmax / width, ymax / height]) labels.append(label_index) return boxes, labels except Exception as e: print(f"解析标注文件出错 {annotation_path}: {e}") return [], []
内容的提问来源于stack exchange,提问作者Mehar Hassan
相关产品推荐
相关产品推荐

