ResNet18兄弟姐妹检测:如何调整数据集标签匹配图片对需求
问题根源分析
你现在遇到的核心问题是**flow_from_directory的默认逻辑和你的任务需求不匹配**:这个API是为「单张图片对应一个样本/标签」的场景设计的,但你的任务是「两张图片组成一个样本,对应一个是否为兄弟姐妹的标签」。
你当前的文件夹结构是按类别(false/siblings)存放单张图片,所以flow_from_directory会给每张单独的图片打上所在文件夹的标签,最终classes数组的长度等于图片总数,而不是你需要的图片对数量。
解决方案
下面提供两种可行的方案,你可以根据自己的实际情况选择:
方案一:自定义图片对数据生成器(推荐)
这种方式最灵活,完全贴合你的「双输入图片对」任务需求,步骤如下:
1. 重新组织数据集结构
创建这样的文件夹层级:
dataset/ ├─ train/ │ ├─ sample_0/ # 标签0:非兄弟姐妹 │ │ ├─ img1.jpg │ │ └─ img2.jpg │ ├─ sample_1/ # 标签1:兄弟姐妹 │ │ ├─ img1.jpg │ │ └─ img2.jpg │ ├─ sample_2/ # 标签0:非兄弟姐妹 │ │ ├─ img1.jpg │ │ └─ img2.jpg │ └─ sample_3/ # 标签1:兄弟姐妹 │ ├─ img1.jpg │ └─ img2.jpg └─ train_labels.csv # 记录每个样本的标签
train_labels.csv的内容示例:
sample_name,label sample_0,0 sample_1,1 sample_2,0 sample_3,1
2. 实现自定义数据生成器
继承Keras的Sequence类,读取每个样本文件夹下的两张图片,返回双输入和对应标签:
import numpy as np import pandas as pd import os from tensorflow.keras.utils import Sequence from tensorflow.keras.preprocessing.image import load_img, img_to_array class SiblingPairGenerator(Sequence): def __init__(self, data_dir, labels_csv, target_size=(28,28), batch_size=32, shuffle=True): self.data_dir = data_dir self.labels_df = pd.read_csv(labels_csv) self.target_size = target_size self.batch_size = batch_size self.shuffle = shuffle self.indexes = np.arange(len(self.labels_df)) if self.shuffle: np.random.shuffle(self.indexes) def __len__(self): # 计算每个epoch的批次数 return int(np.ceil(len(self.labels_df) / self.batch_size)) def __getitem__(self, index): # 获取当前批次的样本索引 batch_indexes = self.indexes[index*self.batch_size : (index+1)*self.batch_size] batch_imgs1, batch_imgs2, batch_labels = [], [], [] for idx in batch_indexes: sample_row = self.labels_df.iloc[idx] sample_path = os.path.join(self.data_dir, sample_row['sample_name']) # 读取两张图片并预处理 img1 = load_img(os.path.join(sample_path, 'img1.jpg'), target_size=self.target_size) img2 = load_img(os.path.join(sample_path, 'img2.jpg'), target_size=self.target_size) img1 = img_to_array(img1) / 255.0 # 归一化到0-1区间 img2 = img_to_array(img2) / 255.0 # 添加到批次 batch_imgs1.append(img1) batch_imgs2.append(img2) batch_labels.append(sample_row['label']) # 转换为numpy数组返回 return [np.array(batch_imgs1), np.array(batch_imgs2)], np.array(batch_labels) def on_epoch_end(self): # 每个epoch结束后打乱样本顺序 if self.shuffle: np.random.shuffle(self.indexes)
3. 用生成器训练模型
假设你的ResNet18是双输入结构,训练代码示例:
from tensorflow.keras.layers import Input, GlobalAveragePooling2D, concatenate, Dense from tensorflow.keras.models import Model from tensorflow.keras.applications import ResNet18 # 注意:如果是自定义ResNet18,替换成你的实现 # 构建双输入模型 input1 = Input(shape=(28,28,3)) input2 = Input(shape=(28,28,3)) # 共享ResNet18特征提取层 base_model = ResNet18(include_top=False, input_shape=(28,28,3)) x1 = GlobalAveragePooling2D()(base_model(input1)) x2 = GlobalAveragePooling2D()(base_model(input2)) # 合并特征并输出分类结果 merged = concatenate([x1, x2]) output = Dense(1, activation='sigmoid')(merged) model = Model(inputs=[input1, input2], outputs=output) # 编译并训练 model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy']) train_generator = SiblingPairGenerator( data_dir='dataset/train', labels_csv='dataset/train_labels.csv', target_size=(28,28), batch_size=32 ) model.fit(train_generator, epochs=10)
方案二:拼接图片对后使用flow_from_directory(简易版)
如果想继续用flow_from_directory,可以把每张图片对拼接成一张图片(比如左右拼接),再按类别存放:
- 提前将所有非兄弟姐妹的图片对拼接成单张图,放到
training/false文件夹 - 将所有兄弟姐妹的图片对拼接成单张图,放到
training/siblings文件夹 - 此时
flow_from_directory读取的每个文件就是一个完整的图片对样本,classes数组就会和你需要的一致(比如false里2张拼接图,siblings里2张,classes就是[0,0,1,1])
这种方式优点是简单,但灵活性差,后续调整图片对的输入方式会很麻烦。
内容的提问来源于stack exchange,提问作者Andrei Dumitrascu
相关产品推荐
相关产品推荐

