| import numpy as np |
|
|
| import torch |
| import torch.nn as nn |
| import torch.optim as optim |
| import torch.nn.functional as F |
| import random |
| import math |
|
|
| from datetime import datetime |
| import os |
| from collections import namedtuple, deque |
| import csv |
|
|
| from utils import save_distance_data |
| from Const import * |
|
|
| |
| device = DEVICE |
|
|
| |
| Transition = namedtuple('Transition', |
| ('state', 'action', 'next_state', 'reward', 'done')) |
|
|
| Att_Transition = namedtuple('Att_Transition', |
| ('state', 'state_mask', 'action', 'next_state', 'next_state_mask', 'reward', 'done')) |
| class ReplayMemory(object): |
| """Stores transitions for experience replay.""" |
|
|
| def __init__(self, capacity): |
| """Initialize a ReplayMemory instance. |
| |
| Args: |
| capacity (int): Maximum size of the replay buffer. |
| """ |
| |
| self.memory = deque([], maxlen=capacity) |
|
|
| def push(self, *args): |
| """Save a transition tuple.""" |
| self.memory.append(Transition(*args)) |
|
|
| def sample(self, batch_size): |
| """Randomly sample a batch of transitions from memory. |
| |
| Args: |
| batch_size (int): The number of transitions to sample. |
| |
| Returns: |
| list: A list of Transition tuples. |
| """ |
| return random.sample(self.memory, batch_size) |
|
|
| def __len__(self): |
| """Return the current size of the memory.""" |
| return len(self.memory) |
|
|
|
|
| class DQNNetwork(nn.Module): |
| """Neural Network for approximating Q-values.""" |
|
|
| def __init__(self, n_observations, n_actions): |
| """Initialize the DQN Network. |
| |
| Args: |
| n_observations (int): The size of the state space (input dimensions). |
| n_actions (int): The size of the action space (output dimensions). |
| """ |
| super(DQNNetwork, self).__init__() |
| |
| self.layer1 = nn.Linear(n_observations, 128) |
| self.layer2 = nn.Linear(128, 128) |
| self.layer3 = nn.Linear(128, n_actions) |
|
|
| def forward(self, x): |
| """Defines the forward pass of the network. |
| |
| Args: |
| x (torch.Tensor): The input state tensor. |
| |
| Returns: |
| torch.Tensor: The output Q-values for each action. |
| """ |
| x = F.relu(self.layer1(x)) |
| x = F.relu(self.layer2(x)) |
| return self.layer3(x) |
|
|
|
|
|
|
| class DuelingDQNNetwork(nn.Module): |
| """Dueling Network Architecture for Q-values. |
| Q(s,a) = V(s) + (A(s,a) - mean(A(s,a'))) |
| """ |
| def __init__(self, n_observations, n_actions): |
| """Initialize the Dueling DQN Network. |
| |
| Args: |
| n_observations (int): The size of the state space. |
| n_actions (int): The size of the action space. |
| """ |
| super(DuelingDQNNetwork, self).__init__() |
| self.n_actions = n_actions |
|
|
| |
| self.feature_layer1 = nn.Linear(n_observations, 128) |
| self.feature_layer2 = nn.Linear(128, 128) |
|
|
| |
| self.value_stream_layer1 = nn.Linear(128, 128) |
| self.value_stream_output = nn.Linear(128, 1) |
|
|
| |
| self.advantage_stream_layer1 = nn.Linear(128, 128) |
| self.advantage_stream_output = nn.Linear(128, n_actions) |
|
|
| def forward(self, x): |
| """Defines the forward pass.""" |
| |
| features = F.relu(self.feature_layer1(x)) |
| features = F.relu(self.feature_layer2(features)) |
|
|
| |
| value_hidden = F.relu(self.value_stream_layer1(features)) |
| value = self.value_stream_output(value_hidden) |
|
|
| |
| advantage_hidden = F.relu(self.advantage_stream_layer1(features)) |
| advantages = self.advantage_stream_output(advantage_hidden) |
|
|
| |
| |
| |
| q_values = value + (advantages - advantages.mean(dim=1, keepdim=True)) |
|
|
| return q_values |
|
|
| class Agent: |
| """Interacts with and learns from the environment using DQN.""" |
|
|
| def __init__(self, state_size, action_size, replay_memory_capacity, batch_size, |
| gamma, eps_start, eps_end, eps_decay, tau, lr, target_update_freq): |
| """Initialize the DQN Agent. |
| |
| Args: |
| state_size (int): Dimension of each state. |
| action_size (int): Dimension of each action. |
| replay_memory_capacity (int): Size of the replay buffer. |
| batch_size (int): Minibatch size for training. |
| gamma (float): Discount factor. |
| eps_start (float): Starting value of epsilon for epsilon-greedy. |
| eps_end (float): Minimum value of epsilon. |
| eps_decay (float): Decay rate for epsilon. |
| tau (float): Soft update parameter for target network. |
| lr (float): Learning rate for the optimizer. |
| target_update_freq (int): Frequency (in steps or episodes) for hard target updates (if tau=0). |
| """ |
| self.state_size = state_size |
| self.action_size = action_size |
| self.memory = ReplayMemory(replay_memory_capacity) |
| self.batch_size = batch_size |
| self.gamma = gamma |
| self.eps_start = eps_start |
| self.eps_end = eps_end |
| self.eps_decay = eps_decay |
| self.tau = tau |
| self.lr = lr |
| self.target_update_freq = target_update_freq |
|
|
| |
| |
| self.policy_net = DuelingDQNNetwork(state_size, action_size).to(device) |
| |
| self.target_net = DuelingDQNNetwork(state_size, action_size).to(device) |
| self.target_net.load_state_dict(self.policy_net.state_dict()) |
| self.target_net.eval() |
|
|
| |
| self.optimizer = optim.AdamW(self.policy_net.parameters(), lr=self.lr, amsgrad=True) |
|
|
| self.steps_done = 0 |
|
|
| def select_action(self, state): |
| """Selects an action using an epsilon-greedy policy. |
| |
| Args: |
| state (torch.Tensor): The current state. |
| |
| Returns: |
| torch.Tensor: The selected action tensor. |
| """ |
| sample = random.random() |
| |
| eps_threshold = self.eps_end + (self.eps_start - self.eps_end) * \ |
| math.exp(-1. * self.steps_done / self.eps_decay) |
| self.steps_done += 1 |
|
|
| if sample > eps_threshold: |
| |
| with torch.no_grad(): |
| |
| |
| |
| action = self.policy_net(state).max(1)[1].view(1, 1) |
| else: |
| |
| action = torch.tensor([[random.randrange(self.action_size)]], device=device, dtype=torch.long) |
|
|
| return action |
|
|
| def store_experience(self, state, action, next_state, reward, done): |
| """Stores a single experience tuple in the replay memory. |
| |
| Args: |
| state (torch.Tensor): The starting state. |
| action (torch.Tensor): The action taken. |
| next_state (torch.Tensor or None): The resulting state (None if terminal). |
| reward (torch.Tensor): The reward received. |
| done (bool): True if the episode terminated, False otherwise. |
| """ |
| |
| done_tensor = torch.tensor([done], device=device, dtype=torch.bool) |
| self.memory.push(state, action, next_state, reward, done_tensor) |
|
|
| def optimize_model(self): |
| """Performs one step of optimization on the policy network.""" |
| if len(self.memory) < self.batch_size: |
| return |
|
|
| |
| transitions = self.memory.sample(self.batch_size) |
| |
| |
| batch = Transition(*zip(*transitions)) |
|
|
| |
| |
| non_final_mask = torch.tensor(tuple(map(lambda s: s is not None, batch.next_state)), |
| device=device, dtype=torch.bool) |
| |
| non_final_next_states = torch.cat([s for s in batch.next_state if s is not None]) |
|
|
| |
| state_batch = torch.cat(batch.state) |
| action_batch = torch.cat(batch.action) |
| reward_batch = torch.cat(batch.reward) |
| |
|
|
| |
| |
| state_action_values = self.policy_net(state_batch).gather(1, action_batch) |
|
|
| |
| |
| next_state_values = torch.zeros(self.batch_size, device=device) |
| |
| with torch.no_grad(): |
| next_state_values[non_final_mask] = self.target_net(non_final_next_states).max(1)[0] |
|
|
| |
| |
| |
| expected_state_action_values = (next_state_values * self.gamma) + reward_batch |
|
|
| |
| |
| criterion = nn.SmoothL1Loss() |
| loss = criterion(state_action_values, expected_state_action_values.unsqueeze(1)) |
|
|
| |
| self.optimizer.zero_grad() |
| loss.backward() |
| |
| torch.nn.utils.clip_grad_value_(self.policy_net.parameters(), 100) |
| self.optimizer.step() |
|
|
| def update_target_net(self, soft_update=True): |
| """Updates the target network weights. |
| |
| Args: |
| soft_update (bool): If True, uses soft updates (Polyak averaging). |
| If False, performs a hard copy (less common with tau > 0). |
| """ |
| if soft_update and self.tau > 0: |
| |
| target_net_state_dict = self.target_net.state_dict() |
| policy_net_state_dict = self.policy_net.state_dict() |
| for key in policy_net_state_dict: |
| target_net_state_dict[key] = policy_net_state_dict[key] * self.tau + \ |
| target_net_state_dict[key] * (1 - self.tau) |
| self.target_net.load_state_dict(target_net_state_dict) |
| elif not soft_update: |
| |
| |
| self.target_net.load_state_dict(self.policy_net.state_dict()) |
|
|
| def save_weights_and_distances(self, all_distances_acquisitions,model_name = 'DQN', filename="agent_policy_weights.pth"): |
| """Saves the weights of the policy network.""" |
| |
| |
| datetime_str = datetime.now().strftime("%Y%m%d-%H%M%S") |
| models_dir = f"Metrics/{model_name}/{datetime_str}/" |
| |
|
|
| if not os.path.exists(models_dir): |
| os.makedirs(models_dir) |
|
|
| |
| save_distance_data(all_distances_acquisitions, save_folder=models_dir) |
|
|
| filename = os.path.join(models_dir, "agent_policy_weights.pth") |
| try: |
| |
| save_dir = os.path.dirname(filename) |
| if save_dir and not os.path.exists(save_dir): |
| os.makedirs(save_dir, exist_ok=True) |
| |
| torch.save(self.policy_net.state_dict(), filename) |
| print(f"Agent policy weights saved to {filename}") |
| except Exception as e: |
| print(f"Error saving agent weights: {e}") |
|
|
| def load_weights(self, model_name = 'DQN/'): |
| """Loads weights into the policy and target networks.""" |
| all_subdirs = [d for d in os.listdir(f'Metrics/{model_name}/')] |
| latest_subdir = max(all_subdirs) |
| path = os.path.join('Metrics/', model_name) |
| path = os.path.join(path, latest_subdir) |
| filename = os.path.join(path, "agent_policy_weights.pth") |
|
|
| |
| if not os.path.exists(filename): |
| print(f"Warning: Weight file not found at {filename}. Starting with initial weights.") |
| return False |
| try: |
| |
| state_dict = torch.load(filename, map_location=device) |
| self.policy_net.load_state_dict(state_dict) |
| |
| self.target_net.load_state_dict(self.policy_net.state_dict()) |
| self.policy_net.eval() |
| self.target_net.eval() |
| print(f"Agent policy weights loaded from {filename}") |
| return True |
| except Exception as e: |
| print(f"Error loading agent weights from {filename}: {e}") |
| return False |
| |
|
|
|
|
| class Att_ReplayMemory(object): |
| """Stores transitions (including masks) for experience replay.""" |
|
|
| def __init__(self, capacity): |
| """Initialize a ReplayMemory instance.""" |
| self.memory = deque([], maxlen=capacity) |
|
|
| def push(self, *args): |
| """Save a transition tuple (state, state_mask, action, next_state, next_state_mask, reward, done).""" |
| |
| assert len(args) == 7, "Incorrect number of arguments for Transition tuple" |
| self.memory.append(Att_Transition(*args)) |
|
|
| def sample(self, batch_size): |
| """Randomly sample a batch of transitions from memory.""" |
| return random.sample(self.memory, batch_size) |
|
|
| def __len__(self): |
| """Return the current size of the memory.""" |
| return len(self.memory) |
|
|
|
|
| |
| class AttentionDQNNetwork(nn.Module): |
| """ |
| DQN Network using Attention to process sequence states. |
| Uses learned positional embeddings instead of initial Conv1D layers. |
| Input shape: (batch, num_features, seq_length) |
| Mask shape: (batch, seq_length) |
| Output shape: (batch, n_actions) |
| """ |
| def __init__(self, n_features, seq_len, n_actions, embed_dim=64, num_heads=4): |
| """ |
| Args: |
| n_features (int): Number of features in the input sequence (e.g., 5). |
| seq_len (int): Length of the input sequence. |
| n_actions (int): Number of possible actions. |
| embed_dim (int): Dimension for embedding and attention mechanism. |
| num_heads (int): Number of attention heads. |
| """ |
| super(AttentionDQNNetwork, self).__init__() |
| self.n_features = n_features |
| self.seq_len = seq_len |
| self.n_actions = n_actions |
| self.embed_dim = embed_dim |
|
|
| |
| |
| |
| self.input_proj = nn.Conv1d(n_features, embed_dim, kernel_size=1) |
|
|
| |
| |
| self.positional_embedding = nn.Embedding(seq_len, embed_dim) |
|
|
| |
| |
| self.attention = nn.MultiheadAttention(embed_dim, num_heads, batch_first=True) |
|
|
| |
| self.norm1 = nn.LayerNorm(embed_dim) |
| self.norm2 = nn.LayerNorm(embed_dim) |
|
|
| |
| self.linear1 = nn.Linear(embed_dim, embed_dim * 2) |
| self.linear2 = nn.Linear(embed_dim * 2, n_actions) |
|
|
| def forward(self, x, attention_mask): |
| """ |
| Forward pass through the network with positional embeddings. |
| |
| Args: |
| x (torch.Tensor): Input state sequence tensor (batch, n_features, seq_len). |
| attention_mask (torch.Tensor): Boolean mask (batch, seq_len). True indicates valid data. |
| |
| Returns: |
| torch.Tensor: Q-values for each action (batch, n_actions). |
| """ |
| |
| |
| x = self.input_proj(x) |
| |
| x = F.relu(x) |
|
|
| |
| |
| positions = torch.arange(0, self.seq_len, device=x.device).unsqueeze(0) |
| |
| pos_emb = self.positional_embedding(positions) |
|
|
| |
| |
| |
| x = x.permute(0, 2, 1) |
| |
| x = x + pos_emb |
|
|
| |
|
|
| |
| key_padding_mask = ~attention_mask |
|
|
| |
| x_norm = self.norm1(x) |
|
|
| |
| attn_output, _ = self.attention(x_norm, x_norm, x_norm, key_padding_mask=key_padding_mask) |
|
|
| |
| x = x + attn_output |
| x = self.norm2(x) |
|
|
| |
| mask_expanded = attention_mask.unsqueeze(-1).float() |
| masked_sum = (x * mask_expanded).sum(dim=1) |
| valid_counts = mask_expanded.sum(dim=1) |
| valid_counts = torch.clamp(valid_counts, min=1.0) |
| aggregated_output = masked_sum / valid_counts |
|
|
| |
| q_values = F.relu(self.linear1(aggregated_output)) |
| q_values = self.linear2(q_values) |
|
|
| return q_values |
|
|
|
|
| class Att_Agent: |
| """Interacts with and learns from the environment using Attention DQN.""" |
|
|
| def __init__(self, n_features, seq_len, action_size, replay_memory_capacity, batch_size, |
| gamma, eps_start, eps_end, eps_decay, tau, lr, target_update_freq, embed_dim=64, num_heads=4): |
| """Initialize the Attention DQN Agent. |
| |
| Args: |
| n_features (int): Number of features per time step in the state sequence. |
| seq_len (int): The length of the state sequence. |
| action_size (int): Dimension of each action. |
| replay_memory_capacity (int): Size of the replay buffer. |
| batch_size (int): Minibatch size for training. |
| gamma (float): Discount factor. |
| eps_start (float): Starting value of epsilon for epsilon-greedy. |
| eps_end (float): Minimum value of epsilon. |
| eps_decay (float): Decay rate for epsilon. |
| tau (float): Soft update parameter for target network. |
| lr (float): Learning rate for the optimizer. |
| target_update_freq (int): Frequency for hard target updates (if tau=0). |
| embed_dim (int): Embedding dimension for the Attention network. |
| num_heads (int): Number of attention heads for the Attention network. |
| """ |
| self.n_features = n_features |
| self.seq_len = seq_len |
| self.action_size = action_size |
| self.memory = Att_ReplayMemory(replay_memory_capacity) |
| self.batch_size = batch_size |
| self.gamma = gamma |
| self.eps_start = eps_start |
| self.eps_end = eps_end |
| self.eps_decay = eps_decay |
| self.tau = tau |
| self.lr = lr |
| self.target_update_freq = target_update_freq |
| |
|
|
| |
| self.policy_net = AttentionDQNNetwork(n_features, seq_len, action_size, embed_dim, num_heads).to(device) |
| self.target_net = AttentionDQNNetwork(n_features, seq_len, action_size, embed_dim, num_heads).to(device) |
| self.target_net.load_state_dict(self.policy_net.state_dict()) |
| self.target_net.eval() |
|
|
| |
| self.optimizer = optim.AdamW(self.policy_net.parameters(), lr=self.lr, amsgrad=True) |
|
|
| self.steps_done = 0 |
|
|
| |
| self.step_losses = [] |
| self.log_writer = None |
| self.log_file = None |
| self.log_file_path = None |
| self.log_header_written = False |
|
|
| def select_action(self, state_tuple): |
| """Selects an action using an epsilon-greedy policy based on state sequence and mask. |
| |
| Args: |
| state_tuple (tuple): A tuple containing (state_sequence, state_mask). |
| state_sequence shape: (1, n_features, seq_len) |
| state_mask shape: (1, seq_len) |
| |
| Returns: |
| torch.Tensor: The selected action tensor (shape [1, 1]). |
| """ |
| state_sequence, state_mask = state_tuple |
| sample = random.random() |
| eps_threshold = self.eps_end + (self.eps_start - self.eps_end) * \ |
| math.exp(-1. * self.steps_done / self.eps_decay) |
| self.steps_done += 1 |
|
|
| if sample > eps_threshold: |
| |
| with torch.no_grad(): |
| |
| q_values = self.policy_net(state_sequence.to(device), state_mask.to(device)) |
| |
| |
| action = q_values.max(1)[1].view(1, 1) |
| |
| else: |
| |
| action = torch.tensor([[random.randrange(self.action_size)]], device=device, dtype=torch.long) |
| |
|
|
| return action |
|
|
| def store_experience(self, state_tuple, action, next_state_tuple, reward, done): |
| """Stores a single experience tuple (including masks) in the replay memory. |
| |
| Args: |
| state_tuple (tuple): (state_sequence, state_mask). |
| action (torch.Tensor): The action taken (shape [1, 1]). |
| next_state_tuple (tuple or None): (next_state_sequence, next_state_mask) or None if terminal. |
| reward (torch.Tensor): The reward received (shape [1]). |
| done (bool): True if the episode terminated. |
| """ |
| |
| state_sequence, state_mask = state_tuple |
| if next_state_tuple is not None: |
| next_state_sequence, next_state_mask = next_state_tuple |
| else: |
| |
| |
| next_state_sequence, next_state_mask = None, None |
|
|
| done_tensor = torch.tensor([done], device=device, dtype=torch.bool) |
| |
| self.memory.push(state_sequence, state_mask, action, next_state_sequence, next_state_mask, reward, done_tensor) |
|
|
|
|
| def optimize_model(self): |
| """Performs one step of optimization and stores the loss.""" |
| if len(self.memory) < self.batch_size: |
| return |
|
|
| |
| transitions = self.memory.sample(self.batch_size) |
| batch = Att_Transition(*zip(*transitions)) |
| |
| non_final_mask_indices = [i for i, ns in enumerate(batch.next_state) if ns is not None] |
| state_batch = torch.cat([s for s in batch.state if s is not None]) |
| state_mask_batch = torch.cat([m for m in batch.state_mask if m is not None]) |
| if non_final_mask_indices: |
| non_final_next_states = torch.cat([batch.next_state[i] for i in non_final_mask_indices]) |
| non_final_next_state_masks = torch.cat([batch.next_state_mask[i] for i in non_final_mask_indices]) |
| else: |
| non_final_next_states = None |
| non_final_next_state_masks = None |
| action_batch = torch.cat(batch.action) |
| reward_batch = torch.cat(batch.reward) |
| non_final_mask = torch.tensor(tuple(map(lambda ns: ns is not None, batch.next_state)), device=device, dtype=torch.bool) |
|
|
| |
| state_action_values = self.policy_net(state_batch, state_mask_batch).gather(1, action_batch) |
|
|
| |
| next_state_values = torch.zeros(self.batch_size, device=device) |
| if non_final_next_states is not None and non_final_next_state_masks is not None: |
| with torch.no_grad(): |
| max_target_q = self.target_net(non_final_next_states, non_final_next_state_masks).max(1)[0] |
| next_state_values[non_final_mask] = max_target_q |
| expected_state_action_values = (next_state_values * self.gamma) + reward_batch |
|
|
| |
| criterion = nn.SmoothL1Loss() |
| loss = criterion(state_action_values, expected_state_action_values.unsqueeze(1)) |
|
|
| |
| self.step_losses.append(loss.item()) |
|
|
| |
| self.optimizer.zero_grad() |
| loss.backward() |
| torch.nn.utils.clip_grad_value_(self.policy_net.parameters(), 100) |
| self.optimizer.step() |
|
|
|
|
| def setup_logging(self, model_name='AttentionDQN', base_dir="Metrics"): |
| """Creates logging directory and initializes the CSV log file.""" |
| try: |
| datetime_str = datetime.now().strftime("%Y%m%d-%H%M%S") |
| self.log_dir = os.path.join(base_dir, model_name, datetime_str) |
| if not os.path.exists(self.log_dir): |
| os.makedirs(self.log_dir) |
|
|
| self.log_file_path = os.path.join(self.log_dir, "training_log.csv") |
| |
| self.log_file = open(self.log_file_path, mode='a', newline='', encoding='utf-8') |
| self.log_writer = csv.writer(self.log_file) |
|
|
| |
| self.log_file.seek(0, os.SEEK_END) |
| if self.log_file.tell() == 0: |
| header = ["Episode", "Steps", "TotalReward", "AverageLoss", "Epsilon", "GoalAchieved"] |
| self.log_writer.writerow(header) |
| self.log_header_written = True |
| print(f"Logging initialized at: {self.log_file_path}") |
|
|
| except Exception as e: |
| print(f"Error setting up logging: {e}") |
| if self.log_file: |
| self.log_file.close() |
| self.log_writer = None |
| self.log_file = None |
|
|
| |
| def log_episode_data(self, episode, steps, avg_reward, goal_achieved): |
| """Calculates metrics and writes a row to the log file for the completed episode.""" |
| if self.log_writer is None: |
| print("Warning: Log writer not initialized. Cannot log episode data.") |
| return |
|
|
| |
| avg_loss = np.mean(self.step_losses) if self.step_losses else 0.0 |
| self.step_losses = [] |
|
|
| |
| current_epsilon = self.eps_end + (self.eps_start - self.eps_end) * \ |
| math.exp(-1. * self.steps_done / self.eps_decay) |
|
|
| try: |
| row = [episode, steps, f"{avg_reward:.4f}", f"{avg_loss:.6f}", f"{current_epsilon:.4f}", goal_achieved] |
| self.log_writer.writerow(row) |
| |
| |
| except Exception as e: |
| print(f"Error writing log data: {e}") |
|
|
| |
| def close_log(self): |
| """Closes the log file handle.""" |
| if self.log_file: |
| try: |
| self.log_file.close() |
| print(f"Log file closed: {self.log_file_path}") |
| self.log_file = None |
| self.log_writer = None |
| except Exception as e: |
| print(f"Error closing log file: {e}") |
|
|
| def update_target_net(self, soft_update=True): |
| """Updates the target network weights (same logic as before).""" |
| if soft_update and self.tau > 0: |
| target_net_state_dict = self.target_net.state_dict() |
| policy_net_state_dict = self.policy_net.state_dict() |
| for key in policy_net_state_dict: |
| target_net_state_dict[key] = policy_net_state_dict[key] * self.tau + \ |
| target_net_state_dict[key] * (1 - self.tau) |
| self.target_net.load_state_dict(target_net_state_dict) |
| elif not soft_update: |
| |
| self.target_net.load_state_dict(self.policy_net.state_dict()) |
|
|
| |
| |
| |
|
|
| def save_weights_and_distances(self, all_distances_acquisitions, filename="agent_policy_weights.pth"): |
| """Saves the weights of the policy network and distance data.""" |
| base_dir = f'Metrics/{MODEL_NAME}/' |
| if not os.path.exists(base_dir): |
| print(f"Warning: Base directory {base_dir} not found. Cannot load weights.") |
| return False |
|
|
| all_subdirs = [d for d in os.listdir(base_dir) if os.path.isdir(os.path.join(base_dir, d))] |
| if not all_subdirs: |
| print(f"Warning: No subdirectories found in {base_dir}. Cannot load weights.") |
| return False |
|
|
| latest_subdir = max(all_subdirs) |
| models_dir = os.path.join(base_dir, latest_subdir) |
| if not os.path.exists(models_dir): |
| os.makedirs(models_dir) |
|
|
| |
| |
| try: |
| |
| from utils import save_distance_data |
| save_distance_data(all_distances_acquisitions, save_folder=models_dir) |
| except ImportError: |
| print("Warning: 'save_distance_data' function not found. Skipping distance saving.") |
| except Exception as e: |
| print(f"Error saving distance data: {e}") |
|
|
|
|
| |
| weights_filename = os.path.join(models_dir, filename) |
| try: |
| torch.save(self.policy_net.state_dict(), weights_filename) |
| print(f"Agent policy weights saved to {weights_filename}") |
| except Exception as e: |
| print(f"Error saving agent weights: {e}") |
|
|
| def load_weights(self): |
| |
| |
| try: |
| base_dir = f'Metrics/{MODEL_NAME}/' |
| if not os.path.exists(base_dir): |
| print(f"Warning: Base directory {base_dir} not found. Cannot load weights.") |
| return False |
|
|
| all_subdirs = [d for d in os.listdir(base_dir) if os.path.isdir(os.path.join(base_dir, d))] |
| if not all_subdirs: |
| print(f"Warning: No subdirectories found in {base_dir}. Cannot load weights.") |
| return False |
|
|
| latest_subdir = max(all_subdirs) |
| path = os.path.join(base_dir, latest_subdir) |
| filename = os.path.join(path, "agent_policy_weights.pth") |
|
|
| if not os.path.exists(filename): |
| print(f"Warning: Weight file not found at {filename}. Starting with initial weights.") |
| return False |
|
|
| state_dict = torch.load(filename, map_location=device) |
| self.policy_net.load_state_dict(state_dict) |
| self.target_net.load_state_dict(self.policy_net.state_dict()) |
| self.policy_net.eval() |
| self.target_net.eval() |
| print(f"Agent policy weights loaded from {filename}") |
| |
| |
| return True |
| except FileNotFoundError: |
| print(f"Warning: Directory or file not found during weight loading for {MODEL_NAME}. Starting with initial weights.") |
| return False |
| except Exception as e: |
| print(f"Error loading agent weights for {MODEL_NAME}: {e}") |
| return False |
| |