Pytorch快速教程

文章目录

    • 张量
      • 1. 创建
      • 2. 计算
      • 3. 使用gpu计算
      • 4. 其他常用方法
        • torch.Tensor.expand(*size):
        • torch.Tensor.repeat(*size)
        • torch.Tensor.gather(dim, index):
    • 自动求导
    • 神经网络
    • 定义网络
      • 容器
      • nn.Module和nn.functional的区别
      • 定义网络和训练
        • 以下是一个预测图片分类的完整例子
    • 数据导入与预处理
    • 优化

张量

Tensor是pytorch中最基本的数据类型, 相当于numpy中的ndarray, 是具有追踪运算, 自动求导功能的多维数组.

1. 创建

x = torch.rand(5, 3) # 随机二维数组
x = torch.empty(5, 3) # 创建二维数组但不进行初始化
x = torch.zeros(5, 3) # 创建全零的二维数组
x = torch.tensor([1, 2, 3]) # 从list创建tensor
x = torch.from_numpy(np.array([1, 2, 3])) # 使用ndarray创建张量
# 从已有张量创建新张量, 新张量复用原有张量的未指定参数
x = x.new_ones(5, 3, dtype=torch.long) 
x = torch.randn_like(x, dtype = torch.double) # reuse the size and override the type

2. 计算

张量加法:

x + y
torch.add(x, y)
y.add_(x) # 所有带_后缀的方法都是在原地修改张量

切片: 语法和numpy一致
修改shape: 使用x.view(dim1, dim2, ..) 使用-1表示按照其他维度自适应
矩阵乘: 使用torch.mm(tensor1, tensor2)计算矩阵乘
矩阵转置: 二维矩阵的转置torch.Tensor.t()
高维矩阵的转置: torch.Tensor.transpose(dim1, dim2, dim3, ...), dim是原矩阵的维度index
元素点乘: 使用troch.mul(tensor1, tensor2)计算元素范围的乘法
numpy桥梁: 对张量使用x.numpy()获得张量对应的np.ndarray, 二者共享底层的数据, 即修改该array能够直接修改tensor的数据
获取值: 对于只含有一个元素的tensor, 可以使用x.item()获得数值, 对于含有多个元素的tensor, 使用x. tolist().

3. 使用gpu计算

在使用GPU计算前, 需要将训练集, 标签, 模型都转入到GPU中, 对于tensor, 使用x.to(device)转移到对应的设备中.

4. 其他常用方法

torch.Tensor.expand(*size):

expand函数对tensor按照size进行扩展, 这种扩展不会增加内存, 也就是说, 他们是指向的同一对象, 修改其中任意一个会修改全部. size与原tensor.size()的关系, size能够在原tensor的size为1的维扩展, 其他维保持不变, 也可以使用-1代替其他维的shape, 但是不能够不一致. 可以直接扩展为更高的维, 更高的维一定在现有维之前.

*size是解序列, 这里例如传入一个list, *list将list解析为它的所有元素

In [42]: a = torch.tensor([1, 2, 3, 4])
In [43]: a.unsqueeze(1).expand(4, 2) # (4,1)->(4,2)
Out[43]: 
tensor([[1, 1],
        [2, 2],
        [3, 3],
        [4, 4]])
In [45]: a.unsqueeze(1).expand(-1, 2) #(4,1) - (-1, 2) -> (4, 2)
Out[45]: 
tensor([[1, 1],
        [2, 2],
        [3, 3],
        [4, 4]])
a.unsqueeze(1).expand(5, -1, 2) #(4,1) -> (5, 4, 2) 
a.unsqueeze(1).expand(-1, 2, 5) # 报错, 高维在前
a.unsqueeze(1).expand(3, 2) # 报错, size不为1的dim的size, 4和3不能够对应

torch.Tensor.repeat(*size)

和expand的功能类似, 但是是对原tensor的复制, 而不是引用. 参数是每个维度的重复次数.

