标签: 深度学习

旷视「天元」深度学习框架上手:概况、安装和初步体验

概况

天元(英文名:MegEngine)是旷视科技 3 月 25 日开源的深度学习框架,这一名字取自围棋棋盘中心点的名称,也有向 AlphaGo 致敬之意。听了旷视大佬们在发布会上的介绍,MegEngine 从 2014 年开始研发,作为旷视内部全员使用的框架,MegEngine 是驱动旷视在深度学习领域取得一系列成绩的核心动力。

趁没开学在家,对照官方文档快速上手了一下,这篇文章会记录一下对 MegEngine 初步的一些感受。

  • 天元 MegEngine 官方网站:https://megengine.org.cn/
  • GitHub repo:https://github.com/MegEngine/MegEngine
  • 中文社区:https://discuss.megengine.org.cn/
  • MegStudio:https://studio.brainpp.com/

安装

目前 MegEngine 的支持平台还比较单一,仅支持 Linux 环境下安装。对 Windows 用户,官方提示了可以使用 WSL 来运行 MegEngine,不过只支持 CPU 后端。

详细的环境要求是:

  • 64 位、16.04 及以上版本的 Ubuntu
  • Python 3.5+
  • NVIDIA 驱动版本 418.x

旷视 MegStudio 平台提供了 MegEngine 0.3.1 + Python 3.8 的环境,可以很方便地开始使用 MegEngine。

除了 MegStudio,实测 Google Colab 平台也可以成功安装 MegEngine。

MegEngine 安装包本身集成了 CUDA 环境,因此不区分 CPU 和 GPU 版本。

通过 pip 安装 MegEngine:

pip3 install megengine -f https://megengine.org.cn/whl/mge.html

整个安装包 700MB+,下载的速度非常快,服务器在国内果然无惧速度问题。

import 一下来测试安装是否成功,官方对 MegEngine 的习惯性缩写是 mge

import megengine as mge

对于要参与到 MegEngine 开发贡献的开发者,或者需要使用未进入 release 的功能,则需要从源码安装,可以参考 README 中的指引

体验

MegStudio

MegStudio 是旷视开放的提供免费算力的在线深度学习开发平台。目前提供下面三种配置的环境,其中基础版的环境不限时长,高级版(CPU)和高级版(GPU)环境需要通过算力卡获得。目前算力卡是通过邀请用户的方式获得。

MegStudio 开发环境是基于 JupyterLab,环境关闭之后额外添加的文件会被销毁。使用体验上和 JupyterLab 基本没有区别,如果官方能默认支持一下代码补全就更好了。

友好的中文文档

对照 官方文档 上手的过程当中,很大的一个体会是 MegEngine 的文档做得很用心。

首先从语言上说,中文书写的文档对于国内的开发者来说无疑是很友好的。

官方文档目前分为基础学习和进阶学习两个部分,MegEngine 团队设计了一个循序渐进的入门指引帮助使用者熟悉 MegEngine。

MegEngine 的文档中穿插了对神经网络知识的简要讲解,比如下面是文档中介绍的 BP 过程。

文档代码中的注释也非常详细,介绍了代码释义,和 API 的细节,阅读起来会比较轻松。


支持基于 Module 的网络搭建

MegEngine 提供两种网络搭建方式:基于 functional (提供常见算子)和基于 Module(提供常见网络层)。

基于 Module 的构建方式和 PyTorch 的风格十分相似,下面分别是 MegEngine 和 PyTorch 的 LeNet 实现代码,可以看出总体的写法没有很大的区别,对 PyTorch 用户来说,熟悉 MegEngine 网络搭建压力很小(官方也提到支持导入 PyTorch Module)。

