169 lines
5.4 KiB
Python
169 lines
5.4 KiB
Python
import unittest
|
|
|
|
import ray
|
|
import ray.rllib.algorithms.ppo as ppo
|
|
from ray.rllib.algorithms.ppo.ppo_learner import LEARNER_RESULTS_CURR_ENTROPY_COEFF_KEY
|
|
from ray.rllib.core import DEFAULT_MODULE_ID
|
|
from ray.rllib.core.learner.learner import DEFAULT_OPTIMIZER, LR_KEY
|
|
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
|
|
from ray.rllib.utils.metrics import LEARNER_RESULTS
|
|
from ray.rllib.utils.test_utils import check, check_train_results_new_api_stack
|
|
|
|
|
|
def get_model_config(lstm=False):
|
|
return (
|
|
dict(
|
|
use_lstm=True,
|
|
lstm_use_prev_action=True,
|
|
lstm_use_prev_reward=True,
|
|
lstm_cell_size=10,
|
|
max_seq_len=20,
|
|
)
|
|
if lstm
|
|
else {"use_lstm": False}
|
|
)
|
|
|
|
|
|
def on_train_result(algorithm, result: dict, **kwargs):
|
|
stats = result[LEARNER_RESULTS][DEFAULT_MODULE_ID]
|
|
# Entropy coeff goes to 0.05, then 0.0 (per iter).
|
|
check(
|
|
stats[LEARNER_RESULTS_CURR_ENTROPY_COEFF_KEY],
|
|
0.05 if algorithm.iteration == 1 else 0.0,
|
|
)
|
|
|
|
# Learning rate should decrease by 0.0001/4 per iteration.
|
|
check(
|
|
stats[DEFAULT_OPTIMIZER + "_" + LR_KEY],
|
|
0.0000075 if algorithm.iteration == 1 else 0.000005,
|
|
)
|
|
# Compare reported curr lr vs the actual lr found in the optimizer object.
|
|
optim = algorithm.learner_group._learner.get_optimizer()
|
|
actual_optimizer_lr = (
|
|
optim.param_groups[0]["lr"]
|
|
if algorithm.config.framework_str == "torch"
|
|
else optim.lr
|
|
)
|
|
check(stats[DEFAULT_OPTIMIZER + "_" + LR_KEY], actual_optimizer_lr)
|
|
|
|
|
|
class TestPPO(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
ray.init()
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
ray.shutdown()
|
|
|
|
def test_ppo_compilation_and_schedule_mixins(self):
|
|
"""Test whether PPO can be built with all frameworks."""
|
|
|
|
# Build a PPOConfig object with the `SingleAgentEnvRunner` class.
|
|
config = (
|
|
ppo.PPOConfig()
|
|
.env_runners(num_env_runners=0)
|
|
.training(
|
|
num_epochs=2,
|
|
# Setup lr schedule for testing lr-scheduling correctness.
|
|
lr=[[0, 0.00001], [512, 0.0]], # 512=4x128
|
|
# Setup `entropy_coeff` schedule for testing whether it's scheduled
|
|
# correctly.
|
|
entropy_coeff=[[0, 0.1], [256, 0.0]], # 256=2x128,
|
|
train_batch_size=128,
|
|
)
|
|
.callbacks(on_train_result=on_train_result)
|
|
.evaluation(
|
|
# Also test evaluation with remote workers.
|
|
evaluation_num_env_runners=2,
|
|
evaluation_duration=3,
|
|
evaluation_duration_unit="episodes",
|
|
evaluation_parallel_to_training=True,
|
|
)
|
|
)
|
|
|
|
num_iterations = 2
|
|
|
|
for env in [
|
|
"CartPole-v1",
|
|
"Pendulum-v1",
|
|
]:
|
|
print("Env={}".format(env))
|
|
for lstm in [False]:
|
|
print("LSTM={}".format(lstm))
|
|
config.rl_module(model_config=get_model_config(lstm=lstm))
|
|
|
|
algo = config.build(env=env)
|
|
# TODO: Maybe add an API to get the Learner(s) instances within
|
|
# a learner group, remote or not.
|
|
learner = algo.learner_group._learner
|
|
optim = learner.get_optimizer()
|
|
# Check initial LR directly set in optimizer vs the first (ts=0)
|
|
# value from the schedule.
|
|
lr = optim.param_groups[0]["lr"]
|
|
check(lr, config.lr[0][1])
|
|
|
|
# Check current entropy coeff value using the respective Scheduler.
|
|
entropy_coeff = learner.entropy_coeff_schedulers_per_module[
|
|
DEFAULT_MODULE_ID
|
|
].get_current_value()
|
|
check(entropy_coeff, 0.1)
|
|
|
|
for i in range(num_iterations):
|
|
results = algo.train()
|
|
check_train_results_new_api_stack(results)
|
|
print(results)
|
|
|
|
# algo.evaluate()
|
|
algo.stop()
|
|
|
|
def test_ppo_free_log_std(self):
|
|
"""Tests the free log std option works."""
|
|
config = (
|
|
ppo.PPOConfig()
|
|
.environment("Pendulum-v1")
|
|
.env_runners(
|
|
num_env_runners=1,
|
|
)
|
|
.rl_module(
|
|
model_config=DefaultModelConfig(
|
|
fcnet_hiddens=[10],
|
|
fcnet_activation="linear",
|
|
free_log_std=True,
|
|
vf_share_layers=True,
|
|
),
|
|
)
|
|
.training(
|
|
gamma=0.99,
|
|
)
|
|
)
|
|
|
|
algo = config.build()
|
|
module = algo.get_module(DEFAULT_MODULE_ID)
|
|
|
|
# Check the free log std var is created.
|
|
matching = [v for (n, v) in module.named_parameters() if "log_std" in n]
|
|
assert len(matching) == 1, matching
|
|
log_std_var = matching[0]
|
|
|
|
def get_value(log_std_var=log_std_var):
|
|
return log_std_var.detach().cpu().numpy()[0]
|
|
|
|
# Check the variable is initially zero.
|
|
init_std = get_value()
|
|
assert init_std == 0.0, init_std
|
|
algo.train()
|
|
|
|
# Check the variable is updated.
|
|
post_std = get_value()
|
|
assert post_std != 0.0, post_std
|
|
algo.stop()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
sys.exit(pytest.main(["-v", __file__]))
|