この記事でできること
学習用シミュレータから別のシミュレータへ移す前に、出力の単位・車の応答・センサー・相手の動き・CPU負荷を順に点検できます。各比較のモデル、台数、試行回数を併記し、1本の成績を一般化しないようにしています。
自動運転AIチャレンジ2026 の End to End AI 部門で、私(高専生1人のチーム KSK)は 2D LiDAR だけで舵を決めるモデルを自作の GPU シミュレータで学習させ、決勝戦で2位(準優勝)になりました。この記事は、その途中でいちばん時間を取られた「自作シミュレータと本番シミュレータの差」の記録です。
この記事の要点
- 自作 GPU シムで当時最高の成績(検証コースで 45 秒間ぶつからなかった割合 90.2%)を出した中間版モデルが、本番シミュレータ AWSIM の4台6周では4台とも完走できませんでした(4台とも3〜4周で制限 300 秒の時間切れ、罰 23 件・計 203.1 秒、1本)。自作シムで 83.6% だった別の版は、同じ条件の1本で 3/4 完走でした。
- 差の出どころは5つありました。
- 舵の倍率:同じ重みで1周 93 秒 → 43 秒(中間版・AWSIM 単独2周)
- 車両モデル:同じ舵でも 8.75 → 6.56 m/s(初期版が AWSIM で出した舵を、自作シムの車両モデルに流したもの)
- 壁の形:同じ姿勢から見たスキャンが平均 1.28 m ずれる(AWSIM の本番コースを走った記録の姿勢で比較)
- 相手の動き:他車に入れた「時間の 25% は 5 km/h」は、AWSIM で前を走る遅い車の実測(2.4% と 0.0%)の 10 倍以上(混走1本の記録)
- CPU の取り合い:スレッド数を1にすると完走 8/12 → 12/12(初期版・4台×3本)
- 最後は「自作シムはデータ集めと候補の絞り込みに使い、採否は AWSIM で複数本走らせて決める」という運用に落ち着きました。

