当前位置:   article > 正文

深度学习——PyTorch框架_pytorch框架原理

pytorch框架原理

绪论
  对于算法科研人员来说,熟练掌握并应用一种深度学习框架是一项必备技能。学术研究人员关心的是研究中算法的迭代速度,其应用场景通常是相对较小的数据集上,最大的限制因素不是性能,而是快速实现并验证假设的能力,使得学术研究倾向于PyTorch。

一、NumPy基础
  在机器学习和深度学习中,图像等输入数据最终都要转换为数组或矩阵。NumPy能够有效的进行数组和矩阵的运算,是数据科学的通用语言,对PyTorch而言,其重要性十分明显。
  Python本身含有列表与数组数据结构,但对于大数据来说,这些结构与很多不足。例如列表的元素可以是任何对象,因此其保存的是对象的指针,这种结构显然浪费CPU和内存等宝贵资源;至于数组,其与C相似,但又不支持高维。NumPy的诞生弥补了这些不足:NumPy提供了ndarray与ufunc用于存储与处理高维数组。

1.1 生成NumPy数组
  NumPy是Python的外部库,不在标准库中,在使用之前需要导入:

import numpy as np
  • 1
'
运行

NumPy封装了一个新的数据类型ndarray,该对象封装了许多常用的数学运算函数,方便我们做数据处理、数据分析等。

1.1.1 转换创建数组
  Python的基本数据类型可以直接转换为ndarray:

list = [3.14, 2.17, 0, 1, 2]
nd = np.array(list)
  • 1
  • 2

上述示例的列表换成元组依然成立。如果Python的基本数据类型是嵌套的,那么将会生成多维ndarray。

1.1.2 np.random生成数组
  在深度学习中,需要对一些参数进行初始化,为了更有效训练模型,提高模型的性能,有些初始化还需要满足一定条件,如满足正态分布或均匀分布等。np.random提供了常用的随机生成:
  -np.random.random(),生成0到1之间的随机数;
  -np.random.uniform(),生成均匀分布的随机数;
  -np.random.randn(),生成标准正态的随机数;
  -np.random.randint(),生成随机整数;
  -np.random.normal(),生成正态分布。

1.1.3 指定创建数组
  参数初始化时,有时需要生成一些特殊矩阵,如零矩阵等,可以通过NumPy的函数实现,例如:
  -np.zeros(),创建全为0的数组;
  -np.ones(),创建全为1的数组;
  -np.empty(),创建空数组;
  -np.eye(),创建单位矩阵;

1.1.4 其他函数生成数组
  arange()是NumPy中的函数,其函数格式为

arange([start,] stop, [step,] dtype = None)
  • 1

其与Python内值的range()相似,并生成一个包含从start或0,到stop之前元素的,以step或1为步长的ndarray。

  linspace()也是NumPy中的函数,其函数格式为

linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
  • 1

其可以根据输入的指定数据范围以及数量自动生成一个线性等分向量,其中endpoint指包含终点,retstep指返回包含ndarray和步长的元组。

  此外,NumPy还提供其他的函数生成数组,在此不再展开介绍。

1.2 NumPy数据操作
  在机器学习和深度学习中,涉及大量的数组或矩阵的运算与操作。

1.2.1 NumPy数组运算
  np.multiply()用于矩阵的元素级乘法,其函数的简单格式为

multiply(x1, x2)
  • 1

其也可以简单的代替为

x1 * x2
  • 1

其元素之一也可以是标量。

  np.dot()用于矩阵的矩阵级乘法,其函数的格式为

dot(x1, x2, out=None)
  • 1

其参数需遵守矩阵乘法法则。

