赞
踩
spawn
替代linux的fork
(创建一个新进程)。所以要将把loader进行迭代的代码进行封装起来(如if语句或者函数里面,即不能直接写在外面), 也可以直接外面加上if __name__ == '__main__':
。{x: y}
的集合,需要在dataset
类中说明有这么一组{x: y}
即可:
使用torch.nn
创建神经网络,nn
包会使用autograd
包定义模型和求梯度。一个nn.Module
对象包括了许多网络层,并且用forward(input)
方法来计算损失值,返回output。
训练一个神经网络通畅需要以下步骤:
Module
对象的forward()
方法)weight=weight-learning_rate × gradien
。PyTorch数据读入是通过Dataset
+Dataloader
的方式完成的。
Dataset
定义好数据的格式和数据变换形式,用来构造数据集。从而数据集支持【索引】,通过下标将样本取出
Dataloader
用iterative的方式不断读入批次数据。Dataloader
用来拿出一个mini-batch。
我们可以定义自己的Dataset
类来实现灵活的数据读取,定义的类需要继承PyTorch自身的Dataset
类。主要包含三个函数:
__init__
: 用于向类中传入外部参数,同时定义样本集__getitem__
: 用于逐个读取样本集合中的元素(获取样本对,即上面说的{x: y}
),可以进行一定的变换,并将返回训练/验证所需的数据__len__
: 用于返回数据集的样本数,即数据集的长度其实数据集就是一组{x: y}
的集合,需要在dataset
类中说明有这么一组{x: y}
即可:
下面以cifar10数据集为例给出构建Dataset类的方式:
train_data = datasets.ImageFolder(train_path,
transform=data_transform)
val_data = datasets.ImageFolder(val_path,
transform=data_transform)
这里使用了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)_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__
返回数据集的样本数。
构建好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)
其中:
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()
这种格式也是用得最多的,相信大家在深度学习训练代码中也经常看到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'>
可以看到下面的结果,每个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)对dataloader实例化对象使用下标即可调出当前这样本数据(注意:会返回__getitem__
函数的返回值,但是不会执行函数内容)。
(2)Data.iloc[1:]
可以得到从第二行开始的数据。
外层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_data
和self.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()
首先创建类对象m,然后通过 model(input)
实际上调用 __call__(input)
,然后 __call__(input)
调用forward()
函数。可以看到Loss图像后面是震荡地下降的,后面可以调整学习速率,或者尝试进行数据增强、改用其他优化器等操作。
一图胜千言(如下),如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)
pad_sequence
进行padding,自定义的collate_fn
函数的形参只有一个——可以是字典列表或者元组列表(取决于dataset
怎么写)。input_data
和label
取出,分别组成对应的列表;然后将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)使用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
(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函数传参
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。