零、训练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__: 用于逐个读取样本集合中的元素,可以进行一定的变换,并将返回训练/验证所需的数据
__len__: 用于返回数据集的样本数
1.2 代码栗子
下面以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') if self.transform is not None: image = self.transform(image) return image, label def __len__(self): return len(self.image_file)
构建好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:对于样本最后一部分没有达到批次数的样本,不再参与训练
看加载的数据。PyTorch中的DataLoader的读取可以使用next和iter来完成。
import matplotlib.pyplot as plt images, labels = next(iter(val_loader)) print(images.shape) plt.imshow(images[0].transpose(1,2,0)) plt.show()
二、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_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()
四、作业
(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