自作シムの検証で上だったモデルが、AWSIM の4台6周(各1本)では1台も完走しませんでした。どちらも中間版で、自作シムの値は 45 秒間ぶつからなかった割合です(詳しくは「罠4」)
30秒でわかる自動運転AIチャレンジ
自動運転AIチャレンジ2026(自動車技術会主催)は、レーシングカートを自動運転ソフトで走らせて競う大会です。End to End AI 部門は、センサーの入力から操舵までを学習モデルでつなぐ部門で、決勝は公式シミュレータ AWSIM の上で4台が同時に走るレースでした。この部門で私は、選抜戦は組で1位、選抜戦と発表の総合2位で決勝戦に進み、決勝戦で2位(準優勝)でした。もう1つの Sim to Real 部門は、予選が学生クラス2位、SIM 選抜戦で敗退です。
関連する記事と動画です。
前提:何を作って、どこで測ったか
モデルは公式サンプルの TinyLidarNet(1次元の畳み込みネットワーク、約15万パラメータ)で、ネットワークの形は変えていません。入力は 2D LiDAR の距離 750 個だけで、出力のうち舵だけを使います。アクセルは一定で、ブレーキと壁からの後退はモデルの外のルールです。
学習は自作の GPU シミュレータで行いました。乱数で作ったコースの上で、Pure Pursuit(中心線上の少し先の点へ向けて舵を切る、古典的な追従のしかた)を先生役にして、その舵を生徒のモデルにまねさせます。まねのさせ方は BC(Behavior Cloning、先生の舵をそのまま回帰で写す)と DAgger(生徒に運転させ、ずれた場面に「先生ならこう切る」を足して学び直す)です。
最終モデルと、本番コースを除外する仕組みを入れた後に学習した中間版は、本番コースの走行データを学習に入れていません(コース名で読み込み時に弾いています)。最初の試作だけはその仕組みを入れる前に学習していて、学習に使ったコースの置き場に本番コースが入っていたので、ほぼ確実に学習に混ざっています。また、本番コースは選ぶことと調整には使いました。自作シムに置いた本番コースでの完走率をモデルや先生の設定を選ぶ指標にしましたし、後で出てくる舵の倍率 0.80 などは AWSIM の本番コースを走らせて決めています。
この記事に出てくるモデルの版は3種類です。数字ごとにどの版かを書きます。
| 呼び名 | 中身 |
|---|---|
| 初期版 | AWSIM の本番コースで、SW 部門の制御器を先生にして収録・学習した版 |
| 中間版 | 自作シムで学習した途中の版。いくつもあるので、本文では「最初の試作」「揺らぐ相手の版」のように書き分けます。最初の試作だけは本番コースの除外を入れる前に学習 |
| 最終モデル | 本番コースの走行データは学習に不使用。決勝で使用 |
2つの「完走」と、この記事の用語
自作シムの「完走率」と AWSIM の「完走」は、ものさしが違います。
- 自作シムの完走率:学習に使っていない検証用のコースに置いた車のうち、45 秒(900 ステップ)のあいだ壁や他の車にぶつからなかった割合です。1周を走り切ったかどうかは見ていません。
- AWSIM の完走:制限時間内に6周を走り切ることです。
この記事で自作シムの%と AWSIM の「○/4 完走」を並べるのは、同じものさしで比べるためではなく、順位が入れ替わったことを示すためです。
ほかに、次の言葉を使います。
- 罰:壁や他の車にぶつかったときなどに科されるペナルティです。公式の結果ファイルに件数と秒数が出ます。罰の間は車速が約 5 km/h に抑えられます(走行記録で見た値)。
- 減速ガード:前方の最短距離を車速で割って衝突までの時間(TTC、Time To Collision)を出し、0.45 秒より短いと減速する、モデルの外のルールです。最終的に採用しました。
- 壁ガード:斜め前の壁が近いと、反対側へ舵を足すルールです。試しましたが、最終的には採用していません。
2つのシミュレータ
シミュレータで作ったものが実機で動かない問題(sim-to-real)と同じことが、シミュレータどうしでも起きます。ここでは sim-to-sim と呼びます。
| 自作 GPU シム | AWSIM(本番シミュレータ) | |
|---|---|---|
| 中身 | PyTorch のテンソルで N 台ぶんのカートを一括で計算 | Unity 製の公式シミュレータ。Autoware(ROS 2)とつないで走らせる |
| 同時に走らせる数 | 256 環境 | 最大4台。実時間で進む(CPU が足りないとさらに遅い) |
| 処理量 | 256 環境の合計で 13.1k ステップ/秒 | 20 ステップ/秒(LiDAR 20 Hz) |
| 他の車 | 半径 0.85 m の円が中心線に沿って等速で動く | 実際に運転している車(手元の検証では自チームのモデル、本番は他チームのソフト) |
| 使い道 | データ集め・学習・候補のふるい | 採否の判定 |
処理量の比は約 655 倍ですが、これは全環境を合計した処理量の比で、学習が 655 倍速く終わるという意味ではありません(1環境あたりでは約 51 ステップ/秒です)。

自作 GPU シムの俯瞰(試走、開始 8〜20 秒)。乱数で作ったコースの上の 12 台は互いにぶつからない別々の環境で、1枚に重ねて描いています。上の「生存」はまだ壁にぶつかっていない台数、黄色の車の LiDAR はオレンジで表示。走らせた重みがどの版かは記録が残っていません

