DonkeyCar simulatorで強化学習のサンプルを実行してみる(VAE+SAC編)
DonkeyCar simulatorで強化学習(その1)、
DonkeyCar simulatorで強化学習(その2)
ではDonkeyCar simulatorに添付のサンプルプログラムを実行してみたが、結果がイマイチだったので別のプログラムを試してみる。
パクった 参考にしたのは、Jetson Nanoで動く深層強化学習を使ったラジコン向け自動運転ソフトウェアの紹介
# 作業ディレクトリ作成
mkdir -p /work2/donkey_sim3
cd /work2/donkey_sim3/
# 仮想環境構築
pyenv virtualenv 3.8.11 donkey_sim3
pyenv local donkey_sim3
pip install --upgrade pip setuptools wheel
# 必要なライブラリのインストール
pip install opencv-python
pip install torch
pip install torchvision
pip install pyyaml
pip install stable_baselines3
pip install gym
pip install git+https://github.com/tawnkramer/gym-donkeycar@v21.07.24
# tensorboard も必要
pip install tensorboard
# たぶん要らないけど、念のため入れとく(tensorboard 実行時になんか言われるので)
pip install tensorflow
[!NOTE] 今回は stable-baselines3 を使用するので、 tensorflow ではなく、pytorch。
しかし、tensorboardは必要(学習ログ記録のため)。
tensorboardで可視化機能を使用する際はtensorflowが入ってないと実行時になんか言われるので 念のためtensorflowも入れとく(たぶん入れなくても大丈夫)。
[!NOTE] 試したときのモジュール類のバージョンは以下の通り
opencv-python 4.5.4.60 torch 1.10.0 torchvision 0.11.1 PyYAML 6.0 stable-baselines3 1.3.0 gym 0.19.0 gym-donkeycar 1.1.1 ← Githubのtagはv21.07.24 tensorboard 2.7.0 tensorflow 2.7.0
DonkeyCar simulatorで強化学習(その1) と同じ
git clone https://github.com/masato-ka/airc-rl-agent.git
cd airc-rl-agent/
git checkout -b release-v1.5.2 refs/tags/release-v1.5.2
diff --git a/config.yml b/config.yml
index bda2308..3bf2ad4 100644
--- a/config.yml
+++ b/config.yml
@@ -48,8 +48,8 @@ AGENT_SETTING:
N_COMMAND_HISTORY: 20
MIN_STEERING: -1.0
MAX_STEERING: 1.0
- MIN_THROTTLE: 0.7 # 0.4
- MAX_THROTTLE: 0.95 # 0.9
+ MIN_THROTTLE: 0.3 # 0.7 # 0.4
+ MAX_THROTTLE: 0.95 # 0.95 # 0.9
MAX_STEERING_DIFF: 0.9 #0.35
JETRACER_SETTING:
diff --git a/learning_racer/commands/subcommand.py b/learning_racer/commands/subcommand.py
index 0cf6eac..e4474e7 100644
--- a/learning_racer/commands/subcommand.py
+++ b/learning_racer/commands/subcommand.py
@@ -56,6 +56,7 @@ def command_train(args, config):
model = CustomSAC(agent, args, config)
model.lean(callback=callback)
model.save(args.save)
+ agent.close()
def command_demo(args, config):
@@ -65,4 +66,14 @@ def command_demo(args, config):
for step in range(args.time_steps):
if step % 100 == 0: print("step: ", step)
action, _states = model.predict(obs)
+ steer = action[0]
+ throttle = action[1]
obs, rewards, dones, info = agent.step(action)
+ steer2 = agent.action_history[-2]
+ throttle2 = agent.action_history[-1]
+ speed = info["speed"]
+ cte = info["cte"]
+ print(f'steer:{steer:9.5f} steer2:{steer2:9.5f} throttle:{throttle:9.5f} throttle2:{throttle2:9.5f} speed:{speed:9.5f} cte:{cte:9.5f}')
+ if dones :
+ obs = agent.reset()
+ agent.close()
diff --git a/learning_racer/racer.py b/learning_racer/racer.py
index 3e67ca2..dee0905 100644
--- a/learning_racer/racer.py
+++ b/learning_racer/racer.py
@@ -1,3 +1,7 @@
+import sys
+import os
+sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+
import argparse
from learning_racer.commands.subcommand import command_demo, command_train
from learning_racer.config import ConfigReader
@@ -8,6 +12,20 @@ logger = getLogger(__name__)
__version__ = '1.5.1'
+track_list = [
+ "donkey-generated-roads-v0",
+ "donkey-warehouse-v0",
+ "donkey-avc-sparkfun-v0",
+ "donkey-generated-track-v0",
+ "donkey-mountain-track-v0",
+ "donkey-roboracingleague-track-v0",
+ "donkey-waveshare-v0",
+ "donkey-minimonaco-track-v0",
+ "donkey-warren-track-v0",
+ "donkey-thunderhill-track-v0",
+ "donkey-circuit-launch-track-v0",
+]
+
parser = argparse.ArgumentParser(description='Learning Racer command.')
parser.add_argument('--version', action='version', version='learning_racer version {} .'.format(__version__))
subparser = parser.add_subparsers()
@@ -39,7 +57,7 @@ parser_train.add_argument('-host', '--sim-host', help='Define host IP of DonkeyS
parser_train.add_argument('-port', '--sim-port', help='Define port number of DonkeySim host.',
default='9091', type=int)
parser_train.add_argument('-track', '--sim-track', help='Define track name for DonkeySim',
- default='donkey-generated-track-v0', type=str)
+ default='donkey-generated-track-v0', type=str, choices=track_list)
parser_train.set_defaults(handler=command_train)
# demo subcommand.
@@ -63,7 +81,7 @@ parser_demo.add_argument('-host', '--sim-host', help='Define host IP of DonkeySi
parser_demo.add_argument('-port', '--sim-port', help='Define port number of DonkeySim host.',
default='9091', type=int)
parser_demo.add_argument('-track', '--sim-track', help='Define track name for DonkeySim',
- default='donkey-generated-track-v0', type=str)
+ default='donkey-generated-track-v0', type=str, choices=track_list)
parser_demo.add_argument('-user', '--sim-user', help='Define user name for own car that showed DonkeySim',
default='anonymous', type=str)
parser_demo.add_argument('-car', '--sim-car', help='Define car model type for own car that showed DonkeySim',
diff --git a/learning_racer/sac/custom_sac.py b/learning_racer/sac/custom_sac.py
index 734fd95..3642ca8 100644
--- a/learning_racer/sac/custom_sac.py
+++ b/learning_racer/sac/custom_sac.py
@@ -21,6 +21,7 @@ def _load_sac(agent, args, config, policy):
sde_sample_freq=config.sac_sde_sample_freq()
)
else:
+ print(f"**** load model{args.load_model} ****")
model = SAC.load(args.load_model, env=agent,
policy_kwargs=policy,
verbose=config.sac_verbose(),
@@ -31,7 +32,7 @@ def _load_sac(agent, args, config, policy):
ent_coef=config.sac_ent_coef(), learning_rate=config.sac_learning_rate(),
tensorboard_log="tblog", gamma=config.sac_gamma(), tau=config.sac_tau(),
use_sde_at_warmup=config.sac_use_sde_at_warmup(), use_sde=config.sac_use_sde(),
- sde_sample_freq=config.sac_sample_freq(), n_episodes_rollout=1)
+ sde_sample_freq=config.sac_sde_sample_freq(), n_episodes_rollout=1)
return model
diff --git a/learning_racer/sac/hyperparam.py b/learning_racer/sac/hyperparam.py
index e58b3fa..5b4b3f0 100644
--- a/learning_racer/sac/hyperparam.py
+++ b/learning_racer/sac/hyperparam.py
@@ -2,7 +2,6 @@ import math
from learning_racer.config.config import ConfigReader
-hit_counter = 0
speed_counter = 0
config = ConfigReader()
@@ -27,29 +26,32 @@ def reward_sim(self, done):
# For gym_donkey
-hit_counter = 0
speed_counter = 0
initial = False
def episode_over_sim(self):
- global hit_counter, speed_counter, initial
+ global speed_counter, initial
# print(self.speed)
if not initial and self.speed > 3.0:
initial = True
if self.hit != "none":
- hit_counter += 1
- if hit_counter > 5:
- self.over = True
- hit_counter = 0
+ self.over = True
+ initial = False
+ elif math.fabs(self.cte) > self.max_cte * 1.5:
+ self.over = True
+ initial = False
elif self.speed < 0.03 and initial:
speed_counter += 1
if speed_counter > 10:
self.over = True
speed_counter = 0
+ initial = False
elif self.missed_checkpoint:
self.over = True
+ initial = False
elif self.dq:
self.over = True
+ initial = False
[!NOTE] 主な変更内容は、
- config.yml
- MIN_THROTTLE の値修正(ちょっと速すぎな感じだったので)
- learning_racer/commands/subcommand.py
- simulatorのクローズ処理を追加
- demo時の状態表示を追加
- demo時のdonesステータスでsimulator初期化を追加
- learning_racer/racer.py
- pip installせずに実行できるよう、sys.pathを修正する部分の追加
- learning_racer/sac/custom_sac.py
- typo修正
- learning_racer/sac/hyperparam.py
- エピソード終了判定
episode_over_sim()
の変更
cd learning_racer/
wget "https://drive.google.com/uc?export=download&id=19r1yuwiRGGV-BjzjoCzwX8zmA8ZKFNcC" -O vae.torch
[!NOTE] このVAEの学習済みモデルは
donkey-generated-track-v0
用なので、 以下の実行ではこのコースを使用する(-track
オプションのデフォルト)。
[!NOTE]
donkey-waveshare-v0
は簡単なコースなので流用できるっぽい。
とりあえず学習。
リモートマシンでDonkeyCar シミュレータを実行しておき、以下のコマンドを実行します。
シミュレータ実行マシンのIPアドレスを--host
オプションに指定します。
python racer.py train -robot=sim -vae=./vae.torch -config=../config.yml -device=cpu -host=192.168.78.200
追加学習する場合は元のモデルファイルを-l
オプションで指定します。
学習に使用するステップ数を変更する場合は-steps
オプションで指定します(デフォルトは5000)。
その他詳細はソースを見てちょ。
学習のときのコマンドtrain
をdemo
に変更するだけです。
テストを実行するステップ数を変更したい場合は--steps
オプションで指定します。
python racer.py demo -robot=sim -vae=./vae.torch -config=../config.yml -device=cpu -host=192.168.78.200
途中で止めたい場合はCTRL-Cで止めてください。
のはやめとく。
おおざっぱに言うと、コースの画像を入力してその特徴を抽出するVAE(Variational Auto Encoder)と VAEの出力と過去の操作(steering/throttle)の履歴を入力に次の操作を決定するSAC(Soft-Actor-Critic)で構成されている。
VAEはあらかじめ大量のコース画像を撮影したデータで学習しておく(airc-rl-agent/notebooks/colabo/VAE_CNN.ipyn
)。
この学習はカメラ画像さえ用意できていれば実機(or シミュレータ)は不要なので、Google Colaboratoryなど高性能のマシンで一気に学習できる。
上記では参照元ページで用意されていた学習済みモデルを使用している。
VAEは車載カメラ画像(RGBのカラー画像)で160x120pixelにリサイズしたものの下部160x80pixelを入力としている。
出力は32個のデータ。
VAEは160x80x3(38400)の画素データを32の出力に圧縮するので、そのまま画素データを入力するより学習効率が上がるのかな??
ちゃんと検証してないけど、「右カーブ」とか「左カーブ」とか「直進」みたいな情報に集約されるのかな?
SACの入力はVAEの出力(32個)と過去の操作履歴(過去何回分かはconfig.yml
の AGENT_SETTING.N_COMMAND_HISTOR
で指定。
上記手順で使用した設定値は20なので、steeringとthrottleの2個 × 20 で40個)を使用。
出力はsteeringとthrottleの2個のデータ。
SACの出力はそのまま車の操作に使用するのではなく、
throttleはconfig.yml
の AGENT_SETTING.MIN_THROTTLE
とAGENT_SETTING.MAX_THROTTLE
で指定した範囲に変換。
steeringは前回の設定値との差がconfig.yml
の MAX_STEERING_DIFF
で指定した値を超えないように制限処理、
を行って使用する。