自动求导

2020-04-16  本文已影响0人  潘旭

针对pytorch中自动求导机制进行介绍。

import torch
x = torch.tensor(1.5, requires_grad=True)
y = 2 * x * x
y
tensor(4.5000, grad_fn=<MulBackward0>)
y.backward()
x.grad
tensor(6.)

y = 2 * x^2, dy/dx = 4x 当 x = 1.5时, dy/dx = 4 * x = 4*1.5 = 6

.reqires_grad 设置为 True, 它将开始追踪(trace)在其上的所有操作(也就是链式法则)。
计算完成后 .backward() 来完成梯度计算。这个 Tensor 的梯度是累计到 .grad 属性中的。

如果不想进行梯度计算的追踪, 可以调用 .detach() 就可以将其从追踪记录中分离防止追踪。
使用 with torch.no_grad() 将不想被追中的包裹起来更方便,这在进行评估的时候很有用,
不需要进行梯度追踪了。

另外, Function 是另外一个很重要的类, TensorFunction 结合构成了
有向无环图 (DAG). 每一个 Tensor 都有 .grad_fn 属性, 该属性就是创建该
Tensor 的 Fucntion, 也就是说 该 Tensor 是不是通过某些运算得到的,如果是
grad_fun 返回一个与这些运算相关的对象。

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

x.grad_fn 是 None.

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

因为 x 是直接创建的,所以 grad_fnNone, 但是 y 是通过运算创建的,所以会有一个 grad_fn 来记录是通过 "加法" 创建的 y,这样才能够通过梯度反向传播回去。

这种 x 被称作叶子节点,y 不是叶子节点。

print(x.is_leaf, y.is_leaf)
True False
z = y * y * 3
out = z.mean()
print(z, out)
tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)

梯度

out 是个变量, 可以直接进行求导:

out.backward()

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

我们令outo , 因为
o=\frac14\sum_{i=1}^4z_i=\frac14\sum_{i=1}^43(x_i+2)^2
所以
\frac{\partial{o}}{\partial{x_i}}\bigr\rvert_{x_i=1}=\frac{9}{2}=4.5

那么,我们再进行一次求导呢? 直接报错了,只能做一次,反向传播,就会把所有求梯度的buffer释放掉。

out.backward() 
---------------------------------------------------------------------------

RuntimeError                              Traceback (most recent call last)

<ipython-input-38-821713d11f88> in <module>
----> 1 out.backward()


~/anaconda3/lib/python3.7/site-packages/torch/tensor.py in backward(self, gradient, retain_graph, create_graph)
    193                 products. Defaults to ``False``.
    194         """
--> 195         torch.autograd.backward(self, gradient, retain_graph, create_graph)
    196 
    197     def register_hook(self, hook):


~/anaconda3/lib/python3.7/site-packages/torch/autograd/__init__.py in backward(tensors, grad_tensors, retain_graph, create_graph, grad_variables)
     97     Variable._execution_engine.run_backward(
     98         tensors, grad_tensors, retain_graph, create_graph,
---> 99         allow_unreachable=True)  # allow_unreachable flag
    100 
    101 


RuntimeError: Trying to backward through the graph a second time, but the buffers have already been freed. Specify retain_graph=True when calling backward the first time.
print(x.grad)

上面的 out 是标量,而如果是向量呢?比如中间变量 y 就是一个向量。其实, 就是每一个 y 的分量对所有 x 进行求偏导。 也就是成了一个矩阵,这个矩阵叫做 雅克比矩阵。

J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)

显然,每一行就是 y 的一个分量的所有偏导。

同样不如过是标量也可以表示成一个雅克比矩阵, l=g\left(\vec{y}\right) 的梯度矩阵:

v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)

接下来根据链式法则,就可以有 l\vec{x} 的求导:

vJ = \left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right) \left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right) = \left(\begin{array}{ccc}\frac{\partial l}{\partial x_{1}} & \cdots & \frac{\partial l}{\partial x_{n}}\end{array}\right)

所以反向传播的梯度计算,实际上就是每一个分量梯度计算的乘积。

注意: grad 在反向传播过程中是累加的, 所以每一次反向传播梯度都会加上之前的梯度,(前面已经推导过), 所以在进行反向传播之前先把梯度清零。

out2 = x.sum()
out2.backward()
print(x.grad)

再进行一次反向传播

out3 = x.sum()
x.grad.data.zero_()
out3.backward()
print(x.grad)
print(x)

梯度累加,如果还没有看清楚, 那就重新看下完整的例子:

y = torch.tensor([1.], requires_grad=True)
print("y", y)
z = y * y
z.backward()
print(y.grad)
z = y * y
z.backward()
print(y.grad)

问题: 在进行 batch 训练的时候,更新权重是如何做的?

最后一个技巧,如果我们想修改 tensor x的值,但是有不想被 auto grad 记录下来,那么就用 x.data.

x = torch.ones(1,requires_grad=True)

print(x.data) # 还是一个tensor
print(x.data.requires_grad) # 但是已经是独立于计算图之外

y = 2 * x
x.data *= 100 # 只改变了值,不会记录在计算图,所以不会影响梯度传播

y.backward()
print(x) # 更改data的值也会影响tensor的值
print(x.grad)

上一篇 下一篇

猜你喜欢

热点阅读