Pytorch入门之一文看懂自动求梯度

PyTorch由4个主要包装组成:
1.Torch:类似于Numpy的通用数组库,可以在将张量类型转换为(torch.cuda.TensorFloat)并在GPU上进行计算。
2.torch.autograd:用于构建计算图形并自动获取渐变的包
3.torch.nn:具有共同层和成本函数的神经网络库
4.torch.optim:具有通用优化算法(如SGD,Adam等)的优化包

 

Pytorch的autograd包会根据输入和前向传播(推理)过程自动构建计算图,并执行反向传播求参数的梯度。

关于autpgrad流程机制原理,我觉得这个博主讲的挺详细的,推荐看这个:

https://blog.csdn.net/MR_kdcon/article/details/108937374中的链接

 

这里顺带提一下,现在很多资料都有Variable这个变量,这个是老版本了。新版本中,torch.autograd.Variable 和 torch.Tensor 将同属一类。更确切地说,torch.Tensor 能够追踪日志并像旧版本的 Variable 那样运行; Variable 封装仍旧可以像以前一样工作,但返回的对象类型是 torch.Tensor。这意味着你的代码不再需要变量封装器。

所以我们想要导入autograd包的话只需要:import torch

 

我们之前已经知道了Tensor的某些属性,比如dtype、device,那么现在我们又可以继续学到4个属性

1、tensor.requires_grad

2、tensor.grad

3、tensor.grad_fn

4、tensor.data

说明如下:

requires_grad有True和False(默认值),True代表这个tensor将在计算图中被追踪其所有操作行为(加、乘、求平均、logistic、softmax.....),这么做的目的就是便于之后通过反向梯度传播计算梯度。tensor必须为浮点型

两者设置跟踪的方法

x = torch.ones(2, 3, requires_grad=True)
y = torch.ones(6)
print(y.requires_grad)  # False
y.requires_grad_(True)
print(y.requires_grad)  # True

 

note:

1、x.requires_grad_(True/False) 设置tensor的可导与不可导。但是需要注意的是,我只能够设置叶子变量,即leaf variable的这个方法,否则会出现以下错误:

RuntimeError: you can only change requires_grad flags of leaf variables.

2、只有当所有的“叶子变量”,即所谓的leaf variable都是不可求导的,那函数y才是不能求导的。

 

那么如果不想被跟踪咋办?

法1:

方法.detach可以将tensor从计算图中分离出来,切断后面的计算跟踪,这样反向传播的时候,梯度就传不过去了。但不影响正向传播。

法2:

with torch.no_grad: #包裹不想被跟踪的操作
    ...
    ...

可以将不想被跟踪的操作代码块包裹起来。但不影响正向传播。

 

那么如何计算梯度呢?

很简单,一句话就解决:loss.backward(),或者使用函数torch.autograd.backward()。一个是方法一个是函数,都可以实现一样的效果。

loss是损失函数,是你前向传播的最后计算结果,也是反向传播的开端。方法.backward()是用来完成计算图中所有的梯度计算。以一己之力解决所有梯度计算。

这里需要注意一下:loss.backward()中loss如果是0维张量(标量)则backward不需要任何实参。但如果loss是个非标量(向量或者多维张量),则backward()需要传入一个同样规模的Tensor,即loss.backward(loss_like),等价于先计算y = torch,sum(loss*loss_like),然后loss对y求偏导。

为啥要这么做呢?主要是为了避免张量对张量求导时产生的维数扩张,从而使得链式传播过程中难以匹配合适的相乘项,因此我们最希望是用标量去对张量求导,这样可以使得参数的求导结果和参数是同规模的,也便于后期的计算,如SGD、Adam等等。

 

梯度保存在哪?

所以接下来说第二个属性tensor.grad,这个属性用来保存计算好的梯度值

note:

grad在反向传播过程中是累加的,因此在每次loss.backward()之前,都需要把有关上一轮batch产生的梯度清零,即x.grad.data.zeros_()。然后再调用loss.backward()来计算这一轮batch的新梯度。

举个栗子:

a = torch.tensor(2,dtype=torch.float32, requires_grad=True)
b = torch.tensor(3, dtype=torch.float32, requires_grad=True)
d = torch.tensor(4, dtype=torch.float32, requires_grad=True)
e = torch.tensor(5, dtype=torch.float32, requires_grad=True)
c = a * b
f = c * d
g = e * c
f.backward()
print(a.grad)  # tensor(12)
print(e.grad)  # None

从这个例子我们可以看出,c节点有2个子分支,一路和e产生g,一路和d产生f,但是我只对f求反向传播,因此g那一路相当于不存在,因此最后对a的梯度只有d*b=3*4=12。

 

第三个属性tensor.grad_fn,这个属性就是保存tensor所经过的某些计算相关的对象,比如

x = torch.tensor([1, 2], dtype=torch.float32, requires_grad=True)
y = x + 2
print(y)  # tensor([3., 4.], grad_fn=<AddBackward0>)

grad_fn就显示了经过了Add加法计算,返回这么个对象。另外自己创建的Tensor,这种叫叶子节点,叶子节点的grad_fn为None,后期经过计算得到的Tensor就会有类似上述例子的值。

note:

1、叶子节点不能进行in-place操作,但是除了requires_grad_()。

2、requires_grad=False的这种叶子张量grad_fn=None。

 

最后说一下最后一个属性 tensor.data

x.data 返回和 x的相同数据 Tensor,只是保存原张量的值,而且这个新的Tensor和原来的Tensor是共用数据的,一者改变,另一者也会跟着改变,而且新分离得到的Tensor的require s_grad = False, 即不可求导的,即脱离了计算图,切断被跟踪

note:

x.data 不能被 autograd 追踪求微分 。改变x.data,导致原来的张量x的值也跟着改变了,但是这种改变对于autograd是没有察觉的,它依然按照求导规则来求导,导致得出完全错误的导数值却浑然不知。它的风险性就是如果我再任意一个地方更改了某一个张量,求导的时候也没有通知我已经在某处更改了,导致得出的导数值完全不正确,可怕的是,你还不知道是错误的答案。这个问题可以用x.detach来解决,使用detach会产生RunTimeError,提示你x已经被改变了,接下去求导会出现错误。

x = torch.ones(1, requires_grad=True)
xx = x.detach()
print(xx)  # tensor([1.])
print(xx.requires_grad)  # False
#前向传播
y = x * x * 4
xx *= 100
#后向传播
y.backward()
print(x)
print(x.grad)  

 

但话又说回来了,如果我们想要修改tensor的值,但又不希望被autograd记录在计算图中(即不影响反向传播),就对tensor.data操作吧。下面来看2个例子进一步加深对data的理解

x = torch.ones(1, requires_grad=True)
print(x.data)  # tensor([1.])
print(x.data.requires_grad)  # False
#前向传播
y = x * x * 4
x.data *= 100
#后向传播
y.backward()
print(x)  # tensor([100.], requires_grad=True)
print(x.grad)  # tensor([800.])

再看下面这个

x = torch.ones(1, requires_grad=True)
print(x.data)  # tensor([1.])
print(x.data.requires_grad)  # False
#前向传播
y = x * x * 4
x = x*100
#后向传播
y.backward()
print(x)  # tensor([100.], requires_grad=True, grad_fn=<MulBackward0>)
print(x.grad)  # None

这里我们想要去改变x的值,但我们从x.grad看出反向梯度计算出了错误,这一点从x.grad_fn可以看出,x进行了Mul乘法运算,我们在前向传播完成之后,即y=x*x*4,后向传播其实以完成了,就等着我们去调用backward调用,但这时候x又进行了Mul运算,使得整个后向传播收到了影响,因此产生了x.grad=None的结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值