# MegEngine Implementation
class LeNet(M.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = M.Conv2d(1, 6, 5)
        self.relu1 = M.ReLU()
        self.pool1 = M.MaxPool2d(2, 2)
        self.conv2 = M.Conv2d(6, 16, 5)
        self.relu2 = M.ReLU()
        self.pool2 = M.MaxPool2d(2, 2)
        self.fc1 = M.Linear(16 * 5 * 5, 120)
        self.relu3 = M.ReLU()
        self.fc2 = M.Linear(120, 84)
        self.relu4 = M.ReLU()
        self.classifer = M.Linear(84, 10)

    def forward(self, x):
        x = self.pool1(self.relu1(self.conv1(x)))
        x = self.pool2(self.relu2(self.conv2(x)))
        x = F.flatten(x, 1)
        x = self.relu3(self.fc1(x))
        x = self.relu4(self.fc2(x))
        x = self.classifer(x)
        return x
# PyTorch Implementation
class LeNet5(nn.Module):
   def __init__(self):
       super().__init__()
       self.conv1 = nn.Conv2d(1, 6, 5, padding=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 = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
       x = F.max_pool2d(F.relu(self.conv2(x)), (2, 2))
       x = x.view(-1, self.num_flat_features(x))
       x = F.relu(self.fc1(x))
       x = F.relu(self.fc2(x))
       x = self.fc3(x)
       return x
   def num_flat_features(self, x):
       size = x.size()[1:]
       num_features = 1
       for s in size:
           num_features *= s
       return num_features

动态图与静态图的转换

此前的 TensorFlow 框架注重静态图,这对于调试来说比较困难,PyTorch 侧重动态图,对调试开发来说比较便利,但是不利于高效部署。MegEngine 在动态图和静态图的兼容上下了功夫,支持通过用 jit 包中的 trace 装饰器来完成动静转换,这样调试时可以使用动态图,实时输出中间结果,部署时再使用静态图,提升训练和推理速度。这也是 MegEngine 作为工业级框架的重要特性之一。

import megengine.functional as F
from megengine.jit import trace

trace.enabled = True

@trace
def train_func(data, label, *, opt, net):
    pred = net(data)
    loss = F.cross_entropy_with_softmax(pred, label)
    opt.backward(loss)
    return pred, loss

train_func(data, label, opt=optimizer, net=le_net)

中文社区

MegEngine 建立了中文语言的 社区,对国内开发者无疑是一大好处,我在站务反馈区提了一个捉虫帖,没想到没几分钟之后管理员就反馈修改完毕,效率可以说是非常高了!

以上就是对 MegEngine 初步的体验,对国内人工智能生态来说,MegEngine 的开源无疑是一件好事,期待未来 MegEngine 和国内深度学习框架的发展!

PyTorch深度学习60分钟闪电战:04 训练一个分类器

本系列是PyTorch官网Tutorial Deep Learning with PyTorch: A 60 Minute Blitz 的翻译和总结。
1. PyTorch概览
2. Autograd – 自动微分
3. 神经网络
4. 训练一个分类器
下载本文的Jupyter NoteBook文件:60min_04_Training a Classifier.ipynb

到目前,你已经知道了如何定义神经网络、计算损失、更新网络权重。

那么,数据呢?

数据

通常,当你处理图像,文本,音频或视频数据时,可以使用标准python包将数据加载到numpy数组中。然后便可以将数组转换为torch.*Tensor

  • 对于图像,Pillow,OpenCV等软件包很有用
  • 对于音频,可以使用scipy和librosa等包
  • 对于文本,基于Python或Cython的加载、NLTK和SpaCy都很有用

特别地,对于图像领域,我们创建了一个叫做torchvision的包,这个包包含了支持常见数据集(如Imagenet, CIFAR10, MNIST等)的数据加载器以及图像的数据转换器,即torchvision.datasetstorch.utils.data.DataLoader

这提供了极大的便利,并且避免了编写样板代码。

在这个教程中,我们将会使用CIFAR10数据集。它具有以下类别:“飞机”,“汽车”,“鸟”,“猫”,“鹿”,“狗”,“青蛙”,“马”,“船”,“卡车”。CIFAR-10中的图像尺寸为3×32×32,即尺寸为32×32像素的3通道彩色图像。

训练图像分类器

我们将按顺序执行以下步骤:

  1. 使用以下命令加载和标准化CIFAR10训练和测试数据集 torchvision
  2. 定义卷积神经网络
  3. 定义损失函数
  4. 根据训练数据训练网络
  5. 在测试数据上测试网络

1. 加载CIFAR10并将其规则化

torchvision的帮助下,加载CIFAR10非常容易。

import torch
import torchvision
import torchvision.transforms as transforms

torchvision数据集的输出是在[0,1]范围的PILImage。我们将它们转换成范围是[-1,1]的张量。

Note:

如果在Windows上运行时你遇到了一个BrokenPipeError,请尝试将torch.utils.data.DataLoader()num_worker设置为0。

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')
Files already downloaded and verified
Files already downloaded and verified

让我们来展示一些训练图像:

import matplotlib.pyplot as plt
import numpy as np

# functions to show an image


def imshow(img):
    img = img / 2 + 0.5     # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()


# get some random training images
dataiter = iter(trainloader)
images, labels = dataiter.next()

# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

plane  bird   car truck

2. 定义卷积神经网络

复制之前的神经网络,然后对其进行修改以获取3通道图像(而不是原来定义的1通道图像)。

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()

3. 定义损失函数和优化器

这里我们使用分类交叉熵损失和带有动量的SGD。

import torch.optim as optim

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

4. 训练网络

我们只需要遍历数据迭代器,然后将输入喂给网络并进行优化。

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')
[1,  2000] loss: 2.192
[1,  4000] loss: 1.815
[1,  6000] loss: 1.630
[1,  8000] loss: 1.541
[1, 10000] loss: 1.475
[1, 12000] loss: 1.446
[2,  2000] loss: 1.370
[2,  4000] loss: 1.350
[2,  6000] loss: 1.302
[2,  8000] loss: 1.292
[2, 10000] loss: 1.268
[2, 12000] loss: 1.253
Finished Training

5. 在测试集上测试网络

我们已经使用训练集在网络上进行了两轮的训练,但我们需要确认一下网络是否真的学到了些什么。

我们将对测试集上的数据进行预测,并根据实际情况进行检查。如果预测正确,则将样本添加到正确预测列表中。

第一步,让我们显示一些测试集中的图像。

dataiter = iter(testloader)
images, labels = dataiter.next()

# print images
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))

