PyTorch基础教程学习笔记(二):自动微分

autograd 包是 PyTorch 中所有神经网络的核心,为 Tensors 上的所有操作提供自动微分。它是一个由运行定义的框架,这意味着以代码运行方式定义你的后向传播,并且每次迭代都可以不同

 TENSOR

torch.Tensor 是包的核心类。

  • 如果将其属性 .requires_grad 设置为 True,则会开始跟踪针对 tensor 的所有操作。
  • 完成计算后,可以调用 .backward() 来自动计算所有梯度。该张量的梯度将累积到 .grad 属性中。
  • 要停止 tensor 历史记录的跟踪,您可以调用 .detach(),它将其与计算历史记录分离,并防止将来的计算被跟踪。
  • 要停止跟踪历史记录(和使用内存),还可以将代码块使用 with torch.no_grad(): 包装起来。在评估模型时,这是特别有用的,因为模型在训练阶段具有 requires_grad = True 的可训练参数有利于调参,但在评估阶段我们不需要梯度。
属性 说明
.requires_grad = True 开始跟踪针对 tensor 的所有操作
.backward() 自动计算所有梯度
.grad 张量的梯度将累积到其中
.deatch() 停止tensor历史记录的追踪
with.torch.no_grad() 停止跟踪历史记录和使用内存。在评估模型时,这是特别有用,因为模型在训练阶段具有 requires_grad = True 的可训练参数有利于调参,但在评估阶段我们不需要梯度。

Function 类

还有一个类对于 autograd 实现非常重要,那就是 Function。

Tensor 和 Function 互相连接并构建一个非循环图,它保存整个完整的计算过程的历史信息。每个张量都有一个 .grad_fn 属性保存着创建了张量的 Function 的引用,(如果用户自己创建张量,则grad_fn 是 None )。

如果你想计算导数,你可以调用 Tensor.backward()。如果 Tensor 是标量(即它包含一个元素数据),则backward()不需要指定任何参数,但是如果它有更多元素,则需要指定一个gradient 参数来指定张量的形状。

import torch
x = torch.ones(2, 2, requires_grad=True)#创建一个张量,设置 requires_grad=True 来跟踪与它相关的计算
print(x)

y = x + 2 #针对张量做一个操作
print(y)

输出:
tensor([[3., 3.],
        [3., 3.]], grad_fn=)  #y 作为操作的结果被创建,所以它有 grad_fn


print(y.grad_fn)
输出:

z = y * y * 3 #针对 y 做更多的操作
out = z.mean()

print(z, out)
输出:
tensor([[27., 27.],
        [27., 27.]], grad_fn=) tensor(27., grad_fn=)

.requires_grad_( ... ) 会改变张量的 requires_grad 标记。如果没有提供相应的参数, 输入的标记默认为 False 。

a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)

输出:
False
True

梯度

现在后向传播,因为上面的输出包含了一个标量,b.backward() 等同于b.backward(torch.tensor(1.))。

# 梯度
x = torch.ones(2, 2, requires_grad=True)
y = x + 2
z = y * y *3
out = z.mean()

#因为输出包含了一个标量,out.backward()等同于out.backward(torch.tensor(1.))
out.backward()
#打印梯度 d(out)/dx
print(x.grad)
#输出
# tensor([[4.5000, 4.5000],
#         [4.5000, 4.5000]])

PyTorch基础教程学习笔记(二):自动微分_第1张图片

 

PyTorch基础教程学习笔记(二):自动微分_第2张图片

 

PyTorch基础教程学习笔记(二):自动微分_第3张图片

  • 雅可比向量积的例子:
x = torch.randn(3, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
    y = y * 2

print(y)

输出:
tensor([  224.8858,   -33.0537, -1880.7501]

y.data.norm()指的是y的范数,举一个例子
假设x是[1.,2.,3.],则y是[2.,4.,6.],那么y.data.norm()为
y.data.norm()=(y_1^2+y_2^2+y_3^2)^{1/2}=(4+16+36)^{1/2}=7.48

在这种情况下,y 不再是一个标量。torch.autograd 不能够直接计算整个雅可比,但是如果我们只想要雅可比向量积,只需要简单的传递向量给 backward 作为参数。

 

v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)

print(x.grad)

输出:
tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])

