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

Keras flow_from_dataframe语义分割报错:目标维度不匹配问题求助

解决Keras flow_from_dataframe语义分割的维度不匹配问题

你遇到的ValueError: Error when checking target: expected conv1 to have 4 dimensions, but got array with shape (1, 1)错误,根源是你错误地将one-hot编码后的标签数组直接存在了DataFrame中,而flow_from_dataframe在处理y_col时,并没有正确识别这种高维数组格式,最终导致输出的标签维度完全不符合模型预期。

问题分析

当你把y = keras.utils.to_categorical(y, n_classes)后的数组存入DataFrame时,flow_from_dataframe会把这个数组当成单个标量值处理,最终输出的标签形状变成了(batch_size, 1),而你的模型期望的是(batch_size, height, width, n_classes)的4维张量,这就导致了维度不匹配。

正确的做法应该是让flow_from_dataframe从磁盘读取标签图像文件,然后在数据生成过程中对标签进行one-hot编码处理。

修正后的完整代码

下面是调整后的可运行代码,主要修改了DataFrame的构建方式和数据生成器的预处理逻辑:

import numpy as np
import pandas as pd
import os
from PIL import Image
import keras
from keras.models import Model
from keras.layers import Input, Conv2D
from keras_preprocessing.image import ImageDataGenerator

def get_file_list(root_path):
    """ 
    # Returns:
    file_list: _list_, list of full paths to all files found
    """
    file_list = []
    for root, dirs, files in os.walk(root_path):
        for name in files:
            file_list.append(os.path.join(root, name))
    return file_list

def gen_rand_img_labels(n_rand_imgs, path_img, path_label):
    for i in range(n_rand_imgs):
        img_rand = np.random.randint(0, 256, size=img_dim)
        Image.fromarray(img_rand.astype('uint8')).save(os.path.join(path_img, 'img{}.png'.format(i)))
        label_rand = np.random.randint(0, n_classes, size=(img_dim[0], img_dim[1]))
        print('label_rand.shape: ', label_rand.shape)
        Image.fromarray(label_rand.astype('uint8')).save(os.path.join(path_label, 'img{}.png'.format(i)))

if __name__ == "__main__":
    img_dim = (100, 200, 3) # height, width, channels
    batch_size = 1
    nr_epochs = 1
    n_classes = 5
    n_rand_imgs = 10
    savepath_img = r'/path/to/img'
    savepath_label = r'/path/to/label'

    # 创建存储目录(如果不存在)
    os.makedirs(savepath_img, exist_ok=True)
    os.makedirs(savepath_label, exist_ok=True)

    # --- generate random images and random labels and save them to disk
    gen_rand_img_labels(n_rand_imgs, savepath_img, savepath_label)

    # --- 修正:DataFrame只存储图像路径和对应标签文件的路径,不存储预处理后的数组
    train_df = pd.DataFrame(columns=['path', 'label'])
    list_img_names = get_file_list(savepath_img)
    for fname in list_img_names:
        fname_pure = os.path.split(fname)[1]
        label_path = os.path.join(savepath_label, fname_pure)
        train_df.loc[len(train_df)] = [fname, label_path]

    # --- 定义图像和标签的预处理函数
    def preprocess_img(img):
        return img / 255.0

    def preprocess_label(label):
        # 将标签图像转换为整数格式,然后做one-hot编码
        label = label.astype(np.int32)
        # 确保标签是单通道(如果读取的是3通道则取第一维)
        if label.ndim == 3:
            label = label[:, :, 0]
        return keras.utils.to_categorical(label, n_classes)

    # --- 构建数据生成器:分别处理输入图像和标签
    datagen = ImageDataGenerator(preprocessing_function=preprocess_img, validation_split=0.25)
    label_datagen = ImageDataGenerator(preprocessing_function=preprocess_label, validation_split=0.25)

    train_img_generator = datagen.flow_from_dataframe(
        dataframe=train_df,
        x_col="path",
        subset="training",
        batch_size=batch_size,
        class_mode=None,  # 不需要默认的分类模式,只返回图像
        target_size=(img_dim[0], img_dim[1]),
        seed=42  # 设置seed保证图像和标签对应
    )

    train_label_generator = label_datagen.flow_from_dataframe(
        dataframe=train_df,
        x_col="label",
        subset="training",
        batch_size=batch_size,
        class_mode=None,
        target_size=(img_dim[0], img_dim[1]),
        seed=42
    )

    valid_img_generator = datagen.flow_from_dataframe(
        dataframe=train_df,
        x_col="path",
        subset="validation",
        batch_size=batch_size,
        class_mode=None,
        target_size=(img_dim[0], img_dim[1]),
        seed=42
    )

    valid_label_generator = label_datagen.flow_from_dataframe(
        dataframe=train_df,
        x_col="label",
        subset="validation",
        batch_size=batch_size,
        class_mode=None,
        target_size=(img_dim[0], img_dim[1]),
        seed=42
    )

    # --- 将图像生成器和标签生成器合并
    def combine_generator(img_gen, label_gen):
        while True:
            yield (next(img_gen), next(label_gen))

    train_generator = combine_generator(train_img_generator, train_label_generator)
    valid_generator = combine_generator(valid_img_generator, valid_label_generator)

    # --- create the model and train it
    input_ = Input(shape=img_dim)
    x = Conv2D(n_classes, (3, 3), activation='relu', padding='same', name='conv1')(input_)
    model = Model(inputs=input_, outputs=[x])
    model.summary()
    model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=["accuracy"])

    # Train model
    STEP_SIZE_TRAIN = train_img_generator.n // train_img_generator.batch_size
    STEP_SIZE_VALID = valid_img_generator.n // valid_img_generator.batch_size
    model.fit_generator(generator=train_generator,
                        steps_per_epoch=STEP_SIZE_TRAIN,
                        validation_data=valid_generator,
                        validation_steps=STEP_SIZE_VALID,
                        epochs=nr_epochs)

关键修改点说明

  • DataFrame存储内容调整:不再存储预处理后的标签数组,而是存储标签文件的路径,让flow_from_dataframe从磁盘读取原始标签图像。
  • 替换废弃的图像读写方法:把scipy.misc相关方法换成更稳定的PIL.Image,避免版本兼容性问题。
  • 分开定义图像和标签的生成器:因为输入图像和标签的预处理逻辑不同(图像归一化,标签one-hot编码),所以需要两个独立的ImageDataGenerator,并且设置相同的seed来保证每一批的图像和标签一一对应。
  • 合并生成器:通过自定义的combine_generator函数,将图像生成器和标签生成器的输出配对,返回模型需要的(input, target)格式数据。
  • class_mode设置为None:因为我们不需要flow_from_dataframe处理分类标签,只需要它读取并返回原始图像数据,所以设置class_mode=None。

额外注意事项

  • 确保你的标签图像是单通道的,代码中也加入了判断逻辑,避免3通道标签带来的维度错误。
  • 如果你的标签值范围不是从0开始的连续整数,需要额外做映射处理,保证to_categorical能正确生成one-hot编码。

内容的提问来源于stack exchange,提问作者ormop

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 07:58:15