Day 21,我們終於把長時間訓練告一段落,留下了最後要用的 Dueling Double DQN。
前面二十多天一直在想一件事:怎麼讓 Agent 學會玩 Breakout?
從今天開始,問題要換了。
接下來我要做的是一個網頁:左邊讓人自己玩 Breakout,右邊則讓訓練好的 RL Agent 同時玩。也就是說,這個模型不能永遠只待在 Python 和 PyTorch 裡,它最後得走進瀏覽器。
但這時就會遇到一個很實際的問題:
PyTorch 訓練出來的模型,要怎麼交給瀏覽器使用?
這就是 Day 22 的主題。
今天不做網頁,也不做 WebGPU。我先處理中間最重要的一步:把 PyTorch 模型匯出成 ONNX。
.pt?前面訓練模型時,我們一直使用 PyTorch,所以模型自然也存成 PyTorch 熟悉的格式,例如 .pt。
這在 Python 裡完全沒問題,但到了別的環境就不一定了。
可以先把它想成:
| 格式 | 比較像什麼 | 適合在哪裡用 |
|---|---|---|
PyTorch .pt |
PyTorch 自己最熟悉的模型存檔 | Python / PyTorch |
ONNX .onnx |
比較通用的神經網路描述格式 | ONNX Runtime、瀏覽器等其他環境 |
ONNX 全名是 Open Neural Network Exchange。
它不是一個新的 AI 模型,也不會讓模型突然變強。它做的事情比較像是:把原本只在 PyTorch 裡很好使用的神經網路,整理成其他執行環境也比較容易讀懂的格式。
所以今天不是重新訓練模型,也不是換演算法。
權重沒有重學,Dueling Double DQN 也沒有變成別的東西。只是把同一個已經訓練好的模型,換成比較適合接下來部署的格式。
這件事比副檔名本身更重要。
我們的 Agent 並不是直接把一張原始 Breakout 彩色畫面丟進神經網路。
前面的環境會先把畫面整理過:
84 × 84;所以模型真正看到的,其實是最近 4 張處理過的畫面。
程式裡會把它表示成:
4 張畫面 × 84 × 84
接著再把原本 0~255 的像素值縮到 0~1,才送進神經網路。
為什麼這件事要特別講?
因為如果 PyTorch 版本把畫面縮到 0~1,但未來瀏覽器版本卻直接把 0~255 丟進去,模型雖然一樣可以「跑」,結果卻可能完全不同。
同樣地,如果四張畫面的排列方式搞錯,也可能不會立刻報錯,但 Agent 的判斷就會歪掉。
所以部署模型時,不能只搬權重,連模型原本是怎麼吃資料的,也要一起保持一致。
這張圖可以從左往右看。
前兩步是在整理畫面,第三步才是真正的神經網路。
模型拿到最近 4 張畫面之後,會輸出 4 個數字。這 4 個數字就是前面一直提過的 Q-value:可以先把它理解成「模型覺得每個動作現在有多值得做」。
四個動作分別是:
例如某一瞬間模型算出:
不動 1.2
發球 0.4
往右 3.8
往左 2.1
那它就會選 3.8 最高的「往右」。
這也解釋了為什麼最後匯出的 ONNX 模型,不需要自己處理整個 Atari 遊戲。
ONNX 的工作很單純:
收到整理好的畫面 → 算出四個 Q-value。
遊戲怎麼跑、畫面怎麼整理、最後怎麼把動作送回遊戲,仍然由外面的程式負責。
這樣反而比較好理解,也比較好除錯。
概念講完,真正匯出時做的事情其實不複雜。
我先載入 Day 21 留下來的最後模型,把它切到推論模式,然後準備一筆符合模型輸入格式的資料,交給 PyTorch 的 ONNX exporter。
大致上就是:
Day 21 的 PyTorch 模型
↓
告訴 exporter 模型的輸入長什麼樣
↓
匯出
↓
model.onnx
最後得到的 ONNX 模型,輸入仍然是 4 張 84 × 84 的畫面,輸出仍然是 4 個 Q-value。
換句話說:
格式換了,但模型做的事情沒有換。
這才是我們要的結果。
.onnx 檔案,就代表成功了嗎?還不能這樣說。
如果 exporter 沒有報錯,只能證明「檔案有寫出來」。檔案存在,不代表之後真的能正確使用。
所以匯出後,我至少先做兩層檢查。
第一層是用 ONNX 自己的 checker 檢查模型檔案,確認模型結構是合法的,輸入和輸出也符合預期。
這可以幫我們抓到像是模型檔損壞、輸入輸出定義有問題之類的錯誤。
但它有一個很重要的限制:
它只能告訴我們「這個 ONNX 檔案看起來是合法的」,不能告訴我們「ONNX 算出來一定和 PyTorch 一模一樣」。
所以我還另外保留了一批固定的 Breakout 遊戲狀態,並先記下原本 PyTorch 模型對這些狀態算出的 Q-value 和動作。
這些資料今天先留著,明天會派上用場。
因為今天其實只完成了:
PyTorch 模型
↓
ONNX 模型
我們還沒有真的拿 ONNX Runtime 去執行它。
這兩件事差很多。
ONNX 是模型檔案的格式;ONNX Runtime 才是真正負責把這個模型跑起來的程式。
所以目前我們知道的是:模型已經成功匯出,而且檔案結構沒有明顯問題。
但我們還不知道:
同一張 Breakout 畫面丟進 PyTorch 和 ONNX Runtime,兩邊最後會不會算出一樣的 Q-value、選到一樣的動作?
這正是下一篇 Day 23 要驗證的事情。
今天真正重要的成果,不只是多了一個 model.onnx。
更重要的是,我開始把模型從「只能在訓練程式裡使用的東西」,變成「準備交給其他環境使用的東西」。
到這裡可以確認三件事:
而今天刻意還沒有做的,是瀏覽器、WebGPU、Cloudflare 或 Human vs RL 畫面。
這些都還太早。
在把模型真正放進網頁之前,我想先回答一個更基本的問題:
換了執行模型的方式之後,它還是不是原本那個 Agent?
Day 23,就來驗證這件事。