GroundTruth:    cat  ship  ship plane

那么,我们的神经网络是如何预测上面的图片的呢?

outputs = net(images)

outputs给出十个类的置信度,置信度越高,代表网络认为这张图片越可能属于这一类别,所以我们现在获取置信度最高的类别的下标。

_, predicted = torch.max(outputs, 1)

print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
                              for j in range(4)))
Predicted:    cat  ship  ship  ship

结果似乎还行,我们来看一下网络在整个数据集上的表现。

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))
Accuracy of the network on the 10000 test images: 53 %

看起来,这个准确率比随机选择10%的准确率要高得多,看来我们的网络学到了一些东西。

让我们看下哪些类的准确率高,哪些类的准确率低:

class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs, 1)
        c = (predicted == labels).squeeze()
        for i in range(4):
            label = labels[i]
            class_correct[label] += c[i].item()
            class_total[label] += 1


for i in range(10):
    print('Accuracy of %5s : %2d %%' % (
        classes[i], 100 * class_correct[i] / class_total[i]))
Accuracy of plane : 57 %
Accuracy of   car : 58 %
Accuracy of  bird : 57 %
Accuracy of   cat : 49 %
Accuracy of  deer : 49 %
Accuracy of   dog : 19 %
Accuracy of  frog : 41 %
Accuracy of horse : 67 %
Accuracy of  ship : 73 %
Accuracy of truck : 61 %

那么接下来,我们该如何在GPU上运行这个神经网络呢?

在GPU上训练

就像把Tensor转移到GPU上一样,神经网络也可以转移到GPU上。

如果CUDA可用,首先让我们将设备定义为第一个可见的CUDA设备:

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# Assuming that we are on a CUDA machine, this should print a CUDA device:

print(device)
cuda:0

接下来,我们假定device为CUDA设备。

下面这些方法将逐一将模块转移到CUDA设备上。

net.to(device)
Net(
  (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=400, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

请记得你需要将输入和每一步的目标都设定在GPU。

inputs, labels = data[0].to(device), data[1].to(device)

为什么相比CPU,GPU在这里没有表现出很大的加速?这是因为这里的网络真的很小。

至此,我们实现了:
* 全面了解PyTorch的Tensor库和神经网络。
* 训练一个小型神经网络对图像进行分类。

PyTorch深度学习60分钟闪电战:03 神经网络

本系列是PyTorch官网Tutorial Deep Learning with PyTorch: A 60 Minute Blitz 的翻译和总结。
1. PyTorch概览
2. Autograd – 自动微分
3. 神经网络
4. 训练一个分类器
下载本文的Jupyter NoteBook文件:60min_03_Neural Networks.ipynb

可以使用torch.nn包构建神经网络。

现在你已经对autograd有了基本的了解,nn依赖autograd来定义模型并执行微分。一个nn.Module包含层和一个forward(input)方法用以返回output

例如,请看以下对图像分类网络:

这是一个简单的前馈网络。它获取输入,将其一层又一层地馈入,然后最终给出输出。

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

定义具有一些可学习参数(或权重)的神经网络
遍历输入数据集
通过网络处理输入
计算损失(输出正确的距离有多远)
将梯度传播回网络参数
通常使用简单的更新规则来更新网络的权重: weight = weight – learning_rate * gradient

定义网络

我们来定义上面的网络:

import torch
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__() # 多重继承
        # 一个图像输入channel,六个输出channel,3*3卷积核
        self.conv1 = nn.Conv2d(1,6,3)
        self.conv2 = nn.Conv2d(6,16,3)
        # 线性变换: y = Wx + b
        self.fc1 = nn.Linear(16 * 6 * 6, 120)  # 6*6 from image dimension
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self,x):
        # 经过卷积层1、relu激活函数、max_pool降采样
        x = F.max_pool2d(F.relu(self.conv1(x)),(2,2))
        # 经过卷积层2、relu激活函数、max_pool降采样
        # 如果降采样尺寸为正方形,也可以只写一个数字
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        # resize
        x = x.view(-1, self.num_flat_features(x))
        # 线性变换
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
print(net)
Net(
  (conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
  (fc1): Linear(in_features=576, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

你刚刚已经定义了前向传播的函数,而反向传播的函数将会由autograd自动给出。

网络的可学习由net.parameters()返回。

params = list(net.parameters())
print(len(params))
print(params[0].size())  # conv1's .weight
10
torch.Size([6, 1, 3, 3])

让我们尝试一个32×32随机输入。注意:该网络(LeNet)的预期输入大小为32×32。要在MNIST数据集上使用此网络,请将图像从数据集中调整为32×32。

input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
tensor([[ 0.0203,  0.0255,  0.0466,  0.1283,  0.1069,  0.1514, -0.0276, -0.0390,
         -0.0746, -0.0476]], grad_fn=<AddmmBackward>)

用随机梯度将所有参数和反向传播器的梯度缓冲区归零:

net.zero_grad()
out.backward(torch.randn(1, 10))

Note:

torch.nn仅支持mini-batches。整个torch.nn仅支持作为mini-batch采样的输入,而非单采样的输入。
例如,nn.Conv2d接受4维的张量:nSamples x nChannels x Height x Width.
如果你有一个单采样的输入,则只需使用input.unsqueeze(0)即可添加伪造的批次尺寸。

在继续进行之前,让我们回顾一下到目前为止所看到的所有课程。

概括:

  • torch.Tensor – 一个支持例如backward()等autograd操作的多维数组,同时包含关于张量的梯度。
  • nn.Module – 神经网络模块。一个封装参数的便捷途径,同时有将其移动到GPU、导出、加载的帮助器。
  • nn.Parameter – 一种张量,当给Module赋值时能够自动注册为一个参数。
  • autograd.Function – 使用autograd自动实现前向传播和反向传播。每个张量的操作都至少会生成一个独立的Function节点,与生成该张量的函数相连之后,记录下操作历史。

到这里,我们掌握了:
* 如何定义一个神经网络
* 处理输入并调用反向传播

接下来我们还将了解到:
* 计算损失函数
* 更新网络权重

损失函数

损失函数采用一对(输出,目标)作为输入,并计算一个输出值来评估与目标的距离。

nn包中有几个不同的损失函数。一个简单的损失函数是:nn.MSELoss,计算输入和目标之间的均方误差。

例如:

output = net(input)
target = torch.randn(10)  # a dummy target, for example
target = target.view(1, -1)  # make it the same shape as output
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)
tensor(0.5661, grad_fn=<MseLossBackward>)

现在,如果你使用.grad_fn属性在反向传播方向跟踪loss,你将会看到这样一张计算图。
::

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
      -> view -> linear -> relu -> linear -> relu -> linear
      -> MSELoss
      -> loss

所以,当我们调用loss.backward(),整个图是有关损失的微分,图中所有requires_grad=True的张量的.grad属性中将会累积梯度。

print(loss.grad_fn)  # MSELoss
print(loss.grad_fn.next_functions[0][0])  # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU
<MseLossBackward object at 0x0000014700F28940>
<AddmmBackward object at 0x0000014700F28940>
<AccumulateGrad object at 0x0000014700EFECC0>

反向传播

要完成反向传播,我们所要做的是loss.backward()。你需要清空现有的梯度值,否则梯度将被累积到现有的梯度中。

现在我们将调用loss.backward(),并观察conv1在反向传播之前和之后的偏置梯度。

net.zero_grad()     # zeroes the gradient buffers of all parameters

print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)

loss.backward()

print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([-0.0008, -0.0017, -0.0009,  0.0055, -0.0086,  0.0021])

现在我们已经知道了如何使用损失函数。

只剩下:

  • 如何更新网络权重

更新网络权重

实践中最简单的权重更新规则是随机梯度下降(Stochastic Gradient Descent, SGD):

weight = weight - learning_rate * gradient

我们可以在python中这样实现出来:

learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)

如果希望使用各种不同的更新规则,例如SGD,Nesterov-SGD,Adam,RMSProp等,torch.optim实现所有这些方法。使用它非常简单:

import torch.optim as optim

# 创建你的优化器
optimizer = optim.SGD(net.parameters(), lr=0.01)

# 在训练循环中
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update

PyTorch深度学习60分钟闪电战:02 Autograd – 自动微分

本系列是PyTorch官网Tutorial Deep Learning with PyTorch: A 60 Minute Blitz 的翻译和总结。
1. PyTorch概览
2. Autograd – 自动微分
3. 神经网络
4. 训练一个分类器
下载本文的Jupyter NoteBook文件:60min_02_Autograd – Automatic Differentiation.ipynb

在PyTorch中,一切神经网络的核心是autograd模块。

autograd模块为张量的所有操作提供自动微分。这是一个由运行定义的框架,这意味着你的反向传播是由代码的运行的方式定义的,并且每一次迭代都可能不同。

让我们通过一些示例以更简单的方式看待这一点。

Tensor

torch.Tensor是这个包的核心类。如果将.requires_grad属性设置为True,这将开始追踪它上的所有操作。当计算结束,你可以调用.backward()方法,自动计算所有梯度。该张量的梯度将累加到.grad属性中。

为了停止一个张量上的历史记录跟踪,你可以调用.detach()方法将它从计算历史中分离,并阻止将来计算中的跟踪。

为了防止跟踪历史记录(和使用内存),你还可以将代码块包装在with torch.no_grad():中。这在评估模型时特别有用,因为模型可能存在带requires_grad=True的可训练参数,但事实上我们不需要梯度。

还有一个类对于autograd实现非常重要——Function

TensorFunction相互连接并建立一个无环图,该图对完整的计算历史进行编码。每个张量都有一个.grad_fn属性,该属性引用一个创建这个张量的Function(用户创建的张量除外,他们的.grad_fn属性为None)。

如果你想计算导数,你可以在张量上调用.backward()。如果Tensor为标量(即,它包含一个元素数据),则无需为.backward()指定参数,但是如果它有更多元素,则需要指定gradient 参数为匹配形状的张量。

import torch

创建一个张量,并设置requires_grad=True来跟踪计算。

x = torch.ones(2, 2, requires_grad=True)
print(x)
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)

