深度学习实践篇 第一章:数据集读取和构建

简介: 简要介绍cifar数据集的使用和pytorch数据集的构建。

说在前面的话

教程参考:
https://pytorch.org/tutorials/
https://github.com/TingsongYu/PyTorch_Tutorial
https://github.com/yunjey/pytorch-tutorial


cifar 数据介绍

官网地址: http://www.cs.toronto.edu/~kriz/cifar.html

官网上对CIFAR-10数据和CIFAR-100数据进行了简要介绍。
CIFAR10 数据包括十个类别,每个类别各6000张,一共6万张大小为32x32的彩色图片,其中包括5w张训练集和1w张测试集。
其中5w张训练集被分为了5个batch,每个包括1万张图片。测试集从每个类别的图片中随机选择了1000张,十个类别共1万张图片。
测试集中的图片和训练集中的图片没有重复,并且顺序也是随机的。训练集的每个batch中图片的类别分布是不均匀的,比如说第一个类别在第一个batch中可能存在2000张,在第二个batch中只有500。无论分布如何,任意一个类别在五个train_batch中的图像总数都是1万张。
各个类别间是完全独立的,比如automobile和truck之间就不存在相同的数据,automobile包括了大轿车sedans,越野车suvs等,而trucks只包括大货车 big trucks。
在这里插入图片描述

CIFAR100数据包括100个类别,每个类别各600张,一共6w张图片。每个类别有500张训练图片和100张测试图片。结构上来说和CIFAR10相似。

官网上提供的数据都分为三个版本:python version, matlab version 和 binary version。
我们在此只使用python version。
数据读取的方式参考官网给出的代码样例。

def unpickle(file):
    import pickle
    with open(file, 'rb') as fo:
        dict = pickle.load(fo, encoding='bytes')
    return dict

在pytorch的datasets中提供了Cifar10数据的直接使用方法。
本章节后面的部分对数据处理的过程作出了介绍,便于大家较为完整的数据集构建过程,但是如果只是单纯想使用CIFAR10,直接使用torchvision.datasets.CIFAR10是一个更快捷方便的做法。

transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

batch_size = 4

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size,
                                         shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

pickle 数据格式

教程参考: https://www.datacamp.com/tutorial/pickle-python-tutorial

为什么使用pickle

可以看到,cifar数据是存储为pickle格式的。
python中的pickle模块和其他模块存储的主要区别是pickle模块可以把python对象直接保存到文件里,而且不需要转成字符串再保存。
举例说明:假如我们有一个dict类型的数据

students = {
'student1': {'name':'a', 'age':'b'},
'student2': {'name':'c', 'age':'d'},
}

当我们想保存这个数据,比如保存到txt文件中时

with open('student_info.txt','w') as data: 
    data.write(str(students))

当我们重新从txt文件中读取 students时,发现这个数据的类型会变成str格式,而不是我们所希望的dict。而如果我们使用pickle模块来进行保存,则不会改变students的数据类型。
这就是pickle相比别的数据保存方式的一个优势:Pickle can serialize almost every commonly used built-in Python data type。
When dealing with more complex data types like dictionaries, data frames, and nested lists, serialization allows the user to preserve the object’s original state without losing any relevant information.

**pickle能够保存Python的复杂的数据类型,包括列表、元组、自定义类等,并且不会丢失相关信息。
在使用pickle,你以什么形式保存数据,就可以在读取时获得什么形式的数据,省去了重新编辑代码的麻烦。
此外,pickle是一个专属于python的模块,因此你不能使用别的语言来读取你保存的数据。**

怎么使用pickle

pickle的用法十分简单,最基本的操作就是dumps()和loads()。

# 将python数据据对象obj转换保存到file中去
pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)

# 将python数据对象obj转换为pickle格式
pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)

# 从file文件中读取数据并转换为python类型
pickle.load(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)

# 将pickle格式的数据转换为python类型
pickle.loads(data, /, *, fix_imports=True, encoding=”ASCII”, errors=”strict”, buffers=None)

cifar 数据读取

cifar 文件介绍

下载并解压cifar10文件后,可以看到压缩包中包括的文件内容如下:
image.png

