【PyTorch学习】二、自动求梯度(Automatic Gradient)


前言

torch.autograd是 PyTorch 的自动差分引擎,可为神经网络训练提供支持。

神经网络就是寻求一个拟合函数,但是因为参数过多,所以不得不借助每一点的梯度来一点一点的接近最佳的loss值,PyTorch 拥有动态的计算图,存储记忆对向量的每一个函数操作,最后通过反向传播来计算梯度,这可以说是pytorch的核心。

训练 神经网络 (Neural Network,NN)分为两个步骤:

正向传播:在正向传播中,NN 对正确的输出进行最佳猜测。 它通过其每个函数运行输入数据以进行猜测。

反向传播:在反向传播中,NN 根据其猜测中的误差调整其参数。 它通过从输出向后遍历,收集有关函数参数(梯度)的误差导数并使用梯度下降来优化参数来实现。


斯人若彩虹,遇上方知有。

一、autograd自动求梯度

在深度学习中,我们经常需要对函数求梯度(gradient)。PyTorch提供的autograd包能够根据输入和前向传播过程自动构建计算图,并执行反向传播。

深度学习模型的训练就是不断更新权值,权值的更新需要求解梯度。

Pytorch提供自动求导系统,我们不需要手动计算梯度,只需要搭建好前向传播的计算图,然后根据Pytorch中的autograd方法就可以得到所有张量的梯度。 PyTorch中,所有神经网络的核心是autograd包。autograd包为张量上的所有操作提供了自动求导机制。它是一个在运行时定义(define-by-run)的框架,这意味着反向传播是根据代码如何运行来决定的,并且每次迭代可以是不同的。

Variable和Tensor
Variable是 torch.autograd中的数据类型,主要用于封装 Tensor,进行自动求导。Pytorch 0.4.0版开始,Variable并入Tensor。

  • data : 被包装的Tensor
  • grad : data的梯度
  • grad_fn : 创建 Tensor的 Function,是自动求导的关键
  • requires_grad:指示是否需要梯度
  • is_leaf : 指示是否是叶子结点

Tensor是PyTorch实现多维数组计算和自动微分的关键数据结构。

  • dtype:张量的数据类型,如torch.FloatTensor,torch.cuda.FloatTensor
  • shape:张量的形状,如(64,3,224,224)
  • device:张量所在设备,GPU/CPU

Function类
Tensor和Function互相结合就可以构建一个记录有整个计算过程的有向无环图(Directed Acyclic Graph,DAG)。每个Tensor都有一个.grad_fn属性,该属性即创建该Tensor的Function。判断该Tensor是不是通过某些运算得到的,若是,则grad_fn返回一个与这些运算相关的对象,否则是None。

DAG的节点是Function对象,边表示数据依赖,从输出指向输入。 每当对Tensor施加一个运算的时候,就会产生一个Function对象,它产生运算的结果,记录运算的发生,并且记录运算的输入。Tensor使用.grad_fn属性记录这个计算图的入口。反向传播过程中,autograd引擎会按照逆序,通过Function的backward依次计算梯度。

自动求取梯度

torch.autograd.backward(tensors,
                    grad_tensors=None,
                    retain_grad=None,
                    create_graph=False)
  • tensors: 用于求导的张量,如loss
  • retain_graph : 保存计算图;由于pytorch采用动态图机制,在每一次反向传播结束之后,计算图都会释放掉。如果想继续使用计算图,就需要设置参数retain_graph为True
  • create_graph : 创建导数计算图,用于高阶求导,例如二阶导数、三阶导数等
  • grad_tensors:多梯度权重;当有多个loss需要去计算梯度的时候,就要设计各个loss之间的权重比例

计算并返回outputs对inputs的梯度

torch.autograd.grad(outputs,
                inputs,
                grad_outputs=None,
                retain_graph=None,
                create_graph=False)
  • outputs:用于求导的张量,如loss
  • inputs:需要梯度的张量,如w
  • create_graph:创建导数计算图,用于高阶求导
  • retain_graph:保存计算图
  • grad_outputs:多梯度权重

注意事项

(1)梯度不自动清零,如果不清零梯度会累加,所以需要在每次梯度后人为清零。
(2)依赖于叶子结点的结点,requires_grad默认为True。
(3)叶子结点不可执行in-place,因为其他节点在计算梯度时需要用到叶子节点,所以叶子地址中的值不得改变否则会是其他节点求梯度时出错。所以叶子节点不能进行原位计算。
(4)注意在y.backward()时,如果y是标量量,则不需要为backward()传⼊入任何参数;否则,需要传⼊一个与y同形的Tensor。

二、使用步骤

1.示例一

from torch.autograd import Variable, Function
import torch

# 生成Variable
x = Variable(torch.ones(2, 2), requires_grad=True) # requires_grad表示在backward是否计算其梯度
print(x)

print('-----------------------')

# 查看Variable的属性.data, .grad, .creator
print(x.data)    # Variable保存的值
print(x.grad)    # 由于目前还未进行.backward()运算,因此不存在梯度
# print(x.creator)  # 对于手动申明的Variable,不存在creator,即在计算图中,该Variable不存在父节点
# creator属性名称已经改为grad_fn
print(x.grad_fn)  # 可获取y的已创建Function属性的属性Variable

print('-----------------------')

# Variable进行运算
y = x + 2
print(y)
# print(y.creator) # y存在x这个父节点,并且通过'+'这个Function进行连接,因此y.creator是运算+
print(x.grad_fn)

2.示例二

import torch

#创建一个`Tensor`并设置`requires_grad=True`
x = torch.ones(2, 2, requires_grad=True)
print(x)
print(x.grad_fn)  # None

# 再做一下运算操作:
y = x + 2
print(y)  # grad_fn=<AddBackward0>
print(y.grad_fn)

#像x这种直接创建的称为叶子节点,叶子节点对应的`grad_fn`是`None`。
print(x.is_leaf, y.is_leaf) # True False

#再来点复杂度运算操作:
z = y * y * 3  # 27
out = z.mean()
print(z, out)  # grad_fn=<MulBackward0>

#通过`.requires_grad_()`来用in-place的方式改变`requires_grad`属性:
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)


torch.manual_seed(10)  #用于设置随机数
w = torch.tensor([1.], requires_grad=True)    #创建叶子张量,并设定requires_grad为True,因为需要计算梯度;
x = torch.tensor([2.], requires_grad=True)    #创建叶子张量,并设定requires_grad为True,因为需要计算梯度;
a = torch.add(w, x)    #执行运算并搭建动态计算图
b = torch.add(w, 1)
y = torch.mul(a, b)
y.backward(retain_graph=True)
print(w.grad)   #输出为tensor([5.])


x = torch.tensor([1.0, 2.0, 3.0, 4.0], requires_grad=True)
y = 2 * x
z = y.view(2, 2)
print(z)

#现在 `z` 不是一个标量,所以在调用`backward`时需要传入一个和`z`同形的权重向量进行加权求和得到一个标量。
v = torch.tensor([[1.0, 0.1], [0.01, 0.001]], dtype=torch.float)
z.backward(v)
print(x.grad)

#再来看看中断梯度追踪的例子:
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

#可以看到,上面的`y2`是没有`grad_fn`而且`y2.requires_grad=False`的,而`y3`是有`grad_fn`的。如果我们将`y3`对`x`求梯度的话会是多少呢?
y3.backward()
print(x.grad)

#此外,如果我们想要修改`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)
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值