利用 matrix derivative 思考 back propagation

利用 matrix derivative 思考 back propagation

在之前 矩阵偏导 一文中提到,我们通常采用 numerator layout 的格式表示 偏导矩阵。

但是 !!!!!!

在 back propagation 中,我们应当采用 denominator layout 的格式,理由有两点 :

  1. 损失函数 \(J\) 一般是标量,而 权重 \(\mathbf {W}\) 和 偏置 \(\mathbf {b}\) 均为矩阵, 我们求解 \(\cfrac {\partial J}{\partial \mathbf {W}}\)\(\cfrac {\partial J}{\partial \mathbf {b}}\) 矩阵,大小和分母保持一致更合适
  2. 梯度下降中,\(\mathbf {W} = \mathbf {W} – \alpha \cfrac {\partial J}{\partial \mathbf {W}}\) ,如果我们采用 denominator layout,可以进行矩阵减法运算,而不用转置后再运算

其实反向传播的出现的原因很简单 – 简化计算

神经网络中,用损失函数对每个权重、偏置计算梯度时,反向传播可以利用前一层的计算结果,避免重复计算。

本文的网络结构参考吴恩达老师的 Deeplearning.ai 课程

1. back propagation

\[
\begin {align}
\mathbf {Z}^{[l]} &= \mathbf {W} \mathbf {A}^{[l-1]} + \mathbf {b} \\
\mathbf {A}^{[l]} &= g(\mathbf {Z}^{[l]})
\end {align}
\]

这里我们计算的是第 \(l​\) 层神经元的值 \((l \gt 0)​\)

其中 \(\mathbf {A}^{[l]}, \mathbf {Z}^{[l]}\) 大小为 \((n^{[l]}, m)\)\(\mathbf {A}^{[l-1]}\) 大小为 \((n^{[l-1]}, m)\)\(\mathbf {W}\) 大小为 \((n^{[l]}, n^{[l-1]})\)\(\mathbf {b}\) 大小为 \((n^{[l]}, 1)\) , \(g\) 为 activation function,这里 \(n^{[l]}, n^{[l-1]}\) 表示 第 \(n, n-1\) 层神经元个数,\(m\) 为 batch 数,

需要注意的是: \(\mathbf {W}\mathbf {A}^{[l-1]} + \mathbf {b}\) 中的矩阵加法运算利用了 Numpy 库的 Broadcasting 特性,因为 \(\mathbf {b}\) 大小为 \((n^{[l]}, 1)\) ,而 \(\mathbf {W}\mathbf {A}^{[l-1]}\) 的大小为 \((n^{[l]}, m)\) ,Numpy 会自然地对 \(\mathbf {b}\) 进行水平方向扩展,扩展的值均相同

那么问题来了,这个 Broadcasting 操作如何用函数,或者更具体地说,如何用矩阵表示呢,否则我们后面怎么计算 \(\cfrac {\partial \mathbf {Z}^{[l]}} {\partial \mathbf {b}}\) ?

其实扩展过程可以用如下矩阵变换表示: \[
\mathbf {b}' = \mathbf {b}\mathbf {K}
\]
其中 \(\mathbf {K}\) 是大小为 \((1, m)\) 的所有元素为 \(1\) 的行向量

