tl;dr
- 前回「学習なしで Claude に直接操縦させたら 1 時間で 1 回しか掴めなかった」の続き。今度は真っ当に
lerobot-record→ ACT 学習 → 実機評価 をやった - が、一発では動かなかった。実機評価でアームが完全静止(0/10)
- 原因は学習データのフロントカメラに操作者本人が写り込んでいたこと。ACT が「人・手」ごと過学習し、人がいない評価シーンを「未知」と判断して固まっていた
- 人を画角から外して録り直したら 50 エピソードで成功率 60%
- ところが翌朝また数 cm 空振りするように。
replayで「ロボットは無実」を証明し、開始姿勢のズレ・制御ループ 10Hz 落ち・キューブ位置が学習分布外、と順に潰した - 教訓:模倣学習が動かない時、犯人はモデルではなくデータと運用にいる
前回(Claude に SO-101 を「学習なし」で直接操縦させたら…)は「だから模倣学習をやる」で終わりました。その続きです。結論から言うと、模倣学習は動きました。ただしその過程は「録って学習したら動いた」ではなく、実機で動かない理由を一つずつ潰すデバッグ作業でした。
まず、最終的に動いた自律実行がこれです。学習済み ACT が自分でブロックを掴んでカップに入れています。
ここに辿り着くまでの落とし穴を、同じ沼にハマる人のためにまとめてみました。
パイプライン
やることは LeRobot の標準的な流れです。
record.sh … リーダーで操縦、フォロワーの軌跡 + カメラ映像を記録 → HF Hub
train.sh … ACT を学習(GPU)→ 学習済みポリシーを HF Hub へ
eval.sh … record から teleop を抜き --policy.path を渡して自律実行
- ロボット:SO-101 双腕(今回はフォロワー側 6 軸で作業)
- カメラ:正面 C920 + 手首マウントの UVC カメラの 2 台
- タスク:紙粘土/ブロックを掴んで透明カップ(綿棒ケース)に入れる
- ポリシー:ACT(Action Chunking Transformer, ResNet18 バックボーン)
- 学習:RTX A6000、100k step で約 10.7 時間
学習 loss はきれいに下がります。ただし後で痛感しますが、この曲線がいくら下がっても実機で動くとは限りません。
第 1 の壁:実機評価でアームが完全に静止する(0/10)
50 本近くデータを録って ACT を学習。学習 loss は 0.022 まで下がった。意気揚々と eval.sh を回すと——アームがピクリとも動かない。10 エピソード全部、フォロワーは静止したまま。
まず切り分ける
学習 loss は下がっているのにポリシーが動かない。犯人は「ハード」「データ」「モデル」「推論」のどれか。順に潰します。
① ハードは動くか → Python API で直接 +10° の指令を送る。実測 +9.2°。アームは動ける。 ハードは無実。
② 学習データはちゃんと動いているか → 全エピソードの action の振れ幅を集計。shoulder_lift 平均 114°、elbow 129°、gripper も 40/43 本で開閉。教師データは本物のピック動作。 データの「中身」は無実。
③ モデルは推論できるか(オフライン) → 学習画像をポリシーに食わせて予測 action を見る。ここで最初の落とし穴:
lerobot 0.5.1 では正規化(mean/std)がモデル本体ではなく別ファイル(
policy_preprocessor_..._normalizer_processor.safetensors/postprocessor)に分離されている。ACTPolicy.from_pretrained()だけ呼んでselect_actionすると正規化が効かず出力が 0 付近に固まり、「モデルが壊れている」と誤診する。
make_pre_post_processors で pre/post を通したら、教師画像に対しては誤差 1〜3° で軌跡を再現した。モデルは正常。
犯人は「教師データの見た目」だった
ハードもデータもモデルも正常。なのに実機だけ動かない。差は「評価時に実際に写っている画」だけ。学習フレームと評価フレームを並べたら一発でした。
- 学習時:フロントカメラに操作者本人(体・手)が毎フレーム写り込んでいた(テレオペ中、カメラの前に座っていた)
- 評価時:人がいない、裸のテーブル
50 本程度の ACT はこの「人・手」をシーンの一部として過学習していて、それが消えた評価シーンを「未知」と判断し、平均姿勢=静止に潰れていたのです。テレオペ中、自分がカメラに写っていた——ただそれだけのことでした。
録り直したら 60%
対策は単純。フロントカメラの画角に操作者を入れない(カメラの真横に座る)。ついでに 2 つ改善:
- リセット時間を
→でスキップせず、10 秒使ってキューブ再配置+アームを毎回同じホーム姿勢へ戻す(開始姿勢の標準偏差が 46° → 0.2° に激減) - タスク実演は「Recording episode N」表示後だけ(リセット中は記録されない)
50 エピソード録り直して再学習。学習半分(50k step)の checkpoint で評価したら——成功率 6/10。前回 0/10 だったのが嘘のよう。データを直しただけで、モデルは同じ ACT です。
第 2 の壁:翌朝、また数 cm 空振りする
100k まで学習を回し、翌朝に完成版を評価。ところが掴む手前で数 cm ずれて空振り。しかも 50k に戻しても失敗。昨夜 60% だったモデルが今朝は連続で外す。確率的にありえない(0.4^6 ≒ 0.4%)ので、昨夜と今朝で何かが変わったはず。
順に潰します。
開始姿勢のズレ → 手でホームに戻していたが、6 軸を手で合わせるのは無理で、特に手首の向き(flex +10°, roll -15°)がズレていた。手首カメラは手首に付いているので、この向きのズレで「前方が見える」→「真下しか見えない」に激変し、位置合わせが数 cm 狂う。
→ 開始姿勢へ正確に移動するスクリプト(go_home.py) を書いて解決。トルクを保持したまま切断し、そのまま eval に入れるようにした。
制御ループが 10Hz に落ちていた → ログに Record loop is running slower (10.8 Hz)。犯人は --display_data=true の rerun ライブ表示で CPU を 176% 食っていた。ACT は 100 ステップのチャンクを開ループ実行するので、10Hz だと 1 エピソード中ほぼ 1 回しか再計画せず、初期の奥行き誤差が最後まで補正されない。
→ display_data=false(評価は実機を目で見る)で解決。
それでも数 cm 外す。ロボットが壊れた? ここで効いたのが lerobot-replay。
lerobot-replayは学習データの関節指令をそのまま再生する(ポリシーもカメラも介在しない)。同じ位置にキューブを置いて再生し、掴めれば「ロボットは正常」、外せば「機械/キャリブレーションがズレた」と切り分けられる。
replay したらきれいに掴んでカップに入れた。ロボットは無実。 つまり機械もキャリブも正常で、問題はポリシー/知覚側。
結論:キューブが学習分布の外だった
replay で「確実に掴める」と分かった位置に、今度はポリシー(eval)でキューブを置いて実行 → 掴めた。
犯人が確定しました。今朝バラバラに置いていたキューブ位置が、50 本の学習データがカバーしていない領域だったのです。ACT は「学習した一番近い軌跡」を再生するので、手薄な位置では系統的に数 cm ずれる。加えてループ 10Hz 落ちが補正を弱めていた。モデルもロボットも正常で、運用(置く位置・表示設定・開始姿勢)の問題でした。
教訓:模倣学習が動かない時のチェックリスト
今回ハマった順に、そのまま切り分け手順になります。
| 症状 | 疑うもの | 切り分け方 |
|---|---|---|
| 実機で完全静止 | 正規化が効いていない誤診 / データの見た目 | pre/post processor を通してオフライン推論。学習フレームに余計なもの(人・手)が写っていないか目視 |
| 数 cm 系統的に空振り | 開始姿勢・カメラ位置のズレ | 開始関節角をスクリプトで統一。手首カメラの見え方を学習時と比較 |
| 動きがカクつく/補正が効かない | 制御ループ速度 | ログの Hz を確認。display_data=false、重いプロセスを止める |
| ロボットが原因か知りたい | 機械/キャリブ |
lerobot-replay で教師指令を再生。掴めればロボットは無実 |
| 特定位置だけ外す | 学習データの位置カバレッジ不足 | 学習分布内の位置で成功率を測る。足りなければデータ拡張 |
一番の学びはこれです。模倣学習が動かない時、犯人はたいていモデルではなくデータと運用にいる。 loss は下がるし、オフラインでは軌跡を再現する。それでも実機では、カメラに写った自分の手ひとつ、手首の 10° のズレひとつで止まる。人間が無意識に吸収している「同じ環境・同じ構え」を、ロボットには全部お膳立てしてやる必要がある。
副産物:使えるようになった道具
-
go_home.py:フォロワーを学習開始姿勢へ正確に移動。評価の再現性が跳ね上がる -
lerobot-replayの使い所:ハードとポリシーを切り分ける最強の一手 -
HF_HUB_DISABLE_XET=1:回線によっては新転送バックエンドがハングする。旧方式で 100kB/s → 6.5MB/s に改善
次にやること
50 本は「実証」には十分でしたが、掴める範囲を広げるにはデータ量です。
- キューブ位置・向きをもっと広く振って 100〜200 エピソード収集
- 4.6V 電源だとブラウンアウトが頻発するので 7.4V 系電源を調達
- 100k 版の成功率を学習分布内で正当に測定 → データ拡張の効果を定量比較
「テレオペのデモを録るだけ」の裏に、これだけの落とし穴が畳み込まれている——というのを、また遠回りして理解しました。


