Foxgrad's picture
Add learning methods (GA, ES, CEM, PPO)
719b42f verified
Raw History Blame Contribute Delete
4.94 kB
"""PPO (Proximal Policy Optimization) pour Irrational, Peace et 3D.
Chaque « partie » d'entraînement est un seul tir : le bot voit la situation, choisit
les coefficients de sa fonction, et reçoit sa récompense. Le réseau de politique donne
une moyenne, on ajoute du bruit pour explorer, et on renforce ce qui a mieux marché que
prévu par le réseau de valeur (le « critique »).
Le réseau de valeur sert aussi en combat : il estime la chance de toucher chaque ennemi,
et le bot vise celui où il se sent le plus sûr de lui.
"""
from __future__ import annotations
import numpy as np
from ..nets import MLP
from .common import Logger, line, split, task_rollout, validate
def _torch():
try:
import torch
return torch
except ImportError as e:
raise SystemExit("PPO a besoin de PyTorch : pip install torch") from e
def _mlp(torch, sizes):
layers = []
for i, (a, b) in enumerate(zip(sizes[:-1], sizes[1:])):
layers.append(torch.nn.Linear(a, b))
if i < len(sizes) - 2:
layers.append(torch.nn.Tanh())
return torch.nn.Sequential(*layers)
def _to_flat(net):
parts = []
for m in net:
if hasattr(m, "weight"):
parts += [m.weight.detach().cpu().numpy().T.ravel(), m.bias.detach().cpu().numpy().ravel()]
return np.concatenate(parts).astype(np.float32)
def _from_flat(torch, net, flat):
i = 0
with torch.no_grad():
for m in net:
if hasattr(m, "weight"):
a, b = m.in_features, m.out_features
m.weight.copy_(torch.tensor(flat[i:i + a * b].reshape(a, b).T)); i += a * b
m.bias.copy_(torch.tensor(flat[i:i + b])); i += b
def train_ppo(bot, pool, iters, rng, say=print, save=None, resume=False, val_every=5, best0=-1.0,
batch=1536, epochs=8, minibatch=256, lr=1e-3, clip=0.2, ent=0.0, log_std0=-1.0):
torch = _torch()
torch.manual_seed(int(rng.integers(1 << 30)))
sizes = bot.policy.sizes
vsizes = [sizes[0], *bot.hidden, 1]
pi, vf = _mlp(torch, sizes), _mlp(torch, vsizes)
log_std = torch.nn.Parameter(torch.full((sizes[-1],), log_std0))
if bot.trained_iters > 0: # reprise
_from_flat(torch, pi, bot.policy.flat)
if bot.value is not None:
_from_flat(torch, vf, bot.value.flat)
opt = torch.optim.Adam([*pi.parameters(), *vf.parameters(), log_std], lr=lr)
log = Logger(bot.key, resume)
best = best0
def export():
bot.policy.set_flat(_to_flat(pi))
bot.value = MLP(vsizes, "linear", flat=_to_flat(vf))
for it in range(1, iters + 1):
flat = _to_flat(pi)
ls = log_std.detach().numpy().copy()
seeds = rng.integers(0, 2 ** 31 - 1, batch)
jobs = [(bot.key, flat, ls, chunk, int(rng.integers(1 << 30))) for chunk in split(seeds, pool.workers * 2)]
res = pool.map(task_rollout, jobs)
obs = torch.tensor(np.concatenate([r[0] for r in res]), dtype=torch.float32)
act = torch.tensor(np.concatenate([r[1] for r in res]), dtype=torch.float32)
rew = torch.tensor(np.concatenate([r[2] for r in res]), dtype=torch.float32)
with torch.no_grad():
dist = torch.distributions.Normal(pi(obs), log_std.exp())
old_logp = dist.log_prob(act).sum(-1)
adv = rew - vf(obs).squeeze(-1)
adv = (adv - adv.mean()) / (adv.std() + 1e-8)
n = len(rew)
for _ in range(epochs):
perm = torch.randperm(n)
for k in range(0, n, minibatch):
idx = perm[k:k + minibatch]
dist = torch.distributions.Normal(pi(obs[idx]), log_std.exp())
logp = dist.log_prob(act[idx]).sum(-1)
ratio = (logp - old_logp[idx]).exp()
a = adv[idx]
loss_pi = -torch.min(ratio * a, ratio.clamp(1 - clip, 1 + clip) * a).mean()
loss_v = ((vf(obs[idx]).squeeze(-1) - rew[idx]) ** 2).mean()
loss = loss_pi + 0.5 * loss_v - ent * dist.entropy().sum(-1).mean()
opt.zero_grad()
loss.backward()
torch.nn.utils.clip_grad_norm_([*pi.parameters(), *vf.parameters(), log_std], 0.5)
opt.step()
with torch.no_grad():
log_std.clamp_(-2.5, 0.5)
if it % val_every == 0 or it == iters:
export()
v = validate(pool, bot.key, bot.policy.flat)
log.log(it, float(rew.mean()), v)
if v["touche"] > best:
best = v["touche"]
bot.trained_iters = it + log.offset
save(bot, "best")
say(line(bot.key, it + log.offset, iters + log.offset, float(rew.mean()), v, best,
f"(bruit {float(log_std.detach().exp().mean()):.2f})"))
export(); bot.trained_iters = iters + log.offset; save(bot, "last")
return best