Reinforcement Learning 101

Reinforcement Learning (RL) is a framework to train an intelligent agent to take a sequence of actions in order to maximize a reward, given the feedback from a dynamic environment.

flowchart LR
%% Define the nodes
Agent[Agent]
Environment[Environment]

%% Define the interactions (edges)
Agent -- Action (A1, A2, ..., At) --> Environment
Environment -- State (S1, S2, ..., St) --> Agent
Environment -- Reward (R1, R2, ..., Rt) --> Agent

Gaming Agent

One of the most common use cases is gaming, where the agent is trained to win the game by controlling te keypads based on the gaming environment. For example, in the game of Pong, an agent can perform two actions $A_t$ at every timestamp: UP and DOWN. The environment will present the visual outcome as state $S_t$, which is a video frame of two paddles and a ball. In addition, the environment will reward the model (e.g. $R_t=1$) when the agent wins the game, and otherwise penalize it (e.g. $R_t=-1$).

Discounted Reward

Practically, the reward (or penalty) will be carried over to the previous timestamps with a discounting factor $\gamma$:

def discount_rewards(rewards, gamma=0.99):
  discounted = np.zeros_like(rewards, dtype=np.float32)
  running_add = 0
  for t in reversed(range(len(rewards))):
    if rewards[t] != 0:
      # BOUNDARY: Reset the chain when a point is scored
      running_add = 0
    running_add = running_add * gamma + rewards[t]
    discounted[t] = running_add
  return discounted

Policy Network

The agent learns a policy network $\pi_\theta(a|s)$ to decide what actions ($a$) to take given the current state ($s$). It produces a probablistic distribution of actions to sample from at each timestamp, parameterized by $\theta$.

Reward Optimization

The objective is to maximize the expected reward:

\[J(\theta) = E_{\pi_\theta} [R]\]

To optimize the reward, we compute its gradient:

\[\begin{align*} \nabla_\theta J(\theta) &= \nabla_\theta \sum_{s,a} \pi_\theta(s, a) \ R(s, a) \\ &= \sum_{s,a} \pi_\theta(s, a) \frac{\nabla_\theta \pi_\theta(s, a)}{\pi_\theta(s, a)} R(s, a) \\ &= \sum_{s,a} \pi_\theta(s, a) \left( \nabla_\theta \log \pi_\theta(s, a)\right) R(s, a) \\ &= E_{\pi_\theta} \left[ \nabla_\theta \log \pi_\theta(s, a) \ R(s, a) \right] \\ &= E_{\pi_\theta} \left[ \nabla_\theta \log \pi_\theta(a | s) \ R(s, a) \right] \end{align*}\]

Loss Function

To make the policy network trainable, we define a loss function that produces the same gradient (but negative) as above:

\[L = - \log \pi_\theta(a|s) \ R(s, a)\]

As we sample from the dataset, we are essentially trending towards the expected value.

Advantage vs Reward

However, this loss function will produce very high variance, mostly due to the noisy reward signal: some intermediate actions may be suboptimal, but the same reward will be applied to all past actions.

To mitigate the issue, people often replace reward $R$ with advantage $A$ that has more stable mean and variance. There are many design choices for this advantage. One simple option is normalization: we can define advantage as the normalized reward within the batch.

\[A = (R - \bar{R}) / \sqrt{R}\]

Another commonly used option is to use a critic network $Q_\phi(s, a)$ and $V_\phi(s)$, where $Q_\phi(s, a)$ predicts the reward given the current $s$ and $a$, and $V_\phi(s)$ predicts the expected reward given the current $s$ only.

\[A = Q_{\phi}(s, a) - V_{\phi}(s)\]

As the advantage $A$ is not a function of $\theta$, the loss $L$ and its gradient remain the same.

Training Loop

The training loop consists of 4 steps:

  1. Data Collection: The agent plays the game with the frozen policy $\pi_\theta(a|s)$ until certain criteria are met, e.g. M frames are seen or N games are finished.
  2. Optimization: We calculate the advantage $A$ and the loss $L$, and propagate the gradient to the policy network $\pi_\theta(a|s)$. We run this optimization for a few steps.
  3. Policy Update: We update the weight $\theta$ of the policy network $\pi_\theta(a|s)$.
  4. Repeat: We repeat the process.
for _ in range(num_iterations):
  # 1. Data collection
  states, actions, rewards, old_probs = collect_rollout(
      env, policy_net
  )
  # Calculate advantage using the critic
  advantages = compute_advantages(rewards, critic_net, states)
  
  # 2. Optimization
  optimizer.zero_grad()
  for _ in range(num_steps):
    # 3. Policy update
    new_probs = policy_net(states)
    loss = compute_loss(new_probs, old_probs, advantages)
    loss.backward()
    optimizer.step()

Proximal Policy Optimization (PPO)

To further reduce the variance, Proximal Policy Optimization (PPO) defines a bounded loss function:

\[L = -\min \left( r(\theta) A,\ clip(r(\theta), 1-\epsilon, 1+\epsilon) A \right) \\ r(\theta) = \pi_{new}(\theta) / \pi_{old}\]

When $A>0$, it is a good action, and otherwise a bad action. When $A$ and $r-1$ has the same sign, the policy moves toward the same direction, and otherwise a different direction. Here is a case-by-case study of how this bounded loss function plays its role:

A Action r Direction Clipping Min term ∂L/∂θ
A ≥ 0 Good action r > 1+ε Same direction Has clipping Clipping term ∂L/∂θ = 0
A ≥ 0 Good action 1 ≤ r ≤ 1+ε Same direction No clipping Non-clipping term ∂L/∂θ ≠ 0
A ≥ 0 Good action 1-ε ≤ r < 1 Different direction No clipping Non-clipping term ∂L/∂θ ≠ 0
A ≥ 0 Good action r < 1-ε Different direction Has clipping Non-clipping term ∂L/∂θ ≠ 0
A < 0 Bad action r < 1-ε Same direction Has clipping Clipping term ∂L/∂θ = 0
A < 0 Bad action 1-ε ≤ r ≤ 1 Same direction No clipping Non-clipping term ∂L/∂θ ≠ 0
A < 0 Bad action 1 < r ≤ 1+ε Different direction No clipping Non-clipping term ∂L/∂θ ≠ 0
A < 0 Bad action r > 1+ε Different direction Has clipping Non-clipping term ∂L/∂θ ≠ 0

As is shown from the above table, there is no gradient to $\theta$ (∂L/∂θ = 0) when the new policy moves towards the same direction as the reward, and the change is too much:

  • $A\ge0$, $r>1+\epsilon$: Action is good, but the new policy increases the probability too much.
  • $A<0$, $r<1-\epsilon$: Action is bad, but the new policy decreases the probability too much.

Reference

  • https://karpathy.github.io/2016/05/31/rl/
Written on July 7, 2026