关于第5周反向传播算法的一些争论与思考
第5周介绍了神经网络的反向传播算法。由于介绍得比较简要,很多地方没有讲透(众:You can you up!),后来C站论坛里有几个网友开始争论其中的公式有点奇怪,究竟是讲错了还是另有原因(最后结论似乎应该是没讲错)。全程围观的耿先生记录了相关的一些要点。
背景:
反向传播算法就是说好比你有一个神经网络,输入层 -> 隐藏层 -> 输出层酱紫。我们现在把所有系数初始化,把训练集里的 x 都代入到输入层,经过层层计算最后得到一组输出值。这时候我们就可以和实际的值 y 作比较,计算cost函数。假设我们用的是逻辑回归来研究分类问题,那么cost函数就应该是:
(摘自课程维基页。下同)
学过前几周应该会知道这个公式是怎么来的。前半部分和前几周逻辑回归的cost函数区别在于多了一个 K。因为逻辑回归最后的结果是一个输出值,0 到 1 之间。而上述神经网络最后的结果是一排值,每一个都是 0 到 1 之间,所以多了一次求和。后半部分其实就是把所有的统统求平方加起来。
cost函数有了之后我们就可以使用梯度下降法,那么就需要计算梯度gradient。这就要用到反向传播算法了。简单来说就是,我们从最后的输出层的值(可以叫或者叫)开始,计算每一个值的值。这个值描述了当前的计算结果和实际结果 y 之间的偏差量error。
对于输出层,课程告诉我们,就等于。争论就发生在这里,我们下面会详细说是什么争论。现在先把背景知识介绍完。
对于其它层,则需要从后一层的,倒推前一层的。公式是:
这个公式体现了什么思想呢?我们说了表现了每一个结点的值与实际值之间的偏差。对于输出层看似很好理解,减一下得到的就是偏差。不过实际上这里面暗藏玄机。详见下文(此处为悬念1)。
而对于中间层,所谓“偏差”应该这么理解:对于后一层某一个结点上出现的偏差,前一层的每一个结点都要承担一部分责任,所以我们可以把后一层结点的偏差量按照系数比例分配给前一层的结点。同时,前一层的某一个结点,对后一层的每一个节点都承担着责任,那么它的“总责任”就是这些所有的小责任之和。如果你仔细想想这个矩阵乘法的计算过程,会发现正好就是上面说的这个责任分配与求和的过程。
但是后面乘的 g' 项是从哪里来的呢?我们先不说,详见下文(此处为悬念2)。我们只要知道,由于这里用的是逻辑回归,g(z)函数就是逻辑函数。g' 是指对它求导。求导之后我们会发现:,而根据定义又有,所以我们就把上式展开为:
这样我们就得到了每一层的。之后再用前一层的“每一个”结点的值 a,乘以后一层的“每一个”结点的,就得到了一个矩阵。写成公式是。这个矩阵的规模和前后两层之间的系数矩阵是一致的,因为它们都是由前一层的每一个结点指向后一层的每一个结点。这个矩阵有什么意义呢?实际上,前一层的结点 i 的值,乘以后一层结点 j 的值,得到的就是cost函数对于的偏微分。为什么呢?详见下文(悬念3)。总之,这其实就是我们要求的梯度。
注意,上面这部分(从“简单来说就是……”那句话往后)是针对“一条”输入数据所进行的计算。而我们的训练集里通常有很多条,比如有 m 条数据。我们针对每一条都进行上述计算,最后把每一条的都累加起来再除以 m,得到的就是我们真正需要的梯度矩阵。然后就可以使用梯度下降法寻找最优了。
争议:
在C站课程论坛上,有人发了一个帖子,指出输出层的计算公式有问题。课程给出的公式是:
Andrew Ng在这里并没有详细解释这个计算公式是怎么来的。实际上它是cost函数求导求出来的,具体求法详见下文(悬念4)。该贴楼主和楼主的小伙伴认为Andrew Ng犯了一个错误,误将线性回归的cost函数用在了逻辑回归计算中。因为逻辑回归的cost函数是长最上面那个鬼样子。为了便于看清楚我们将它简化一下(忽略求和,忽略后半部分项):
对它求导后得到的应该是:
(注意。)
而只有线性回归的cost函数:
求导之后才能得到:
几位TA觉得他们说的好有道理无法反驳,只能不断地说我信任Prof Ng男神是不会错的!最后还是楼主的小伙伴自己发了个帖子指出了真正的问题出在哪里。
原来还是他们理解错了公式。并非简单地对cost函数求导就可以。它真正的计算方法是:详见下文(众:你去死吧!)……
真相:
问题的焦点集中在了真正的计算公式上面。维基百科“反向传播算法”页面给出了这个过程。我们下面来看看到底是怎么求的。我们之前留下的悬念1、2、3、4,也将一次性解决。
让我们回到我们的初心。这一切究竟是为了什么?啊!是为了求cost函数对于每一个的偏微分,也就是求:
(其中分母上的那坨东东是指:第 l 层的结点 i,指向其下一层也就是 l+1 层的结点 j 的系数。)
(评论中有人指出这里似乎 i 和 j 的意义弄反了。我查了一下似乎是反了,表示前一层结点的字母应该写在后面。如果第 l 层是结点 i,第 l+1 层是结点 j,的下标应该是 j,i 这样子。如果这里反了的话上面背景部分的倒数第二段估计也弄反了。如果你需要参考这篇文章里的公式,请留意一下这个问题。我暂时先不改了。)
再回顾一下我们计算输出值时的一些定义:,,。
为了使用这些定义,我们可以将上面的偏微分式子展开为:
(此处使用技能:链式法则x2)
把求一次偏微分变成了求三次偏微分。
右边这三项,我们先看最后一项。根据定义我们知道:
所以:
JOB DONE...........33%
顺便说一句,右边前两项的乘积,就是课程里引入的值!这就回答了悬念 3 提出的问题:为什么得到后一层的值之后,要乘以前一层的 a 值来得到偏微分?答案是:因为它们分别是偏微分式子展开后的两个乘数。
接下来我们看第二项:这不就是对sigmoid函数求导吗?之前我们遇到过的g'(z),它的出处原来是这里!
我们经过计算后会得到这样一个式子:
恰好,算出来也是这个式子,所以我们就用后面这个更好算的式子来计算了。这就是计算公式的后半部分,的来源。
JOB DONE...........67%
现在来到第一项偏微分。对于中间层的结点,这个偏微分并不好算。(也能算,需要继续使用链式法则展开成更多的项!最后算出来就是上面计算公式的前半部分。)但是我们只想知道关于最后输出层的情况。那就简单多了!上面争议部分中,那位楼主的小伙伴已经给出了cost函数对于输出层求偏微分的结果:
JOB DONE...........100%
那么说到底输出层的到底等于多少?
等于前两项的乘积:
原来地球是这个样子滴……
结论:
Andrew Ng给的公式没有错。他只是把复杂的推倒,不是,推导过程省略了。但是这样一来容易产生误解。很多人以为输出层的偏差量就是计算值减实际值这么简单,其实是碰巧才这么简单的。还有很多人说为什么这个值和很多别的网站,包括维基百科上说的不一样啊?因为很多别的网站包括维基百科,cost函数用的是线性回归的那种,。它的偏微分就和逻辑回归的cost函数有差别了。具体地说,就差在分母的那一项上。
tl;nr:男神没搞错。微积分很复杂。而猫咪依旧是可爱的。