2023-06-15 09:48:44 +01:00
|
|
|
from typing import Optional
|
|
|
|
|
|
2023-06-19 20:27:08 +01:00
|
|
|
import numpy as np
|
2023-06-19 21:53:25 +01:00
|
|
|
from stable_baselines3 import PPO, A2C
|
2023-06-20 16:06:55 +01:00
|
|
|
from stable_baselines3.ppo import MlpPolicy as PPOMlp
|
2023-06-15 09:48:44 +01:00
|
|
|
|
2023-06-19 21:53:25 +01:00
|
|
|
from primaite import getLogger
|
2023-06-19 20:27:08 +01:00
|
|
|
from primaite.agents.agent import AgentSessionABC
|
2023-06-20 16:06:55 +01:00
|
|
|
from primaite.common.enums import AgentIdentifier, AgentFramework
|
2023-06-15 09:48:44 +01:00
|
|
|
from primaite.environment.primaite_env import Primaite
|
|
|
|
|
|
2023-06-19 21:53:25 +01:00
|
|
|
_LOGGER = getLogger(__name__)
|
2023-06-18 22:40:56 +01:00
|
|
|
|
2023-06-19 21:53:25 +01:00
|
|
|
|
|
|
|
|
class SB3Agent(AgentSessionABC):
|
2023-06-18 22:40:56 +01:00
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
training_config_path,
|
|
|
|
|
lay_down_config_path
|
|
|
|
|
):
|
|
|
|
|
super().__init__(training_config_path, lay_down_config_path)
|
2023-06-19 21:53:25 +01:00
|
|
|
if not self._training_config.agent_framework == AgentFramework.SB3:
|
|
|
|
|
msg = (f"Expected SB3 agent_framework, "
|
|
|
|
|
f"got {self._training_config.agent_framework}")
|
|
|
|
|
_LOGGER.error(msg)
|
|
|
|
|
raise ValueError(msg)
|
2023-06-20 16:06:55 +01:00
|
|
|
if self._training_config.agent_identifier == AgentIdentifier.PPO:
|
2023-06-19 21:53:25 +01:00
|
|
|
self._agent_class = PPO
|
2023-06-20 16:06:55 +01:00
|
|
|
elif self._training_config.agent_identifier == AgentIdentifier.A2C:
|
2023-06-19 21:53:25 +01:00
|
|
|
self._agent_class = A2C
|
|
|
|
|
else:
|
2023-06-20 16:06:55 +01:00
|
|
|
msg = ("Expected PPO or A2C agent_identifier, "
|
|
|
|
|
f"got {self._training_config.agent_identifier.value}")
|
2023-06-19 21:53:25 +01:00
|
|
|
_LOGGER.error(msg)
|
|
|
|
|
raise ValueError(msg)
|
|
|
|
|
|
2023-06-28 16:34:00 +01:00
|
|
|
self._tensorboard_log_path = self.learning_path / "tensorboard_logs"
|
2023-06-18 22:40:56 +01:00
|
|
|
self._tensorboard_log_path.mkdir(parents=True, exist_ok=True)
|
2023-06-19 20:27:08 +01:00
|
|
|
self._setup()
|
2023-06-19 21:53:25 +01:00
|
|
|
_LOGGER.debug(
|
|
|
|
|
f"Created {self.__class__.__name__} using: "
|
|
|
|
|
f"agent_framework={self._training_config.agent_framework}, "
|
2023-06-20 16:06:55 +01:00
|
|
|
f"agent_identifier="
|
|
|
|
|
f"{self._training_config.agent_identifier}"
|
2023-06-19 21:53:25 +01:00
|
|
|
)
|
2023-06-15 09:48:44 +01:00
|
|
|
|
|
|
|
|
def _setup(self):
|
2023-06-19 20:27:08 +01:00
|
|
|
super()._setup()
|
2023-06-18 22:40:56 +01:00
|
|
|
self._env = Primaite(
|
|
|
|
|
training_config_path=self._training_config_path,
|
|
|
|
|
lay_down_config_path=self._lay_down_config_path,
|
|
|
|
|
session_path=self.session_path,
|
|
|
|
|
timestamp_str=self.timestamp_str
|
|
|
|
|
)
|
2023-06-19 21:53:25 +01:00
|
|
|
self._agent = self._agent_class(
|
2023-06-15 09:48:44 +01:00
|
|
|
PPOMlp,
|
|
|
|
|
self._env,
|
2023-06-20 16:06:55 +01:00
|
|
|
verbose=self.output_verbose_level,
|
2023-06-18 22:40:56 +01:00
|
|
|
n_steps=self._training_config.num_steps,
|
|
|
|
|
tensorboard_log=self._tensorboard_log_path
|
2023-06-15 09:48:44 +01:00
|
|
|
)
|
|
|
|
|
|
2023-06-19 20:27:08 +01:00
|
|
|
def _save_checkpoint(self):
|
|
|
|
|
checkpoint_n = self._training_config.checkpoint_every_n_episodes
|
|
|
|
|
episode_count = self._env.episode_count
|
|
|
|
|
if checkpoint_n > 0 and episode_count > 0:
|
|
|
|
|
if (
|
|
|
|
|
(episode_count % checkpoint_n == 0)
|
|
|
|
|
or (episode_count == self._training_config.num_episodes)
|
|
|
|
|
):
|
2023-06-28 12:01:01 +01:00
|
|
|
checkpoint_path = self.checkpoints_path / f"sb3ppo_{episode_count}.zip"
|
|
|
|
|
self._agent.save(checkpoint_path)
|
|
|
|
|
_LOGGER.debug(f"Saved agent checkpoint: {checkpoint_path}")
|
2023-06-19 20:27:08 +01:00
|
|
|
|
|
|
|
|
def _get_latest_checkpoint(self):
|
|
|
|
|
pass
|
|
|
|
|
|
2023-06-18 22:40:56 +01:00
|
|
|
def learn(
|
|
|
|
|
self,
|
|
|
|
|
time_steps: Optional[int] = None,
|
2023-06-19 20:27:08 +01:00
|
|
|
episodes: Optional[int] = None,
|
|
|
|
|
**kwargs
|
2023-06-18 22:40:56 +01:00
|
|
|
):
|
|
|
|
|
if not time_steps:
|
|
|
|
|
time_steps = self._training_config.num_steps
|
|
|
|
|
|
|
|
|
|
if not episodes:
|
|
|
|
|
episodes = self._training_config.num_episodes
|
2023-06-28 12:01:01 +01:00
|
|
|
_LOGGER.info(f"Beginning learning for {episodes} episodes @"
|
|
|
|
|
f" {time_steps} time steps...")
|
2023-06-18 22:40:56 +01:00
|
|
|
for i in range(episodes):
|
|
|
|
|
self._agent.learn(total_timesteps=time_steps)
|
2023-06-19 20:27:08 +01:00
|
|
|
self._save_checkpoint()
|
2023-06-20 22:29:46 +01:00
|
|
|
|
2023-06-19 21:53:25 +01:00
|
|
|
self._env.close()
|
2023-06-19 20:27:08 +01:00
|
|
|
super().learn()
|
2023-06-18 22:40:56 +01:00
|
|
|
|
|
|
|
|
def evaluate(
|
|
|
|
|
self,
|
|
|
|
|
time_steps: Optional[int] = None,
|
|
|
|
|
episodes: Optional[int] = None,
|
2023-06-19 20:27:08 +01:00
|
|
|
deterministic: bool = True,
|
|
|
|
|
**kwargs
|
2023-06-18 22:40:56 +01:00
|
|
|
):
|
|
|
|
|
if not time_steps:
|
|
|
|
|
time_steps = self._training_config.num_steps
|
|
|
|
|
|
|
|
|
|
if not episodes:
|
|
|
|
|
episodes = self._training_config.num_episodes
|
2023-06-28 16:34:00 +01:00
|
|
|
self._env.set_as_eval()
|
|
|
|
|
if deterministic:
|
|
|
|
|
deterministic_str = "deterministic"
|
|
|
|
|
else:
|
|
|
|
|
deterministic_str = "non-deterministic"
|
|
|
|
|
_LOGGER.info(f"Beginning {deterministic_str} evaluation for "
|
|
|
|
|
f"{episodes} episodes @ {time_steps} time steps...")
|
2023-06-18 22:40:56 +01:00
|
|
|
for episode in range(episodes):
|
|
|
|
|
obs = self._env.reset()
|
2023-06-15 09:48:44 +01:00
|
|
|
|
2023-06-18 22:40:56 +01:00
|
|
|
for step in range(time_steps):
|
|
|
|
|
action, _states = self._agent.predict(
|
|
|
|
|
obs,
|
|
|
|
|
deterministic=deterministic
|
|
|
|
|
)
|
2023-06-19 20:27:08 +01:00
|
|
|
if isinstance(action, np.ndarray):
|
|
|
|
|
action = np.int64(action)
|
2023-06-18 22:40:56 +01:00
|
|
|
obs, rewards, done, info = self._env.step(action)
|
2023-06-28 16:34:00 +01:00
|
|
|
_LOGGER.info(f"Finished evaluation")
|
2023-06-15 09:48:44 +01:00
|
|
|
|
2023-06-20 16:06:55 +01:00
|
|
|
@classmethod
|
2023-06-15 09:48:44 +01:00
|
|
|
def load(self):
|
2023-06-18 22:40:56 +01:00
|
|
|
raise NotImplementedError
|
2023-06-15 09:48:44 +01:00
|
|
|
|
|
|
|
|
def save(self):
|
2023-06-18 22:40:56 +01:00
|
|
|
raise NotImplementedError
|
2023-06-15 09:48:44 +01:00
|
|
|
|
|
|
|
|
def export(self):
|
2023-06-18 22:40:56 +01:00
|
|
|
raise NotImplementedError
|