PyTorch:Torch 用户

此教程翻译自 PyTorch 官方教程

作者:Soumith Chintala

在这个教程中,你将学习如下知识:

  1. 使学习用 torch 张量, 和与 (Lua) Torch 的不同;
  2. 使用自动求导包;
  3. 构建神经网络

    • 构建卷积网络(‘ConvNet`)
    • 构建 循环神经网络(`Recurrent Net)
  4. 使用多GPU

一. 张量(Tensors)

PyTorch 中的张量和 Torch 中的张量完全一样的.

创建一个未初始化的大小为 5*7 的张量:

1
2
import torch
a = torch.FloatTensor(5, 7)

用均值为 0, 方差为 1 的正态分布随机初始化一个张量:

1
2
3
a = torch.randn(5, 7)
print(a)
print(a.size())

输出:

1
2
3
4
5
6
7
 0.6608  0.0584 -1.9318  1.6068 -1.0049  1.3115 -0.3893
0.5118 1.7707 -0.3681 0.2345 -0.0295 2.0229 0.2815
1.2753 -1.2878 1.3112 -0.0965 1.4672 0.5731 2.7306
1.2824 0.5972 1.4461 1.1568 0.1307 0.0381 -1.1927
0.7658 0.1555 0.2385 1.0136 -0.6666 -0.3318 -0.8383
[torch.FloatTensor of size 5x7]
torch.Size([5, 7])

注意
torch.Size 本质上是一个元组,所以它支持和元组一样的操作.

Inplace/Out-of-place

第一个不同就是张量上的所有就地(Inplace)操作都有一个’_’后缀,比如,add不是一个原地(Out-of-place)操作版本,add_是一个原地操作版本.

1
2
3
4
5
6
7
a.fill_(3.5)
# a has now been filled with the value 3.5

b = a.add(4.0)
# is still filled with 3.5
# new tensor b is returned with 3.5 + 4.0 = 7.0
print(a, b)

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
3.5000  3.5000  3.5000  3.5000  3.5000  3.5000  3.5000
3.5000 3.5000 3.5000 3.5000 3.5000 3.5000 3.5000
3.5000 3.5000 3.5000 3.5000 3.5000 3.5000 3.5000
3.5000 3.5000 3.5000 3.5000 3.5000 3.5000 3.5000
3.5000 3.5000 3.5000 3.5000 3.5000 3.5000 3.5000
[torch.FloatTensor of size 5x7]

7.5000 7.5000 7.5000 7.5000 7.5000 7.5000 7.5000
7.5000 7.5000 7.5000 7.5000 7.5000 7.5000 7.5000
7.5000 7.5000 7.5000 7.5000 7.5000 7.5000 7.5000
7.5000 7.5000 7.5000 7.5000 7.5000 7.5000 7.5000
7.5000 7.5000 7.5000 7.5000 7.5000 7.5000 7.5000
[torch.FloatTensor of size 5x7]

某些操作,比如 narrow 没有原地操作版本, 因此 narrow_不存在.
同样地,某些操作, 比如 fill_ 没有 out-of-place 版本,所以 .fill 不存在.

零索引(Zero Indexing)

另一个不同是 PyTorch 中的张量是从 0 开始索引,而在 lua 中, 它是从1 开始索引.

1
b = a[0, 3] # 选择 a 中的第一行,第四列的元素

张量也可以用 Python 的切片来索引

1
b = a[:, 3:5]

无驼峰写法

下一个微小的不同是所有的函数现在不再是驼峰命名.比如 indexAdd 现在写为 index_add_.

1
2
x = torch.ones(5, 5)
print(x)

输出:

1
2
3
4
5
6
1  1  1  1  1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
[torch.FloatTensor of size 5x5]

1
2
3
4
z = torch.Tensor(5, 2)
z[:, 0] = 10
z[:, 1] = 100
print(z)

输出:

1
2
3
4
5
6
10  100
10 100
10 100
10 100
10 100
[torch.FloatTensor of size 5x2]

1
2
x.index_add_(1, torch.LongTensor([4, 0]), z)
print(x)

输出:

1
2
3
4
5
6
101    1    1    1   11
101 1 1 1 11
101 1 1 1 11
101 1 1 1 11
101 1 1 1 11
[torch.FloatTensor of size 5x5]

numpy 桥

