Pytorch 初体验
深度学习框架很多,TensorFlow 算是比较流行的了,但静态图有时候确实写的很难受,以及一些比较复杂的 model 实现起来比较蛋疼;最近在看的文章的实现没有 TensorFlow 版本,起初我想改写一个 TensorFlow 版本出来,结果实现不了,一方面可能我确实比较菜,另一方面,也只能说 TensorFlow 有些东西在设计上让人为难。所以,那就试试 PyTorch 咯。这篇文章主要是记录我学习 PyTorch 官方 60 分钟教程的过程。
Tensor
逃不开的还是 Tensor 这个对象,PyTorch 创建、操作 Tensor 的方法和 numpy 很类似,据说 Torch 一开始就是 Numpy 的 GPU 加速版,简单列些一些 API:
|
|
比较有意思的是,创建 Tensor 的时候 size 参数不是一个 tuple e.g. (5, 3) ,而是用 *args 的方式传入。
常用的算术操作可以通过 x+y 或者是 torch.add(x, y) 来实现,同时 PyTorch 也提供了就地 (in place) 的操作:
|
|
所有的操作名 + 下划线都是一个就地操作,x.copy_(y) 会用 y 的值替代x。
另外,对于单元素的 Tensor,可以通过使用 .item() 来获取它;TensorFlow 中的 reshape() 对应的是 view(),并且,和 numpy 一样,Tensor 支持切片操作:
|
|
既然之前说 Torch 是 Numpy 的一个 GPU 版本,也就支持 ndarray 和 Tensor 的相互转化(这就比 TensorFlow 用起来舒服很多),可以用 Tensor.numpy() 获取到对应的 ndarray,逆操作则是 torch.from_numpy():
|
|
上面这个例子很重要,说明了ndarray 和 Tensor 对象之间的转换是一种引用关系,而非创建新的对象。也就是说,任何一个相关对象的变化都会影响到另一方,就像例子 1 中,我们对原始的 a 进行 add_(1),在此之前得到的 b 对象的值也改变了,例子二中同样出现了这样的情况,这一点需要特别注意。
CUDA Tensors
我们可以把 Tensor 放到 GPU 上来加速计算,而 PyTorch 为此提供了很方便的方式:
|
|
如上面的例子所示,我们可以在创建 Tensor 对象时指定 device 参数来指明 Tensor 创建的位置,也可以再创建了之后使用 .to() 方法来进行双向的迁移:既可以从 CPU 到 GPU(例子中的 x.to(device),也可以从 GPU 到 CPU(z.to("cpu", torch.double))。对比 TensorFlow :
|
|
就我所知,TensorFlow 只能在创建的时候指明位置,而无法像 PyTorch 这般灵活的迁移。
Autograd
自动求导已经是深度学习框架的必备了,首先,我们可以通过设置 Tensor 的 .requires_grad 参数来指明 Tensor 是否参与梯度运算,这和 TensorFlow 中的 trainable 是类似的;接着我们通过对某个对象(比如 loss )函数进行 .backward() 操作,来进行反向梯度的计算,并且能够通过计算链上对象的 .grad 属性来获取到对应的梯度。拿官方的例子做一个简单的说明:
|
|
梯度计算的过程如下:
$ o =\frac{1}{4}\sum_i z_i$
$ z_i = 3(x_i+2)^2 $
$ z_i\bigr\rvert_{x_i=1} = 27 $
$ \frac{\partial o}{\partial x_i} = \frac{3}{2}(x_i+2) $
$ \frac{\partial o}{\partial x_i}\bigr\rvert_{x_i=1} = \frac{9}{2} = 4.5 $
因此,x.grad 的值在我们对 out 进行 out.backward() 操作之后,结果为:
tensor([[ 4.5000, 4.5000],
[ 4.5000, 4.5000]])
backward() 函数的原型如下:
torch.autograd.backward(tensors, grad_tensors=None, retain_graph=None, create_graph=False, grad_variables=None)
其中第一个参数用来声明梯度的权重。如果目标函数是一个标量,则可以不传这个参数,比如上面的 out.backward() ,则默认权重为 1 ,如果我们改成 out.backward(torch.tensor(2,dtype=torch.float)) 则各个 \(x\_i\) 梯度就会变成原来的 2 倍,利用权重我们就可以来控制各个参数的更新速度。
Build A Neural Network
学框架不搭一个 NN 来玩 MNIST 或者 CIFAR 怎么能叫入门呢?来来来,走一波~
|
|
诶,有没有觉得有点像 Keras,不过高级的 API 用起来就是爽,不用写那么多代码2333,接下来就是定义 loss 和进行 backprop 来更新参数:
|
|
和 TensorFlow 的区别就在于,TensorFlow 的话只要我们用 session.run(optimizer) 就可以完成梯度的更新操作,而 PyTorch 则需要:
- Optimizer 清零梯度
- 对 loss 进行手动的
backward()操作 - Optimizer 更新
.step()
三步走,记住了没有!
还有一点比较方便的是,和 Tensor 类似,我们可以使用 Net.to() 来把模型部署到 GPU 上:
|
|
但是!记得要把你的输入也放到 GPU 上,不然就会报错:
|
|
Summary
初体验如果要给个评价的话,我觉得是比 TensorFlow 好很多(毕竟 TensorFlow 一上来的 Session、静态图会让人有点摸不着头脑)。但用什么框架其实都无所谓,就和语言一样,虽然争来争去,但每种语言都有自己的用武之地,以前我还会和室友争辩 Java 和 Python 谁才是最好的语言,现在就不会了(因为我也觉得 Python 好写一点,逃)。框架更不用说,TensorFlow 有他应用的工业场景(希望能早日接触到),PyTorch 现在看来也许更适合需要快速实现原型的科研人员。而我们能做的,就是多接触,横向比较着来看而不要因为自己擅长而蒙蔽了双眼,多学一技压身,总是没错的。