0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIにロケットを着陸させたら、手作りの制御に勝てなかった話 —— そして「勝った」と思った自分を、自分で反証するまで

0
Last updated at Posted at 2026-07-13

AIにロケットを着陸させたら、手作りの制御に勝てなかった話 —— そして「勝った」と思った自分を、自分で反証するまで

対象: 強化学習・進化計算・制御工学に興味がある人 / 「学習は手作りに勝つ」を検証したい人。
前提知識: Python が読めれば十分。制御用語は都度かみくだきます。
数値はすべてローカル実測。robust NULL(学習は公平な PID に勝てず引き分け)を、自分が一度はまった罠ごと正直に書きます。

PD 誘導での着陸: 打上げ→ピッチオーバー→弧を描いて降り、パッドに垂直着陸(手作り制御のベースライン)

終端着陸のクローズアップ。脚が接地した瞬間にエンジンをカットする


0. 3行まとめ(先に結論)

  • MuJoCo でジンバル推力のロケット(booster)を垂直着陸させ、「手で設計した制御(PID)」vs「進化で学習した方策」を正直に比較した。
  • 素朴にやると学習が勝つように見える。しかし**評価を厳密にし(毎世代再サンプル+ソフト目的+paired・10 seed・ブートストラップ信頼区間で測り直す)、審判 PID も公平に再調整すると、学習は勝てず「引き分け」**だった。(後で分かるが、効いたのは主に"統計の厳密さ"で、審判の再調整は衛生作業だった。)
  • いちばんの学びは技術ではなく態度: 「学習が制御に勝った」ときは、まず"審判が弱い"か"統計が薄い"を疑え。この記事は、私自身がその罠に一度はまり、自分で自分を反証するまでを含む。

1. 用語(先に地図)

用語 かみくだき
終端着陸(terminal descent) ロケットが地面に着く「最後の数秒」だけを扱う。軌道力学は扱わない
ジンバル推力(thrust vectoring) エンジンノズルを傾けて推力の向きを変える=倒立振子を立て直す舵
PD / PID 制御 誤差に比例(P)・微分(D)・積分(I)して打ち消す、制御工学の定番。I(積分)は「ずっと同じ方向の誤差」を溜めて消す記憶
残差方策(residual policy) 「PD の出力 + 小さなニューラルネットの補正」。ネットがゼロ初期化ならそのまま PD(進化のウォームスタート)
過学習(overfitting) 評価に使った少数のシナリオだけに強くなり、新しいシナリオで崩れる
held-out / TEST 学習に一切使っていない「新しい問題セット」での成績。汎化の唯一の証拠
fitness(適応度) 着陸の良さを1つの数値にしたスコア(遅く・まっすぐ・パッド上ほど高い)。進化が最大化する量
CI(信頼区間, confidence interval) 「本当の平均はこの範囲にありそう」の幅。下限が 0 を超えて初めて"有意に正"
事前登録(pre-registration) 結果を見るに「何をもって成功とするか」を凍結する。後付けの言い訳を封じる

2. 舞台: 排気の上に立つ倒立振子

onocollo.rocket(CPU で回る MuJoCo トイ)で、フリージョイントの機体・4本の着陸脚・2軸ヒンジのジンバルノズルを持つ booster を作る。制御は3つ:

idx アクチュエータ 効果
0 thrust 落下を止める推力(0〜TWR×重量、既定 TWR 2.4)
1 gimbal_x y 平面へ推力を振る=x 軸まわりの姿勢
2 gimbal_y x 平面へ推力を振る=y 軸まわりの姿勢

(TWR = thrust-to-weight ratio, 推力重量比=最大推力÷重量。1 を超えないと落下を止められない。)

ポイントはこの機体は放っておくと必ず倒れること(排気の上に立つ倒立振子)。だから「着陸させること自体が制御問題」になる。脚はノズルより下まで届き、接地した瞬間にエンジンをカットする(実機と同じ。噴きっぱなしだと着地後に倒される)。

デバッグ小話: ジンバルの符号を物理で較正する

