155 lines
5.8 KiB
Python
155 lines
5.8 KiB
Python
import unittest
|
|
|
|
import numpy as np
|
|
from gymnasium.spaces import Box, Discrete
|
|
|
|
from ray.rllib.algorithms.impala.tests.test_vtrace_old_api_stack import (
|
|
_ground_truth_vtrace_calculation,
|
|
)
|
|
from ray.rllib.algorithms.impala.torch.vtrace_torch_v2 import (
|
|
make_time_major,
|
|
vtrace_torch,
|
|
)
|
|
from ray.rllib.core.distribution.torch.torch_distribution import TorchCategorical
|
|
from ray.rllib.utils.framework import try_import_torch
|
|
from ray.rllib.utils.test_utils import check
|
|
from ray.rllib.utils.torch_utils import convert_to_torch_tensor
|
|
|
|
torch, _ = try_import_torch()
|
|
|
|
|
|
def flatten_batch_and_time_dim(t):
|
|
if not torch.is_tensor(t):
|
|
t = torch.from_numpy(t)
|
|
new_shape = [-1] + list(t.shape[2:])
|
|
return torch.reshape(t, new_shape)
|
|
|
|
|
|
class TestVtraceRLModule(unittest.TestCase):
|
|
"""Tests V-trace-v2 against ground truth data calculated.
|
|
|
|
There is a ground truth implementation that we used to test our original
|
|
implementation against. This test checks that the new implementation still
|
|
matches the ground truth test from our first implementation of V-Trace.
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
"""Sets up inputs for V-Trace and calculate ground truth.
|
|
|
|
We use tf operations here to compile the inputs but convert to numpy arrays to
|
|
calculate the ground truth (and the other v-trace outputs in the
|
|
framework-specific tests).
|
|
"""
|
|
|
|
# we can test against any trajectory length or batch size and it won't matter
|
|
trajectory_len = 5
|
|
batch_size = 10
|
|
|
|
action_space = Discrete(10)
|
|
action_logit_space = Box(-1.0, 1.0, (action_space.n,), np.float32)
|
|
behavior_action_logits = torch.from_numpy(
|
|
np.array([action_logit_space.sample()], dtype=np.float32)
|
|
)
|
|
target_action_logits = torch.from_numpy(
|
|
np.array([action_logit_space.sample()], dtype=np.float32)
|
|
)
|
|
behavior_dist = TorchCategorical(logits=behavior_action_logits)
|
|
target_dist = TorchCategorical(logits=target_action_logits)
|
|
dummy_action_batch = [
|
|
[action_space.sample() for _ in range(trajectory_len)]
|
|
for _ in range(batch_size)
|
|
]
|
|
|
|
behavior_log_probs = torch.stack(
|
|
[
|
|
torch.squeeze(
|
|
behavior_dist.logp(torch.from_numpy(np.array(v, np.uint8)))
|
|
)
|
|
for v in dummy_action_batch
|
|
]
|
|
)
|
|
target_log_probs = torch.stack(
|
|
[
|
|
torch.squeeze(target_dist.logp(torch.from_numpy(np.array(v, np.uint8))))
|
|
for v in dummy_action_batch
|
|
]
|
|
)
|
|
# target_log_probs = torch.stack(
|
|
# tree.map_structure(
|
|
# lambda v: torch.squeeze(target_dist.logp(v)), dummy_action_batch
|
|
# )
|
|
# )
|
|
|
|
value_fn_space_w_time = Box(-1.0, 1.0, (batch_size, trajectory_len), np.float32)
|
|
value_fn_space = Box(-1.0, 1.0, (batch_size,), np.float32)
|
|
|
|
# using randomly sampled values in lieu of actual values sampled from a value fn
|
|
values = value_fn_space_w_time.sample()
|
|
# this is supposed to be the value function at the last timestep of each
|
|
# trajectory in the batch. In IMPALA its bootstrapped at training time
|
|
cls.bootstrap_values = np.array(value_fn_space.sample() + 1.0)
|
|
|
|
# discount factor used at all of the timesteps
|
|
discounts = torch.from_numpy(
|
|
np.array([0.9 for _ in range(trajectory_len * batch_size)])
|
|
)
|
|
rewards = value_fn_space_w_time.sample()
|
|
cls.clip_rho_threshold = 3.7
|
|
cls.clip_pg_rho_threshold = 2.2
|
|
|
|
# convert to time major dimension
|
|
cls.behavior_log_probs_time_major = make_time_major(
|
|
flatten_batch_and_time_dim(behavior_log_probs),
|
|
trajectory_len=trajectory_len,
|
|
).numpy()
|
|
cls.target_log_probs_time_major = make_time_major(
|
|
flatten_batch_and_time_dim(target_log_probs), trajectory_len=trajectory_len
|
|
).numpy()
|
|
cls.discounts_time_major = make_time_major(
|
|
flatten_batch_and_time_dim(discounts), trajectory_len=trajectory_len
|
|
).numpy()
|
|
cls.rewards_time_major = make_time_major(
|
|
flatten_batch_and_time_dim(rewards), trajectory_len=trajectory_len
|
|
).numpy()
|
|
cls.values_time_major = make_time_major(
|
|
flatten_batch_and_time_dim(values), trajectory_len=trajectory_len
|
|
).numpy()
|
|
|
|
log_rhos = cls.target_log_probs_time_major - cls.behavior_log_probs_time_major
|
|
|
|
cls.ground_truth_v = _ground_truth_vtrace_calculation(
|
|
discounts=cls.discounts_time_major,
|
|
log_rhos=log_rhos,
|
|
rewards=cls.rewards_time_major,
|
|
values=cls.values_time_major,
|
|
bootstrap_value=cls.bootstrap_values,
|
|
clip_rho_threshold=cls.clip_rho_threshold,
|
|
clip_pg_rho_threshold=cls.clip_pg_rho_threshold,
|
|
)
|
|
|
|
def test_vtrace_torch(self):
|
|
output_torch_vtrace = vtrace_torch(
|
|
behaviour_action_log_probs=convert_to_torch_tensor(
|
|
self.behavior_log_probs_time_major
|
|
),
|
|
target_action_log_probs=convert_to_torch_tensor(
|
|
self.target_log_probs_time_major
|
|
),
|
|
discounts=convert_to_torch_tensor(self.discounts_time_major),
|
|
rewards=convert_to_torch_tensor(self.rewards_time_major),
|
|
values=convert_to_torch_tensor(self.values_time_major),
|
|
bootstrap_values=convert_to_torch_tensor(self.bootstrap_values),
|
|
clip_rho_threshold=self.clip_rho_threshold,
|
|
clip_pg_rho_threshold=self.clip_pg_rho_threshold,
|
|
)
|
|
check(output_torch_vtrace, self.ground_truth_v)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
sys.exit(pytest.main(["-v", __file__]))
|