零基礎(chǔ)入門深度學(xué)習(xí)(7) - 遞歸神經(jīng)網(wǎng)絡(luò)

往期回顧

在前面的文章中棍潘,我們介紹了循環(huán)神經(jīng)網(wǎng)絡(luò)恃鞋,它可以用來處理包含序列結(jié)構(gòu)的信息崖媚。然而,除此之外恤浪,信息往往還存在著諸如樹結(jié)構(gòu)畅哑、圖結(jié)構(gòu)等更復(fù)雜的結(jié)構(gòu)。對于這種復(fù)雜的結(jié)構(gòu)水由,循環(huán)神經(jīng)網(wǎng)絡(luò)就無能為力了敢课。本文介紹一種更為強(qiáng)大、復(fù)雜的神經(jīng)網(wǎng)絡(luò):遞歸神經(jīng)網(wǎng)絡(luò) (Recursive Neural Network, RNN)绷杜,以及它的訓(xùn)練算法BPTS (Back Propagation Through Structure)直秆。顧名思義,遞歸神經(jīng)網(wǎng)絡(luò)(巧合的是鞭盟,它的縮寫和循環(huán)神經(jīng)網(wǎng)絡(luò)一樣圾结,也是RNN)可以處理諸如樹、圖這樣的遞歸結(jié)構(gòu)齿诉。在文章的最后筝野,我們將實(shí)現(xiàn)一個(gè)遞歸神經(jīng)網(wǎng)絡(luò),并介紹它的幾個(gè)應(yīng)用場景粤剧。

遞歸神經(jīng)網(wǎng)絡(luò)是啥

因?yàn)樯窠?jīng)網(wǎng)絡(luò)的輸入層單元個(gè)數(shù)是固定的歇竟,因此必須用循環(huán)或者遞歸的方式來處理長度可變的輸入。循環(huán)神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)了前者抵恋,通過將長度不定的輸入分割為等長度的小塊焕议,然后再依次的輸入到網(wǎng)絡(luò)中,從而實(shí)現(xiàn)了神經(jīng)網(wǎng)絡(luò)對變長輸入的處理弧关。一個(gè)典型的例子是盅安,當(dāng)我們處理一句話的時(shí)候,我們可以把一句話看作是詞組成的序列世囊,然后别瞭,每次向循環(huán)神經(jīng)網(wǎng)絡(luò)輸入一個(gè)詞,如此循環(huán)直至整句話輸入完畢株憾,循環(huán)神經(jīng)網(wǎng)絡(luò)將產(chǎn)生對應(yīng)的輸出蝙寨。如此,我們就能處理任意長度的句子了嗤瞎。入下圖所示:

然而墙歪,有時(shí)候把句子看做是詞的序列是不夠的,比如下面這句話『兩個(gè)外語學(xué)院的學(xué)生』:

上圖顯示了這句話的兩個(gè)不同的語法解析樹猫胁∠湟冢可以看出來這句話有歧義,不同的語法解析樹則對應(yīng)了不同的意思弃秆。一個(gè)是『兩個(gè)外語學(xué)院的/學(xué)生』届惋,也就是學(xué)生可能有許多髓帽,但他們來自于兩所外語學(xué)校;另一個(gè)是『兩個(gè)/外語學(xué)院的學(xué)生』脑豹,也就是只有兩個(gè)學(xué)生郑藏,他們是外語學(xué)院的。為了能夠讓模型區(qū)分出兩個(gè)不同的意思瘩欺,我們的模型必須能夠按照樹結(jié)構(gòu)去處理信息必盖,而不是序列,這就是遞歸神經(jīng)網(wǎng)絡(luò)的作用俱饿。當(dāng)面對按照樹/圖結(jié)構(gòu)處理信息更有效的任務(wù)時(shí)歌粥,遞歸神經(jīng)網(wǎng)絡(luò)通常都會(huì)獲得不錯(cuò)的結(jié)果。

遞歸神經(jīng)網(wǎng)絡(luò)可以把一個(gè)樹/圖結(jié)構(gòu)信息編碼為一個(gè)向量拍埠,也就是把信息映射到一個(gè)語義向量空間中失驶。這個(gè)語義向量空間滿足某類性質(zhì),比如語義相似的向量距離更近枣购。也就是說嬉探,如果兩句話(盡管內(nèi)容不同)它的意思是相似的,那么把它們分別編碼后的兩個(gè)向量的距離也相近棉圈;反之涩堤,如果兩句話的意思截然不同,那么編碼后向量的距離則很遠(yuǎn)分瘾。如下圖所示:

從上圖我們可以看到胎围,遞歸神經(jīng)網(wǎng)絡(luò)將所有的詞、句都映射到一個(gè)2維向量空間中芹敌。句子『the country of my birth』和句子『the place where I was born』的意思是非常接近的痊远,所以表示它們的兩個(gè)向量在向量空間中的距離很近。另外兩個(gè)詞『Germany』和『France』因?yàn)楸硎镜亩际堑攸c(diǎn)氏捞,它們的向量與上面兩句話的向量的距離,就比另外兩個(gè)表示時(shí)間的詞『Monday』和『Tuesday』的向量的距離近得多冒版。這樣液茎,通過向量的距離,就得到了一種語義的表示辞嗡。

