來源:機器之心? 編譯:路雪、思源、張倩 David Code 有多個身份:他是旅行作家,通曉多國語言,他還是一名「AI 教師」。沒有數學和軟件背景的 David 用 18 個月的時間通過在線課程和博客自學 AI,并花費兩年時間撰寫了一篇長文。為了使和他一樣沒有數學基礎的人也能入門人工智能,他在這篇文章中運用了大量類比、例子、故事、圖示,將重要的知識點或步驟反復強調。這是一篇真正針對初學者的 AI 教程,不只講概念,還講概念的底層原理。
由于原文過長,在編譯過程中進行了少量刪節。想了解更多細節,請查看原文鏈接:https://colab.research.google.com/drive/1VdwQq8JJsonfT4SV0pfXKZ1vsoNvvxcH#scrollTo=C810qURdm3hZ。
想學 AI 又擔心沒有數學背景或軟件背景?沒關系,這篇博客非常適合你。
我曾經花費一年半的時間自學各種在線課程和博客,過程中有太多專家、太多信息,而且他們的很多觀點還有沖突。我在學習過程中經常充滿自我懷疑。
我不想要很多專家幫我學習,我只想要一個老師。我希望有人能夠拉著我的手說:「Dave,這就是你需要學的東西,請按照這個順序學。現在我將用圖畫、有趣的故事、現實示例和淺顯易懂的語言教你學習 AI。」
而現在,我就是那個老師。
為什么是我?和網上的專家不同,我沒有數學或編程背景,不過我曾就讀于耶魯大學和普林斯頓大學,環游過 100 多個國家,是一名旅行作家,我曾在《周六夜現場》工作,我的作品得過獎。也就是說,我知道如何通過寫作來傳達復雜的概念,知道如何講故事。我熱愛教學,也善于發現好的老師。在學習深度學習過程中我遇到了四位優秀的老師,他們是 Andrew Trask、Grant Sanderson、Kalid Azad 和我的導師 Adam Koenig。
最重要的是,我理解你正在經歷的痛苦。你聽說過「專家盲區」(expert blindness)嗎?專家向新手講授某個學科時,由于他成為專家的時間太久,會忘了初學者對教材的感受。因此專家會快速介紹一些復雜的概念,而這些概念需要分解成小塊才能方便初學者掌握。或者他們不使用類比、圖畫或示例來幫助初學者掌握概念,導致初學者非常受挫。
每一個初學者都想要專家來教他們 AI。而事實上,你需要的不是專家,而是一名老師。
最好的教師就是那個剛剛學過這些知識的人,因為他仍然記得自己掙扎過的地方以及克服方法,并且他可以向你傳授捷徑。而我就是這個人。我不是專家,但我是個好老師,而且富有激情。
本文使用指南
本文的閱讀過程和小說不同,只讀一次是無法理解和掌握所有內容的。我學數學的朋友告訴我,他們常常需要讀至少 7 遍數學文本才能開始理解。這不是開玩笑……
為方便講授,我使用了類比、圖畫、示例和幾何表示。但是請放心,本文在數學層面上是準確而嚴謹的。請做好閱讀本文五遍的準備,無法立刻領會也不要著急。
我在學習復雜材料的時候,通常會設置計時器,每五分鐘響一次,不斷地提醒自己不要在絕望中沉淪,要微笑、耐心并堅持下去。這真的有效,相信我。
以下是一些宏觀要點:
神經網絡是深度學習中非常流行的前沿技術;
深度學習是機器學習的分支;
機器學習是人工智能的分支。
深度學習包括四個主要概念。本文的目標是讓讀者掌握這四個深度學習基礎概念:
前饋 梯度下降 全局最小值 反向傳播
之前對這四個概念一無所知?沒關系,首先我會使用類比和圖示盡量簡單地講授這些知識,然后不斷地回到這四個概念,探討其中的細節。你應該將本文看作一個「螺旋上升」的學習過程,每一次回到這些概念時你都會收獲更多見解。
本文共有五個部分:
1. 深度學習概覽:示例、類比、圖示、玩笑
2. 28 行代碼創建神經網絡:神經元和突觸
3. 前饋:做出有根據的猜測,60000 次迭代
4. 從試錯中學習:梯度下降和全局最小值
5. 反向傳播:鏈式法則
1. 深度學習概覽
1.1 示例
想象你是一家寵物店的老板,事業經營得很成功,而這成功很大程度上是因為你善用 AI 技術。你構建了一個深度神經網絡,來選擇潛在新顧客,并向他們發送廣告。一個月前,你上線了一款新貓砂「Litter Rip!」。你試圖找到愿意給自己的貓使用這款貓砂的顧客。
而你的秘密武器是數據集。新貓砂上線一個月后,你對寵物店顧客進行了調查并收集了一些數據。調查包括以下問題:
您有貓嗎?
您喝進口啤酒嗎?
過去一個月,您是否訪問過我們的網站 LitterRip.com?
過去一個月,您是否購買過 Litter Rip! 貓砂?
這四個問題的答案即之前顧客的「特征」(feature)。
那么問題來了:什么使得 AI 網絡如此強大?
答案是:它使用調查結果進行訓練,從而準確地預測未來顧客的購買行為。
首先,你需要將之前顧客的調查數據和他們對前三個問題的回答輸入到網絡中,進行訓練。該網絡使用這些數據預測某位顧客是否確實購買了新款貓砂。然后網絡再將預測結果與顧客第四個問題的答案進行對比。第四個問題的答案就是標簽,作為事實供網絡進行對比。例如,如果網絡預測結果是「是的,我認為這位顧客買過 Litter Rip! 貓砂」,而這位顧客第四個問題的答案確實是「Yes」,那么你就擁有了一個成功的神經網絡。
神經網絡通過試錯進行自我訓練:網絡先預測,然后對比預測結果與第四個問題的真正答案,再從錯誤中學習,并在多次迭代中不斷改進。
神經網絡通常在一個數據集上訓練,在另一個數據集上執行預測,理解這一點很重要。一旦你的神經網絡很擅長根據之前顧客的調查數據預測新款貓砂的購買情況,那么你就可以換一個新的數據集,該數據集包含潛在新顧客的名單。
你從獸醫那里得到了新的數據集,這些被調查者回答了前三個問題。現在,如果讓你用訓練好了的網絡在潛在新顧客中預測廣告投放的最佳對象,你該怎么做呢?
我們來看下一節。
1.2 類比:神經元和突觸
下圖是我們將要構建的 3 層神經網絡,圖中使用的是常見的「神經元和突觸」格式:
?
?
我們先來看這張圖,圖中是一個三層的前饋神經網絡。左側為輸入層:三個圓圈表示神經元(即節點或特征,該網絡將使用前三個調查問題作為特征)。現在,你看著這一列圓圈,想象它們分別代表一位顧客的答案。左上的圓圈包含問題 1「你有貓嗎?」的答案,左中圓圈包含問題 2「你喝進口啤酒嗎?」的答案,左下圓圈表示問題 3「你是否訪問過我們的網站 LitterRip.com?」的答案。那么,如果顧客 1 對這三個問題的答案是「Yes/No/Yes」,則左上圓圈包含 1,左中圓圈包含 0,左下圓圈包含 1。
突觸(連接這些圓圈和隱藏層的所有線)是神經網絡用來「思考」的部位。右側的單個圓圈(它依然和四個突觸相連)是網絡的預測結果,即「基于輸入到網絡的特征組合,此處展示了這位顧客購買新款貓砂的概率。」
最右側標注「y」的單個圓圈表示真值,即每個顧客對第四個調查問題「你是否購買過 Litter Rip! 貓砂?」的回答。這個圓圈有兩個選擇:0 表示沒買過,1 表示買過。神經網絡將輸出一個預測概率,并將其與 y 進行對比,查看準確率,然后在下一次迭代中吸取教訓。神經網絡在數秒時間內可以完成數萬次試錯。
上圖是神經網絡的典型圖示。本質上,它描述的是前饋,即我們要介紹的第一個主要概念。你可能以為神經元是該過程中最重要的部分,但是這里的類比似乎存在一些誤導性。事實上,本文要介紹的四個深度學習主要概念的共同驅動力是突觸。因此,目前這部分最重要的知識點是:突觸使得預測發生。下面一節我會把這一概念類比為落進碗里的乒乓球。
在進行下一個類比之前,我想首先詳細解釋一下神經網絡之所以強大的原因。
1.3 類比:碗與球
重點來了!人工智能如此強大的原因是:神經網絡使用概率對下一次預測進行漸進式的改進。該過程將試錯學習提升到一個全新的層次。
我們先來看一下人類是如何預測的:假設你的桌子上有一些之前顧客的調查結果,旁邊還有一疊潛在新顧客的調查結果(即獸醫提供給你的調查數據)。人類如何使用之前顧客的調查結果預測未來顧客的購買行為呢?你可能會想:「我的模糊邏輯告訴我,喝進口啤酒的顧客和買新款貓砂的顧客沒有關聯。我查看了顧客調查結果,試圖尋找出一種模式,我認為擁有貓和訪問過 LitterRip.com 網站的顧客購買過 Litter Rip! 貓砂。」
在只有三個調查問題、四名被調查顧客時,這是可行的。但是如果有 40 個問題、4000 名顧客呢?人類如何決定哪一個問題作為執行準確預測的核心因素?人類大腦能夠容納的數量是有限的,我們很難量化 40000 名顧客中的某一位購買新款貓砂的概率。這個數字是 67% 還是 68%?誰知道呢!
現在我們來看神經網絡如何執行預測:神經網絡不會將預測局限于直截了當的「是」或「否」,相反,它會預測出一個 0 和 1 之間的數字——概率。例如,0.67 表示「該顧客有 67% 的可能購買新款貓砂」,0.13 表示「該顧客有 13% 的可能購買新款貓砂,可能性較小。」
這就說明了為什么給出一個 0 到 1 之間的概率數字是明智的:就算計算機的第一次預測結果與實際情況大相徑庭也沒有關系。真正重要的是,網絡會將概率 0.13 與真值進行對比(假設真值是 1,即該顧客購買了新款貓砂),網絡會注意到它的預測結果偏離真值 0.87,這是一個比較大的誤差,因此網絡會進行調整。網絡將保持數字清晰展現,同時調整數字,增加一些值降低另一些值,以找到更好的問題組合,從而使下一次預測能夠得到更加準確的預測結果。將該步驟重復數萬次,直到計算機最終能夠自信地說:「我很高興地宣布,現在我的預測結果可以媲美真值了,誤差幾近于零。現在我可以準確預測了。」
現在,你知道了深度神經網絡的強大原因,它使用概率和試錯學習方法,漸進式地改進下一次預測的結果。
我可以用一幅簡單清晰的圖畫描述試錯學習過程:網絡的試錯學習就像順著碗邊滾落的乒乓球,最終將落在碗底。
前面我解釋了神經網絡如何執行預測:計算誤差,改善下一次的預測結果,直到誤差減少到幾乎為零。執行預測的神經網絡就像順著碗側滾落的乒乓球。我們假設碗底就是「烏托邦」——準確的預測結果,那么網絡的第一次預測就是該「預測球」(乒乓球)的起始位置;第二次預測時,乒乓球沿著碗側向底部前進一點距離;第三次預測時,球又向碗底前進一點……如下圖所示,網絡的每一次預測就是乒乓球向碗底前進時的新位置。
?
?
預測球滾落以及在到達完美位置(碗底)之前的準確率改進過程包括四步:
前饋:想象一下 1960 年的 IBM 計算機,大到填滿整個房間,穿孔卡片從一端輸入,答案從另一端輸出。上文提到的神經網絡以前三個調查問題的數據作為輸入,得出預測結果;
全局最小值:想象一下桌子上有一個黃色的碗(如上圖所示)。桌子表面表示幾乎零誤差的完美預測結果,那么很顯然碗底是最接近完美預測結果的位置,具備最小的誤差。與碗整個表面(即「全局表面」(global surface))相比,碗底最接近完美,它具備全局最小誤差值。
網絡每次進行更好的預測時,粉色的預測球沿著碗側向底部全局最小誤差值前進。每一次預測后,網絡將預測結果與第四個問題的答案進行對比,這類似于在特定時刻衡量預測球與碗底的距離。衡量預測結果與真值的距離叫做「找出誤差」。網絡每次預測的目標都是持續地縮短與全局最小值之間的誤差。
反向傳播:想象一位雜技表演者,他能向空中拋接 16 個不同大小和重量的保齡球瓶,并使它們同時懸在空中,甚至可以神奇地調整保齡球瓶的大小和重量。網絡在執行預測后,會返回到上一次預測的過程中,查看是否可以做一些調整,以便在下一次預測中縮小誤差,推動小球向碗底前進。
梯度下降:想象粉色乒乓球沿著碗側向碗底滾落,碗底即全局最小值(見上圖)。網絡就像那個球,碗的表面由網絡的每一次預測構成。梯度下降就是球沿著碗側滾落向碗底(即具備全局最小誤差的預測)的過程。
換句話說:
梯度下降是網絡在達到準確預測(即全局最小誤差)前的試錯過程,就像乒乓球滾落碗底的過程;
前饋即執行預測。預測就像給定時刻球停留在碗表面某個位置的定格圖像;
全局最小值即預測幾乎沒有誤差的完美位置(碗底)。我們的目標是到達碗底。網絡將預測結果與真值進行對比,來衡量球目前位置與碗底的距離(誤差);
反向傳播即返回到上一次預測,找出錯誤并修正。反向傳播衡量球現在位置到其下桌面的距離(即誤差),并找出推動球向碗底前進的方法。
記住:我們現在只是粗略了解,所以即便有些地方沒有掌握也不要擔心。
在詳細解釋這些概念之前,我想把「碗和球」的類比再推進一步。上圖展示了神經網絡訓練過程中的四個主要步驟,但過于簡化。它準確地表示了只有一個調查問題的網絡,該網絡也僅基于這一個問題做出預測。
但是,我們想做的是結合三個調查問題找出最佳預測。因此,如果網絡在試錯迭代過程中使用不同問題組合進行試驗,碗會是什么樣子呢?答案是:一只凹凸不平的碗。
如下圖所示:
?
?
圖源:https://www.youtube.com/watch?v=IHZwWFHWa-w&t=2s&index=3&list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi
上圖中紅色的碗有一些凸起和凹陷,為什么會這樣呢?
首先,我們必須理解紅色碗由什么構成。從圖中看它似乎是塑料做的……不過并不是,想象它由數百萬個紅點構成,每個紅點都是 3D 網格中的一個點。每個點表示一個可能的調查問題組合,以及該組合中哪個問題對于網絡執行預測的等級更高。這個凹凸不平的碗讓我們看到了所有可能組合的凹凸不平的表面。
記住這幅圖:紅色碗是「所有排列組合的表面」,它像山脈一樣有高峰、深谷、山嶺、河床。網絡無法窮盡浩瀚宇宙中每一個可能的排列組合,這會花費太多計算時間。因此,它從你隨機「拋擲預測球」的位置出發,一路向下走向谷底(即全局最小值)。網絡不需要管它從哪里開始出發,它只需要在意從隨機的起始點到達碗底即可。
在解釋紅色的凹凸不平處之前,我們先來看從紅碗右上角開始的白色虛線。這條線的頂端就是網絡的第一次預測。還記得上文介紹的紅色乒乓球嗎?假設乒乓球就在白線頂端處,白色虛線表示網絡預測結果從起始點到全局最小值的路徑。也就是說乒乓球沿著這條白色虛線走到誤差最小、預測準確率最高的地方。
但是為什么這條白色虛線路徑如此彎曲呢?原因在于,網絡的常規試驗需要考慮組合哪些問題、每個問題的權重多大,才能得到誤差最小的最佳預測結果。網絡的常規目標是盡可能降低誤差,即讓乒乓球盡快到達紅碗底部。因此網絡通常采用虛線上球所在點的坡度,來確定哪個方向具備最陡峭的坡度,能夠實現最快的向下路徑。由于坡度不斷改變,這條路徑就很曲折。我們來看一個簡單的例子:
人類第一眼就會判斷這兩個問題(「你有貓嗎?」和「你訪問過我們網站嗎?」)是做出預測的基礎問題。但是哪個問題對預測準確度影響最大呢?第一個問題影響更大,還是二者五五分?
網絡想用這兩個問題的不同權重組合進行試驗,從而找出實現準確預測的最佳組合。上圖紅色碗中的每個凸起都表示「走在錯誤道路上」的問題組合和問題權重,因為每個凸起都使網絡的「預測球」愈發偏離底部的全局最小值。而碗中的每個凹陷都表示「走在正確道路上」的問題組合,因為它使預測球離碗底更近。
但是,如果網絡找出了三個調查問題的完美權重組合,但預測準確率仍然只有 60% 左右,該怎么辦?不要怕,網絡還有另一個技巧:推斷問題(inferred question)。
下面我們用一個簡單示例講述這個重要概念:
我們回過頭看關于進口啤酒的那個問題。神經網絡不停嘗試不同的問題組合。那么舉例來說,或許只有富人才有錢買進口喜力啤酒,而我們都知道養貓的人很可能是富人。(本貓奴的內心:不,我不是,我沒有……)那么也許當「你喝進口啤酒嗎?」和「你有貓嗎?」這兩個問題在網絡計算過程中是組合問題且權重較高時,預測結果會得到改進。
這里的推斷問題就是「富人更有可能購買 Litter Rip 貓砂嗎?」。不過我們剛才做了一個(愚蠢的)推斷:喝進口喜力啤酒和養貓的人可能比較富有。
但是,隨著粉色預測球在紅色碗中兜兜轉轉,它到了凸起處,而原因在于網絡使用了這個推斷問題進行試驗。也就是說該推斷問題對于改善預測準確率并沒有幫助。(可能是因為貓砂不是奢侈品而是養貓者的必需品。)在接下來的迭代中,預測球離開凸起處,不再使用這個無用的推斷問題。但是它也有可能因為有用的推斷問題掉進凹陷處,從而幫助它更快地到達碗底。
上文的示例展示了如何測試推斷問題能否有效幫助實現更準確的預測結果。人類使用模糊邏輯探索推斷問題,而神經網絡嘗試窮盡每一種排列組合。如果試驗獲得了較好的預測結果,它會保留該問題。如果試驗得到了更糟的預測結果,網絡將拋棄該問題。
總之,紅色碗表面上每一個紅點表示網絡使用特定問題組合和權重組合進行的一次試驗。每一個凹陷表示「這一步走在了正確的道路上」,網絡注意到這一點并將繼續保持。每一個凸起處表示「這一步走錯了方向」,網絡也會注意到并摒棄它。預測球的路徑(白色虛線)歪歪扭扭,是因為預測球在通往碗底的路上不斷尋找凹陷處、避免凸起處。你可以說白色虛線是預測球通往碗底的最高效路徑。
現在我們轉回來,更詳細地了解一下「前饋」、「全局最小值」、「反向傳播」和「梯度下降」。
1.3.1 前饋:將穿孔卡片輸入到 1960 年的 IBM 計算機
前饋的目標是創建預測。假設每一次預測是預測球向碗底前進過程中的一個新位置。在上圖紅色碗中,網絡做的第一次預測由白色虛線右上角的點表示,我們假設預測球就在這個點上。前饋是創建第一次預測的過程。白色虛線上的下一個點即是第二次預測,這樣預測球移動到下一個點,之后再進行第三次預測。
大部分 AI 課程和博客不會提及,預測球所在的每個位置由紅色碗底部白色網格的兩個坐標軸決定。紅色碗并非在空間中漂移,它位于白色網格之上,該網格具備 X 軸和 Y 軸。
還記得上圖介紹全局最小值時所用的圖像嗎?黃色碗位于桌面上。同理,紅色碗位于白色網格上。白色網格即「桌面」,「完美、無誤差的預測位置」即紅色碗實際位于的點。注意,碗與桌面唯一的連接點是碗底,即白色虛線結束的點(全局最小值)。
預測球在紅色碗中的每次停頓,即每次預測點,都由 3 個坐標軸決定:X 軸和 Y 軸表示網格位置,Z 軸表示預測球到網格的距離。
現在讓我們跳出抽象描述。3D 空間中這三個坐標軸在現實生活中表示什么?
回憶一下,紅色碗表面上每一個紅點表示網絡使用特定問題組合和權重組合進行的一次試驗,那么網絡如何執行試驗呢?它使用 X 軸和 Y 軸在白色網格上定一個位置,表示特定的問題組合。X 軸和 Y 軸必然會問:「嗨,Z 軸你好,這個組合怎么樣?」Z 軸將告訴我們該組合的效果——Z 軸負責衡量誤差。
我們之前提到,碗底,即碗與白色網格相連接的位置,是完美的預測。那么碗中每一個紅點都表示誤差,離白色網格越遠,誤差就越大。
這點非常重要:白色網格表示網絡可以嘗試的問題及權重組合。對于白色網格上的每一個點,其上方的紅點表示該組合的誤差值。也就是說,紅色碗上的每個紅點表示其下方預測的誤差。紅色碗就是「誤差組成的碗」。只有碗底,紅點接觸白色網格的地方,二者之間沒有距離,也因此沒有誤差。參見下圖:
?
?
上圖中的錐形黃色箭頭表示 Z 軸,用于衡量誤差。它很有用,你可以從中了解誤差值并學習,以便下次得到更加準確的預測結果。
想象一下,粉色的預測球在白色虛線上移動,黃色箭頭總是在預測球下方,并隨之移動。粉色球表示網絡做出的每次預測,黃色箭頭衡量預測球到白色網格的距離,即衡量每次預測的誤差。它的運行原理是什么?
你可能會想到前饋——以前三個調查問題作為輸入并用不同方式進行問題組合,再執行預測,預測結果為 0 到 1 之間的數字,即概率。想象預測球所在的點表示在白色虛線上的數字。我們知道第四個問題的答案是「1」,因此真值是該顧客買過新款貓砂。真值即是全局最小值,是我們改進預測結果的目標,也是紅色碗和白色網格的接觸點。因此,網絡用 1 減去預測概率。舉例來說,如果網絡的第一次預測為 0.5,即該顧客有 50% 的可能性購買過這款貓砂,1 減去 50% 所得的數字表示誤差,黃色箭頭就用于衡量該誤差。某種程度上可以說,「真值數字 - 預測球數字 = 黃色箭頭數字」。在該示例中,即 1 - 0.5 = 0.5。誤差(黃色箭頭的長度)為 0.5(誤差通常用絕對值表示,不能為負值)。
沒有第四個調查問題的話,你無法訓練網絡,因為你需要真值來測試預測結果。黃色箭頭衡量網絡預測與真值的距離,即誤差,這個例子中真值為 1。我們的第一次預測(0.5)準確率并不高,0.99 的預測才可以說比較準確。
1.3.2 找到全局最小值
我們的目標是訓練神經網絡找出減少預測誤差的最快方式,也就是說讓黃色箭頭變短,這意味著我們需要預測球從起始點到碗底的最高效路徑。碗底即「烏托邦」,在那里黃色箭頭(預測誤差)的長度幾乎為 0(全局最小值),即我們的預測具備最小誤差,網絡達到非常高的準確率。而預測球到達碗底的最高效路徑是那條白色虛線。一旦我們使用第一個數據集完成訓練,網絡就可以準確預測另一撥潛在新客戶購買新款貓砂的概率。
現在想象這個場景:預測球從白色虛線頂端開始,此時黃色箭頭(即第一次預測的誤差值)等于 0.5。我們如何讓球到達碗底呢?也就是說,我們如何調整網絡,使預測球(目前 X,Y 坐標為 (3,3))在白色網格上移動到碗底(碗底點的坐標大致為 (3,0))。目前,我們僅能基于顧客 1 的答案執行預測。那么,我們如何基于每個顧客的回答改進之后每一次預測的結果,直到預測誤差幾乎為 0?也就是預測球到達碗底,網絡訓練得足夠好,可以利用新數據集做出預測。
找到從不那么準確的預測結果 0.5 到最終第 6 萬次預測結果 0.9999 的每一步路徑,這就是梯度下降過程。
1.3.3 梯度下降和反向傳播
梯度下降表示網絡的試錯學習過程。對于數學 nerd 來說,它可定義為一個總體規劃:改變突觸權重、在每次迭代時最大程度上降低誤差。
對于普通人來說,這里有一個更好的解釋:將小球沿著最陡峭的坡度滾下,盡快地到達碗底。反向傳播是一種計算梯度的方法,梯度其實就是坡度(slope)。反向傳播告訴你預測球所在位置的坡度。
坡度在這里非常重要。所有專家都會使用術語「梯度下降」,而梯度的意思就是「坡度」。找到預測球在碗表面位置點的坡度,可以指示出球盡快到達碗底應該移動的方向。
但是,為什么是坡度呢?請考慮梯度下降的過程:
首先,計算機執行前饋預測。用真值(全局最小值所在位置,即碗底)減去預測得到誤差(黃色箭頭長度),然后使用反向傳播計算誤差的坡度。坡度決定了預測球滾動的方向和速度(即網絡應該在數字方面做出多大的調整)。
找出坡度是反向傳播的重要工具。如果說梯度下降是總體規劃,那么反向傳播就是達成規劃的主要工具。
2. 28 行代碼創建神經網絡
現在我們來看代碼。我將首先展示今天要學習的全部代碼,然后再進行詳細的步驟講解。建議在兩個窗口中打開本文,一個窗口顯示代碼,另一個繼續往下閱讀。從下文代碼注釋中可以看到,我將構建神經網絡的過程分解成了 13 個步驟。現在準備好觀看計算機從它所犯的錯誤中學習并最終識別出模式了嗎?
不過,首先我們先來了解矩陣和線性代數的概念。
在下文的代碼中,你會看到單詞「matrix」或「matrices」(矩陣)。它非常重要:矩陣是這輛汽車的引擎。沒有矩陣,神經網絡哪兒都去不了。
矩陣是多行數字的集合。你將遇到的第一個矩陣如下所示,它容納了寵物店顧客調查的數據。
將每一行想象為一名顧客,那么以上矩陣中共有 4 位顧客,每一行的方括號中包含三個數字(1 和 0 分別表示「是」和「否」)。在 1.2 節中,我們看到顧客 1 對前三個調查問題的答案是「Yes/No/Yes」,因此該矩陣的第一行為 1, 0 ,1。第一列是四位顧客對第一個問題「你有貓嗎?」的回答。
為了更詳細地介紹矩陣這一概念,請看下圖。圖中表示的矩陣與上面的矩陣相同,不過它多了一些標簽和顏色,用于強調顧客的回復是一行,特征/問題是一列:
?
?
上圖厘清了一個最初使我非常困惑的點:行與列之間的關系。
在該矩陣中,每一位顧客的數據被表示為一行中的三個數字。在神經網絡圖示中(神經元和突觸格式),輸入層是一個包含三個圓形神經元的列。你需要注意到,每個神經元并不表示一位顧客,即矩陣中的一行數據。相反,每個神經元表示一個特征,即矩陣中的一列數據。因此,一個神經元內包含所有顧客對同一個問題/特征的答案。拿第一個問題「你有貓嗎?」舉例,我們只選取了四位顧客的數據,因此上圖中該問題的回復中只有四個數字(兩個 0、兩個 1),而如果我們選取了一百萬名顧客的數據,則上圖中該神經元內會包含一百萬個數字,表示這些顧客對該問題的回復。
到這里,我希望大家能夠理解我們為什么需要矩陣:因為我們有不止一位顧客。在下面的神經網絡示例中,我們描述了四位顧客,所以我們需要四行數字。
該網絡包括不止一個問題。每個問題需要一列,因此我們有三列,分別表示對前三個調查問題的回答(第四個問題將出現在后面的另一個矩陣中)。
因此我們的矩陣很小,4 行 X 3 列(4 by 3)。不過真實神經網絡的矩陣可能擁有數百萬位顧客和數百個調查問題。做圖像識別的神經網絡可能具備數十億個「顧客」行和特征列。
總結一下,我們需要矩陣使所有數據清晰展現,方便我們在其上進行計算,這樣矩陣就把數據組織成美觀、整潔的行與列。
下面我們來看代碼,代碼選取自 Andrew Trask 的博客,不過注釋是我寫的。
代碼
2.1 Sigmoid 函數:行 8-12
sigmoid 函數在網絡學習過程中起到非常重要的作用。
「nonlin()」是一種 sigmoid 函數類型,叫做 logistic 函數。logistic 函數在科學、統計學和概率論中非常常見。此處該函數的表達有些復雜,因為這里它作為兩個函數使用:
第一個函數是將矩陣(此處表示為 x)放入括號內,將每個值轉換為 0 到 1 之間的數字(即統計概率)。轉換過程通過代碼行 12 實現:return 1/(1+np.exp(-x))。
那么為什么需要統計概率呢?神經網絡不會預測 0 或 1,它不會直接吼「哇,顧客 1 絕對會買這款貓砂!」,而是預測概率:「顧客 1 有 74% 的可能購買這款貓砂」。
這里的區別很大,如果你直接預測 0 和 1,那么網絡就沒有改進空間了。要么對要么錯。但是使用概率的話就有改進空間。你可以調整系統,每一次使概率增加或減少幾個點,從而提升網絡的準確率。這是一個受控的增量過程,而不是盲猜。
將數字轉換成 0 到 1 之間的數字這一過程非常重要,它帶給我們四大優勢。下文將詳細討論這些優勢,現在我們先來看 sigmoid 函數如何將其括號內每個矩陣的每個數字轉換成 0 到 1 之間的數字,并使其落在下圖的 S 曲線上:
?
?
圖源:https://iamtrask.github.io/2015/07/12/basic-python-network/
sigmoid 的第一個函數將矩陣中的每個值轉換為統計概率,而它的第二個函數如代碼行 9 和 10 所示:
' if(deriv==True): return x*(1-x)'
該函數將給定矩陣中的每個值轉換成 sigmoid 函數 S 曲線上特定點處的坡度。該坡度值也叫做置信度(confidence measure)。也就是說,該數值回答了這個問題:我們對該數值能夠準確預測結果的自信程度如何?你可能會想:這又能怎么樣呢?我們的目標是神經網絡可靠地做出準這是我們的標簽確的預測。而實現這一目標的最快方式就是修復置信度低、準確率低的預測,不改變置信度高、準確率高的預測。「置信度」這一概念非常重要,下文我們將深入解讀它。
接下來我們看第 2 步:
2.2 創建輸入 X:行 23-26
代碼行 23-26 創建了一個輸入值的 4x3 矩陣,可用于訓練網絡。X 將成為網絡的 layer 0(或 l0),現在開始創建神經網絡吧!
以下是我們從顧客調查中獲得的特征集,只不過用計算機能理解的語言表達出來:
Line 23 creates the X input (which becomes l0, layer 0, in line 57)
X:
[1,0,1], [0,1,1], [0,0,1], [1,1,1]
我們有四個顧客,他們分別回答了三個問題。前面我們討論過第一行 1,0,1 是顧客 1 的答案。將這個矩陣的每一行看作即將輸入網絡的訓練樣本,每一列就是輸入的一個特征。因此矩陣 X 可被描述為下圖,該矩陣即為圖中的 l0:
?
?
你可能會想:矩陣 X 是怎么變成圖中的 layer 0 的?稍后我會解釋這一點。
接下來,我們將創建正確答案列表。
2.3 創建輸出 y:行 34-37
這是我們的標簽,即第四個調查問題「你是否購買過新款貓砂?」的答案。看下面這列數字,顧客 1 回答的是「Yes」,顧客 2 回答的是「Yes」,顧客 3 和 4 回答的是「No」。
?
?
如果把 y 看作「目標」值的話,我將畫一個箭靶。隨著網絡的改進,它射出的箭離靶心越來越近。一旦網絡可以根據矩陣 X 提供的輸入準確預測 4 個目標值,則網絡準備就緒,可以在其他數據集上執行預測了。
2.4 生成隨機數:行 40
這一步是「做家務」。我們必須生成隨機數(隨機數將在訓練過程的下一步生成突觸/權重),以使 debug 過程更加簡單。你不必理解這行代碼的工作原理,只需要使用它就好了。
生成隨機數的原因是,你必須從某個地方開始。因此我們從一組捏造數字開始,然后在 6 萬次迭代中慢慢改變每一個數字,直到它們輸出具備最小誤差值的預測。這一步使得測試可重復(即使用同樣的輸入測試多次,結果仍然相同)。
2.5 創建突觸(即權重):行 47-48
第一次看到下圖時,你可能會認為神經網絡的「大腦」是那些圓圈(神經元)。而事實上,神經大腦的真正核心是那些能夠學習和改進的部分——突觸,也就是圖中那些連接圓圈的線。這兩個矩陣——syn0 和 syn1 才是網絡的真正大腦。它們是網絡用來試錯學習、執行預測、對比目標值和預測,進而改善下一次預測結果的部分。
注意代碼 syn0 = 2np.random.random((3,4)) - 1 創建了 3x4 矩陣,并為它生成隨機數。這將是突觸(或權重)的第一層 Synapse 0,它連接 l0 和 l1。該矩陣如下所示:
?
?
我曾經在這里犯了一個錯:我無法理解為什么 syn0 應該是 3x4 矩陣。我認為它應該是 4x3 矩陣,因為 syn0 必須與 l0 相乘,而后者是 4x3 矩陣,我們為什么不讓兩個矩陣的數字按行與列排列整齊呢?
而這就是我的錯誤:4x3 乘 4x3 能夠使數字排列整齊?這是錯的。事實上,如果我們想要數字排列整齊,我們就應該讓 4x3 乘 3x4。這是矩陣乘法里的一項基礎且重要的規則。仔細查看下圖中的第一個神經元,該神經元內是每位顧客對「你有貓嗎?」這個問題的回復。以下是 4x3 layer0 矩陣的第一列:
[1] [0] [0] [1]
現在注意,有四條線(突觸)將 l0 中「你有貓嗎?」這個神經元與 l1 的四個神經元連接起來。這意味著上述列 1,0,0,1 中的每個數字都要與四個不同權重相乘,因此得到 16 個值。l1 確實是一個 4x4 矩陣。
注意,現在我們要對第二個神經元內的四個數字執行同樣的操作,也得到 16 個值。我們將這 16 個值中的每個值與剛才創建的值中的對應值相加。
重復這個步驟,直到將第三個神經元中的四個數字也處理完畢。這樣 4x4 l1 矩陣就有 16 個值,每個值是三次乘法所得值的對應相加結果。
也就是說,3 個調查問題 x 4 位顧客 = 3 個神經元 x 4 個突觸 = 3 個特征 x 4 個權重 = 3x4 矩陣。
看起來很復雜?習慣就好了。而且計算機會替你執行乘法操作。我只是想幫助大家理解其下的底層操作。當你看到下圖時,這些線不會說謊。
?
?
也許你會疑惑,代碼行 47 中的「2」和「-1」是哪兒來的。np.random.random 函數生成均勻分布于 0 到 1 之間的隨機數(對應的平均值為 0.5),而我們想讓隨機數初始值的平均值為 0。這樣該矩陣中的初始權重不會存在偏向于 1 或 0 的先驗偏置(在最開始的時候,網絡不知道接下來會發生什么,因此它對其預測結果是沒有信心的,直到我們在每個迭代后更新它)。
那么,我們如何將一組平均值為 0.5 的數字轉變為平均值為 0 的數字呢?首先,將所有隨機數乘 2(這樣所有數字分布在 0 到 2 之間,平均值為 1),然后減去 1(這樣所有數字分布在-1 到 1 之間,平均值為 0)。這就是「2」和「-1」出現的原因。
接下來看下一行代碼:syn1 = 2np.random.random((4,1)) - 1 創建了一個 4x1 向量,并為它生成隨機數。這就是網絡第二層的權重 Synapse 1,它連接 l1 和 l2。
?
?
for loop 使網絡執行 6 萬次迭代。每次迭代中,網絡使用顧客調查數據 X 作為輸入,基于該數據得出對顧客購買新款貓砂概率的最佳預測。然后將預測與真值進行對比,再從錯誤中學習,在下次迭代中做出更好的預測。該過程持續 6 萬次,直到網絡通過試錯學會如何準確預測。然后這個網絡可以使用任意輸入數據,并準確預測可能購買新款貓砂的顧客。
2.6 For Loop:行 52
for loop 使網絡執行 6 萬次迭代。每次迭代中,網絡使用顧客調查數據 X 作為輸入,基于該數據得出對顧客購買新款貓砂概率的最佳預測。然后將預測與真值進行對比,再從錯誤中學習,在下次迭代中做出更好的預測。該過程持續 6 萬次,直到網絡通過試錯學會如何準確預測。然后這個網絡可以使用任意輸入數據,并準確預測可能購買新款貓砂的顧客。
3. 前饋:做出有根據的猜測,60000 次迭代
網絡在這一步驟開始執行預測。這是深度學習過程中最令人興奮的部分,所以我打算從三個不同角度介紹這個概念:
關于前饋的迷人童話
關于前饋的美麗畫作
為什么矩陣乘法是前饋的引擎
3.1 城堡和生命的意義:前饋網絡
想象你自己是一個神經網絡。恰好你是一個有駕照的神經網絡,而且喜歡開快車和神秘的心靈之旅。你迫切想找到生命的意義。奇妙的是,你剛好發現只要驅車前往某個城堡,神秘先知將告訴你生命的意義。天啊!
不用說,你肯定特別想找到先知的城堡。先知就代表真值,也就是神秘問題「你買過新款貓砂嗎?」的答案。也就是說,如果你的預測與真值匹配,那么你就到達先知城堡,即 l2 誤差為 0、黃色箭頭的長度為 0、粉色乒乓球到達碗底。
不過,找到先知城堡需要一些耐心和堅持,你需要嘗試數千次,迷路數千次。(小提示:數千次旅程 = 迭代,不斷迷路 = l2 預測存在誤差,誤差使得你離先知城堡 y 還有距離。)
但是也有好消息:你知道隨著時間的流逝,每一次旅程并不是沒有意義,你會越來越靠近先知(先知城堡 y 正在閃耀……)。而壞消息是每一次當你無法到達城堡時,第二天醒來你又回到了原點(即 Layer 0,輸入特征,3 個調查問題),必須從那里重新出發(新的迭代)。這個過程有點像下圖:
?
?
幸運的是,這個故事有一個完美的結局,你不停嘗試、不斷修改路徑,在進行了 58000 次試驗后終于到達了目標。這很值得。
我們來看一下從你的房子 X 到先知城堡 y 的其中一次旅程(迭代):每一次旅程都是代碼行 57-59 所執行的前饋,每天你都到達一個新地方,但是它們都不是你的目的地。你當然想知道如何在下一次旅程中更接近城堡,我會在這個故事的后續篇中解釋。
接下來,我們來看一個更簡單的示例。
3.2 關于前饋的美麗畫作
接下來我們來看 16 個權重中的其中一個。這個權重是 syn0,12 條線中最上方那條,連接 l0 和 l1 最上方的神經元。出于簡潔性考慮,我們稱其為 syn0,1(syn0(1,1) 的簡化,表示矩陣 syn0 的行 1 列 1)。如下圖所示:
?
?
為什么表示 l2 和 l1 神經元的圓圈被從中間分割開了?圓圈的左半邊(帶有 LH 字樣)是 sigmoid 函數的輸入值,右半邊是 sigmoid 函數的輸出:l1 或 l2。在這個語境下,sigmoid 函數只是將前一層與前一個突觸相乘,并轉換為 0 到 1 之間的值。代碼如下:
return 1/(1+np.exp(-x))
這里的前饋使用了我們的一個訓練樣本,l0 的第 1 行,即「顧客 1 對 3 個調查問題的回復」:[1,0,1]。因此,我們首先將 l0 的第一個值與 syn0 的第一個值相乘。假設 syn0 矩陣已經過多次訓練迭代(我們在 2.4 節已經進行過初始化了),現在該矩陣如下所示:
syn0:
[ 3.66 -2.88 3.26 -1.53] [-4.84 3.54 2.52 -2.55] [ 0.16 -0.66 -2.82 1.87]
到這里,或許你會疑惑:為什么 syn0 的值與 2.4 節創建的 syn0 如此不同?好問題。前面介紹的很多矩陣有逼真的初始值,就好像它們剛被計算機通過隨機種子創建出來一樣。而這里和下面的矩陣并非初始值。它們是經過多次訓練迭代的,因此它們的值已經在學習過程中經過了更新和改變。
現在,我們用 l0 的第一個值 1 與 syn0 的第一個值 3.66 相乘,看看會得到什么:
?
?
下面是前饋的偽代碼,你可以借助上圖(從左至右的順序)幫助理解。
?
?
下面,我們再從另一個角度看前饋過程的底層數學原理。
3.3 前饋的數學原理
l0 x syn0 = l1LH,在這個示例中即 1 x 3.66 = 3.66,不要忘記還要加上另外兩個 l0 值和 syn0 對應權重的乘積。在該示例中,l0,2 x syn0,2= 0 x something = 0,l0,3 x syn0,3 中 l0,3=1,從上一節中我們知道 syn0,3 = 0.16,因此 l0,3 x syn0,3 = 1 x 0.16 = 0.16。因此 l0,1 x syn0,1 + l0,3 x syn0,3 = 3.66 + 0.16 = 3.82,即 l1_LH = 3.82。
接下來,我們將 l1_LH 輸入 nonlin() 函數,將該數字轉換為 0 到 1 之間的概率。Nonlin(l1_LH) 使用代碼 return 1/(1+np.exp(-x)),因此該示例中,1/(1+(2.718^-3.82))=0.98,l1 = 0.98。
那么,在公式 1/(1+np.exp(-x)) = [1/(1+2.718^-3.82))] = 0.98 中發生了什么呢?計算機使用代碼 return 1/(1+np.exp(-x)) 代替了我們的人工計算,我們可以通過下圖看到在 sigmoid 曲線上 x = 3.82 所對應的 y 值:
?
?
圖源:https://iamtrask.github.io/2015/07/12/basic-python-network/
注意,X 軸上 3.82 在藍色曲線上對應點的對應 y 值是 0.98,而代碼將 3.82 轉換為 0 到 1 之間的概率。上圖有助于大家了解該計算并不神秘,也不抽象,計算機只不過做了和我們一樣的事:它使用數學查看上圖中 X 軸上 3.82 對應的 y 值,僅此而已。
同理,我們重復以上步驟,計算出 l2_LH 和 l2 的值。至此,我們就完成了第一次前饋。
現在,為了簡潔起見,我們把所有變量放在一起,如下所示:
l0=1 syn0,1=3.66 l1_LH=3.82 l1=0.98 syn1,1=12.21 l2_LH=0 l2=~0.5 y=1 (this is a "Yes" answer to survey Question 4, "Actually bought Litter Rip?") l2_error = y-l2 = 1-0.5 = 0.5
現在,我們來看讓這一切實現的矩陣乘法(對矩陣乘法和線性代數陌生的朋友,可以先學習 Grant Sanderson 的課程:https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab)。
首先,在代碼行 58,我們將 4x3 l0 和 3x4 Syn0 相乘,創建(隱藏層)l1,它是一個 4x4 矩陣:
?
?
在第 58 行代碼中,我們將 l1 輸入 nonlin() 函數,得到一個 0 到 1 之間的概率值:
1/(1 + 2.781281^-x) This creates layer 1, the hidden layer of our neural network:
l1:
[0.98 0.03 0.61 0.58] [0.01 0.95 0.43 0.34] [0.54 0.34 0.06 0.87] [0.27 0.50 0.95 0.10]
看不懂也沒關系,我們來看一個簡單的訓練示例。第一行(顧客 1 的調查數據)[1,0,1] 是一個 1x3 矩陣。我們將其乘以 syn0(3x4 矩陣),得到的 l1 是 1x4 矩陣。過程如下所示:
?
?
注意,在代碼第 58 行 l1=nonlin(np.dot(l0,syn0)),我們將 l1 輸入 sigmoid 函數,因為我們需要一個 0 到 1 的數值。
從這行代碼中,我們可以看到 sigmoid 函數的第一大優勢。當我們將 l0 和 syn0 的點乘矩陣輸入到 nonlin() 函數時,sigmoid 函數將矩陣中的每個值轉換成 0 到 1 之間的概率。
3.4 核心重點:隱藏層中的推斷問題
我們為什么要關心統計概率呢?我認為原因在于,統計概率是讓一堆沉默矩陣煥發生機并像小孩一樣學習的主要因素!
在第一層中,當我們將 l0 乘以 syn0 時,為什么要嘗試給 syn0 的權重賦不同的值呢?因為我們想嘗試不同的特征問題組合,發現對預測結果幫助最大的問題組合。上文舉過一些拙劣的例子,推斷出有貓且喝進口啤酒的人更有可能購買新款貓砂,從而利用權重強化這兩個特征組合。
另一個例子:如果一些顧客沒有養貓,但他們喝進口啤酒且訪問過 Litter Rip.com (http://rip.com/) 網站,我們可以推斷出這些顧客熱衷技術:他們喝進口啤酒因為他們欣賞其供應鏈物流,他們訪問網站,各種各樣的網站,說明他們很顯然熱衷技術。因此我們可以推斷出,這些顧客可能會出于對這款貓砂先進技術的欣賞而購買它,盡管他們可能實際上并沒有養貓。所以我們或許需要調整 syn0 的權重,強化這些特征之間的連接。
現在你明白了嗎?當我們將調查問題的回復 l0 與 syn0 中的權重(每個權重表示我們對一個推斷問題對預測結果的重要程度的最佳猜測)相乘時,我們是在嘗試不同的答案組合,以查看哪些組合對預測結果最有幫助。很明顯,在 6 萬次迭代中,訪問過貓砂網站的貓主人更有可能購買這款貓砂,因此在迭代的過程中它們對應的權重會增加,即其統計概率更接近 1 而不是 0。而喝進口啤酒卻沒有貓的人購買這款貓砂的可能性較低,因此對應的權重較小,即其統計概率更接近 0 而不是 1。是不是很奇妙?這就像數字譜寫的詩歌,這些是會推理和思考的矩陣!
這也是我為什么大費周章介紹這個的原因。你是否經歷過,一些傲慢的軟件工程師或制造恐慌的記者告訴你,神經網絡隱藏層是潘多拉的魔盒?而事實上它的底層并沒有什么「神奇魔法」。這些數學知識清晰、優雅而美麗。你可以掌握它,只要有耐心肯堅持。
接下來我們繼續看矩陣乘法。
3.5 用神經元和突觸的形式可視化矩陣乘法
我們用神經元和突觸的形式可視化 l0 和 syn0,如下圖所示:
?
?
上圖展示了輸入 l0 的行 1 如何完成在網絡中的第一步。第一行代表顧客 1 對三個調查問題的答案。這三個數字需要乘以 syn0 的 12 個值,然后網絡還要對其他三位顧客的答案進行同樣的處理,那么如何清晰地展現這些數字并進行計算呢?
這里的關鍵在于,將四位顧客看作一個「batch」(批次),他們在其中堆疊在一起,即一共 4 個 stack。那么,最上面的那個 stack 就是第一行(顧客 1),依此類推。如上圖所示,你將第一行的三個數字和 syn0 的 12 個值相乘,再相加,最后得到 l1 最上面 stack 的四個值。
接著是 batch 中的第二個 stack——顧客 2 的回答是 0,1,1。將這三個數字和 syn0 的 12 個值相乘,再相加,最后得到 l1 第二個 stack 的四個值。
依此類推。其核心在于一次計算一個 stack,這樣不管 batch 中有多少個 stack,四個還是四百萬個,你都可以很好地處理。你可能會說每個特征都有一個 batch,在貓砂這個示例中,每個調查問題(特征)的 batch 為 4,因為只有四位顧客的答案。但它也可能是四百萬。「full batch configuration」的概念是非常常見的模型,接下來我將解釋這一點。
其實認為給定特征具備一個 batch 的值是最容易理解的。當你看到一個特征時,你知道它底下有一個 batch 的值。
?
?
就像代碼行 59 所示,我們把 4x4 l1 和 4x1 syn1 相乘,然后輸入 sigmoid 函數得出 4x1 l2,其每個值都是 0 到 1 的統計概率。
l1 (4x4): [0.98 0.03 0.61 0.58] [ 12.21] [0.01 0.95 0.43 0.34] X [ 10.24] = [0.54 0.34 0.06 0.87] [ -6.31] [0.27 0.50 0.95 0.10] [-14.52]
Then pass the above 4x1 product through "nonlin()" and you get l2, our prediction: l2: [ 0.50] [ 0.90] [ 0.05] [ 0.70]
?
那么這四個預測結果告訴我們什么呢?預測值距離 1 越近,則該顧客購買這款貓砂的可能性越高;預測值距離 0 越近,則該顧客購買這款貓砂的可能性越低。
現在我們完成了前饋部分。接下來我們將看 6 萬次迭代過程中如何調整網絡權重,使預測結果越來越好。
4. 從試錯中學習:梯度下降
4.1 梯度下降概覽
梯度下降的目的是什么?是為了更好地調整網絡權重,從而在下次迭代中獲得更好的預測結果。也就是說,網絡的突觸矩陣中的某些值要被增減。為了調整這些值,我們必須回答以下兩個重要問題:
我應該按什么方向調整數字?應該增加還是減少數值?正方向還是負方向?……
數值應該增減多少?
下面我們將詳細解釋這兩個基礎問題。還記得上文的紅色碗嗎?「梯度」就是「坡度」,「梯度下降」即計算出使小球從碗表面上的某個點盡快下降到碗底的最優坡度。
梯度下降的第一步即,計算當前的預測結果與真值 y(1/yes 或 0/no)之間的差距。
4.2 預測結果與調查問題 4 的答案相比有多大差距?
代碼行 66:
l2_error = y - l2
第一次預測結果距離目標值「Yes/1」(顧客 1 對第四個調查問題的回答真值是「購買過」)有多遠距離呢?我們需要將 l2 預測值與 y 值(1)進行對比,即 y 值減去 l2 得到的就是 l2_error——「預測值距離目標值 y 的距離」。
因此,我們可以想象這幅圖景:網絡使用每位顧客的回復作為輸入,并操作這些數據,以得到顧客是否購買貓砂的預測結果。
我們有四位顧客,網絡就做了四次預測。因此 l2_error 是四次誤差的向量(每個誤差針對一次預測)。接下來我們將打印出該誤差:
打印誤差:行 72-73
72 if (j% 10000)==0: 73 print("Avg l2_error after 10,000 more iterations: "+str(np.mean(np.abs(l2_error))))
行 72 使計算機每隔一萬次迭代打印一次 l2_error。這有助于我們每隔一萬次查看網絡的學習效果和進展。if (j% 10000)==0: 表示「如果你的迭代器所處的迭代次數除以 10000 后沒有余數,則……」。j%10000 共有六次沒有余數的情況:迭代數為 10000、20000、30000、40000、50000、60000 時。該打印輸出會幫助我們很好地了解網絡的學習進展。
代碼 + str(np.mean(np.abs(l2_error)))) 取誤差的絕對值,然后求平均數并打印出來,從而簡化了打印過程。
現在我們已經知道預測結果(l2)距離真值(y)的距離,并打印了出來。但是,我們和城堡的距離實在太遠,我們要如何降低目前令人失望的預測誤差 0.5,最終到達目的地呢?
一步步來。接下來,我們將了解調整網絡的哪一部分才能改進下一次預測的結果,之后會討論如何調整網絡。
4.3 我們需要調整網絡的哪一部分?
先看下圖,神經網絡真正用來學習和記憶的核心部分是突觸,而不是神經元。我們的網絡中有 16 個變量:3x4 矩陣 syn0 中的 12 個變量和 4x1 向量 syn1 中的 4 個變量。查看下圖,你會發現每一條線(「邊」或「突觸」)表示一個變量,它包含一個數值,這就是權重。
?
?
我們可以控制這 16 個權重。
輸入 l0 是固定的,不能改變。l1 由 syn0 中的權重決定(l1 = syn0 x l0),l2 由 syn1 中的權重決定(l2 = syn1 x l1)。上圖中的 16 條線(突觸、權重)是神經網絡達到目標過程中唯一能夠調整的數字。
現在我們知道了預測值與真值的距離,知道了 l2_error。那么我們如何利用這些數值調整兩個矩陣中的 16 個權重呢?這個答案就是 AI 最神奇的屬性之一:這是一個統計學和概率概念,叫做置信度。
4.4 置信度:使冷冰冰的數字像人類一樣思考
代碼行 88:
l2_delta = l2_error*nonlin(l2,deriv=True)
「置信度」這個術語可能比較抽象,但是它實際上就是我們每天都在用的東西。我用一個有趣的故事來提醒你:
先知城堡 2:你必須明白你其實一直在使用置信度!
在先知城堡的故事中,你執行前饋,驅使 l2 向最佳預測 y(城堡)前進,但是當你到達 l2 后卻發現你雖然距離城堡更近了,卻仍未到達。第二天早上你發現自己在家中醒來(回到 l0),然后開始再一次的旅程(新的迭代)。
你該如何改進駕駛方向,才能做有用功呢?
首先,在你今天的旅程結束時,你急切地詢問當地的騎士這個地方距離城堡還有多遠,騎士告訴你答案(即 l2_error)。每天的旅程結束時,你都要計算如何改變權重才能使明天的 l2 預測比今天更好,并最終使你抵達城堡。這就是 l2_delta(參見下圖),即要想使明天的 l2 抵達城堡,今天你需要對權重做出的改變。
重點來了:
注意 l2_delta 與 l2_error 不同,l2_error 僅告訴你與城堡的距離,而 l2_delta 則影響你對方向的信心。你在衡量信心。這些置信度數字就是導數(不過,這里暫且忘記微積分吧,我們暫時使用這個詞「坡度」),即 l2 每個值的坡度。這些坡度就是你對今天旅程中每一次轉向的自信程度。有些方向你非常確定且自信,而有些則不然。
使用置信度這一概念來計算明天能到哪里似乎有點抽象?實際上,你每時每刻都在使用置信度導航,只不過你沒意識到而已。接下來我們就讓你「恢復」這一意識。
回憶一下你的迷路經歷。一開始,你按照自己以為正確的道路前進,你非常自信。但是慢慢地你發現旅途似乎比預期更加漫長,你開始懷疑是否忘記拐彎。你沒最初那么自信了。隨著時間的流逝,你原本應該已經到達目的地了,然而你還在路上,此時你更加確定錯過了一次拐彎。這時候你的自信度很低。你知道自己沒有到達目的地,但你不確定如何從現在的位置到達目的地。于是你決定停下來問路,但是被問路的女士告訴你的拐彎和路標太多,你沒記全,你沿著她指的方向走到一半又不知道怎么走了。這時你會再次問路,不過這一次你離目的地更近,前進的方向也更加簡單,你沿著方向前進最終抵達目的地。
在這段講述中,你需要注意以下幾件事:
首先,你通過試錯進行學習,你的信心在不停變化。稍后我會解釋為什么置信度使得網絡試錯學習,以及 sigmoid 函數如何提供重要的置信度。
其次,注意你的旅程分為兩部分:第一部分從你啟程到第一次問路(l1),第二部分是從第一次問路到 l2(你以為已經到達目的地了)。但是后來你意識到這里并不是終點,不得不詢問離真正的終點還有多遠。
你看到置信度在旅程中的角色了嗎?一開始,你確定自己走在正確的路上,然后你懷疑自己少拐了個彎,之后你確定自己少拐了個彎,決定停下來問路。圖中這兩個分割的部分就像狗腿一樣彎,但隨著每日旅程的改進,狗腿將一點點變直。
這和你前往城堡的過程一樣:
?
?
每天,你(3 層神經網絡)沿著一組方向(syn0)前往城堡。結束時你發現自己停在了 l1,就詢問接下來的方向(syn1)。這些方向使得你到達當天的終點(預測結果),你以為那里就是目的地城堡。但事實上你面前并沒有城堡。于是你詢問騎士:「我離城堡還有多遠?」(l2_error 的值是多少?)你是一個天才,于是你將 l2_error 與你對每一次轉向的置信度(l2 的坡度)相乘,得出了明天可以到達的地方(l2_delta)。
注意:這個比喻有一點不恰當,即當你停在 l1 問路時,女士告訴你新的方向。而事實上,你的方向(syn1 值)是你準備更新上一次迭代時就已經選擇好的。因此準確來說,那位女士并沒有和你說話,只是舉起了你昨天在那里消失之前給她的路牌,因為你知道今天會再次路過她身旁。
在繼續之前,你需要厘清 3 項事實:
你到達的地方,即當前位置(l2); 你距離城堡的距離 (l2_error);
要想提高對明天旅程更接近城堡的信心,你需要做出的方向改變 (l2_delta)。
了解這三項事實后,你可以計算前進方向(即突觸權重)需要做的改變。接下來,我們來看如何使用 sigmoid 函數獲得置信度,并利用置信度計算 l2_delta。
4.5 如何利用 Sigmoid 函數的曲線特征得出置信度
sigmoid 函數的四步魔鬼操作將讓你見識到她的魅力。對我而言,神經網絡的學習能力很大程度上基于這四步操作。
2.5 節解釋了 nonlin() 函數可以將 l2_LH 的值轉換成統計概率(l2),這是 sigmoid 函數四大優勢中的第一點。
而 sigmoid 函數的第二部分 nonlin(l2,deriv=True) 可以將 l2 中的 4 個值轉換成置信度。這就是 sigmoid 函數的第二大優勢。如果網絡每個預測(l2 中的 4 個值)都具備高準確率、高置信度,則這是一次不錯的預測。我們不會改變帶來如此優秀預測結果的 syn0、syn1 權重,只想改變那些不 work 的權重。接下來,我將介紹 nonlin(l2,deriv=True) 如何告訴我們哪些權重需要格外注意。
置信度幫助我們首先確定哪些權重需要改變。如果 nonlin(l2) 生成的置信度為 0.999,則該網絡「很確定該顧客購買了這款貓砂」,而置信度 0.001 則等同于「我很確定該顧客沒有購買這款貓砂」。但是處于中間的數字怎么辦呢?低置信度數字通常在 0.5 左右。例如,置信度 0.6 意思是「該顧客可能會買這款貓砂,不過我不確定。」置信度 0.5 表示「兩條路都可行,而我站在中間猶豫不決……」
也因此我們需要關注中間的數字:0.5 周圍的數字都不堅決,都缺乏信心。那么,我們應該如何改變網絡才能使 l2 的四個值都具備高準確率和高置信度呢?
關鍵在于權重。上文已經提到,syn0 和 syn1 是神經網絡的絕對核心。我們將對 l2_error 的四個值執行數學計算,得到 l2_delta。l2_delta 即「要使網絡輸出 (l2) 更接近 y(真值),我們想看到的 l2 改變。」也就是說,l2_delta 即你想在下次迭代中看到的 l2 變化。
這就是 Sigmoid 函數四大優勢的第三點:l2 的四個概率值都是位于 sigmoid 函數 S 曲線圖上的某個點(見下圖)。例如,l2 的第一個值為 0.9,我們在下圖中找 0.9 所對應的 Y 軸位置,會發現它對應下圖中的綠色點:
?
?
圖源:https://iamtrask.github.io/2015/07/12/basic-python-network/
除了綠色點,你注意到穿過該點的綠色線嗎?這條線表示該點的坡度(正切值)。計算一個點的坡度不需要你懂微積分,計算機會幫你做這些。但是你需要注意,S 曲線上上限(接近 1)和下限(接近 0)的位置的坡度很淺。sigmoid 函數曲線上的淺坡度正好對應預測結果的高置信度!
你還需要了解 S 曲線上的淺坡度意味著坡度值較小,這是一個好消息!
因為,當我們更新突觸(權重)時,我們并不想改變能帶來高置信度、高準確率的權重,它們已經足夠好了。所以我們只想對其引入微小的改變。事實上,這些權重的改變(l2_delta)是由原數值乘以特別小的數字(坡度)來進行的,這恰好符合我們的期望。
這就是 Sigmoid 函數四大優勢中的最后一項。
高置信度對應 S 曲線上的淺坡度,淺坡度對應一個很小的數字。從而使得 syn0 和 syn1 的值乘以這些小數字后能夠滿足我們的期望,即突觸中有用的權重值基本上變化不大,這樣就能夠保持 l2 的置信度和準確率。
同理,低準確率的 l2 值(對應 S 曲線的中間點)即是 S 曲線上坡度最大的數字。即上圖 Y 軸 0.5 左右的值對應 S 曲線的中間點,這里坡度最陡,因而坡度值也最大。這些大數值意味著當我們將它們與 l2 中的低準確率值相乘時,這些值會發生很大改變。
具體來講,我們應該如何計算 l2_delta 呢?
我們已經找到了 l2_error,即第一次預測 l2 與目標值 y 的距離。而我們尤其關心「大誤差」(Big Miss)。
在代碼行 88 中,我們要做的第一件事就是使用 sigmoid 函數的第二部分 nonlin(l2,deriv=True) 找出 l2 預測中 4 個值各自的坡度。坡度將告訴我們哪些預測可信度高,哪些比較牽強。這就是我們找出并修復神經網絡中最弱環節(低置信度預測)的方式。接下來,將這 4 個坡度(置信度)與 l2_error 中的四個誤差值相乘,得到的就是 l2_delta。
這一步非常重要。你注意到我們將大誤差與低準確率、高坡度值的 l2 預測相乘嗎?這是最重要的部分,稍后我將給大家解釋。
現在,我們先看這部分的可視化展示,如下所示:
y: l2: l2_error: [1] [0.50] [ 0.50] [1] _ [0.90] = [ 0.10] [0] [0.05] [-0.05] [0] [0.70] [-0.70]
下面的公式是理解神經網絡學習原理的重點:
l2 slopes after nonlin(): l2_error: l2_delta: [0.25] Not Confident [ 0.50] Big Miss [ 0.125] Big change [0.09] Fairly Confident X [ 0.10] Small Miss = [ 0.009] Small-ish Change [0.05] Confident [-0.05] Tiny miss [-0.003] Tiny change [0.21] Not Confident [-0.70] Very Big Miss [-0.150] Huge Change
?
注意,大誤差即 l2_error 中值較大的數字,而低準確率的 l2 值的坡度也最陡,即它們在 nonlin(l2,deriv=True) 中的數字最大。因此,當我們用大誤差乘以低準確率 l2 值時,就是大數字與大數字相乘,因此也將得到 l2_delta 向量中最大的數字。
l2_delta 即「下一次迭代中我們希望看到的 l2 改變」。較大的 l2_delta 值意味著下一次迭代中 l2 預測會有很大的改變,而這正是通過大幅改變對應的 syn1 和 syn0 值來實現的。將這些大數值與 syn1 中已有的值相加,得到的更新權重將在下一次迭代中帶來更好的 l2 預測結果。
為什么要使用 l2 的坡度呢?
此舉的目的是為了更快地修復 16 個權重中最不合適的那些。上文我們提到 sigmoid 函數的 S 曲線圖時說過,l2 坡度與置信度相關。也就是說,大坡度值等于低置信度,最小坡度值等于最高置信度。因此,將小數字與 l2_error 中的對應值相乘不會給 l2_delta 帶來大的變化,而我們也恰好不想改變那些權重,它們已經做得很好了。
但是置信度最低的 l2 預測具備最陡的坡度,坡度值也最大。當我們將這個大數字乘以 l2_error 時,l2_delta 的數字也會很大。之后當我們更新 syn1 時,大的乘數意味著大的積、大的改變或調整。正該如此,因為我們想最大程度地改變置信度最低的權重,這也是調整權重大小時使我們獲得最大收益的地方。總之,l2 坡度指示了每個 l2 預測值的置信度,從而允許我們決定哪些數字最需要調整,以及如何最快地調整它們。
但是精彩的部分還未結束,回到關于推斷問題這一重點上。
在 3.4 節我們討論了,改變 syn0 的權重類似于改變對預測最有用的推斷問題。例如,訪問過貓砂網站的養貓人更有可能購買這款貓砂,而喝進口啤酒但不養貓的人購買貓砂的幾率相對較低。這就是 l2_delta 的用武之地。l2_delta 增加最有用推斷問題的重要性(權重),降低不那么有用推斷問題的重要性。接下來,我們將了解如何確定隱藏層 1 中每個推斷問題的效用。
不過,首先我們先確定自己是否完全了解 l2 置信度如何修改 syn1 中的權重。sigmoid 函數隨機選取矩陣中的任一數字,
將它轉換為統計概率; 將概率轉換為置信度; 對突觸進行或大或小的調整; 該調整(幾乎)總是朝著提升置信度和準確率的方向,從而降低 l2_error。
sigmoid 函數是一個奇跡,它讓矩陣中的數字可以根據置信度進行學習,數學多么神秘!
接下來,我們來看置信度如何告訴我們要對 syn0 做怎樣的改變才能實現更加準確的層 1,然后實現更加準確的層 2。
4.6 將「大誤差/小誤差」置信度應用于層 1
代碼行 100:
l1_error = = l2_delta.dot(syn1.T)
城堡的故事非常有用:在那幅圖中,我們可以看到 l2_error 代表 l2 預測與城堡的距離,也可以了解到 l2_delta 是目前的預測與下一次迭代中預測結果之間的距離。剛剛介紹的「學習大誤差/小誤差」的方法可以幫助我們理解,如何調整當前的 syn1(見代碼行 115)。
現在你已經準備好用來更新 syn1 的 l2_delta 了,那么為什么不也更新一下 syn0 呢?
怎么做?回到 4.2 節,我們找到了 l2_error,知道 l2,也知道我們要追尋的「完美 l2」——y。但是,在 l1_error 這里,情況變了,我們不知道「完美 l1」是什么。
如何找到「完美 l1」呢?讓我們再次回到推斷問題。
回到 3.4 和 4.5 節,關于推斷問題(特征)的概念,以及特征問題組合。當我們改變 syn1 中的權重時,我們真正在做的其實是用我們想賦予該問題或問題組合的重要性進行試驗。對于 syn0 也是一樣,我們想找出能夠幫助確定 l1_error 的數學方法,因為 l1_error 將告訴我們哪些問題的優先級被錯誤分配了或者哪些問題被錯誤地組合在了一起。當我們接著計算 l1_delta 時,l1_delta 將告訴我們下一次迭代中更好的問題優先級和問題組合。
例如,syn0 最初的權重提示推斷問題「這個人富有嗎?」是重要的問題。然而,隨著訓練的開展,網絡意識到貓砂并非奢侈品,是否富有并不重要,因此網絡決定調整 syn0 的值,以降低推斷問題「這個人富有嗎」的重要性。如何降低呢?將 syn0 中該問題的對應值乘以較大的 l1_delta 值,不過該值前面帶有一個負號。然后網絡將更新新的推斷問題「這個人對貓屎過敏嗎?」。更新方法是將該問題對應的 syn0 值乘以較大的 l1_delta 值(正值)。
這兩個推斷問題的優先級不斷變化。但是另一個推斷問題「這個人知道如何網絡購物嗎」一直都很重要,因為目標受眾是喜歡在線購物的人。因此網絡將該問題對應的 syn0 值乘以較小的 l1_delta 值。這樣,其優先級就會保持不變。
另一個謎題是你需要多少推斷問題?在這個網絡中,我們一次只能容納四個推斷問題。這是由 syn0 的大小決定的。如果你沒有足夠的推斷問題,那網絡可能就無法做出準確的預測。而問題太多則會浪費過多精力。
現在,我們知道改變 syn0 的值會改變某個推斷問題對于 l1 的貢獻,進而影響 l1 對 l2 準確預測的貢獻程度。syn1 使用特征問題組合,并進一步優化從而取得更加準確的 l2。
我在這個階段提出的問題是:為什么要使用 l2 的坡度?
上文提到,我們使用 l2 坡度來更快地調整權重:l2 坡度告訴我們每個 l2 預測的置信度,從而允許我們決定哪些數字最需要調整,以及如何最快地調整它們。
我們來看數學部分。現在我們知道下一次迭代中 l2 預測要做的改變是 l2_delta,也知道到達目前預測結果的 syn1 值。那么重要的問題來了:如果我們用 l2_delta 乘以這次迭代的 syn1 值會怎么樣?
這就像好萊塢編劇一樣:你寫了一個悲傷結局的電影,主角被龍噴出的火焰灼傷然后被吃掉,所以未能到達城堡。導演看了劇本滿屋咆哮:「我要 happy ending,我要主角打敗惡龍,發現生命的意義,然后轉身離開!」
你同意按老板的意思修改劇本。你知道了要達到的結局,就轉回去尋找哪個動作出了錯。哪個情節使得你沒有成為英雄反而被龍吃掉?
數學就是在做這樣的事情:如果你把 l2_delta(期望的完美結局)乘以 syn1(導致錯誤結局的情節),那么你將收獲 l1_error。改變造成錯誤結局的情節,下一版劇本將變得更好。
再次提醒:如果你將下一次想去的地方(l2_delta)與旅程第二部分的錯誤方向(現在的 syn1)相乘,則乘積將是「旅程第一部分出錯的地方」,即 l1_error。
知道了 l1_error,你就可以計算在下次迭代中將 l1 改變多少才能得到更好的 l2 預測,而這就是 l1_delta。
代碼行 109:
l1_delta = l1_error * nonlin(l1,deriv=True) l1_delta 的計算方法和 l2_delta 一樣,此處不再贅述。
4.7 更新突觸
代碼行 115-116:
115 syn1 += l1.T.dot(l2_delta) 116 syn0 += l0.T.dot(l1_delta)
代碼的最后部分是高光時刻:所有的工作完成了,我們恭敬地將 l1_delta 和 l2_delta 搬到神圣的領導者「突觸國王」面前,他才是所有操作的真正核心。
計算 syn0 更新的第一步是 l1_delta 乘輸入 l0,然后將乘積與目前的 syn0 相加。這就使得 syn0 的構成部分發生很大改變,從而對 l1 產生更強影響。也就是說,我們降低了這次迭代中誤認為重要的問題或問題組合的重要性。它們并沒有我們想象的那么重要,因此用 l1_delta 在正確的方向上修正錯誤,從而使我們在下一次迭代中距離城堡更近。
「試錯學習」,這就是網絡所做的事情。以下介紹了網絡如何利用試錯來學習:
嘗試:網絡執行 6 萬次前饋,即嘗試 6 萬次不同的特征問題組合,只為輸出最佳的預測結果;
錯誤:網絡將目前的預測與真值進行對比,找出誤差;
學習:網絡使用置信度(梯度下降)找出下一次迭代需要在正確的方向上做出多少改變,即 delta。然后網絡使用新的 delta,并將它乘以舊的 l1 或 l2,然后將乘積加在舊的 syn0 或 syn1 上。你明白為什么說突觸是神經網絡的大腦了嗎?它們在試錯中學習,和人類一樣。
你可能會說:「神經元將一切聚集在一起!」(對準確預測貢獻最多的特征組合在網絡中的最終優先級最高)。是突觸權重將一切特征串聯起來,從而做出最后的預測。
4.8 置信度的坡度與紅色碗表面的坡度相同!
回憶一下 1.3 提到的紅色碗,我們在本文中介紹的所有技巧都呈現在這個碗中。
當我們使用舊層和新 delta 的乘積更新突觸時,它推動突觸在正確的方向上前進,去往置信度高、準確率高的預測結果。「在正確的方向上推動」意味著:
l2_delta 的一些值可能是負的。l2_delta 為負值意味著下一個迭代中的 l2 接近 0,這會以多種方式在突觸中呈現。因此找出給予我們「方向感」的坡度非常重要。方向感,或者說坡度,是推動球到達碗底的關鍵。網絡的工作是增加權重,使得下一個 l2 被 l2_delta 撼動。
?
?
上面這幅簡單的二維圖展示了梯度下降的小球。
代碼行 88 計算出 l2 每個值的坡度值。在上圖的 3 個凹陷中(或者說「碗」),很明顯真正的全局最小值是最左側最深的碗。出于簡潔考慮,我們假裝中間的碗是全局最小值。那么,向右下的陡峭坡度(即負坡度值,如上圖綠色線所示)意味著小球會在這個方向上滾動很大幅度,導致 syn1 中的對應權重出現較大的負值調整,而該權重將用于在下一次迭代中預測 l2。也就是說,四個 l2 值中有一些會接近 0,則預測結果就是該顧客不購買這款貓砂。
但是,如果左下有一個淺坡度,這意味著預測值已經具備高準確率和置信度,所以坡度值是小的正數,球只會向左稍微挪動一點,syn1 中的對應權重也只會有些微調整,這樣下一次迭代中該值的預測結果不會有太大改動。這奏效的原因是,隨著網絡預測的準確率越來越高,小球的來回移動幅度將越來越小,直到它到達全局最小值——碗底,不需要再移動為止。
現在第四部分「從試錯中學習:梯度下降」即將結束。是時候坦白一個事實了:你實際上已經在做微積分了,只不過我們沒有指明它是微積分!
這個樸素的真相就是:
l2 每個值的置信度 = l2 的坡度 = l2 的導數
但置信度只是冰山一角,接下來我們將介紹可用于更復雜神經網絡(具備更多層和特征)的策略——反向傳播。
5. 反向傳播
5.1 打破反向傳播的迷思
反向傳播是執行梯度下降的核心工具,但它以難學而著稱……不過反向傳播是為了幫助我們,它只有好的意圖。
關于反向傳播有兩個迷思,接下來我們就來一一打破。
迷思 1:反向傳播非常難
錯。反向傳播只是需要耐心和堅持罷了。如果你只讀過一次關于反向傳播的文本就舉手投降,那你就完了!但是如果你把 Grant Sanderson 關于反向傳播的視頻 3 慢速看 5 遍,再把關于反向傳播數學知識的視頻 4 看 5 遍,你會覺得很順利。(視頻地址:https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi)
迷思 2:理解反向傳播,必須會微積分
很多在線文章稱,你需要了解多變量微積分才能理解 AI。這種說法并不對。深度學習大牛 Andrew Trask 表示,如果你上過三個學期的大學微積分課,其中只有一小部分材料對學習反向傳播有用:鏈式法則。然而,即使你學了 3 學期大學微積分課程,反向傳播中的鏈式法則與你在大學課堂上見到的并不一樣。你最好把 Kalid Azad 關于鏈式法則的書讀五遍。
我之前一看到「導數」這個詞就發慌,還沒怎么著自己就先認輸了。大家不要重蹈我的覆轍。你必須用這樣的內心聲音告誡自己:「我不需要任何背景也能掌握這個概念。」
5.2 反向傳播的定義
5.2.1 反向傳播之于梯度下降,相當于鏈式法則之于反向傳播
第四章介紹的梯度下降過程是理解梯度下降的重要步驟,不過第五章將從不同的角度理解梯度下降,它可應用于大部分神經網絡。
反向傳播是理解神經網絡的必要環節。其優雅的數學運算允許我們同時調整每一個權重,而這是通過鏈式法則實現的。
什么是鏈式法則?我先介紹數學定義,再用類比的方式幫助你理解它。
數學定義:鏈式法則的本質是,嵌套函數的導數是構成嵌套函數的每個函數的導數乘積。即,最終導數等于構成函數的導數乘積。
類比:把鏈式法則看作馬戲團的雜耍人。神經網絡有 16 個權重,因此我們把這些權重想象為 16 個保齡球瓶,雜耍人需要使它們同時停留在空中。假設這些球瓶大小不一,則雜耍人必須足夠嫻熟才能用合適的力道在恰當的時間點拋出每個球瓶,同時確保其他 15 個球瓶停留在空中。現在我們假設這位雜耍人很厲害,當一個球瓶在空中突然體積改變時,他能夠立刻調整其他 15 個球瓶,來彌補這一變化。他可以適應球瓶的任意變化并進行調整,使全部 16 個球瓶都停留在空中!
簡直是天才,對吧?鏈式法則也是如此:從一個權重到最終預測有很多路徑,鏈式法則調整這些路徑,使它們全都是相對較直的路。我們示例中的神經網絡比較小,事實上鏈式法則同樣適用于包含數百萬甚至數十億權重的大型神經網絡。
5.2.2 我們想要解決的問題:如何調整 16 個權重
難點在于:每次你調整 16 個權重中的其中一個時,整個神經網絡都會受到影響。如何既考慮到權重調整對預測誤差的影響,還能計算出每個權重的最佳調整值呢?這聽起來很復雜。
事實也的確如此。我們剛剛已經使用 Python 代碼和置信度進行了必要的計算,但是為了掌握這個要點,我們需要從另一個角度再做一次:鏈式法則。
我們將利用坡度使 16 個球瓶同時待在空中。而找出坡度的秘訣就是導數。
5.2.3 鏈式法則和變化率
這一次,我們使用鏈式法則找出變化率的坡度。
變化率是一個重要概念。3.2 節提到 syn0,1 為 3.66,那么關鍵問題來了:當我們增加或減小 syn0,1 的值時,這對 l2_error 的增減有多大影響?也就是說,將 l2_error 對 syn0,1 變化的反應看作「敏感度」,或者變化率:即調整 syn0,1 的值后,l2_error 值的變化會與之成比例。這個比例是多大呢?l2_error 對 syn0,1 的敏感度如何呢?
記住:反向傳播的目標是找出每個權重需要調整的量,從而在下一次迭代中盡可能地降低 l2_error。但是挑戰在于每個權重都會對其他 15 個權重產生影響,因此權重 1 調整的量依賴于權重 2 調整的量,權重 2 的調整量依賴于權重 3 調整的量,依此類推。如何調整 16 個不斷變化的保齡球瓶取決于某個瓶子有多大變化。
不過我有一個更好的類比:蝴蝶。
5.3 定義鏈式法則:它像蝴蝶效應
還記得我們試圖解決的問題嗎?「如何調整 16 個權重,使每個瓶子都能與其他 15 個球瓶完美搭配,同時停留在空中,并且最小化 l2_error,幫助我到達城堡,找到生命的意義。」
我們將這個大問題分解一下。我們先看其中的一個權重 syn0 (1,1)(本文將其簡寫為 syn0,1)。我如何以有序、依存的方式調整 syn0,1,才能使 16 個球瓶正常運轉,同時還能盡可能多地降低 l2_error?
答案就是鏈式法則,它和蝴蝶效應很像(一只新墨西哥州的蝴蝶扇動翅膀,引發了一系列連鎖事件,引起中國颶風)。我們來看這個類比如何應用到反向傳播中必須計算的變化率上:
?
?
而當我們將 syn0,1 的值進行增減時,新墨西哥州的蝴蝶開始扇動翅膀。出于簡潔性考慮,我們將 syn0,1 的值增加到 3.66,這一舉動引發了一系列連鎖反應——其他權重合力組成的「完美風暴」,從而降低 l2_error。
接下來,我們將蝴蝶效應和另一個類比——連鎖反應結合起來。
5.4 蝴蝶效應遇見 5 個變化率:連鎖反應
蝴蝶效應只是更寬泛概念——連鎖反應的一個實例。下圖將蝴蝶效應和變化率改變帶來的連鎖反應結合了起來,圖看起來很復雜,但它其實由以下三項組成:
和前文圖一樣的白色圓圈,從左至右表示蝴蝶效應;
圖下方方框中是反向傳播鏈式法則中的變化率。這些方框從右至左反向移動,即最右方的變化率改變會通過其他變化率最終影響到最左側的變化率。
彩色箭頭起連接作用。
Ripple 1,即對 syn0,1 進行調整后的第一個連鎖反應,將導致 l1_LH 上升一定比率,這個比率就是變化率。這就是「內華達州的大風」(見下圖灰色箭頭)。
由于 l1_LH 是 sigmoid 函數的輸入,那么計算 l1_LH 和 L1 之間的變化率就需要衡量 Ripple 2,即「洛杉磯的狂風」(見下圖紫色箭頭)。
很顯然,l2_LH 會受到 l1 變化及 l1 乘以 syn1,1 的影響(為了簡化示例,syn1,1 及其他 14 個權重的值并未改變),因此 l2_LH 和 l2 之間的變化率帶來了 Ripple 3——「夏威夷的雷暴」(見下圖黃色箭頭)。
l2 的變化與 l2_LH 的變化成比例,因此 l2_LH 的坡度將帶來 Ripple 4——「太平洋的暴風雨」(見下圖綠色箭頭)。
最后,y 值減去新的 l2 得到的余數——l2_error 將會改變,即 Ripple Effect #5——「中國的颶風」(見下圖藍色箭頭)。
?
?
我們的目標是計算每次連鎖反應帶來的變化率,找出 syn0,1 需要增減的量,以便在下一次迭代中最小化 l2_error。當我們說神經網絡「學習」的時候,我們其實表達的是在每一次迭代中降低 l2_error,使得每一次迭代中網絡的預測結果準確率都有提升。
倒過來看,我們可以說「颶風 l2_error 的變化取決于 l2 的變化,l2 的變化取決于 l2_LH 的變化,l2_LH 的變化取決于 l1 的變化,l1 的變化取決于 l1_LH 的變化,而 l1_LH 的變化取決于蝴蝶 syn0,1 的變化」。該示例中所用的規則即步長等于坡度。
此外,我還想讓大家了解 Python 代碼和這些是如何同步的。接下來,我們就來看看哪些代碼行對應鏈式法則函數中的變化率。
5.5 代碼與鏈式法則的同步
?
?
5.5.1 移除中間變量
上圖中出現了代碼行 66 到 115 中的代碼,紅色線連接代碼與變化率。如果你覺得這些連線不太對,這是因為原始代碼將反向傳播過程分解成了多個中間步和多個額外的中間變量。下面我們將從代碼中移除以下四個中間變量:l2_error、l2_delta、l1_error 和 l1_delta。
先從紅色箭頭連接的代碼片段開始。假設四個中間變量已經移除,還剩下什么呢?我們來看下圖的最底下一行。將中間變量從代碼中移除后,這行代碼并沒有改變。現在這三行完美同步了。
?
?
5.6 實戰演示
下面,我們看看改變 syn0 中一個權重的數學背景。出于簡便,這里的變量和第 3 節相同:
l0= 1 syn0,1= 3.66 l1_LH= 3.82 l1= 0.98 syn1,1= 12.21 l2_LH= 0.00 l2= 0.50 y= 1 (This is a "Yes" answer to survey question 4, "Ever bought Litter Rip?" which corresponds to training example #1, i.e., row 1 of l0) l2_error = y-l2 = 1-0.5 = 0.5
5.6.1 不要重蹈我的覆轍
在解決變化率時,千萬不要和我犯同樣的錯誤。我最初是這么計算變化率的(錯誤示范):
?
?
那么上圖有什么問題呢?
5.6.2 不是為了計算變化率,而是為了計算變化率的變化:坡度
在上圖中,我忘記了此時的目標是計算相對變化。看到上圖中每個變量前面的 d 了嗎?我忽略了它們,只寫下了每個變量的當前值。
而那些 d 表示 delta,delta 非常重要,不能忽略。
我們想預測未來。我們想知道 syn0,1 改變多少會最終導致下一次迭代中 l2_error 減小。也就是說,我們不是要對比 syn0,1 中單個數值 3.66 對 l2_error(0.5)的影響,而是想知道 syn0,1 中 3.66 應該如何變化才能影響到 5 個連鎖變化率,從而最終產生更好的 l2_error。我們不想要一堆變化率,我們想要的是能改變 delta 的變化率。
5.6.3 計算變化率的公式:x_current 和 x_nearby
?
?
「current」表示每個變量的當前值,「nearby」表示我們想提供的與當前數字接近的數字。nearby 數字減去 current 數字,會得到一個很小的數字。如果兩個點在一條曲線上,這方便計算出更準確的坡度值。接下來我們過一遍上述 5 個變化率,練習如何尋找每個變化率的 nearby 數字。
下圖展示了完整的反向傳播:
?
?
5.6.4 示例
從后往前,我們要先計算的 Ratio 1 是 Ripple 5:d l2_error / d l2。那么「current」和「nearby」從哪里來呢?
x_current 是前饋計算出的 l2——0.5; y_current 是 y-l2 = 1-0.5 = 0.5,即 l2_error; x_nearby 是我們舉的一個簡單例子。如果 l2 為 0.6,距離 x_current(0.5)較近,則 y-0.6 = 0.4; 也就是 x_nearby = 0.6,y_nearby = 0.4;
知道這四個變量后,計算就很簡單了,坡度即敏感度= -1;
這意味著 l2 每增加 0.1,l2_error 就減 0.1。
其他幾步的計算過程此處不再贅述,想了解更多,請閱讀原文。
總結
課程到這里已接近尾聲,現在你們已經掌握了梯度下降的核心工具——反向傳播。最后,我將送給大家一份離別禮物:
Andrew Trask 教會我:記住這些 Python 代碼才能精通。出于以下兩點原因我認同這個觀點:
當你試著根據記憶寫出代碼時,你發現你忘記的地方恰好是不理解的地方。全部理解后,所有代碼會永遠存在你的大腦中;
這些代碼是構建所有深度學習網絡的基礎。掌握了這些代碼,你以后學習每一個網絡、每一篇論文都會更加清晰和簡單。
牢記這些代碼幫助我編了一個把所有概念串在一起的荒誕故事,你也可以編造自己的故事。
希望你能享受深度學習旅程!
原文鏈接:https://colab.research.google.com/drive/1VdwQq8JJsonfT4SV0pfXKZ1vsoNvvxcH