一共八个文件。
其中 xxx_batch是保存为pickle格式的数据文件。每个batch文件加载后,是一个dict,它拥有四个key,分别是dict_keys([b'batch_label', b'labels', b'data', b'filenames'])。
batch_label -- index of batch. 当前使用的batch的标号
data -- a 10000x3072 numpy array of uint8s. 保存了所有的32x32的彩色图片,按照red-green-blue的顺序。
labels -- a list of 10000 numbers in the range 0-9. 保存了每个图片对应的类别。
filenames -- a list of filenames of 10000 images。保存了所有图片的名称。

此外还有一个单独的batches.meta文件,它也是一个dict,拥有三个key,分别是
dict_keys([b'num_cases_per_batch', b'label_names', b'num_vis'])。包括了一些数据的基本信息。比如class_index和class_name的对应关系,在这里我们先不需要使用这个文件。

cifar 文件读取与处理

读取数据

批量处理数据的代码可以直接参考: https://github.com/TingsongYu/PyTorch_Tutorial/blob/master/Code/1_data_prepare/1_1_cifar10_to_png.py

使用官网给出的代码样例来进行文件的读取。

比如说对于batch文件:file1 = 'xxx/cifar-10-batches-py/data_batch_1',我们可以使用如下代码来读取文件

import pickle
def unpickle(file):
    with open(file,'rb')as f:
        dict = pickle.load(f,encoding = 'bytes')
    return dict
data = unpickle(file1)

读取到的data包括当前batch的10000个数据。我们可以用datab'labels'获得第i个图像的类别,datab'filename'获得第i个图像的名称,datab'data'为第i个图片数据本身,这个数据为长度3072 = 3 x 32 x 32的array,我们需要把它转换成一个图片的格式
可以使用代码

# 首先ere'sha成(3, 32, 32)是因为数据是按照通道排序的
img = np.reshape(data[b'data'][i], (3, 32, 32)
# 将通道数反转到最后一维。要注意这里的图片是rgb格式。
img = img.transpose(1, 2, 0)

数据集的划分

数据集的划分可以直接参考: https://github.com/TingsongYu/PyTorch_Tutorial/blob/master/Code/1_data_prepare/1_2_split_dataset.py

该代码中没有使用所有的数据集,而是将一万张test数据重新划分。

这里只是给出了一个数据集划分的样例,不代表我们真的要这样对cifar10数据集进行划分,在实际训练中请使用完整的数据集。

划分完成后,训练集包含每个类别各800张,共8000张图片,验证集和测试集分别包括1000张图片。

关于为什么要划分数据集,在这里不做过多描述。

制作图片索引

教程代码中将图片和标签保存到txt中,我本人更喜欢用csv,在这里提供csv样例代码。之后的数据读取过程均基于csv编写。

def gen_csv(csv_path, img_dir):
    out = {}
    out['path'] = []
    out['label'] = []
    for root, s_dirs, _ in os.walk(img_dir, topdown=True):  # 获取 train文件下各文件夹名称
        for sub_dir in s_dirs:
            i_dir = os.path.join(root, sub_dir)             # 获取各类的文件夹 绝对路径
            img_list = os.listdir(i_dir)                    # 获取类别文件夹下所有png图片的路径
            for i in range(len(img_list)):
                if not img_list[i].endswith('png'):         # 若不是png文件,跳过
                    continue
                label = img_list[i].split('_')[0]
                img_path = os.path.join(i_dir, img_list[i])
                out['path'].append(img_path)
                out['label'].append(label)
                
    out = pd.DataFrame(out)
    out.to_csv(csv_path,index=False)
 


gen_csv(train_csv_path, train_dir)
gen_csv(valid_csv_path, valid_dir)

生成的csv内容如下,第一列为图片的位置,第二列为图片的label。
image.png

pytorch 读取数据

Dataset的使用

教程参考: https://pytorch.org/tutorials/beginner/basics/data_tutorial.html?highlight=dataset

要想使用pytorch读取数据并用于训练,就需要将数据处理成pytorch可以使用的格式,遵守pytorch的基本法。pytorch中提供了两个类帮助大家进行数据的处理,分别是Dataset和DataLoader。Dataset类帮助我们预加载数据,并存储数据和对应的label。Dataloader则帮助我们快速获取其中的样本。

在pytorch教程中,给出了制作自己的数据集的样例代码(添加了注释):


import os
import pandas as pd
from torchvision.io import read_image

class CustomImageDataset(Dataset):
    def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
        self.img_labels = pd.read_csv(annotations_file) #  读取包含了图片相对地址和标注信息的csv
        self.img_dir = img_dir # 图片存放的路径
        self.transform = transform #使用的图片transform方法
        self.target_transform = target_transform # 使用的label的transform方法
    def __len__(self):
        return len(self.img_labels)

    def __getitem__(self, idx):
        img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0]) # 获取图片的绝对路径
        image = read_image(img_path) # 读取图片
        label = self.img_labels.iloc[idx, 1] # 获取图片的label
        if self.transform: # 将图片进行transform
            image = self.transform(image)
        if self.target_transform: # 将label进行transform
            label = self.target_transform(label)
        return image, label

