ScriptedExpert を Sort/Conveyor が継承し、差分だけ上書き(Sort=正解ビンへ振り分け、Conveyor=動くパックを追う+オフセット補正搬送)。.npz。 collect_*_demos.py が Isaac で大量並列に走らせ、成功エピソードだけを保存(obs/action/エピソード境界)。それを bc_pretrain.py が模倣クローン→train.py が BC-MSE か reverse-KL アンカーで PPO 微調整。
.npz データセットに蓄積 → 生徒方策(ニューラルネット)が模倣・強化学習。これがあなたの教師データ生成の全体像です。すべて tools/isaac_rl/ 配下。3段の継承構造:1つの特権スクリプト状態機械(Pick)を Sort/Conveyor が継承し、各タスクに collect_*_demos.py が対応して .npz を書き出します。
| タスク | エキスパート・クラス | 観測次元 | 差分(Pickからの上書き) | エキスパート成功率 |
|---|---|---|---|---|
| Pick | ScriptedExpert | 29-dim | 基本の8フェーズ状態機械(土台) | ~90.7% |
| 3-class Sort | ScriptedSortExpert | 34-dim | 搬送先を BIN_POSITIONS[class] の正解ビンに振り分け | ~97.1%(正解ビン) |
| Conveyor | ScriptedConveyorExpert | 34-dim | 動くパック追従+搬送脚でオフセット補正 | 92.2% |
観測(obs)は数値ベクトル:Pick=29次元、Sort/Conveyor=34次元(位相観測 is_grasped+remaining_horizon 追加)。行動(action)は全タスク共通の7次元(関節 Δq×6 + グリッパ×1)。
教師データの源は、RLでも学習でもなく 人手でコーディングした状態機械です(scripted_expert.py の ScriptedExpert)。全環境を一括処理するベクトル化トップダウン・ピック&プレース。
エキスパートはシミュレーションの真値(正確なパック/TCP姿勢)を見てサーボします。ところが collect 側が記録するのはノイズ入りの観測(σ=3mm/5mm のYOLO相当ノイズ)。つまり「完璧な手本の行動」と「現実的に劣化した観測」のペアを学ぶので、生徒は知覚ノイズへの頑健性を最初から身につける(scripted_expert.py:3-21)。
各フェーズは「目標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)
各フェーズの目標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フレーム・把持アクションが同じオフセットを共有します。
ScriptedSortExpert(ScriptedExpert) は M1 の状態機械を丸ごと継承。唯一の特化は搬送/設置先を「クラス正解ビン」に向けること。_refresh_bins がリセット毎に BIN_POSITIONS[env.puck_class] を self.bin_b に書き込む(scripted_sort_expert.py:47-56)。3ビン。
ScriptedConveyorExpert。ベルトは −Y に 100mm/s(belt_vel=(0,-0.10,0))。把持前は親クラスでライブのパックを反応追従。搬送脚でオフセット補正を効かせるのが特徴(次節)。
パックは +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 で走らせ、ロールアウトをディスクに記録するのが collect_*_demos.py です。各タスクに1本ずつ。
| タスク | 収録スクリプト | 既定コマンド | 出力 |
|---|---|---|---|
| Pick | collect_demos.py | --num_envs 128 --episodes 400 | demos/fr5_pick_demos.npz |
| Sort | collect_sort_demos.py | --num_envs 128 --episodes 400 | demos/fr5_sort_demos.npz |
| Conveyor | collect_conveyor_demos.py | --num_envs 128 --episodes 400 | demos/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% に届かせたレシピでは Sort/Conveyor を 2,000–2,400 エピソードに増やしました(例:collect_conveyor_demos.py --num_envs 256 --episodes 2000)。デモを増やしたことで BC クローンが大きく跳ねた(12/29% → 76/78%)のが ≥85% 突破の一因です。
| タスク | スケール後エピソード | ディスク上サイズ | エキスパート成功率 |
|---|---|---|---|
| Pick | 400(基準) | 2.7 MB | ~90.7% |
| Sort | ~2,000–2,400 | 16.4 MB | ~97.1%(正解ビン) |
| Conveyor | ~2,000 | 11.8 MB | 92.2% |
.npz の中身教師データの「形」は 圧縮 NumPy アーカイブ .npz(np.savez_compressed)。保存先は tools/isaac_rl/demos/。中身は時系列のフラットな配列+エピソード境界です。
| キー | dtype / 形状 | 意味 |
|---|---|---|
| observations | float32 (N, obs_dim) | 各ステップのノイズ入り方策観測(N=総ステップ数) |
| actions | float32 (N, 7) | 各ステップの行動(Δq×6 + グリッパ×1) |
| episode_starts | bool (N,) | 各エピソード先頭ステップで True |
| success | bool (E,) | エピソード単位の成功フラグ(全て True=成功のみ保存) |
| episode_lengths | int64 (E,) | 各エピソードのステップ数(総和=N) |
| class Sortのみ | int64 (E,) ∈{0,1,2} | そのエピソードのパッククラス |
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_bin+class_onehot(3)、Conveyor は belt_velocity(3) を持つ。
行動は全タスク共通の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}
教師データ .npz は2段階で消費されます。まずオフラインの模倣クローンで暖機、次にPPO 微調整中のアンカーとして。
bc_pretrain.pyIsaac を起動せず、純 PyTorch で .npz の obs→action を MSE 回帰(bc_pretrain.py:158-159)。PPO と同一構造の ActorCritic を作り、暖機チェックポイント(checkpoints/bc_init.pt/sort_bc34.pt/conveyor_bc34.pt)に保存。train.py が --warmstart で読み込み、iter-0 の決定論的成功率が閾値未満なら中断(暖機失敗を即検知)。
train.py(BCRegPPO)デモを GPU に読み込み、runner.alg.__class__ を BCRegPPO に差し替えてアンカー付き更新を注入(train.py:634-643)。PPO の損失に「エキスパートに寄せる」項を足す。--anchor {bc_mse, reverse_kl} で2方式を切替。
| 方式 | やること | 性質 | 到達点 |
|---|---|---|---|
| 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-MSE アンカーは安全だが、模倣の天井(Isaac 約83%)で頭打ちになる。reverse-KL は「BCクローンから離れすぎない」緩い拘束に変えることで、~97% のデモを超えて ≥85% を達成しました(--anchor reverse_kl)。これが Session D/F の突破の核です。
Conveyor だけは教師データが2つの別物に分かれます。混同しないことが大事です。
demos/fr5_conveyor_demos.npzオフセット補正エキスパートが生成した成功実演(§①②)。形式は他タスクと同じ .npz。これが学習の教師データ。
app/backend/rl-replay/conveyor.jsonrecord_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.py | Pick の特権トップダウン状態機械エキスパート(ScriptedExpert)+ 微分IK FR5DiffIK |
| scripted_sort_expert.py | ScriptedSortExpert:M1状態機械+3クラス正解ビン振り分け |
| scripted_conveyor_expert.py | ScriptedConveyorExpert:動くパック追従+搬送オフセット補正 |
| collect_demos.py | Pick エキスパートを Isaac で走らせ成功フィルタ → fr5_pick_demos.npz(29-dim) |
| collect_sort_demos.py | Sort 収録 → fr5_sort_demos.npz(+エピソード class) |
| collect_conveyor_demos.py | Conveyor 収録 → fr5_conveyor_demos.npz |
| bc_pretrain.py | オフライン PyTorch BC:.npz → 暖機チェックポイント(bc_init.pt 等) |
| train.py | BC-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)。
以前あった2つの食い違い(収録スクリプトの「32次元」コメント/Conveyor エキスパートの「ambush」docstring)はソース側を実装に合わせて修正済みです。collect_*_demos.py / fr5_sort_env_cfg.py は34次元を、scripted_conveyor_expert.py はreactive-track + offset-corrected carry(ambush・velocity-lead は試験後却下)を正しく記述します。sort_smoke_test.py の == 32 アサートも 34 に修正(34次元 env で失敗していた潜在バグ)。docstring を信頼して構いません。