当前位置:   article > 正文

【PyTorch基础教程8】数据预处理和批次读入(dataset和dataloader类,自定义collate_fn)_pytorch 自定义图片数据读入

pytorch 自定义图片数据读入

内容总结

  • mini-batch:外层for为训练周期,内层for迭代mini-batch。
    1)epoch:将所有的训练样本都进行了一次前向传递和反向传播,是一个epoch
    2)Batch-size:每次训练所用的样本数量
    3)Iteration:内层for执行的次数。
    ex:共有10000个样本,batch-size=1000,即每次拿1000个样本进行训练,则Iteration=10次。
  • linux和win系统的多进程库是不同的:在win中是用spawn替代linux的fork(创建一个新进程)。所以要将把loader进行迭代的代码进行封装起来(如if语句或者函数里面,即不能直接写在外面), 也可以直接外面加上if __name__ == '__main__':
  • 数据集就是一组{x: y}的集合,需要在dataset类中说明有这么一组{x: y}即可:
    • 对于图像分类任务,图像+分类
    • 对于目标检测任务,图像+bbox、分类
    • 对于超分辨率任务,低分辨率图像+超分辨率图像
    • 对于文本分类任务,文本+分类

零、训练DNN的过程

在这里插入图片描述
使用torch.nn创建神经网络,nn包会使用autograd包定义模型和求梯度。一个nn.Module对象包括了许多网络层,并且用forward(input)方法来计算损失值,返回output。
训练一个神经网络通畅需要以下步骤:

  • 定义一个神经网络,通常有一些可以训练的参数
  • 迭代一个数据集(Dataset)
  • 处理网络的输入
  • 计算损失(会调用Module对象的forward()方法)
  • 计算损失函数对参数的梯度
  • 更新参数,通常使用如下的梯度下降方法来更新:weight=weight-learning_rate × gradien

一、读入数据

1.1 参数解读

PyTorch数据读入是通过Dataset+Dataloader的方式完成的。

Dataset定义好数据的格式和数据变换形式,用来构造数据集。从而数据集支持【索引】,通过下标将样本取出
Dataloader用iterative的方式不断读入批次数据。Dataloader用来拿出一个mini-batch

我们可以定义自己的Dataset类来实现灵活的数据读取,定义的类需要继承PyTorch自身的Dataset类。主要包含三个函数:

  • __init__: 用于向类中传入外部参数,同时定义样本集
  • __getitem__: 用于逐个读取样本集合中的元素(获取样本对,即上面说的{x: y}),可以进行一定的变换,并将返回训练/验证所需的数据
  • __len__: 用于返回数据集的样本数,即数据集的长度

1.2 继承dataset父类的数据类

其实数据集就是一组{x: y}的集合,需要在dataset类中说明有这么一组{x: y}即可:

  • 对于图像分类任务,图像+分类
  • 对于目标检测任务,图像+bbox、分类
  • 对于超分辨率任务,低分辨率图像+超分辨率图像
  • 对于文本分类任务,文本+分类

下面以cifar10数据集为例给出构建Dataset类的方式:

train_data = datasets.ImageFolder(train_path, 
								  transform=data_transform)
val_data = datasets.ImageFolder(val_path, 
								transform=data_transform)
  • 1
  • 2
  • 3
  • 4

这里使用了PyTorch自带的ImageFolder类的用于读取按一定结构存储的图片数据(path对应图片存放的目录,目录下包含若干子目录,每个子目录对应一个类的图片)。其中data_transform可以对图像进行一定的变换,如翻转、裁剪等操作,可自己定义。

另一个例子:其中图片存放在一个文件夹,另外有一个csv文件给出了图片名称对应的标签。这种情况下需要自己来定义Dataset类:

class MyDataset(Dataset):
    def __init__(self, data_dir, info_csv, image_list, transform=None):
        """
        Args:
            data_dir: path to image directory.
            info_csv: path to the csv file containing image indexes
                with corresponding labels.
            image_list: path to the txt file contains image names to training/validation set
            transform: optional transform to be applied on a sample.
        """
        label_info = pd.read_csv(info_csv)
        image_file = open(image_list).readlines()
        self.data_dir = data_dir
        self.image_file = image_file
        self.label_info = label_info
        self.transform = transform

    def __getitem__(self, index):
        """
        Args:
            index: the index of item
        Returns:
            image and its labels
        """
        image_name = self.image_file[index].strip('\n')
        raw_label = self.label_info.loc[self.label_info['Image_index'] == image_name]
        label = raw_label.iloc[:,0]
        image_name = os.path.join(self.data_dir, image_name)
        image = Image.open(image_name).convert('RGB')
        # 引入之前写的transform
        if self.transform is not None:
            image = self.transform(image)
        return image, label

    def __len__(self):
        return len(self.image_file)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

分析上面的代码:
(1)_init_函数来构造这个数据类的基本组成部分,本函数内传入了四个参数(data_dir,info_csv,image_list,transform)

  • data_dir:图像目录的路径。
  • info_csv:包含图像索引与对应标签的 csv 文件的路径
  • image_list:包含训练/验证集的图像名称的txt 文件的路径
  • transform:要应用于样本的可选变换。一般会在图像识别内应用,形如反转图像,图像颠倒操作

(2)__getitem__函数返回一个对应的数据以及其标签or分类信息,注意这里引入了之前的self.transform(image)
(3)__len__返回数据集的样本数。

1.3 两种格式的dataloader批次读入数据

构建好Datadet后,就可以使用DataLoader来按批次读入数据了,实现代码如下。

train_loader = torch.utils.data.DataLoader( train_data, 
											batch_size=batch_size, 
											num_workers=4, 
											shuffle=True, 
											drop_last=True)
val_loader = torch.utils.data.DataLoader(val_data, 
										 batch_size=batch_size, 
										 num_workers=4, 
										 shuffle=False)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

其中:

  • batch_size:样本是按“批”读入的,batch_size就是每次读入的样本数
  • num_workers:有多少个进程用于读取数据(在构成mini-batch的过程中)
  • shuffle:是否将读入的数据打乱
  • drop_last:对于样本最后一部分没有达到批次数的样本,不再参与训练

dataloader本质上是一个可迭代对象,可以使用iter()进行访问,采用iter(dataloader)返回的是一个迭代器,然后可以使用next()访问。
也可以使用enumerate(dataloader)的形式访问。看加载的数据。

import matplotlib.pyplot as plt
images, labels = next(iter(val_loader))
print(images.shape)
plt.imshow(images[0].transpose(1,2,0))
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5

(1)iterator格式数据

这种格式也是用得最多的,相信大家在深度学习训练代码中也经常看到for i, data in enumerate(dataloader, 0):语句,注意这里的i不是当前的batch编号,而是当前batch中的第i个样本数据。

# 第一种数据集:iterator格式
data = [i for i in range(100)]
for item in data:
    print(item, end = " ")
print("\n")

# 1.iter将容器变为迭代器
data_iter = iter(data)
print(data_iter)

# 2.next
item = next(data_iter, None)
print(item)

# 3.有点像yield迭代遍历数据 
while item is not None:
    print(item, end = ' ')
    item = next(data_iter, None)
    
    
# 4.实例化dataloader
from torch.utils.data import DataLoader
dataloader = DataLoader(dataset = data,
                        batch_size = 5,
                        shuffle = False)

# 5.dataloader迭代遍历数据(每次遍历一个batch)
for i, data in enumerate(dataloader, 0):
    print(i, data)
    # print("data的type:", type(data), "\n")
    # 打印:data的type: <class 'torch.Tensor'>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

(2)map格式的dataset数据集

可以看到下面的结果,每个batch中有2个样本数据,每个样本数据就是一对键值对。

# 第二种数据集:map格式的dataset
dataset = {0: '张三', 1:'李四', 2:'王五', 3:'赵六'}

# 2个数据组成一个batch
dataloader = DataLoader(dataset, batch_size=2)

for i, value in enumerate(dataloader):
    print(i, value)