上圖還顯示了自然語言可組合的性質(zhì):詞可以組成句捆等、句可以組成段落、段落可以組成篇章续室,而更高層的語義取決于底層的語義以及它們的組合方式栋烤。遞歸神經(jīng)網(wǎng)絡(luò)是一種表示學(xué)習(xí),它可以將詞挺狰、句明郭、段买窟、篇按照他們的語義映射到同一個(gè)向量空間中,也就是把可組合(樹/圖結(jié)構(gòu))的信息表示為一個(gè)個(gè)有意義的向量薯定。比如上面這個(gè)例子始绍,遞歸神經(jīng)網(wǎng)絡(luò)把句子"the country of my birth"表示為二維向量[1,5]。有了這個(gè)『編碼器』之后话侄,我們就可以以這些有意義的向量為基礎(chǔ)去完成更高級的任務(wù)(比如情感分析等)亏推。如下圖所示,遞歸神經(jīng)網(wǎng)絡(luò)在做情感分析時(shí)年堆,可以比較好的處理否定句吞杭,這是勝過其他一些模型的:

在上圖中,藍(lán)色表示正面評價(jià)变丧,紅色表示負(fù)面評價(jià)芽狗。每個(gè)節(jié)點(diǎn)是一個(gè)向量,這個(gè)向量表達(dá)了以它為根的子樹的情感評價(jià)锄贷。比如"intelligent humor"是正面評價(jià)译蒂,而"care about cleverness wit or any other kind of intelligent humor"是中性評價(jià)。我們可以看到谊却,模型能夠正確的處理doesn't的含義柔昼,將正面評價(jià)轉(zhuǎn)變?yōu)樨?fù)面評價(jià)。

盡管遞歸神經(jīng)網(wǎng)絡(luò)具有更為強(qiáng)大的表示能力炎辨,但是在實(shí)際應(yīng)用中并不太流行捕透。其中一個(gè)主要原因是,遞歸神經(jīng)網(wǎng)絡(luò)的輸入是樹/圖結(jié)構(gòu)碴萧,而這種結(jié)構(gòu)需要花費(fèi)很多人工去標(biāo)注乙嘀。想象一下,如果我們用循環(huán)神經(jīng)網(wǎng)絡(luò)處理句子破喻,那么我們可以直接把句子作為輸入虎谢。然而,如果我們用遞歸神經(jīng)網(wǎng)絡(luò)處理句子曹质,我們就必須把每個(gè)句子標(biāo)注為語法解析樹的形式婴噩,這無疑要花費(fèi)非常大的精力。很多時(shí)候羽德,相對于遞歸神經(jīng)網(wǎng)絡(luò)能夠帶來的性能提升几莽,這個(gè)投入是不太劃算的。

我們已經(jīng)基本了解了遞歸神經(jīng)網(wǎng)絡(luò)是做什么用的宅静,接下來章蚣,我們將探討它的算法細(xì)節(jié)。

遞歸神經(jīng)網(wǎng)絡(luò)的前向計(jì)算

接下來姨夹,我們詳細(xì)介紹一下遞歸神經(jīng)網(wǎng)絡(luò)是如何處理樹/圖結(jié)構(gòu)的信息的纤垂。在這里矾策,我們以處理樹型信息為例進(jìn)行介紹。

遞歸神經(jīng)網(wǎng)絡(luò)的輸入是兩個(gè)子節(jié)點(diǎn)(也可以是多個(gè))洒忧,輸出就是將這兩個(gè)子節(jié)點(diǎn)編碼后產(chǎn)生的父節(jié)點(diǎn)蝴韭,父節(jié)點(diǎn)的維度和每個(gè)子節(jié)點(diǎn)是相同的。如下圖所示:

\mathbf{c}_1\mathbf{c}_2分別是表示兩個(gè)子節(jié)點(diǎn)的向量熙侍,\mathbf{p}是表示父節(jié)點(diǎn)的向量榄鉴。子節(jié)點(diǎn)和父節(jié)點(diǎn)組成一個(gè)全連接神經(jīng)網(wǎng)絡(luò),也就是子節(jié)點(diǎn)的每個(gè)神經(jīng)元都和父節(jié)點(diǎn)的每個(gè)神經(jīng)元兩兩相連蛉抓。我們用矩陣W表示這些連接上的權(quán)重庆尘,它的維度將是d\times 2d,其中巷送,d表示每個(gè)節(jié)點(diǎn)的維度驶忌。父節(jié)點(diǎn)的計(jì)算公式可以寫成:

\mathbf{p} = tanh(W\begin{bmatrix}\mathbf{c}_1\\\mathbf{c}_2\end{bmatrix}+\mathbf)\qquad(式1)

在上式中笑跛,tanh是激活函數(shù)(當(dāng)然也可以用其它的激活函數(shù))付魔,\mathbf是偏置項(xiàng)飞蹂,它也是一個(gè)維度為d的向量几苍。如果讀過前面的文章,相信大家已經(jīng)非常熟悉這些計(jì)算了陈哑,在此不做過多的解釋了妻坝。

然后,我們把產(chǎn)生的父節(jié)點(diǎn)的向量和其他子節(jié)點(diǎn)的向量再次作為網(wǎng)絡(luò)的輸入惊窖,再次產(chǎn)生它們的父節(jié)點(diǎn)刽宪。如此遞歸下去,直至整棵樹處理完畢界酒。最終圣拄,我們將得到根節(jié)點(diǎn)的向量,我們可以認(rèn)為它是對整棵樹的表示毁欣,這樣我們就實(shí)現(xiàn)了把樹映射為一個(gè)向量售担。在下圖中,我們使用遞歸神經(jīng)網(wǎng)絡(luò)處理一棵樹署辉,最終得到的向量\mathbf{p}_3,就是對整棵樹的表示:

舉個(gè)例子岩四,我們使用遞歸神將網(wǎng)絡(luò)將『兩個(gè)外語學(xué)校的學(xué)生』映射為一個(gè)向量哭尝,如下圖所示:

