自动求导
针对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.)
当 x = 1.5时,
.reqires_grad
设置为 True
, 它将开始追踪(trace)在其上的所有操作(也就是链式法则)。
计算完成后 .backward()
来完成梯度计算。这个 Tensor
的梯度是累计到 .grad
属性中的。
如果不想进行梯度计算的追踪, 可以调用 .detach()
就可以将其从追踪记录中分离防止追踪。
使用 with torch.no_grad()
将不想被追中的包裹起来更方便,这在进行评估的时候很有用,
不需要进行梯度追踪了。
另外, Function
是另外一个很重要的类, Tensor
与 Function
结合构成了
有向无环图 (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_fn
是 None
, 但是 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]])
我们令out
为 , 因为
所以
那么,我们再进行一次求导呢? 直接报错了,只能做一次,反向传播,就会把所有求梯度的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 进行求偏导。 也就是成了一个矩阵,这个矩阵叫做 雅克比矩阵。
显然,每一行就是 y 的一个分量的所有偏导。
同样不如过是标量也可以表示成一个雅克比矩阵, 的梯度矩阵:
接下来根据链式法则,就可以有 对
的求导:
所以反向传播的梯度计算,实际上就是每一个分量梯度计算的乘积。
注意: 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)