AWSIM の4台混走(1周目)。4台とも自チームのモデル(最終モデル ×2、中間版 ×2)で、互いの画面に他の車がはっきり写ります
最初の転移は 21 km/h
自作シムで学習した最初の試作(自作シムのランダムコースで検証平均 84.5%)を、はじめて AWSIM に載せました。走りはしましたが、画面の表示で車速 21 km/h(2周目の時点で計時は 158 秒)で、制限 600 秒で6周に届きませんでした。当時 AWSIM で学習していた初期版は 38 km/h、1周 40.6〜41.2 秒です。逆向き、つまり初期版の重みを自作シムの本番コースに載せる実験もその前にしていて、こちらは 64 台すべてが途中でぶつかりました(完走率 0.0%)。
ここから2日間で、差を1つずつ見つけて潰していきました。
罠1:舵の倍率を一度も AWSIM で確かめていなかった
自作シムでは、モデルの出力 $u \in [-1, 1]$ に最大舵角 0.64 rad を掛け、さらに「舵の効き」の係数 0.70(SW 部門で AWSIM の走行記録から合わせた値)を掛けたものが実際に効く舵角でした。
$$
\delta_{\mathrm{eff}} = 0.70 \times 0.64,u = 0.448,u
$$
一方、AWSIM 側の推論ノードは出力をそのまま舵角 [rad] として送っていました。自作シムの定義どおりに計算すると、学習した重みをそのまま載せれば舵が約 2.2 倍(1/0.448)効くはずでした。最初の転移(21 km/h の回)は、倍率を掛けずにそのまま載せた状態です。
そこで倍率を掛ける口を推論ノードに足し、まず走らせずに確かめました。AWSIM の本番コースを初期版が走った記録から、実スキャン 5,315 枚に最初の試作を通して、そのとき実際に出ていた指令と比べます。差は RMSE(差の二乗を平均して平方根をとった値)で見ます。
| 条件 | 舵のばらつき(標準偏差) | 実際の指令との差(RMSE) |
|---|---|---|
| 実際に出ていた指令 | 0.2200 | - |
| 最初の試作をそのまま | 0.4270 | 14.23° |
| 最初の試作 × 0.448 | 0.1913 | 5.66° |
最小二乗で求めた最適な倍率は 0.467 で、計算値の 0.448 とほぼ一致しました。私はここで「理論と合っている」と判断し、以後、自作シムで学習した重みの AWSIM の試験は、すべて倍率 0.448 で走らせました。
その日の夜、別の中間版で AWSIM を単独2周ずつ走らせ、倍率を振ってみたのが次の図です。この版は MPC(モデル予測制御。車の動きを少し先まで予測して、最も良い操作を選ぶ制御)を先生にした系統です。

同じ重みで倍率だけを変えた比較。0.448 では1周 93.04 秒、0.80 では 43.02 秒(AWSIM・単独2周・中間版・倍率ごとに1本)
同じ重みで、ラップが 2 倍以上動きました。0.95 では当てすぎて壁に触れ、71.06 秒の周が出ています。オフラインの検算は「別のモデルが出した指令に形を合わせる」ことしか見ておらず、AWSIM の中で閉じたループで走ったときの最適とは一致しませんでした。自作シムの定義から出した「2.2 倍効く」という計算の前提自体が、AWSIM の実際と合っていなかったことになります。0.448 で出していたそれまでの AWSIM の判定は、測り直す必要が出ました。
試した2つの版では、どちらも 0.80 が良い側でしたが、理由は先生によって逆でした。舵の小さい MPC の先生(平均 5.2°)の版は舵が足りずに膨らんでいたと見ていて、倍率を上げると大きく速くなりました。舵の大きい Pure Pursuit の先生(平均 12.5°)の版は、0.95 に上げるとむしろ遅くなりました(この版で試したのは 0.80 と 0.95 の2点だけで、0.95 は単独3周で最速 46.14 秒。条件の揃っていない参考値です)。最終モデルの AWSIM での試験も 0.80 です。
推論ノードの該当部分です(コメントは短く要約しています)。
# AWSIM の実走で最も良かったのが 0.80
self.declare_parameter('steer_scale',
float(os.environ.get('E2E_STEER_SCALE', '0.80')))
...
# 学習した場所に合わせて舵を実舵角[rad]に直す
steer *= self.steer_scale
埋め方は単純で、自作シムから持ってくる定数は AWSIM で数点振ってから使う、新しい重みでは毎回振る、の2つです。のちに、この倍率を含む 45 項目を走行中に ros2 param set で変えられるようにしました。
罠2:車両モデルが AWSIM より「重かった」
自作シムの Pure Pursuit の先生は、どう設定を振っても平均の速さが 4〜5 m/s で頭打ちでした。自作シムの縦の運動は次の式で、舵を切るほど減速する項が入っています。係数は 0.70 と同じく、SW 部門で AWSIM の走行記録に合わせたものをそのまま使っていました。
$$
\dot v = a_{\mathrm{cmd}} - 0.10,v - 1.3,\lvert \delta_{\mathrm{eff}} \rvert,v
$$
先生が走ったときの加速度を平均すると、加速の指令 +1.294 m/s² のうち、転がり抵抗で 0.516、舵による減速で 0.594 が消え、残りは +0.184 m/s² でした。86% が抵抗で相殺されています。
どこまでが先生のせいで、どこからが車両モデルのせいかを分けるため、AWSIM で初期版が単独で走ったときの実際の舵を、自作シムの縦の運動モデルに流しました。