可以看到该样例代码的数据读取也是基于csv文件的。
这里读取图片时使用的是torchvision.io 的read_image函数,这个函数的作用是Reads a JPEG or PNG image into a 3 dimensional RGB or grayscale Tensor. Optionally converts the image to the desired format. The values of the output tensor are uint8 in [0, 255]. 所以不需要我们额外将图片转为tensor。
实例化CustomImageDataset后,可以使用以下代码绘制出dataset的样例。

figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3 # 三行三列图片
for i in range(1, cols * rows + 1):
    sample_idx = torch.randint(len(training_data), size=(1,)).item() # 随机取得一个index
    img, label = training_data[sample_idx] # 获取数据
    figure.add_subplot(rows, cols, i)
    plt.title(label)
    plt.axis("off")
    plt.imshow(img.numpy().transpose(1,2,0)) # 因为数据为大小(3, 32, 32)的tensor,所以将它transpose一下。
plt.show()

image.png

Dataloader的使用

dataset中每次可以获取到我们的数据集中的一共sample和对应的label。但是在训练的时候,我们希望按照batch进行数据的读取,并且数据需要通过shuffle来增加随机性,防止overfitting,甚至使用multiporcessing来加速数据的获取。
综合来说,Dataloader起到了对以下功能的支持:

  • map-style and iterable-style datasets [pytorch的两种dataset类型,第一种是我们常用的]
  • customizing data loading order
  • automatic batching
  • single-and multi-process data loading
  • automatic memory pinning

我们使用一个最简单的dataloader,将batch_size设为64,shuffler = True。这样我们的dataloader每次会按照随机顺序取64个数据。

from torch.utils.data import DataLoader

DataLoader(dataset, batch_size=1, shuffle=False, sampler=None,
           batch_sampler=None, num_workers=0, collate_fn=None,
           pin_memory=False, drop_last=False, timeout=0,
           worker_init_fn=None, *, prefetch_factor=2,
           persistent_workers=False)

train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)

我们可以测试一下数据的情况。

# Display image and label.
train_features, train_labels = next(iter(train_dataloader))
print(f"Feature batch shape: {train_features.size()}")
print(f"Labels batch shape: {train_labels.size()}")

image.png

可以看到我们获得的featurebatch包括64个大小为[3, 32, 32]的数据,我们的label的大小也是64。

使用和之前类似的方法画出整个batch的数据的图像。因为我们的shuffle = True,所以数据的读取是随机的,得到的label也是无序的。
image.png

假如我们使用 shuffle = False,数据会按照我们csv中的顺序读取,那么得到的batch的数据的图像就会如下。

image.png

map-style and iterable-style dataset

map-style datasets就是我们在上一节中使用的Dataset的类型,它拥有__getitem__()和__len__(),并可以根据给定的index来返回data samples。

iterable-style dataset和它不同,是一个基于IterableDataset实现的dataset,它拥有__iter__(),一般用于很难进行随机读取的情况。这个几乎没有用过,在这里不多介绍。可以参考: https://pytorch.org/docs/stable/data.html#torch.utils.data.IterableDataset

data loading order and sampler

这里的order主要是指对map-style数据集的order的控制,对于iterable-style的数据集,它的 order is entirely controlled by the user-defined iterable。

DataLoader的入参sampler,在这里起到了决定数据顺序的作用。所以当你使用sampler时,你的shuffle就失去了作用。
默认来讲,在不使用sampler的情况下,当你的shuffle = True时,你使用的其实是SequentialSampler,即按顺序取样;当你的shuffle = False时,你使用的其实是RandomSampler,即随机取样。

loading batched and non-batched data

DataLoader中有四个参数影响着基于batch取数据的过程,分别是batch_size, drop_last, batch_sampler 和 collate_fn。

  • batch_size 决定了一个batch的大小。
  • drop_last 决定了batch中数据是否要保留。
  • batch_sampler 决定了batch的取样顺序。
  • collate_fn 决定了batch中数据的组合方式。

