什么叫维度分析?举一个最简单的例子。设某一层的Forward Pass为
,X是NxD的矩阵,W是DxC的矩阵,b是1xC的矩阵,那么score就是一个NxC的矩阵。现在上层已经告诉你L对score的导数是多少了,我们求L对W和b的导数。
一定是一个NxC的矩阵(因为Loss是一个标量,score的每一个元素变化,Loss也会随之变化),那么就有
现在问题来了,score是一个矩阵,W也是个矩阵,矩阵对矩阵求导,怎么求啊?如果你对矩阵微分不熟悉的话,到这里就直接懵逼了。于是很多同学都出门右转去学习矩阵微分到底怎么搞,看到那满篇的推导过程就感到一阵恶心,之后就提前走完了从入门到放弃,从深度学习到深度厌学的整个过程。
其实我们没有必要直接求score对W的导数,我们可以利用另外两个导数间接地把
算出来。首先看看它是多大的。我们知道
一定是DxC的(和W一样大),而
一定是DxC的(和W一样大),而
定是DxN的,因为(DxN)x(NxC)=>(DxC),并且你还发现你随手写的这个式子右边两项写反了,应该是
那好,我们已经知道了
是DxN的,那就好办了。既然score=XW+b,如果都是标量的话,score对W求导,本身就是X;X是NxD的,我们要DxN的,那就转置一下呗,于是我们就得出了:
完事了。
你看,我们并没有直接去用诸如
这种细枝末节的一个一个元素求导的方式推导
,而是利用
那
呢?我们来看看,
是NxC的,
是1xC的,
看起来像1,那聪明的你肯定想到
其实就是1xN个1了,因为(1xN)x(NxC)=>(1xC)。其实这也就等价于直接对d_score的第一维求个和,把N降低成1而已。
多说一句,这个求和是怎么来的?原因实际上在于所谓的“广播”机制。你会发现,XW是一个NxC的矩阵,但是b只是一个1xC的矩阵,按理说,这俩矩阵形状不一样,是不能相加的。但是我们都知道,实际上我们想做的事情是让XW的每一行都加上b。也就是说,我们把b的第一维复制了N份,强行变成了一个NxC的矩阵,然后加在了XW上(当然这件事实际上是numpy帮你做的)。那么,当你要回来求梯度的时候,既然每一个b都参与了N行的运算,那就要把每一份的梯度全都加起来求个和的。因为求导法则告诉我们,如果一个变量参与了多个运算,那就要把它们的导数加起来。这里借用一下@午后阳光的图,相信大家可以看得更明白。

我一眼就能看出来
,还用得着先把
当成一个中间函数么?
不幸的是,在神经网络里面,你会发现事情没那么容易。上面的这些推导只在标量下成立,如果w,x和b都是矩阵的话,我们很容易就感到无从下笔。还举上面这个例子,设
,我们要求
那么我们直接就可以写出

是一个DxN的矩阵。然后就会发现没有任何招可以用了。事实上,卡壳的原因在于,
根本不是一个矩阵,而是一个4维的tensor。对这个鬼玩意的运算初学者是搞不定的。准确的讲,它也可以表示成一个矩阵,但是它的大小并不是DxN,而且它和
的运算也不是简单的矩阵乘法,会有向量化等等的过程。有兴趣的同学可以参考这篇文章,里面有一个例子讲解了如何直接求这个导数:矩阵求导术(下)(https://zhuanlan.zhihu.com/p/24863977)。
当做一个中间变量的话,事情就简单的多了。因为如果每一步求导都只是一个简单二元运算的话,那么即使是矩阵对矩阵求导,求出来也仍然是一个矩阵,这样我们就可以用维度分析法往下做了。
,则有
利用维度分析:dS是NxC的,dH是NxC的,考虑到
也是NxC的,也就是
这是一个element-wise的相乘;所以
再求
,用上一部分的方法,很容易求得
所以就求完了。
如果你错误地认为
是一个DxN的矩阵的话,再往下运算:
我们已经知道
,这两个矩阵一个是NxC的,一个是DxN的,无论怎么相乘,也得不出DxN的矩阵。矛盾就是出在H对W的导数其实并不是一个矩阵。但是如果使用链式法则运算的话,我们就可以避开这个复杂的tensor,只使用矩阵运算和标量求导就搞定神经网络中的梯度推导。

就是第i个样本预测的其正确class的概率。关于softmax的知识在这里就不多说了。我们来求Loss关于W, X和b的导数。为了简便起见,下面所有的d_xxx指的都是Loss对xxx的导数。
不要一步到位,我们把前面一部分和后面一部分分开看。设
, rowsum就是每一行的score指数和,因此是Nx1的,那么就有

先看d_score,其大小与score一样,是NxC的。你会发现如果扔掉前面的1/N不看 ,

然后看d_rowsum,其实就是
非常简单。
需要注意的是我们不要直接求
是什么,两个都是矩阵,不好求;相反,我们求
是多少。我们会发现上面我们求了一个d_score,这里又求了一个d_score,这说明score这个矩阵参与了两个运算,这是符合这里Loss的定义的。求导法则告诉我们,当一个变量参与了两部分运算的时候,把这两部分的导数加起来就可以了。
这一部分的d_score就很好求了:
左边是NxC的,右边已知的是Nx1的,那么剩下的有可能是1xC的,也有可能是NxC的。这个时候就要分析一下了。我们会发现右边应该是NxC的,因为每一个score都只影响一个rowsum的元素,因此我们不应该求和。NxC的矩阵就是
自己,所以我们就很容易得出:
