PyTorch学习(二)
- 前言
- 一、autograd自动求梯度
- 二、使用步骤
- 1.示例一
- 2.示例二
前言
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)