"""
0 ['张三', '李四']
1 ['王五', '赵六']
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

小结:
(1)对dataloader实例化对象使用下标即可调出当前这样本数据(注意:会返回__getitem__函数的返回值,但是不会执行函数内容)。
(2)Data.iloc[1:]可以得到从第二行开始的数据。

二、Epoch,Batch-size,Iterations

外层for为训练周期,内层for迭代mini-batch。

(1)epoch:将所有的训练样本都进行了一次前向传递和反向传播,是一个epoch
(2)Batch-size:每次训练所用的样本数量
(3)Iteration:内层for执行的次数。

ex:共有10000个样本,batch-size=1000,即每次拿1000个样本进行训练,则Iteration=10次。
在这里插入图片描述
加载数据集的两种方法:
(1)all data:适合数据集比较小的
(2)大数据类似几十G的图像数据或语音等无结构数据:加载部分。

linux和win系统的多进程库是不同的:在win中是用spawn替代linux的fork(创建一个新进程)。所以要将把loader进行迭代的代码进行封装起来(如if语句或者函数里面,即不能直接写在外面), 也可以直接外面加上if __name__ == '__main__':

三、糖尿病数据集处理

(1)因为该数据集较小,所以将所有的数据和标签都加载到self.x_dataself.y_data(在内存中)中了。 在__getitem__函数内当传参(x, y)时,别看形式参数好像只有index,但其实返回的是元组(x, y)(回顾之前的*args)。

(2)为了获得当前是第几次迭代,可以用enumerate,这样就可以获得下标i即当前的迭代次数。在训练的内循环中,每次拿出的data是一个元组(x, y)。而dataloader会自动将多个data组合成Tensor矩阵(所以不需要我们自己组成tensor)。

(3)训练过程和之前类似,只是为了使用mini-batch方式训练,嵌套了内层的for循环。
在测试中的dataloader一般设置shuffle = False即不用shuffle(洗不洗影响不大)。

# -*- coding: utf-8 -*-
"""
Created on Mon Oct 18 14:06:02 2021

@author: 86493
"""
import numpy as np
import torch
import torch.nn as nn
# Dataset是抽象类(不能实例化,只能被子类继承)
from torch.utils.data import Dataset 
# dataloader做shuffle、batchsize等,可实例
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt

losslst = []

# 构建自己的数据集类
class DiabetesDataset(Dataset):
    
    def __init__(self, filepath):
        # 一般类型用np.float32位,而不用double
        xy = np.loadtxt(filepath, 
                        delimiter = ' ',
                        dtype = np.float32)
        self.len = xy.shape[0]
        # 最后一列不要,即要前9列,不要最后的一列
        self.x_data = torch.from_numpy(xy[:, :-1])
        # [-1]则拿出来的是一个矩阵,去了中括号则拿出向量
        self.y_data = torch.from_numpy(xy[:, [-1]])
                
        
    # magic function
    # 后面可以用dataset[index]
    def __getitem__(self, index): 
        return self.x_data[index], self.y_data[index]
    
    # 返回数据集的length,即数据条数
    def __len__(self): 
        return self.len 

dataset = DiabetesDataset('diabetes.csv')
"""
train_loader = DataLoader(dataset = dataset,
                          batch_size = 4,
                          shuffle = True,
                          num_workers = 1)