左:同じ舵でも自作シムでは 8.75 → 6.56 m/s。Pure Pursuit の先生は舵を約 1.9 倍当てるので、さらに 4.34 m/s まで落ちます。右:加速の内訳
AWSIM では平均 8.75 m/s で走れた舵が、自作シムでは 6.56 m/s にしかなりません。自作シムの縦の運動は AWSIM より約 25% 重く、そのうえ Pure Pursuit は舵を AWSIM の初期版の約 1.9 倍(12.5° 対 6.7°)当てるので、4.34 m/s まで落ちます。係数を軽くすれば速くなりそうですが、SW 部門で係数を軽くした版は AWSIM で成績が落ちた記録があったので、触りませんでした。
この差から生まれた2つの期待は、どちらも外れました(どちらも AWSIM の4台6周・制限 400 秒、各1本)。
- 速い先生を混ぜれば速くなる。自作シムで平均 9.82 m/s 出る MPC の先生の走りを学習データの 30% に混ぜると、自作シムの本番コース(学習には不使用)で車速が 4.15 → 4.61 m/s(+11%)に上がりましたが、AWSIM では最速ラップが約 0.7 秒縮んだだけで、罰は 5 件(29.7 秒)から 7 件(59.9 秒)に増えました。
- 自作シムが重いぶん、AWSIM では余裕が出る。横加速度の上限を上げた先生の版(自作シムで完走率 73.4%)は、AWSIM では最速ラップ 46.20〜47.48 秒と遅く、罰も 16 件(115.9 秒)でした。
罠3:壁の形が合っていなかった
倍率を 0.448 にしても(後で分かるとおり、これ自体が小さすぎたのですが)最初の試作は AWSIM で完走しませんでした。次に疑ったのは入力そのものです。AWSIM で記録した姿勢に自作シムの LiDAR を置き、同じ場所から見たスキャンを並べました。

