PyTorch 是 Facebook 团队于 2017 年初发布的一个深度学习框架,相对于 TensorFlow 不直观的代码,PyTorch 以声明式的书写方式可以很大程度上简化我们的代码。PyTorch 不是在 C++ 代码上层单纯地做绑定,而是用 Python 实现的,可以很方便与与 numpy 等交互调用。

安装 PyTorch 非常简单(至少相对于 OpenCV 是这样的),按其官网上选定平台后,即可输入对应的命令进行安装。以我的 Mac 为例(注:这样的安装方式并不支持 GPU 运算):

1
2
pip install http://download.pytorch.org/whl/torch-0.1.12.post1-cp35-cp35m-macosx_10_7_x86_64.whl
pip install torchvision

由于此前一直使用 Keras 写代码,所以在免会去把 PyTorch 和 Keras 做比较。关于深度学习框架的选择,可以参考知乎相关的回答:

总结一下从知乎所了解到的结论是:不推荐使用 keras,推荐 TensorFlow & PyTorch。我个人的想法是,前期可以使用 keras 作为入门,在熟悉模型及基本的方法之后,需要了解其后端语言,即使用 TensorFlow or Theano 进行实现。这里 TensorFlow 显然是重点。

使用

最好的 PyTorch 学习资料,莫过于官方提供的文档,包括 Tutorial 和 Github 上提供的示例工程。这里,我的目的是尽可能快地熟悉 PyTorch 的基本用法,所以前期主要是看了下官网上的教程内容。

张量

用过 numpy 的话,应该很容易理解 PyTorch 中张量(Tensor)的概念。其基本操作与 numpy 很相似,也支持类似于 numpy 中所谓的 broadcast 操作,即张量加标量后,标量会自动转化为相应 shape 的张量,再进行相加。

1
2
3
4
5
6
7
8
9
10
11
12
A = torch.Tensor(3, 4, 5) # create unintialized Tensor with size 3 x 4 x 5
print(A.size())
B = torch.rand(3, 4, 5)
C = A + B
D = torch.Tensor(3, 4, 5)
torch.add(A, B, out=D)
# read or dump to numpy
D.numpy() # 转换为 numpy 类型
E = torch.from_numpy(np.random.rand(3, 4, 5))
# 直接 D + E 会出现类型 dismatch,需要将 E 转换为 FloatTensor
D + E.float()

需要特别值得注意的是,Tensor 类提供的带 _ 的方法,如 .add_.fill_ 等,都是在张量本身进行 inplace 操作。

自动微分

PyTorch 一个很大的特性在于其支持的自动微分功能。不同于 TensorFlow 的自动微分,在 TF 中,我们需要先构建计算图,然后进行预编译;但在 PyTorch 中,我们可以很直观地以正常声明式的代码方式书写,其内核自动计算微分。

先了解一下自动微分中比较重要的两个概念,也是 PyTorch 的两个基础类:Variable 和 Function。Variable 类包含 Tensor 并支持 Tensor 的几乎所有操作,当我们进行微分的时候,梯度的计算也会存储在 Variable 中 grad 字段,而原始的 Tensor 数据则存储在 data 字段中,见下图:

也许,你会发现里面还有个 creator 的字段。事实上,它存储的是一个 Function 实例。当我们计算梯度的时候,总有个以此 Variable 为依赖的目标函数,这个目标函数即所谓的 creator。

下面以一个简单的非线性回归问题的参数求解来说明。

我们的代价函数定义为 $L(X, Y) = \sum | W_2 \cdot{} \textrm{LeRu}(W_1 \cdot{} x) -Y |^2 $,给定 $X$ 和 $Y$,计算 $W_1$ 和 $W_2$:

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
from torch.autograd import Variable
dtype = torch.FloatTensor
N, D_in, H, D_out = 64, 1000, 100, 10
x = Variable(torch.randn(N, D_in).type(dtype), requires_grad=False)
y = Variable(torch.randn(N, D_out).type(dtype), requires_grad=False)
w1 = Variable(torch.randn(D_in, H).type(dtype), requires_grad=True)
w2 = Variable(torch.randn(H, D_out).type(dtype), requires_grad=True)
learning_rate = 1e-6
for t in range(500):
y_pred = x.mm(w1).clamp(min=0).mm(w2)
loss = (y_pred - y).pow(2).sum()
print(t, loss.data[0])
# Manually zero the gradients before running the backward pass
if t > 0:
w1.grad.data.zero_()
w2.grad.data.zero_()
loss.backward()
w1.data -= learning_rate * w1.grad.data
w2.data -= learning_rate * w2.grad.data

神经网络构建

TODO

参考