How to Implement Deep Reinforcement Learning with Policy Gradients and Q-Learning

You can implement deep reinforcement learning using policy gradients (actor-critic) with TensorFlow or Q-learning with PyTorch by training neural networks on the CartPole environment—both methods are fully documented in the Microsoft AI-For-Beginners curriculum with runnable notebooks.

Deep reinforcement learning combines neural networks with RL algorithms to solve complex control problems. The AI-For-Beginners repository provides a complete educational module at lessons/6-Other/22-DeepRL/ that demonstrates how to implement deep reinforcement learning with policy gradients and Q-learning using the classic CartPole-v1 environment. This guide walks through both architectures, their training loops, and how to run the official notebooks.

Policy Gradients: Actor-Critic Implementation in TensorFlow

The policy-gradient approach directly learns a stochastic policy π(s) that outputs action probabilities. The TensorFlow notebook CartPole-RL-TF.ipynb implements this using the actor-critic architecture, where two networks cooperate to stabilize training.

Network Architecture

  • Actor network: Takes the 4-dimensional state vector (cart position, cart velocity, pole angle, pole velocity) and outputs a softmax distribution over two actions (left/right).
  • Critic network: Shares the same hidden layers but emits a single scalar value estimating the expected return from the current state.
import gym, numpy as np, tensorflow as tf

env = gym.make("CartPole-v1")
num_states = env.observation_space.shape[0]
num_actions = env.action_space.n

# Actor + Critic model

inputs = tf.keras.layers.Input(shape=(num_states,))
hidden = tf.keras.layers.Dense(24, activation='relu')(inputs)
policy = tf.keras.layers.Dense(num_actions, activation='softmax')(hidden)
value  = tf.keras.layers.Dense(1, activation='linear')(hidden)
model = tf.keras.Model(inputs, [policy, value])
optimizer = tf.keras.optimizers.Adam(learning_rate=0.01)

Discounted Rewards Calculation

The policy gradient requires discounted returns G_t to weight actions by their long-term consequences. The discounted_rewards function in the notebook computes:

G_t = Σ_{k=0}^{∞} γ^k r_{t+k} where γ ≈ 0.99

def discounted_rewards(rews, gamma=0.99):
    ret = np.zeros_like(rews, dtype=np.float32)
    running_add = 0
    for t in reversed(range(len(rews))):
        running_add = rews[t] + gamma * running_add
        ret[t] = running_add
    return (ret - ret.mean()) / (ret.std() + 1e-8)

Training Loop with Policy Gradient Loss

The core training loop collects episode trajectories and computes the combined actor-critic loss:


# Training loop

for ep in range(300):
    state = env.reset()
    states, actions, probs, rewards = [], [], [], []
    done = False
    while not done:
        prob, _ = model(np.expand_dims(state, 0))
        prob = prob.numpy()[0]
        action = np.random.choice(num_actions, p=prob)
        next_state, reward, done, _ = env.step(action)

        states.append(state)
        actions.append(action)
        probs.append(prob)
        rewards.append(reward)

        state = next_state

    G = discounted_rewards(rewards)
    with tf.GradientTape() as tape:
        pi, V = model(np.array(states))
        # Actor loss (policy gradient): -log π(a|s) * G

        action_one_hot = tf.one_hot(actions, num_actions)
        log_pi = tf.math.log(tf.reduce_sum(pi * action_one_hot, axis=1) + 1e-8)
        actor_loss = -tf.reduce_mean(log_pi * G)
        # Critic loss (value regression): MSE between predicted and actual returns

        critic_loss = tf.reduce_mean(tf.square(V[:,0] - G))
        loss = actor_loss + critic_loss
    grads = tape.gradient(loss, model.trainable_variables)
    optimizer.apply_gradients(zip(grads, model.trainable_variables))

The actor loss implements the policy gradient theorem: ∇_θ J ≈ Σ_t ∇_θ log π_θ(a_t|s_t) * G_t. The critic loss trains the value estimator to predict returns accurately, reducing variance in the policy gradient.

Q-Learning: Deep Q-Network Implementation in PyTorch

The Q-learning approach learns an action-value function Q(s,a) rather than a policy directly. The optimal policy selects actions with maximum Q-value: a* = argmax_a Q(s,a). The PyTorch notebook CartPole-RL-PyTorch.ipynb implements this using the Bellman equation for target updates.

Q-Network Architecture

import gym, torch, torch.nn as nn, torch.optim as optim
import random, numpy as np

env = gym.make('CartPole-v1')
state_dim = env.observation_space.shape[0]
n_actions = env.action_space.n

class QNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = nn.Sequential(
            nn.Linear(state_dim, 64), nn.ReLU(),
            nn.Linear(64, n_actions)
        )
    def forward(self, x):
        return self.fc(x)

qnet = QNet()
optimizer = optim.Adam(qnet.parameters(), lr=0.001)
criterion = nn.MSELoss()
gamma = 0.99

ε-Greedy Exploration and Experience Replay

