从头学pytorch(二) 自动求梯度

PyTorch提供的autograd包可以根据输⼊和前向传播过程⾃动构建计算图,并执⾏反向传播。html

Tensor

Tensor的几个重要属性或方法python

  • .requires_grad 设为true的话,tensor将开始追踪在其上的全部操做
  • .backward()完成梯度计算
  • .grad属性 计算的梯度累积到.grad属性
  • .detach()解除对一个tensor上操做的追踪,或者用with torch.no_grad()将不想被追踪的操做代码块包裹起来.
  • .grad_fn属性 该属性即建立Tensor的Function类的类型,即该Tensor是由什么运算得来的

几个例子具体地解释一下:函数

import torch
x = torch.ones(2, 2, requires_grad=True)
print(x)
print(x.grad_fn)

y = x+2
print(y)
print(y.grad_fn)

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

输出ui

tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
None
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward>)
<AddBackward object at 0x0000018752434B70>


tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward>) tensor(27., grad_fn=<MeanBackward1>)

y由加法获得,因此y.grad_fn= ,x直接建立,其x.grad_fn=None. x这种直接建立的又称为叶子节点. spa

print(x.is_leaf, y.is_leaf) # True False

能够用.requires_grad_()来用in-place的方式改变requires_grad属性.code

a = torch.randn(2, 2) # 缺失状况下默认 requires_grad = False
a = ((a * 3) / (a - 1))
print(a.requires_grad) # False
a.requires_grad_(True)
print(a.requires_grad) # True
b = (a * a).sum()
print(b.grad_fn)

输出htm

False
True
<SumBackward0 object at 0x0000018752434D30>

梯度

所计算的梯度都是结果变量关于建立变量的梯度。
好比对:blog

x = torch.ones(2, 2, requires_grad=True)
print(x)
print(x.grad_fn)

y = x+2
print(y)
print(y.grad_fn)

z = y*3
z.backward(torch.ones_like(z))
print(y.grad) #None  
print(x.grad)

输出element

None
tensor([[3., 3.],
        [3., 3.]])

上述代码至关于建立了一个动态图,其中x是咱们建立的变量,y和z都是由于x的改变会改变的结果变量. 因此在这个动态图里可以求的梯度只有\(\frac{\partial{z}}{\partial{x}}\),\(\frac{\partial{y}}{\partial{x}}\)文档

为何l.backward(gradient)须要传入一个和l一样形状的gradient?
对于l.backward()而言,当l是标量时,能够不传参,至关于l.backward(torch.tensor(1.))
当l不是标量时,须要传入一个和l同shape的gradient。

假设 x 通过一番计算获得 y,那么 y.backward(w) 求的不是 y 对 x 的导数,而是 l = torch.sum(y*w) 对 x 的导数。w 能够视为 y 的各份量的权重,也能够视为遥远的损失函数 l 对 y 的偏导数(这正是函数说明文档的含义)。特别地,若 y 为标量,w 取默认值 1.0,才是按照咱们一般理解的那样,求 y 对 x 的导数

简单地说就是,张量对张量无法求导,因此咱们须要人为地定义一个w,把一个非标量的Tensor经过torch.sum(y*w)的形式转换成标量。咱们本身定义的这个w的不一样,固然最后获得的梯度就不一样.一般定义为全1.也就是认为Tensor y中的每个变量的重要性是等同的.

另外一个角度的理解就是,y是一个tensor,是一个向量,有N个标量,这每个标量都与x有关。对这N个标量咱们须要赋以不一样的权重,以显示y中每个标量受到x影响的程度.

好比对

import torch
x = torch.ones(2, 2, requires_grad=True)
print(x)
print(x.grad_fn)

y = x+2
print(y)
print(y.grad_fn)

z = y*3
print(z.shape)
w1=torch.Tensor([[1,2],[1,2]])
z.backward([w1])
print(x.grad)

x.grad.data.zero_()
w2=torch.Tensor([[1,1],[1,1]])
z.backward([w2])
print(x.grad)

输出

tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
None
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward>)
<AddBackward object at 0x00000187524A6828>
torch.Size([2, 2])
tensor([[3., 6.],
        [3., 6.]])
tensor([[3., 3.],
        [3., 3.]])

对w1和w2而言,z.backward()之后x.grad是不一样的。
注意:梯度是累加的,因此第二次计算以前咱们作了清零的操做:x.grad.data.zero_()

能够参考:
https://zhuanlan.zhihu.com/p/29923090
http://www.javashuo.com/article/p-wgrlmkdc-kd.html

再来看看中断梯度追踪的例子:

x = torch.tensor(1.0, requires_grad=True)
y1 = x ** 2 
with torch.no_grad():
    y2 = x ** 3
y3 = y1 + y2
    
print(x.requires_grad)
print(y1, y1.requires_grad) # True
print(y2, y2.requires_grad) # False
print(y3, y3.requires_grad) # True

输出:

True
tensor(1., grad_fn=<PowBackward0>) True
tensor(1.) False
tensor(2., grad_fn=<ThAddBackward>) True

反向传播,求梯度

y3.backward()
print(x.grad)

输出:

tensor(2.)

为何是2呢?$ y_3 = y_1 + y_2 = x^2 + x^3$,当 \(x=1\)\(\frac {dy_3} {dx}\) 不该该是5吗?事实上,因为 \(y_2\) 的定义是被torch.no_grad():包裹的,因此与 \(y_2\) 有关的梯度是不会回传的,只有与 \(y_1\) 有关的梯度才会回传,即 \(x^2\)\(x\) 的梯度。

上面提到,y2.requires_grad=False,因此不能调用 y2.backward(),会报错:

RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

此外,若是咱们想要修改tensor的数值,可是又不但愿被autograd记录(即不会影响反向传播),那么我么能够对tensor.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)

输出:

tensor([1.])
False
tensor([100.], requires_grad=True)
tensor([2.])
相关文章
相关标签/搜索