FR5 RL — 教師データ生成の内部

教師データ(エキスパート実演)はどう作られているか

これまでの学習で使った「教師データ」は、人間が手で集めたものではありません。 特権スクリプト・エキスパート(手書きの状態機械)が Isaac の中で実演し、 成功したエピソードだけ.npz に記録したものです。 そのスクリプトの形作り方を、Pick / 3-class Sort / Conveyor の3タスクについて実装事実で解説します。

ブループリント状の特権エキスパート(教師)が実演し、白い生徒ロボットが学ぶ

3行まとめ

  • 教師 = 手書きの「特権」状態機械エキスパート。 8フェーズ(接近→下降→把持→持上→搬送→設置→離す→完了)で、真の物体姿勢を見て動く。一方で記録する観測はノイズ入り(YOLO相当)——だから生徒は知覚ノイズに強くなる。
  • 3タスクは「継承」で作る。 Pick の ScriptedExpert を Sort/Conveyor が継承し、差分だけ上書き(Sort=正解ビンへ振り分け、Conveyor=動くパックを追う+オフセット補正搬送)。
  • 形式は成功フィルタ済みの .npz collect_*_demos.py が Isaac で大量並列に走らせ、成功エピソードだけを保存(obs/action/エピソード境界)。それを bc_pretrain.py が模倣クローン→train.py が BC-MSE か reverse-KL アンカーで PPO 微調整。

全体像教師データ → 生徒方策 のパイプライン

