#917 - Overhauled transaction and mean reward writing.

- Separated out learning outputs from evaluation outputs
This commit is contained in:
Chris McCarthy
2023-06-28 16:34:00 +01:00
parent 7482192046
commit 1d3778f400
13 changed files with 258 additions and 250 deletions

View File

@@ -21,6 +21,7 @@ _PLATFORM_DIRS: Final[PlatformDirs] = PlatformDirs(appname="primaite")
def _get_primaite_config(): def _get_primaite_config():
config_path = _PLATFORM_DIRS.user_config_path / "primaite_config.yaml" config_path = _PLATFORM_DIRS.user_config_path / "primaite_config.yaml"
if not config_path.exists(): if not config_path.exists():
config_path = Path( config_path = Path(
pkg_resources.resource_filename( pkg_resources.resource_filename(
"primaite", "setup/_package_data/primaite_config.yaml" "primaite", "setup/_package_data/primaite_config.yaml"
@@ -36,7 +37,7 @@ def _get_primaite_config():
"ERROR": logging.ERROR, "ERROR": logging.ERROR,
"CRITICAL": logging.CRITICAL, "CRITICAL": logging.CRITICAL,
} }
primaite_config["log_level"] = log_level_map[primaite_config["log_level"]] primaite_config["log_level"] = log_level_map[primaite_config["logging"]["log_level"]]
return primaite_config return primaite_config
@@ -108,11 +109,11 @@ def _log_dir() -> Path:
_LEVEL_FORMATTER: Final[_LevelFormatter] = _LevelFormatter( _LEVEL_FORMATTER: Final[_LevelFormatter] = _LevelFormatter(
{ {
logging.DEBUG: _PRIMAITE_CONFIG["logger_format"]["DEBUG"], logging.DEBUG: _PRIMAITE_CONFIG["logging"]["logger_format"]["DEBUG"],
logging.INFO: _PRIMAITE_CONFIG["logger_format"]["INFO"], logging.INFO: _PRIMAITE_CONFIG["logging"]["logger_format"]["INFO"],
logging.WARNING: _PRIMAITE_CONFIG["logger_format"]["WARNING"], logging.WARNING: _PRIMAITE_CONFIG["logging"]["logger_format"]["WARNING"],
logging.ERROR: _PRIMAITE_CONFIG["logger_format"]["ERROR"], logging.ERROR: _PRIMAITE_CONFIG["logging"]["logger_format"]["ERROR"],
logging.CRITICAL: _PRIMAITE_CONFIG["logger_format"]["CRITICAL"] logging.CRITICAL: _PRIMAITE_CONFIG["logging"]["logger_format"]["CRITICAL"]
} }
) )
@@ -132,10 +133,10 @@ _FILE_HANDLER: Final[RotatingFileHandler] = RotatingFileHandler(
backupCount=9, # Max 100MB of logs backupCount=9, # Max 100MB of logs
encoding="utf8", encoding="utf8",
) )
_STREAM_HANDLER.setLevel(_PRIMAITE_CONFIG["log_level"]) _STREAM_HANDLER.setLevel(_PRIMAITE_CONFIG["logging"]["log_level"])
_FILE_HANDLER.setLevel(_PRIMAITE_CONFIG["log_level"]) _FILE_HANDLER.setLevel(_PRIMAITE_CONFIG["logging"]["log_level"])
_LOG_FORMAT_STR: Final[str] = _PRIMAITE_CONFIG["logger_format"] _LOG_FORMAT_STR: Final[str] = _PRIMAITE_CONFIG["logging"]["logger_format"]
_STREAM_HANDLER.setFormatter(_LEVEL_FORMATTER) _STREAM_HANDLER.setFormatter(_LEVEL_FORMATTER)
_FILE_HANDLER.setFormatter(_LEVEL_FORMATTER) _FILE_HANDLER.setFormatter(_LEVEL_FORMATTER)
@@ -145,7 +146,7 @@ _LOGGER.addHandler(_STREAM_HANDLER)
_LOGGER.addHandler(_FILE_HANDLER) _LOGGER.addHandler(_FILE_HANDLER)
def getLogger(name: str) -> Logger: def getLogger(name: str) -> Logger: # noqa
""" """
Get a PrimAITE logger. Get a PrimAITE logger.

View File

@@ -64,7 +64,11 @@ class AgentSessionABC(ABC):
"The session timestamp" "The session timestamp"
self.session_path = _get_session_path(self.session_timestamp) self.session_path = _get_session_path(self.session_timestamp)
"The Session path" "The Session path"
self.checkpoints_path = self.session_path / "checkpoints" self.learning_path = self.session_path / "learning"
"The learning outputs path"
self.evaluation_path = self.session_path / "evaluation"
"The evaluation outputs path"
self.checkpoints_path = self.learning_path / "checkpoints"
self.checkpoints_path.mkdir(parents=True, exist_ok=True) self.checkpoints_path.mkdir(parents=True, exist_ok=True)
"The Session checkpoints path" "The Session checkpoints path"
@@ -205,7 +209,6 @@ class HardCodedAgentSessionABC(AgentSessionABC):
self._env: Primaite = Primaite( self._env: Primaite = Primaite(
training_config_path=self._training_config_path, training_config_path=self._training_config_path,
lay_down_config_path=self._lay_down_config_path, lay_down_config_path=self._lay_down_config_path,
transaction_list=[],
session_path=self.session_path, session_path=self.session_path,
timestamp_str=self.timestamp_str timestamp_str=self.timestamp_str
) )

View File

@@ -21,7 +21,6 @@ def _env_creator(env_config):
return Primaite( return Primaite(
training_config_path=env_config["training_config_path"], training_config_path=env_config["training_config_path"],
lay_down_config_path=env_config["lay_down_config_path"], lay_down_config_path=env_config["lay_down_config_path"],
transaction_list=env_config["transaction_list"],
session_path=env_config["session_path"], session_path=env_config["session_path"],
timestamp_str=env_config["timestamp_str"] timestamp_str=env_config["timestamp_str"]
) )
@@ -106,7 +105,6 @@ class RLlibAgent(AgentSessionABC):
env_config=dict( env_config=dict(
training_config_path=self._training_config_path, training_config_path=self._training_config_path,
lay_down_config_path=self._lay_down_config_path, lay_down_config_path=self._lay_down_config_path,
transaction_list=[],
session_path=self.session_path, session_path=self.session_path,
timestamp_str=self.timestamp_str timestamp_str=self.timestamp_str
) )

View File

@@ -34,7 +34,7 @@ class SB3Agent(AgentSessionABC):
_LOGGER.error(msg) _LOGGER.error(msg)
raise ValueError(msg) raise ValueError(msg)
self._tensorboard_log_path = self.session_path / "tensorboard_logs" self._tensorboard_log_path = self.learning_path / "tensorboard_logs"
self._tensorboard_log_path.mkdir(parents=True, exist_ok=True) self._tensorboard_log_path.mkdir(parents=True, exist_ok=True)
self._setup() self._setup()
_LOGGER.debug( _LOGGER.debug(
@@ -49,7 +49,6 @@ class SB3Agent(AgentSessionABC):
self._env = Primaite( self._env = Primaite(
training_config_path=self._training_config_path, training_config_path=self._training_config_path,
lay_down_config_path=self._lay_down_config_path, lay_down_config_path=self._lay_down_config_path,
transaction_list=[],
session_path=self.session_path, session_path=self.session_path,
timestamp_str=self.timestamp_str timestamp_str=self.timestamp_str
) )
@@ -108,10 +107,13 @@ class SB3Agent(AgentSessionABC):
if not episodes: if not episodes:
episodes = self._training_config.num_episodes episodes = self._training_config.num_episodes
self._env.set_as_eval()
_LOGGER.info(f"Beginning evaluation for {episodes} episodes @" if deterministic:
f" {time_steps} time steps...") deterministic_str = "deterministic"
else:
deterministic_str = "non-deterministic"
_LOGGER.info(f"Beginning {deterministic_str} evaluation for "
f"{episodes} episodes @ {time_steps} time steps...")
for episode in range(episodes): for episode in range(episodes):
obs = self._env.reset() obs = self._env.reset()
@@ -123,6 +125,7 @@ class SB3Agent(AgentSessionABC):
if isinstance(action, np.ndarray): if isinstance(action, np.ndarray):
action = np.int64(action) action = np.int64(action)
obs, rewards, done, info = self._env.step(action) obs, rewards, done, info = self._env.step(action)
_LOGGER.info(f"Finished evaluation")
@classmethod @classmethod
def load(self): def load(self):

View File

@@ -38,7 +38,7 @@ hard_coded_agent_view: FULL
action_type: ANY action_type: ANY
# Number of episodes to run per session # Number of episodes to run per session
num_episodes: 10 num_episodes: 1000
# Number of time_steps per episode # Number of time_steps per episode
num_steps: 256 num_steps: 256

View File

@@ -1,10 +1,6 @@
# Crown Copyright (C) Dstl 2022. DEFCON 703. Shared in confidence. # Crown Copyright (C) Dstl 2022. DEFCON 703. Shared in confidence.
"""Main environment module containing the PRIMmary AI Training Evironment (Primaite) class.""" """Main environment module containing the PRIMmary AI Training Evironment (Primaite) class."""
import copy import copy
import csv
import logging
import time
from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Dict, Tuple, Union, Final from typing import Dict, Tuple, Union, Final
@@ -14,6 +10,7 @@ import yaml
from gym import Env, spaces from gym import Env, spaces
from matplotlib import pyplot as plt from matplotlib import pyplot as plt
from primaite import getLogger
from primaite.acl.access_control_list import AccessControlList from primaite.acl.access_control_list import AccessControlList
from primaite.agents.utils import is_valid_acl_action_extra, \ from primaite.agents.utils import is_valid_acl_action_extra, \
is_valid_node_action is_valid_node_action
@@ -27,7 +24,7 @@ from primaite.common.enums import (
NodeType, NodeType,
ObservationType, ObservationType,
Priority, Priority,
SoftwareState, SoftwareState, SessionType,
) )
from primaite.common.service import Service from primaite.common.service import Service
from primaite.config import training_config from primaite.config import training_config
@@ -47,11 +44,9 @@ from primaite.pol.ier import IER
from primaite.pol.red_agent_pol import apply_red_agent_iers, \ from primaite.pol.red_agent_pol import apply_red_agent_iers, \
apply_red_agent_node_pol apply_red_agent_node_pol
from primaite.transactions.transaction import Transaction from primaite.transactions.transaction import Transaction
from primaite.transactions.transactions_to_file import \ from primaite.utils.session_output_writer import SessionOutputWriter
write_transaction_to_file
_LOGGER = logging.getLogger(__name__) _LOGGER = getLogger(__name__)
_LOGGER.setLevel(logging.INFO)
class Primaite(Env): class Primaite(Env):
@@ -67,7 +62,6 @@ class Primaite(Env):
self, self,
training_config_path: Union[str, Path], training_config_path: Union[str, Path],
lay_down_config_path: Union[str, Path], lay_down_config_path: Union[str, Path],
transaction_list,
session_path: Path, session_path: Path,
timestamp_str: str, timestamp_str: str,
): ):
@@ -76,7 +70,6 @@ class Primaite(Env):
:param training_config_path: The training config filepath. :param training_config_path: The training config filepath.
:param lay_down_config_path: The lay down config filepath. :param lay_down_config_path: The lay down config filepath.
:param transaction_list: The list of transactions to populate.
:param session_path: The directory path the session is writing to. :param session_path: The directory path the session is writing to.
:param timestamp_str: The session timestamp in the format: :param timestamp_str: The session timestamp in the format:
<yyyy-mm-dd>_<hh-mm-ss>. <yyyy-mm-dd>_<hh-mm-ss>.
@@ -96,9 +89,6 @@ class Primaite(Env):
super(Primaite, self).__init__() super(Primaite, self).__init__()
# Transaction list
self.transaction_list = transaction_list
# The agent in use # The agent in use
self.agent_identifier = self.training_config.agent_identifier self.agent_identifier = self.training_config.agent_identifier
@@ -245,20 +235,31 @@ class Primaite(Env):
_LOGGER.error( _LOGGER.error(
f"Invalid action type selected: {self.training_config.action_type}" f"Invalid action type selected: {self.training_config.action_type}"
) )
# Set up a csv to store the results of the training
try:
header = ["Episode", "Average Reward"]
file_name = f"average_reward_per_episode_{timestamp_str}.csv" self.episode_av_reward_writer = SessionOutputWriter(
file_path = session_path / file_name self,
self.csv_file = open(file_path, "w", encoding="UTF8", newline="") transaction_writer=False,
self.csv_writer = csv.writer(self.csv_file) learning_session=True
self.csv_writer.writerow(header)
except Exception:
_LOGGER.error(
"Could not create csv file to hold average reward per episode"
) )
_LOGGER.error("Exception occured", exc_info=True) self.transaction_writer = SessionOutputWriter(
self,
transaction_writer=True,
learning_session=True
)
def set_as_eval(self):
"""Set the writers to write to eval directories."""
self.episode_av_reward_writer = SessionOutputWriter(
self,
transaction_writer=False,
learning_session=False
)
self.transaction_writer = SessionOutputWriter(
self,
transaction_writer=True,
learning_session=False
)
self.episode_count = 0
def reset(self): def reset(self):
""" """
@@ -267,12 +268,14 @@ class Primaite(Env):
Returns: Returns:
Environment observation space (reset) Environment observation space (reset)
""" """
if self.episode_count > 0:
csv_data = self.episode_count, self.average_reward csv_data = self.episode_count, self.average_reward
self.csv_writer.writerow(csv_data) self.episode_av_reward_writer.write(csv_data)
self.episode_count += 1 self.episode_count += 1
# Don't need to reset links, as they are cleared and recalculated every step # Don't need to reset links, as they are cleared and recalculated every
# step
# Clear the ACL # Clear the ACL
self.init_acl() self.init_acl()
@@ -303,12 +306,8 @@ class Primaite(Env):
done: Indicates episode is complete if True done: Indicates episode is complete if True
step_info: Additional information relating to this step step_info: Additional information relating to this step
""" """
if self.step_count == 0:
_LOGGER.info(f"Episode: {str(self.episode_count)}")
# TEMP # TEMP
done = False done = False
self.step_count += 1 self.step_count += 1
self.total_step_count += 1 self.total_step_count += 1
@@ -321,13 +320,16 @@ class Primaite(Env):
# Create a Transaction (metric) object for this step # Create a Transaction (metric) object for this step
transaction = Transaction( transaction = Transaction(
datetime.now(), self.agent_identifier, self.episode_count, self.agent_identifier,
self.episode_count,
self.step_count self.step_count
) )
# Load the initial observation space into the transaction # Load the initial observation space into the transaction
transaction.set_obs_space_pre(copy.deepcopy(self.env_obs)) transaction.obs_space_pre = copy.deepcopy(self.env_obs)
# Load the action space into the transaction # Load the action space into the transaction
transaction.set_action_space(copy.deepcopy(action)) transaction.action_space = copy.deepcopy(action)
initial_nodes = copy.deepcopy(self.nodes)
# 1. Implement Blue Action # 1. Implement Blue Action
self.interpret_action_and_apply(action) self.interpret_action_and_apply(action)
@@ -381,7 +383,7 @@ class Primaite(Env):
# 5. Calculate reward signal (for RL) # 5. Calculate reward signal (for RL)
reward = calculate_reward_function( reward = calculate_reward_function(
self.nodes_post_pol, initial_nodes,
self.nodes_post_red, self.nodes_post_red,
self.nodes_reference, self.nodes_reference,
self.green_iers, self.green_iers,
@@ -390,17 +392,22 @@ class Primaite(Env):
self.step_count, self.step_count,
self.training_config, self.training_config,
) )
_LOGGER.debug(f" Step {self.step_count} Reward: {str(reward)}") _LOGGER.debug(
f"Episode: {self.episode_count}, "
f"Step {self.step_count}, "
f"Reward: {reward}"
)
self.total_reward += reward self.total_reward += reward
if self.step_count == self.episode_steps: if self.step_count == self.episode_steps:
self.average_reward = self.total_reward / self.step_count self.average_reward = self.total_reward / self.step_count
if self.training_config.session_type == "EVALUATION": if self.training_config.session_type is SessionType.EVAL:
# For evaluation, need to trigger the done value = True when # For evaluation, need to trigger the done value = True when
# step count is reached in order to prevent neverending episode # step count is reached in order to prevent neverending episode
done = True done = True
_LOGGER.info(f" Average Reward: {str(self.average_reward)}") _LOGGER.info(f"Episode: {self.episode_count}, "
f"Average Reward: {self.average_reward}")
# Load the reward into the transaction # Load the reward into the transaction
transaction.set_reward(reward) transaction.reward = reward
# 6. Output Verbose # 6. Output Verbose
# self.output_link_status() # self.output_link_status()
@@ -408,28 +415,14 @@ class Primaite(Env):
# 7. Update env_obs # 7. Update env_obs
self.update_environent_obs() self.update_environent_obs()
# Load the new observation space into the transaction # Load the new observation space into the transaction
transaction.set_obs_space_post(copy.deepcopy(self.env_obs)) transaction.obs_space_post = copy.deepcopy(self.env_obs)
# 8. Add the transaction to the list of transactions # Write transaction to file
self.transaction_list.append(copy.deepcopy(transaction)) self.transaction_writer.write(transaction)
# Return # Return
return self.env_obs, reward, done, self.step_info return self.env_obs, reward, done, self.step_info
def close(self):
self.__close__()
def __close__(self):
"""
Override close function
"""
write_transaction_to_file(
self.transaction_list,
self.session_path,
self.timestamp_str
)
self.csv_file.close()
def init_acl(self): def init_acl(self):
"""Initialise the Access Control List.""" """Initialise the Access Control List."""
self.acl.remove_all_rules() self.acl.remove_all_rules()
@@ -467,7 +460,7 @@ class Primaite(Env):
): # Node actions in multdiscrete (array) from have len 4 ): # Node actions in multdiscrete (array) from have len 4
self.apply_actions_to_nodes(_action) self.apply_actions_to_nodes(_action)
else: else:
logging.error("Invalid action type found") _LOGGER.error("Invalid action type found")
def apply_actions_to_nodes(self, _action): def apply_actions_to_nodes(self, _action):
""" """

