You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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传给它,没有按类别分文件夹,导致生成器无法识别有效数据。

此外还有几个次要问题:

  1. 同时实现了手动加载数据的load_data函数和flow_from_directory,但训练仅用了后者,手动加载代码完全冗余。
  2. saved_model_path被重复赋值为文件夹路径,而ModelCheckpoint需要具体文件路径(如xxx.h5),会导致模型保存失败。
  3. parse_annotation函数未做异常处理,若XML缺失size或bndbox节点会直接报错。

修复步骤

方案一:调整目录结构适配flow_from_directory

  1. 按类别为train/val目录创建子文件夹,将同类图像移入对应子文件夹(XML可保留原位置或同步移动,flow_from_directory无需XML,会自动按文件夹名标注类别)。
  2. 修正saved_model_path:
    saved_model_path = 'C:/Users/UnitechSolutions/Desktop/python projects/1st assignment/tensorflow/saved_model.h5'
    
  3. 删除冗余的load_data、preprocess_image、parse_annotation及数据加载代码,flow_from_directory会自动处理图像加载与预处理。

方案二:放弃flow_from_directory,使用手动加载的数据训练

若不想调整目录结构,可基于自己写的load_data函数训练:

  1. 将手动加载的数据转换为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)
    
  2. 修改model.fit的输入:
    history = model.fit(
        train_dataset,
        epochs=epochs,
        validation_data=val_dataset,
        callbacks=[checkpoint_callback, early_stopping_callback]
    )
    
  3. 同样修正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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 19:54:56