[Python] Is this a correct Implementation of Flexible DDPG?

Discussão em 'Python' iniciado por Stack, Março 14, 2025.

  1. Stack

    Stack Membro Participativo

    so a bit of context, I am trying to use this flexible ddpg implementation to get train a model with the least possible peb(position error bound) of a RIS(reconfigurable intelligent surface). A problem I am facing is that the numbers are coming out to be a bit erratic, like the rewards(calculated as 1/peb) and peb itself, which should minimize itself to lesser than the previous episode's peb(ideally speaking) is just bouncing around. This most obviously would mean that the RL model is not configured right. If that is the case, can someone let me know if the code here has any fault? It would go a long way in helping me out with my thesis work.

    Also, flexible DDPG is just DDPG with epsilon greedy noise addition in terms of selecting action as well as choosing whether to explore or not based on the same epsilon value.

    Thanks

    import numpy as np
    import torch
    import torch.nn as nn
    import torch.optim as optim
    from collections import deque
    import random
    import copy

    class ActorNetwork(nn.Module):
    def __init__(self, state_dim, action_dim, hidden_dims=[400, 300]):
    super(ActorNetwork, self).__init__()
    layers = []
    prev_dim = state_dim
    for hidden_dim in hidden_dims:
    layers.extend([
    nn.Linear(prev_dim, hidden_dim),
    nn.ReLU(),
    nn.LayerNorm(hidden_dim)
    ])
    prev_dim = hidden_dim

    layers.append(nn.Linear(prev_dim, action_dim))
    layers.append(nn.Tanh())

    self.policy_network = nn.Sequential(*layers)

    def forward(self, state):
    return self.policy_network(state)

    def get_action(self, state):
    action = self.forward(state)
    return action

    class CriticNetwork(nn.Module):
    def __init__(self, state_dim, action_dim, hidden_dims=[400, 300]):
    super(CriticNetwork, self).__init__()
    self.state_layer = nn.Sequential(
    nn.Linear(state_dim, hidden_dims[0]),
    nn.ReLU(),
    nn.LayerNorm(hidden_dims[0])
    )
    layers = []
    prev_dim = hidden_dims[0] + action_dim
    for hidden_dim in hidden_dims[1:]:
    layers.extend([
    nn.Linear(prev_dim, hidden_dim),
    nn.ReLU(),
    nn.LayerNorm(hidden_dim)
    ])
    prev_dim = hidden_dim
    layers.append(nn.Linear(prev_dim, 1))

    self.q_network = nn.Sequential(*layers)

    def forward(self, state, action):
    state_features = self.state_layer(state)
    combined = torch.cat([state_features, action], dim=1)
    q_value = self.q_network(combined)

    return q_value

    class TargetActorNetwork(nn.Module):
    def __init__(self, state_dim, action_dim, hidden_dims=[400, 300], tau=0.001):
    super(TargetActorNetwork, self).__init__()

    self.tau = tau
    self.online_network = ActorNetwork(state_dim, action_dim, hidden_dims)
    self.target_network = copy.deepcopy(self.online_network)
    for param in self.target_network.parameters():
    param.requires_grad = False

    def forward(self, state):
    return self.target_network(state)

    def soft_update(self):
    for target_param, online_param in zip(self.target_network.parameters(),
    self.online_network.parameters()):
    target_param.data.copy_(
    self.tau * online_param.data + (1.0 - self.tau) * target_param.data
    )

    def get_online_network(self):
    return self.online_network

    def get_target_network(self):
    return self.target_network

    class TargetCriticNetwork(nn.Module):
    def __init__(self, state_dim, action_dim, hidden_dims=[400, 300], tau=0.001):
    super(TargetCriticNetwork, self).__init__()

    self.tau = tau
    self.online_network = CriticNetwork(state_dim, action_dim, hidden_dims)
    self.target_network = copy.deepcopy(self.online_network)
    for param in self.target_network.parameters():
    param.requires_grad = False

    def forward(self, state, action):
    return self.target_network(state, action)

    def soft_update(self):
    for target_param, online_param in zip(self.target_network.parameters(),
    self.online_network.parameters()):
    target_param.data.copy_(
    self.tau * online_param.data + (1.0 - self.tau) * target_param.data
    )

    def get_online_network(self):
    return self.online_network

    def get_target_network(self):
    return self.target_network

    class ReplayBuffer:
    def __init__(self, capacity, state_dim):
    self.buffer = deque(maxlen=capacity)
    self.state_dim = state_dim

    def push(self, state, action, reward, next_state):
    state = np.squeeze(np.array(state))
    next_state = np.squeeze(np.array(next_state))
    assert state.shape == (self.state_dim,), f"State shape mismatch: {state.shape}"
    assert next_state.shape == (self.state_dim,), f"Next state shape mismatch: {next_state.shape}"

    self.buffer.append((state, action, reward, next_state))

    def __len__(self):
    return len(self.buffer)

    def sample(self, batch_size):
    batch = random.sample(self.buffer, batch_size)
    state, action, reward, next_state = zip(*batch)

    state = np.array([np.squeeze(s) for s in state])
    next_state = np.array([np.squeeze(ns) for ns in next_state])

    action = np.array(action)
    reward = np.array(reward).reshape(-1, 1)

    assert state.shape == (batch_size, self.state_dim), f"State shape mismatch: {state.shape}"
    assert next_state.shape == (batch_size, self.state_dim), f"Next state shape mismatch: {next_state.shape}"
    assert action.shape[0] == batch_size, f"Action batch size mismatch: {action.shape[0]} != {batch_size}"
    assert reward.shape[0] == batch_size, f"Reward batch size mismatch: {reward.shape[0]} != {batch_size}"

    return (torch.FloatTensor(state),
    torch.FloatTensor(action),
    torch.FloatTensor(reward),
    torch.FloatTensor(next_state))

    class FLDDPG:
    def __init__(self, state_dim, action_dim, hidden_dims=[400, 300], buffer_size=10000,
    batch_size=16, gamma=0.95, tau=0.00001, actor_lr=1e-3, critic_lr=1e-3,
    lr_decay_rate=0.995, min_lr=1e-6):

    #? Selecting GPU based on device
    if(torch.backends.mps.is_available()):
    self.device = torch.device("mps")
    else:
    self.device = torch.device("cuda")

    self.target_actor = TargetActorNetwork(
    state_dim=state_dim,
    action_dim=action_dim,
    hidden_dims=hidden_dims,
    tau=tau
    ).to(self.device)

    self.target_critic = TargetCriticNetwork(
    state_dim=state_dim,
    action_dim=action_dim,
    hidden_dims=hidden_dims,
    tau=tau
    ).to(self.device)

    self.actor = self.target_actor.get_online_network()
    self.critic = self.target_critic.get_online_network()
    self.actor_optimizer = optim.Adam(self.actor.parameters(), lr=actor_lr)
    self.critic_optimizer = optim.Adam(self.critic.parameters(), lr=critic_lr)
    self.gamma = gamma
    self.batch_size = batch_size
    self.replay_buffer = ReplayBuffer(buffer_size, state_dim)

    self.initial_actor_lr = actor_lr
    self.initial_critic_lr = critic_lr
    self.lr_decay_rate = lr_decay_rate
    self.min_lr = min_lr
    self.current_actor_lr = actor_lr
    self.current_critic_lr = critic_lr

    self.actor_scheduler = optim.lr_scheduler.ExponentialLR(
    optimizer=self.actor_optimizer,
    gamma=lr_decay_rate
    )
    self.critic_scheduler = optim.lr_scheduler.ExponentialLR(
    optimizer=self.critic_optimizer,
    gamma=lr_decay_rate
    )

    def decay_learning_rates(self):
    self.actor_scheduler.step()
    for param_group in self.actor_optimizer.param_groups:
    param_group['lr'] = max(param_group['lr'], self.min_lr)
    self.current_actor_lr = self.actor_optimizer.param_groups[0]['lr']
    self.critic_scheduler.step()
    for param_group in self.critic_optimizer.param_groups:
    param_group['lr'] = max(param_group['lr'], self.min_lr)
    self.current_critic_lr = self.critic_optimizer.param_groups[0]['lr']

    def select_action(self, state, explore, epsilon):
    state = torch.FloatTensor(state).unsqueeze(0).to(self.device)
    with torch.no_grad():
    if explore:
    action = self.actor.get_action(state)
    noise_scale = epsilon * np.random.normal(0, 1, size=action.shape)
    action = torch.clamp(action + torch.FloatTensor(noise_scale).to(self.device), -1, 1)
    else:
    action = self.actor(state)
    return action.cpu().numpy().squeeze()

    def update(self):
    if len(self.replay_buffer) < self.batch_size * 3:
    return 0, 0
    state_batch, action_batch, reward_batch, next_state_batch = self.replay_buffer.sample(self.batch_size)
    state_batch = state_batch.to(self.device)
    action_batch = action_batch.to(self.device)
    reward_batch = reward_batch.to(self.device)
    next_state_batch = next_state_batch.to(self.device)
    with torch.no_grad():
    next_actions = self.target_actor(next_state_batch)
    target_q = reward_batch + self.gamma * self.target_critic(next_state_batch, next_actions)

    current_q = self.critic(state_batch, action_batch)
    critic_loss = torch.nn.MSELoss()(current_q, target_q)

    self.critic_optimizer.zero_grad()
    critic_loss.backward()
    torch.nn.utils.clip_grad_norm_(self.critic.parameters(), max_norm=1.0)
    self.critic_optimizer.step()

    actor_loss = -self.critic(state_batch, self.actor(state_batch)).mean()

    self.actor_optimizer.zero_grad()
    actor_loss.backward()
    torch.nn.utils.clip_grad_norm_(self.actor.parameters(), max_norm=1.0)
    self.actor_optimizer.step()

    self.target_actor.soft_update()
    self.target_critic.soft_update()

    return actor_loss.item(), critic_loss.item()


    I tried to see if the error was in the simulation end, but the values there seemed just as they were supposed to be. The flow of the program seems correct as well with the following training loop:

    for episode in range(num_episodes):
    matlab_state = self.eng.reset(self.sim)
    state = self.process_state(matlab_state)
    episode_reward = 0
    episode_losses = {'actor': [], 'critic': []}

    initial_peb = float(self.eng.calculatePerformanceMetrics(self.sim))
    min_peb_in_episode = initial_peb
    peb_values_in_episode = [initial_peb]

    step_counter = 0

    for step in range(max_steps):
    value = np.random.uniform(0,1)
    if (value < epsilon_start):
    explore = True
    else:
    explore = False
    step_counter = step_counter + 1

    action = self.agent.select_action(state, explore, epsilon_start)

    matlab_action = matlab.double(action.tolist())

    next_matlab_state, reward, done = self.eng.step(self.sim, matlab_action, nargout=3)
    current_peb = float(self.eng.calculatePerformanceMetrics(self.sim))
    rate_values[episode].append(float(self.eng.getrate(self.sim)))

    peb_values_in_episode.append(current_peb)
    min_peb_in_episode = min(min_peb_in_episode, current_peb)

    next_state = self.process_state(next_matlab_state)
    done = bool(done)

    self.agent.replay_buffer.push(state, action, reward, next_state)
    actor_loss, critic_loss = self.agent.update()

    episode_reward += reward
    if actor_loss is not None:
    episode_losses['actor'].append(actor_loss)
    episode_losses['critic'].append(critic_loss)

    state = next_state
    epsilon_start = max(epsilon_en, epsilon_start*epsilon_decay_rate)

    if done:
    prev_peb = current_peb
    break

    episode_reward = episode_reward/step_counter

    Continue reading...

Compartilhe esta Página