执行一项操作:

y = x + 2
print(y)
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)

y是由相加操作得来的张量,所以它有grad_fn属性。

print(y.grad_fn)
<AddBackward0 object at 0x0000022497B4C630>

我们在y上做更多的操作:

z = y * y * 3
out = z.mean()

print(z, out)
tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)

.requires_grad_( ... )方法可以变换一个张量的requires_grad属性。

a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)

a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
False
None
True
<SumBackward0 object at 0x0000022497F12EF0>

Gradients

现在让我们开始反向传播。

out.backward()

打印梯度d(out)/dx

print(x.grad)
tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])

接下来是一个vector-Jacobian product的例子

x = torch.randn(3, requires_grad=True)

y = x * 2
print(y)
while y.data.norm() < 1000:
    y = y * 2
    print(y)
tensor([-0.1796, -0.6436, -1.6582], grad_fn=<MulBackward0>)
tensor([-0.3592, -1.2871, -3.3164], grad_fn=<MulBackward0>)
tensor([-0.7184, -2.5743, -6.6329], grad_fn=<MulBackward0>)
tensor([ -1.4368,  -5.1485, -13.2657], grad_fn=<MulBackward0>)
tensor([ -2.8737, -10.2970, -26.5314], grad_fn=<MulBackward0>)
tensor([ -5.7474, -20.5940, -53.0628], grad_fn=<MulBackward0>)
tensor([ -11.4948,  -41.1880, -106.1257], grad_fn=<MulBackward0>)
tensor([ -22.9896,  -82.3761, -212.2513], grad_fn=<MulBackward0>)
tensor([ -45.9791, -164.7521, -424.5026], grad_fn=<MulBackward0>)
tensor([ -91.9583, -329.5043, -849.0052], grad_fn=<MulBackward0>)
tensor([ -183.9165,  -659.0085, -1698.0105], grad_fn=<MulBackward0>)

