The problem
PPO (Proximal Policy Optimization) improves a policy using samples collected by an older copy of it. Taking several gradient steps on the same samples is efficient but dangerous: the policy could move so far that the samples no longer describe it. PPO limits how far each token's probability can move, by clipping the update.
For every sampled token, with (how much more, or less, likely the new policy makes it than the old one) and advantage (how much better than expected the token was, positive or negative):
where (epsilon) is 0.2, so is forced into . is maximized: a token with positive advantage wants its probability to go up, one with negative advantage wants it down, but the clip stops the update once has gone past the limit.
Example. . Four tokens, all with :
- , : , , minimum : gradient 0 (clipped).
- , : , , minimum : gradient .
- , : , , minimum : gradient 0 (clipped).
- , : , , minimum : gradient .
Objective: . Gradient: . Clip fraction: 2 of 4 tokens are clipped, .
Where the gradient is zero
Where the unclipped term is the minimum, its derivative with respect to is (since ). Where the clipped term is the strict minimum, it does not change when the policy moves: derivative 0. That happens when:
- and : a good token already made more likely than allowed;
- and : a bad token already made less likely than allowed.
The other two cases keep their gradient: a mistake in the wrong direction (the second and fourth cases) is always corrected.
What to implement
For arrays of shape :
ppo_objective(logp_new, logp_old, adv, eps=0.2): .ppo_grad(logp_new, logp_old, adv, eps=0.2): , shape , including the of the mean.clip_fraction(logp_new, logp_old, adv, eps=0.2): the fraction of tokens whose term is clipped, that is, where the gradient is 0. Training logs track it: if it is large, the updates are being cut a lot.
Requirements
- Work from log-probabilities: compute with
np.expof the difference. - No loops.