スクリプトエキスパート→大量並列シミュレーション→成功フィルタ(✓を保持、✗を破棄)→データセット→ニューラルネット生徒
左から:手書きエキスパートが実演 → Isaac で数百並列に走らせる → 成功(✓)だけ漏斗で残し、失敗(✗)は捨てる.npz データセットに蓄積 → 生徒方策(ニューラルネット)が模倣・強化学習。これがあなたの教師データ生成の全体像です。
scripted_*_expert.py  →  collect_*_demos.py  →  demos/fr5_*_demos.npz  →  bc_pretrain.py  →  checkpoints/*_bc.pt  →  train.py (BCRegPPO)  →  ppo_final.pt  →  export_onnx.py

すべて tools/isaac_rl/ 配下。3段の継承構造:1つの特権スクリプト状態機械(Pick)を Sort/Conveyor が継承し、各タスクに collect_*_demos.py が対応して .npz を書き出します。


3タスクPick / 3-class Sort / Conveyor

左:ピック(1ビン)/中央:3色仕分け(赤緑青ビン)/右:コンベア追従ピック
左=Pick(1個を1ビンへ)/中央=3-class Sort(色ごとに正解ビンへ)/右=Conveyor(動くベルト上のパックを追って把持)。同じ状態機械を土台に、差分だけを変えて3タスクを作っています。
タスクエキスパート・クラス観測次元差分(Pickからの上書き)エキスパート成功率
PickScriptedExpert29-dim基本の8フェーズ状態機械(土台)~90.7%
3-class SortScriptedSortExpert34-dim搬送先を BIN_POSITIONS[class] の正解ビンに振り分け~97.1%(正解ビン)
ConveyorScriptedConveyorExpert34-dim動くパック追従+搬送脚でオフセット補正92.2%

観測(obs)は数値ベクトル:Pick=29次元、Sort/Conveyor=34次元(位相観測 is_graspedremaining_horizon 追加)。行動(action)は全タスク共通の7次元(関節 Δq×6 + グリッパ×1)。


① スクリプト・エキスパート手書きの「特権」状態機械

教師データの源は、RLでも学習でもなく 人手でコーディングした状態機械です(scripted_expert.pyScriptedExpert)。全環境を一括処理するベクトル化トップダウン・ピック&プレース。

「特権(privileged)」とは — ここが肝

真の状態で動き、ノイズ観測を記録する

エキスパートはシミュレーションの真値(正確なパック/TCP姿勢)を見てサーボします。ところが collect 側が記録するのはノイズ入りの観測(σ=3mm/5mm のYOLO相当ノイズ)。つまり「完璧な手本の行動」と「現実的に劣化した観測」のペアを学ぶので、生徒は知覚ノイズへの頑健性を最初から身につけるscripted_expert.py:3-21)。

8フェーズの状態機械

1 APPROACH
パック上空 z=0.12 へ
2 DESCEND
把持高 z=0.0275 へ下降
3 GRASP
グリッパ閉じる
4 LIFT
z=0.25 へ持上げ
5 CARRY
ビン上空へ搬送
6 PLACE
z=0.031 へ設置
7 RELEASE
グリッパ開く
8 DONE
完了

各フェーズは「目標TCP姿勢(xy+絶対z)+グリッパ指令」を定義(_targets, scripted_expert.py:183-214)。把持はライブのパックを狙い、把持は固定した把持xy/ビンxyと絶対zを使う(掴んだパックを追いかけない)。フェーズ遷移は真の距離・把持状態でゲートします:

# scripted_expert.py:216-240 — 真値でフェーズを進める
adv |= (ph == APPROACH) & (d < 0.030)
adv |= (ph == DESCEND)  & (d < 0.020)
adv |= (ph == GRASP)    & grasped
adv |= (ph == LIFT)     & grasped & (tcp_z > 0.20)
adv |= (ph == CARRY)    & (dxy_bin < 0.025)
adv |= (ph == PLACE)    & (d < 0.020)
adv |= (ph == RELEASE)  & (~grasped) & (phase_steps > 25)

目標姿勢 → 7次元アクションへの変換

各フェーズの目標TCP姿勢は、内蔵の微分IK(減衰最小二乗 FR5DiffIK, scripted_expert.py:37-103)で関節目標に変換し、環境の相対 Δq アクションに落とします(action_scale=0.05):

# scripted_expert.py:248-254
q_target = self.ik.joint_target(tgt, self.target_quat)
dq = torch.clamp((q_target - q_cur) / self.scale, -1.0, 1.0)  # 6関節
grip = torch.where(grip_open, +1, -1)                          # グリッパ
return torch.cat([dq, grip.unsqueeze(-1)], dim=-1)            # → 7-dim

TCP は wrist3_link 原点+ツールオフセット TCP_OFFSET=(0,0,0.10)(=フランジ上 +100mm、B5由来の規約。fr5_pick_env_cfg.py:46)。エキスパートIK・EEフレーム・把持アクションが同じオフセットを共有します。

継承で3タスクを作る

Sort — 正解ビンへ振り分け

ScriptedSortExpert(ScriptedExpert) は M1 の状態機械を丸ごと継承。唯一の特化は搬送/設置先を「クラス正解ビン」に向けること。_refresh_bins がリセット毎に BIN_POSITIONS[env.puck_class]self.bin_b に書き込む(scripted_sort_expert.py:47-56)。3ビン。

Conveyor — 動くパック追従+オフセット補正

ScriptedConveyorExpert。ベルトは −Y に 100mm/s(belt_vel=(0,-0.10,0))。把持前は親クラスでライブのパックを反応追従。搬送脚でオフセット補正を効かせるのが特徴(次節)。

「オフセット補正エキスパート」= 84.8% → 92.2% を生んだレバー

パックは +y 側から 4cm の把持ゲートに滑り込むため、TCP→パックに残差オフセットが残ったまま掴まれます。そこで搬送先を「ビン中心」ではなく bin −(パック−TCP) に狙うと、オフセット分ずれて運ばれるパックがちょうどビン中心に落ちるscripted_conveyor_expert.py:72-78):

# scripted_conveyor_expert.py:72-78 — 搬送脚のオフセット補正
tcp_pos_b, _ = self.ik.tcp_pose_b()
off_xy   = puck_b[:, :2] - tcp_pos_b[:, :2]   # 把持中はほぼ一定の TCP→パック オフセット
corrected = self.bin_b[:, :2] - off_xy
tgt[:, :2] = torch.where(bin_ph, corrected, tgt[:, :2])

② 収録スクリプトIsaac で走らせて記録する

エキスパートを実際に Isaac で走らせ、ロールアウトをディスクに記録するのが collect_*_demos.py です。各タスクに1本ずつ。

タスク収録スクリプト既定コマンド出力
Pickcollect_demos.py--num_envs 128 --episodes 400demos/fr5_pick_demos.npz
Sortcollect_sort_demos.py--num_envs 128 --episodes 400demos/fr5_sort_demos.npz
Conveyorcollect_conveyor_demos.py--num_envs 128 --episodes 400demos/fr5_conveyor_demos.npz

3本とも:Isaac を AppLauncher で起動 → gym 環境生成 → エキスパート生成 → 制御ステップ毎に expert.act()env.step() を回し、(obs["policy"], action) を記録(collect_demos.py:88-98)。

成功フィルタ — 失敗エピソードは捨てる

「成功した実演だけ」を残す

エピソード終了時、成功終了(success)が立ったエピソードだけをグローバルデータセットに追加(collect_demos.py:108-119)。Sort はさらに正解ビンであること(wrong_bin でない)を要求し、各エピソードにクラスを付与。Conveyor は missed(パックがベルト外)も追跡するが、保存は success のみ。

# collect_demos.py:108-119 — 成功だけ append
if is_succ:
    n_success += 1
    g_obs.append(np.stack(ep_obs[e]))
    g_act.append(np.stack(ep_act[e]))
    ep_lengths.append(L)

エピソード数 — 既定400 →「≥85%レシピ」で 2,000–2,400 にスケール

収録スクリプトの既定は成功400エピソード。両タスクを ≥85% に届かせたレシピでは Sort/Conveyor を 2,000–2,400 エピソードに増やしました(例:collect_conveyor_demos.py --num_envs 256 --episodes 2000)。デモを増やしたことで BC クローンが大きく跳ねた(12/29% → 76/78%)のが ≥85% 突破の一因です。

タスクスケール後エピソードディスク上サイズエキスパート成功率
Pick400(基準)2.7 MB~90.7%
Sort~2,000–2,40016.4 MB~97.1%(正解ビン)
Conveyor~2,00011.8 MB92.2%

③ 保存形式.npz の中身

教師データの「形」は 圧縮 NumPy アーカイブ .npznp.savez_compressed)。保存先は tools/isaac_rl/demos/。中身は時系列のフラットな配列+エピソード境界です。

キーdtype / 形状意味
observationsfloat32 (N, obs_dim)各ステップのノイズ入り方策観測(N=総ステップ数)
actionsfloat32 (N, 7)各ステップの行動(Δq×6 + グリッパ×1)
episode_startsbool (N,)各エピソード先頭ステップで True
successbool (E,)エピソード単位の成功フラグ(全て True=成功のみ保存)
episode_lengthsint64 (E,)各エピソードのステップ数(総和=N)
class  Sortのみint64 (E,) ∈{0,1,2}そのエピソードのパッククラス

観測次元(obs_dim)

Pick = 29次元:joint_pos(6) joint_vel(6) last_action(7) gripper(1) detected_puck(3) tcp_to_puck(3) tcp_to_bin(3)。
Sort/Conveyor = 34次元:29次元に is_grasped(1)remaining_horizon(1) の位相観測を追加。Sort は tcp_to_bin→tcp_to_correct_binclass_onehot(3)、Conveyor は belt_velocity(3) を持つ。

行動次元(7-dim)と保存しないもの

行動は全タスク共通の7次元(関節 Δq×6 + グリッパ開閉×1)。
報酬(reward)と done 配列は保存されません。これらは「保持/破棄」の判定にライブで使うだけ。教師データに必要なのは obs・action・エピソード境界・成功フラグだけだからです。

# 読み込み例
data = np.load("demos/fr5_sort_demos.npz")
obs  = data["observations"]   # (N, 34) float32
act  = data["actions"]        # (N, 7)  float32
cls  = data["class"]          # (E,)    int64  ∈ {0,1,2}

④ 学習での使われ方模倣クローン → アンカー付き PPO

教師データ .npz は2段階で消費されます。まずオフラインの模倣クローンで暖機、次にPPO 微調整中のアンカーとして。

(a) BC 模倣クローン — bc_pretrain.py

Isaac を起動せず、純 PyTorch で .npz の obs→action を MSE 回帰(bc_pretrain.py:158-159)。PPO と同一構造の ActorCritic を作り、暖機チェックポイント(checkpoints/bc_init.ptsort_bc34.ptconveyor_bc34.pt)に保存。train.py--warmstart で読み込み、iter-0 の決定論的成功率が閾値未満なら中断(暖機失敗を即検知)。

(b) PPO アンカー — train.pyBCRegPPO

デモを GPU に読み込み、runner.alg.__class__BCRegPPO に差し替えてアンカー付き更新を注入(train.py:634-643)。PPO の損失に「エキスパートに寄せる」項を足す。--anchor {bc_mse, reverse_kl} で2方式を切替。

2つのアンカー方式 — 83% の天井と、それを破る reverse-KL

方式やること性質到達点
BC-MSE
(既定・実績パス)
毎ミニバッチでデモを引き、方策平均をエキスパート行動へ MSE 回帰。係数は bc_weight→bc_floor に線形減衰(非ゼロ床=崩壊防止 模倣に縛られる Isaac 約83%が天井
reverse-KL
(天井ブレーカー)
凍結した BC クローンに対する解析的 D_KL(π‖π_ref) を損失に追加。係数 β_t=max(floor, β0·(1−成功EMA)) で成功が上がるほど減衰 モード探索的=手本を超えられる ≥85%(Conv 92.6 / Sort 89.9)

「BCアンカー(83%)」の意味

純 BC-MSE アンカーは安全だが、模倣の天井(Isaac 約83%)で頭打ちになる。reverse-KL は「BCクローンから離れすぎない」緩い拘束に変えることで、~97% のデモを超えて ≥85% を達成しました(--anchor reverse_kl)。これが Session D/F の突破の核です。


⑤ コンベアの特殊事情「実演 .npz」と「再生 JSON」の二重構造

Conveyor だけは教師データが2つの別物に分かれます。混同しないことが大事です。

(i) BC学習用の実演 — demos/fr5_conveyor_demos.npz

オフセット補正エキスパートが生成した成功実演(§①②)。形式は他タスクと同じ .npzこれが学習の教師データ

(ii) Web再生用 — app/backend/rl-replay/conveyor.json

record_conveyor_replay.py が生成。これは教師データではなく学習済み ONNX 方策を Isaac で走らせた本番ロールアウトの記録(再生用)。

なぜ再生が要るのか

学習済みコンベア方策は Isaac で 92.6% だが、Web のキネマティック簡易シムでは ~0%(瞬時 Δq と Isaac の PD 慣性の差が、タイミング命の「動く把持」では致命的)。そこで Web は再シミュレーションせず、方策の実際の Isaac ロールアウトを再生します。record_conveyor_replay.py は学習済み ONNX を onnxruntime で駆動し、各ステップの 6 関節角 q・パック位置・把持ラッチ g・グリッパ開 o を記録、成功エピソード(>10フレーム)だけを JSON(control_hz:50)に保存。CLI は --num_envs 32 --episodes 8


ファイル一覧教師データ生成に関わる全スクリプト

ファイル役割
scripted_expert.pyPick の特権トップダウン状態機械エキスパート(ScriptedExpert)+ 微分IK FR5DiffIK
scripted_sort_expert.pyScriptedSortExpert:M1状態機械+3クラス正解ビン振り分け
scripted_conveyor_expert.pyScriptedConveyorExpert:動くパック追従+搬送オフセット補正
collect_demos.pyPick エキスパートを Isaac で走らせ成功フィルタ → fr5_pick_demos.npz(29-dim)
collect_sort_demos.pySort 収録 → fr5_sort_demos.npz(+エピソード class
collect_conveyor_demos.pyConveyor 収録 → fr5_conveyor_demos.npz
bc_pretrain.pyオフライン PyTorch BC:.npz → 暖機チェックポイント(bc_init.pt 等)
train.pyBC-MSE / reverse-KL アンカー付き PPO(BCRegPPO)でデモを消費
record_conveyor_replay.py学習済み ONNX を Isaac で駆動し成功ロールアウトを記録 → conveyor.json(Web再生用・教師データではない)

設定ファイル(生成器ではないが定数を定義):fr5_pick_env_cfg.py(TCP_OFFSET・29-dim obs・action_scale=0.05)、fr5_sort_env_cfg.py(BIN_POSITIONS・class_onehot)、fr5_conveyor_env_cfg.py(BELT_VEL・belt_velocity)、mdp/phase_observations.py(is_grasped・remaining_horizon)。

docstring とコードは一致しています(2026-06-28 修正済み)

以前あった2つの食い違い(収録スクリプトの「32次元」コメント/Conveyor エキスパートの「ambush」docstring)はソース側を実装に合わせて修正済みです。collect_*_demos.py / fr5_sort_env_cfg.py34次元を、scripted_conveyor_expert.pyreactive-track + offset-corrected carry(ambush・velocity-lead は試験後却下)を正しく記述します。sort_smoke_test.py== 32 アサートも 34 に修正(34次元 env で失敗していた潜在バグ)。docstring を信頼して構いません。