现在,y不再是标量。torch.autograd无法直接计算完整雅可比行列,但是如果只想要 vector-Jacobian product,只需通过 backward 将向量作为参数传入:

v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)

print(x.grad)
tensor([2.0480e+02, 2.0480e+03, 2.0480e-01])

你也可以使用with torch.no_grad():来包装代码块来使.requires_grad=True的张量通过追踪历史进行自动求导。

print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
    print((x ** 2).requires_grad)
True
True
False

PyTorch深度学习60分钟闪电战:01 PyTorch概览

本系列是PyTorch官网Tutorial Deep Learning with PyTorch: A 60 Minute Blitz 的翻译和总结。
1. PyTorch概览
2. Autograd – 自动微分
3. 神经网络
4. 训练一个分类器
下载本文的Jupyter NoteBook文件:60min_01_PyTorch Overview.ipynb

Tensor 张量

Tensor类似于Numpy的ndarray,但不同的是,Tensor可以在GPU上被加速。

from __future__ import print_function
import torch

生成一个未初始化的 5×3 矩阵:

x = torch.empty(5,3)
print(x)
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])

生成一个随机矩阵:

x = torch.rand(5,3)
print(x)
tensor([[0.4907, 0.1470, 0.9752],
        [0.9518, 0.6973, 0.4775],
        [0.5643, 0.6586, 0.3142],
        [0.2364, 0.8435, 0.6187],
        [0.3253, 0.5903, 0.9939]])

生成一个0矩阵,数据类型为long

x = torch.zeros(5,3,dtype=torch.long)
print(x)
tensor([[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]])

直接从数据创造tensor