"""
train_loader = DataLoader(dataset = dataset,
                          batch_size = 32,
                          shuffle = True)

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.linear1 = nn.Linear(9, 6)
        self.linear2 = nn.Linear(6, 4)
        self.linear3 = nn.Linear(4, 1)
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x):
        x = self.sigmoid(self.linear1(x))
        x = self.sigmoid(self.linear2(x))
        x = self.sigmoid(self.linear3(x))
        return x

model = Model()
# 使用交叉熵
# criterion = nn.BCELoss(size_average = True)
criterion = nn.BCELoss(reduction = 'mean')
optimizer = torch.optim.SGD(model.parameters(),
                            lr = 0.01)


# batch处理
if __name__ == '__main__':
    for epoch in range(50):
        # 内层循环执行一个mini-batch
        for i, data in enumerate(train_loader, 0):
        # 也可以写成for i, (input, labels) in enumerate(train_loader, 0):
            
            # 1.准备数据
            inputs, labels = data 
        
            # 2.向前传递
            y_predict = model(inputs)
            loss = criterion(y_predict, labels)
            losslst.append(loss.item())
            print(epoch, i, loss.item())
        
            # 3.反向传播
            optimizer.zero_grad()
            loss.backward()
        
            # 4.更新参数
            optimizer.step()
            
# 画图
plt.plot(range(700), losslst)
plt.xlabel("epoch")
plt.ylabel("Loss")
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102

在这里插入图片描述
首先创建类对象m,然后通过 model(input)实际上调用 __call__(input),然后 __call__(input)调用forward()函数。可以看到Loss图像后面是震荡地下降的,后面可以调整学习速率,或者尝试进行数据增强、改用其他优化器等操作。

四、使用自定义的collate_fn函数

一图胜千言(如下),如NLP中每个句子长度都不一样时,如果按照每个batch的长度都填充到一致,那显然效率不是最高的(占内存),dataloader中的参数collate_fn就是为了这种情况,定制函数使得每个batch中的样本长度,只和当前batch中最长的样本长度相同,即每个batch中的样本长度可以不同,使得高效迭代样本训练模型:
在这里插入图片描述

from torch.nn.utils.rnn import pad_sequence #(1)
from pprint import pprint

# values are token indices but it does not matter - it can be any kind of variable-size data
nlp_data = [
    {'tokenized_input': [1, 4, 5, 9, 3, 2],
     'label':0},
    {'tokenized_input': [1, 7, 3, 14, 48, 7, 23, 154, 2],
     'label':0},
    {'tokenized_input': [1, 30, 67, 117, 21, 15, 2],
     'label':1},
    {'tokenized_input': [1, 17, 2],
     'label':0},
]

def custom_collate(data): #(2)
    inputs = [torch.tensor(d['tokenized_input']) for d in data] #(3)
    labels = [d['label'] for d in data]
    inputs = pad_sequence(inputs, batch_first=True) #(4)
    labels = torch.tensor(labels) #(5)
    return { #(6)
        'tokenized_input': inputs,
        'label': labels
    }
loader = DataLoader(nlp_data, batch_size=2, shuffle=False, collate_fn=custom_collate) #(7)
iter_loader = iter(loader)

batch1 = next(iter_loader)
pprint(batch1)

batch2 = next(iter_loader)
pprint(batch2)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 上面代码中使用pad_sequence进行padding,自定义的collate_fn函数的形参只有一个——可以是字典列表或者元组列表(取决于dataset怎么写)。
  • 如果形参data是一个字典列表,则需要单独将数据的input_datalabel取出,分别组成对应的列表;然后将inputs按照当前batch的最长样本进行补齐,将labels从数组转为tensor。
  • 结果如下:
{'label': tensor([0, 0]),
 'tokenized_input': tensor([[  1,   4,   5,   9,   3,   2,   0,   0,   0],
        [  1,   7,   3,  14,  48,   7,  23, 154,   2]])}
{'label': tensor([1, 0]),
 'tokenized_input': tensor([[  1,  30,  67, 117,  21,  15,   2],
        [  1,  17,   2,   0,   0,   0,   0]])}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

五、作业

(1)使用torchvision.datasets,其中也有很多数据集,如MNIST、Fashion-MNIST、EMNIST、COCO、LSUN、ImageFolder、DatasetFolder、Imagenet-12、CIFAR、STL10、PhotoTour等数据集。
在这里插入图片描述
(2)Build DataLoader for:
• Titanic dataset: https://www.kaggle.com/c/titanic/data
• Build a classifier using the DataLoader

Reference

(1)pytorch: RuntimeError: DataLoader worker (pid(s) 27292) exited unexpectedly
(2)datawhale notebook
(3)Understand collate_fn in PyTorch
(4)pytorch中collate_fn函数的使用&如何向collate_fn函数传参

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/318247
推荐阅读
相关标签
  

闽ICP备14008679号