从一个map-style的数据集中加载数据,可以等价于下面的过程:

for indices in batch_sampler:
    yield collate_fn([dataset[i] for i in indices])

在这个过程中,collate_fn可以按照你指定的方式对一个batch中所有数据进行组合。

# Example with a batch of `int`s:
default_collate([0, 1, 2, 3]) => tensor([0, 1, 2, 3])

# Example with a batch of `str`s:
default_collate(['a', 'b', 'c']) => ['a', 'b', 'c']

# Example with `Map` inside the batch:
default_collate([{'A': 0, 'B': 1}, {'A': 100, 'B': 100}]) => {'A': tensor([  0, 100]), 'B': tensor([  1, 100])}

# Example with `Tuple` inside the batch:
default_collate([(0, 1), (2, 3)]) => [tensor([0, 2]), tensor([1, 3])]

# Example with `List` inside the batch:
default_collate([[0, 1], [2, 3]]) => [tensor([0, 2]), tensor([1, 3])]

可以看到默认的形式中会对数据优先按照dimension进行组合,当你不希望使用这样的组合方式时,可以使用自定义的collate_fn,如下例子:

def collate_fn_resturecture(data):
    data_tensor = torch.tensor(data, dtype = torch.float32)
    xs, y = data_tensor[:,:2], data_tensor[:,-1].squeeze()
    return xs, y 
    
#给定一个输入
item_list = [[1,2,3] , [3,4,5], [5, 6, 7]]
collate_fn_resturcture(item_list) 
# 得到的结果是 (tensor([[1., 2.],[3., 4.],[5., 6.]]), tensor([3., 5., 7.]))

当入参的batch_size和batch_sampler都是None的时候,意味着没有使用automatic-batching。从数据集中加载数据,可以等价于下面的过程:

for index in sampler:
    yield collate_fn(dataset[index])

在这个过程中,collate_fn只起到了把numpy转为tensor的作用。

single- and multi-process data loading

DataLoader默认使用single-process,即入参num_workers = 0。因此数据加载过程很慢的话,可能会block计算的过程。在资源不足或者数据集很小的情况下,大家还是更喜欢用single-process。单线程时如果出现bug,也更容易追踪和修改。

将num_workers设置成一个正整数时,会使用多进程数据加载。这种情况下可能会引发一些问题:https://github.com/pytorch/pytorch/issues/13246#issuecomment-905703662

memory pinning

当内存固定时,数据到gpu的传输速度会更快,所以可以使用 pin_memory = True 来固定数据的内存空间。


下一步会按照tutorial教程的顺序进行transform的介绍和一些augmentation的扩展。

