一言以蔽之:這個世界再彎的東西,只要你肯切得夠細,都可以用直的拼出來——神經網路其實就是在拼折線而已。
好啊,我們來上課吧。
上一講的最後,我留了一個問題給大家。我們花了一整堂課,做出來的 model 是這個:
這是一條直線。就算後來我們加到七個 feature,變成 ,它變成一個高維度的平面,但本質上還是直的。
可是這個世界很多事情不是直的啊。
今天這堂課,就是要來解決這件事情。而且我先跟你講,解法出乎意料地土——土到你會覺得「這樣也可以喔?」
這堂課我們分成六段走:
先備知識:上一講的三個步驟(定 model → 定 Loss → gradient descent)。如果你忘記了,回去翻一下,因為今天只會換掉第一步,後面兩步完全照抄。
這一版是互動的。每一個滑桿你都可以拉,每一個「訓練」按鈕按下去,網路都是真的在你的瀏覽器裡從頭 train 的,不是預先算好的表演。請務必動手——這堂課有一半的東西是動手才會懂的。
這個問題其實就是上一講練習 3 留給你的那題。
假設你還是那個 YouTuber。你現在想知道一件事:我的影片到底該剪多長?
這個問題很實際喔。你手上有兩百支影片的資料,每支影片你知道它多長、拿到多少觀看數。你想找出那個「甜蜜點」。
那你憑直覺想想看,影片長度跟觀看數會是什麼關係?
你八成會猜:太短的沒人看——三十秒的東西講不出什麼名堂,觀眾覺得被浪費時間。太長的也沒人看——沒有人願意點開一個二十五分鐘的影片。中間某個長度剛剛好。
所以它是一個倒 U 形。
不過真實資料通常比你想的還怪一點。我們先把資料畫出來,然後你自己來當 optimizer——拉那兩根滑桿,看你能不能把那條直線喬到資料上面。
你會發現這張圖比我們剛剛猜的還複雜一點。它不只是一個倒 U,它有兩個山頭:
第二個山頭是什麼?那是深度長片的死忠觀眾。願意點開二十五分鐘影片的人不多,但那些人是真的想看完。
好,那你剛剛拉滑桿拉得怎麼樣?你調不出來的對不對。怎麼調都有一大半的點離線很遠。
而且我要特別強調一件事,這件事是今天第一個重點:這不是因為 gradient descent 沒找好。
你按一下上面那顆「顯示最好的那條直線」。那條線不是 train 出來的,是用最小平方法直接解出來的——數學上它就是所有直線裡面最好的那一條,不存在「再多跑幾步會更好」這種可能。它的 RMSE 是 30.7 千次。
那「完全不學」有多爛呢?如果你每支影片都閉著眼睛猜平均值 42.9 千次,RMSE 是 34.0 千次。
所以我們辛苦定義了 model、定義了 Loss、跑了 gradient descent,換來的是——從 34.0 進步到 30.7。這根本等於沒做事。
那問題出在哪?問題出在——答案根本不在直線裡面。
這邊我們要建立一個很重要的觀念。你要把 model 想成一個「函式的集合」,不是一個函式。
什麼意思?當你寫下 的時候,你其實是在說:「我要的答案,就在所有直線這個集合裡面。」然後 跟 的工作,是決定你從這個集合裡面挑哪一條。
所以機器學習在幹嘛?其實就是在一個集合裡面撈東西而已。剛剛你拉滑桿,就是在那個集合裡面一條一條翻。
那現在的問題就很清楚了:
我們在大海裡面撈針。撈了半天撈不到。
為什麼呢?因為那根針根本不在這個海裡面。
你的 model 是「所有直線的集合」,可是真正的答案是一條有兩個山頭的曲線,它不是直線,所以它不在你的集合裡。你撈到天荒地老都撈不出來。
這件事情有一個名字,叫做 model bias(模型偏誤)。白話文就是:你的 model 太笨了,笨到裡面根本沒有正確答案。
注意這個跟「沒 train 好」是完全不同的兩件事,千萬不要搞混:
所以今天要做的事情就是——把海變大。怎麼變大呢?
好,那神奇的地方來了。這邊是今天整堂課的種子。
我們先不要想什麼神經網路。你就看著那條彎彎的曲線,然後想一件事:如果我只准你用「直的線段」,你能不能把它畫出來?
你會說不行啊,它是彎的。
那我改問:你能不能畫得「很像」?
想想看。如果我給你 3 段直線,你大概只能畫個大概。如果給你 10 段呢?就有點像了。給你 50 段呢?
與其用講的,不如你自己拉。下面這根滑桿控制段數。慢慢往右拉,看右邊那條誤差曲線掉下去。
你會發現:段數越多,越像。3 段的時候最大誤差是 44.2,10 段掉到 12.6,50 段只剩 0.6——那個灰色的粗線幾乎完全被橘色蓋住了。
不過這裡有個小細節,你拉的時候可能會注意到:它不是每加一段就一定變好。從 4 段加到 5 段,誤差反而從 33.8 升到 37.7;7 加到 8 也是。為什麼?因為誤差取決於轉折點剛好切在哪裡——如果新的切點剛好落在山腰上,那反而會錯過山頂。所以整體趨勢是往下的,但局部會有小反彈(48 個段數裡有 3 次)。這種「大方向對、局部有雜訊」的曲線,你之後看 learning curve 會看到一輩子。
而且你想想看,如果我給你 1000 段呢?那就根本看不出差別了。好,那我們把這件事情講清楚一點:
任何一條連續的曲線,只要你肯把它切得夠細,都可以用折線逼近到你要的精度。
這句話聽起來很像廢話對不對?但它是今天整堂課的地基。為什麼呢?因為它把一個很難的問題,換成了一個簡單的問題:
這種「把難問題換成簡單問題」的招數,在整個 AI 領域會一直出現。
好,現在我們的目標變成:做出一條任意形狀的折線。
你的直覺可能是「那我就一段一段畫」。可以,但那樣不好處理。我們換個角度:把它看成一堆東西「疊」出來的。
想像你在堆積木。你先放一塊平的墊底,然後往上疊。每一塊積木長這樣:一開始是平的,中間爬一段,然後又變平。一個爬升,就是一塊積木。一個下降,就是一塊「負的」積木。
那問題就剩一個了:那塊積木,數學上要怎麼寫?
我們要的積木形狀是:左邊平的 → 中間爬一段 → 右邊平的。
最直覺的做法是拿一把剪刀硬切一個梯形出來。可以,但它有兩個尖角。尖角在數學上很討厭,因為尖角的地方不能微分,而我們等一下要用 gradient descent,沒有微分就沒得玩。
那怎麼辦呢?我們找一個長得很像、但是平滑的函式來代替它。這個函式就是 sigmoid:
名字聽起來很唬人,其實就是一個平滑版的階梯而已。它把任何數字壓到 0 跟 1 之間: 很小的時候輸出趨近 0, 很大的時候輸出趨近 1,中間平滑地爬上去。
我們把它寫成有三個旋鈕的完整版:
這三個旋鈕分別在幹嘛?這是今天你一定要記起來的東西。與其我用講的,你自己去轉:
轉完以後,這三件事你要能自己講出來:
你有沒有發現,這三個旋鈕加起來,就可以做出任何一塊積木?
我要一塊「在第 12 分鐘的地方、往下掉 50、掉得很急」的積木?那就是 開大(急)、(位置在 12)、(往下掉 50)。就結束了。沒有別的東西了。
剛剛我們說 sigmoid 是「平滑版的積木」。那有沒有「折線版」的呢?有,而且它更簡單、現在也更常用。
它叫 ReLU,全名是 Rectified Linear Unit,聽起來很厲害,其實就是一句話:負的變成 0,正的照抄。
就這樣而已。真的就這樣。
你可能會想說:那這個東西怎麼做出積木?它明明是一路往上,不會變平啊。問得好。答案是:你要用兩個。一個往上、一個往下,減一減,那個平台就出現了: 就是一塊積木。
所以 sigmoid 跟 ReLU 在做的是同一件事——都是在提供「積木」。只是 sigmoid 一個抵一個,ReLU 要兩個抵一個,但 ReLU 算起來快很多。這種用來提供積木的函式,有一個統稱,叫做 activation function(激活函數)。
今天為了講解方便,我們主要用 sigmoid,因為它一個就是一塊積木,比較好想。但等一下第六段做深度實驗的時候,我們會換成 ReLU。
好,我們現在手上有積木了。那就來疊疊看。我們的目標是那條有兩個山頭的曲線。想想看需要幾塊積木?
所以四塊應該就有個樣子了。我已經把參數手動調好了(是真的用眼睛看著圖調的,調了老半天)。你一塊一塊把它加上去,看形狀怎麼長出來。
疊的過程中有一件事你一定有注意到,而且它很重要:
加上第一塊之後,反而變得更糟。只有底墊的時候最大誤差是 100.0,加了第一塊爬升以後變成 107.8——它一路衝上去就不下來了,右半邊整個爆掉。要等到第二塊(那塊往下的)疊上去,誤差才掉到 45.0。
第三塊也一樣:單獨加上去是 49.2,比 45.0 還差。要等第四塊補上,才掉到 8.7。
這告訴你一件事:積木是「成對」工作的。一個山頭要兩塊——一塊送它上去,一塊接它下來。這件事等一下會變成一個很具體的預測:我們讓機器自己去找積木的時候,1 塊積木一定做不出山頭。等一下就驗證給你看。
剛剛我們做的事情,寫成數學是這樣:
你看著這個式子。這個式子有名字。
這就是一個「一層」的 neural network(神經網路)。就是這樣而已。
我不是在打比方,也不是在簡化。你剛剛就是在手刻神經網路。我們把裡面的東西正式命名一下:
然後那個「疊越多塊、越像」的性質,也有正式名字,叫做 universal approximation theorem(通用逼近定理)。這個定理在講什麼?其實就是我們剛剛拉滑桿看到的那件事而已:
只要積木夠多,這個式子可以逼近任何連續函數,要多像有多像。
聽起來超級厲害對不對?定理欸。但你回頭看看我們怎麼走到這裡的——我們只是說「曲線可以用折線逼近」,然後「折線可以用積木疊出來」。然後就結束了。不是什麼神奇的東西。
那你可能會想說:既然這樣,那我們是不是已經解決問題了?
還沒有喔。剛剛那四塊積木的參數,是我用眼睛看著圖,手動調出來的。兩百支影片、四塊積木,我調了老半天。那如果是一百塊積木呢?一萬塊呢?
你不可能用手調的。怎麼辦呢?
你應該已經知道答案了——讓機器自己找啊。這不就是上一講整堂課在做的事嗎?
好,我們回到三個步驟:
看到沒有?只有第一步換了。我上一講就跟你講過會這樣。
唯一的新東西是:參數變多了,微分要一層一層往回推。這個「一層一層往回推」的過程有個名字叫 backpropagation(反向傳播),名字很嚇人,其實就是連鎖律而已,高中數學就教過了。
開始之前有一個實務動作:我們要先把資料縮放一下,讓 跟 都變成大概 到 之間的數字。為什麼要這樣做?這個等一下練習 3 會讓你親身體驗,第 3 講會正式講。你先照做,就先相信這樣。
好,按下去,看它自己貼上來。左邊是 fit 的過程,右邊是 Loss 隨步數的變化。你也可以換積木數量再跑一次。
好,這就是今天的第一個高潮。
同一組資料、同一個 Loss、同一個 gradient descent。我們只換掉了 model,RMSE 就從 30.7 掉到 5.8。
而且你注意看那個 5.8。我們造這批資料的時候,加進去的雜訊標準差是 6.0,實際落在這 200 支影片上的殘差是 5.93。也就是說——天花板就在 5.9 附近,而網路已經頂到天花板了。
這代表什麼?代表剩下那 5.9 不是它學得不好,是那部分本來就是隨機的運氣,誰都學不到。它已經把資料裡面所有可以學的東西都學走了。
眼尖的同學會問:5.78 比 5.93 還低,它怎麼會贏過天花板?因為這是在訓練資料上算的分數——它多少也把一點點雜訊背了進去。這正是上一講講的那件事:training loss 低不代表厲害。真要驗收,得拿沒看過的資料考它。
現在請你回去把積木數量從 1 掃到 20,看下面那張長條圖:
為什麼加到後面沒用了?因為剩下的誤差是雜訊,那本來就學不到。你再給它一百塊積木,它也不會比 5.9 更好。這就是「不是參數越多越好」的第一個證據。等一下還有更狠的。
另外,右邊那張 learning curve 你有沒有注意到一件怪事?它不是乖乖一路往下掉的,中間有幾根往上的毛刺——最兇的一根在第 4500 步左右,Loss 一口氣彈上去好幾倍,然後才又掉下來。
那是什麼?那是 gradient descent 一步跨太大、衝過頭了。它踩到一個很陡的地方,一步跨出去反而跨到對面山壁上,Loss 就彈上去了。還好它後來自己修回來了。這次它運氣好。但它不一定每次都這麼好運。這件事我們下一講再算帳。
講到這邊你可能還是覺得有點虛。它真的是在疊積木嗎?還是有什麼黑魔法?
沒有黑魔法。把上面那個「把每一塊積木拆開來看」打勾,用 10 塊積木跑一次,你會看到機器自己找出來的那 10 塊。對照一下它們的轉折點跑到哪裡去了:
你會發現,最高的那四塊積木,全部都卡在兩座山的上坡跟下坡的位置上。剩下那六塊高度都只有它們的一半以下,它們在修谷底和山腰的形狀,互相抵銷一部分。
我們完全沒有告訴它有兩座山。我們沒有告訴它主峰在 8 分鐘,也沒有告訴它 25 分鐘有死忠觀眾。我們只是給它十塊積木,加上一個評分標準,然後硬 train 一發。它自己把那兩座山挖出來了。
這跟我們前面手動調參數,做的是一模一樣的事情。差別只在於,剛剛是我用眼睛看著圖慢慢調,現在是 gradient descent 自己調。
好,現在來回答今天標題那個問題。
我們剛剛講了 universal approximation——一層就夠了,只要積木夠多,任何函數都逼近得了。那你就會問了,而且這是一個非常好的問題:
既然一層就夠了,那為什麼要疊很多層?
為什麼這門學問叫 deep learning,不叫 fat learning?
這個問題困擾了很多人很多年。我先講答案,再解釋為什麼。答案是:一層「做得到」,但是「很沒效率」。做得到跟做得划算,是兩件事。
我用一個比喻,你就懂了。假設你手上有一張紙,你要在上面弄出很多摺痕。
差別在哪?差別在於:第二次摺的時候,你是在「已經摺過的東西」上面摺。所以效果是相乘的,不是相加的。
一層一層疊上去,每一層都是在前一層的成果上面再加工。所以複雜度是指數成長的——摺的次數加 1,山峰數就翻一倍。摺 1 次是 1 個山峰,摺 2 次 2 個,摺 3 次 4 個。摺 10 次就是 512 個。摺 20 次就是 50 萬個。
關鍵是:「摺一次」這個動作,一層網路就做得到(一個尖角 = 兩個 ReLU)。所以一個 層的網路,可以用很少的參數做出 個山峰。而寬的網路沒辦法「在成果上面再加工」,它只能一個山峰一個山峰慢慢堆。
好,那我們就來實測看看。這是今天的壓軸實驗。
實驗設計是這樣,我特別強調公平這件事:
按下去以後,八條線會同時開始跑。這要花幾秒鐘,因為它是真的在算。
好,結果出來了,我們來讀這張圖。
第一件事:深的贏,而且贏很多。參數量幾乎一樣(205 vs 208),但深的那組平均 Loss 是 0.024,寬的是 0.068——深的平均好 2.9 倍。
你看左邊那張圖更明顯。切到「寬淺」看:它根本沒學起來,四個山峰它一次都沒做齊過,最多只擠得出 3 個。切回「深窄」,最好的那兩次幾乎完全貼合,四個峰清清楚楚。
而且我要補一句:這不是參數不夠的問題。我另外跑過把寬的加到 1 → 400 → 1,那是 1201 個參數,將近六倍。結果呢?四個起點的平均 Loss 是 0.078,比只有 68 個 neuron 的時候還差,而且四個山峰一次都沒做出來。加寬救不了它。
第二件事,這件事更重要:你注意看第 2 次。
深的那組,第 2 次卡住了(Loss 0.060),卡在跟寬的差不多的位置——它只做出 2 個山峰,跟寬的一樣爛。第 4 次也只做到一半(0.026,3 個山峰)。真正漂亮的是第 1 次跟第 3 次,四個峰都做出來了,Loss 掉到 0.0001 和 0.010——最好的那次比寬的最好的那次好了五百多倍。
這代表什麼?這代表深的網路比較有威力,但也比較難 train。換個隨機起點,它就有可能卡在半路上爬不出來。
我故意跑四次、故意把四次都畫給你看,就是要讓你看到這件事。如果我只跑一次然後挑漂亮的給你看,那叫做騙人。
而「怎麼讓它不要卡住」——那就是我們下一講的主題了。
講到這邊,我想跟大家分享一下這件事在歷史上是怎麼發生的。
以前——我是說深度學習流行起來之前——大家做影像辨識是怎麼做的?
是這樣的:你要先用手設計 feature。比如說我要認貓,那我就想辦法寫程式去抓「邊緣」、抓「角點」、抓「材質」、抓「顏色分布」……一堆專家坐在那邊,花好幾年,設計出一組很精巧的特徵抽取流程。光是設計 feature 這件事,就可以發一堆論文,開一整個研討會。然後最後一步,才把這些手工 feature 丟給一個簡單的分類器。
後來發生什麼事?後來大家發現——
欸,我不要設計那些 feature 了。我直接把原始的圖片丟進去,疊很多層,硬 train 一發,居然就贏了。而且是大贏。
那些辛苦設計的手工 feature,全部被取代掉了。為什麼呢?因為那些「層」自己會長出 feature 來。就像我們剛剛看到的——我們沒教它兩座山在哪裡,它自己找出來了。你不用告訴它要看邊緣,它自己會學到要看邊緣。
這就是為什麼這件事這麼重要。深,不只是比較準,它還把「人要想清楚怎麼抽 feature」這件苦工整個省掉了。
不過我要誠實補一句:這句話不是萬用的。有很多任務是沒辦法硬 train 一發的,你硬 train 一發它就是 train 不起來,一定要有巧思才行。什麼時候可以硬幹、什麼時候不行,這個就是經驗了。
很多人聽到 deep learning,第一個反應是「那就是參數很多嘛」,然後覺得參數多就是強。但其實不是。重點不是參數的「數量」,是參數的「排法」。
你今天已經看到三個證據了:
同樣的錢,蓋成一棟三層樓,跟全部攤平蓋成一層平房,能做的事情差很多。
而且參數太多還有一個副作用,就是上一講講的——它會把考古題背起來,也就是 overfitting。到時候測試資料一來就現出原形。
這個是講笑話的,但真的有人這樣以為。deep learning 的 deep,就只是「層數比較多」而已。就是這麼字面的意思。沒有任何深奧、玄妙、高深的意思在裡面。
你想想看,如果當初有人把「寬」的那條路做起來,那今天這門學問可能就叫 fat learning 了。聽起來就沒那麼潮了對不對?
三題,選了馬上告訴你對不對。
光看是學不會的,你一定要自己改改看。
你會發現一件很煩的事:model 對了,資料對了,它還是可以 train 不起來。那到底要怎麼判斷是哪裡出問題?卡住的時候要怎麼救?NaN 是怎麼來的?這就是下一堂課要處理的東西。
今天講了三件事:
然後有兩個很重要的提醒:
最後留一個問題給你,這就是下一堂課的主題:
你現在知道要把 model 加深了。所以你興沖沖地疊了十層,按下執行,然後——loss 卡在那邊完全不動。或者跑一跑變成 NaN。或者 training loss 明明很漂亮,一拿去測試就爛掉。
這時候你要怎麼辦?你怎麼知道是哪裡出了問題?
這個我們下一堂課再跟大家講。
以上就是我今天想跟大家分享的內容。