同じ姿勢から見たスキャンでも、自作シムのほうが壁が近く、遠くまで抜ける点が少なくなっています。距離どうしの相関は +0.750
記録した姿勢には誤差があるので、フレームごとに位置 ±0.6 m・向き ±0.1 rad の範囲で最もよく合う姿勢を総当たりで探しました。それでも差は平均 1.28 m、中央値 0.97 m 残りました。AWSIM の実スキャンの平均距離(7.95 m)の約 16% にあたり、形の違いとして小さくありません。
原因の候補として疑って、否定できたものは次のとおりです。表の距離の差は、どれも姿勢を探索する前の比較で出した値です。ただし図の「記録した姿勢のまま」の 2.43 m とは別に集計した値なので、数字は一致しません。表で見るのは、条件を変えても差が変わらなかったことです。
| 疑ったこと | 確かめ方 | 結果(姿勢を探索する前の差) |
|---|---|---|
| 路面の傾きで地面を拾っている | 標高あり・なしで比較 | どちらも平均 2.47 m で変わらず。無関係 |
| LiDAR の取り付け位置 | 前後位置を 0〜2.0 m で振る | 差は 2.46〜2.63 m でほぼ変わらず |
| コースの周長が違う | 自作シム 358.8 m と AWSIM の実走 348〜370 m | 一致。最初に「周長が怪しい」と書いたメモは誤り |
壁の形を AWSIM に合わせ込む作業は、最後までしていません。代わりに、学習は乱数で作ったコース(最終モデルは 80 本)で行って特定の壁を覚えさせないようにし、走らせる前の足切りには AWSIM の記録済みスキャンを使いました。重みを通して舵を比べるだけなら数秒で済み、AWSIM を1回走らせる 5〜10 分よりずっと安く済みます。ただし罠1のとおり、これは足切り専用で、採否には使いません。
最終モデルは、壁の形がずれたままの自作シムで学習しましたが、AWSIM の単独6周を罰0(合計 278.9 秒)で走り切っています。乱数のコースで「壁一般」を学ばせたことが形の差を吸収した、というのが当時の見立てですが、確かめてはいません。コースの本数を変えた比較も、測り直すとばらつきが大きく、結論を撤回しています。同じ40本で学習した別モデルでも結果が81〜98%にばらつき、本数だけの効果は分離できませんでした。
罠4:「現実っぽい相手」が AWSIM の実測と違った
冒頭の図の話です。自作シムの他車は「中心線に沿って等速で動く円」です。自車の 4〜25 m 先に、自車の最高速の 0.25〜0.60 倍の速さで置きます。一方 AWSIM の相手は実際に運転している車で、ぶつかれば罰で減速し(走行記録で見ると約 5 km/h に抑えられます)、避けるときは横にも動きます。
「この差が4台走行の弱さの原因だろう」と考えて、自作シムの他車に揺らぎを入れて学習し直しました。該当部分です(コメントは短く要約しています)。
opp_stall_prob: float = 0.0 # 1周期のうち「罰で落ちている」割合
opp_stall_period: float = 12.0 # [s] その周期
opp_stall_speed: float = 1.389 # [m/s] 5km/h
opp_weave_m: float = 0.0 # [m] 横の振れ幅
opp_weave_period: float = 7.0 # [s]
...
ph = (self._opp_t / cfg.opp_stall_period + self._opp_phase) % 1.0
v = torch.where(ph < cfg.opp_stall_prob,
torch.full_like(v, cfg.opp_stall_speed), v)
このときは opp_stall_prob=0.25(12 秒のうち 3 秒は 5 km/h)、opp_weave_m=0.5(7 秒周期の正弦波で横に 0.5 m)にしました。揺れの位相は車ごとにずらしています。
自作シムでは、揺らぐ他車3台を入れた検証で完走率が平均 90.2%(最悪のコースで 77.7%)と、それまでの最高が出ました。同じ条件で、等速の他車で学習した中間版(後で AWSIM の混走の相手役にも使う版。以下「相手役の中間版」)は 83.6% です。ところが AWSIM の4台6周(4台とも同じ重み、倍率 0.80、減速ガードなし、制限 300 秒)では結果が逆になりました。
この比較は各1本で、後で書く「3本以上ずつ測る」の基準を満たしていません。ただ、差は完走 0/4 対 3/4、罰 23 件対 3 件と大きく、揺らぐ相手の版はこの後使っていません。
| 中間版 | 自作シム(揺らぐ他車、45 秒間ぶつからない割合) | AWSIM 完走(1本) | AWSIM 罰 | AWSIM 最速ラップ |
|---|---|---|---|---|
| 揺らぐ相手で学習 | 90.2% | 0/4 | 23 件・203.1 秒 | 55.95〜61.54 秒 |
| 等速の相手で学習(相手役の中間版) | 83.6% | 3/4 | 3 件・20.1 秒 | 44.68〜44.90 秒 |
未完走はどれも制限 300 秒の時間切れで、揺らぐ相手の版は4台とも3〜4周まで、相手役の中間版の1台は5周まで進んでいました。
後から、AWSIM の混走(速い初期版2台と、遅い相手役の中間版2台)を1本記録して、実際の車がどれくらい遅くなっているかを数えました。

設定した「時間の 25% は 5 km/h」に対し、前を走る遅い車が 2 m/s 未満だったのは 2.4% と 0.0%。2.4% のほうもスタート直後の 7 秒だけでした(AWSIM 混走1本の記録)
自作シムの他車がまねしたかったのは「前を走る遅い車」です。その役の2台は 2.4% と 0.0% でした。後ろから追いつく速い2台は、詰まりや罰で 11.5% と 5.9% ありましたが、それでも 25% には届きません。入れた揺らぎは前を走る車の実測の 10 倍以上で、少なくとも手元の AWSIM の混走に対しては、等速の円のほうが近かったことになります。
自作シムでだけ成績が上がった理由は確かめきれていませんが、規則的な揺らぎはそれ自体が学べる模様になるので、その周期に合わせた避け方を覚えたのだと見ています。既定は 0(等速)に戻し、「変えるなら AWSIM で A/B」とコードに書き残しました。相手の動きを本当に入れたいなら、AWSIM で記録した実際の軌跡を再生するべきでした(未実施)。
罠5:シミュレータではなく、測っている PC が詰まっていた
丸一日、AWSIM の4台走行で完走できない問題を「学習の問題」として追っていました。先生を MPC にする、ラベルの食い違いを直す、DAgger の配合を変える。自作シムの成績は上がっても、AWSIM では 0/4 のままでした。
その夜に気づいたのは、24 コアの PC で Linuxのload average(実行待ちや割り込み不能な待ち状態も含むタスク数の平均)が 84 だったことです。推論ノードは多いもので1つ 2.8 コア、4つ合わせて 9.4 コアを使っていました。750 入力の小さなネットワークなのに、です。推論は NumPy で書いていて、行列積を回す BLAS(数値計算ライブラリ)のスレッド数が既定でコア数(24)になっていました。4 ノードで 96 本のスレッドが CPU を取り合っていたのです。
直したのは推論ノードの先頭です(コメントは短く要約しています)。
# numpy を import する前でないと効かない。setdefault ではなく必ず代入する
_TH = os.environ.get('E2E_NUM_THREADS') or '1'
for _v in ('OMP_NUM_THREADS', 'OPENBLAS_NUM_THREADS', 'MKL_NUM_THREADS',
'NUMEXPR_NUM_THREADS', 'VECLIB_MAXIMUM_THREADS'):
os.environ[_v] = _TH
import numpy as np