1.2.2 NumPy数组变形
  修改指定数组的形状是NumPy的常用操作之一,常见函数与变量如下:
  -arr.reshape(),对arr的维度改变而不修改向量;
  -arr.resize(),对arr的维度改变且对向量等比改变;
  -arr.T,arr的转置;
  -arr.ravel(),生成将arr展为1维的数组的视图,随着arr的改变而改变;
  -arr.flatten(),生成将arr展为1维的数组的副本,作为新的ndarray;
  -arr.squeeze(),对arr进行降维,及删除通道数为1的维度;
  -arr.transpose(),对arr的高维矩阵进行轴对换。

1.2.3 NumPy批量处理
  深度学习中,由于源数据都比较大,通常需要采用批处理。将大数据拆分成批数据需要经过随机打乱数据、指定批大小并划分的步骤。一个简单的示例为:

import numpy as np

data = np.random.randn(10000, 2, 3)
np.random.shuffle(data)
batch_size = 100

for i in range(0, len(data), batch_size):
    x_batch_sum = np.sum(data[i:i + batch_size])
    # some codes to processing x_batch_sum
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
'
运行

其中np.random.shuffle()将data的第一维度作为数据数量将数据打乱而不打乱单个数据点的数据。

1.3 广播机制
  NumPy的ufunc对输入数组的shape有一定的要求,当其不满足要求时,会使用广播机制。广播在整个NumPy中用于决定如何处理形状迥异的数组,其具有如下规则:
  -所有输入数组项shape的维度最高的对齐,不足的维度通过在shape前面加一个通道数为1维度的补齐;
  -输出数组的shape是输入数组的shape各轴的最大值;
  -仅当输入数组对应的维度的通道相同或为1时可以用于计算;
  -当输入数组的某个维度通道数为1时,则在运算时按需赋值此通道的值。

  考虑一个简单的示例,对于shape为(4, 1)的矩阵A与shape为(3,)的向量B,其加法操作根据广播机制有如下处理:
  -B需要向A的维度对齐,其shape变为(1, 3);
  -输出数组的shape取A与B的通道最大值,shape为(4, 3);
  -A复制第2维度的通道形成(4, 3)的shape,B同理,并运算。


二、PyTorch基础
  PyTorch类似于NumPy,属于动态框架,支持快速和灵活的方式构建动态神经网络,并允许在训练过程中快速更改代码且不妨碍其性能,并支持动态图,是快速实验的理想选择。

2.1 Numpy与Tensor
  PyTorch与Numpy相似,其共享内存,转换非常方便。其最大的区别是Numpy将ndarray放在CPU中加速运算,而PyTorch将Tensor放在GPU中加速运算。

2.1.1 生成Tensor张量
  生成Tensor的方法很多,可以从列表或者ndarray中构建,也可以指定形状构建。常见的构建方法有:
  -torch.Tensor(),直接从Python或Numpy构造张量;
  -torch.eye(),创建二维对角张量;
  -torch.ones(),创建指定shape的零张量;
还有一些类似NumPy的生成张量操作,这里不再展开叙述。

2.1.2 Tensor张量变形
  在处理数据、构建网络层等过程中,经常需要了解与操作Tensor的形状。Torch与NumPy对形状的操作类似:
  -tensor.numel(),计算tensor中元素的个数;
  -tensor.view(),生成修改tensor的shape的视图,随着tensor的改变而改变;
  -tensor.item(),若tensor只有单个元素,则返回Python标量;
还有一些类似NumPy的生成张量操作,这里不再展开叙述。

2.2 参数学习
  在神经网络中,一个重要内容就是进行参数学习,而参数学习离不开求导。PyTorch为张量上的所有操作提供了自动求导功能。

2.2.1 计算图
  计算图是一种有向无环图,其用图数据结构表示算子与变量之间的关系。例如表达式 y = w x + b \bm y = \bm{wx} + \bm b y=wx+b,其中 x , w , b \bm x, \bm w, \bm b x,w,b是自定义的变量,其不依赖于其他变量,也是图的叶结点,为了计算叶结点的梯度,需要把张量参数requires_grad属性设置为True;而 y \bm y y是计算得到的变量,作为最终的计算结果,亦是根结点。由变量及算子,如加法add(),乘法mul()构成的完整的计算过程,称为前向传播
  深度学习的目标是根据叶结点的梯度,根据复合函数的链式法则更新叶节点,这一过程称为反向传播。PyTorch调用backward()将自动计算各结点的梯度,对于叶结点,梯度值会累加到grad属性中,而对于非叶结点的计算操作会记录在grad_fn属性中,而叶结点的该属性值为None。

2.2.2 反向传播
  考虑标量算式 y = w x + b y = wx + b y=wx+b,那么对于前向及反向传播并自动求导,首先需要定义叶结点及算子结点:

import torch


x = torch.Tensor([2])
w = torch.randn(1, requires_grad=True)
b = torch.randn(1, requires_grad=True)

z = torch.mul(x, w)
y = torch.add(z, b)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以通过x.requires_grad、x.is_leaf、x.grad_fn来访问变量x的梯度、叶结点、算子结点属性,其他变量同理。由于变量x无需更新,是计算图的叶结点,因此上述三个表达式的结果分别为False、True、None。
  在定义计算图后,使用

y.backward()
  • 1

实现梯度的反向传播。

2.3 机器学习
  考虑一个有关回归的机器学习:给出一组数据 x \bm x x,并基于表达式 y = 3 x 2 + 2 y = 3x^2+2 y=3x2+2与一定的噪声形成数据 y \bm y y,然后构造一个机器学习模型 y = w x 2 + b y = wx^2 + b y=wx2+b,学习参数 w w w b b b。分别使用NumPy和PyTorch完成,来比较其之间的异同。

2.3.1 NumPy实现机器学习
  NumPy实现及其学习的代码多但透明,有利于理解每步的工作原理。
  首先导入库:

import numpy as np
import matplotlib.pyplot as plt
  • 1
  • 2
'
运行

然后设置随机数种子,生成同一份数据 x \bm x x y \bm y y

np.random.seed(100)
x = np.linspace(-1, 1, 100).reshape(100, 1)
y = 3 * np.power(x, 2) + 2 + 0.2 * np.random.rand(x.size).reshape(100, 1)
  • 1
  • 2
  • 3

并观察其分布:

plt.scatter(x, y)
plt.show()
  • 1
  • 2

matplot
再初始化权重参数:

在这里插入代码片
  • 1

设批大小为100,定义损失函数为均方误差,那么有 L o s s = 1 2 ∑ i = 1 100 ( w x i 2 + b − y i ) 2 Loss = \frac{1}{2}\sum_{i=1}^{100}(wx_i^2+b-y_i)^2 Loss=21i=1100(wxi2+byi)2损失函数对参数求导,有 ∂ L o s s / ∂ w = ∑ i = 1 100 ( w x i 2 + b − y i ) x i 2 ∂ L o s s / ∂ b = ∑ i = 1 100 ( w x i 2 + b − y i )

Loss/w=i=1100(wxi2+byi)xi2Loss/b=i=1100(wxi2+byi)
Loss/wLoss/b=i=1100(wxi2+byi)xi2=i=1100(wxi2+byi)利用梯度下降法学习参数,学习率为 l r lr lr,那么代码实现为

lr = 0.01

for i in range(800):
    y_pred = np.power(x, 2) * w + b
    w_grad = np.sum((y_pred - y) * np.power(x, 2))
    b_grad = np.sum((y_pred - y))
    w -= lr * w_grad
    b -= lr * b_grad
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

使用代码可视化

plt.plot(x, y_pred, 'r-', label='predict')
plt.scatter(x, y, color='blue', marker='o', label='true')
plt.show()
  • 1
  • 2
  • 3

效果为
matplot
而学习得到的参数 w = 2.9913 , b = 2.0974 w = 2.9913, b = 2.0974 w=2.9913,b=2.0974。从结果上看,学习效果尚可。

2.3.2 PyTorch实现机器学习
  使用NumPy手工的构建完成机器学习适用于比较简单的情况,但如果复杂一些,代码量将几何级增加。PyTorch可以简洁的完成该任务。
  首先导入库:

import torch
import matplotlib.pyplot as plt
  • 1
  • 2

并生成训练数据,生成与上述一致的数据并可视化:

torch.manual_seed(100)
x = torch.unsqueeze(torch.linspace(-1, 1, 100), 1)
y = 3 * x.pow(2) + 2 + 0.2 * torch.rand(x.size())

plt.scatter(x.numpy(), y.numpy())
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

matplot
再初始化权重:

w = torch.randn(1, 1, dtype=torch.float, requires_grad=True)
b = torch.zeros(1, 1, dtype=torch.float, requires_grad=True)
  • 1
  • 2

定义损失并训练模型:

lr = 0.001

for i in range(800):
    y_pred = x.pow(2).mm(w) + b
    loss = 0.5 * (y_pred - y) ** 2
    loss = loss.sum()
    loss.backward()

    with torch.no_grad():
        w -= lr * w.grad
        b -= lr * b.grad

        w.grad.zero_()
        b.grad.zero_()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

可视化训练结果:

plt.plot(x.numpy(), y_pred.detach().numpy(), 'r-', label='predict')
plt.scatter(x.numpy(), y.numpy(), color='blue', marker='o', label='True')
plt.legend()
plt.show()
  • 1
  • 2
  • 3
  • 4

matplot
而学习到的参数 w = 2.9645 , b = 2.1146 w=2.9645, b=2.1146 w=2.9645,b=2.1146,该结果与NumPy学习的差不多。


三、PyTorch核心组件
  PyTorch的数据结构与自动求导机制,可以大大的提高开发效率。而PyTorch的核心组件,则可以极大的简化构建神经网络模型的任务。

3.1 简单神经网络实例
  神经网络看起来很复杂,但核心部分并不多,把这些组件确定后,神经网络基本就确定了。考虑使用神经网络完成手写数字进行识别的实例,来说明借助nn实现一个神经网络,并对神经网络有一个直观的了解。

3.1.1 数据处理
  首先导入必要的模块:

import torch
from torchvision.datasets import mnist
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim
from torch import nn
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

并定义超参数:

train_batch_size = 64
test_batch_size = 128
learning_rate = 0.01
num_epoches = 20
lr = 0.01
momentum = 0.5
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
'
运行

再下载数据并对数据进行预处理:

transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize([0.5], [0.5])])
train_dataset = mnist.MNIST('./data', train=True, transform=transform, download=True)
test_dataset = mnist.MNIST('./data', train=False, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=test_batch_size, shuffle=False)
  • 1
  • 2
  • 3
  • 4
  • 5

其中torchvision.transforms.Compose()的参数为transforms,其接受转换函数的列表,形成单个转换函数。最后进行可视化,完成数据的准备:

import matplotlib.pyplot as plt

examples = enumerate(test_loader)
batch_idx, (example_data, example_targets) = next(examples)
fig = plt.figure()
for i in range(6):
    plt.subplot(2, 3, i+1)
    plt.tight_layout()
    plt.imshow(example_data[i][0], cmap='gray', interpolation='none')
    plt.title("Ground Truth: %d" % (example_targets[i]))
    plt.xticks([])
    plt.yticks([])
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

效果如下:
MINIST
3.1.2 网络构建
  数据预处理完成后,进行网络的搭建,创建网络的模型:

class Net(nn.Module):
	
	def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
		super(Net, self).__init__()
		self.layer1 = nn.Sequential(nn.Linear(in_dim, n_hidden_1), nn.BatchNorm1d(n_hidden_1))
		self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2), nn.BatchNorm1d(n_hidden_2))
		self.layer3 = nn.Sequential(nn.Linear(n_hidden_2, out_dim))

	def forward(self, x):
		x = F.relu(self.layer1(x))
		x = F.relu(self.layer2(x))
		x = self.layer3(x)
		return x
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

其中,torch.nn.Sequential()的参数为*args,其接受一系列层。在建立网络的类后,进行实例化:

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = Net(28 * 28, 300, 100, 10)
model.to(device)
  • 1
  • 2
  • 3

并定义损失函数与优化器

loss = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)
  • 1
  • 2

3.1.3 网络训练
  训练模型可以简单的使用for循环迭代:

losses = []
acces = []
eval_losses = []
eval_acces = []

for epoch in range(num_epoches):

    # train model
    train_loss = 0
    train_acc = 0
    model.train()

    # train
    for img, label in train_loader:
        img = img.to(device)
        label = label.to(device)
        img = img.view(img.size(0), -1)

        # forward
        out = model(img)
        loss = loss_func(out, label)

        # backward
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # record loss
        train_loss += loss.item()

        # compute accuracy
        _, pred = out.max(1)
        num_correct = (pred == label).sum().item()
        acc = num_correct / img.shape[0]
        train_acc += acc

    losses.append(train_loss / len(train_loader))
    acces.append(train_acc / len(train_loader))

    eval_loss = 0
    eval_acc = 0

    # evaluate model
    model.eval()

    # evaluate
    for img, label in test_loader:
        img = img.to(device)
        label = label.to(device)
        img = img.view(img.size(0), -1)
        out = model(img)
        loss = loss_func(out, label)

        # record loss
        eval_loss += loss.item()

        # compute accuracy
        _, pred = out.max(1)
        num_correct = (pred == label).sum().item()
        acc = num_correct / img.shape[0]
        eval_acc += acc

    eval_losses.append(eval_loss / len(test_loader))
    eval_acces.append(eval_acc / len(test_loader))

    print("epoch: %d, Train Loss: %.4f, Train Acc: %.4f, Test Loss: %.4f, Test Acc: %.4f" %
            (epoch, losses[-1], acces[-1], eval_losses[-1], eval_acces[-1]))
  • 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

其部分迭代结果如下:

epoch: 15, Train Loss: 0.0106, Train Acc: 0.9978, Test Loss: 0.0640, Test Acc: 0.9799
epoch: 16, Train Loss: 0.0087, Train Acc: 0.9984, Test Loss: 0.0594, Test Acc: 0.9812
epoch: 17, Train Loss: 0.0077, Train Acc: 0.9987, Test Loss: 0.0562, Test Acc: 0.9829
epoch: 18, Train Loss: 0.0074, Train Acc: 0.9987, Test Loss: 0.0591, Test Acc: 0.9818
epoch: 19, Train Loss: 0.0072, Train Acc: 0.9987, Test Loss: 0.0610, Test Acc: 0.9811
  • 1
  • 2
  • 3
  • 4
  • 5

该神经网络的架构较为简单,在迭代20次后得到了98%的测试准确率,效果尚可。而且,可以通过plt进行损失的可视化,这里不再详细介绍。

3.2 torch.nn
  在上一节,使用nn构建了一个神经网络。通过直接引用nn预定义的网络层,就可以组织并构建网络。

3.2.1 构建网络
  torch.nn.Sequential()如同搭积木一般构建网络层,但每一层的编码缺省而使用默认的数字,不易区分。为了给每层定义一个名称,可以通过字典的形式添加每一层,并设置单独的层的名称:

class Net(torch.nn.Module):
	
	def __init__(self):
		super(Net4, self).__init()__
		self.conv = torch.nn.Sequentical(
			OrderedDict(
				[
					("conv1", torch.nn.Conv2d(3, 32, 3, 1, 1)),
					("relu1", torch.nn.ReLU()),
					("pool", torch.nn.MaxPool2d(2))
				]
			))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.2.2 前向传播
  在定义好网络层后,需要forward函数将网络层连接起来,实现数据的前向传播。该函数的参数一般为输入数据,并返回输出数据。
  在forward()函数中,如果使用nn.Module的函数需要实例化,而使用nn.functional的函数可以直接使用。

3.2.3 反向传播
  在前向传播函数定义好后,需要定义梯度的反向传播,其原理为复合函数的链导法则。PyTorch提供了自动反向传播的功能,只需要对损失函数得到的loss调用backward()函数即可。
  在反向传播的过程中,优化器是关键的一步。优化方法很多,如SGD等,其各有优缺点。

3.2.4 模型训练
  当网络构建完成,传播参数设定好,就可以开始训练模型。
  训练模型时,需要调用model.train(),其会把所有model设置为训练模式,而当进行推测时需要调用model.eval()将model的训练属性置为False。
  缺省的情况下,训练时的梯度将会累加,需要人工将梯度初始化,需要调用optimizer.zero_grad()。在训练过程中,首先计算正向传播得到的输出与实际值之间的损失loss,调用loss.backward()自动生成梯度,再使用optimizer.step()执行优化,把梯度传播回整个网络。
  如果希望使用GPU训练,则需要调用model.to(device)将相关数据发送到device上。

3.2.5 nn.Module
  nn是专门为深度学习设计的一个模块,而nn.Module是nn的核心数据结构,其可以神经网络的某个层,也可以是包含多个层的神经网络。最常用的用法是继承nn.Module,从而生成网络或层。nn中已实现了绝大多数层,均是nn.Module的子类。

3.2.6 nn.functional
  nn的层一部分继承自nn.Module,其命名类似于nn.Linear、nn.Conv2d、nn.CrossEntropyLoss等,而另一类是nn.funcitonal中的函数,其命名类似于nn.functional.linear、nn.functional.conv2d、nn.functional.cross_entropy等。从功能上讲两者相当,性能上也没有太大差异,但是在具体使用中还是有如下区别:
  -继承自nn.Module的层需要实例化并传入参数,且可以与nn.Sequential结合使用,但nn.functional的层不可以;
  -继承自nn.Module的层不需要定义和管理权重、偏置等参数,而nn.functional的层需要;
  -若网络存在dropout,继承自nn.Module的层在训练与推测时会自动转换状态,而nn.functional不能;
PyTorch标准推荐具有学习参数的层继承nn.Module,而无学习参数的则根据情况选择。

3.3 优化器
  PyTorch常用的优化方法都封装在torch.optim中,且设计灵活,可以扩展为自定义的优化方法。所有的优化方法都继承自基类optim.Optimizer。
  以SGD优化器为例,使用优化器首先需要导入optim模块并实例化一个优化器:

import torch.optim as optim
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)
  • 1
  • 2

并在训练的迭代中得到前向传播的结果,计算损失函数:

out = model(img)
loss = loss_func(out, label)
  • 1
  • 2

由于在缺省的情况下,梯度会累加,在梯度反向传播之前,需要将梯度初始化:

optimizer.zero_grad()
  • 1

并基于损失将梯度反向传播

loss.backward()
  • 1

反向传播会将当前梯度存储在参数的.grad属性中,进行参数的更新:

optimizer.step()
  • 1

四、PyTorch数据处理
  PyTorch提供了Torch.utils.data用于数据的处理,其包括三个类:
  -Dataset,其他数据集需要继承这个类;
  -DataLoader,定义了一个迭代器,其批量读取、打乱数据,并提供并行加速功能;
  -random_split,其将数据集随即拆分为给定长度的非重叠新数据集。
此外,PyTorch还有一个可视化处理工具torchvision,其独立于PyTorch,包含几个主要功能:
  -datasets,提供常用的数据集加载,设计上继承自torch.utils.data.Dataset,如MNIST,CIFAR10等;
  -models,提供深度学习中经典的网络架构及其预训练模型,如AlexNet,VGG等;
  -transforms,常用的数据预处理操作,包括对Tensor与PIL的Image对象的操作;
  -utils,对数据的处理与存储。

4.1 torch.utils.data
  torch.util.data包括Dataset与DataLoader。其中,torch.utils.data.Dataset是一个抽象类,自定义数据集需要继承这个类,并实现两个函数:__len__,其需要提供数据的大小;__getitem__,其通过给定索引获取数据和标签。__getitem__一次仅获取一个数据,因此需要通过torch.utils.data.DataLoader来定义一个新的迭代器,实现批的读取。接下来定义一个简单的数据集。首先导入模块

import torch
from torch.utils import data
import numpy as np
  • 1
  • 2
  • 3

然后定义一个获取数据集的类,其继承自积累Dataset,并自定义其数据与标签。

class CusDataset(data.Dataset):
    def __init__(self):
        self.Data = np.asarray([[1, 2], [3, 4], [2, 1], [3, 4], [4, 5]])
        self.Label = np.asarray([0, 1, 0, 1, 2])

    def __getitem__(self, index):
        txt = torch.from_numpy(self.Data[index])
        label = torch.tensor(self.Label[index])
        return txt, label

    def __len__(self):
        return len(self.Data)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

然而,Dataset只负责数据的抽取,一次调用__getitem__只返回一个样本。如果希望批处理,则需要DataLoader类,其格式为

data.DataLoader(
	dataset,
	batch_size=1,
	shuffle=False,
	sampler=None,
	batch_sampler=None,
	num_workers=0,
	collate_fn=,
	pin_memory=False,
	drop_last=False,
	timeout=0
	workder_init_fn=None
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

将上述继承自Dataset类的数据集转换为DataLoader对象,批尺寸为2:

data_loader = data.DataLoader(cus_dataset, batch_size=2, shuffle=False)
for i, train_data in enumerate(data_loader):
    print('i:', i)
    cus_data, cus_label = train_data
    print('data:', cus_data)
    print('label:', cus_label)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

其运行结果是一个批量读取,可以像使用迭代器一样使用,并进行循环操作。

i:  0 data:  tensor([[1, 2], [3, 4]]) label:  tensor([0, 1])
i:  1 data:  tensor([[2, 1], [3, 4]]) label:  tensor([0, 1])
i:  2 data:  tensor([[4, 5]]) label:  tensor([2])
  • 1
  • 2
  • 3

不过,DataLoader对象并不是一个迭代器,可以通过iter函数转换:

data_iter = iter(data_loader)
imgs, labels = next(data_iter)
  • 1
  • 2

4.2 torchvision
  torchvision有几个功能模块,其中datasets的ImageFolder用于处理自定义数据集,而transform对源数据进行预处理。

4.2.1 torchvision.transforms
  torchvision.transforms提供了对PIL.Image对象和torch.Tensor对象的常用操作,对PIL.Image较为重要的是ToTenser(),其将取值范围是 [ 0 , 255 ] [0, 255] [0,255]的、形状为(H, W, C)的PIL.Image转换为取值范围为 [ 0 , 1.0 ] [0, 1.0] [0,1.0]的、形状为(C, H, W)的torch.FloatTensor;对Tensor的操作则有Normalize()与ToPILImage()。
  当需要对数据集进行多个操作时,可以使用Compose将这些操作连接起来,类似于torch.nn.Sequential()。

4.2.2 torchvision.datasets.ImageFolder
  当文件依据标签处于不同文件下时,可以利用torchvision.datasets.ImageFolder直接构造出dataset,形如:

dataset = datasets.ImageFolder(path)
loader = data.DataLoader(dataset)
  • 1
  • 2

torchvision.datasets.ImageFolder会根据目录中的文件名自动转化成序列。

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

闽ICP备14008679号