基本配置

以下的超参数可以统一配置,方便后续修改:

  1. batch_size
  2. 初始学习率
  3. 训练次数(max_epochs)
  4. GPU配置

batch_size=16
lr=1e4
max_epochs=100
device=torch.device("cuda:1" if torch.cuda.is_available() else "cpu")

数据读入

pytorch是通过Dataset和DataLoader的形式完成数据读入的。其中两个类分别有如下作用:

  1. Dataset:定义好数据的格式和数据变换形式。
  2. DataLoader:用iterative的方式不断地读入批次数据。

Dataset

pytorch预定义了很多数据集,定义在了torchvision.datasets包中,常用的MNIST、CIFAR、ImageNet等数据都有,但是在实际项目中, 我们大多数情况都是使用的自定义的数据集,这时候急需要继承torch.utils.data.Dataset,下面我们将演示一下,如何实现自定义数据集。

通用的Dataset模板

1
2
3
4
5
6
7
8
9
10
11
class MyDataset(Dataset):
def __init__(self, ...):
...

def __len__(self):
...
return len # 返回数据集数据个数

def __getitem__(self, index):
...
return image, label # 返回第 index 个数据 + 标签

通用模板包含3个方法:

  1. __init__:数据集的初始化,可传入一些必要的参数
  2. __len__:返回数据集个数
  3. __getitem__:传入index,返回第index个数据和标签值

对于以上3个方法的实现比较灵活,一般包含以下两种方式:

  • __init__初始化时,将所有数据读入,__len__返回数据集长度,__getitem__直接通过索引返回数据即可。
  • __init__只给出本地文件地址,在__getitem__中现场读取相应数据和标签。

自定义Dataset示例

一个简单的实例,该数据集包含从1-1000的整数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from torch.utils.data import Dataset

class NumbersDataset(Dataset):
def __init__(self):
self.samples = list(range(1, 1001))

def __len__(self):
return len(self.samples)

def __getitem__(self, idx):
return self.samples[idx]


if __name__ == '__main__':
dataset = NumbersDataset()
print(len(dataset))
print(dataset[100])
print(dataset[122:361])

高级Dataset示例

该示例来自pytorch官方网站

该数据集返回Fashion-MNIST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)
self.img_dir = img_dir
self.transform = transform
self.target_transform = target_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]
if self.transform:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label

应当注意:自定义的数据集应当只包含Train数据或者Test数据,然后通过传入Train数据和Test数据构成不同的数据集。

DataLoader

1
2
3
4
5
6
7
8
9
from torch.utils.data import DataLoader

# Dataset
train_data = CustomImageDataset(train_path, transform=data_transform)
val_data = CustomImageDataset(val_path, transform=data_transform)

# DataLoader
train_loader = torch.utils.data.DataLoader(train_data, batch_size=16, num_workers=4, shuffle=True, drop_last=True)
val_loader = torch.utils.data.DataLoader(val_data, batch_size=16, num_workers=4, shuffle=False)

其中:

  • batch_size:样本是按“批”读入的,batch_size就是每次读入的样本数,以上示例每次读入16个样本。
  • num_workers:有多少个进程用于读取数据,Windows下该参数设置为0,Linux下常见的为4或者8,根据自己的电脑配置来设置
  • shuffle:是否将读入的数据打乱,一般在训练集中设置为True,验证集中设置为False
  • drop_last:对于样本最后一部分没有达到批次数的样本,使其不再参与训练

PyTorch中的DataLoader的读取可以使用next和iter来完成,使用如下代码查看dataloader读入的数据:

1
images, labels = next(iter(val_loader))

模型构建

神经网络构建

通用的神经网络模板

Module 类是 torch.nn 模块里提供的一个模型构造类,是所有神经网络模块的基类,我们可以继承它来定义我们想要的模型。

1
2
3
4
5
6
7
8
9
10
import torch
from torch import nn

class CustomModule(nn.Module):
# 模型初始化
def __init__(self, **kwargs):
···
# 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
def forward(self, x):
···

定义模型时,无需定义反向传播函数,系统将通过计算图,自动生成反向传播所需的backward函数。

简单的MLP网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch
from torch import nn

class MLP(nn.Module):
# 声明带有模型参数的层,这里声明了两个全连接层
def __init__(self, **kwargs):
# 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数
super(MLP, self).__init__(**kwargs)
self.hidden = nn.Linear(784, 256)
self.act = nn.ReLU()
self.output = nn.Linear(256,10)

# 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
def forward(self, x):
o = self.act(self.hidden(x))
return self.output(o)

使用网络:

1
2
3
X = torch.rand(batch_size,784) # 设置一个随机的输入张量
net = MLP() # 实例化模型
net(X) # 前向计算

神经网络中常见的层

损失函数

训练与评估

训练

一个完成的训练过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def train(epoch):
model.train()
train_loss = 0
for data, label in train_loader:
data, label = data.cuda(), label.cuda()
optimizer.zero_grad()
output = model(data)
loss = criterion(output, label)
loss.backward()
optimizer.step()
train_loss += loss.item()*data.size(0)

train_loss = train_loss/len(train_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))

验证

对应的,一个完整的验证过程为:

1
2
3
4
5
6
7
8
9
10
11
12
13
def val(epoch):       
model.eval()
val_loss = 0
with torch.no_grad():
for data, label in val_loader:
data, label = data.cuda(), label.cuda()
output = model(data)
preds = torch.argmax(output, 1)
loss = criterion(output, label)
val_loss += loss.item()*data.size(0)
running_accu += torch.sum(preds == label.data)
val_loss = val_loss/len(val_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, val_loss))