№ 006互動
把一隻機器狗搬進瀏覽器:它的走路策略只是四次矩陣乘法
用 MuJoCo 的 WebAssembly 版和 Deep Robotics 公開的走路策略,在瀏覽器裡讓 Lite3 走起來,然後動手把它弄倒:蒙住它的感覺、推它、調壞它的馬達。
- 發布
- 閱讀時間
- 7 分鐘
前幾篇文章的模型都是在你的瀏覽器裡訓練出來的。這一篇不是。 下面這隻機器狗的大腦,是 Deep Robotics 用 GPU 在模擬器裡訓練好、再公開放在 GitHub 上的。真的 Lite3 機器狗身上跑的就是這種策略。
我做的事情只有兩件:把物理模擬器搬進瀏覽器,然後把那個大腦接上去。訓練它需要幾千隻狗同時在 GPU 裡摔上幾個小時,那不是一個網頁做得到的。但是「訓練好的東西到底是什麼、它靠什麼站著」,是可以在這裡動手拆開來看的。
模擬器加上機器狗的模型大約 4.5 MB,所以它不會自己載入,要你按下按鈕。
物理引擎、機器狗的模型和它的走路策略。按了才會下載,全篇的儀器共用這一份。
拖曳,或先點一下再用方向鍵/WASD。放開就停。
┄ 你要的─ 實際的
按下去之後,你會看到它以每秒半公尺左右的速度往前走。把搖桿推到底,它會以每秒 2 公尺跑起來;往下拉會倒退,勾選橫移還能像螃蟹一樣走。後面的儀器可以推它:150 牛頓,它踉蹌一下站穩;300 牛頓,它會四腳朝天。
沒有任何一行程式碼在處理「被推了要怎麼辦」。
大腦只有四次矩陣乘法
公開的策略檔是一個 758 KB 的 ONNX 檔。打開來看,裡面沒有任何特別的東西:
| 這隻狗的大腦 | |
|---|---|
| 輸入 | 45 個數字 |
| 隱藏層 | 512 → 256 → 128,啟動函數是 ELU |
| 輸出 | 12 個數字,每個關節一個 |
| 參數總數 | 189,324 |
| 記憶 | 沒有。每一次都是從頭算 |
它比上一篇的 Transformer 大十幾倍,但結構簡單得多:四層全連接層,前三層後面各接一個 ELU。所以這裡不需要任何推論函式庫,CNN 那篇寫的 dense 再加上一個新的 elu,就是全部:
act(obs: Float32Array): Float32Array {
let x = tensor(obs, [1, 45]);
this.layers.forEach(({ w, b }, i) => {
x = dense(x, w, b);
if (i + 1 < this.layers.length) x = elu(x);
});
return x.data;
}ELU 和 ReLU 只差在負的那一半:ReLU 直接歸零,ELU 是一條平滑地趨近 −1 的曲線,。
我拿原本的 ONNX 檔算出來的動作當標準答案,和這段程式的輸出比對,差距在小數第七位。
這個網路每秒被呼叫 83 次,每次不到 0.2 毫秒。真正花時間的是物理。
它感覺得到什麼
那 45 個輸入是這隻狗對世界的全部認識。沒有相機,沒有地圖,它甚至不知道自己在哪裡、走了多快。
| 幾個數字 | 是什麼 | 真機上從哪裡來 |
|---|---|---|
| 3 | 身體轉動的角速度 | 陀螺儀 |
| 3 | 重力的方向(從身體的角度看,哪邊是下面) | IMU |
| 3 | 你下的指令:往前、往側邊、轉彎各要多快 | 搖桿 |
| 12 | 每個關節現在的角度 | 馬達編碼器 |
| 12 | 每個關節轉動的速度 | 馬達編碼器 |
| 12 | 它自己上一次輸出的動作 | 記下來的 |
既然只有這六種感覺,最直接的問題就是:少了哪一種它會倒?下面每一組長條就是那一種感覺現在的讀數,按「蒙住」會把它歸零。
物理引擎、機器狗的模型和它的走路策略。按了才會下載,全篇的儀器共用這一份。
45 個輸入
12 個輸出
我量到的結果和我原本猜的很不一樣:
| 蒙住 | 結果 |
|---|---|
| 陀螺儀 | 幾乎沒差,照走 |
| 重力方向 | 撐 0.8 秒後倒下 |
| 關節角度 | 0.28 秒就倒 |
| 關節速度 | 不會倒,但是暴衝:要它走 0.8,它跑到 2.5 公尺每秒 |
| 上一次的動作 | 站得好好的,但一步都不走 |
最後兩個最有意思。關節速度是它用來煞車的:感覺不到腿在動,它就以為自己還沒出力,於是越踩越用力。而「上一次的動作」是這個沒有記憶的網路唯一的節拍器。走路是一個週期性的動作,網路要知道現在輪到哪隻腳,靠的就是看自己上一拍做了什麼。把它拿掉,每一拍看起來都像第一拍,它就永遠停在起步的姿勢。
它輸出的不是力
12 個輸出也不是「每個馬達出多少力」。它們是目標角度,而且是相對於站姿的偏移:
縮放是髖關節側擺 0.125、其他關節 0.25 弧度,所以網路輸出 1,關節大約移動 14 度。真正算出力矩的是一個每毫秒跑一次的 PD 控制器:
是彈簧有多硬, 是阻尼有多黏。力矩最多 30 牛頓公尺,這是馬達的極限。
這樣分工的原因是時間尺度。網路一秒想 83 次,馬達一秒要被指揮 1000 次。中間那 11 毫秒,是彈簧在替它撐著。
代價是:網路是在 、 的身體裡長大的,它學到的每一個動作都默默假設了這副彈簧。換掉彈簧,它不會知道:
物理引擎、機器狗的模型和它的走路策略。按了才會下載,全篇的儀器共用這一份。
左前膝的角度(最近 2 秒)
┄ 網路要的─ 實際的
左前膝的力矩(上限 ±30 N·m)
把 拉到 10,實線就追不上虛線了,它會慢慢趴下去。拉到 100,實線幾乎貼著虛線,它也走得比你要求的快。我量到的速度:
| 指令 0.5 時的實際速度 | ||
|---|---|---|
| 10 | 1 | 0.01,腿太軟,趴在地上 |
| 20 | 1 | 0.24 |
| 30 | 1 | 0.47 |
| 60 | 1 | 0.66 |
| 100 | 1 | 0.80 |
| 30 | 0.2 | 0.62 |
| 30 | 3 | 0.31 |
彈簧變硬,同樣的目標角度會被更用力地執行,步伐變大,它就走得比你要求的快。它沒有壞掉,只是不準了。真的機器人上,每一顆馬達的實際增益都和規格書差一點,這就是其中一種「模擬裡好好的,上真機就怪怪的」。
推它
開頭說過,沒有任何一行程式碼在處理被推的情況。所以直接推推看,每一下都是從側面推十分之一秒。
物理引擎、機器狗的模型和它的走路策略。按了才會下載,全篇的儀器共用這一份。
機身傾斜角度,0 到 45°,最近 6 秒
紀錄
還沒推過。同樣的力道多推幾次,結果不一定一樣:要看你推在它步伐的哪一個瞬間。
我把力道從 100 牛頓掃到 400,每個力道在步伐的 8 個不同時間點、從兩邊各推一次:
| 力道 | 往裡推,8 次裡倒幾次 | 往外推 |
|---|---|---|
| 175 N 以下 | 0 | 0 |
| 200 N | 1 | 0 |
| 225 N | 4 | 0 |
| 250 N | 7 | 4 |
| 275 N 以上 | 8 | 8 |
中間那一段沒有一個乾淨的門檻。同樣 225 牛頓、同一個方向,我只是把推的時間點挪 60 毫秒,結果就從撐住變成倒地。兩邊也不對稱。在儀器裡用同樣的力道多推幾次,你會推出不一樣的結果。175 牛頓那一次,機身最多只歪了 6 度就回正了。
真實世界沒有這麼乾淨
模擬器裡,感測器沒有雜訊、訊號沒有延遲、地板摩擦係數剛好是 1。真機上三件事都不成立,所以我把它們一個一個弄髒。
物理引擎、機器狗的模型和它的走路策略。按了才會下載,全篇的儀器共用這一份。
前進速度:你要的和實際的,最近 8 秒
延遲。 讓網路看到的永遠是幾拍之前的世界:
| 延遲 | 實際速度(指令 0.5) |
|---|---|
| 0 ms | 0.47 |
| 24 ms | 0.42 |
| 48 ms | 0.34 |
| 72 ms | 0.26 |
到 72 毫秒都沒有倒,但越走越慢。
雜訊。 在感測器讀數上加隨機誤差:±0.2 以下看不出差別;±0.3 還站得住,但速度掉到 0.2 到 0.35;±0.4 是邊界,我用 10 組不同的亂數各走 10 秒,4 次倒了,剩下 6 次只剩每秒 0.1 到 0.25 公尺;±0.5 十次全倒;±0.8 不到半秒就倒。
摩擦。 這是我最意外的一個。把地板從橡膠(μ = 1)換成比冰還滑(μ = 0.05),速度只從 0.47 掉到 0.44。腳確實在滑,著地的腳平均滑動速度從每秒 0.12 公尺升到 0.27,但它照走。要到 μ = 0.01 才明顯走不動。
這三個結果放在一起,其實在說同一件事。這種策略訓練的時候,模擬器會故意亂調摩擦、亂推機器人、在感測器上加雜訊,這叫 domain randomization,目的就是讓它不要太相信模擬器。你在這裡弄不倒它的那些方式,多半是有人已經在訓練時替你試過了;你弄得倒它的方式(蒙住關節角度、把彈簧換軟),則是訓練時沒人想過要防的。
這裡沒有的東西
這篇文章只有「執行」,沒有「學習」。那個 758 KB 的檔案是怎麼來的,這裡一個字都還沒說:
- 獎勵函數。 沒有人教它怎麼走。訓練時只有一個分數:速度跟上指令加分,摔倒、抖動、耗電、腳拖地扣分。走路的樣子是這個分數逼出來的。
- 幾千隻狗。 訓練用的是 PPO,在 GPU 上同時模擬幾千隻狗,每一隻都在不同的地形和不同的摩擦係數上摔。
- 只有平地。 官方的部署程式裡有樓梯地形,這裡沒有放。
要在瀏覽器裡重現訓練,需要的算力差了好幾個數量級。那是另一篇文章,而且需要一張 GPU。
授權與來源
- 走路策略:DeepRoboticsLab/Lite3_rl_deploy,BSD-3-Clause。
- 機器人模型與網格:DeepRoboticsLab/deep_robotics_model,BSD-3-Clause。碰撞網格換成了凸包,外觀網格減了面。
- 物理引擎:MuJoCo 的官方 WebAssembly 版,Apache-2.0。