TinyLidarNetの学習を始める前に、まずラベルと入出力の単位を確かめると、学習をやり直す回数を減らせます。私は自動運転AIチャレンジ2026のEnd to End AI部門で、この小さなLiDARモデルを使って決勝戦2位になりました。その途中で踏んだ問題を、次の参加者が確認できる手順にします。
この記事でできること
- 学習データのラベルが、出力層の範囲に収まるか調べる
- 加速度の正側・負側の倍率を、学習と推論でそろえる
- LiDARの実際のレンジと、前処理の正規化を区別する
- Hydraの上書きが、学習で読むキーに届いているか確かめる
最新版では、私が出したPR #362が2026年10月2日にマージされ、共通の設定ファイルと加速度の倍率が追加されています。「コードに倍率を足す」旧版の対処と、「設定を確認する」現在の対処を分けて説明します。
決勝で使ったモデルを手元のAWSIMで走らせた記録です。大会本番のレースではありません。モデルの外には減速・復帰のルールも置いています。
この記事は、公式リポジトリの dev、コミット 50d6f65038b7ebd41620c442c25cfedcc1b3c7a6 を2026年10月3日に確認して書いています。実測データは開発時に収録・集計したものです。自分の作業コピーで、まず版を記録してください。
# ホスト側、aichallenge-racingkartのリポジトリ直下
git rev-parse HEAD
git branch --show-current
git status --short
設定ファイルや処理が以下と違う場合は、先に版の違いを確認します。作業中の変更を残したまま、更新コマンドを無条件に実行する必要はありません。
先に見る4項目
| 確認するもの | 何を防ぐか | 現在の確認場所 |
|---|---|---|
| 教師ラベルの分布・単位 | 出力範囲の外の正解を学習させる | 抽出後の accelerations.npy・steers.npy
|
| 加速度の倍率と制御モード | 学習時と推論時の指令の大きさが違う |
tiny_lidar_net_common.param.yaml とノード固有の設定 |
| スキャンの正規化 | 同じ壁を別の距離として入力する | 抽出処理、学習データセット、推論前処理 |
| Hydraのキー | 設定が追加されるだけで学習の条件が変わらない |
config/train.yaml と --cfg job
|
1. ラベルの範囲と、出力層の範囲を先に並べる
公式サンプルのTinyLidarNetは、LiDARの距離の並びを入力し、[加速度, 舵] の2つを出します。出力層は tanh です。実数の式では出力範囲は $(-1,1)$ で、絶対値の大きな入力ほど飽和します。浮動小数点の計算では端の値に丸められることもあるので、「1.0が絶対に出ない」ことより、ラベルの範囲と学習のしやすさを確認します。
私が公式のサンプルデータ24本を集計したとき、加速度指令は次の分布でした。これは生の制御トピックの集計で、LiDARの時刻に同期した学習サンプル数とは違います。
| ファイル名による分類 | rosbag本数 | 加速度指令の数 | 分布 |
|---|---|---|---|
ファイル名に mpc がないもの |
21 | 22,062 | −1、0、+1の3値。+1が80.7% |
ファイル名に mpc があるもの |
3 | 8,508 | +0.7が98.4%。最小−1.6 |
分類はファイル名によります。生成元の制御器をすべて確認した分類ではありません。
特に−1.6は、倍率1.0の tanh の出力範囲から外れます。範囲内でも、ラベルがほぼ一定なら、センサーから加速度を回帰する手がかりは少ないかもしれません。ただし、この分布だけで「加速度学習が失敗する原因」を特定したわけではありません。
舵も同じです。21本のデータでは23.4%が±1.0 radでした。制御指令の値と、車体が実際に出した舵角は区別してください。指令を出せることと、その角度で曲がれることは同じではありません。
自分の抽出済みデータを調べる
次のコードを check_labels.py として保存します。NumPyだけを使い、学習処理は動かしません。ラベルのファイル数、要素数、最小・最大、頻度の高い丸め値を表示します。
from pathlib import Path
import sys
import numpy as np
root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("dataset/train")
for name in ("accelerations", "steers"):
files = sorted(root.rglob(f"{name}.npy"))
if not files:
raise SystemExit(f"{root}: {name}.npy がありません")
arrays = [np.load(p, allow_pickle=False).reshape(-1) for p in files]
a = np.concatenate(arrays)
if any(not len(x) for x in arrays) or not np.isfinite(a).all():
raise SystemExit(f"{name}: 空のデータ、NaNまたはinfがあります")
values, counts = np.unique(np.round(a, 2), return_counts=True)
order = np.argsort(counts)[-5:][::-1]
top = [(float(values[i]), float(counts[i] / len(a))) for i in order]
print(name, "files=", len(files), "N=", len(a),
"min=", float(a.min()), "max=", float(a.max()), "top=", top)
# 公式の開発コンテナ内、学習用workspaceで実行
cd /aichallenge/ml_workspace/tiny_lidar_net
python3 check_labels.py dataset/train
python3 check_labels.py dataset/val
この集計は分布の点検です。訓練・検証の独立性は、同じ走行の近接フレームが両方に混ざらないよう、走行単位で分けて確認します。
2. 加速度の倍率は、学習と推論で同じにする
旧版で起きたこと
旧版の control_mode: "ai" は、加速度の出力を±1.0にクリップし、その値を加速度指令として使っていました。ラベルを大きくしても、出力側の範囲は広がりませんでした。
また、control_mode: "fixed" ではモデルの加速度出力を使わず、設定値 acceleration を出します。既定値は0.6です。これは ai の値と掛け合わせる設定ではありません。
現在の設定
PR #362以降は、次のファイルで学習・推論の共通設定を指定できます。
aichallenge/workspace/src/aichallenge_submit/tiny_lidar_net_controller/config/tiny_lidar_net_common.param.yaml
確認した版の既定値は次のとおりです。
/**:
ros__parameters:
model:
input_dim: 750
max_range: 30.0
accel_scale: 1.0
decel_scale: 1.0
正の教師加速度は accel_scale、負の教師加速度は decel_scale で割って学習します。推論では、クリップした出力に、正なら accel_scale、負なら decel_scale を掛けます。
たとえば、説明用に両方を2.0とすると、+1.37は+0.685、−1.6は−0.8になり、推論で2倍すると元の単位に戻ります。これは計算例で、2.0が最適という実験結果ではありません。
選ぶ倍率は、自分の教師ラベルの範囲を見て決めます。最大絶対値と等しい倍率ではラベルが±1に達するので、出力層の飽和まで含めて検討してください。負の目標加速度と、車両へのブレーキ操作の関係も、別に確認が必要です。
既存の重みに倍率だけを掛けても、その倍率で学習したモデルにはなりません。
加速度を学習する場合は、学習時の倍率を記録し、同じ値を推論で使います。倍率を変えて学習し直した重みと、設定を一緒に管理してください。fixed モードではこの加速度倍率は使われず、ノード固有の acceleration が使われます。
私は大会では舵だけを学習し、加速度と減速はモデルの外で決めました。今回の共通設定の修正は、入出力の対応を扱いやすくするものです。この修正を使った加速度学習で、走行性能が改善したという比較はしていません。
3. センサーの最大距離と、正規化に使う距離は違う
サンプルデータでのLiDARは、750点・前方約180°・range_max: 25.0・約20 Hzでした。反射が返らなかった方向は inf です。仕様表の修正もPR #117でマージされています。
# AWSIMと同じROS_DOMAIN_IDの開発コンテナ内で実行
# ドメイン番号は自分の実行設定に合わせる
ros2 topic echo --once --no-arr /sensing/lidar/scan
ros2 topic hz /sensing/lidar/scan
一方、私が使ったサンプルの正規化は30 mでした。25 m以下の有限値は、正規化後に最大で約0.833になり、無反射を30 mで埋めた点だけが1.0になります。
これは、学習と推論がそろっていれば、ただちに不具合になるという話ではありません。私も30 mのままで走らせました。正規化の値域がそろった場合との性能比較はしていません。
危険なのは、学習は30 mのまま、推論だけ25 mにすることです。たとえば同じ10 mの壁が、学習時は0.333、推論時は0.4になり、入力の尺度が変わります。
現在の共通設定は学習データセットと推論ノードの max_range をそろえます。ただし、rosbagから .npy を作る抽出スクリプトには、別に ExtractionConfig.max_scan_range: 30.0 があります。共通ファイルを変更しても、抽出済みのファイルや過去の重みは自動で作り直されません。
確認した版の抽出スクリプトには、max_scan_range を変えるコマンドライン引数はありません。前処理を変えるときは、次を順に確認します。
- 抽出時に
infを何mで埋め、どこでクリップしたか - 学習側が何mで割るか
- 推論側が生スキャンをどう処理して、何mで割るか
- 変更後の設定で重みを学習し直したか
大きなレンジに変更した場合、以前の抽出で切り捨てた距離や inf の区別を、抽出済みデータから戻せるとは限りません。必要なら元のrosbagから作り直します。
4. Hydraの上書きは、最終設定を表示して確かめる
学習の損失重みは train: の下にあります。舵だけ学習するキーは、現在も次の形です。
cd /aichallenge/ml_workspace/tiny_lidar_net
python3 train.py train.loss.accel_weight=0.0 --cfg job
--cfg job は学習を始めずに、Hydraが構成した設定を表示します。表示の train → loss → accel_weight が0.0なら、上書き先は合っています。
旧READMEでは loss.accel_weight=0.0 と書かれていました。エラー文に従って +loss.accel_weight=0.0 とすると、最上位に別の loss が追加されるだけで、学習が読む train.loss.accel_weight は変わりません。このREADMEはPR #356で修正済みです。
train:
loss:
steer_weight: 1.0
accel_weight: 0.0
なお --cfg job の表示だけでは、共通YAMLの中身まで読んだことにはなりません。共通YAMLは train.py の実行時に読み込まれます。学習開始時に出る Common parameters: の表示も確認してください。common_param_path の既定値は開発コンテナ内の絶対パスなので、ホストで学習するなら、実在する共通ファイルのパスを指定します。
学習を実行する場合は --cfg job を外し、訓練・検証データの場所も自分の環境に合わせます。
python3 train.py \
data.train_dir=/path/to/train_dir \
data.val_dir=/path/to/val_dir \
train.loss.steer_weight=1.0 \
train.loss.accel_weight=0.0
舵だけ学習した重みで走る場合は、推論の control_mode を fixed にします。損失から除いた加速度出力を、ai モードで使わないためです。
手元で使う確認リスト
- 使用したコミット、重み、共通YAML、ノード固有YAMLを一組として記録した
- 訓練と検証を別の走行に分け、ラベルの単位・分布を見た
- 正負の倍率で割ったラベルが、意図した出力範囲に収まる
- 学習と推論の倍率、入力点数、正規化が一致している
- 抽出済みデータの前処理も、現在の設定と合っている
-
--cfg jobで損失のキーを確認した - 舵だけ学習したモデルでは、加速度出力を使わない
サンプルが走らないとき、最初にモデルを大きくする必要はありません。私の場合は、入出力と設定の対応を確認するだけで、学習を疑う前に切り分けられる問題がありました。
方法全体は1本目のE2E部門の記事、教師に使った制御はPure PursuitとMPCの記事にまとめています。
参考文献・出典
- 確認した公式サンプル:https://github.com/AutomotiveAIChallenge/aichallenge-racingkart/tree/50d6f65038b7ebd41620c442c25cfedcc1b3c7a6/aichallenge/ml_workspace/tiny_lidar_net
- 確認した推論設定:https://github.com/AutomotiveAIChallenge/aichallenge-racingkart/tree/50d6f65038b7ebd41620c442c25cfedcc1b3c7a6/aichallenge/workspace/src/aichallenge_submit/tiny_lidar_net_controller
- 共通設定と倍率の追加、PR #362:https://github.com/AutomotiveAIChallenge/aichallenge-racingkart/pull/362
- Hydraのキーの修正、PR #356:https://github.com/AutomotiveAIChallenge/aichallenge-racingkart/pull/356
- LiDAR仕様の修正、PR #117:https://github.com/AutomotiveAIChallenge/aichallenge-documentation-racingkart/pull/117
- 公式サンプルデータの案内、Issue #259:https://github.com/AutomotiveAIChallenge/aichallenge-racingkart/issues/259
- Hydraの設定表示:https://hydra.cc/docs/tutorials/basic/running_your_app/debugging/


