| |
| import os |
| import warnings |
| import tensorflow as tf |
|
|
| os.environ["TF_USE_LEGACY_KERAS"] = "1" |
| os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" |
| os.environ["TF_FUNCTION_NUMERIC_CHECKS"] = "0" |
|
|
| tf.keras.backend.clear_session() |
| tf.config.threading.set_intra_op_parallelism_threads(0) |
| tf.config.threading.set_inter_op_parallelism_threads(0) |
| tf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.ERROR) |
|
|
| warnings.filterwarnings("ignore", category=UserWarning, module="gym") |
| warnings.filterwarnings("ignore", category=RuntimeWarning, module="tf_agents.specs.array_spec") |
| warnings.filterwarnings("ignore", category=DeprecationWarning) |
|
|
| from tf_agents.agents.ppo import ppo_agent |
| from tf_agents.networks import network |
| from tf_agents.networks import categorical_projection_network |
| from tf_agents.environments import tf_py_environment |
|
|
| |
| from config import Config |
| from env import CyberPettingZooEnv |
| from utilities import configure_gpu, PettingZooToTFAgentsWrapper |
| from trainer import MultiAgentTrainer |
|
|
| |
| class CyberGNNNetwork(network.Network): |
| """ |
| A custom TF-Agents Network that binds GNN feature extraction with |
| native CategoricalProjectionNetworks, explicitly forwarding outer_rank. |
| """ |
| def __init__(self, input_tensor_spec, output_spec, fc_layer_params=(128, 64), is_value_net=False, name="CyberGNNNetwork"): |
| super().__init__(input_tensor_spec=input_tensor_spec, state_spec=(), name=name) |
| self.is_value_net = is_value_net |
| |
| |
| self.w1 = tf.Variable(tf.keras.initializers.GlorotUniform()(shape=(2, 32)), trainable=True, name="gcn_w1") |
| self.w2 = tf.Variable(tf.keras.initializers.GlorotUniform()(shape=(32, 32)), trainable=True, name="gcn_w2") |
| |
| |
| self.dense_layers = [] |
| for units in fc_layer_params: |
| self.dense_layers.append(tf.keras.layers.Dense(units, activation=tf.nn.tanh)) |
| |
| |
| if self.is_value_net: |
| self.projection_head = tf.keras.layers.Dense(1, kernel_initializer=tf.keras.initializers.Orthogonal(1.0)) |
| else: |
| self.projection_head = categorical_projection_network.CategoricalProjectionNetwork(output_spec) |
|
|
| def call(self, observations, step_type=(), network_state=(), training=False): |
| A_norm = observations['adjacency_matrix'] |
| X = observations['node_features'] |
|
|
| outer_rank = X.shape.rank - 2 |
| |
| |
| h1 = tf.nn.tanh(tf.matmul(tf.matmul(A_norm, X), self.w1)) |
| h2 = tf.nn.tanh(tf.matmul(tf.matmul(A_norm, h1), self.w2)) |
| |
| |
| batch_shape = tf.shape(h2)[:-2] |
| flat_features = tf.reshape(h2, tf.concat([batch_shape, [-1]], axis=0)) |
| |
| |
| x = flat_features |
| for layer in self.dense_layers: |
| x = layer(x) |
| |
| |
| if self.is_value_net: |
| value_predictions = self.projection_head(x) |
| value_predictions = tf.squeeze(value_predictions, axis=-1) |
| return value_predictions, network_state |
| else: |
| action_distributions, _ = self.projection_head(x, outer_rank=outer_rank, training=training) |
| return action_distributions, network_state |
|
|
| |
| def create_ppo_agent(env_time_step_spec, env_action_spec, agent_name, lr_schedule): |
| """Creates an independent PPO Agent with guaranteed trainable variable tracking.""" |
| agent_obs_spec = env_time_step_spec.observation[agent_name] |
| agent_action_spec = env_action_spec[agent_name] |
|
|
| actor_net = CyberGNNNetwork( |
| input_tensor_spec=agent_obs_spec, |
| output_spec=agent_action_spec, |
| fc_layer_params=Config.ACTOR_LAYERS, |
| is_value_net=False, |
| name=f"actor_gnn_{agent_name}" |
| ) |
| |
| value_net = CyberGNNNetwork( |
| input_tensor_spec=agent_obs_spec, |
| output_spec=None, |
| fc_layer_params=Config.CRITIC_LAYERS, |
| is_value_net=True, |
| name=f"value_gnn_{agent_name}" |
| ) |
|
|
| optimizer = tf.keras.optimizers.Adam(learning_rate=lr_schedule) |
|
|
| agent = ppo_agent.PPOAgent( |
| time_step_spec=env_time_step_spec._replace( |
| observation=agent_obs_spec, |
| reward=env_time_step_spec.reward[agent_name] |
| ), |
| action_spec=agent_action_spec, |
| optimizer=optimizer, |
| actor_net=actor_net, |
| value_net=value_net, |
| num_epochs=10, |
| discount_factor=Config.GAMMA, |
| use_gae=True, |
| use_td_lambda_return=True, |
| normalize_observations=False, |
| normalize_rewards=True, |
| value_pred_loss_coef=0.5 |
| ) |
| |
| agent.initialize() |
| print(f"🔒 Verified Trainable Variables for {agent_name} Actor: {len(actor_net.trainable_variables)}") |
| return agent |
|
|
| |
| def main(): |
| configure_gpu() |
| print(f"🚀 Initializing Custom GNN MARL Cyber Range Framework in **{Config.MODE}** mode.") |
| |
| raw_pz_env = CyberPettingZooEnv() |
| py_env = PettingZooToTFAgentsWrapper(raw_pz_env) |
| tf_env = tf_py_environment.TFPyEnvironment(py_env) |
| |
| time_step_spec = tf_env.time_step_spec() |
| action_spec = tf_env.action_spec() |
| |
| print("🧠 Constructing Custom Graph Policy Networks for Red Agent...") |
| red_agent = create_ppo_agent(time_step_spec, action_spec, 'red', Config.get_red_lr_schedule()) |
| |
| print("🧠 Constructing Custom Graph Policy Networks for Blue Agent...") |
| blue_agent = create_ppo_agent(time_step_spec, action_spec, 'blue', Config.get_blue_lr_schedule()) |
| |
| trainer = MultiAgentTrainer(config=Config, tf_env=tf_env, agent_1=red_agent, agent_2=blue_agent) |
| |
| print("✨ Graph Neural Network pipeline successfully compiled. Launching training loop...") |
| for epoch in range(1, Config.TOTAL_EPISODES + 1): |
| trainer.train_epoch(epoch_idx=epoch) |
|
|
| if __name__ == '__main__': |
| main() |