制御を書くとき地味にハマったのがジンバルの符号。2軸は「効き方の符号が逆」で、gx = -k·(誤差) だが gy = +k·(誤差)。机上で悩まず、機体に実際に小さな舵を打って d(傾き)/d(ジンバル) の符号を実測して確定した。制御は「式を眺める」より「1回動かして符号を見る」ほうが速い。


3. ベースライン: よくチューニングした PD 誘導

強い"手作り"の基準として、カスケード PD 誘導を置く:

  • : 降下速度プロファイル v_target = -(v_land + v_slope·高度) を追従。地面付近で v_land まで細くなる=ソフトタッチダウン。
  • 横/姿勢(カスケード): 横ずれ・横流れがあると「パッド方向へ少し傾け」を指令し(機体は傾いて初めて横移動する)、内側の姿勢ループがジンバルでその傾きを追う→真上で立て直す。

実測(32ランダム着陸、グリッド調整済み、正直): 着陸率 1.00 / ソフト率 ~0.85 / 接地 ~0.68 m/s / 接地時の傾き ~7°。全ランダム降下が着陸し、~85% が「遅く・まっすぐ・パッド上」のソフト着陸。手作り、かなり強い。


4. 学習を足す: 残差方策と、正直な基準

ctrl = pd_control(obs) + scale · MLP(obs)。MLP(多層パーセプトロン=小さなニューラルネット)はゼロ初期化=最初は PD そのもの。(μ+λ) 進化(親 μ 個から子 λ 個を変異で作り、良い方を残す進化アルゴリズム)で PD の上の改善を探す。全候補と PD を同じランダム初期条件で採点するので、best > baseline は「学習が手作りを超えたか」の公平なテスト——のはずだった。

  • 無風(nominal): 進化残差が PD をわずかに上回る(76.60 → 77.78、+1.5%)。PD が既に強いので差は小さい。学習は「良い手作りの上にちょっと足す」だけで、ダメな制御を救うわけではない。

5. 風を入れると null が出る(正直な行き詰まり)

hard_config() は持続横風(≈0.13×重量、毎エピソード向き・強さランダム)を足す。ここが面白い: メモリレスな P(+D) 則は、一定の横力を"一定の傾き"でしか相殺できず、消せない定常オフセットが残る(積分の記憶が無いから)。

  • 風下でメモリレス残差を小さい eval(10エピソード)で進化→訓練 +26%(52.8→66.4)。でも held-out では PD に負ける(20.5 vs 27.2)。ゲインは有限の評価エピソードへの過学習だった。
  • eval を24に増やす→訓練ゲインは縮み(+13%)、held-out は PD と引き分け。転移しない。

正直な結論(第1幕): メモリレス残差は、持続風の下で PD を超えない。理由は明快で状態(記憶)が無いから。→ 次は「状態を持つ制御」だ。

6. 状態を足す: PID(積分)は"少しだけ"効く

仮説「状態が足りない」は検証可能なので検証した。PIDController は横ずれの積分を持ち(アンチワインドアップ付き)、持続風を時間をかけて相殺する。

