这篇文章主要介绍了PyTorch如何自动计算梯度,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

1. 概念

Tensor是这个pytorch的自动求导部分的核心类,如果将其属性.requires_grad=True,它将开始追踪(track) 在该tensor上的所有操作,从而实现利用链式法则进行的梯度传播。完成计算后,可以调用.backward()来完成所有梯度计算。此Tensor的梯度将累积到.grad属性中。

如果不想要被继续对tensor进行追踪,可以调用.detach()将其从追踪记录中分离出来,接下来的梯度就传不过去了。此外,还可以用with torch.no_grad()将不想被追踪的操作代码块包裹起来,这种方法在评估模型的时候很常用,因为此时并不需要继续对梯度进行计算。

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

2. 具体实现2.1. 创建可自动求导的tensor

首先我们创建一个tensor,同时设置requires_grad=True:

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

输出:

tensor([[1., 1.],

[1., 1.]], requires_grad=True)

None

'''

像x这种直接创建的tensor 称为叶子节点,叶子节点对应的grad_fn是None。如果进行一次运算操作:

y=x+1print(y)print(y.grad_fn)'''tensor([[2.,2.],[2.,2.]],grad_fn=<AddBackward>)<AddBackwardobjectat0x1100477b8>'''

而y是通过一个加法操作创建的,所以它有一个为操作的grad_fn。

尝试进行更复杂的操作:

z=y**2out=z.mean()print(z,out)'''tensor([[4.,4.],[4.,4.]],grad_fn=<PowBackward0>)tensor(4.,grad_fn=<MeanBackward0>)'''

上面的out是一个标量4,通常对于标量直接使用out.backward()进行求导,不需要指定求导变量,后面进行详细说明。

也可以通过.requires_grad_()改变requires_grad属性:

a=torch.randn(3,2)#缺失情况下默认requires_grad=Falsea=(a**2)print(a.requires_grad)#Falsea.requires_grad_(True)#使用in-place操作,改变属性print(a.requires_grad)#Trueb=(a*a).sum()print(b.grad_fn)'''FalseTrue<SumBackward0objectat0x7fd8c16edd30>'''2.2. 梯度计算

torch.autograd实现梯度求导的链式法则,用来计算一些雅克比矩阵的乘积,即函数的一阶导数的乘积。

注意:grad在反向传播过程中是累加的(accumulated),每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零x.grad.data.zero_()。

x=torch.ones(2,2,requires_grad=True)y=x+1z=y**2out=z.mean()print(z,out)out.backward()print(x.grad)#注意grad是累加的out2=x.sum()out2.backward()print(out2)print(x.grad)out3=x.sum()x.grad.data.zero_()out3.backward()print(out3)print(x.grad)'''tensor([[4.,4.],[4.,4.]],grad_fn=<PowBackward0>)tensor(4.,grad_fn=<MeanBackward0>)tensor([[1.,1.],[1.,1.]])tensor(4.,grad_fn=<SumBackward0>)tensor([[2.,2.],[2.,2.]])tensor(4.,grad_fn=<SumBackward0>)tensor([[1.,1.],[1.,1.]])'''

Tensor的自动求导对于标量比如上面的out.backward()十分方便,但是当反向传播的对象不是标量时,需要在y.backward()种加入一个与out同形的Tensor,不允许张量对张量求导,只允许标量对张量求导,求导结果是和自变量同形的张量。

这是为了避免向量(甚至更高维张量)对张量求导,而转换成标量对张量求导。

x=torch.tensor([1.0,2.0,3.0,4.0],requires_grad=True)y=2*xz=y.view(2,2)print(z)'''tensor([[2.,4.],[6.,8.]],grad_fn=<ViewBackward>)'''

显然上面的tensor z不是一个标量,所以在调用 z.backward()时需要传入一个和z同形的权重向量进行加权求和得到一个标量。

c=torch.tensor([[1.0,0.1],[0.01,0.001]],dtype=torch.float)z.backward(c)print(x.grad)'''tensor([[2.,4.],[6.,8.]],grad_fn=<ViewBackward>)tensor([2.0000,0.2000,0.0200,0.0020])'''2.3 停止梯度追踪

我们可以使用detach()或者torch.no_grad()语句停止梯度追踪:

x=torch.tensor(1.0,requires_grad=True)y1=x**2withtorch.no_grad():y2=x**3y3=y1+y2print(x.requires_grad)print(y1,y1.requires_grad)#Trueprint(y2,y2.requires_grad)#Falseprint(y3,y3.requires_grad)#True'''Truetensor(1.,grad_fn=<PowBackward0>)Truetensor(1.)Falsetensor(2.,grad_fn=<ThAddBackward>)True'''

我们尝试计算梯度:

y3.backward()print(x.grad)#y2.backward()#这句会报错,因为此时y2.requires_grad=False,,无法调用反向传播'''tensor(2.)'''

这里结果为2,是因为我们没有获得y2的梯度,仅仅是对y1做了一次反向传播,作为最后的梯度输出。

2.4. 修改tensor的值

如果我们想要修改tensor的数值,但是不希望保存在autograd的记录中,require s_grad = False, 即不影响到正在进行的反向传播,那么可以用tensor.data进行操作。但是这种操作需要注意可能会产生一些问题,比如标量为0

x=torch.ones(1,requires_grad=True)print(x.data)#仍然是一个tensorprint(x.data.requires_grad)#但是已经是独立于计算图之外y=2*xx.data*=100#只改变了值,不会记录在计算图,所以不会影响梯度传播y.backward()print(x)#更改data的值也会影响tensor的值print(x.grad)

pytorch0.4以后保留了.data() 但是官方文档建议使用.detach(),因为使用x.detach时,任何in-place变化都会使backward报错,因此.detach()是从梯度计算中排除子图的更安全方法。

如下面的例子:

torch.tensor([1,2,3.],requires_grad=True)out=a.sigmoid()c=out.detach()c.zero_()#in-place为0,tensor([0.,0.,0.])print(out)#modifiedbyc.zero_()!!tensor([0.,0.,0.])out.sum().backward()#Requirestheoriginalvalueofout,butthatwasoverwrittenbyc.zero_()'''RuntimeError:oneofthevariablesneededforgradientcomputationhasbeenmodifiedbyaninplaceoperation'''a=torch.tensor([1,2,3.],requires_grad=True)out=a.sigmoid()c=out.datac.zero_()#tensor([0.,0.,0.])print(out)#outwasmodifiedbyc.zero_()tensor([0.,0.,0.])out.sum().backward()a.grad#这么做不会报错,但是a已经被改变,最后计算的梯度实际是错误的'''tensor([0.,0.,0.])'''

补充:pytorch如何计算导数_Pytorch 自动求梯度(autograd)

深度学习其实就是一个最优化问题,找到最小的loss值,因为自变量过多,想要找到最小值非常困难。所以就出现了很多最优化方法,梯度下降就是一个非常典型的例子。本文针对python的pytorch库中的自动求梯度进行了详细的解释

Tensor

pytorch里面的tensor可以用来存储向量或者标量。

torch.tensor(1)#标量torch.tensor([1])#1*1的向量

tensor还可以指定数据类型,以及数据存储的位置(可以存在显存里,硬件加速)

torch.tensor([1,2],dtype=torch.float64)梯度

在数学里,梯度的定义如下:

可以看出,自变量相对于因变量的每一个偏导乘以相应的单位向量,最后相加,即为最后的梯度向量。

在pytorch里面,我们无法直接定义函数,也无法直接求得梯度向量的表达式。更多的时候,我们其实只是求得了函数的在某一个点处相对于自变量的偏导。

我们先假设一个一元函数:y = x^2 + 3x +1,在pytorch里面,我们假设x = 2, 那么

>>>x=torch.tensor(2,dtype=torch.float64,requires_grad=True)>>>y=x*x+3*x+1>>>y.backward()>>>x.gradtensor(7.,dtype=torch.float64)

可以看出,最后y相对于x的导数在x=2的地方为7。在数学里进行验证,那么就是

y' = 2*x + 3, 当x=2时,y' = 2 * 2 + 3 = 7, 完全符合torch自动求得的梯度值。

接下来计算二元函数时的情况:

>>>x1=torch.tensor(1.0)>>>x2=torch.tensor(2.0,requires_grad=True)>>>y=3*x1*x1+9*x2>>>y.backward()tensor(6.)>>>x2.gradtensor(9.)

可以看出,我们可以求得y相对于x2的偏导数。

以上讨论的都是标量的情况,接下来讨论自变量为向量的情况。

mat1=torch.tensor([[1,2,3]],dtype=torch.float64,requires_grad=True)>>>mat2tensor([[1.],[2.],[3.]],dtype=torch.float64,requires_grad=True)

mat1是一个1x3的矩阵,mat2是一个3x1的矩阵,他们俩的叉乘为一个1x1的矩阵。在pytorch里面,可以直接对其进行backward,从而求得相对于mat1或者是mat2的梯度值。

>>>y=torch.mm(mat1,mat2)>>>y.backward()>>>mat1.gradtensor([[1.,2.,3.]],dtype=torch.float64)>>>mat2.gradtensor([[1.],[2.],[3.]],dtype=torch.float64)

其实可以把mat1中的每一个元素当成一个自变量,那么相对于mat1的梯度向量,就是分别对3个x进行求偏导。

相当于是y = mat1[0] * mat2[0] + mat1[1] * mat2[1] + mat1[2] * mat2[2]

然后分别求y对于mat1,mat2每个元素的偏导。

另外,如果我们最后输出的是一个N x M 的一个向量,我们要计算这个向量相对于自变量向量的偏导,那么我们就需要在backward函数的参数里传入参数。

其实pytorch的autograd核心就是计算一个 vector-jacobian 乘积, jacobian就是因变量向量相对于自变量向量的偏导组成的矩阵,vector相当于是因变量向量到一个标量的函数的偏导。最后就是标量相对于一个向量的梯度向量。

感谢你能够认真阅读完这篇文章,希望小编分享的“PyTorch如何自动计算梯度”这篇文章对大家有帮助,同时也希望大家多多支持亿速云,关注亿速云行业资讯频道,更多相关知识等着你来学习!