Python:从给定数据集生成唯一的批次

3

我将应用CNN对给定的数据集进行分类。

我的函数:

def batch_generator(dataset, input_shape = (256, 256), batch_size = 32):
    dataset_images = []
    dataset_labels = []
    for i in range(0, len(dataset)):
        dataset_images.append(cv2.resize(cv2.imread(dataset[i], cv2.IMREAD_COLOR), 
                     input_shape, interpolation = cv2.INTER_AREA))
        dataset_labels.append(labels[dataset[i].split('/')[-2]])
    return dataset_images, dataset_labels

这个函数应该在每个epoch被调用,它应该返回一个唯一的大小为 'batch_size' 的批次,其中包含数据集图像(每个图像均为256x256)和来自标签字典的相应数据集标签。

输入参数'dataset'包含所有图像的路径,因此我正在打开它们并将它们的大小调整为256x256。有人可以帮我添加代码以便它返回所需的批次吗?


你能澄清一下你预计如何使用这个函数以及你所说的“独特批次”是什么意思吗?你的意思是每个元素在批次内应该是唯一的吗?还是说你想随机将数据集分成大小为32的批次,并对所有批次进行迭代?无论哪种情况,使用torch.utils.data.DataLoader似乎更合适,而不是编写自己的生成器。 - jodag
@jodag 我想将数据集随机分成大小为32的批次。这些批次将传递给我的 CNN 模型进行特定周期的训练。希望这能澄清事情。 - Ashar
我查看了DataLoader类。它似乎需要一个输入数据集。然而,我有一个256x256图像的数据集列表和一个相应的标签列表。您能否详细说明如何将它们组合并传递给DataLoader? - Ashar
2个回答

1

正如@jodag所建议的那样,使用DataLoaders是一个好主意。

我有一段代码片段在Pytorch中用于我的一些CNN。

from torch.utils.data import Dataset, DataLoader
import torch
class Data(Dataset):
    """
    Constructs a Dataset to be parsed into a DataLoader
    """
    def __init__(self,X,y):
        X = torch.from_numpy(X).float()

        #Transpose to fit dimensions of my network
        X = torch.transpose(X,1,2)

        y = torch.from_numpy(y).float()
        self.X,self.y = X,y

    def __getitem__(self, i):
        return self.X[i],self.y[i]

    def __len__(self):
        return self.X.shape[0]

def create_data_loader(X,y,batch_size,**kwargs):
    """
    Creates a data-loader for the data X and y

    params:
    -------

    X: np.array
        - numpy array of size "n" x k where n is samples an "k" is number of features

    y: np.array
        - numpy array of sie "n"

    batch_size: int
        - Take a wild guess, dumbass

    kwargs:
        - Additional keyword-arguments for "DataLoader"

    return
    ------

    dl: torch.utils.data.DataLoader object
    """

    data = Data(X, y)

    dl = DataLoader(data, batch_size=batch_size,num_workers=0,**kwargs)
    return dl

这是这样使用的:

from create_data_loader import create_data_loader

train_data_loader= create_data_loader(X_train,y_train,batch_size=32) #Note, it has "shuffle=True" as default!
val_data_loader= create_data_loader(X_val,y_val,batch_size=32,shuffle=False) #If you want to keep index'es in the same order for e.g cross-validate


for x_train, y_train in train_data_loader:
   logit = net(x_train,y_train)
   .
   .
   net.eval()
   for x_val,y_val in val_data_loader:
       logit  = net(x_val,y_val)
       classes_pred = logit.argmax(axis=1)
       print(f"Val accuracy: {(y_val==classes_pred).mean()}")

0

PyTorch有两个听起来相似但非常不同的数据加载抽象。我强烈建议阅读dataloaders文档here进行总结:

  1. Dataset是您通常实现的对象,它返回单个样本(数据+标签)
  2. DataLoader是pytorch中的内置类,从数据集中取样本批次(可能并行)。

(映射方式)数据集是一个简单的对象,只实现了两种必需方法:__getitem____len__。Getitem是在使用方括号操作符即dataset[i]时调用的方法,而__len__是在使用python内置的len函数在您的对象上调用时调用的方法,即len(dataset)

对于pytorch,通常希望__getitem__返回一个元组,其中包含数据和数据集对象中单个项目的标签。例如,基于您提供的内容,应该像这样适合您的需要:

from torch.utils.data import Dataset, DataLoader
import torchvision.transforms.functional as F

class CustomDataset(Dataset):
    def __init__(self, image_paths, labels, input_shape=(256, 256)):
        # `image_paths` is what you called `dataset` in your example.
        #               I'm assume this is a list of image paths.
        # `labels` isn't defined in your script but I assume its a
        #          dict that maps image names to an integer label
        #          between 0 and num classes minus 1
        self.image_paths = image_paths
        self.labels = labels
        self.input_shape = input_shape

    def __getitem__(self, index):
        # return the data and label for the specified index
        image_path = self.image_paths[index]
        data = cv2.resize(cv2.imread(image_path, cv2.IMREAD_COLOR), 
                          self.input_shape, interpolation = cv2.INTER_AREA)
        label = self.labels[image_path.split('/')[-2]]

        # convert data to PyTorch tensor
        # This converts data from a uint8 np.array of shape HxWxC
        # between 0 and 255 to a pytorch float32 tensor of shape CxHxW
        # between 0.0 and 1.0.
        data = F.to_tensor(data)

        return data, label

    def __len__(self):
        return len(self.image_paths)

...
# using what you call "dataset" and "labels"
# num_workers > 0 allows you to load data in parallel while network is running
dataloader = DataLoader(
    CustomDataset(dataset, labels, (256, 256)),
    batch_size=32,
    shuffle=True,    # shuffle tells us to randomly sample the
                     # dataset without replacement
    num_workers=4    # num workers is the number of worker processes
                     # that load from dataset in parallel while your
                     # model is processing stuff
)

# training loop
for epoch in range(num_epochs):
    # iterates over all data in your dataset in a random order
    # in batches of size 32 each time this loop is run
    for data_batch, label_batch in dataloader:
        # data_batch is a pytorch FloatTensor of shape 32x3x256x256
        # label_batch is a pytorch LongTensor of shape 32

        # if using GPU acceleration now is the time to move data_batch and label_batch to GPU
        # data_batch = data_batch.cuda()
        # label_batch = label_batch.cuda()

        # zero the gradients, pass data through your model, backprop, and step the optimizer
        ...

非常感谢您提供详细的回复,它在很大程度上消除了我的疑虑。现在只有一个小问题,如果我将num_worker设置为任何非零值,就会出现错误,提示pid x、y、z意外关闭。但是,将其设置为0后,模型可以顺利迭代而没有任何错误。您能提供任何见解吗? - Ashar
@Ashar 可能是您正在运行的系统不允许进程分叉的问题。或者在 torch 想要共享信息的地方没有可用空间(在 Linux 上为 /dev/shm,在 Windows 上不确定)。最坏的情况就是您需要使用 0 个工作进程,这比使用 > 0 个工作进程慢。此外,如果这提供了有用的答案,请考虑接受和/或点赞。 - jodag
我正在macOS Monterey 12.0.1上运行它。也许这就是问题所在。无论如何,它解决了我遇到的更大的问题。接受这个答案。 - Ashar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接