Course materials · Reinforcement Learning for Electrical Engineering

Advantage Actor-Critic (A2C)

English translation

[Core Actor-Critic concepts]

[Advantage Actor-Critic (A2C) algorithm]

[A2C implementation]

[A2C limitations and directions for improvement]

[Activity: CartPole with Advantage Actor-Critic (A2C)]

1. Import libraries and set hyperparameters

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Categorical
import gymnasium as gym
import numpy as np
import imageio

# Hyperparameters
gamma = 0.99
learning_rate = 0.003
hidden_size = 128  # Default (2-layer neural network)
num_steps = 10
num_episodes = 2000
progress_interval = 50
early_stop_threshold = 400 # CartPole-v1: Generally classified as successful at 200 or higher
early_stop_patience = 10
render_gif_path = "cartpole_simulation_a2c.gif"

2. Define the A2C neural network model (actor–critic)

class ActorCritic(nn.Module):
    def __init__(self, input_size, num_actions, hidden_size=128, use_three_layers=False):
        super(ActorCritic, self).__init__()
        self.use_three_layers = use_three_layers
        self.fc1 = nn.Linear(input_size, hidden_size)
        if self.use_three_layers:
            self.fc_h = nn.Linear(hidden_size, hidden_size)
        self.fc2 = nn.Linear(hidden_size, hidden_size)
        self.fc_pi = nn.Linear(hidden_size, num_actions)
        self.fc_v = nn.Linear(hidden_size, 1)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        if self.use_three_layers:
            x = F.relu(self.fc_h(x))
        x = F.relu(self.fc2(x))
        policy = F.softmax(self.fc_pi(x), dim=0)
        value = self.fc_v(x)
        return policy, value

3. Define the training loop function

Policy/value prediction and action selection

Apply the selected action to interact with the environment

Store experiences

Compute advantages and losses from rollout data

Optimization:

def train(model, optimizer, env, num_episodes, num_steps, gamma, check_interval, early_stop_threshold, early_stop_patience):
    scores = []
    for episode in range(num_episodes):
        state, _ = env.reset()
        done = False
        total_reward = 0
        memory = []

        while not done:
            for t in range(num_steps):
                state_tensor = torch.from_numpy(state).float()
                policy, value = model(state_tensor)
                action_dist = Categorical(policy)
                action = action_dist.sample()

                next_state, reward, terminated, truncated, _ = env.step(action.item())
                total_reward += reward
                done = terminated or truncated

                memory.append((state_tensor, action, reward, value, done))
                state = next_state

                if done:
                    break

            # Compute advantages and the loss function from rollout data
            loss = 0
            _, last_value = model(torch.from_numpy(next_state).float())
            returns = last_value.detach()

            for state_tensor, action, reward, value, done in reversed(memory):
                returns = reward + gamma * returns * (1 - done)
                advantage = returns - value
                loss += -Categorical(model(state_tensor)[0]).log_prob(action) * advantage.detach() + \
                        F.smooth_l1_loss(value, returns.detach()) # Huber loss

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            memory = [] # Clear memory

        scores.append(total_reward)

        # Check progress and early stopping
        if (episode+1) % check_interval == 0:
            avg_score = np.mean(scores[-check_interval:])
            print(f"Episode: {episode+1}, Average Reward: {avg_score:.2f}")

        if np.min(scores[-early_stop_patience:]) >= early_stop_threshold:
            print(f"Early stopping triggered! at Episode: {episode+1}")
            break

4. Run training and check results

# Create the environment
env = gym.make('CartPole-v1', render_mode = 'rgb_array')
input_size = env.observation_space.shape[0]
num_actions = env.action_space.n

# Create the model (example using a 3-layer neural network)
model = ActorCritic(input_size, num_actions, hidden_size, use_three_layers=True)
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# Run training
train(model, optimizer, env, num_episodes, num_steps, gamma, progress_interval, early_stop_threshold, early_stop_patience)

env.close()
Episode: 50, Average Reward: 16.78
Episode: 100, Average Reward: 24.02
Episode: 150, Average Reward: 18.66
Episode: 200, Average Reward: 37.02
Episode: 250, Average Reward: 39.84
Episode: 300, Average Reward: 31.26
Episode: 350, Average Reward: 55.00
Episode: 400, Average Reward: 104.56
Early stopping triggered! at Episode: 422

5. Test the trained model and render a GIF

state, _ = env.reset()
done = False
total_reward = 0
frames = []

with torch.no_grad():  # Disable gradient computation (inference mode)
    while not done:
        state_tensor = torch.from_numpy(state).float()
        policy, _ = model(state_tensor)
        action_dist = Categorical(policy)
        action = action_dist.sample()

        next_state, reward, terminated, truncated, _ = env.step(action.item())
        total_reward += reward
        state = next_state
        done = terminated or truncated

        # Append the rendered image to the frame list
        frames.append(env.render())

# Save as a GIF file
imageio.mimsave(render_gif_path, frames, duration=33)  # Adjust fps as needed: duration = 1000/fps
print(f"Test Episode Reward: {total_reward}")
env.close()
Test Episode Reward: 500.0