CMU-深度学习系统-第四章

这一章主要是讲了反向模式的自动微分

数值计算和符号推导

使用公式直接计算偏导的微分方式容易出现数值错误,以及效率太低,通常是用来作为检验方法(也就是采用具体数值来求出对应位置的微分,like this:

image-20250309152525383

或者这种:

image-20250309152550314

采用符号推导的方式浪费算力,也就是我们常用的数学推导方式,比如链式法则之类的:

image-20250309152641130

如果遇到连乘,计算所有偏导的开销是

因此进一步提出了计算图: image-20250309152824589

前向自动微分

首先介绍了前向模式的自动微分。同上图,记

然后向前一步一步计算,,这样一轮跑下来的整个过程是:

image-20250309153533025

但是这只是针对的,针对的偏导还得再经历一遍上述过程,也就是对于,需要经历n个前向传播,才能得到最终结果对每一个输入的偏导。而一般n都比较大,反而是k很小,经常是1,所以换用反向自动微分。

反向自动微分

同样的计算图:image-20250309152824589

,称为点的伴随(adjoint)

按照完全相反的拓扑顺序来计算各个节点的,也就是,…,。这样的好处是只需要走一遍就可以把每个输入的偏导都求出来。单词的反向自动微分过程如下:

image-20250309163419705

针对多路情况的求导,也就是说一个输入作为了多个输入,如下图

image-20250309163555056

可以得到:

image-20250309163724343

这里把的边的表示利用起来,其实如果把表述成边上的特征,也就可以理解为的伴随只需要沿着他所有出的边的特征之和就好了。把这个定义成部分伴随(partial adjoint),即,因此就有:

可以解释为计算图中每个节点的伴随等于它所有与下一个节点邻接边的部分伴随之和。因此只需要计算所有的部分伴随,要求伴随的时候给他们求和就好。

伪代码表示如下:

image-20250309164516516

具体实现过程可参考CMU-深度学习系统-第五章&hw1 | Shuyu Zhang’s Blog

通过扩展计算图的反向自动微分

按照上面的伪代码过程,针对这个计算图:

image-20250309165912256

首先是对这个点计算其,那肯定是1,所以记录到node_to_grad里面,并且可以做出这样的计算图:

image-20250309170126494

这个id实际上就是一个占位符,不太重要。进一步计算,以为例,就是,而其中可以得到的是实则就是,因此我们可以更新一个新的计算图,就是说其实就是这个节点和这个节点的积。同理,但不一样的是其实就是,因为只有一条向外的边。因此可以进一步更新计算图,并更新node_to_grad(key是node的编号,每个value代表一条边的partial adjoint):

image-20250309171213995

进一步考虑,考虑所有它的上一个点,计算partial adjoint,以此类推,得到一个拓展的计算图,最终如下:

image-20250309171840443

但如果是传统的反向自动微分,计算图是这样的:

image-20250309171907385

传统的反向自动微分是第一代深度学习框架使用的方法,如caffe, cuda-convnet;现代的深度学习框架使用扩展的计算图。这样的计算图有利于进一步计算二阶偏导,也更有利于优化

针对tensor的反向模式的自动求导也是一样的。对于计算图:image-20250309223719503

定义的伴随,其实过程也就类似标量,矩阵的正向传播时,反向之后,可以求得:(其实不用很严谨的推导,甚至可以直接用链式法则知道哪两个相乘,然后用转置凑形式就行(Zico老师前几节课教的

最后说的这个在数据结构上的反向自动微分我没看懂?我理解大概是讲的如果正向的时候是对数据结构里面的某个元素进行传递,反向依然是针对同一个元素?