PyTorch在autograd模块中实现了计算图的相关功能,autograd中的核心数据结构是Variable。从v0.4版本起,Variable和Tensor合并。我们可以认为需要求导(requires_grad)的tensor即Variable. autograd记录对tensor的操作记录用来构建计算图。
Variable提供了大部分tensor支持的函数,但其不支持部分inplace
函数,因这些函数会修改tensor自身,而在反向传播中,variable需要缓存原来的tensor来计算反向传播梯度。如果想要计算各个Variable的梯度,只需调用根节点variable的backward
方法,autograd会自动沿着计算图反向传播,计算每一个叶子节点的梯度。
variable.backward(gradient=None, retain_graph=None, create_graph=None)
主要有如下参数:
y.backward()
,grad_variables相当于链式法则dzdx=dzdy×dydxdzdx=dzdy×dydx中的dzdydzdy。grad_variables也可以是tensor或序列。backward of backward
实现求高阶导数。上述描述可能比较抽象,如果没有看懂,不用着急,会在本节后半部分详细介绍,下面先看几个例子。
Function
,每一个变量在图中的位置可通过其grad_fn
属性在图中的位置推测得到。在反向传播过程中,autograd沿着这个图从当前变量(根节点zz)溯源,可以利用链式求导法则计算所有叶子节点的梯度。每一个前向传播操作的函数都有与之对应的反向传播函数用来计算输入的各个variable的梯度,这些函数的函数名通常以Backward
结尾。下面结合代码学习autograd的实现细节。变量的requires_grad
属性默认为False,如果某一个节点requires_grad被设置为True,那么所有依赖它的节点requires_grad
都是True。这其实很好理解,对于x→y→zx→y→z,x.requires_grad = True,当需要计算∂z∂x∂z∂x时,根据链式法则,∂z/∂x=∂z∂y∂y∂x∂z∂x=∂z∂y∂y∂x,自然也需要求∂z∂y∂z∂y,所以y.requires_grad会被自动标为True.
有些时候我们可能不希望autograd对tensor求导。认为求导需要缓存许多中间结构,增加额外的内存/显存开销,那么我们可以关闭自动求导。对于不需要反向传播的情景(如inference,即测试推理时),关闭自动求导可实现一定程度的速度提升,并节省约一半显存,因其不需要分配空间计算梯度。
with torch.no_grad(): # 运行的代码不会自动求导
如果我们想要修改tensor的数值,但是又不希望被autograd记录,那么我么可以对tensor.data进行操作
a = t.ones(3,4,requires_grad=True) b = t.ones(3,4,requires_grad=True) c = a * b a.data # 还是一个tensor a.data.requires_grad # 但是已经是独立于计算图之外 d = a.data.sigmoid_() # sigmoid_ 是个inplace操作,会修改a自身的值 d.requires_grad # 如果我们希望对tensor操作,但是又不希望被记录, 可以使用tensor.data 或者tensor.detach() # 近似于 tensor=a.data, 但是如果tensor被修改,backward可能会报错 tensor = a.detach() tensor.requires_grad # 统计tensor的一些指标,不希望被记录 mean = tensor.mean() std = tensor.std() maximum = tensor.max() tensor[0]=1 # 下面会报错: RuntimeError: one of the variables needed for gradient # computation has been modified by an inplace operation # 因为 c=a*b, b的梯度取决于a,现在修改了tensor,其实也就是修改了a,梯度不再准确 # c.sum().backward()
在PyTorch中计算图的特点可总结如下:
Function
。grad_fn
为None。叶子节点中需要求导的variable,具有AccumulateGrad
标识,因其梯度是累加的。requires_grad
属性默认为False,如果某一个节点requires_grad被设置为True,那么所有依赖它的节点requires_grad
都为True。volatile
属性默认为False,如果某一个variable的volatile
属性被设为True,那么所有依赖它的节点volatile
属性都为True。volatile属性为True的节点不会求导,volatile的优先级比requires_grad
高。retain_graph
=True来保存这些缓存。autograd.grad
或hook
技术获取非叶子节点的值。backward
的参数grad_variables
可以看成链式求导的中间结果,如果是标量,可以省略,默认为1这些知识不懂大多数情况下也不会影响对pytorch的使用,但是掌握这些知识有助于更好的理解pytorch,并有效的避开很多陷阱
目前绝大多数函数都可以使用autograd
实现反向求导,但如果需要自己写一个复杂的函数,不支持自动反向求导怎么办? 写一个Function
,实现它的前向传播和反向传播代码,Function
对应于计算图中的矩形, 它接收参数,计算并返回结果。下面给出一个例子。
class Mul(Function): @staticmethod def forward(ctx, w, x, b, x_reuqires_grad = True): ctx.x_requires_grad = x_requires_grad ctx.save_for_backward(w, x) output = w * x + b return output @staticmethod def backword(ctx, grad_output): w, x = ctx.save_tensors grad_w = grad_output * x if ctx.x_requires_grad: grad_x = grad_output * w else: grad_x None grad_b = grad_output * 1 return grad_w, grad_x, grad_b, None
__init__
,forward和backward函数都是静态方法grad_variables
from torch.autograd import Function class MultiplyAdd(Function): @staticmethod def forward(ctx, w, x, b): ctx.save_for_backward(w,x) output = w * x + b return output @staticmethod def backward(ctx, grad_output): w,x = ctx.saved_tensors grad_w = grad_output * x grad_x = grad_output * w grad_b = grad_output * 1 return grad_w, grad_x, grad_b
之所以forward函数的输入是tensor,而backward函数的输入是variable,是为了实现高阶求导。backward函数的输入输出虽然是variable,但在实际使用时autograd.Function会将输入variable提取为tensor,并将计算结果的tensor封装成variable返回。在backward函数中,之所以也要对variable进行操作,是为了能够计算梯度的梯度(backward of backward)。下面举例说明,有关torch.autograd.grad的更详细使用请参照文档。
原文:https://www.cnblogs.com/ziwh666/p/12355723.html