相关文章
|
机器学习/深度学习 编解码 人工智能
人脸表情[七种表情]数据集(15500张图片已划分、已标注)|适用于YOLO系列深度学习分类检测任务【数据集分享】
本数据集包含15,500张已划分、已标注的人脸表情图像,覆盖惊讶、恐惧、厌恶、高兴、悲伤、愤怒和中性七类表情,适用于YOLO系列等深度学习模型的分类与检测任务。数据集结构清晰,分为训练集与测试集,支持多种标注格式转换,适用于人机交互、心理健康、驾驶监测等多个领域。
|
2月前
|
机器学习/深度学习 人工智能 文字识别
中药材图像识别数据集(100类,9200张)|适用于YOLO系列深度学习分类检测任务
本数据集包含9200张中药材图像,覆盖100种常见品类,已标注并划分为训练集与验证集,支持YOLO等深度学习模型。适用于中药分类、目标检测、AI辅助识别及教学应用,助力中医药智能化发展。
|
4月前
|
机器学习/深度学习 人工智能 监控
河道塑料瓶识别标准数据集 | 科研与项目必备(图片已划分、已标注)| 适用于YOLO系列深度学习分类检测任务【数据集分享】
随着城市化进程加快和塑料制品使用量增加,河道中的塑料垃圾问题日益严重。塑料瓶作为河道漂浮垃圾的主要类型,不仅破坏水体景观,还威胁水生生态系统的健康。传统的人工巡查方式效率低、成本高,难以满足实时监控与治理的需求。
|
4月前
|
机器学习/深度学习 传感器 人工智能
火灾火焰识别数据集(2200张图片已划分、已标注)|适用于YOLO系列深度学习分类检测任务【数据集分享】
在人工智能和计算机视觉的快速发展中,火灾检测与火焰识别逐渐成为智慧城市、公共安全和智能监控的重要研究方向。一个高质量的数据集往往是推动相关研究的核心基础。本文将详细介绍一个火灾火焰识别数据集,该数据集共包含 2200 张图片,并已按照 训练集(train)、验证集(val)、测试集(test) 划分,同时配有对应的标注文件,方便研究者快速上手模型训练与评估。
火灾火焰识别数据集(2200张图片已划分、已标注)|适用于YOLO系列深度学习分类检测任务【数据集分享】
|
4月前
|
机器学习/深度学习 人工智能 自动驾驶
7种交通场景数据集(千张图片已划分、已标注)|适用于YOLO系列深度学习分类检测任务【数据集分享】
在智能交通与自动驾驶技术快速发展的今天,如何高效、准确地感知道路环境已经成为研究与应用的核心问题。车辆、行人和交通信号灯作为城市交通系统的关键元素,对道路安全与交通效率具有直接影响。然而,真实道路场景往往伴随 复杂光照、遮挡、多目标混杂以及交通信号状态多样化 等挑战,使得视觉识别与检测任务难度显著增加。
|
4月前
|
机器学习/深度学习 人工智能 监控
坐姿标准好坏姿态数据集(图片已划分、已标注)|适用于YOLO系列深度学习分类检测任务【数据集分享】
坐姿标准好坏姿态数据集的发布,填补了计算机视觉领域在“细分健康行为识别”上的空白。它不仅具有研究价值,更在实际应用层面具备广阔前景。从青少年的健康教育,到办公室的智能提醒,再到驾驶员的安全监控和康复训练,本数据集都能发挥巨大的作用。
坐姿标准好坏姿态数据集(图片已划分、已标注)|适用于YOLO系列深度学习分类检测任务【数据集分享】
|
4月前
|
机器学习/深度学习 数据采集 算法
PCB电路板缺陷检测数据集(近千张图片已划分、已标注)| 适用于YOLO系列深度学习检测任务【数据集分享】
在现代电子制造中,印刷电路板(PCB)是几乎所有电子设备的核心组成部分。随着PCB设计复杂度不断增加,人工检测PCB缺陷不仅效率低,而且容易漏检或误判。因此,利用计算机视觉和深度学习技术对PCB缺陷进行自动检测成为行业发展的必然趋势。
PCB电路板缺陷检测数据集(近千张图片已划分、已标注)| 适用于YOLO系列深度学习检测任务【数据集分享】
|
4月前
|
机器学习/深度学习 编解码 人工智能
102类农业害虫数据集(20000张图片已划分、已标注)|适用于YOLO系列深度学习分类检测任务【数据集分享】
在现代农业发展中,病虫害监测与防治 始终是保障粮食安全和提高农作物产量的关键环节。传统的害虫识别主要依赖人工观察与统计,不仅效率低下,而且容易受到主观经验、环境条件等因素的影响,导致识别准确率不足。
|
机器学习/深度学习 人工智能 监控
单车、共享单车已标注数据集(图片已划分、已标注)|适用于深度学习检测任务【数据集分享】
数据是人工智能的“燃料”。一个高质量、标注精准的单车与共享单车数据集,不仅能够推动学术研究的进步,还能为智慧交通、智慧城市的建设提供有力支撑。 在计算机视觉领域,研究者们常常会遇到“数据鸿沟”问题:公开数据集与真实业务需求之间存在不匹配。本次分享的数据集正是为了弥补这一不足,使得研究人员与工程师能够快速切入单车检测领域,加速模型从实验室走向真实应用场景。
|
4月前
|
机器学习/深度学习 自动驾驶 算法
道路表面缺陷数据集(裂缝/井盖/坑洼)(6000张图片已划分、已标注)|适用于YOLO系列深度学习分类检测任务【数据集分享】
随着城市化与交通运输业的快速发展,道路基础设施的健康状况直接关系到出行安全与城市运行效率。长期高强度的使用、气候变化以及施工质量差异,都会导致道路表面出现裂缝、坑洼、井盖下沉及修补不良等缺陷。这些问题不仅影响驾驶舒适度,还可能引发交通事故,增加道路养护成本。
道路表面缺陷数据集(裂缝/井盖/坑洼)(6000张图片已划分、已标注)|适用于YOLO系列深度学习分类检测任务【数据集分享】

热门文章

最新文章