View File

@@ -85,9 +85,6 @@ def calculate_reward_function(
) )
if live_blocked and not reference_blocked: if live_blocked and not reference_blocked:
_LOGGER.debug(
f"Applying reward of {ier_reward} because IER {ier_key} is blocked"
)
reward_value += ier_reward reward_value += ier_reward
elif live_blocked and reference_blocked: elif live_blocked and reference_blocked:
_LOGGER.debug( _LOGGER.debug(

View File

@@ -20,6 +20,7 @@ def run(training_config_path: Union[str, Path], lay_down_config_path: Union[str,
session.setup() session.setup()
session.learn() session.learn()
session.evaluate()
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,8 +1,9 @@
# The main PrimAITE application config file # The main PrimAITE application config file
# Logging # Logging
log_level: INFO logging:
logger_format: log_level: INFO
logger_format:
DEBUG: '%(asctime)s: %(message)s' DEBUG: '%(asctime)s: %(message)s'
INFO: '%(asctime)s: %(message)s' INFO: '%(asctime)s: %(message)s'
WARNING: '%(asctime)s::%(levelname)s::%(name)s::%(lineno)s::%(message)s' WARNING: '%(asctime)s::%(levelname)s::%(name)s::%(lineno)s::%(message)s'

View File

@@ -1,57 +1,115 @@
# Crown Copyright (C) Dstl 2022. DEFCON 703. Shared in confidence. # Crown Copyright (C) Dstl 2022. DEFCON 703. Shared in confidence.
"""The Transaction class.""" """The Transaction class."""
from datetime import datetime
from typing import List, Tuple
class Transaction(object): class Transaction(object):
"""Transaction class.""" """Transaction class."""
def __init__(self, _timestamp, _agent_identifier, _episode_number, _step_number): def __init__(
self,
agent_identifier,
episode_number,
step_number
):
""" """
Init. Transaction constructor.
Args: :param agent_identifier: An identifier for the agent in use
_timestamp: The time this object was created :param episode_number: The episode number
_agent_identifier: An identifier for the agent in use :param step_number: The step number
_episode_number: The episode number
_step_number: The step number
""" """
self.timestamp = _timestamp self.timestamp = datetime.now()
self.agent_identifier = _agent_identifier "The datetime of the transaction"
self.episode_number = _episode_number self.agent_identifier = agent_identifier
self.step_number = _step_number self.episode_number = episode_number
"The episode number"
self.step_number = step_number
"The step number"
self.obs_space_pre = None
"The observation space before any actions are taken"
self.obs_space_post = None
"The observation space after any actions are taken"
self.reward = None
"The reward value"
self.action_space = None
"The action space invoked by the agent"
def set_obs_space_pre(self, _obs_space_pre): def as_csv_data(self) -> Tuple[List, List]:
""" if isinstance(self.action_space, int):
Sets the observation space (pre). action_length = self.action_space
else:
action_length = self.action_space.size
obs_shape = self.obs_space_post.shape
obs_assets = self.obs_space_post.shape[0]
if len(obs_shape) == 1:
# A bit of a workaround but I think the way transactions are
# written will change soon
obs_features = 1
else:
obs_features = self.obs_space_post.shape[1]
Args: # Create the action space headers array
_obs_space_pre: The observation space before any actions are taken action_header = []
""" for x in range(action_length):
self.obs_space_pre = _obs_space_pre action_header.append("AS_" + str(x))
def set_obs_space_post(self, _obs_space_post): # Create the observation space headers array
""" obs_header_initial = []
Sets the observation space (post). obs_header_new = []
for x in range(obs_assets):
for y in range(obs_features):
obs_header_initial.append("OSI_" + str(x) + "_" + str(y))
obs_header_new.append("OSN_" + str(x) + "_" + str(y))
Args: # Open up a csv file
_obs_space_post: The observation space after any actions are taken header = ["Timestamp", "Episode", "Step", "Reward"]
""" header = header + action_header + obs_header_initial + obs_header_new
self.obs_space_post = _obs_space_post
def set_reward(self, _reward): row = [
""" str(self.timestamp),
Sets the reward. str(self.episode_number),
str(self.step_number),
str(self.reward),
]
row = (
row
+ _turn_action_space_to_array(self.action_space)
+ _turn_obs_space_to_array(self.obs_space_pre, obs_assets,
obs_features)
+ _turn_obs_space_to_array(self.obs_space_post, obs_assets,
obs_features)
)
return header, row
Args:
_reward: The reward value
"""
self.reward = _reward
def set_action_space(self, _action_space): def _turn_action_space_to_array(action_space) -> List[str]:
""" """
Sets the action space. Turns action space into a string array so it can be saved to csv.
Args: :param action_space: The action space
_action_space: The action space invoked by the agent :return: The action space as an array of strings
""" """
self.action_space = _action_space if isinstance(action_space, list):
return [str(i) for i in action_space]
else:
return [str(action_space)]
def _turn_obs_space_to_array(obs_space, obs_assets, obs_features) -> List[str]:
"""
Turns observation space into a string array so it can be saved to csv.
:param obs_space: The observation space
:param obs_assets: The number of assets (i.e. nodes or links) in the
observation space
:param obs_features: The number of features associated with the asset
:return: The observation space as an array of strings
"""
return_array = []
for x in range(obs_assets):
for y in range(obs_features):
return_array.append(str(obs_space[x][y]))
return return_array

View File

@@ -1,119 +0,0 @@
# Crown Copyright (C) Dstl 2022. DEFCON 703. Shared in confidence.
"""Writes the Transaction log list out to file for evaluation to utilse."""
import csv
from pathlib import Path
import numpy as np
from primaite import getLogger
_LOGGER = getLogger(__name__)
def turn_action_space_to_array(_action_space):
"""
Turns action space into a string array so it can be saved to csv.
Args:
_action_space: The action space.
"""
if isinstance(_action_space, list):
return [str(i) for i in _action_space]
else:
return [str(_action_space)]
def turn_obs_space_to_array(_obs_space, _obs_assets, _obs_features):
"""
Turns observation space into a string array so it can be saved to csv.
Args:
_obs_space: The observation space
_obs_assets: The number of assets (i.e. nodes or links) in the observation space
_obs_features: The number of features associated with the asset
"""
return_array = []
for x in range(_obs_assets):
for y in range(_obs_features):
return_array.append(str(_obs_space[x][y]))
return return_array
def write_transaction_to_file(transaction_list, session_path: Path, timestamp_str: str):
"""
Writes transaction logs to file to support training evaluation.
:param transaction_list: The list of transactions from all steps and all
episodes.
:param session_path: The directory path the session is writing to.
:param timestamp_str: The session timestamp in the format:
<yyyy-mm-dd>_<hh-mm-ss>.
"""
# Get the first transaction and use it to determine the makeup of the
# observation space and action space
# Label the obs space fields in csv as "OSI_1_1", "OSN_1_1" and action
# space as "AS_1"
# This will be tied into the PrimAITE Use Case so that they make sense
template_transation = transaction_list[0]
if isinstance(template_transation.action_space, int):
action_length = template_transation.action_space
else:
action_length = template_transation.action_space.size
obs_shape = template_transation.obs_space_post.shape
obs_assets = template_transation.obs_space_post.shape[0]
if len(obs_shape) == 1:
# bit of a workaround but I think the way transactions are written will change soon
obs_features = 1
else:
obs_features = template_transation.obs_space_post.shape[1]
# Create the action space headers array
action_header = []
for x in range(action_length):
action_header.append("AS_" + str(x))
# Create the observation space headers array
obs_header_initial = []
obs_header_new = []
for x in range(obs_assets):
for y in range(obs_features):
obs_header_initial.append("OSI_" + str(x) + "_" + str(y))
obs_header_new.append("OSN_" + str(x) + "_" + str(y))
# Open up a csv file
header = ["Timestamp", "Episode", "Step", "Reward"]
header = header + action_header + obs_header_initial + obs_header_new
try:
filename = session_path / f"all_transactions_{timestamp_str}.csv"
_LOGGER.debug(f"Saving transaction logs: {filename}")
csv_file = open(filename, "w", encoding="UTF8", newline="")
csv_writer = csv.writer(csv_file)
csv_writer.writerow(header)
for transaction in transaction_list:
csv_data = [
str(transaction.timestamp),
str(transaction.episode_number),
str(transaction.step_number),
str(transaction.reward),
]
csv_data = (
csv_data
+ turn_action_space_to_array(transaction.action_space)
+ turn_obs_space_to_array(
transaction.obs_space_pre, obs_assets, obs_features
)
+ turn_obs_space_to_array(
transaction.obs_space_post, obs_assets, obs_features
)
)
csv_writer.writerow(csv_data)
csv_file.close()
_LOGGER.debug("Finished writing transactions")
except Exception:
_LOGGER.error("Could not save the transaction file", exc_info=True)

View File

@@ -0,0 +1,73 @@
import csv
from logging import Logger
from typing import List, Final, IO, Union, Tuple
from typing import TYPE_CHECKING
from primaite import getLogger
from primaite.transactions.transaction import Transaction
if TYPE_CHECKING:
from primaite.environment.primaite_env import Primaite
_LOGGER: Logger = getLogger(__name__)
class SessionOutputWriter:
_AV_REWARD_PER_EPISODE_HEADER: Final[List[str]] = [
"Episode", "Average Reward"
]
def __init__(
self,
env: "Primaite",
transaction_writer: bool = False,
learning_session: bool = True
):
self._env = env
self.transaction_writer = transaction_writer
self.learning_session = learning_session
if self.transaction_writer:
fn = f"all_transactions_{self._env.timestamp_str}.csv"
else:
fn = f"average_reward_per_episode_{self._env.timestamp_str}.csv"
if self.learning_session:
self._csv_file_path = self._env.session_path / "learning" / fn
else:
self._csv_file_path = self._env.session_path / "evaluation" / fn
self._csv_file_path.parent.mkdir(exist_ok=True, parents=True)
self._csv_file = None
self._csv_writer = None
self._first_write: bool = True
def _init_csv_writer(self):
self._csv_file = open(
self._csv_file_path, "w", encoding="UTF8", newline=""
)
self._csv_writer = csv.writer(self._csv_file)
def __del__(self):
if self._csv_file:
self._csv_file.close()
_LOGGER.info(f"Finished writing file: {self._csv_file_path}")
def write(
self,
data: Union[Tuple, Transaction]
):
if isinstance(data, Transaction):
header, data = data.as_csv_data()
else:
header = self._AV_REWARD_PER_EPISODE_HEADER
if self._first_write:
self._init_csv_writer()
self._csv_writer.writerow(header)
self._first_write = False
self._csv_writer.writerow(data)

View File

@@ -37,7 +37,6 @@ def _get_primaite_env_from_config(
env = Primaite( env = Primaite(
training_config_path=training_config_path, training_config_path=training_config_path,
lay_down_config_path=lay_down_config_path, lay_down_config_path=lay_down_config_path,
transaction_list=[],
session_path=session_path, session_path=session_path,
timestamp_str=timestamp_str, timestamp_str=timestamp_str,
) )