tensor backward_pytorch中inplace以及detach()操作对求梯度过程(backward)的影响

写在前面:我认为如果说“两个变量AB共享data”,但是它们地址不同,这样理解比较好:两个变量的id指的是变量名的内存地址,但是两个变量名地址又指向一个同一个data地址。inplace操作(比如pytorch中的最后带下划线的方法,+=,*=等,都是inplace操作,我想是直接改变变量所指向的data内存地址中的data值,指向的还是那个内存地址,只是那个内存地址的值变了。)改变的是他们共同指向的那个data。比如说AB变量共享一个data,如果对A执行inplace操作,如A+=100,A的data改变而且B的data也改变。但如果对A执行非inplace操作,比如,A = A+100,那么,A的data改变但是B的data不会改变。而且,经过这个操作,A指向的不再是原来共同的那个data,而是指向了一个新的数值的内存地址。这时候,再对A进行inplace操作也不会再改变B了。关于这个想法,后来我找到了更确切的证据,见下侧链接:

pytorch torch.Storage学习​www.cnblogs.com
v2-0fc27d4ccaa818593976ea9a63270e95_ipico.jpg

1.tensor的reshape,resize

x = torch.randn(4, 4)

y = x.view(16)

z = x.view(-1, 8)

只是共享data,内存地址不同,需要inplace操作才能影响两者。

如果不想共享,也就是不想被inplace影响,先clone()再view()

clone()与copy_()的区别是,clone会被记录在计算图中,梯度回传到副本时也会回传到源tensor。

2.tensor的numpy()方法使得生成tensor对应的ndarray,也只是共享data(反之亦然)。用inplace改变一个(注:直接给一个赋其他值,不是inplace操作),另一个也会变。

a = torch.ones(5)

b = a.numpy()

a.add_(1)

a,b都变

a = np.ones(5)

b = torch.from_numpy(a)

np.add(a,1,out=a)

a,b都变

注意,将tensor索引一部分,给另一个变量,索引出来的结果与原数据共享data(比如y[0,1]=233也是inplace)。numpy也是这样。

y = x[0, :]

y += 1

print(y)

print(x[0, :]) # 源tensor也被改了

3.detach出来的部分,从计算图中分离。但是与原来的tensor共享data。如果执行in-place操作,两者都会改变新版好像不能再让detach出来的影响原来的了。直接报错。

4.注意某tensor x,x和x.data内存地址不同,但是,x.data不需要被inplace操作,只要x.data改变,x也会改变,但还是要明白,虽然x随之改变,但如果不是inplace操作,那就不是inplace操作。不是就不是,没理解错的话,x.data是x的.data属性。

当明确上侧几个特性后,可以看如下几个关于求梯度的例子。
(1)默认c的requires_grad=False,但可以设置。但没卵用。c的grad_fn是None。(当然不能用这样的c去backward,没有grad_fn,就是没在计算图中。)

v2-0aeee3038a3121f20a28a343b1f8b527_b.png

v2-dc0567249f1fc45575498f1007f8fe58_b.jpg


c的requires_grad=False,可以设置True。但没卵用。看源码也许和NoGrad(self)有关。

v2-9e895661a20d406e039172abc3614768_b.jpg


(2)http://www.luyixian.cn/news_show_39273.aspx解释了inplace,并指出
不能使用in-place的情况
对于 requires_grad=True 的 叶子张量(leaf tensor) 不能使用 inplace operation
对于在 求梯度阶段需要用到的张量 不能使用 inplace operation
(3)x梯度是4*x,如果backward之前,能用inplace改变x的值,结果就不同。我想计算图构建起来后,直接就确定了哪一个内存地址应该乘以哪个值,只有inplace操作才能改变制定内存地址的值。

v2-3c4007540f56f6fc4c2f3424fd8184e6_b.jpg


(4)这里有3个小例子做比较
这里,c做出inplace操作,y的值变了(共享data),但是,grad_fn不变。不信可以不做c,只看y的grad_fn

v2-825738adb48fe6e84ae21fcd7182a02a_b.jpg


这里,如果y直接inplace操作,y的值以及grad_fn都变了,影响了计算图的结构,计算图记录了zero_的操作。 从数学上说,这个例子中对x的导数值不受y值的变化的影响,但我想,这里zero_使得grad_fn都变了,不仅仅是y值变了这么简单,而是zero_使得整个计算图发生了微妙变化,从而最终x.grad也变了。(至于为什么将grad_fn变成zerobackward,就导致x.grad变成了0,具体机制不深究,但可以说,x.grad数学上说本应该不是0,却变成了0,一定和grad_fn变化有关。)

v2-409c2bea96c2c4d0814ac8644484644c_b.jpg


这里,如果只对y.data做inplace操作,y的grad_fn也不会变。也就是说对. data进行inplace操作会改变对应内存的值,但不会影响计算图的结构,grad_fn不变。计算图没有记录到zero_的操作。因此,数学上说,如果仅仅是y值变化,x. grad不受影响,这里,x. grad果然不受影响,说明,对y. data进行inplace操作改变的仅仅是y的数值,不会改变计算图的结构。

v2-33bb865e20083cfa0c7f2dcb9489e8f7_b.jpg


(5)如果是非inplace操作,那么出现的前后顺序就很重要了

v2-af7a2e06a18e8622dcfe79a9042a3883_b.jpg


(6)为了能让backward运行两次,要设置retaingraph=True,因为第一次backward会将构建好的计算图释放掉,像下边那样,再想在第二个cell中对f进行backward,要么在第二个cell中,f.backward()之前,再一次构建起f的表达式,从新建立计算图,要么令retain_graph参数等于True。在这个过程中,a.data什么时候改变,改变后产生什么影响,请自己探索。有助于理解求导的机制。

v2-4ba4b19fbf147df71e1b9e06702d399b_b.jpg


对这个例子,下边的cell每运行一次,a.grad累加一次。上下两个cell的注释,分别有什么效果,根据前边的结论不难猜出来。

参考链接

Dive-into-DL-PyTorch​tangshusen.me

这里关于自动求梯度的地方还讲了tensor.data可以执行inplace操作而不会报错。可感受一下.data的具体影响。文章中举例时好像直接用了这个结论。结合文章中的例子可以更好地体会.data的影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值