初期版4台×3本、減速ガード・壁ガードとも ON。モデルの計算の中身は同じで、スレッド数だけを変えています
CPU の合計は 943% から 99.5% に、load は 84 から 13 に下がり、完走は 8/12 から 12/12 に、車どうしの接触による罰(crash)は 4 件から 0 件になりました。その日に「AWSIM で駄目」と判定していた相手役の中間版を同じ条件(倍率 0.80・ガードなし)で測り直すと、0/4(罰 22 件・191.4 秒)から 3/4(罰 3 件・20.1 秒)に変わりました(どちらも1本)。それまでの判定の多くは、モデルより PC の混み具合に左右されていた可能性があります。
ほかに、手元の AWSIM がシーンを進めないまま止まる回もありました。自作の起動スクリプトはレース開始の合図しか見ておらず、止まったまま時間切れまで待っていました。推論ノードの CPU が 25% 前後から 0.1% に落ちるので、今はそれで見分けています。スレッド数の設定を setdefault にしない理由など、詳しくはスレッド数の記事に書きました。
採否は AWSIM で決める:落ち着いた運用
コース 80 本で学習した版(これがそのまま最終モデルになります)ができたあとも、自作シムでこれを上回る版は作れましたが、AWSIM では勝てませんでした。

自作シムで最も良かった「DAgger のコースを増やした版」は、AWSIM の混走で罰が最終モデルの約 2.8 倍(前者は 6 台、最終モデルは採用を決めた時点までの 10 台での比較)。40 本の中間版は学習コースの組がほかの2つと違い、自作シムの検証コースの組も違う可能性があります
AWSIM の混走は、評価したいモデル2台と、遅い相手役の中間版2台を同時に走らせ、6周・制限 400 秒・減速ガード ON・倍率 0.80 で測りました。図の台数は、DAgger のコースを増やした版が3本で6台、最終モデルが5本で10台、40 本の中間版が4本で8台です。
- 最終モデルの台数:図の 10 台に後で2本(4台)を足した 14 台では、罰 11 件(0.79 件/台、うち接触4件)でした。このうち1周目だけが 104〜108 秒に延びた1本(2台)を除くと、12 台で罰 4 件(0.33 件/台)です。
- 除いた回とその理由:その1本は、同じ日の夕方に1周目だけが延びる回が続いていたので除きました。当時は、手元で 10 時間以上続けて動かしていた AWSIM の状態によるものと見ていました(原因は確かめていません)。DAgger のコースを増やした版の 6 台も、別の評価コンテナと CPU を取り合った回(6.75 件/台)を除いた値です。どちらも後で書く「測る前に PC を見る」の基準によるもので、走行が最後まで終わらず結果が残らなかった回は、どの数にも入っていません。
- 言えること:台数が少ないので、差の大きさは目安です。自作シムで上の版が AWSIM で勝てなかっただけでなく、罠2の 73.4% の版のように自作シムで悪かったものが AWSIM で良くなることもありませんでした。自作シムの数字は、どちら向きにも AWSIM の予測に使えませんでした。
最終的に、次のように役割を分けました。
AWSIM で測るときに守ったことをまとめます。
- A/B は1変数だけ変え、3 本以上ずつ測ります。単発では壁ガードで罰が 34.7 → 5.3 秒に減ったように見えたものが、3 本ずつでは 221.5 → 169.3 秒でした(どちらも罠5に気づく前、CPU の取り合いが起きていた時期の値です)。
- 速さの違う相手を混ぜます。4 台とも同じ重みだと前の車に追いつく場面が起きず、追突に関わる仕組みを評価できません(下の図)。
- 制限時間と周回数を添えます。相手役の中間版は制限 300 秒で 9/12 完走(3本。未完走はすべて5周まで到達)、400 秒で 4/4(1本)でした。「完走しない」と「間に合わない」は対策が違います。
- 測る前に PC を見ます。load、推論ノードの CPU、別の評価コンテナとの競合(競合した回は 6.75 件/台)、手元の AWSIM を長く動かし続けたときの1周目の延びを確かめ、怪しい回は捨てます。

速度差のある4台混走×3本(初期版2台+相手役の中間版2台)。ガードの有無で完走 12/12 対 8/12。罠5の「8/12 → 12/12」とは別の実験で、数字が偶然同じです
この方針は、開発に使っていた AI エージェント向けの作業指示ファイルにも「GPUシミュレータの値だけで採用を決めず、最終判断は公式AWSIM条件で行う」という一文で書き込みました。
5つの罠と埋め方(早見表)
| 罠 | 症状 | 気づいたきっかけ | 埋め方 |
|---|---|---|---|
| 舵の倍率 | 1周 93 秒(0.448) | 倍率を AWSIM で振った | AWSIM で数点振る。重みを替えたら毎回振る |
| 車両モデル | 先生が 4〜5 m/s で頭打ち | AWSIM の舵を自作シムに流した | 自作シムのタイムを AWSIM の予測に使わない |
| 壁の形 | 同じ姿勢で平均 1.28 m ずれる | 同じ姿勢のスキャンを並べた | 乱数のコースで学ばせる。実スキャンで足切り |
| 相手の動き | 自作シム 90.2%(45 秒ぶつからない率)→ AWSIM 0/4(1本) | AWSIM の車速を数えた | 等速に戻す。変えるなら AWSIM で A/B |
| CPU の取り合い | 4 台だと完走しない | load 84 に気づいた | BLAS を1スレッドに固定。測る前に load を見る |
その後
決勝に持ち込んだのは、最終モデルと、減速ガードや壁からの後退などモデルの外の安全装置です。AWSIM では単独6周で罰0・合計 278.9 秒、4台混走6周の1本(相手2台は自チームの中間版、制限 400 秒)で4台とも完走・車どうしの接触0件です。決勝戦は2位(準優勝)でした。一方で、単独の最速ラップ 45.33 秒は本番コースで学習した初期版の 39.77 秒より約 5.6 秒遅く、Pure Pursuit の先生の遅さが残っています。
まとめ
- 自作シムの数字は自作シムの中の比較にだけ使えます。自作シムで 90.2%(45 秒ぶつからない率)のモデルが AWSIM の1本で 0/4、自作シムで1位の版が最終モデルに負ける、が2日間で起きました。
- 差の出どころは舵の倍率・車両モデル・壁の形・相手の動き・測る PC の5つ。前の3つはシミュレータの定義の違い、4つ目は現実に寄せたつもりの推測、5つ目はシミュレータの外でした。
- 埋め方は、定数は AWSIM で振る、乱数のコースで形を覚えさせない、相手は実測を見てから変える、測る前に PC を見る、採否は AWSIM の複数本で決める、です。
来年出る人へ一言
自作シミュレータは本当に役に立ちます。256 環境を同時に走らせられなければ、コースの本数を振る実験も、それを後で測り直して結論を撤回することもできませんでした。ただ、自作シムの成績が上がった日ほど、AWSIM で1回走らせてから喜んでください。私は「自作シムで当時最高」の日に、AWSIM で4台とも完走できませんでした。
参考文献・出典
- 公式のEnd to End AI部門説明:https://automotiveaichallenge.github.io/aichallenge-documentation-racingkart/competition/ai-class.html
- 公式TinyLidarNetの学習・推論サンプル:https://github.com/AutomotiveAIChallenge/aichallenge-racingkart/tree/dev/aichallenge/ml_workspace/tiny_lidar_net
- NumPyのスレッド設定:https://numpy.org/doc/stable/reference/global_state.html
- 学習・評価の全体像:https://qiita.com/Raptor-S/items/d0dd1ba87a1871112e9a。本文の数字は、表・図に記載した実験条件の値です。