如何用tf.keras.preprocessing.text_dataset_from_directory加载多输入数据集
多输入CNN适配嵌套目录的GPU优化数据集加载方案
前置修正
- 首先纠正接口笔误:你提到的
tf.keras.preprocessing.text_dataset_from_directory是面向文本分类的加载接口,无法处理图像文件,图像训练场景请替换为同体系的tf.keras.utils.image_dataset_from_directory,否则流程从起点就无法运行。 - 你的目录结构设计完全适配双输入样本的组织逻辑:每个嵌套在顶层类目录下的二级子文件夹,就是一个独立样本,文件夹内的两张图像就是该样本的双输入,二级文件夹所属的顶层类(Class_1/Class_2)就是模型输出的单值标签,只是Keras自带的目录加载接口默认逻辑不匹配,无需硬改目录结构,自定义适配即可。
为什么不能直接用自带接口
Keras自带的image_dataset_from_directory的默认逻辑有两个核心硬伤,完全适配不了你的场景:
- 标签判定逻辑是取图像所在的最近一级父文件夹名当类标签,也就是说它会把Class1_1、Class1_2这类二级文件夹当类,根本拿不到最顶层Class_1/Class_2的类信息,标签完全错位
- 它会把每一张遍历到的图像单独作为一个样本,完全打乱了你在同一个文件夹内组织配对图像的逻辑,最后加载出来的都是随机单图样本,根本达不到双输入的训练要求
- 别信网上说的给函数加递归参数就能解决,这个接口本身就会递归遍历所有子目录下的图像,但标签逻辑固定不会改,硬跑只会得到错的结果。
具体实现步骤
1. 扫描目录构建样本索引
先遍历所有文件夹,把配对的图像路径和对应标签整理成结构化列表,全程不需要移动任何文件:
import os import tensorflow as tf from glob import glob # 替换为你的数据集根目录 DATA_ROOT = "./your_dataset_root" # 全局配置 IMG_TARGET_SIZE = (224, 224) BATCH_SIZE = 32 # 构建顶层类名到整数标签的映射 class_names = sorted([entry.name for entry in os.scandir(DATA_ROOT) if entry.is_dir()]) class2label = {cls: idx for idx, cls in enumerate(class_names)} # 收集所有有效样本 sample_records = [] for top_class in class_names: top_class_path = os.path.join(DATA_ROOT, top_class) # 遍历该类下的所有二级样本文件夹 for sample_subdir in os.scandir(top_class_path): if not sample_subdir.is_dir(): continue # 收集文件夹内的所有PNG图像,排序保证输入顺序固定 img_paths = sorted(glob(os.path.join(sample_subdir.path, "*.png"))) # 过滤不符合双输入要求的样本 if len(img_paths) == 2: sample_records.append( (img_paths, class2label[top_class]) )
2. 编写样本加载预处理函数
按照双输入模型的要求,把成对的图像加载成张量,做统一的尺寸缩放、预处理:
def load_formatted_sample(img_pair, label): # 加载第一张输入图 img_a = tf.io.read_file(img_pair[0]) img_a = tf.image.decode_png(img_a, channels=3) img_a = tf.image.resize(img_a, IMG_TARGET_SIZE) # 可替换为你用的骨干网络对应的预处理函数,比如ResNet的标准化 # img_a = tf.keras.applications.resnet.preprocess_input(img_a) # 加载第二张输入图 img_b = tf.io.read_file(img_pair[1]) img_b = tf.image.decode_png(img_b, channels=3) img_b = tf.image.resize(img_b, IMG_TARGET_SIZE) # img_b = tf.keras.applications.resnet.preprocess_input(img_b) # 按照Keras多输入模型的格式返回输入元组和标签 return (img_a, img_b), label
3. 构建GPU优化的tf.data.Dataset
全程开启和Keras自带接口一致的性能优化,保证数据加载速度匹配GPU训练节奏:
# 拆分流式处理需要的路径和标签数组 img_pairs = [record[0] for record in sample_records] labels = [record[1] for record in sample_records] # 构建基础数据集 train_ds = tf.data.Dataset.from_tensor_slices( (img_pairs, labels) ) # 配置性能优化项 train_ds = ( train_ds .shuffle(buffer_size=len(sample_records), seed=42) # 全量打乱样本顺序 .map(load_formatted_sample, num_parallel_calls=tf.data.AUTOTUNE) # 多线程并行加载样本 .batch(BATCH_SIZE) # 按批次打包 .prefetch(tf.data.AUTOTUNE) # 预取后续批次,避免GPU空等数据 )
关键注意事项
- 收集图像路径时一定要做sorted排序,保证每次加载同一对图像的顺序固定,不要随机打乱同一样本内的两张图顺序,否则模型输入维度的对应关系会混乱
- 不要为了适配自带接口把二级文件夹里的图全部移动到顶层类目录下,那样会彻底丢失成对图像的关联关系,最后训练时随机把不同样本的图凑成一对,训练完全失效
- 这套生成的Dataset和Keras自带接口生成的对象在性能上没有差异,完全可以直接传给
model.fit()进行训练
内容的提问来源于stack exchange,提问作者Anandha Krishnan H
相关产品推荐
相关产品推荐