如果不使用v这个向量,直接使用y.backward()就会报错,即:如果在这里运行y.backward(),会得到这样一个报错:RuntimeError: grad can be implicitly created only for scalar outputs。另外,我想说的是,这里y不是一个固定的运算,因为while y.data.norm() < 1000可能需要3次运算,也可能需要5次运算。这里v相当于各个标量(共有3个标量,构成一个向量)的系数,如果要直接对所有标量进行backward,全部改成1.0即可。

y.backward(v) 的含义是:先计算 l = torch.sum(y * v),然后求 l 对(能够影响到 y 的)所有变量 x 的导数。这里,y 和 v 是同型 Tensor。也就是说,可以理解成先按照 v 对 y 的各个分量加权,加权求和之后得到真正的 loss,再计算这个 loss 对于所有相关变量的导数。这样一来,所有求导操作都是求 Scalar 关于 Tensor 的导数,统一了起来,不存在 Tensor 对 Tensor 求导了.

详情见:

  • Pytorch问题:autograd与backward()及相关参数的理解
  • PyTorch 的 backward 为什么有一个 grad_variables 参数?
  • pytorch中backward()函数详解

可以通过将代码包裹在 with torch.no_grad(),来停止对 从跟踪历史中的 .requires_grad=True 的张量 自动求导

    >>> import torch as t                                                       
     
    >>> x = t.ones(2,2, requires_grad=True)                                     
     
    >>> with t.no_grad(): 
    ...:     y = x + 2 
    ...:                                                                         
     
    >>> y.grad_fn
    >>> (这里啥都没有,因为此时y没有自动求导函数)                                             
     
    >>> z = x + 2
    >>> z.grad_fn                                                               
    Out[6]: 

requires_grad 的含义及标志位说明

在tensor.py中backward是这样定义的:
def backward(self, gradient=None, retain_graph=None, create_graph=False)
注:这里面调用的也还是autograd中的backward。
在autograd.__init__.py中是这样定义的:
def backward(tensors, grad_tensors=None, retain_graph=None, create_graph=False, grad_variables=None)

detach

如果 x 为中间输出,y = x.detach 表示创建一个与 x 相同,但requires_grad==False 的tensor, 实际上就是把y 以前的计算图 grad_fn 都消除,y自然也就成了叶节点。原先反向传播时,回传到x时还会继续,而现在回到y处后,就结束了,不继续回传求导了。另外值得注意, x和y指向同一个Tensor ,即 x.data 。而detach_() 表示不创建新变量,而是直接修改 x 本身。

retain_graph

如果retain_graph=true,就会每次运行时重新生成图。也就是说,每次 backward() 时,默认会把整个计算图free掉。一般情况下是每次迭代,只需一次 forward() 和一次 backward() , 前向运算forward() 和反向传播backward()是成对存在的,一般一次backward()也是够用的。但是不排除,由于自定义loss等的复杂性,需要一次forward(),多个不同loss的backward()来累积同一个网络的grad来更新参数。于是,若在当前backward()后,不执行forward() 而可以执行另一个backward(),需要在当前backward()时,指定保留计算图,即backward(retain_graph)。

create_graph

为True时,会构造一个导数的图,用来计算出更高阶导数结果。

这里面,我没找到一个create_graph的详细数学上能明确的解释,只好把大概的意思描述一下,比如对前面第一个例子

y.backward(v)
print(x.grad)
 
y.backward(v) # --- error ----
print(x.grad)

你会在第二个backward()产生严重报错:

Exception has occurred: 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...

如果要避免报错,你只能这样用:

y.backward(v, create_graph=True)
print(x.grad)
 
y.backward(v)
print(x.grad)

实际内置了retain_graph = create_graph。实现高阶层数:

import torch as t
from torch.autograd import Variable as V
 
# ----key function----
#grad_x =t.autograd.grad(y, x, create_graph=True)
#grad_grad_x = t.autograd.grad(grad_x[0],x)
 
x = V(t.Tensor([4]), requires_grad=True)
y = x ** 2 
 
grad_x = t.autograd.grad(y, x, create_graph=True)
print(grad_x)  # dy/dx = 2*x = 8
 
grad_2x = t.autograd.grad(grad_x[0],x)
print(grad_2x) # d(2*x)/dx = 2

 

你可能感兴趣的:(pytorch学习笔记)