Q-learning requires exploration to discover good actions. The implementation uses ε-greedy action selection with decaying ε, and a simple replay buffer to break correlation between consecutive transitions:

epsilon = 1.0
memory = []          # simple list replay buffer

BATCH = 64
MAX_MEM = 5000

def epsilon_greedy(state):
    if random.random() < epsilon:
        return env.action_space.sample()
    q_vals = qnet(torch.from_numpy(state).float().unsqueeze(0))
    return torch.argmax(q_vals).item()

Training Loop with Bellman Target Updates

The target Q-value follows the Bellman equation: Q(s,a) ← r + γ * max_a' Q(s',a'):

for episode in range(500):
    state = env.reset()
    done = False
    while not done:
        action = epsilon_greedy(state)
        nxt, reward, done, _ = env.step(action)
        memory.append((state, action, reward, nxt, done))
        if len(memory) > MAX_MEM: memory.pop(0)

        # Sample minibatch for experience replay

        if len(memory) >= BATCH:
            batch = random.sample(memory, BATCH)
            s_batch, a_batch, r_batch, ns_batch, d_batch = zip(*batch)
            s_batch = torch.tensor(s_batch, dtype=torch.float32)
            a_batch = torch.tensor(a_batch, dtype=torch.int64).unsqueeze(1)
            r_batch = torch.tensor(r_batch, dtype=torch.float32).unsqueeze(1)
            ns_batch = torch.tensor(ns_batch, dtype=torch.float32)
            d_batch = torch.tensor(d_batch, dtype=torch.float32).unsqueeze(1)

            # Current Q-values for selected actions

            q_vals = qnet(s_batch).gather(1, a_batch)
            # Target Q-values: r + γ * max_a' Q(s',a')

            with torch.no_grad():
                next_q = qnet(ns_batch).max(1)[0].unsqueeze(1)
                target = r_batch + gamma * next_q * (1 - d_batch)
            loss = criterion(q_vals, target)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
        state = nxt
    epsilon = max(0.01, epsilon * 0.995)   # decay exploration

Policy Gradients vs. Q-Learning: When to Use Each

Aspect Policy Gradients (Actor-Critic) Q-Learning (Deep Q-Network)
What is learned Stochastic policy π(a|s) directly Action-value function Q(s,a)
Policy type Probabilistic, naturally explores Deterministic (after argmax), requires ε-greedy
Best for Continuous action spaces, stochastic domains Discrete action spaces, sample-efficient learning
Variance Higher variance, needs baseline (critic) Lower variance, more stable with replay
Convergence Slower but handles complex policies Faster on simple tasks like CartPole

In the AI-For-Beginners curriculum, policy gradients are implemented with TensorFlow following the official TensorFlow actor-critic tutorial, while Q-learning follows the PyTorch DQN tutorial.

Key Files and Next Steps

File Purpose
lessons/6-Other/22-DeepRL/README.md Environment setup and learning objectives
lessons/6-Other/22-DeepRL/CartPole-RL-TF.ipynb Complete TensorFlow actor-critic implementation
lessons/6-Other/22-DeepRL/CartPole-RL-PyTorch.ipynb Complete PyTorch Q-learning implementation
lessons/6-Other/22-DeepRL/lab/README.md Assignment to apply Q-learning to Mountain Car

To extend your learning, the lab assignment challenges you to implement deep reinforcement learning with policy gradients and Q-learning on the Mountain Car environment—a more difficult control problem requiring sustained exploration.

Summary

  • Policy gradients directly optimize action probabilities using the actor-critic architecture, combining policy gradient loss with value regression for stable training.
  • Q-learning estimates action-values through the Bellman equation and uses ε-greedy exploration with experience replay for sample efficiency.
  • Both methods are fully implemented in the Microsoft AI-For-Beginners repository with runnable notebooks for CartPole-v1.
  • The TensorFlow notebook (CartPole-RL-TF.ipynb) demonstrates actor-critic, while the PyTorch notebook (CartPole-RL-PyTorch.ipynb) covers Q-learning.

Frequently Asked Questions

What is the difference between actor-critic and Q-learning?

Actor-critic is a policy-gradient method that learns both a policy (actor) and a value function (critic) simultaneously, using the critic to reduce variance in policy updates. Q-learning is a value-based method that learns action-values directly and derives the policy implicitly through argmax selection. Actor-critic handles continuous actions naturally; Q-learning works best with discrete actions.

Why does the policy gradient use discounted rewards?

Discounted rewards weight immediate and future rewards by their temporal distance, preventing infinite returns in continuing environments and matching the mathematical objective of maximizing expected cumulative return. The normalization step in discounted_rewards further stabilizes training by controlling variance across episodes.

How does experience replay improve Q-learning?

Experience replay breaks the correlation between consecutive transitions by sampling random mini-batches from stored experiences, rather than learning from correlated sequential data. This improves sample efficiency and learning stability, as implemented in the PyTorch notebook's simple list-based replay buffer.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →