PyTorch 为我们提供了一种十分方便的数据读取机制,即使用 Dataset 类与 DataLoader 类的组合,来得到数据迭代器。在训练或预测时,数据迭代器能够输出每一批次所需的数据,并且对数据进行相应的预处理与数据增强操作。
下面我们分别来看下 Dataset 类与 DataLoader 类。
PyTorch 中的 Dataset 类是一个抽象类,它可以用来表示数据集。我们通过继承 Dataset 类来自定义数据集的格式、大小和其它属性,后面就可以供 DataLoader 类直接使用。
其实这就表示,无论使用自定义的数据集,还是官方为我们封装好的数据集,其本质都是继承了 Dataset 类。而在继承 Dataset 类时,至少需要重写以下几个方法:
光看原理不容易理解,下面我们来编写一个简单的例子,看下如何使用 Dataset 类定义一个 Tensor 类型的数据集。
import torch
from torch.utils.data import Dataset
class MyDataset(Dataset):
# 构造函数
def __init__(self, data_tensor, target_tensor):
self.data_tensor = data_tensor
self.target_tensor = target_tensor
# 返回数据集大小
def __len__(self):
return self.data_tensor.size(0)
# 返回索引的数据与标签
def __getitem__(self, index):
return self.data_tensor[index], self.target_tensor[index]
结合代码可以看到,我们定义了一个名字为 MyDataset 的数据集,在构造函数中,传入 Tensor 类型的数据与标签;在 __ len __ 函数中,直接返回 Tensor 的大小;在 __ getitem __ 函数中返回索引的数据与标签。
下面,我们来看一下如何调用刚才定义的数据集。首先随机生成一个 10*3 维的数据 Tensor,然后生成 10 维的标签 Tensor,与数据 Tensor 相对应。利用这两个 Tensor,生成一个 MyDataset 的对象。查看数据集的大小可以直接用 len() 函数,索引调用数据可以直接使用下标。
# 生成数据
data_tensor = torch.randn(10, 3)
target_tensor = torch.randint(2, (10,)) # 标签是0或1
# 将数据封装成Dataset
my_dataset = MyDataset(data_tensor, target_tensor)
# 查看数据集大小
print('Dataset size:', len(my_dataset))
'''
输出:
Dataset size: 10
'''
# 使用索引调用数据
print('tensor_data[0]: ', my_dataset[0])
'''
输出:
tensor_data[0]: (tensor([ 0.4931, -0.0697, 0.4171]), tensor(0))
'''
在实际项目中,如果数据量很大,考虑到内存有限、I/O 速度等问题,在训练过程中不可能一次性的将所有数据全部加载到内存中,也不能只用一个进程去加载,所以就需要多进程、迭代加载,而 DataLoader 就是基于这些需要被设计出来的。
DataLoader 是一个迭代器,最基本的使用方法就是传入一个 Dataset 对象,它会根据参数 batch_size 的值生成一个 batch 的数据,节省内存的同时,它还可以实现多进程、数据打乱等处理。
DataLoader 类的调用方式如下:
from torch.utils.data import DataLoader
tensor_dataloader = DataLoader(dataset=my_dataset, # 传入的数据集, 必须参数
batch_size=2, # 输出的batch大小
shuffle=True, # 数据是否打乱
num_workers=0) # 进程数, 0表示只有主进程
# 以循环形式输出
for data, target in tensor_dataloader:
print(data, target)
'''
输出:
tensor([[-0.1781, -1.1019, -0.1507],
[-0.6170, 0.2366, 0.1006]]) tensor([0, 0])
tensor([[ 0.9451, -0.4923, -1.8178],
[-0.4046, -0.5436, -1.7911]]) tensor([0, 0])
tensor([[-0.4561, -1.2480, -0.3051],
[-0.9738, 0.9465, 0.4812]]) tensor([1, 0])
tensor([[ 0.0260, 1.5276, 0.1687],
[ 1.3692, -0.0170, -1.6831]]) tensor([1, 0])
tensor([[ 0.0515, -0.8892, -0.1699],
[ 0.4931, -0.0697, 0.4171]]) tensor([1, 0])
'''
# 输出一个batch
print('One batch tensor data: ', iter(tensor_dataloader).next())
'''
输出:
One batch tensor data: [tensor([[ 0.9451, -0.4923, -1.8178],
[-0.4046, -0.5436, -1.7911]]), tensor([0, 0])]
'''
结合代码,我们梳理一下 DataLoader 中的几个参数,它们分别表示:
原文链接: 十分钟掌握Pytorch搭建神经网络的流程.
如果你想做一个网络,需要先定义一个Class,继承 nn.Module(这个是必须的,所以先import torch.nn as nn,nn是一个工具箱,很好用),我们把class的名字就叫成Net.
Class Net(nn.Module):
这个Class里面主要写两个函数,一个是初始化的__init__函数,另一个是forward函数。我们随便搭一个,如下:
def __init__(self):
super().__init__()
self.conv1=nn.Conv2d(1,6,5)
self.conv2=nn.Conv2d(6,16,5)
def forward(self, x):
x=F.max_pool2d(F.relu(self.conv1(x)),2)
x=F.max_pool2d(F.relu(self.conv2(x)),2)
return x
net=Net()
这样我们就可以往里传x了,假设你已经有一个要往神经网络的输入的数据“input"(这个input应该定义成tensor类型,怎么定义tensor那就自己去看看书了。另外pytorch三点几版本的你还得把它弄成Variable类型,4.0以后的就不需要了) 在传入的时候,是:
output=net(input)
看之前的定义:
def __init__(self):
……
def forward(self, x):
……
有点奇怪。好像常规python一般向class里面传入一个数据x,在class的定义里面,应该是把这个x作为形参传入__init__函数里的,而在上面的定义中,x作为形参是传入forward函数里面的。 其实也不矛盾,因为你定义net的时候,是net=Net(),并没有往里面传入参数。如果你想初始化的时候按需传入,就把需要的传入进去。只是x是神经网络的输入,但是并非是初始化需要的,初始化一个网络,必须要有输入数据吗?未必吧。只是在传入网络的时候,会自动认为你这个x是喂给forward里面的。
在网络定义好以后,就涉及到传入参数,算误差,反向传播,更新权重…确实很容易记不住这些东西的格式和顺序。 传入的方式上面已经介绍了,相当于一次正向传播,把一路上各层的输入x都算出来了。 想让神经网络输出的output跟你期望的ground truth差不多,那就是不断减小二者间的差异,这个差异是你自己定义的,也就是目标函数(object function)或者就是损失函数(loss function)。如果损失函数loss趋近于0,那么自然就达到目的了。损失函数loss基本上没法达到0,但是希望能让它达到最小值,那么就是希望它能按照梯度进行下降。梯度下降的公式,大家应该都很熟悉,不熟悉的话,建议去看一下相关的理论。 只是你的输入是由你来决定的,那神经网络能学习和决定什么呢?自然它只能决定每一层卷积层的权重。所以神经网络只能不停修改权重,比如y=wx+b,x是你给的,它只能改变w,b让最后的输出y尽可能接近你希望的y值,这样损失loss就越来越小。如果loss对于卷积层里面的参数W的偏导数接近0了,不就意味着到达了一个极小值吗?而在你的loss计算方式已经给定的情况下,loss对于w的偏导数的减小,其实只能通过更新参数卷积层参数W来实现(别的它决定不了啊,都是你输入和提供的)。 所以,通过下述方式实现对W的更新:(注意这些编号,下面还要提)【1】 先算loss对于输入x的偏导,(当然网络好几层,这个x指的是每一层的输入,而不是最开始的输入input);【2】 对【1】的结果再乘以一个步长(这样就相当于是得到一个对参数W的修改量);【3】 用W减掉这个修改量,完成一次对参数W的修改。 说的不太严谨,但是大致意思是这样的。这个过程你可以手动实现,但是大规模神经网络怎么手动实现?那是不可能的事情。所以我们要利用框架pytorch和工具箱torch.nn。 所以要定义损失函数,以MSEloss为例
compute_loss=nn.MSELoss()
明显它也是个类,不能直接传入输入数据,所以直接loss=nn.MSEloss(target,output)是不对的。需要把这个函数赋一个实例,叫成compute_loss。之后就可以把你的神经网络的输出,和标准答案target传入进去:
loss=compute_loss(target,output)
算出loss,下一步就是反向传播:
loss.backward()
这一步其实就是把【1】给算完了,得到对参数W一步的更新量,算是一次反向传播。这里就注意了,loss.backward()是啥玩意?如果是自己的定义的loss(比如你就自己定义了个def loss(x,y):return y-x )这样肯定直接backward会出错。所以应当用nn里面提供的函数。当然搞深度学习不可能只用官方提供的loss函数,所以如果你要想用自己的loss函数,必须也把loss定义成上面Net的样子(不然你的loss不能反向传播,这点要注意 ,注:这点是以前写的,很久之前的版本不行,现在都可以了,基本不太需要这样了),也是继承nn.Module,把传入的参数放进forward里面,具体的loss在forward里面算,最后return loss。__init()__就空着,写个super().__init__就行了。 在反向传播之后,第【2】和第【3】怎么实现?就是通过优化器来实现。让优化器来自动实现对网络权重W的更新。所以在Net定义完以后,需要写一个优化器的定义(选SGD方式为例):
from torch import optim
optimizer=optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
同样,优化器也是一个类,先定义一个实例optimizer,然后之后会用。注意在optimizer定义的时候,需要给SGD传入了net的参数parameters,这样之后优化器就掌握了对网络参数的控制权,就能够对它进行修改了。传入的时候把学习率lr也传入了。 在每次迭代之前,先把optimizer里存的梯度清零一下(因为W已经更新过的“更新量”下一次就不需要用了)
optimizer.zero_grad()
在loss.backward()反向传播以后,更新参数:
optimizer.step()
所以我们的顺序是:
卷积层是通过特定数目的卷积核(又称滤波器)对输入的多通道(channel)特征图进行扫描和运算,从而得到多个拥有更高层语义信息的输出特征图(通道数目等于卷积核的个数)。
class torch.nn.Conv1d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
一维卷积层,输入的尺度是 (N,C_in,L_in),输出尺度( N,C_out,L_out)
3.1.1. 参数说明:
3.1.2. example:
m = nn.Conv1d(16, 33, 3, stride=2)
input = autograd.Variable(torch.randn(20, 16, 50))
output = m(input)
class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
二维卷积层, 输入的尺度是(N,C_in,H_in,W_in),输出尺度是(N,C_out,H_out,W_out)
3.2.2. example:
# With square kernels and equal stride
m = nn.Conv2d(16, 33, 3, stride=2)
# non-square kernels and unequal stride and with padding
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2))
# non-square kernels and unequal stride and with padding and dilation
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1))
input = autograd.Variable(torch.randn(20, 16, 50, 100))
output = m(input)
class torch.nn.Conv3d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
三维卷积层, 输入的尺度是(N, C_in,D,H,W),输出尺度是(N, C_out, D_out, H_out,W_out)
3.3.1. 参数说明:
3.2. example:
# With square kernels and equal stride
m = nn.Conv3d(16, 33, 3, stride=2)
# non-square kernels and unequal stride and with padding
m = nn.Conv3d(16, 33, (3, 5, 2), stride=(2, 1, 1), padding=(4, 2, 0))
input = autograd.Variable(torch.randn(20, 16, 10, 50, 100))
output = m(input)
在卷积神经网络中通常会在相邻的卷积层之间加入一个池化层,池化层可以有效的缩小参数矩阵的尺寸,从而减少最后连接层的中的参数数量。所以加入池化层可以加快计算速度和防止过拟合的作用。
class torch.nn.MaxPool1d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
如果输入的大小是 (N,C_in,L_in),那么输出的大小是 (N,C_out,L_out)
4.1.1. 参数说明:
4.1.2. example:
# pool of size=3, stride=2
m = nn.MaxPool1d(3, stride=2)
input = autograd.Variable(torch.randn(20, 16, 50))
output = m(input)
class torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
如果输入的大小是 (N,C, H i n H_{in} Hin, W i n W_{in} Win),那么输出的大小是 (N,C, H o u t H_{out} Hout, W o u t W_{out} Wout)
4.2.1. 参数说明:
4.2.2. example:
# pool of square window of size=3, stride=2
m = nn.MaxPool2d(3, stride=2)
# pool of non-square window
m = nn.MaxPool2d((3, 2), stride=(2, 1))
input = autograd.Variable(torch.randn(20, 16, 50, 32))
output = m(input)
class torch.nn.MaxPool3d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
如果输入的大小是 (N,C,D,H,W),那么输出的大小是 (N,C,D , H o u t ,H_{out} ,Hout, W o u t W_{out} Wout)
4.3.1. 参数说明:
4.3.2. example:
# pool of square window of size=3, stride=2
m = nn.MaxPool3d(3, stride=2)
# pool of non-square window
m = nn.MaxPool3d((3, 2, 2), stride=(2, 1, 2))
input = autograd.Variable(torch.randn(20, 16, 50, 32))
output = m(input)
class torch.nn.ReLU(inplace=False)
5.1.1 参数:
5.1.2 example:
m = nn.ReLU()
input = autograd.Variable(torch.randn(2))
print(input)
print(m(input))
class torch.nn.ELU(alpha=1.0, inplace=False)
5.2.1 参数:
5.2.2 example:
m = nn.ELU()
input = autograd.Variable(torch.randn(2))
print(input)
print(m(input))
class torch.nn.LeakyReLU(negative_slope=0.01, inplace=False)
5.3.1 参数:
5.3.2 example:
m = nn.LeakyReLU(0.1)
input = autograd.Variable(torch.randn(2))
print(input)
print(m(input))
对n维输入张量运用Softmax函数,将张量的每个元素缩放到(0,1)区间且和为1。
class torch.nn.Softmax()
5.4.1 example:
m = nn.Softmax()
input = autograd.Variable(torch.randn(2))
print(input)
print(m(input))
对n维输入张量运用Softmax函数,将张量的每个元素缩放到(0,1)区间且和为1。
class torch.nn.Sigmoid()
5.5.1 example:
m = nn.Sigmoid()
input = autograd.Variable(torch.randn(2))
print(input)
print(m(input))
对n维输入张量运用Softmax函数,将张量的每个元素缩放到(0,1)区间且和为1。
class torch.nn.Tanh()
5.6.1 example:
m = nn.Tanh()
input = autograd.Variable(torch.randn(2))
print(input)
print(m(input))
对小批量(mini-batch)的2d或3d输入进行批标准化(Batch Normalization)操作
class torch.nn.BatchNorm1d(num_features, eps=1e-05, momentum=0.1, affine=True)
y = x − m e a n [ x ] V a r [ x ] + ϵ ∗ g a m m a + b e t a y = \frac{x - mean[x]}{ \sqrt{Var[x]} + \epsilon} * gamma + beta y=Var[x]+ϵx−mean[x]∗gamma+beta
在每一个小批量(mini-batch)数据中,计算输入各个维度的均值和标准差。gamma与beta是可学习的大小为C的参数向量(C为输入大小)。
在训练时,该层计算每次输入的均值与方差,并进行移动平均。移动平均默认的动量值为0.1。
在验证时,训练求得的均值/方差将用于标准化验证数据。
6.1.1 参数:
Shape: - 输入:(N, C)或者(N, C, L) - 输出:(N, C)或者(N,C,L)(输入输出相同)
6.1.2 example:
# With Learnable Parameters
m = nn.BatchNorm1d(100)
# Without Learnable Parameters
m = nn.BatchNorm1d(100, affine=False)
input = autograd.Variable(torch.randn(20, 100))
output = m(input)
对小批量(mini-batch)3d数据组成的4d输入进行批标准化(Batch Normalization)操作
class torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True)
y = x − m e a n [ x ] V a r [ x ] + ϵ ∗ g a m m a + b e t a y = \frac{x - mean[x]}{ \sqrt{Var[x]} + \epsilon} * gamma + beta y=Var[x]+ϵx−mean[x]∗gamma+beta
在每一个小批量(mini-batch)数据中,计算输入各个维度的均值和标准差。gamma与beta是可学习的大小为C的参数向量(C为输入大小)。
在训练时,该层计算每次输入的均值与方差,并进行移动平均。移动平均默认的动量值为0.1。
在验证时,训练求得的均值/方差将用于标准化验证数据。
6.2.1 参数:
Shape: - 输入:(N, C,H, W) - 输出:(N, C, H, W)(输入输出相同)
6.2.2 example:
# With Learnable Parameters
m = nn.BatchNorm2d(100)
# Without Learnable Parameters
m = nn.BatchNorm2d(100, affine=False)
input = autograd.Variable(torch.randn(20, 100, 35, 45))
output = m(input)
对小批量(mini-batch)4d数据组成的5d输入进行批标准化(Batch Normalization)操作
class torch.nn.BatchNorm3d(num_features, eps=1e-05, momentum=0.1, affine=True)
y = x − m e a n [ x ] V a r [ x ] + ϵ ∗ g a m m a + b e t a y = \frac{x - mean[x]}{ \sqrt{Var[x]} + \epsilon} * gamma + beta y=Var[x]+ϵx−mean[x]∗gamma+beta
在每一个小批量(mini-batch)数据中,计算输入各个维度的均值和标准差。gamma与beta是可学习的大小为C的参数向量(C为输入大小)。
在训练时,该层计算每次输入的均值与方差,并进行移动平均。移动平均默认的动量值为0.1。
在验证时,训练求得的均值/方差将用于标准化验证数据。
6.3.1 参数:
Shape: - 输入:(N, C,D, H, W) - 输出:(N, C, D, H, W)(输入输出相同)
6.3.2 example:
# With Learnable Parameters
m = nn.BatchNorm3d(100)
# Without Learnable Parameters
m = nn.BatchNorm3d(100, affine=False)
input = autograd.Variable(torch.randn(20, 100, 35, 45, 10))
output = m(input)
class torch.nn.Linear(in_features, out_features, bias=True)
对输入数据做线性变换:(y = Ax + b)
7.1.参数:
7.2.shape
7.3.example
m = nn.Linear(20, 30)
input = autograd.Variable(torch.randn(128, 20))
output = m(input)
print(output.size())
class torch.nn.Dropout(p=0.5, inplace=False)
随机将输入张量中部分元素设置为0。对于每次前向调用,被置0的元素都是随机的。
8.1.参数:
8.2.shape
8.3.example
m = nn.Dropout(p=0.2)
input = autograd.Variable(torch.randn(20, 16))
output = m(input)
基本用法:
criterion = LossCriterion() #构造函数有自己的参数
loss = criterion(x, y) #调用标准时也有参数
原型:
class torch.nn.L1Loss(size_average=True)
创建一个衡量输入x(模型预测输出)和目标y之间差的绝对值的平均值的标准。计算公式:
l o s s ( x , y ) = 1 / n ∑ ∣ x i − y i ∣ loss(x,y)=1/n\sum|x_i-y_i| loss(x,y)=1/n∑∣xi−yi∣
参数:
原型:
class torch.nn.MSELoss(size_average=True)
创建一个衡量输入x(模型预测输出)和目标y之间差均方误差标准。计算公式:
l o s s ( x , y ) = 1 / n ∑ ( x i − y i ) 2 loss(x,y)=1/n\sum(x_i-y_i)^2 loss(x,y)=1/n∑(xi−yi)2
参数:
原型:
class torch.nn.CrossEntropyLoss(weight=None, size_average=True)
此标准将 LogSoftMax 和 NLLLoss 集成到一个类中。
当训练一个多类分类器的时候,这个方法是十分有用的。
l o s s ( x , y ) = 1 / n ∑ ( x i − y i ) 2 loss(x,y)=1/n\sum(x_i-y_i)^2 loss(x,y)=1/n∑(xi−yi)2
调用时参数:
Loss可以表述为以下形式:
当weight参数被指定的时候,loss的计算公式变为:
l o s s ( x , c l a s s ) = w e i g h t s [ c l a s s ] ∗ ( − x [ c l a s s ] + l o g ( ∑ j e x p ( x [ j ] ) ) ) loss(x, class) = weights[class] * (-x[class] + log(\sum_j exp(x[j]))) loss(x,class)=weights[class]∗(−x[class]+log(j∑exp(x[j])))
计算出的loss对mini-batch的大小取了平均。
shape:
pytorch手册Loss functions链接.