x = torch.tensor([6.7,6,7])
print(x)
tensor([6.7000, 6.0000, 7.0000])

在已有tensor的基础上创建tensor。这些方法将会继承原tensor的属性,比如数据类型,除非新的值由用户给出。

print('x:\n',x)
y = x.new_ones(5, 3, dtype=torch.double)
print('y:\n',y)

x = torch.randn_like(x, dtype=torch.float)    # dtype被改写,但是结果继承了size。
print(x)                                      
x:
 tensor([6.7000, 6.0000, 7.0000])
y:
 tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], dtype=torch.float64)
tensor([-0.3186,  0.5506,  0.4940])

获得tensor的size

print(x.size())
torch.Size([3])

Operations 操作

相加

PyTorch中有许多种相加语法:

相加:语法1

x = torch.rand(5, 3)
y = torch.rand(5, 3)
print(x + y)
tensor([[0.9943, 0.3976, 0.6645],
        [0.6796, 1.1703, 1.2389],
        [1.5879, 1.1208, 1.5912],
        [1.2165, 0.8195, 1.0364],
        [1.2178, 1.3195, 1.1736]])

相加:语法2

print(torch.add(x,y))
tensor([[0.9943, 0.3976, 0.6645],
        [0.6796, 1.1703, 1.2389],
        [1.5879, 1.1208, 1.5912],
        [1.2165, 0.8195, 1.0364],
        [1.2178, 1.3195, 1.1736]])

相加:将一个tensor作为out参数,用以输出。

result = torch.empty(5,3)
torch.add(x,y,out=result)
print(result)
tensor([[0.9943, 0.3976, 0.6645],
        [0.6796, 1.1703, 1.2389],
        [1.5879, 1.1208, 1.5912],
        [1.2165, 0.8195, 1.0364],
        [1.2178, 1.3195, 1.1736]])

相加:替代

y.add_(x)
print(y)
tensor([[0.9943, 0.3976, 0.6645],
        [0.6796, 1.1703, 1.2389],
        [1.5879, 1.1208, 1.5912],
        [1.2165, 0.8195, 1.0364],
        [1.2178, 1.3195, 1.1736]])

Note:

任何让tensor被覆盖的方法的名称都以_结尾。例如x.copy_(y), x.t_()等。

索引

NumPy中的索引方法可以使用:

print(x[:, 1])
tensor([0.0200, 0.4981, 0.2466, 0.7497, 0.8315])

Resizing

想要改变tensor的规模,可以使用torch.view

x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)  # the size -1 is inferred from other dimensions
print(x.size(), y.size(), z.size())
torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])

取值

如果你有一个单元素的tensor,.item()方法可以取值。

x = torch.randn(1)
print(x)
print(x.item())
tensor([-0.7093])
-0.709314227104187

与NumPy的沟通桥梁

将Torch张量转换为NumPy数组很容易,反之亦然。
Torch张量将会和NumPy数组共享同一个内存位置(Torch张量在CPU上的情况下),其中一个发生改变,另一个也会随之改变。

将Torch Tensor转换为NumPy数组

a = torch.ones(5)
print(a)
b = a.numpy()
print(b)
a.add_(1)
print(a)
print(b)
tensor([1., 1., 1., 1., 1.])
[1. 1. 1. 1. 1.]
tensor([2., 2., 2., 2., 2.])
[2. 2. 2. 2. 2.]

将NumPy数组转换为Torch Tensor

import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a)
print(b)
[2. 2. 2. 2. 2.]
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)

除了CharTensor,其他在CPU上的Torch张量均支持与NumPy数组的相互转换。

CUDA Tensors

使用.to()方法,可以将张量转移到其他设备。

if torch.cuda.is_available():
    device = torch.device("cuda")          # 创建CUDA设备对象
    y = torch.ones_like(x, device=device)  # 直接在GPU上
    x = x.to(device)
    # x = x.to("cuda") 也可以这么写
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       # ``.to`` 方法可以同时转换数据类型
tensor([0.2907], device='cuda:0')
tensor([0.2907], dtype=torch.float64)