実測(手調整 k_i≈0.005: 小さな積分項が PD を控えめだが一貫して上回る(ソフト率 0.09→0.16 など、決して悪化しない)。ただし劇的ではない——終端降下は ~3秒しかなく、積分が育ちきる前に着地する。大きすぎる k_i はワインドアップで悪化。

→ アーク前半のオチ: 手作りの状態(積分)が、状態の欠けたメモリレス学習に勝つ。

7. 学習に状態を持たせる: recurrent 方策——また過学習

RecurrentPolicy(RNN=recurrent neural network, 再帰型ニューラルネット。隠れ状態で「記憶」を持てる。その隠れ状態を PD の上の残差にする、ゼロ初期化でウォームスタート)に、PID と同じ「記憶」を与え、進化に好きに使わせる。基準は手調整 PID

  • 訓練: PID 53.8 → recurrent 65.1+21%——大きく見える勝ち。
  • held-out: 転移しない。1勝1敗1分、しかも全 held-out でソフト率が PID より低い。進化はスカラー fitness をソフト着陸を犠牲にして稼いだ(目的関数ハッキング)。

正直な結論(第2幕): held-out では手調整 PID が学習 recurrent に勝つ。+21% はまた過学習+目的ハッキング。次の伸びしろは policy の容量ではなく、"評価"だ。

森田(天パ・丸眼鏡)がマドラーをくわえた真顔のドアップで「初めは大人しく成り行きを見守っていたんやが…」「余りにも隙だらけや…」「そう思ってからは早かった」と独白する

🗒️ 「初めは大人しく成り行きを見守っていたんやが…余りにも隙だらけや…そう思ってからは早かった」— 進化アルゴリズム側から見た、私の目的関数の話。ソフト着陸を削ればスコアが伸びる隙を残したのは私で、+21% はその隙を突かれた記録だった(© Forbidden shibukawa / SHUEISHA・『スナックバス江』)


8. 【本題】"評価こそがレバー" を事前登録して検証する

ここからが今回の新しい仕事。null が「評価が伸びしろ」と指したので、それを事前登録して決着させた。設計は結果を見る前に凍結(feedback_benchmark_honest_disclosure)。

8.1 手法を敵対的に事前登録する(複数AIオーケストレーション)

設計自体を6エージェントの Workflowで敵対的に固めた: 4つのレンズ(過学習 / 目的設計 / baseline 公平性 / honest-disclosure)で並列に設計 → 統合 → 敵対批評。批評家は私の初期実装の欠陥を的確に突いた——「validation 選抜が gaming された目的で行われ循環」「選んだ集合で判定する winner's curse(=多数の候補から最良を選ぶと、その"選び"が運を拾って過大評価になる)」「soft ボーナスが二値の崖」「seed 分割の不備」。全て修正してから走らせた。

8.2 eval-signal スタック(すべて既定 OFF=過去の null を壊さない)

  1. 世代ごと再サンプル eval: 固定セットの記憶を封じる。
  2. 連続 min-margin ソフト目的: 3つのソフト余裕の最小に比例する滑らかなボーナス。二値 int(soft) の崖にしない(崖はギリギリのソフトを量産して汎化で崩れる)。
  3. validation ブロック選抜: 動く eval で「最高訓練 fitness」を選ぶと"運の良い引き"に過学習するので、別ブロックで、しかも**元の目的(w_soft=0)**で選ぶ。
  4. 公平に再チューニングした PID: 学習と同じ目的・風・予算で PID を再調整。
  5. リークしない paired 評価: 100万ブロックで seed を分割(TRAIN 1M / VAL 2M / TEST 3M / TUNE 4M)+実行時 assert_no_seed_leakage、共通乱数の paired 差分+ブートストラップ CI。

8.3 事前登録した主判定

recurrent をフルスタックで進化させ、再調整 PID と比較。CONFIRM(評価改善で学習が公平な PID に勝つ)は、TEST(3ストリーム×100エピソード、paired)で全ストリームの fitness CI 下限>0 かつ soft CI 下限≥0、かつ 10 seed 中 8 以上が個別に正——のときだけ。fitness は勝つが soft は負ける、は自動で目的ハッキング nullに分類。


9. 結果: robust NULL——公平な PID には勝てない

条件 方策 / 評価 訓練ゲイン held-out fit差 過学習gap held-out判定
P(主) recurrent, 再サンプル+soft-margin+val選抜 +3.7 −0.1 +3.8 引き分け(0/10正)
C_evaldesign recurrent, 再サンプル+スカラー目的 +9.4 −4.2 +13.6 負け
C_sham recurrent, 固定eval+soft-margin +8.7 −0.0 +8.8 引き分け/負け
C_feedforward メモリレスMLP, 再サンプル+soft-margin +6.7 −1.9 +8.6 負け/引き分け
R_nominal recurrent, 無風(sanity) +0.5 −0.1 +0.6 引き分け

主判定 P は全ゲート不通過: 0/10 seed が正、TEST 3ストリームとも fitness CI が 0 をまたぐ(+0.1, -0.1, +0.5引き分け、負けではない)。フルスタックは学習を引き分けまで押し上げるが、それ以上は無い。

正直な但し書き: 唯一、TEST の soft で s2 が +0.035[+0.015,+0.055] と CI 下限が 0 を超える正の信号を出した。だが別ストリームで相殺され、その stream の fitness CI は 0 をまたぐので勝ちにはならない。null を"きれい"に見せないために、この 1 本の信号も隠さず載せる。

9.1 正直な分解——何が本当に効いたか

  1. 見かけの勝ちは厳密統計で消えた: 過去の「recurrent が勝つ」は "3 seed で fit 3/3、CI 無し、k_i=0.005 相手"(§7 の 1勝1敗1分とは別の、本セッションで固定 eval・別 seed で再現した run。少数 seed だと"見え方"がこれだけ振れる、という例でもある)。paired・10 seed・ブートストラップ CI で再測ると 0/10・pooled fit 平均マイナス。小 N・CI 無し・seed 依存ノイズだった。
  2. eval 改善は本物だが引き分け止まり: soft-margin 目的は目的ハッキングを止め(P は引き分け、スカラーの C_evaldesign は −4.2 で負け)、再サンプルは過学習 gap を半減(P +3.8 vs 固定 C_sham +8.8)。負け→引き分けまで押すが、勝ちは作らない。
  3. 訓練ゲインは転移しない: どの条件も訓練 +3.7〜+9.4 が held-out で ≈0 に潰れる。

10. 【climax】"審判が弱かった" と思った自分を、自分で反証する

ここが今回いちばん正直な部分。私は途中でこう書きかけた——「元の PID(k_i=0.005)が under-tuned だった。公平に再調整(k_i=0.012)したら見かけの勝ちが消えたのだ」と。きれいな話だ。だがそれは間違いだった

実験後に「再調整 PID は本当に強い bar なのか?」を確かめる別 check を走らせたら:

TEST lanes 上の PID: k_i=0.005 → fit 49.16 / soft 0.181
                     k_i=0.012 → fit 49.10 / soft 0.167

TEST では k_i=0.005 ≈ 0.012(soft はむしろ 0.005 が僅かに上)。つまり「元の bar は under-tuned だった」という私の物語は、TEST 上では成り立たない(その主張は別の seed セットでの話だった)。

真因はもっとシンプルで、もっと正直だ: 見かけの勝ちは"弱い審判"ではなく"薄い統計"(小 N・CI 無し・seed 依存)のノイズだった。再調整はやるべき衛生作業だが、支配的な要因ではなかった。

さらに 4体の敵対的 skeptic(baseline 公平性 / リーク / 目的ハッキング / 検出力)で null 自体を検証した。全員、null を反証できず caveat 止まり:

  • リークは"偽 CONFIRM"しか作れず、null は説明できない。
  • 判定時の soft は w_soft=0 で凍結=目的汚染なし。
  • 「検出力不足で本当の効果を null と誤認では?」→ もしそうなら 6〜8/10 seed が正になるはず。実際は 0/10+平均マイナス=誤認の兆候と逆。

正直な限界も開示する: PID の再調整は {k_i,k_pos,k_vel,lean_max} のみで i_max/kv_z/v_land は固定。ただし固定軸を追加 sweep しても TEST で reported bar を上回る PID は見つからなかった(bar は近似最適)。そして fitness チャネルは検出力不足(効果量 d<0.03)=これは「意味ある勝ちは無い」であって「効果ゼロの証明」ではない。

明美(金髪)が集中線を背に目と口を大きく見開き「嘘は良くないわよ…!」と叫ぶドアップ。隅に小さく「声デカ」の注記

🗒️ 「嘘は良くないわよ…!」「声デカ」— honest disclosure を他人ではなく自分の中間結論に向けると、だいたいこの音量になる(© Forbidden shibukawa / SHUEISHA・『スナックバス江』)


11. 教訓(gift-to-reader)

  • 「学習が手作りに勝った」ときの最初の容疑者は、"弱い baseline" と "薄い統計"。 派手な手法や policy 容量ではない。
  • 評価設計 > policy 容量。 今回、伸びしろは RNN を大きくすることではなく、評価を厳密にすることにあった——それでも公平な PID は超えなかった。
  • 自分の中間結論も疑う。 私は一度「under-tuned baseline が原因」と書き、自分の追加 check で反証した。honest-disclosure は他人だけでなく自分にも向ける。
  • null は一級の成果。 「勝てなかった」を正直に、内訳つきで出すことが、次の実験の正しい出発点になる。

11.5. 続編(実験中): 「突風」なら反応性が学習に味方するか

引き分けの実験は持続する(一定の)横風だった。ここは"記憶"=積分(PID の I)が効く領域で、だから手作りが強かった。では時変の突風(gust)——向きも強さもコロコロ変わる風——ならどうか?

理屈はこう: PID の積分は「一定のずれ」を消すために溜める記憶。だが風が積分の育つ速さより速く変われば、積分は常に一歩遅れて的外れを撃つ。つまり突風では"記憶"より**"反応の速さ"**が効く——ここは学習に勝ち目がありそうな領域だ。

onocollo.rocket に時変風(ゼロ平均のオルンシュタイン=ウーレンベック過程。相関時間 ~0.4 秒で滑らかに揺らぐ)を後方互換で足し(gust_config、既定 OFF で既存挙動は不変)、同じ厳密ハーネスで再検証中。すでに分かった下地: 突風下で手作り PID を再調整すると、最適は k_i=0(=積分なしの純 PD)——積分はむしろ害になり、"反応的な"純 PD が最良の手作り制御だった(持続風では k_i>0 が良かったのと対照的)。仮説の機構は裏が取れた。

残る問い: その反応的な純 PD を、学習した反応的方策は超えられるか? 事前登録した主判定(recurrent フルスタック 10 seed vs 再調整 PD、paired・ブートストラップ CI)を走らせた。

結果: また引き分け(NULL)——反応性の領域でも、学習は手作りを超えなかった。

  • P_gust(recurrent): 0/10 seed が正、TEST の 3 ストリームとも fitness CI は 0 をまたぐ(+0.6/-0.1/+0.3=タイ)。soft はむしろ僅かに負(-0.004/-0.033/-0.036)——持続風と同じ「fit はタイ、soft を少し削る」型。
  • C_gust_ff(feedforward): 1 ストリーム(s0)だけ fitness +4.3[+2.3,+6.2]・soft +0.058[+0.008] と両方プラスに光ったが、他 2 ストリームは soft が負、held-out(VAL-DEC)は -0.88、seed も 0/4 が正——全ゲート不通過で、robust な勝ちではない(1 本の光りは、条件を絞った探索で時々出る"まぐれ寄り"の類。前回の教訓どおり、これ 1 本で「勝ち」とは書かない)。

結論: 積分が害になる"反応性"の領域で、手作りの最適が純 PD(k_i=0)になっても、学習した反応的方策はその純 PD を明確には超えない。→ 手作り制御は、持続風でも突風でも頑健。「突風なら反応性で学習有利」という予想は、正直に言うと外れた(仮説の機構=積分が効かない、は当たったが、だからといって学習が勝つわけではなかった)。

なお positive の疑いは残さず処理した: C_gust_ff s0 の唯一の両プラス信号は、他ストリーム・VAL-DEC・seed 一致のどれも満たさず CONFIRM ゲートに届かない。robust な勝ちが出ていれば Phase D 敵対検証を通してから headline する方針(feedback_no_solo_ai_judgment)だが、今回は通す対象の"勝ち"が無い。

12. 次回に続く

上の突風実験の先は、世界モデル(V/M/C)に接続して、画素から着陸を学ぶ——観測が「状態ベクトル」ではなく「カメラ画像」になったとき、この引き分けは動くのか? そして移動パッド——反応性がさらに効く領域では、学習に勝ち目はあるのか。

(コードは onocollo.rocket=物理 model + 正直な fitness env + 手作り baseline + ウォームスタート進化 policy + CLI + テスト + 正直な doc、の再利用テンプレ。新実験は同じ形で載る。)


補足: 数値の正本は docs/rocket_landing.md「The evaluation is the lever」節。実験は scripts/rocket_eval_experiment.py + rocket_eval_aggregate.py で再現、結果は out/rocket/eval_exp/。この記事はドラフト(公開は人間判断)。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?