把一个torch张量转换为numpy数组或者反过来都是很简单的。Torch张量和numpy数组将共享潜在的内存,改变其中一个也将改变另一个。

把 Torch 张量转换为 numpy 数组

1
2
a = torch.ones(5)
print(a)

输出:

1
2
3
4
5
6
1
1
1
1
1
[torch.FloatTensor of size 5]

1
2
b = a.numpy()
print(b)

输出:

1
[ 1.  1.  1.  1.  1.]

1
2
3
a.add_(1)
print(a)
print(b) # 观察 numpy 数组的值如何在变化

输出:

1
2
3
4
5
6
7
8
2
2
2
2
2
[torch.FloatTensor of size 5]

[ 2. 2. 2. 2. 2.]

把 numpy 数组转换为 torch 张量

1
2
3
4
5
6
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a)
print(b) # 观察改变numpy数组的值如何在自动改变torch张量的值

输出:

1
2
3
4
5
6
7
8
[ 2.  2.  2.  2.  2.]

2
2
2
2
2
[torch.DoubleTensor of size 5]

除了 CharTensor, 所有在 CPU 上的张量都支持在 numpy 之间来回转换.

CUDA 张量

在 PyTorch 中, CUDA 张量是很容易实现的,并且从cpu 到 GPU 来转换一个 CUDA 张量将保存它的潜在类型.

1
2
3
4
5
6
7
# 让我们在只有CUDA可用时才运行这个单元
if torch.cuda.is_available():
# 创建一个LongTensor,并把它转换为 GPU 版的 torch.cuda.LongTensor
a = torch.LongTensor(10).fill_(3).cuda()
print(type(a))
b = a.cpu()
# 把它转换为 cpu 版本,还原为torch.LongTensor

输出:

1
<class 'torch.cuda.LongTensor'>

脚本总运行时间: 0.002 秒

Python 源码

Jupyter 源码

自动求导(Autograd)

自动求导是 torch 中自动微分的核心包,它使用一个基于磁带的系统来进行自动微分.

在前向阶段,这个磁带会记住所有执行的操作,在反向阶段, 它重新执行这些操作.

变量(Variable)

autograd 中,我们引入一个 Variable 类, 它是一个 Tensor 的包装器.你可以通过 .data 属性来访问原始的张量,在执行完反向过程后,关于这个变量的梯度被累积到 .grad 属性中.

![Variable][/images/Variable.png]

还有一个类对于自动求导的实验非常重要,即 Function类.
VariableFunction 是相互连接的并构成一个无环图来编码整个计算历史.每个 Variable 有一个 grad_fn 属性,它指向创建该变量的一个Function,用户自己创建的变量除外,它的grad_fn属性为None.

如果你想计算导数,你可以在一个 Variable 上调用 .backward(). 如果 Variable 是一个标量(他只有一个元素),你不必给 backward()指定任何的参数,然而如果他拥有多个元素, 你需要指定一个 grad_output 参数,他是一个和该 Variable 大小相匹配的张量.

1
2
3
4
import torch
from torch.autograd import Variable
x = Variable(torch.ones(2, 2), requires_grad=True)
print(x)

输出:

1
2
3
4
Variable containing:
1 1
1 1
[torch.FloatTensor of size 2x2]

1
print(x)

输出:

1
2
3
1  1
1 1
[torch.FloatTensor of size 2x2]

1
print(x.grad)

输出:

1
None

1
print(x.grad_fn)
1
None

在 x 上执行一个操作:

1
2
y = x + 2
print(y)

输出:

1
2
3
4
Variable containing:
3 3
3 3
[torch.FloatTensor of size 2x2]

y 作为一个操作的结果, 因此它有grad_fn

1
print(y.grad_fn)

输出:

1
<torch.autograd.function.AddConstantBackward object at 0x7faa6602d7c8>

在 y 上执行更多的操作:

1
2
3
z = y * y * 3
out = z.mean()
print(z, out)

输出:

1
2
3
4
5
6
7
Variable containing:
27 27
27 27
[torch.FloatTensor of size 2x2]
Variable containing:
27
[torch.FloatTensor of size 1]

梯度(Gradients)

现在我们进行反向机选,并打印出梯度 $d(out)/dx$

1
2
out.backward()
print(x.grad)

输出:

1
2
3
4
Variable containing:
4.5000 4.5000
4.5000 4.5000
[torch.FloatTensor of size 2x2]

未完待续…