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

简介: 简要介绍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的扩展。

相关文章
|
3天前
|
机器学习/深度学习 人工智能 TensorFlow
人工智能浪潮下的编程实践:从Python到深度学习的探索之旅
【9月更文挑战第6天】 在人工智能的黄金时代,编程不仅仅是一种技术操作,它成为了连接人类思维与机器智能的桥梁。本文将通过一次从Python基础入门到构建深度学习模型的实践之旅,揭示编程在AI领域的魅力和重要性。我们将探索如何通过代码示例简化复杂概念,以及如何利用编程技能解决实际问题。这不仅是一次技术的学习过程,更是对人工智能未来趋势的思考和预见。
|
1天前
|
机器学习/深度学习 数据采集 数据可视化
深度学习实践:构建并训练卷积神经网络(CNN)对CIFAR-10数据集进行分类
本文详细介绍如何使用PyTorch构建并训练卷积神经网络(CNN)对CIFAR-10数据集进行图像分类。从数据预处理、模型定义到训练过程及结果可视化,文章全面展示了深度学习项目的全流程。通过实际操作,读者可以深入了解CNN在图像分类任务中的应用,并掌握PyTorch的基本使用方法。希望本文为您的深度学习项目提供有价值的参考与启示。
|
1天前
|
机器学习/深度学习 边缘计算 人工智能
深度学习的奥秘:从理论到实践
在这篇文章中,我们将深入探讨深度学习的基本原理和实际应用。首先,我们将介绍深度学习的基本概念和工作原理,然后通过一些实际案例来展示深度学习的强大能力。最后,我们将讨论深度学习的未来发展趋势和可能的挑战。无论你是深度学习的初学者,还是已经有一定基础的研究者,这篇文章都将为你提供有价值的信息和启示。
9 1
|
5天前
|
机器学习/深度学习 自动驾驶
深度学习的奥秘:从理论到实践
本文深入浅出地探讨了深度学习的基本原理、关键技术及其在现实世界中的应用。通过浅显易懂的语言,本文旨在为初学者揭开深度学习的神秘面纱,同时为有一定基础的读者提供更深层次的理解和应用思路。
|
9天前
|
机器学习/深度学习 人工智能 自然语言处理
揭秘深度学习:从理论到实践的探索之旅
深度学习,这个听起来有些高冷的技术名词,其实已经悄然渗透进我们生活的方方面面。本文将深入浅出地介绍深度学习的基本概念、核心算法以及在多个领域的应用实例,帮助读者理解这一前沿技术的魅力所在,并探讨其未来的发展趋势。让我们一起踏上这段揭秘深度学习的奇妙旅程吧!
|
10天前
|
机器学习/深度学习 人工智能 自然语言处理
探索深度学习的奥秘:从理论到实践
【8月更文挑战第31天】本文将带你走进深度学习的世界,从理论基础到实际应用,一步步揭示深度学习的神秘面纱。你将了解到深度学习的基本概念、关键技术以及在图像识别、自然语言处理等领域的应用。同时,我们还将通过一个简单的代码示例,让你亲身体验深度学习的魅力。无论你是初学者还是有一定基础的学习者,这篇文章都将为你提供有价值的参考和启示。让我们一起踏上这段探索深度学习的旅程吧!
|
10天前
|
机器学习/深度学习 人工智能 自动驾驶
深度学习中的图像识别技术与实践
【8月更文挑战第31天】 本文深入探索了深度学习在图像识别领域的应用,通过简明易懂的语言和实例,向读者展示了如何利用神经网络模型进行图像处理和分析。文章不仅介绍了理论基础,还提供了实用的代码示例,帮助初学者快速入门并实现自己的图像识别项目。
|
10天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习之旅:从理论到实践
【8月更文挑战第31天】本文将带你走进深度学习的世界,探索其背后的理论基础和实际应用。我们将从神经网络的基本概念出发,逐步深入到深度学习的核心技术,如反向传播、卷积神经网络等。同时,我们还将通过代码示例,展示如何利用深度学习技术解决实际问题。无论你是初学者还是有一定基础的学习者,都能在本文中找到有价值的信息。让我们一起踏上深度学习的探索之旅吧!
|
10天前
|
机器学习/深度学习 人工智能 TensorFlow
探索深度学习的奥秘:从理论到实践
【8月更文挑战第31天】 本文将深入浅出地介绍深度学习的基本原理,并通过一个简单的代码示例,让读者快速掌握深度学习的基本概念和应用。我们将从神经网络的构建、训练和优化等方面展开讨论,帮助读者更好地理解深度学习的内涵和意义。
|
7天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
深度学习在图像识别中的应用与挑战
【9月更文挑战第2天】本文将探讨深度学习技术如何在图像识别领域大放异彩,并分析其面临的主要挑战。我们将通过一个实际的代码示例,展示如何利用深度学习模型进行图像分类任务,从而让读者对深度学习在图像识别中的应用有一个直观的理解。
45 22
下一篇
DDNS