In [5]: a = torch.tensor([1, 2, 3, 4])
In [6]: a.repeat(2, 2)
Out[6]: 
tensor([[1, 2, 3, 4, 1, 2, 3, 4],
        [1, 2, 3, 4, 1, 2, 3, 4]])

torch.Tensor.gather(dim, index):

对tensor在dim维进行索引, 例如对于4*3的tensor, 对dim1进行索引, 则index的所有元素都应该在0-2. 此外, 被索引的tensor和index的维度, 在除了dim维都需要保持一致, 上例中, index的shape应当是(4, *).

In [25]: a=torch.tensor([[.1,.2,.3],
    ...:                 [1.1,1.2,1.3],
    ...:                 [2.1,2.2,2.3],
    ...:                 [3.1,3.2,3.3]])
In [40]: a.gather(1, torch.tensor([[0, 1, 2, 1]]).t())
Out[40]: 
tensor([[0.1000],
        [1.2000],
        [2.3000],
        [3.2000]])

In [41]: a.gather(1, torch.tensor([[0, 1, 2, 1],[2, 1, 0, 1]]).t())
Out[41]: 
tensor([[0.1000, 0.3000],
        [1.2000, 1.2000],
        [2.3000, 2.1000],
        [3.2000, 3.2000]])
a.gather(1, torch.tensor([[0, 1, 2, 1]])) # 报错tensor(4, 3), index(1, 4), dim1, 对不上
a.gather(1, torch.tensor([0, 1, 2, 1])) # 报错tensor(4, 3), index(4,) dim1, 对不上
a.gather(1, torch.tensor([[0, 1, 2, 3]]).t()) # 报错, dim1范围0-2, 出现3

自动求导

autograd包是pytorch的核心包, 它提供了对于在tensor上的所有操作自动计算微分的功能, 使用者只需要关心网络的正向传播结构, 而不用处理反向传播. 对于tensor来说, 当设置其requires_grad参数为True之后, tensor开始追踪在其上的所有操作, 当调用.backward()方法时, 会计算从开始记录到当前tensor的操作的导数, 并留在沿计算流程的每个tensor的.grad属性中, 当不希望tensor记录历史时, 使用.detach()方法停止它记录计算历史, 来避免未来的计算被记录. 除了使用该方法, 也可以对代码块添加with torch.no_grad():. 当对模型进行评估时, 这个功能十分有用, 能够屏蔽requires_grad =True的参数发生改变.

要获得任意层的权重的导数 ∂ L o s s ∂ W i \frac{\partial Loss}{\partial W_i} WiLoss, 包括两步,

loss.backward() # loss -- tensor
print(w_i.grad) # loss对w_i的偏导值

grad属性最初为None, 当使用backward方法时, 该属性会成为一个tensor, 多次使用backward, 同一tensor的grad会累加, 因此需要对其重新置0, 或直接调用nn.Module.zero_grad()方法.

默认情况下, backward被调用后会释放已经完成反向传播的这部分计算图的内存, 当一次迭代的后面部分还需要用到前面的计算图时, 例如A3C的情况, 就可以对backward方法加上retain_graph参数.

神经网络

一个典型的神经网络训练过程如下:

  • 定义具有可学习参数(权重)的神经网络
  • 在输入的数据集上多次迭代
  • 计算输入数据的正向传播结果
  • 计算loss
  • 将loss的梯度反向传播到网络的参数中
  • 更新网络的参数, 最基本的方式是使用weight = weight - learning_rate * gradient

定义网络

  • 所有网络类都是nn.Module的子类
  • 所有网络类需要实现__init__, forward(self, input) 方法, 当调用该类的实例时, 其实是调用的forward方法.构造函数必须先调用父类的构造函数,super(Linear, self).__init__()nn.Module.__init__(), 前者更加普遍.
  • 网络的结构主要在forward方法中实现

容器

nn.Module是最基本的容器类, 其他网络模块都是其子类, module可以相互嵌套. 自己写的model也应该是其子类.
nn.Sequential是比较简单的容器, 能够构建线性网络的结构.

