什么是动态建图?如何动态建图?动态建图为什么可以反向自动求导?
我对于这个问题一直存在疑惑,但是去网上又没有找到好的解释,于是,没办法,自己实验吧。
首先,我们做如下定义:
a = torch.randn(2)
b = torch.randn(2)
c = torch.randn(2,requires_grad=True)
m = a*b
print(m.grad_fn)
q = a*c
print(q.grad_fn)
我们定义a,b为不可自动求导的,而c是可以自动求导的。按照我们的认知,m.grad_fn为None,那q呢?不可求导乘可以求导结果是什么呢?我们进行了打印,输出如下:
None
咦?不可求导乘可求导结果是可以反向传播求导的,这意味着什么呢?是否有一个是可求导的值,那这个值到最终结尾这条路上所有经过的点是不是都可以反向传播求导呢?
于是我们做了一下测试:
a = torch.randn(5,5,requires_grad=True)
b = torch.randn(5,5)
c = torch.randn(5,5)
d = torch.randn(5,5)
f = torch.randn(5,5)
for i in range(5):
for j in range(5):
c[i,j] = a[i,j]*b[i,j]
for i in range(5):
for j in range(5):
f[i,j] = c[i,j]*d[i,j]
m = f.mean()
m.backward()
print(a.grad)
print(m.grad_fn)
print(c.grad_fn)
我们让a是可以反向求导的,b,c,d,f都不可以,然后我们让c中的值被a*b填满,本来c.grad_fn是none,现在,结果呢?然后我们让f的值为c*d,f.grad_fn也是none,现在呢?我们现在还能求出a的梯度吗?我们将结果进行了打印,结果如下:
tensor(1.00000e-02 * [[ 2.6930, -1.1680, -3.5504, -0.7121, 2.8623], [ 0.4919, -3.4261, -0.4158, 4.2845, 9.4236], [ 6.4853, -2.7324, 0.0493, -0.1078, 1.1301], [ 0.3233, 9.0532, -7.5055, 4.6100, 0.7901], [ 0.0487, 1.3407, -4.1330, -0.7206, 0.3232]])
grad_fn中存储的是什么呢?在pytorch中,variable会记录创建它的函数,这样就可以使它能够追踪计算过程,从而利用链式法则求导。如果一个variable是用户创建的,那么它的grad_fn被设为None,这样的变量被成为leaf。在网络进行forward传播的过程中,pytorch会同时计算一个梯度求解函数,放在变量的grad_fn中,在完成forward过程后,我们就可以使用backward()函数来调用grad_fn来进行求导了。
不出所料,真的可以对a反向求导,尽管我们在到f的路上一路上光遇到不可自动求导的点了,但是我们依然能够在终点处对其进行求梯度,同时我们打印出了m.grad_fn和c.grad_fn这两个一个是终点的一个是中间节点的,但是毫无例外,都是可以反向传播的。这进一步验证了我们的猜想。
于是我根据pytorch的动态建图和反向传播有了一下认识:
框架最优美的地方在于什么地方呢?我认为有两点最吸引我:
1:我可以摒弃一些细节的地方,只要引用一些封装好的包就可以快速的验证自己的实验和猜想
2:我可以不用对其进行复杂的数学运算,框架可以自动求导
尤其第二条,简直就是福音对于绝大多数机器学习爱好者,但是如何自动求导呢?
在tensorflow里,我们会先搭建一个图,我们要考虑好所有的东西,务必搭建出来的是我们需要的图,然后将其装入session中执行图,如果我们想要中途添加一个节点,对不起,你只能修改图,打破原先的架构,重新构建图,然后再装入session中执行,但是在pytorch中,我们是如何操作的?我们只需要将我们想要优化的作为叶子节点放进来(就是创建我么想要优化的初始值),为什么我们称其为叶子节点?我们想想看,每个叶子节点都可以回溯到树根处 ,而且这么多叶子节点会不断的聚合,最终产生一个值,这个与pytorch的计算何其相似(当然,与别的计算也很少相似),但是(重点来了),如果我们将叶子节点的requires_grad设置为true那么,在叶子节点不断汇聚的过程中,他会将我要优化的消息一路向上传播,这样,框架就会记住,这个要优化的叶子的一路的踪迹,他是从哪来的,经过那些点,这样,我们在终点backward的时候,机器就会按照他来的路一路链式求导回去,最后求出他的梯度,这样就完成了整个的自动求导过程。说复杂也不复杂,但是通过今天自己写了一下程序验证一下,然后根据其文档的说明,对pytorch的自动求导有了一个更清晰的认识。