\(\mathbf {b}'\) 的大小为 \((n^{[l]}, m)\) ,就是扩展之后的矩阵

回到正题,

1.1 激活函数 (activation function) 偏导

这里需要强调的是,请不要盲目套用 矩阵偏导 博客的偏导公式。因为激活函数是 逐个元素 (element-wise) 计算的,它并没有用到矩阵的性质,本质上其实就是类似于:已知 \(y = g(x)\) 请计算导数 \(\cfrac {d y} {dx}\) ,这里 \(y ,x\) 均为标量。不同之处在于激活函数是对一个矩阵的所有元素都执行函数 \(g\)

于是自然我们就有 : \[
\cfrac {\partial \mathbf {A}^{[l]}} {\partial \mathbf {Z}^{[l]}} = g'(\mathbf {Z}^{[l]})
\]
\(g'(\mathbf {Z}^{[l]})\) 矩阵大小为 \((n^{[l]}, m)\) ,和 \(\mathbf {A}^{[l]}, \mathbf {Z}^{[l]}\) 大小一致

结合链式法则,如何计算 \(\cfrac {\partial J} {\partial \mathbf {Z}^{[l]}}\) 呢? \[
\cfrac {\partial J} {\partial \mathbf {Z}^{[l]}} = \cfrac {\partial J} {\partial \mathbf {A}^{[l]}} \cfrac {\partial \mathbf {A}^{[l]}} {\partial \mathbf {Z}^{[l]}} = \cfrac {\partial J} {\partial \mathbf {A}^{[l]}} \ast g'(\mathbf {Z}^{[l]})
\]
我们采用 denominator layout, \(\cfrac {\partial J} {\partial \mathbf {A}^{[l]}}\) 大小为 \((n^{[l]}, m)\)\(g'(\mathbf {Z}^{[l]})\) 大小也是 \((n^{[l]}, m)\) ,这里 \(\ast\) 表示逐点相乘运算,原因我们前面解释了。

1.1.1 ReLu 函数

\[
\mathrm {Relu}(x) = \begin{cases}
x, &\text{if } x \gt 0 \\
0, &\text{if } x \le 0
\end{cases}
\]

导数如下:
\[
\mathrm {Relu}'(x) = \begin{cases}
1, &\text{if } x \gt 0 \\
0, &\text{if } x \le 0
\end{cases}
\]
注: 这里简化了 \(x=0\) 点的不可导情况,直接将其视为 \(0\)

1.1.2 Sigmoid 函数

\[
\mathrm {sigmoid}(x) = \cfrac {1}{1 + e^{-x}}
\]

导数如下: \[
\mathrm {sigmoid}'(x) = \mathrm {sigmoid} (x) \ast (1 – \mathrm {sigmoid}(x))
\]

1.1.3 tanh 函数

\[
\mathrm {tanh}(x) = \cfrac {e^{x} – e^{-x}}{e^{x} + e^{-x}}
\]

导数如下: \[
\mathrm {tanh}'(x) = 1 – \mathrm {tanh}^{2}(x)
\]
值得一提的是,对于 \(\mathrm {sigmoid}(x)\)\(\mathrm {tanh}(x)\) 函数,我们如果看导数曲线,可以看到只有在 \(x=0\) 附近的范围内,导数值是比较大的,这也是为什么我们一般把 权重参数 \(\mathbf {W}\) 设置比较小的原因 (\(0 – 0.01\) 之间)

1.2 线性函数偏导 – \(\mathbf {Z}^{[l]} = \mathbf {W}\mathbf {A}^{[l-1]} + \mathbf {b}\)

这里用到了 矩阵导数 的两个偏导公式: \[
\begin {align}
&\cfrac {\partial \mathbf {u}^T \mathbf {v}} {\partial \mathbf {x}} =\mathbf {u}^T \cfrac {\partial \mathbf {v}} {\partial \mathbf {x}} + \mathbf {v}^T\cfrac {\partial \mathbf {u}} {\partial \mathbf {x}} \\
&\cfrac {\partial (\mathbf {u} + \mathbf {v})} {\partial \mathbf {x}} = \cfrac {\partial \mathbf {u}} {\partial \mathbf {x}} + \cfrac {\partial \mathbf {v}} {\partial \mathbf {x}}
\end {align}
\]
注意到我们这里 \(\mathbf {W}, \mathbf {A}^{[l-1]}, \mathbf {b}\) 中的变量是相互独立的

于是: \[
\begin {align}
\cfrac {\partial \mathbf {Z}^{[l]}} {\partial \mathbf {A}^{[l-1]}} &=
\cfrac {\partial (\mathbf {W}\mathbf {A}^{[l-1]} + \mathbf {b})} {\partial \mathbf {A}^{[l-1]}} \\
&= (\mathbf {W} \cfrac {\partial \mathbf {A}^{[l-1]}} {\partial \mathbf {A}^{[l-1]}} + {\mathbf {A}^{[l-1]}}^T \cfrac {\partial \mathbf {W}} {\partial \mathbf {A}^{[l-1]}}) + \cfrac {\partial \mathbf {b}} {\partial \mathbf {A}^{[l-1]}} \\
&= \mathbf {WI} + \mathbf {0} + \mathbf {0} = \mathbf {W}
\end {align}
\]
类似地, \[
\cfrac {\partial \mathbf {Z}^{[l]}} {\partial \mathbf {W}} ={\mathbf {A}^{[l-1]}}^T \\
\cfrac {\partial \mathbf {Z}^{[l]}} {\partial \mathbf {b}} = \cfrac {\partial \mathbf {bK}} {\partial \mathbf {b}} = \mathbf {K}^T
\]
我们利用链式法则可以得到:
\[
\cfrac {J} {\partial \mathbf {A}^{[l-1]}} = \cfrac {\partial J} {\partial \mathbf {Z}^{[l]}} \cfrac {\partial \mathbf {Z}^{[l]}} {\partial \mathbf {A}^{[l-1]}} = \cfrac {\partial J} {\partial \mathbf {Z}^{[l]}} \mathbf {W}
\]
再次提出,我们采用的是 denominator layout 的形式,因此上面公式还需要考虑矩阵维度:

\(\cfrac {\partial J} {\partial \mathbf {Z}^{[l]}}\) 大小和 \(\mathbf {Z}^{[l]}\) 一致,为 \((n^{[l]}, m)\)\(\mathbf {W}\) 大小为 \((n^{[l]}, n^{[l-1]})\)

考虑到 \(\cfrac {\partial J} {\partial \mathbf {A}^{[l-1]}}\) 的大小必须和 \(\mathbf {A}^{[l-1]}\) 保持一致,即 \((n^{[l-1]}, m)\) ,同时结合上面的激活函数公式,得到 :
\[
\cfrac {\partial J} {\partial \mathbf {A}^{[l-1]}} = \mathbf {W}^T \cfrac {\partial J} {\partial \mathbf {Z}^{[l]}} = \mathbf {W}^T \Big (\cfrac {\partial J} {\partial \mathbf {A}^{[l]}} \ast g'(\mathbf {Z}^{[l]}) \Big)
\]
类似的,基于矩阵维度相同考虑: \[
\begin {align}
\cfrac {\partial J} {\partial \mathbf {W}} &= \cfrac {\partial J} {\partial \mathbf {Z}^{[l]}} {\mathbf {A}^{[l-1]}}^T = \Big (\cfrac {\partial J} {\partial \mathbf {A}^{[l]}} \ast g'(\mathbf {Z}^{[l]}) \Big) {\mathbf {A}^{[l-1]}}^T \\
\cfrac {\partial J} {\partial \mathbf {b}} &= \cfrac {\partial J} {\partial \mathbf {Z}^{[l]}} \mathbf {K}^T = \Big (\cfrac {\partial J} {\partial \mathbf {A}^{[l]}} \ast g'(\mathbf {Z}^{[l]}) \Big) \mathbf {K}^T
\end {align}
\]
由于 \(\mathbf {K}\) 是大小为 \((1, m)\) 的所有元素为 \(1\) 的行向量,则 \(g'(\mathbf {Z}^{[l]}) \mathbf {K}^T\) 其实是对 \(g'(\mathbf {Z}^{[l]})\) 的每一行求和,所以也可以写成: \[
\cfrac {\partial J} {\partial \mathbf {b}} = \sum_{i=1}^{m} \cfrac {\partial J} {\partial \mathbf {z}^{[l](i)}}
\]
这就是反向传播算法,其实只要考虑好矩阵相乘的维度,运用矩阵偏导的知识,就非常简单。

2. 一点小提醒

注意在吴恩达老师的课程中,他是这么写的: \[
\begin {align}
\cfrac {\partial J} {\partial \mathbf {W}} &= \cfrac {1}{m} \cfrac {\partial J} {\partial \mathbf {Z}^{[l]}} {\mathbf {A}^{[l-1]}}^T \\
\cfrac {\partial J} {\partial \mathbf {b}} &= \cfrac {1}{m} \sum_{i=1}^{m} \cfrac {\partial J} {\partial \mathbf {z}^{[l](i)}} \\
\cfrac {\partial J} {\partial \mathbf {A}^{[l-1]}} &= \mathbf {W}^T \cfrac {\partial J} {\partial \mathbf {Z}^{[l]}}
\end {align}
\]
为啥这里会有 \(\cfrac {1}{m}\) 出现呢 ?

(刚开始我也没有留意,是在写本博客的时候,回过头看 Jupyter 的作业中的公式,突然发现两者有不同)

我当时的想法 如下:

在输出层,即第 \(L\) (大写 \(L\) )层计算 cost function 时,因为 \[
J = – \cfrac {1}{m} \sum_{i=1}^m (y^{(i)} \log(a^{[L](i)}) + (1-y^{(i)}) \log (1- a^{[L](i)}))
\]
所以计算这一层的梯度时,一个很自然的想法就是把 \(\cfrac {1}{m}\) 考虑进去了,如果后面的每层在计算 \(\cfrac {\partial J} {\partial \mathbf {W}}\)\(\cfrac {\partial J} {\partial \mathbf {b}}\) 的时候仍然把 \(\cfrac {1}{m}\) 考虑进去,不就会重复计算,造成指数衰减了吗? 结果为什么还是正确的呢?

我埋头想了许久,突然想到,Deeplearing.ai 第 2 门课不是讲了 gradient checking 吗,如果梯度不对,直接检查不就可以出来了吗 ?

然后我又翻出这堂课的作业,发现竟然也是有 \(\cfrac {1}{m}\) 出现的。而且梯度竟然是正确的 !!!

然后,就灵光一闪,知道问题所在了 !!!!!!!!

因为 \(\cfrac {\partial J} {\partial \mathbf {W}}\)\(\cfrac {\partial J} {\partial \mathbf {b}}\) 不会传递到下一层,它就是这一层权重更新的最终结果了 !!

因为 \(\cfrac {\partial J} {\partial \mathbf {W}}\)\(\cfrac {\partial J} {\partial \mathbf {b}}\) 不会传递到下一层,它就是这一层权重更新的最终结果了 !!

因为 \(\cfrac {\partial J} {\partial \mathbf {W}}\)\(\cfrac {\partial J} {\partial \mathbf {b}}\) 不会传递到下一层,它就是这一层权重更新的最终结果了 !!

重要结论说 3 遍,

原因如下:

先把上面的损失函数抄下来: \[
J = – \cfrac {1}{m} \sum_{i=1}^m (y^{(i)} \log(a^{[L](i)}) + (1-y^{(i)}) \log (1- a^{[L](i)}))
\]
作业中在最后输出层并没有考虑 \(\cfrac {1}{m}\) , 它是这么算的: \[
\cfrac {\partial J} {\partial \mathbf {A}^{[L]}} = – \cfrac {Y}{\mathbf {A}^{[L]}} – \cfrac {1- Y}{1- \mathbf {A}^{[L]}}
\]
注意这里的除法是逐点运算

简而言之,它把原本应该在 cost function 后就应该计算的 \(\cfrac {1}{m}\) 给移到了最后计算,即\(\cfrac {\partial J} {\partial \mathbf {W}}\)\(\cfrac {\partial J} {\partial \mathbf {b}}\) 中。

因为 \(\cfrac {\partial J} {\partial \mathbf {W}}\)\(\cfrac {\partial J} {\partial \mathbf {b}}\) 就是我们想要的每一个权重和偏置的梯度了,我们并不需要把这个梯度进行链式法则传递到下一层,真正需要传递的是 \(\cfrac {\partial J} {\partial \mathbf {A}^{[l-1]}}\) ,所以我们看到公式了这里并没有 \(\cfrac {1}{m}\)

解释结束 …

One thought on “利用 matrix derivative 思考 back propagation

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d 博主赞过: