Learn › Part D: Deep reinforcement learning
12. Policy gradients
Nudge what worked
Below are three slot machines, the same idea as the bandit chapter. But this learner is different. It does not keep estimates of how good each machine is. It only keeps a probability of choosing each one. Every pull, it picks a machine at random using those probabilities, sees what it earned, and nudges the probabilities: a good result makes that machine a bit more likely next time.
Press A hundred a few times and watch the bars. Then do something sneaky: raise the bonus on every reward to 10 and switch off subtract a baseline. Press Start over and try again a few times. Then switch the baseline back on.
Policy nudge
The learner has no estimates of value at all. It just has a probability for each machine, and after every pull it nudges those probabilities toward machines that paid well. The machines pay 1 coin with chances of 20%, 50% and 80%, plus the bonus you choose below.
Pulls so far: 0
Try it with a bonus of 10 and the baseline switched off, a few times. Then switch the baseline back on.
With no bonus, the learner heads for the best machine (C, which pays 80% of the time). With a bonus of 10 and no baseline, it usually does something strange. By the end of this chapter you will know why, and the fix is a single subtraction.
Learn what to do, not how good things are
There are two broad ways to build a learning agent.
- Value-based methods, like Q-learning and DQN, learn how good each action is, and then pick the best one. The policy is a by-product.
- Policy-based methods learn the policy itself: a rule that says how likely each action is in each situation. There is no Q-table and no Q-values.
A policy that works with probabilities has a built-in perk: because it picks actions at random, according to those probabilities, it explores on its own. It does not need a separate "try something random with probability " rule.
Where do the probabilities come from? The agent computes a score for each action, using a formula such as a neural network, and then turns scores into probabilities with the softmax:
Here (pi, the policy) is the probability of choosing action , and (theta) is its score. The exponentials make every number positive, and dividing by the total makes them add up to 1. Take scores . Then , and , which add up to 11.107, so the probabilities are . A higher score means a higher probability, and equal scores give equal chances.
What are we trying to improve?
We want a policy that earns a lot. Its expected reward is the average reward it earns, weighted by how likely each action is:
(If machine A pays 0.2 on average, B 0.5 and C 0.8, and the policy picks each with probability , then is the weighted average.) To improve the policy, we want the slope of with respect to each score, so we can nudge the scores uphill. That is gradient ascent: gradient descent in reverse. But here is the catch. We do not know in advance, and the slope of a sum over probabilities does not look easy to estimate from a single pull. There is a neat trick.
The log-derivative trick
Start with the slope of with respect to a score . Only the probabilities depend on :
Now use a fact from school about logarithms: the derivative of is . Rearranged, that says . Apply it to :
Put that back in:
Look at what happened. The sum "each action, weighted by its probability " is exactly an expected value. So the slope of is the average, over the actions we actually pick, of the reward times the slope of the log-probability. And we can estimate an average by sampling! Pull a machine, see the reward , and compute . Over many pulls, those samples average out to the true slope.
We checked this numerically: for a policy with scores and machines paying , the slope of the expected reward found by nudging each score matched the trick's formula to six decimal places.
The slope of the log of a softmax
All we need now is the slope of with respect to each score . With the softmax, . The first part has slope 1 for itself and 0 for the others. The second part, by the chain rule, has slope . So:
In words: one-hot of the chosen action, minus the probabilities. ("One-hot" means a list with a 1 for the chosen action and 0s elsewhere.) The update is therefore
where is the machine we pulled, is the reward and is the step size. (The is a baseline, which we come to in a moment. For now, think of it as 0.)
Let us do one by hand. All three scores start at 0, so the probabilities are each. We pull machine B and get a reward of 1. With :
- The bracket is .
- Multiply by : the scores change by .
- The new scores are . The exponentials are , , , which add up to 3.0886, so the new probabilities are .
Machine B went from 33% to 45%, and the others fell to 27%. The good result made the chosen action more likely. Had the reward been low, the nudge would have been smaller. A reward of zero would not have moved anything, and a negative reward would have pushed B down. That is the whole idea in one line: more of what worked, less of what did not.
The trouble with all-positive rewards, and the baseline
Now we can see why the sneaky experiment broke things. Suppose every reward is shifted up by 10, so each pull pays 10 or 11. Machine C is still the best, but every reward is large and positive. Look at the update again: multiplies the nudge, and with , a reward of 10 or 11 is a huge, positive push toward whichever machine was just pulled. Whichever machine happens to get pulled early on gets boosted massively, even if it is a bad one. The learner chases noise.
The cure is the baseline : subtract a typical reward before nudging. We use the running average reward. Now a reward of 11 when the average is 10.5 gives a small push up, and a reward of 10 gives a small push down. The big shared bonus cancels out, and only the differences between actions are left. Notice this means the shift of 10 simply stops mattering.
You might worry that subtracting something changes the answer. It does not, on average. The reason is neat. The slopes of , weighted by their probabilities, add up to zero, because
So subtracting a constant from the reward removes a term that averages to zero. The baseline does not change what the learner learns on average, but it can greatly reduce the noise.
We measured it: 200 runs of 300 pulls each, step size 0.1. With no bonus, the learner ends up with an average probability of 0.91 on the best machine either way. With a bonus of 10 and no baseline, the average falls to 0.39, and in 61% of runs the best machine ended up with less than a 50% chance of being picked. With a bonus of 10 and a baseline, the result is 0.90, essentially unaffected, and in 95.5% of runs the best machine has over an 80% chance.
From one pull to a whole episode: REINFORCE
Real problems are sequences of decisions. The same trick extends: for an episode, nudge each action by how good the return that followed it was:
Here is the return from step (the discounted total of rewards from that step on, as in the Markov decision process chapter), and is a baseline, such as the average return of the episode. The quantity is called the advantage: how much better than average this step's outcome turned out to be. This method is called REINFORCE.
Now the policy is a neural network that takes the state and outputs one score per action. The recipe is:
- Play an episode, choosing each action at random using the softmax probabilities.
- Work out the return after every step, backwards from the end.
- Subtract the baseline to get the advantage.
- For each step, compute the slope on the scores, multiplied by its advantage, and push it back through the network with backpropagation.
In code, the key lines are tiny:
d_scores = probs.copy()
d_scores[np.arange(len(actions)), actions] -= 1.0 # probabilities minus one-hot
d_scores *= (advantage / len(actions))[:, None] # scaled by how good each step turned out
policy.learn(d_scores, lr) # backpropagation, then a small step
(The sign is flipped from the formula above because the network code takes a gradient descent step, and we want gradient ascent on the return.)
Does it work?
We trained the same cart-pole balancer as in the last chapter, but now with REINFORCE. Here is one run, showing the average steps the pole stayed up per block of 50 episodes:
| Episodes | Average steps |
|---|---|
| 1 to 50 | 30.2 |
| 51 to 100 | 49.4 |
| 101 to 150 | 71.6 |
| 151 to 200 | 122.0 |
| 201 to 250 | 173.7 |
| 251 to 300 | 180.8 |
| 301 to 350 | 181.4 |
| 351 to 400 | about 190 |
| 401 to 450 | about 190 |
| 451 to 500 | about 190 |
The climb is steadier than DQN's, though it wobbles a little too, and the last blocks differ by several steps from one computer to another. And the finished policy, if it simply takes its most likely move each time, balances the pole for close to the full 200 steps (200 on our machine, a little less on some others).
Does the baseline matter here too? We ran five different seeds for each version, looking at the average over the last 50 episodes:
| Version | Result for each of the 5 seeds | Average |
|---|---|---|
| With a baseline | between about 170 and 195 (varies a little by computer) | about 185 |
| No baseline | 148, 163, 153, 156, 177 | 159 |
With the baseline, every seed averaged at least 165 steps, and most were in the 180s or 190s. Without it, the best seed reached only 177. The average gap is about 25 steps. In cart-pole every reward is +1, so every return is positive, which is exactly the situation from the slot-machine experiment. Subtracting the average return turns "everything looks good" into "this part was better than usual".
Values or policies?
Both families work on cart-pole. Which should you reach for?
| Value-based (DQN) | Policy-based (REINFORCE) | |
|---|---|---|
| Learns | how good each action is | the probability of each action |
| Actions | a list to take a max over | works with a list, and can also handle smooth, continuous actions |
| Exploration | needs a separate rule () | built in, since it chooses at random |
| Reuses old data | yes, through the replay buffer | no: learns from each fresh episode, then discards it |
| Noise | moderate | high, which the baseline helps with |
The last point is the price of policy gradients: they learn from fresh experience only, so they can be wasteful, and each update is noisy.
There is a way to get the best of both. Actor-critic methods keep two things: a policy (the actor) and a learned estimate of the value of each state (the critic). The critic replaces the crude "average return" baseline with a much better one, and gives an advantage after every step using the temporal-difference error from the TD chapter, so the actor can learn without waiting for the episode to end. Most modern RL algorithms are in this family. We will not build one here, but you now know every ingredient.
Run it yourself
At the bottom of this page you can download:
bandit_policy.pyis the three-machine example, with and without a baseline and a bonus. It needs only NumPy.cartpole.pyis the same cart-pole task as the last chapter.reinforce_cartpole.pyis the full REINFORCE learner, with a small network and the Adam optimiser. Add--baselineto repeat the comparison.check_numbers.pychecks every number quoted in this chapter, including a numerical check of the log-derivative trick.
Try changing the step size in bandit_policy.py. How large can it get before the learner goes wrong, even with a baseline?
Check yourself
Compute the softmax probabilities for the scores [2, 1, 0].
, and . They add up to about 11.107. The probabilities are , and .
Two actions with scores [0, 0]. Action 2 is taken, the reward is 2, the baseline is 0.5 and the step size is 0.1. By how much does each score change?
The probabilities are , so the slope of the log-probability is . The advantage is . The change is .
An episode's returns after steps 1, 2 and 3 are 5, 3 and 1. Using the average return as the baseline, what are the advantages? What does an advantage of zero mean?
The average is 3, so the advantages are , and . An advantage of zero means that step did exactly as well as average, so it gets no nudge either way.
Why does subtracting a baseline help when every reward is positive?
Without it, every action that gets taken is pushed up, because every reward is positive, so the learner reinforces whichever actions it happened to try, even bad ones. Subtracting a typical reward leaves only the differences: better-than-usual results push their action up, and worse-than-usual results push it down. On average it changes nothing, but it removes a lot of noise.
Why can a policy-gradient method handle continuous actions, like a steering angle, when DQN cannot?
DQN picks the best action by taking a max over a list of Q-values, and a continuous action has no finite list to take a max over. A policy can instead output the parameters of a probability distribution over the actions, such as the average and spread of a bell curve, and choose by sampling from it. The same log-derivative trick works for that distribution.
Recap
- Policy-based methods learn the policy directly, as a probability of each action, instead of learning values and then picking the best.
- The softmax turns scores into probabilities: .
- The log-derivative trick gives the slope of the expected reward as an average we can sample: . For a softmax, the slope of is one-hot minus the probabilities.
- The update is more of what worked: nudge the chosen action up in proportion to its reward.
- A baseline subtracts a typical reward. It leaves the average update unchanged but removes noise, which matters most when all rewards have the same sign.
- REINFORCE applies this to whole episodes, nudging each action by its advantage, the return minus the baseline. On cart-pole it averaged about 185 steps with a baseline and 159 without.
- Policy methods explore on their own, handle continuous actions, and use only fresh data. Actor-critic methods add a learned value function to cut the noise.
Where next
That completes the toolkit for a single learner. But Papersseum has five players, and each is learning at the same time. That changes everything: the world your agent faces keeps shifting as the others improve. Next, we look at what happens when the other players are learning too.
Code for this chapter
Run these on your own machine. Each one is short and commented.
- bandit_policy.py 3.3 KB
- cartpole.py 2.4 KB
- check_numbers.py 4.9 KB
- reinforce_cartpole.py 5.4 KB