Files
2026-07-13 13:17:40 +08:00

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__]))