# Example of using Sequential
model = nn.Sequential(
          nn.Conv2d(1,20,5),
          nn.ReLU(),
          nn.Conv2d(20,64,5),
          nn.ReLU()
        )

pytorch提供了大量的层供使用, 具体参考documentation

nn.Module和nn.functional的区别

大部分nn中的层class都有nn.function对应,其区别是

  • nn.Module实现的layer是由class Layer(nn.Module)定义的特殊类,会自动提取可学习参数nn.Parameter
  • nn.functional中的函数更像是纯函数,由def function(input)定义。

由于两者性能差异不大,所以具体使用取决于个人喜好。对于激活函数和池化层,由于没有可学习参数,一般使用nn.functional完成,其他的有学习参数的部分则使用类。但是Droupout由于在训练和测试时操作不同,所以建议使用nn.Module实现,它能够通过model.eval加以区分。
nn.functional不需要放入__init__进行构造,所以不具有可学习参数的部分可以使用nn.functional进行代替。

两者主要的区别就是对于可学习参数nn.Parameter的识别能力,所以可以使用nn.functional提供的函数构造nn.Module, 构造时, 将参数tensor注册成Parameter类即可.

class Linear(nn.Module):
    def __init__(self, in_features, out_features):
        # nn.Module.__init__(self)
        super(Linear, self).__init__()
        self.w = nn.Parameter(t.randn(out_features, in_features)) # nn.Parameter是Tensor的子类
        self.b = nn.Parameter(t.randn(out_features))
    def forward(self, x):
        # wx+b
        return F.linear(x, self.w, self.b)
        
layer = Linear(4, 3)
input = V(t.randn(2, 4))
output = layer(input)
print(output)

定义网络和训练

  1. 按照class Net(nn.Module)的方式定义网络的__init__forward, forward返回的应当是网络的输出, 即预测.
  2. 定义loss函数, 可以通过nn.MSELoss()等类定义loss函数, loss函数实例的输入是网络输出和标签, 输出是loss值.
  3. net的梯度清零后, 从loss进行反向传播
  4. 实例化优化器类, 对网络权重进行更新
import torch.optim as optim
import torch.nn as nn
net = Net(params)
critierion = nn.MSELoss()
optimizer = optim.SGD(net.parameters(), lr)
optimizer.zero_grad()
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()

以下是一个预测图片分类的完整例子

import torch
import torchvision
import torchvision.transforms as transforms

# The output of torchvision datasets are PILImage images of range [0, 1]. We transform them to Tensors of normalized range [-1, 1].
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
                                         shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

# build network
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

# define loss
import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

# train the network
for epoch in range(2):  # loop over the dataset multiple times

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # get the inputs; data is a list of [inputs, labels]
        inputs, labels = data

        # zero the parameter gradients
        optimizer.zero_grad()

        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        # print statistics
        running_loss += loss.item()
        if i % 2000 == 1999:    # print every 2000 mini-batches
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0
print('Finished Training')

# Test the network
dataiter = iter(testloader)
images, labels = dataiter.next()
outputs = net(images)
_, predicted = torch.max(outputs, 1)

print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
                              for j in range(4)))
correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * correct / total))

数据导入与预处理

pytorch提供了Dataset类用于描述一个抽象的数据集, 使用时自定义类重写__getitems__()方法和__len__()方法实现一个映射型的数据集, 可以通过datasetclass[idx]获得第idx个数据. 也可以重写__iter__()方法实现一个iterableDataset, 这主要适合随机访问开销很大的情形和batch size与数据集大小有关的情形.

Dataset的基础上, pytorch提供了DataLoader类对dataset进行进一步的处理, 这个类实现了

  • 对数据进行分批batching
  • 对数据进行打乱shuffle
  • 对数据进行并行的读取, num_workers

几个功能, 建议数据导入阶段都将数据包装成Dataset并交给DataLoader来管理.

优化

参考

  • DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ
  • 『PyTorch』第十二弹_nn.Module和nn.functional

你可能感兴趣的:(python,机器学习)