最后得到的向量\mathbf{p}_3就是對整個(gè)句子『兩個(gè)外語學(xué)校的學(xué)生』的表示。由于整個(gè)結(jié)構(gòu)是遞歸的剖煌,不僅僅是根節(jié)點(diǎn)材鹦,事實(shí)上每個(gè)節(jié)點(diǎn)都是以其為根的子樹的表示逝淹。比如,在左邊的這棵樹中桶唐,向量\mathbf{p}_2是短語『外語學(xué)院的學(xué)生』的表示栅葡,而向量\mathbf{p}_1是短語『外語學(xué)院的』的表示。

式1就是遞歸神經(jīng)網(wǎng)絡(luò)的前向計(jì)算算法尤泽。它和全連接神經(jīng)網(wǎng)絡(luò)的計(jì)算沒有什么區(qū)別欣簇,只是在輸入的過程中需要根據(jù)輸入的樹結(jié)構(gòu)依次輸入每個(gè)子節(jié)點(diǎn)。

需要特別注意的是坯约,遞歸神經(jīng)網(wǎng)絡(luò)的權(quán)重W和偏置項(xiàng)\mathbf熊咽在所有的節(jié)點(diǎn)都是共享的。

遞歸神經(jīng)網(wǎng)絡(luò)的訓(xùn)練

遞歸神經(jīng)網(wǎng)絡(luò)的訓(xùn)練算法和循環(huán)神經(jīng)網(wǎng)絡(luò)類似闹丐,兩者不同之處在于横殴,前者需要將殘差\delta從根節(jié)點(diǎn)反向傳播到各個(gè)子節(jié)點(diǎn),而后者是將殘差\delta從當(dāng)前時(shí)刻t_k反向傳播到初始時(shí)刻t_1卿拴。

下面衫仑,我們介紹適用于遞歸神經(jīng)網(wǎng)絡(luò)的訓(xùn)練算法,也就是BPTS算法堕花。

誤差項(xiàng)的傳遞

首先文狱,我們先推導(dǎo)將誤差從父節(jié)點(diǎn)傳遞到子節(jié)點(diǎn)的公式,如下圖:

定義\delta_p為誤差函數(shù)E相對于父節(jié)點(diǎn)p的加權(quán)輸入\mathbf{net}_p的導(dǎo)數(shù)航徙,即:

\delta_p\overset{def}{=}\frac{\partial{E}}{\partial{\mathbf{net}_p}}

設(shè)\mathbf{net}_p是父節(jié)點(diǎn)的加權(quán)輸入如贷,則

\mathbf{net}_p=W\begin{bmatrix}\mathbf{c}_1\\\mathbf{c}_2\end{bmatrix}+\mathbf

在上述式子里到踏,\mathbf{net}_p杠袱、\mathbf{c}_1\mathbf{c}_2都是向量窝稿,而W是矩陣楣富。為了看清楚它們的關(guān)系,我們將其展開:

\begin{align} \begin{bmatrix} net_{p_1}\\ net_{p_2}\\ ...\\ net_{p_n} \end{bmatrix}&= \begin{bmatrix} w_{p_1c_{11}}&w_{p_1c_{12}}&...&w_{p_1c_{1n}}&w_{p_1c_{21}}&w_{p_1c_{22}}&...&w_{p_1c_{2n}}\\ w_{p_2c_{11}}&w_{p_2c_{12}}&...&w_{p_2c_{1n}}&w_{p_2c_{21}}&w_{p_2c_{22}}&...&w_{p_2c_{2n}}\\ ...\\ w_{p_nc_{11}}&w_{p_nc_{12}}&...&w_{p_nc_{1n}}&w_{p_nc_{21}}&w_{p_nc_{22}}&...&w_{p_nc_{2n}}\\ \end{bmatrix} \begin{bmatrix} net_{c_{11}}\\ net_{c_{12}}\\ ...\\ net_{c_{1n}}\\ net_{c_{21}}\\ net_{c_{22}}\\ ...\\ net_{c_{2n}} \end{bmatrix} \end{align}

在上面的公式中伴榔,p_i表示父節(jié)點(diǎn)p的第i個(gè)分量纹蝴;c_{1i}表示c_1子節(jié)點(diǎn)的第i個(gè)分量;c_{2i}表示c_2子節(jié)點(diǎn)的第i個(gè)分量踪少;w_{p_ic_{jk}}表示子節(jié)點(diǎn)c_j的第k個(gè)分量到父節(jié)點(diǎn)p的第i個(gè)分量的的權(quán)重塘安。根據(jù)上面展開后的矩陣乘法形式,我們不難看出援奢,對于子節(jié)點(diǎn)c_{jk}來說兼犯,它會(huì)影響父節(jié)點(diǎn)所有的分量。因此,我們求誤差函數(shù)E對c_{jk}的導(dǎo)數(shù)時(shí)切黔,必須用到全導(dǎo)數(shù)公式砸脊,也就是:

\begin{align} \frac{\partial{E}}{\partial{c_{jk}}}&=\sum_i{\frac{\partial{E}}{\partial{net_{p_i}}}}\frac{\partial{net_{p_i}}}{\partial{c_{jk}}}\\ &=\sum_i{\delta_{p_i}}w_{p_ic_{jk}} \end{align}

有了上式,我們就可以把它表示為矩陣形式纬霞,從而得到一個(gè)向量化表達(dá):

\begin{align} \frac{\partial{E}}{\partial{\mathbf{c}_j}}&=U_j\delta_p \end{align}

其中凌埂,矩陣U_j是從矩陣W中提取部分元素組成的矩陣。其單元為:

u_{j_{ik}}=w_{p_k}c_{ji}

上式看上去可能會(huì)讓人暈菜诗芜,從下圖瞳抓,我們可以直觀的看到U_j到底是啥。首先我們把W矩陣拆分為兩個(gè)矩陣W_1W_2绢陌,如下圖所示:

顯然挨下,子矩陣W_1W_2分別對應(yīng)子節(jié)點(diǎn)\mathbf{c}_1\mathbf{c}_2的到父節(jié)點(diǎn)\mathbf{p}權(quán)重。則矩陣U_j為:

U_j=W_j^T

也就是說脐湾,將誤差項(xiàng)反向傳遞到相應(yīng)子節(jié)點(diǎn)\mathbf{c}_j的矩陣U_j就是其對應(yīng)權(quán)重矩陣W_j的轉(zhuǎn)置臭笆。

現(xiàn)在,我們設(shè)\mathbf{net}_{c_j}是子節(jié)點(diǎn)\mathbf{c}_j的加權(quán)輸入秤掌,f是子節(jié)點(diǎn)c的激活函數(shù)愁铺,則:

\begin{align} \mathbf{c}_j=f(\mathbf{net}_{c_j})\\ \end{align}

這樣,我們得到:

\begin{align} \delta_{c_j}&=\frac{\partial{E}}{\partial{\mathbf{net}_{c_j}}}\\ &=\frac{\partial{E}}{\partial{\mathbf{c}_j}}\frac{\partial{\mathbf{c}_j}}{\partial{\mathbf{net}_{c_j}}}\\ &=W_j^T\delta_p\circ f'(\mathbf{net}_{c_j}) \end{align}

如果我們將不同子節(jié)點(diǎn)\mathbf{c}_j對應(yīng)的誤差項(xiàng)\delta_{c_j}連接成一個(gè)向量\delta_c=\begin{bmatrix}\delta_{c_1}\\\delta_{c_2}\end{bmatrix}闻鉴。那么茵乱,上式可以寫成:

\delta_c=W^T\delta_p\circ f'(\mathbf{net}_c)\qquad(式2)

式2就是將誤差項(xiàng)從父節(jié)點(diǎn)傳遞到其子節(jié)點(diǎn)的公式。注意孟岛,上式中的\mathbf{net}_c也是將兩個(gè)子節(jié)點(diǎn)的加權(quán)輸入\mathbf{net}_{c_1}\mathbf{net}_{c_2}連在一起的向量瓶竭。

有了傳遞一層的公式,我們就不難寫出逐層傳遞的公式渠羞。

上圖是在樹型結(jié)構(gòu)中反向傳遞誤差項(xiàng)的全景圖斤贰,反復(fù)應(yīng)用式2,在已知\delta_p^{(3)}的情況下次询,我們不難算出\delta_p^{(1)}為:

\begin{align} \delta^{(2)}&=W^T\delta_p^{(3)}\circ f'(\mathbf{net}^{(2)})\\ \delta_p^{(2)}&=[\delta^{(2)}]_p\\ \delta^{(1)}&=W^T\delta_p^{(2)}\circ f'(\mathbf{net}^{(1)})\\ \delta_p^{(1)}&=[\delta^{(1)}]\\ \end{align}

在上面的公式中荧恍,\delta^{(2)}=\begin{bmatrix}\delta_c^{(2)}\\\delta_p^{(2)}\end{bmatrix}[\delta^{(2)}]_p表示取向量\delta^{(2)}屬于節(jié)點(diǎn)p的部分屯吊。

權(quán)重梯度的計(jì)算

根據(jù)加權(quán)輸入的計(jì)算公式:

\mathbf{net}_p^{(l)}=W\mathbf{c}^{(l)}+\mathbf绅作

其中鸭津,\mathbf{net}_p^{(l)}表示第l層的父節(jié)點(diǎn)的加權(quán)輸入黄锤,\mathbf{c}^{(l)}表示第l層的子節(jié)點(diǎn)结执。W是權(quán)重矩陣,\mathbf蔽介是偏置項(xiàng)淮腾。將其展開可得:

\mathbf{net}_{p_j}^l=\sum_i{w_{ji}c_i^l}+b_j

那么糟需,我們可以求得誤差函數(shù)在第l層對權(quán)重的梯度為:

\begin{align} \frac{\partial{E}}{\partial{w_{ji}^{(l)}}}&=\frac{\partial{E}}{\partial{\mathbf{net}_{p_j}^{(l)}}}\frac{\partial{\mathbf{net}_{p_j}^{(l)}}}{\partial{w_{ji}^{(l)}}}\\ &=\delta_{p_j}^{(l)}c_i^{(l)}\\ \end{align}

上式是針對一個(gè)權(quán)重項(xiàng)w_{ji}的公式,現(xiàn)在需要把它擴(kuò)展為對所有的權(quán)重項(xiàng)的公式谷朝。我們可以把上式寫成矩陣的形式(在下面的公式中,m=2n):

\begin{align} \frac{\partial{E}}{\partial{W^{(l)}}}&= \begin{bmatrix} \frac{\partial{E}}{\partial{w_{11}^{(l)}}}& \frac{\partial{E}}{\partial{w_{12}^{(l)}}}& ...& \frac{\partial{E}}{\partial{w_{1m}^{(l)}}}\\ \frac{\partial{E}}{\partial{w_{21}^{(l)}}}& \frac{\partial{E}}{\partial{w_{22}^{(l)}}}& ...& \frac{\partial{E}}{\partial{w_{2m}^{(l)}}}\\ \\\\\\...\\\\ \frac{\partial{E}}{\partial{w_{n1}^{(l)}}}& \frac{\partial{E}}{\partial{w_{n2}^{(l)}}}& ...& \frac{\partial{E}}{\partial{w_{nm}^{(l)}}}\\ \end{bmatrix}\\ &= \begin{bmatrix} \delta_{p_1}^{(l)}c_1^l&\delta_{p_1}^{(l)}c_2^l&...&\delta_{p_1}^lc_m^{(l)}\\ \delta_{p_2}^{(l)}c_1^l&\delta_{p_2}^{(l)}c_2^l&...&\delta_{p_2}^lc_m^{(l)}\\ \\\\\\...\\\\ \delta_{p_n}^{(l)}c_1^l&\delta_{p_n}^{(l)}lc_2^l&...&\delta_{p_n}^lc_m^{(l)}\\ \end{bmatrix}\\ &=\delta^{{(l)}}(\mathbf{c}^{(l)})^T\qquad(式3) \end{align}

式3就是第l層權(quán)重項(xiàng)的梯度計(jì)算公式武花。我們知道圆凰,由于權(quán)重W是在所有層共享的,所以和循環(huán)神經(jīng)網(wǎng)絡(luò)一樣体箕,遞歸神經(jīng)網(wǎng)絡(luò)的最終的權(quán)重梯度是各個(gè)層權(quán)重梯度之和专钉。即:

\frac{\partial{E}}{\partial{W}}=\sum_l\frac{\partial{E}}{\partial{W^{(l)}}}\qquad(式4)

因?yàn)?strong>循環(huán)神經(jīng)網(wǎng)絡(luò)的證明過程已經(jīng)在零基礎(chǔ)入門深度學(xué)習(xí)(4) - 卷積神經(jīng)網(wǎng)絡(luò)一文中給出,因此累铅,遞歸神經(jīng)網(wǎng)絡(luò)『為什么最終梯度是各層梯度之和』的證明就留給讀者自行完成啦跃须。

接下來,我們求偏置項(xiàng)\mathbf娃兽的梯度計(jì)算公式菇民。先計(jì)算誤差函數(shù)對第l層偏置項(xiàng)\mathbf^{(l)}的梯度:

\begin{align} \frac{\partial{E}}{\partial{b_j^{(l)}}}&=\frac{\partial{E}}{\partial{\mathbf{net}_{p_j}^{(l)}}}\frac{\partial{\mathbf{net}_{p_j}^{(l)}}}{\partial{b_j^{(l)}}}\\ &=\delta_{p_j}^{(l)}\\ \end{align}

把上式擴(kuò)展為矩陣的形式:

\begin{align} \frac{\partial{E}}{\partial{\mathbf投储^{(l)}}}&= \begin{bmatrix} \frac{\partial{E}}{\partial{b_1^{(l)}}}\\ \frac{\partial{E}}{\partial{b_2^{(l)}}}\\ \\\\\\...\\\\ \frac{\partial{E}}{\partial{b_n^{(l)}}}\\ \end{bmatrix}\\ &= \begin{bmatrix} \delta_{p_1}^{(l)}\\ \delta_{p_2}^{(l)}\\ \\\\\\...\\\\ \delta_{p_n}^{(l)}\\ \end{bmatrix}\\ &=\delta_p^{{(l)}}\qquad(式5) \end{align}

式5是第l層偏置項(xiàng)的梯度第练,那么最終的偏置項(xiàng)梯度是各個(gè)層偏置項(xiàng)梯度之和,即:

\frac{\partial{E}}{\partial{\mathbf玛荞}}=\sum_l\frac{\partial{E}}{\partial{\mathbf娇掏^{(l)}}}\qquad(式6)

權(quán)重更新

如果使用梯度下降優(yōu)化算法,那么權(quán)重更新公式為:

W\gets W + \eta\frac{\partial{E}}{\partial{W}}

其中勋眯,\eta是學(xué)習(xí)速率常數(shù)婴梧。把式4帶入到上式,即可完成權(quán)重的更新客蹋。同理浮还,偏置項(xiàng)的更新公式為:

\mathbf\gets \mathbf + \eta\frac{\partial{E}}{\partial{\mathbf}}

式6帶入到上式,即可完成偏置項(xiàng)的更新。

這就是遞歸神經(jīng)網(wǎng)絡(luò)的訓(xùn)練算法BPTS华糖。由于我們有了前面幾篇文章的基礎(chǔ)十办,相信讀者們理解BPTS算法也會(huì)比較容易再扭。

遞歸神經(jīng)網(wǎng)絡(luò)的實(shí)現(xiàn)

現(xiàn)在罢荡,我們實(shí)現(xiàn)一個(gè)處理樹型結(jié)構(gòu)的遞歸神經(jīng)網(wǎng)絡(luò)

在文件的開頭昂羡,加入如下代碼:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-


import numpy as np
from cnn import IdentityActivator

上述四行代碼非常簡單,沒有什么需要解釋的潜支。IdentityActivator激活函數(shù)是在我們介紹卷積神經(jīng)網(wǎng)絡(luò)時(shí)寫的貌笨,現(xiàn)在引用一下它遭商。

我們首先定義一個(gè)樹節(jié)點(diǎn)結(jié)構(gòu)劫流,這樣,我們就可以用它保存卷積神經(jīng)網(wǎng)絡(luò)生成的整棵樹:

class TreeNode(object):
    def __init__(self, data, children=[], children_data=[]):
        self.parent = None
        self.children = children
        self.children_data = children_data
        self.data = data
        for child in children:
            child.parent = self

接下來,我們把遞歸神經(jīng)網(wǎng)絡(luò)的實(shí)現(xiàn)代碼都放在RecursiveLayer類中,下面是這個(gè)類的構(gòu)造函數(shù):

# 遞歸神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)
class RecursiveLayer(object):
    def __init__(self, node_width, child_count, 
                 activator, learning_rate):
        '''
        遞歸神經(jīng)網(wǎng)絡(luò)構(gòu)造函數(shù)
        node_width: 表示每個(gè)節(jié)點(diǎn)的向量的維度
        child_count: 每個(gè)父節(jié)點(diǎn)有幾個(gè)子節(jié)點(diǎn)
        activator: 激活函數(shù)對象
        learning_rate: 梯度下降算法學(xué)習(xí)率
        '''
        self.node_width = node_width
        self.child_count = child_count
        self.activator = activator
        self.learning_rate = learning_rate
        # 權(quán)重?cái)?shù)組W
        self.W = np.random.uniform(-1e-4, 1e-4,
            (node_width, node_width * child_count))
        # 偏置項(xiàng)b
        self.b = np.zeros((node_width, 1))
        # 遞歸神經(jīng)網(wǎng)絡(luò)生成的樹的根節(jié)點(diǎn)
        self.root = None

下面是前向計(jì)算的實(shí)現(xiàn):

    def forward(self, *children):
        '''
        前向計(jì)算
        '''
        children_data = self.concatenate(children)
        parent_data = self.activator.forward(
            np.dot(self.W, children_data) + self.b
        )
        self.root = TreeNode(parent_data, children
                            , children_data)

forward函數(shù)接收一系列的樹節(jié)點(diǎn)對象作為輸入康栈,然后贰逾,遞歸神經(jīng)網(wǎng)絡(luò)將這些樹節(jié)點(diǎn)作為子節(jié)點(diǎn)氯迂,并計(jì)算它們的父節(jié)點(diǎn)。最后言缤,將計(jì)算的父節(jié)點(diǎn)保存在self.root變量中嚼蚀。

上面用到的concatenate函數(shù),是將各個(gè)子節(jié)點(diǎn)中的數(shù)據(jù)拼接成一個(gè)長向量管挟,其代碼如下:

    def concatenate(self, tree_nodes):
        '''
        將各個(gè)樹節(jié)點(diǎn)中的數(shù)據(jù)拼接成一個(gè)長向量
        '''
        concat = np.zeros((0,1))
        for node in tree_nodes:
            concat = np.concatenate((concat, node.data))
        return concat

下面是反向傳播算法BPTS的實(shí)現(xiàn):

    def backward(self, parent_delta):
        '''
        BPTS反向傳播算法
        '''
        self.calc_delta(parent_delta, self.root)
        self.W_grad, self.b_grad = self.calc_gradient(self.root)

    def calc_delta(self, parent_delta, parent):
        '''
        計(jì)算每個(gè)節(jié)點(diǎn)的delta
        '''
        parent.delta = parent_delta
        if parent.children:
            # 根據(jù)式2計(jì)算每個(gè)子節(jié)點(diǎn)的delta
            children_delta = np.dot(self.W.T, parent_delta) * (
                self.activator.backward(parent.children_data)
            )
            # slices = [(子節(jié)點(diǎn)編號(hào)轿曙,子節(jié)點(diǎn)delta起始位置,子節(jié)點(diǎn)delta結(jié)束位置)]
            slices = [(i, i * self.node_width, 
                        (i + 1) * self.node_width)
                        for i in range(self.child_count)]
            # 針對每個(gè)子節(jié)點(diǎn)哮独,遞歸調(diào)用calc_delta函數(shù)
            for s in slices:
                self.calc_delta(children_delta[s[1]:s[2]], 
                                parent.children[s[0]])

    def calc_gradient(self, parent):
        '''
        計(jì)算每個(gè)節(jié)點(diǎn)權(quán)重的梯度拳芙,并將它們求和,得到最終的梯度
        '''
        W_grad = np.zeros((self.node_width, 
                            self.node_width * self.child_count))
        b_grad = np.zeros((self.node_width, 1))
        if not parent.children:
            return W_grad, b_grad
        parent.W_grad = np.dot(parent.delta, parent.children_data.T)
        parent.b_grad = parent.delta
        W_grad += parent.W_grad
        b_grad += parent.b_grad
        for child in parent.children:
            W, b = self.calc_gradient(child)
            W_grad += W
            b_grad += b
        return W_grad, b_grad

在上述算法中皮璧,calc_delta函數(shù)和calc_gradient函數(shù)分別計(jì)算各個(gè)節(jié)點(diǎn)的誤差項(xiàng)\delta以及最終的梯度舟扎。它們都采用遞歸算法,先序遍歷整個(gè)樹悴务,并逐一完成每個(gè)節(jié)點(diǎn)的計(jì)算睹限。

下面是梯度下降算法的實(shí)現(xiàn)(沒有weight decay),這個(gè)非常簡單:

    def update(self):
        '''
        使用SGD算法更新權(quán)重
        '''
        self.W -= self.learning_rate * self.W_grad
        self.b -= self.learning_rate * self.b_grad

以上就是遞歸神經(jīng)網(wǎng)絡(luò)的實(shí)現(xiàn)讯檐,總共100行左右羡疗,和上一篇文章的LSTM相比簡單多了。

最后别洪,我們用梯度檢查來驗(yàn)證程序的正確性:

def gradient_check():
    '''
    梯度檢查
    '''
    # 設(shè)計(jì)一個(gè)誤差函數(shù)叨恨,取所有節(jié)點(diǎn)輸出項(xiàng)之和
    error_function = lambda o: o.sum()
    
    rnn = RecursiveLayer(2, 2, IdentityActivator(), 1e-3)

    # 計(jì)算forward值
    x, d = data_set()
    rnn.forward(x[0], x[1])
    rnn.forward(rnn.root, x[2])
    
    # 求取sensitivity map
    sensitivity_array = np.ones((rnn.node_width, 1),
                                dtype=np.float64)
    # 計(jì)算梯度
    rnn.backward(sensitivity_array)
    
    # 檢查梯度
    epsilon = 10e-4
    for i in range(rnn.W.shape[0]):
        for j in range(rnn.W.shape[1]):
            rnn.W[i,j] += epsilon
            rnn.reset_state()
            rnn.forward(x[0], x[1])
            rnn.forward(rnn.root, x[2])
            err1 = error_function(rnn.root.data)
            rnn.W[i,j] -= 2*epsilon
            rnn.reset_state()
            rnn.forward(x[0], x[1])
            rnn.forward(rnn.root, x[2])
            err2 = error_function(rnn.root.data)
            expect_grad = (err1 - err2) / (2 * epsilon)
            rnn.W[i,j] += epsilon
            print 'weights(%d,%d): expected - actural %.4e - %.4e' % (
                i, j, expect_grad, rnn.W_grad[i,j])
    return rnn

下面是梯度檢查的結(jié)果,完全正確挖垛,OH YEAH痒钝!

遞歸神經(jīng)網(wǎng)絡(luò)的應(yīng)用

自然語言和自然場景解析

在自然語言處理任務(wù)中秉颗,如果我們能夠?qū)崿F(xiàn)一個(gè)解析器,將自然語言解析為語法樹送矩,那么毫無疑問蚕甥,這將大大提升我們對自然語言的處理能力。解析器如下所示:

可以看出栋荸,遞歸神經(jīng)網(wǎng)絡(luò)能夠完成句子的語法分析菇怀,并產(chǎn)生一個(gè)語法解析樹。

除了自然語言之外晌块,自然場景也具有可組合的性質(zhì)爱沟。因此,我們可以用類似的模型完成自然場景的解析匆背,如下圖所示:

兩種不同的場景钥顽,可以用相同的遞歸神經(jīng)網(wǎng)絡(luò)模型來實(shí)現(xiàn)。我們以第一個(gè)場景靠汁,自然語言解析為例。

我們希望將一句話逐字輸入到神經(jīng)網(wǎng)絡(luò)中闽铐,然后蝶怔,神經(jīng)網(wǎng)絡(luò)返回一個(gè)解析好的樹。為了做到這一點(diǎn)兄墅,我們需要給神經(jīng)網(wǎng)絡(luò)再加上一層踢星,負(fù)責(zé)打分。分?jǐn)?shù)越高隙咸,說明兩個(gè)子節(jié)點(diǎn)結(jié)合更加緊密沐悦,分?jǐn)?shù)越低,說明兩個(gè)子節(jié)點(diǎn)結(jié)合更松散五督。如下圖所示:

一旦這個(gè)打分函數(shù)訓(xùn)練好了(也就是矩陣U的各項(xiàng)值變?yōu)楹线m的值)藏否,我們就可以利用貪心算法來實(shí)現(xiàn)句子的解析。第一步充包,我們先將詞按照順序兩兩輸入神經(jīng)網(wǎng)絡(luò)副签,得到第一組打分:

我們發(fā)現(xiàn),現(xiàn)在分?jǐn)?shù)最高的是第一組基矮,The cat淆储,說明它們的結(jié)合是最緊密的。這樣家浇,我們可以先將它們組合為一個(gè)節(jié)點(diǎn)本砰。然后,再次兩兩計(jì)算相鄰子節(jié)點(diǎn)的打分:

現(xiàn)在钢悲,分?jǐn)?shù)最高的是最后一組点额,the mat舔株。于是,我們將它們組合為一個(gè)節(jié)點(diǎn)咖楣,再兩兩計(jì)算相鄰節(jié)點(diǎn)的打分:

這時(shí)督笆,我們發(fā)現(xiàn)最高的分?jǐn)?shù)是on the mat,把它們組合為一個(gè)節(jié)點(diǎn)诱贿,繼續(xù)兩兩計(jì)算相鄰節(jié)點(diǎn)的打分......最終娃肿,我們就能夠得到整個(gè)解析樹:

現(xiàn)在,我們困惑這樣牛逼的打分函數(shù)score是怎樣訓(xùn)練出來的呢珠十?我們需要定義一個(gè)目標(biāo)函數(shù)料扰。這里,我們使用Max-Margin目標(biāo)函數(shù)焙蹭。它的定義如下:

J(\theta)=max(0, \sum_i\underset{y\in A(x_i)}{max}(s(x_i,y)+\Delta(y,y_i))-s(x_i,y_i))

在上式中晒杈,x_iy_i分別表示第i個(gè)訓(xùn)練樣本的輸入和標(biāo)簽孔厉,注意這里的標(biāo)簽y_i是一棵解析樹拯钻。s(x_i,y_i)就是打分函數(shù)s對第i個(gè)訓(xùn)練樣本的打分。因?yàn)橛?xùn)練樣本的標(biāo)簽肯定是正確的撰豺,我們希望s對它的打分越高越好粪般,也就是s(x_i,y_i)越大越好。A(x_1)是所有可能的解析樹的集合污桦,而s(x_i,y)則是對某個(gè)可能的解析樹y的打分亩歹。\Delta(y,y_i)是對錯(cuò)誤的懲罰。也就是說凡橱,如果某個(gè)解析樹y和標(biāo)簽y_i是一樣的小作,那么\Delta(y,y_i)為0,如果網(wǎng)絡(luò)的輸出錯(cuò)的越離譜稼钩,那么懲罰項(xiàng)\Delta(y,y_i)的值就越高顾稀。max(s(x_i,y)+\Delta(y,y_i))表示所有樹里面最高得分。在這里变抽,懲罰項(xiàng)相當(dāng)于Margin础拨,也就是我們雖然希望打分函數(shù)s對正確的樹打分比對錯(cuò)誤的樹打分高,但也不要高過Margin的值绍载。我們優(yōu)化\theta诡宗,使目標(biāo)函數(shù)取最小值,即:

\theta=\underset{\theta}{argmin}J(\theta)

下面是懲罰函數(shù)\Delta的定義:

\Delta(y,y_i)=k\sum_{d\in N(y)}\mathbf{1}{\{subTree(d)\notin y_i\}}

上式中击儡,N(y)是樹y節(jié)點(diǎn)的集合塔沃;subTree(d)是以d為節(jié)點(diǎn)的子樹。上式的含義是阳谍,如果以d為節(jié)點(diǎn)的子樹沒有出現(xiàn)在標(biāo)簽y_i中蛀柴,那么函數(shù)值+1螃概。最終,懲罰函數(shù)的值鸽疾,是樹y中沒有出現(xiàn)在樹y_i中的子樹的個(gè)數(shù)吊洼,再乘上一個(gè)系數(shù)k。其實(shí)也就是關(guān)于兩棵樹差異的一個(gè)度量制肮。

s(x,y)是對一個(gè)樣本最終的打分冒窍,它是對樹y每個(gè)節(jié)點(diǎn)打分的總和。

s(x,y)=\sum_{n\in nodes(y)}s_n

具體細(xì)節(jié)豺鼻,讀者可以查閱『參考資料3』的論文综液。

小結(jié)

我們在系列文章中已經(jīng)介紹的全連接神經(jīng)網(wǎng)絡(luò)卷積神經(jīng)網(wǎng)絡(luò)儒飒、循環(huán)神經(jīng)網(wǎng)絡(luò)遞歸神經(jīng)網(wǎng)絡(luò)谬莹,在訓(xùn)練時(shí)都使用了監(jiān)督學(xué)習(xí)(Supervised Learning)作為訓(xùn)練方法。在監(jiān)督學(xué)習(xí)中桩了,每個(gè)訓(xùn)練樣本既包括輸入特征\mathbf{x}附帽,也包括標(biāo)記\mathbf{y},即樣本d^{(i)}=\{\mathbf{x}^{(i)},\mathbf{y}^{(i)}\}井誉。然而士葫,很多情況下,我們無法獲得形如\{\mathbf{x}^{(i)},\mathbf{y}^{(i)}\}的樣本送悔,這時(shí),我們就不能采用監(jiān)督學(xué)習(xí)的方法爪模。在接下來的幾篇文章中欠啤,我們重點(diǎn)介紹另外一種學(xué)習(xí)方法:增強(qiáng)學(xué)習(xí)(Reinforcement Learning)。在了解增強(qiáng)學(xué)習(xí)的主要算法之后屋灌,我們還將介紹著名的圍棋軟件AlphaGo洁段,它是一個(gè)把監(jiān)督學(xué)習(xí)增強(qiáng)學(xué)習(xí)進(jìn)行完美結(jié)合的案例。

參考資料

  1. CS224d: Deep Learning for Natural Language Processing
  2. Learning Task-Dependent Distributed Representations by Back Propagation Through Structure
  3. Parsing Natural Scenes and Natural Language with Recursive Neural Networks

相關(guān)文章

零基礎(chǔ)入門深度學(xué)習(xí)(1) - 感知器
零基礎(chǔ)入門深度學(xué)習(xí)(2) - 線性單元和梯度下降
零基礎(chǔ)入門深度學(xué)習(xí)(3) - 神經(jīng)網(wǎng)絡(luò)和反向傳播算法
零基礎(chǔ)入門深度學(xué)習(xí)(4) - 卷積神經(jīng)網(wǎng)絡(luò)
零基礎(chǔ)入門深度學(xué)習(xí)(5) - 循環(huán)神經(jīng)網(wǎng)絡(luò)
零基礎(chǔ)入門深度學(xué)習(xí)(6) - 長短時(shí)記憶網(wǎng)絡(luò)(LSTM)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末共郭,一起剝皮案震驚了整個(gè)濱河市祠丝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌除嘹,老刑警劉巖写半,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異尉咕,居然都是意外死亡叠蝇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進(jìn)店門年缎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悔捶,“玉大人铃慷,你說我怎么就攤上這事⊥筛茫” “怎么了犁柜?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長堂淡。 經(jīng)常有香客問我馋缅,道長,這世上最難降的妖魔是什么淤齐? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任股囊,我火速辦了婚禮,結(jié)果婚禮上更啄,老公的妹妹穿的比我還像新娘稚疹。我一直安慰自己,他們只是感情好祭务,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布内狗。 她就那樣靜靜地躺著,像睡著了一般义锥。 火紅的嫁衣襯著肌膚如雪柳沙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天拌倍,我揣著相機(jī)與錄音赂鲤,去河邊找鬼。 笑死柱恤,一個(gè)胖子當(dāng)著我的面吹牛数初,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播梗顺,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼泡孩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了寺谤?” 一聲冷哼從身側(cè)響起仑鸥,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎变屁,沒想到半個(gè)月后眼俊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡粟关,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年泵琳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,742評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡获列,死狀恐怖谷市,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情击孩,我是刑警寧澤迫悠,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站巩梢,受9級特大地震影響创泄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜括蝠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一鞠抑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧忌警,春花似錦搁拙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至朋譬,卻和暖如春盐茎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背徙赢。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工字柠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狡赐。 一個(gè)月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓募谎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親阴汇。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容