№ 005互動
一個身體,兩個頭:在瀏覽器裡訓練同時畫框又畫遮罩的 HydraNet
用 emoji 水果當訓練資料,在瀏覽器裡從零訓練一個共用主幹、兩個輸出頭的小網路。十秒鐘它就找得到水果。然後我們誠實地量一次:多任務學習到底幫了什麼忙,又要付出什麼代價。
- 發布
- 閱讀時間
- 9 分鐘
下面這個網路現在什麼都不會。它的工作是在一張 32×32 的小圖裡找到一顆水果,而且要用兩種方式回答:畫一個框把它圈起來,再畫一張遮罩標出哪些像素屬於它。
按「開始訓練」。大約十秒鐘後,框就會貼上去。
- ━ 模型畫的框
- ┅ 正確的框
- ■ 模型認為是水果的地方
由遮罩取外框: –
還沒開始訓練。權重是隨機的,所以框縮在中間、遮罩是雜訊。
四條邊的分佈
框的頭沒有開,所以沒有分佈可以看。
在我的機器上(Chrome、macOS、Apple 的 emoji),成績是這樣走的:
| 訓練時間 | 看過的圖 | 框 IoU | 遮罩 IoU |
|---|---|---|---|
| 0 秒 | 0 | 0.00 | 0.13 |
| 10 秒 | 4,000 | 0.80 | 0.77 |
| 40 秒 | 16,000 | 0.86 | 0.85 |
| 80 秒 | 32,000 | 0.88 | 0.89 |
你的數字會不一樣,原因很有趣,下面會講到。
資料不用錢
訓練物件偵測最貴的從來不是算力,而是標註:得有人一張一張把框拉好、把輪廓描出來。這裡完全不用,因為圖是我們自己畫的。
做法是在一張透明的畫布上畫一顆 emoji,隨機決定大小、位置、旋轉角度,再把它貼到一張有雜訊的背景上。畫完之後,畫布的 alpha 通道(透明度)就已經精確地記錄了「哪些像素是水果」。遮罩就是 alpha,框就是遮罩的外接矩形:
// 一個門檻同時決定框和遮罩,兩種標註永遠不會互相矛盾
if (alpha[y * SIZE + x] < 0.5) continue;
if (x < x0) x0 = x;
if (x > x1) x1 = x;這件事有一個副作用。每個作業系統的 emoji 是不同的圖:Apple、Google、Microsoft 各畫各的蘋果。 所以你剛才訓練的,是「你的系統的水果」的偵測器。把在 iPhone 上訓練好的權重拿到 Android 上用,成績會掉。這就是機器學習裡說的 domain shift,而你手上剛好有一個活生生的例子。
一個身體,兩個頭
畫框和畫遮罩看起來是兩件事,但它們需要的低階特徵幾乎一樣:邊緣在哪裡、顏色在哪裡變了、哪一塊和背景不同。所以合理的設計是只算一次這些特徵,再讓兩個很小的「頭」各自去讀。
| 部分 | 做什麼 | 參數 |
|---|---|---|
| 主幹 | 三層卷積,把 32×32 的圖變成 8×8 的特徵 | 3,712 |
| 頸部 | 把 8×8 的特徵放大回 16×16,和淺層的特徵接在一起 | 1,296 |
| 遮罩頭 | 一個 1×1 卷積 | 9 |
| 框頭 | 一個 1×1 卷積 | 36 |
| 合計 | 5,053 |
看最後兩列:兩個頭加起來只有 45 個參數,不到全部的 1%。網路的力氣全花在共用的那一段。這個結構叫 HydraNet,名字來自九頭蛇:一個身體,很多顆頭。Andrej Karpathy 在 2019 年介紹 Tesla 的感知系統時用的就是這個詞:同一個共用主幹上接了許多顆頭,同時處理車道線、號誌、行人等等不同的任務。理由很實際:車上的算力是固定的,共用主幹才塞得下。
頸部那一步值得多說一句。主幹為了看得廣,把解析度一路降到 8×8,但畫遮罩需要細節。所以頸部把深層特徵放大之後,會把還保有細節的淺層特徵接回來一起用。這是 U-Net 的 skip connection。
框為什麼這麼難
遮罩是「每個像素各自回答是或不是」,這對卷積網路來說很自然。框不一樣:它要輸出四個連續的數字,而卷積網路天生擅長的是「在哪一格」,不是「是多少」。
我第一版用的是最直覺的做法:把圖切成 8×8 格,讓網路選出水果中心在哪一格,再從那一格回歸到四條邊的距離。結果框的 IoU 只有 0.56,每條邊平均差 2.2 個像素。
| 框的做法 | 框 IoU | 每邊誤差 |
|---|---|---|
| 選一格,再回歸四個距離 | 0.56 | 2.2 px |
| 同上,把格子加密到 16×16 | 0.57 | 2.1 px |
| 同上,換一種 loss | 0.60 | 2.0 px |
| 不訓練框頭,直接對預測的遮罩取外框 | 0.63 | – |
| 把每條邊變成一個分佈(下一節) | 0.74 | 1.1 px |
第四列很傷人:辛苦訓練出來的框頭,輸給了「對遮罩取最小值和最大值」這種一行程式。把格子加密、換 loss 都只是小修小補。問題不在細節,而在「選一格、再從那一格猜距離」這個輸出方式本身:只要選錯格,後面的回歸全部白費,而且梯度只從被選中的那一格流回去。
上面的儀器裡有一行小字「由遮罩取外框」,就是這個基準線在你的瀏覽器裡的即時成績,可以拿來和框頭的 IoU 比。
把邊界變成分佈
有效的做法是換一個問法。不要問「左邊界是多少」,而是問「左邊界在每個位置的機率是多少」。
框頭輸出四張 16×16 的圖,一條邊一張。以左邊界為例:把那張圖沿著垂直方向取平均,壓成 16 個數字,過一次 softmax 變成機率分佈,然後算這個分佈的期望值:
是第 格的中心位置。這個做法叫 integral regression,也叫 soft-argmax。它有三個好處:
- 整條路都可微。不需要「選一格」這種不可微的動作,梯度會流到每一格。
- 答案可以落在兩格之間。兩格各佔一半機率,期望值就在中間,所以沒有量化誤差。
- 可以直接畫出來。儀器右下方的四張小圖就是這四個分佈。紫色長條是機率,青色實線是期望值,粉紅色虛線是正確答案。
const maps = t.conv2d(d, P.boxK, P.boxB, { h: HALF, w: HALF, k: 1 });
const lr = t.softmax(t.marginal(t.sliceRows(maps, 0, 2), { h: HALF, w: HALF, axis: "x" }));
const tb = t.softmax(t.marginal(t.sliceRows(maps, 2, 2), { h: HALF, w: HALF, axis: "y" }));
box = t.matmul(t.concatRows([lr, tb]), this.positions); // 期望值就是和位置向量做內積回到上面重新訓練一次,這次盯著那四張小圖。一開始四條分佈都是平的,期望值落在正中間,所以框縮成中央一小塊。接著你會看到每一條分佈長出一個峰,峰變尖,然後滑向水果的邊緣。沒有任何一行程式叫它這樣做,這是 loss 把它推過去的。
共享是有代價的
多任務學習常見的說法是「兩個任務會互相幫忙」:學畫遮罩會逼主幹學到更好的邊緣特徵,框也因此變準。這個說法很吸引人。我量了,在這個規模下沒有看到。
離線的對照實驗(同樣的架構、同樣三組種子):
| 指標 | 單獨訓練 | 和另一個頭一起訓練 |
|---|---|---|
| 遮罩 IoU | 0.825 | 0.812 |
| 框 IoU | 0.794 | 0.735 |
兩個任務都是單獨訓練比較好。一起訓練時,它們在搶同一個主幹的容量,而這個主幹只有三千多個參數。
那為什麼還要共用?因為帳要這樣算。在這個模型裡,兩個頭只有 45 個參數,幾乎所有的計算都花在主幹和頸部。所以「只有框」的網路並不比「兩個一起」的網路快,想分別得到框和遮罩,就得把主幹整個算兩次。共用主幹省了將近一半的算力,代價是各掉一點準確度。 在算力固定的裝置上,這筆交易通常划算;而且頭越多越划算,因為主幹的成本被更多顆頭分攤。
下面讓你自己量一次。三個網路從同一組隨機權重出發、看同一批圖:
三個網路從同一組隨機權重出發、看同一批圖,各訓練 6,000 張。每按一次會換一組隨機種子,多比幾次,看差距會不會反過來。
| 設定 | 框 IoU | 遮罩 IoU | 張/秒 | 訓練時間 |
|---|---|---|---|---|
| 兩個一起 | 尚未執行 | |||
| 只有框 | 尚未執行 | |||
| 只有遮罩 | 尚未執行 | |||
| 兩個單頭網路合計 | ||||
時間那一欄很穩定:兩個單頭網路合計大約是雙頭的兩倍。準確度那兩欄就不是了。我第一次跑的時候,框是一起訓練比較好(0.810 對 0.781),遮罩是單獨訓練比較好(0.807 對 0.778),和上面離線實驗的結論只對了一半。多按幾次,你會看到差距忽大忽小,有時候反過來。這本身就是重點:一次實驗不能支持「多任務有幫助」這種結論,要支持它得跑很多次。 文獻上兩個方向的結果都有。Standley 等人 2020 年的論文系統性地量過哪些視覺任務適合一起學,結論是「看任務組合而定」,有些組合互相幫忙,有些互相傷害。
它會在哪裡失敗
這一節的數字是用幾何圖形離線量的:同一個模型訓練 12,000 張之後,拿它沒看過的情境去考它。正常情況下它的成績是框 0.82、遮罩 0.75。
- 兩個物體:框和「同時包住兩個」的正確答案相比,IoU 只有 0.32。但它並不是落在兩個中間:框的中心掉進兩者之間空隙的情況只有 12%。絕大多數時候它是挑了其中一個,和那一個的 IoU 有 0.65。softmax 的分佈喜歡只留一個峰,贏家全拿。遮罩頭完全沒有這個問題,兩個物體的遮罩 IoU 是 0.74,和一個物體時幾乎一樣;拿這張遮罩取外框,反而得到 0.85。
- 前景和背景顏色接近:模型靠的主要是顏色對比。物體只比背景亮 0.3(顏色範圍是 0 到 1)時,遮罩掉到 0.56;只亮 0.15 時,遮罩剩 0.16、框剩 0.46。有趣的是框比遮罩耐得住:框只需要知道物體大概在哪,遮罩得逐格判斷。
- 沒看過的 emoji:訓練時只有十種水果。我猜它對沒看過的水果也多半找得到,因為從上一點看起來,它學到的比較像「一團和背景不同的東西」而不是「水果」。但這個我沒有量過,所以只是猜測。
- 換一個作業系統:前面說過的 domain shift。這個我也還沒有跨裝置量過。
這和真的系統差在哪
- 物體數量:真實場景有任意多個物體,框頭要換成每個位置各自預測的設計。
- loss 權重:這裡兩個 loss 直接相加。頭一多,怎麼配重就是大問題。Kendall 等人 2018 年提出讓網路自己學每個任務的不確定性,用它來決定權重。
- 誰來標註:這裡的標註是免費的。真實系統的做法常常是用又大又慢的基礎模型離線產生標註,再拿去訓練一個又小又快的多頭網路,讓它在邊緣裝置上每一幀只跑一次前向傳播。這正是我在工作上做的事。
- 規模:五千個參數對上幾百萬個。原理一樣。
參考資料
- Nibali et al., 2018, Numerical Coordinate Regression with Convolutional Neural Networks(DSNT,把座標算成分佈的期望值)
- Zhou et al., 2019, Objects as Points(CenterNet)
- Standley et al., 2020, Which Tasks Should Be Learned Together in Multi-task Learning?
- Kendall, Gal, Cipolla, 2018, Multi-Task Learning Using Uncertainty to Weigh Losses