Proximale beleidsoptimalisatie met PyTorch en Gymnasium

Proximal Policy Optimization (PPO) is een van de voorkeursalgoritmes om Reinforcement Learning (RL) problemen op te lossen. Het is ontwikkeld in 2017 door John Schuman, medeoprichter van OpenAI. 

PPO is veel gebruikt bij OpenAI om modellen te trainen die menselijk gedrag nabootsen. Het verbetert eerdere methoden zoals Trust Region Policy Optimization (TRPO) en is populair geworden omdat het een robuust en efficiënt algoritme is. 

In deze tutorial bekijken we PPO uitgebreid. We behandelen de theorie en demonstreren hoe het geïmplementeerd kan worden met behulp van PyTorch.

Begrip van Proximal Policy Optimization (PPO)

Traditionele supervised learning algoritmes werken de parameters bij in de richting van de steilste helling. Als deze update te groot blijkt te zijn, wordt dit gecorrigeerd tijdens daaropvolgende trainingsvoorbeelden die onafhankelijk van elkaar zijn.

De trainingsvoorbeelden in reinforcement learning bestaan echter uit de acties van de agent en de opbrengsten. Daardoor zijn de trainingsvoorbeelden gecorreleerd aan elkaar. De agent verkent de omgeving om de optimale strategie te ontdekken. Grote veranderingen in de helling kunnen ertoe leiden dat de strategie vastloopt in een slechte regio met suboptimale beloningen. Aangezien de agent de omgeving moet verkennen, maken grote veranderingen in de strategie het trainingsproces instabiel.

Methoden op basis van vertrouwensregio’s hebben als doel dit probleem te vermijden door ervoor te zorgen dat beleidsupdates binnen een vertrouwde regio vallen. Deze vertrouwde regio is een kunstmatig beperkt gebied binnen de beleidsruimte waarbinnen updates zijn toegestaan. Het bijgewerkte beleid kan alleen binnen een vertrouwde regio van het oude beleid liggen. Het waarborgen dat beleidsupdates incrementeel zijn, voorkomt instabiliteit.

Beleidsupdates in vertrouwensregio (TRPO)

Het algoritme voor beleidsupdates in vertrouwensregio (TRPO) werd voorgesteld in 2015 door John Schulman (die ook PPO voorstelde in 2017). Om het verschil tussen het oude beleid en het bijgewerkte beleid te meten, maakt TRPO gebruik van Kullback-Leibler (KL) divergentie. KL divergentie wordt gebruikt om het verschil tussen twee kansverdelingen te meten. TRPO bleek effectief te zijn bij het implementeren van vertrouwensregio’s. 

Het probleem met TRPO is de computationele complexiteit die gepaard gaat met KL divergentie. Het toepassen van KL divergentie moet worden uitgebreid tot de tweede orde met behulp van numerieke methoden zoals Taylor-uitbreiding. Dit is rekenkundig duur. PPO werd voorgesteld als een eenvoudiger en efficiënter alternatief voor TRPO. PPO knipt de verhouding van de beleidsregels af om de vertrouwensregio te benaderen zonder complexe berekeningen met KL divergentie te hoeven doen.

Dit is waarom PPO de voorkeur heeft gekregen boven TRPO bij het oplossen van RL-problemen. Door de efficiëntere methode van het schatten van vertrouwensgebieden, balanceert PPO effectief prestaties en stabiliteit.

Proximale beleidsbenadering (PPO)

PPO wordt vaak beschouwd als een subklasse van acteur-criticus methoden, die de beleidsgradiënten bijwerken op basis van de waardefunctie. Methoden zoals Advantage Actor-critic (A2C) maken gebruik van een parameter genaamd het voordeel. Dit meet het verschil tussen de rendementen voorspeld door de criticus en de gerealiseerde rendementen door de beleidsuitvoering.

Om PPO te begrijpen, moet je de componenten kennen:

  1. De acteur voert het beleid uit. Het wordt geïmplementeerd als een neuraal netwerk. Gegeven een staat als invoer, geeft het de actie om te nemen uit.
  2. De criticus is een andere neurale netwerk. Het neemt de staat als invoer en geeft de verwachte waarde van die staat als uitvoer. Zo drukt de criticus de staat-waarde functie uit.
  3. Op beleidsgradiënt-gebaseerde methoden kunnen ervoor kiezen om verschillende objectieve functies te gebruiken. In het bijzonder gebruikt PPO de voordeelfunctie. De voordeelfunctie meet het bedrag waarmee de cumulatieve beloning (gebaseerd op het beleid geïmplementeerd door de acteur) de verwachte baselinereward (zoals voorspeld door de criticus) overschrijdt. Het doel van PPO is om de waarschijnlijkheid te vergroten van het kiezen van acties met een hoog voordeel. Het optimalisatiedoel van PPO gebruikt verliesfuncties gebaseerd op deze voordeelfunctie.
  4. De bijgesneden doelfunctie is de belangrijkste innovatie in PPO.Het voorkomt grote beleidsaanpassingen in één trainingsiteratie. Het beperkt hoeveel het beleid wordt bijgewerkt in één iteratie. Om incrementele beleidsaanpassingen te meten, gebruiken op beleid gebaseerde methoden de kansverhouding van het nieuwe beleid ten opzichte van het oude beleid.
  5. De surrogaatverliesfunctie is de doelfunctie in PPO en houdt rekening met de eerder genoemde innovaties.Het wordt als volgt berekend:
    1. Bereken de daadwerkelijke verhouding (zoals hierboven uitgelegd) en vermenigvuldig deze met het voordeel.
    2. Knip het ratio af zodat het binnen een gewenst bereik valt. Vermenigvuldig het afgeknipte ratio met het voordeel.
    3. Neem de minimale waarde van de bovenstaande twee hoeveelheden.
  6. In de praktijk wordt ook een entropieterm toegevoegd aan het surrogaatverlies. Dit wordt de entropiebonus genoemd. Het is gebaseerd op de wiskundige verdeling van actieprobabiliteiten. Het idee achter de entropiebonus is om op gecontroleerde wijze wat extra willekeurigheid te introduceren. Hierdoor wordt het optimalisatieproces aangemoedigd om de actieruimte te verkennen. Een hoge entropiebonus bevordert exploratie boven exploitatie.

Begrip van het knipmechanisme

Stel dat onder het oude beleid πoud, de waarschijnlijkheid om actie a in staat s te ondernemen πoud(a|s) is. Onder het nieuwe beleid is de waarschijnlijkheid om dezelfde actie a vanuit dezelfde staat s te ondernemen bijgewerkt naar πnieuw(a|s). De verhouding van deze waarschijnlijkheden, als functie van de beleidsparameters θ, is r(θ). Wanneer het nieuwe beleid de actie waarschijnlijker maakt (in dezelfde staat), is de verhouding groter dan 1 en vice versa.  

Het knipmechanisme beperkt deze kansverhouding zodanig dat de nieuwe actiekansen binnen een bepaald percentage van de oude actiekansen moeten liggen. Bijvoorbeeld, r(θ) kan beperkt worden tot tussen 0.8 en 1.2. Dit voorkomt grote sprongen, wat op zijn beurt zorgt voor een stabiel trainingsproces.

In de rest van dit artikel leer je hoe je de componenten kunt assembleren voor een eenvoudige implementatie van PPO met behulp van PyTorch.

Voordat we PPO implementeren, moeten we de benodigde softwarebibliotheken installeren en een geschikte omgeving kiezen om het beleid toe te passen. 

PyTorch en vereiste bibliotheken installeren

We moeten de volgende software installeren: 

  • PyTorch en andere softwarebibliotheken, zoals numpy (voor wiskundige en statistische functies) en matplotlib (voor het plotten van grafieken).
  • Het open-source Gym softwarepakket van OpenAI, een Python-bibliotheek die verschillende omgevingen en spellen simuleert die kunnen worden opgelost met behulp van reinforcement learning. U kunt de Gym API gebruiken om uw algoritme te laten communiceren met de omgeving. Aangezien de functionaliteit van gym soms verandert tijdens het upgradeproces, bevriezen we in dit voorbeeld de versie op 0.25.2.

Om te installeren op een server of lokale machine, voer uit:

$ pip install torch numpy matplotlib gym==0.25.2

Om te installeren met een Notebook zoals Google Colab of DataLab, gebruik: 

!pip install torch numpy matplotlib gym==0.25.2

Creëer de CartPole-omgeving(en)

Gebruik OpenAI Gym om twee instanties (één voor training en een andere voor testen) van de CartPole-omgeving te creëren:

env_train = gym.make('CartPole-v1') env_test = gym.make('CartPole-v1')

Nu gaan we PPO implementeren met PyTorch.

De beleidsnetwerk definiëren

Zoals eerder uitgelegd, wordt PPO geïmplementeerd als een actor-critic model. De actor implementeert het beleid, en de critic voorspelt de geschatte waarde. Zowel de actor- als de critic-neurale netwerken nemen dezelfde invoer – de staat op elk tijdstip. Daarom kunnen de actor- en critic-modellen een gemeenschappelijk neurale netwerk delen, dat de backbone-architectuur wordt genoemd. De actor en critic kunnen de backbone-architectuur uitbreiden met extra lagen. 

Definieer het backbone-netwerk

De volgende stappen beschrijven het backbone-netwerk:

  • Implementeer een netwerk met 3 lagen – een invoerlaag, een verborgen laag en een uitvoerlaag. 
  • Na de invoer- en verborgen lagen gebruiken we een activatiefunctie. In deze tutorial kiezen we ReLU omdat het computationeel efficiënt is.
  • We passen ook een dropout-functie toe na de invoer- en verborgen lagen om een robuust netwerk te verkrijgen. De dropout-functie maakt willekeurig enkele neuronen nul. Hierdoor wordt de afhankelijkheid van specifieke neuronen verminderd en overpassing voorkomen, waardoor het netwerk robuuster wordt.

De onderstaande code implementeert het hoofdgedeelte:

class BackboneNetwork(nn.Module): def __init__(self, in_features, hidden_dimensions, out_features, dropout): super().__init__() self.layer1 = nn.Linear(in_features, hidden_dimensions) self.layer2 = nn.Linear(hidden_dimensions, hidden_dimensions) self.layer3 = nn.Linear(hidden_dimensions, out_features) self.dropout = nn.Dropout(dropout) def forward(self, x): x = self.layer1(x) x = f.relu(x) x = self.dropout(x) x = self.layer2(x) x = f.relu(x) x = self.dropout(x) x = self.layer3(x) return x

Definieer het actor-critic netwerk

Nu kunnen we dit netwerk gebruiken om de actor-critic klasse, ActorCritic, te definiëren. De actor modelleert het beleid en voorspelt de actie. De criticus modelleert de waardefunctie en voorspelt de waarde. Ze nemen beide de staat als invoer.

class ActorCritic(nn.Module): def __init__(self, actor, critic): super().__init__() self.actor = actor self.critic = critic def forward(self, state): action_pred = self.actor(state) value_pred = self.critic(state) return action_pred, value_pred

Instantieer de actoren criticus-netwerken

We zullen de hierboven gedefinieerde netwerken gebruiken om een acteur en een criticus te creëren. Vervolgens zullen we een agent maken, inclusief de acteur en de criticus.

Voordat de agent wordt gemaakt, initialiseer de parameters van het netwerk:

  • De dimensies van de verborgen laag, H, die een configureerbare parameter is. De grootte en het aantal verborgen lagen zijn afhankelijk van de complexiteit van het probleem. We zullen een verborgen laag met de dimensies 64 x 64 gebruiken.
  • Invoerkenmerken, N, waar N de grootte van de statusarray is. De invoerlaag heeft dimensies N x H. In de CartPole-omgeving is de status een 4-elementenarray. Dus N is 4.
  • Uitvoerkenmerken van het actorennetwerk, O, waar O het aantal acties in de omgeving is. De uitvoerlaag van de acteur heeft dimensies H x O. De CartPole-omgeving heeft 2 acties.
  • Uitvoerkenmerken van het criticusnetwerk. Aangezien het criticusnetwerk alleen de verwachte waarde voorspelt (gegeven een invoerstatus), is het aantal uitvoerkenmerken 1.
  • Dropout als een breuk.

De volgende code toont hoe de acteur- en criticusnetwerken worden gedeclareerd op basis van het ruggengraatnetwerk:

def create_agent(hidden_dimensions, dropout): INPUT_FEATURES = env_train.observation_space.shape[0] HIDDEN_DIMENSIONS = hidden_dimensions ACTOR_OUTPUT_FEATURES = env_train.action_space.n CRITIC_OUTPUT_FEATURES = 1 DROPOUT = dropout actor = BackboneNetwork( INPUT_FEATURES, HIDDEN_DIMENSIONS, ACTOR_OUTPUT_FEATURES, DROPOUT) critic = BackboneNetwork( INPUT_FEATURES, HIDDEN_DIMENSIONS, CRITIC_OUTPUT_FEATURES, DROPOUT) agent = ActorCritic(actor, critic) return agent

Het berekenen van de opbrengsten

De omgeving geeft een beloning voor elke stap naar de volgende, afhankelijk van de actie van de agent. De beloning, R, wordt uitgedrukt als:

De opbrengst wordt gedefinieerd als de geaccumuleerde waarde van verwachte toekomstige beloningen. Beloningen van tijdstappen die verder in de toekomst liggen, zijn minder waardevol dan directe beloningen. Daarom wordt de opbrengst doorgaans berekend als de verdisconteerde opbrengst, G, gedefinieerd als:

In deze zelfstudie (en vele andere referenties) verwijst opbrengst naar de verdisconteerde opbrengst. 

Om de opbrengst te berekenen:

  • Begin met de verwachte beloningen van alle toekomstige staten.
  • Vermenigvuldig elke toekomstige beloning met een macht van de discontofactor, . Bijvoorbeeld, de verwachte beloning na 2 tijdstappen (vanaf het heden) wordt vermenigvuldigd met 2. 
  • Tel alle verdisconteerde toekomstige beloningen op om de opbrengst te berekenen. 
  • Normaliseer de waarde van de opbrengst. 

De functie calculate_returns() voert deze berekeningen uit, zoals hieronder getoond:

def calculate_returns(rewards, discount_factor): returns = [] cumulative_reward = 0 for r in reversed(rewards): cumulative_reward = r + cumulative_reward * discount_factor returns.insert(0, cumulative_reward) returns = torch.tensor(returns) # normalize the return returns = (returns - returns.mean()) / returns.std() return returns

Implementatie van de voordelenfunctie

De voordelen worden berekend als het verschil tussen de waarde voorspeld door de criticus en de verwachte opbrengst van de acties gekozen door de acteur volgens het beleid. Voor een gegeven actie drukt het voordeel het voordeel uit van het nemen van die specifieke actie boven een willekeurige (gemiddelde) actie.

In de originele PPO-paper (vergelijking 10) wordt het voordeel, kijkend tot tijdstap T, uitgedrukt als:

Tijdens het coderen van het algoritme wordt de beperking om vooruit te kijken tot een vast aantal tijdstappen afgedwongen via de batchgrootte. Dus, de bovenstaande vergelijking kan worden vereenvoudigd als het verschil tussen de waarde en de verwachte opbrengsten. De verwachte opbrengsten worden gekwantificeerd in de state-action value-functie, Q. 

Dus, de onderstaande vereenvoudigde formule drukt het voordeel van het kiezen uit: 

  • een bepaalde actie 
  • in een gegeven staat 
  • onder een bepaald beleid 
  • op een bepaald tijdstip 

Dit wordt uitgedrukt als: 

OpenAI gebruikt ook deze formule om RL te implementeren. De calculate_advantages() functie hieronder berekent het voordeel:

def calculate_advantages(returns, values): advantages = returns - values # Normaliseer het voordeel advantages = (advantages - advantages.mean()) / advantages.std() return advantages

Surrogaatverlies en knipmechanisme

Het beleidsverlies zou het standaard beleidsgradiëntverlies zijn zonder speciale technieken zoals PPO. Het standaard beleidsgradiëntverlies wordt berekend als het product van:

  • De beleidsactiekansen
  • De voordeelfunctie, die wordt berekend als het verschil tussen:
    • De beleidsopbrengst
    • De verwachte waarde

Het standaard beleidsgradiëntverlies kan geen correcties aanbrengen voor plotselinge beleidswijzigingen. Het surrogaatverlies past het standaard verlies aan om de hoeveelheid te beperken die het beleid in elke iteratie kan veranderen. Het is het minimum van twee hoeveelheden:

  • Het product van:
    • De beleidsverhouding. Deze verhouding drukt het verschil uit tussen de oude en nieuwe actiekansen.
    • De voordeelfunctie
  • Het product van:
    • De geklemde waarde van de beleidsverhouding. Deze verhouding wordt bijgesneden zodat het bijgewerkte beleid binnen een bepaald percentage van het oude beleid blijft.
    • De voordeelfunctie

Voor het optimalisatieproces wordt het surrogaatverlies gebruikt als een proxy voor het daadwerkelijke verlies.

Het knipmechanisme

De beleidsratio, R, is het verschil tussen de nieuwe en oude beleidsregels en wordt gegeven als de verhouding van de logwaarschijnlijkheden van het beleid onder de nieuwe en oude parameters:

De bijgesneden beleidsratio, R’, is beperkt zodanig dat:

Gegeven het voordeel, At, zoals getoond in de vorige sectie, en de beleidsratio, zoals hierboven getoond, wordt het surrogaatverlies berekend als:

Hieronder wordt de code getoond voor het implementeren van het knipmechanisme en het surrogaatverlies.

def calculate_surrogate_loss( actions_log_probability_old, actions_log_probability_new, epsilon, advantages): advantages = advantages.detach() policy_ratio = ( actions_log_probability_new - actions_log_probability_old ).exp() surrogate_loss_1 = policy_ratio * advantages surrogate_loss_2 = torch.clamp( policy_ratio, min=1.0-epsilon, max=1.0+epsilon ) * advantages surrogate_loss = torch.min(surrogate_loss_1, surrogate_loss_2) return surrogate_loss

Laten we nu de agent trainen.

Het berekenen van beleids- en waardeverlies

We zijn nu klaar om de beleids- en waardeverliezen te berekenen:

  • Het beleidsverlies is de som van het surrogate verlies en de entropiebonus.
  • Het waardeverlies is gebaseerd op het verschil tussen de waarde die door de criticus wordt voorspeld en de rendementen (cumulatieve beloning) die door het beleid worden gegenereerd. De berekening van het waardeverlies maakt gebruik van de Smooth L1 Loss-functie. Dit helpt om de verliesfunctie te verzachten en maakt deze minder gevoelig voor uitschieters.

Beide verliezen, zoals hierboven berekend, zijn tensoren. Gradient descent is gebaseerd op scalare waarden. Om een enkele scalare waarde te krijgen die het verlies vertegenwoordigt, gebruik je de .sum()-functie om de tensorelementen op te tellen. De onderstaande functie laat zien hoe je dit doet:

def calculate_losses( surrogate_loss, entropy, entropy_coefficient, returns, value_pred): entropy_bonus = entropy_coefficient * entropy policy_loss = -(surrogate_loss + entropy_bonus).sum() value_loss = f.smooth_l1_loss(returns, value_pred).sum() return policy_loss, value_loss

De trainingslus definiëren

Voor het starten van het trainingsproces, maak een set buffers aan als lege arrays. Het trainingsalgoritme zal deze buffers gebruiken om informatie over de acties van de agent, de toestanden van de omgeving en de beloningen op te slaan in elke tijdstap. De onderstaande functie initialiseert deze buffers:

def init_training(): states = [] actions = [] actions_log_probability = [] values = [] rewards = [] done = False episode_reward = 0 return states, actions, actions_log_probability, values, rewards, done, episode_reward

Elke trainingsiteratie laat de agent lopen met de beleidsparameters voor die iteratie. De agent interageert met de omgeving in tijdstappen in een lus totdat het een eindvoorwaarde bereikt.

Na elke tijdstap worden de actie, beloning en waarde van de agent toegevoegd aan de respectievelijke buffers. Wanneer de aflevering eindigt, retourneert de functie de bijgewerkte set buffers, die de resultaten van de aflevering samenvatten.

Voordat de trainingslus wordt uitgevoerd:

  • Zet het model in de trainingsmodus met agent.train().
  • Reset de omgeving naar een willekeurige staat met env.reset(). Dit is de startstaat voor deze trainingsiteratie.

De volgende stappen leggen uit wat er gebeurt in elke tijdstap in de trainingslus:

  • Geef de staat door aan de agent.
  • De agent retourneert:
    • De voorspelde actie gegeven de staat, gebaseerd op het beleid (actor). Stuur deze voorspelde actietensor door de softmax-functie om de reeks actiekansen te krijgen.
    • De voorspelde waarde van de staat, gebaseerd op de criticus.
  • De agent selecteert de actie die moet worden ondernomen:
    • Gebruik de actiekansen om de kansverdeling te schatten.
    • Kies willekeurig een actie door een monster uit deze verdeling te trekken. De dist.sample() functie doet dit.
  • Gebruik de env.step() functie om deze actie aan de omgeving door te geven om de reactie van de omgeving voor deze tijdstap te simuleren. Op basis van de actie van de agent genereert de omgeving:
    • De nieuwe staat
    • De beloning
    • De boolean retourwaarde done (dit geeft aan of de omgeving een terminale staat heeft bereikt)
  • Voeg de waarden van de actie van de agent, de beloningen, de voorspelde waarden en de nieuwe staat toe aan de respectievelijke buffers.

De trainingsaflevering eindigt wanneer de functie env.step() true retourneert voor de boolean retourwaarde van done.

Na afloop van de aflevering, gebruik de opgebouwde waarden van elke tijdstap om de cumulatieve opbrengsten van deze aflevering te berekenen door de beloningen van elke tijdstap toe te voegen. We gebruiken de calculate_returns() functie die eerder is beschreven om dit te doen. De invoer van deze functie zijn de disconteringsfactor en de buffer met de beloningen van elke tijdstap. We gebruiken deze opbrengsten en de opgebouwde waarden van elke tijdstap om de voordelen te berekenen met behulp van de calculate_advantages() functie.

De volgende Python-functie toont hoe deze stappen geïmplementeerd kunnen worden:

def forward_pass(env, agent, optimizer, discount_factor): states, actions, actions_log_probability, values, rewards, done, episode_reward = init_training() state = env.reset() agent.train() while not done: state = torch.FloatTensor(state).unsqueeze(0) states.append(state) action_pred, value_pred = agent(state) action_prob = f.softmax(action_pred, dim=-1) dist = distributions.Categorical(action_prob) action = dist.sample() log_prob_action = dist.log_prob(action) state, reward, done, _ = env.step(action.item()) actions.append(action) actions_log_probability.append(log_prob_action) values.append(value_pred) rewards.append(reward) episode_reward += reward states = torch.cat(states) actions = torch.cat(actions) actions_log_probability = torch.cat(actions_log_probability) values = torch.cat(values).squeeze(-1) returns = calculate_returns(rewards, discount_factor) advantages = calculate_advantages(returns, values) return episode_reward, states, actions, actions_log_probability, advantages, returns

Het bijwerken van de modelparameters

Elke trainingsiteratie voert het model door een volledige aflevering bestaande uit vele tijdstappen (totdat het een eindvoorwaarde bereikt). In elke tijdstap slaan we de beleidsparameters, de actie van de agent, de opbrengsten en de voordelen op. Na elke iteratie werken we het model bij op basis van de prestaties van het beleid gedurende alle tijdstappen in die iteratie.

Het maximale aantal tijdstappen in de CartPole-omgeving is 500. In complexere omgevingen zijn er meer tijdstappen, zelfs miljoenen. In dergelijke gevallen moet de dataset met trainingsresultaten worden opgesplitst in batches. Het aantal tijdstappen in elke batch wordt de optimalisatie batchgrootte genoemd.

Dus, de stappen om de modelparameters bij te werken zijn:

  • Splits de dataset met trainingsresultaten in batches.
  • Voor elke batch:
    • Krijg de actie van de agent en de voorspelde waarde voor elke staat.
    • Gebruik deze voorspelde acties om de nieuwe actie kansverdeling te schatten.
    • Gebruik deze verdeling om de entropie te berekenen.
    • Gebruik deze verdeling om de log waarschijnlijkheid van de acties in de trainingsresultaten dataset te verkrijgen. Dit is de nieuwe set van log waarschijnlijkheden van de acties in de trainingsresultaten dataset. De oude set van log waarschijnlijkheden van dezezelfde acties werd berekend in de trainingslus die in de vorige sectie werd uitgelegd.
    • Bereken het surrogate verlies met behulp van de oude en nieuwe waarschijnlijkheidsverdelingen van de acties.
    • Bereken het beleidsverlies en het waardeverlies met gebruik van het surrogate verlies, de entropie en de voordelen.
    • Voer .backward() afzonderlijk uit op de beleids- en waardeverliezen. Dit werkt de gradients op de verliesfuncties bij.
    • Voer .step() uit op de optimizer om de beleidsparameters bij te werken. In dit geval gebruiken we de Adam optimizer om snelheid en robuustheid in balans te brengen.
    • Accumuleer de beleids- en waardeverliezen.
  • Herhaal de backward pass (de bovenstaande operaties) een paar keer op elke batch, afhankelijk van de waarde van de parameter PPO_STEPS. Het herhalen van de backward pass op elke batch is rekenkundig efficiënt omdat het effectief de omvang van de trainingsdataset vergroot zonder extra forward passes uit te voeren. Het aantal omgevingsstappen in elke afwisseling tussen sampling en optimalisatie wordt de iteratie batchgrootte genoemd.
  • Geef het gemiddelde beleidsverlies en waardeverlies terug.

De onderstaande code implementeert deze stappen:

def update_policy( agent, states, actions, actions_log_probability_old, advantages, returns, optimizer, ppo_steps, epsilon, entropy_coefficient): BATCH_SIZE = 128 total_policy_loss = 0 total_value_loss = 0 actions_log_probability_old = actions_log_probability_old.detach() actions = actions.detach() training_results_dataset = TensorDataset( states, actions, actions_log_probability_old, advantages, returns) batch_dataset = DataLoader( training_results_dataset, batch_size=BATCH_SIZE, shuffle=False) for _ in range(ppo_steps): for batch_idx, (states, actions, actions_log_probability_old, advantages, returns) in enumerate(batch_dataset): # verkrijg nieuwe log kans van acties voor alle invoerstaten action_pred, value_pred = agent(states) value_pred = value_pred.squeeze(-1) action_prob = f.softmax(action_pred, dim=-1) probability_distribution_new = distributions.Categorical( action_prob) entropy = probability_distribution_new.entropy() # schat nieuwe log kansen in met behulp van oude acties actions_log_probability_new = probability_distribution_new.log_prob(actions) surrogate_loss = calculate_surrogate_loss( actions_log_probability_old, actions_log_probability_new, epsilon, advantages) policy_loss, value_loss = calculate_losses( surrogate_loss, entropy, entropy_coefficient, returns, value_pred) optimizer.zero_grad() policy_loss.backward() value_loss.backward() optimizer.step() total_policy_loss += policy_loss.item() total_value_loss += value_loss.item() return total_policy_loss / ppo_steps, total_value_loss / ppo_steps

Laten we eindelijk de PPO-agent uitvoeren.

De prestaties evalueren 

Om de prestaties van de agent te evalueren, maak je een nieuwe omgeving aan en bereken je de cumulatieve beloningen van het laten draaien van de agent in deze nieuwe omgeving. Je moet de agent instellen op evaluatiemodus met behulp van de .eval()-functie. De stappen zijn hetzelfde als voor de trainingslus. Het onderstaande codefragment implementeert de evaluatiefunctie: 

def evaluate(env, agent): agent.eval() rewards = [] done = False episode_reward = 0 state = env.reset() while not done: state = torch.FloatTensor(state).unsqueeze(0) with torch.no_grad(): action_pred, _ = agent(state) action_prob = f.softmax(action_pred, dim=-1) action = torch.argmax(action_prob, dim=-1) state, reward, done, _ = env.step(action.item()) episode_reward += reward return episode_reward

Visualiseren van trainingsresultaten

We zullen de Matplotlib-bibliotheek gebruiken om de voortgang van het trainingsproces te visualiseren. De onderstaande functie laat zien hoe je de beloningen van zowel de trainings- als de testlussen kunt plotten:

def plot_train_rewards(train_rewards, reward_threshold): plt.figure(figsize=(12, 8)) plt.plot(train_rewards, label='Training Reward') plt.xlabel('Episode', fontsize=20) plt.ylabel('Training Reward', fontsize=20) plt.hlines(reward_threshold, 0, len(train_rewards), color='y') plt.legend(loc='lower right') plt.grid() plt.show()
def plot_test_rewards(test_rewards, reward_threshold): plt.figure(figsize=(12, 8)) plt.plot(test_rewards, label='Testing Reward') plt.xlabel('Episode', fontsize=20) plt.ylabel('Testing Reward', fontsize=20) plt.hlines(reward_threshold, 0, len(test_rewards), color='y') plt.legend(loc='lower right') plt.grid() plt.show()

In de onderstaande voorbeeldplots tonen we de trainings- en testbeloningen, verkregen door het beleid respectievelijk toe te passen in de trainings- en testomgeving. Let op dat de vorm van deze plots elke keer dat je de code uitvoert anders zal lijken. Dit komt door de willekeurigheid die inherent is aan het trainingsproces.

Trainingsbeloningen (verkregen door het beleid toe te passen in de trainingsomgeving). Afbeelding door Auteur.

Testbeloningen (verkregen door het beleid toe te passen in de testomgeving). Afbeelding door Auteur.

In de bovenstaande outputgrafieken, observeer de voortgang van het trainingsproces:

  • De beloning begint vanaf lage waarden. Naarmate de training vordert, nemen de beloningen toe.
  • De beloningen fluctueren willekeurig terwijl ze toenemen. Dit komt doordat de agent de beleidsruimte verkent.
  • De training wordt beëindigd en de testbeloningen hebben zich gestabiliseerd rond de drempelwaarde (475) gedurende vele iteraties.
  • De beloningen zijn gemaximeerd op 500. Dit zijn beperkingen die zijn opgelegd door de omgeving (Gym CartPole v1).

Vergelijkbaar kun je de waarde- en beleidsverliezen plotten door de iteraties:

def plot_losses(policy_losses, value_losses): plt.figure(figsize=(12, 8)) plt.plot(value_losses, label='Value Losses') plt.plot(policy_losses, label='Policy Losses') plt.xlabel('Episode', fontsize=20) plt.ylabel('Loss', fontsize=20) plt.legend(loc='lower right') plt.grid() plt.show()

De onderstaande voorbeeldplot toont de verliezen die bijgehouden worden tijdens de trainingsepisoden:

Waarde- en beleidsverliezen tijdens het trainingsproces. Afbeelding door Auteur

Let op de grafiek en merk op:

  • De verliezen lijken willekeurig verdeeld te zijn en volgen geen enkel patroon.
  • Dit is typisch voor RL-training, waar het doel niet is om het verlies te minimaliseren, maar om de beloningen te maximaliseren.

Voer het PPO-algoritme uit

Je hebt nu alle onderdelen om de agent te trainen met behulp van PPO. Om alles samen te voegen, moet je:

  • Hyperparameters verklaren zoals discontovoet, batchgrootte, leersnelheid, enz.
  • Instantieer buffers als lege arrays om de beloningen en verliezen van elke iteratie op te slaan.
  • Maak een instantie van een agent aan met behulp van de functie create_agent().
  • Voer iteratief voorwaartse en achterwaartse passes uit met behulp van de functies forward_pass() en update_policy().
  • Test de prestaties van het beleid met behulp van de functie evaluate().
  • Voeg het beleid, de waardeverliezen en beloningen uit de trainings- en evaluatiefuncties toe aan de respectieve buffers.
  • Bereken het gemiddelde van de beloningen en verliezen over de laatste paar tijdstappen. Het onderstaande voorbeeld berekent het gemiddelde van de beloningen en verliezen over de laatste 40 tijdstappen.
  • Print de resultaten van de evaluatie om de paar stappen. Het onderstaande voorbeeld print elke 10 stappen.
  • Beëindig het proces wanneer het gemiddelde beloning een bepaalde drempel overschrijdt.

De onderstaande code toont hoe je een functie declareert die dit doet in Python:

def run_ppo(): MAX_EPISODES = 500 DISCOUNT_FACTOR = 0.99 REWARD_THRESHOLD = 475 PRINT_INTERVAL = 10 PPO_STEPS = 8 N_TRIALS = 100 EPSILON = 0.2 ENTROPY_COEFFICIENT = 0.01 HIDDEN_DIMENSIONS = 64 DROPOUT = 0.2 LEARNING_RATE = 0.001 train_rewards = [] test_rewards = [] policy_losses = [] value_losses = [] agent = create_agent(HIDDEN_DIMENSIONS, DROPOUT) optimizer = optim.Adam(agent.parameters(), lr=LEARNING_RATE) for episode in range(1, MAX_EPISODES+1): train_reward, states, actions, actions_log_probability, advantages, returns = forward_pass( env_train, agent, optimizer, DISCOUNT_FACTOR) policy_loss, value_loss = update_policy( agent, states, actions, actions_log_probability, advantages, returns, optimizer, PPO_STEPS, EPSILON, ENTROPY_COEFFICIENT) test_reward = evaluate(env_test, agent) policy_losses.append(policy_loss) value_losses.append(value_loss) train_rewards.append(train_reward) test_rewards.append(test_reward) mean_train_rewards = np.mean(train_rewards[-N_TRIALS:]) mean_test_rewards = np.mean(test_rewards[-N_TRIALS:]) mean_abs_policy_loss = np.mean(np.abs(policy_losses[-N_TRIALS:])) mean_abs_value_loss = np.mean(np.abs(value_losses[-N_TRIALS:])) if episode % PRINT_INTERVAL == 0: print(f'Episode: {episode:3} | \ Mean Train Rewards: {mean_train_rewards:3.1f} \ | Mean Test Rewards: {mean_test_rewards:3.1f} \ | Mean Abs Policy Loss: {mean_abs_policy_loss:2.2f} \ | Mean Abs Value Loss: {mean_abs_value_loss:2.2f}') if mean_test_rewards >= REWARD_THRESHOLD: print(f'Reached reward threshold in {episode} episodes') break plot_train_rewards(train_rewards, REWARD_THRESHOLD) plot_test_rewards(test_rewards, REWARD_THRESHOLD) plot_losses(policy_losses, value_losses)

Voer het programma uit:

run_ppo()

De output zou moeten lijken op het voorbeeld hieronder:

Episode: 10 | Mean Train Rewards: 22.3 | Mean Test Rewards: 30.4 | Mean Abs Policy Loss: 0.37 | Mean Abs Value Loss: 0.39 Episode: 20 | Mean Train Rewards: 38.6 | Mean Test Rewards: 69.8 | Mean Abs Policy Loss: 0.46 | Mean Abs Value Loss: 0.37 . . . Episode: 100 | Mean Train Rewards: 289.5 | Mean Test Rewards: 427.3 | Mean Abs Policy Loss: 1.73 | Mean Abs Value Loss: 0.21 Episode: 110 | Mean Train Rewards: 357.7 | Mean Test Rewards: 461.4 | Mean Abs Policy Loss: 1.86 | Mean Abs Value Loss: 0.22 Reached reward threshold in 116 episodes

Je kunt het werkende programma bekijken en uitvoeren op deze DataLab-notitieboek!

In machine learning regelen hyperparameters het trainingsproces. Hieronder leg ik enkele van de belangrijke hyperparameters uit die worden gebruikt in PPO:

  • Leersnelheid: De leersnelheid bepaalt hoeveel beleidsparameters kunnen variëren in elke iteratie. Bij stochastische gradiëntafstamming wordt de hoeveelheid waarmee de beleidsparameters worden bijgewerkt in elke iteratie bepaald door het product van de leersnelheid en de gradiënt.
  • Clipping parameter: Dit wordt ook wel epsilon genoemd, ε. Het bepaalt in hoeverre de beleidsverhouding wordt afgekapt. De verhouding tussen de nieuwe en oude beleidsregels mag variëren binnen het bereik [1-ε, 1+ε]. Wanneer het buiten dit bereik valt, wordt het kunstmatig afgekapt om binnen het bereik te liggen.
  • Batchgrootte: Dit verwijst naar het aantal stappen dat wordt overwogen voor elke gradiëntupdate. In PPO is de batchgrootte het aantal tijdstappen dat nodig is om het beleid toe te passen en de surrogaatverlies te berekenen om de beleidsparameters bij te werken. In dit artikel hebben we een batchgrootte van 64 gebruikt.
  • Iteratiestappen: Dit is het aantal keren dat elke batch opnieuw wordt gebruikt om de achterwaartse doorgang uit te voeren. De code in dit artikel verwijst hiernaar als PPO_STEPS. In complexe omgevingen is het uitvoeren van de voorwaartse doorgang vele malen kostbaar in rekenkracht. Een efficiënter alternatief is om elke batch een paar keer opnieuw uit te voeren. Het wordt doorgaans aanbevolen om een waarde tussen 5 en 10 te gebruiken.
  • Kortingsfactor: Dit wordt ook wel gamma genoemd, γ. Het drukt uit in hoeverre directe beloningen waardevoller zijn dan toekomstige beloningen. Dit lijkt op het concept van rentetarieven bij het berekenen van de tijdswaarde van geld. Wanneer dichter bij 0 ligt, betekent dit dat toekomstige beloningen minder waardevol zijn en de agent de voorkeur moet geven aan directe beloningen. Wanneer dichter bij 1 ligt, betekent dit dat toekomstige beloningen belangrijk zijn.
  • Entropiecoëfficiënt: De entropiecoëfficiënt bepaalt de entropiebonus, die wordt berekend als het product van de entropiecoëfficiënt en de entropie van de verdeling. De rol van de entropiebonus is om meer willekeurigheid in het beleid te introduceren. Dit moedigt de agent aan om de beleidsruimte te verkennen. Echter, de training slaagt er niet in om te convergeren naar een optimale beleid wanneer deze willekeur te hoog is.
  • Succescriteria voor de training: Je moet de criteria vaststellen om te beslissen wanneer de training succesvol is. Een veelvoorkomende manier om dit te doen is door een voorwaarde te stellen dat de gemiddelde beloningen over de laatste N proeven (episodes) boven een bepaalde drempel liggen. In de bovenstaande voorbeeldcode wordt dit uitgedrukt met de variabele N_TRIALS. Wanneer deze op een hogere waarde wordt ingesteld, duurt de training langer omdat de beleidsstrategie de drempelbeloning over meer episodes moet behalen. Het resulteert ook in een robuuster beleid, hoewel het computationeel duurder is. Merk op dat PPO een stochastisch beleid is en er zullen episodes zijn waarin de agent de drempel niet overschrijdt. Dus, als de waarde van N_TRIALS te hoog is, kan je training mogelijk niet beëindigd worden.

Strategieën voor het optimaliseren van de PPO-prestaties

Het optimaliseren van de prestaties van het trainen van PPO-algoritmen omvat trial and error en experimenteren met verschillende hyperparameterwaarden. Er zijn echter enkele algemene richtlijnen:

  • Disconto factor: Wanneer lange termijn beloningen belangrijk zijn, zoals in de CartPole-omgeving, waar de paal stabiel moet blijven in de loop van de tijd, begin dan met een gematigde gamma waarde, zoals 0,99.
  • Entropiebonus: In complexe omgevingen moet de agent de actieruimte verkennen om het optimale beleid te vinden. De entropiebonus bevordert verkenning. De entropiebonus wordt toegevoegd aan het surrogaatverlies. Controleer de omvang van het surrogaatverlies en de entropie van de verdeling voordat u de entropiecoëfficiënt beslist. In dit artikel gebruikten we een entropiecoëfficiënt van 0,01.
  • Clipping parameter:De knipparameter bepaalt hoe verschillend het bijgewerkte beleid kan zijn van het huidige beleid. Een grote waarde van de knipparameter moedigt een betere verkenning van de omgeving aan, maar brengt het risico met zich mee dat de training instabiel wordt. Je wilt een knipparameter die geleidelijke verkenning mogelijk maakt terwijl destabiliserende updates worden voorkomen. In dit artikel hebben we een knipparameter van 0.2 gebruikt.
  • Leersnelheid:Wanneer de leersnelheid te hoog is, wordt het beleid in grote stappen bijgewerkt, en elke iteratie kan het trainingsproces instabiel worden. Als het te laag is, duurt de training te lang. Deze tutorial gebruikte een leersnelheid van 0.001, wat goed werkt voor de omgeving. In veel gevallen wordt aanbevolen om een leersnelheid van 1e-5 te gebruiken.

Uitdagingen en Beste Praktijken in PPO

Na het uitleggen van de concepten en implementatiedetails van PPO, laten we de uitdagingen en beste praktijken bespreken.

Veelvoorkomende uitdagingen bij het trainen van PPO

Hoewel PPO veel wordt gebruikt, moet je op de hoogte zijn van potentiële uitdagingen om met succes problemen in de echte wereld op te lossen met behulp van deze techniek. Enkele van deze uitdagingen zijn:

  • Langzame convergentie:In complexe omgevingen kan PPO sample-inefficiënt zijn en zijn er veel interacties met de omgeving nodig om te convergeren naar het optimale beleid. Dit maakt het traag en duur om te trainen.
  • Gevoeligheid voor hyperparameters: PPO vertrouwt op het efficiënt verkennen van de beleidsruimte. De stabiliteit van het trainingsproces en de snelheid van convergentie zijn gevoelig voor de waarden van de hyperparameters. De optimale waarden van deze hyperparameters kunnen vaak alleen worden bepaald door middel van trial-and-error.
  • Overaanpassing: RL-omgevingen worden doorgaans geïnitialiseerd met willekeurige parameters. PPO-training is gebaseerd op het vinden van het optimale beleid op basis van de omgeving van de agent. Soms convergeert het trainingsproces naar een reeks optimale parameters voor een specifieke omgeving, maar niet voor willekeurige omgevingen. Dit wordt meestal aangepakt door veel iteraties te hebben, elk met een anders gevarieerde trainingsomgeving.
  • Dynamische omgevingen:Eenvoudige RL-omgevingen, zoals de CartPole-omgeving, zijn statisch – de regels blijven hetzelfde in de loop van de tijd. Veel andere omgevingen, zoals een robot die leert lopen op een instabiele bewegende ondergrond, zijn dynamisch – de regels van de omgeving veranderen in de loop van de tijd. Om goed te presteren in dergelijke omgevingen, heeft PPO vaak aanvullende fijnafstemming nodig.
  • Exploratie versus exploitatie:Het knipmechanisme van PPO zorgt ervoor dat beleidsupdates binnen een vertrouwd gebied blijven. Het voorkomt echter ook dat de agent de actieruimte verkent. Dit kan leiden tot convergentie naar lokale optima, met name in complexe omgevingen. Aan de andere kant kan het de agent te veel laten verkennen voorkomen dat deze convergeren naar een optimale beleid.

Best practices voor het trainen van PPO-modellen

Om goede resultaten te behalen met PPO, raad ik enkele beste werkwijzen aan, zoals:

  • Normaliseer invoerkenmerken: Het normaliseren van de waarden van opbrengsten en voordelen vermindert de variabiliteit in gegevens en leidt tot stabiele gradiëntupdates. Het normaliseren van de gegevens brengt alle waarden naar een consistente numerieke reeks. Het helpt om het effect van uitschieters en extreme waarden te verminderen, die anders de gradiëntupdates zouden kunnen verstoren en de convergentie vertragen.
  • Gebruik geschikte batchgroottes: Kleine batches maken snellere updates en training mogelijk, maar kunnen leiden tot convergentie naar lokale optima en instabiliteit in het trainingsproces. Grotere batchgroottes zorgen ervoor dat de agent robuuste beleidsregels leert, wat leidt tot een stabiel trainingsproces. Batchgroottes die echter te groot zijn, zijn ook suboptimaal. Naast het verhogen van de computationele kosten zorgen ze ervoor dat beleidsupdates minder responsief zijn op de waardefunctie omdat de gradiëntupdates zijn gebaseerd op gemiddelden geschat over grote batches. Bovendien kan dit leiden tot overfitting van de updates op die specifieke batch.
  • Iteratiestappen: Het is over het algemeen raadzaam om elke batch 5-10 iteraties te hergebruiken. Dit maakt het trainingsproces efficiënter. Het hergebruiken van dezelfde batch te vaak leidt tot overfitting. De code verwijst naar deze hyperparameter als PPO_STEPS.
  • Voer regelmatige evaluatie uit: Om overpassing te detecteren, is het essentieel om regelmatig de effectiviteit van het beleid te monitoren. Als het beleid niet effectief blijkt te zijn in bepaalde scenario’s, kan verdere training of fijnafstemming nodig zijn.
  • Stel de hyperparameters af: Zoals eerder uitgelegd, is PPO-training gevoelig voor de waarden van de hyperparameters. Experimenteer met verschillende hyperparameterwaarden om de juiste set waarden voor uw specifieke probleem te bepalen.
  • Gedeeld ruggengraatnetwerk:Zoals geïllustreerd in dit artikel, voorkomt het gebruik van een gedeeld ruggengraatnetwerk onevenwichtigheden tussen de acteur- en criticusnetwerken. Het delen van een ruggengraatnetwerk tussen de acteur en criticus helpt bij gedeelde kenmerkextractie en een gemeenschappelijk begrip van de omgeving. Dit maakt het leerproces efficiënter en stabieler. Het helpt ook de computationele ruimte en tijdscomplexiteit van het algoritme te verminderen.
  • Aantal en grootte van verborgen lagen:Verhoog het aantal verborgen lagen en dimensies voor complexere omgevingen. Simpele problemen zoals CartPole kunnen worden opgelost met één verborgen laag. De verborgen laag die in dit artikel wordt gebruikt, heeft 64 dimensies. Het netwerk veel groter maken dan nodig is, is qua rekenkracht verspillend en kan het instabiel maken.
  • Vroegtijdig stoppen:Het stoppen van de training wanneer de evaluatiemetrics zijn bereikt, helpt overtraining te voorkomen en verspilling van middelen te vermijden. Een veelvoorkomende evaluatiemetric is wanneer de agent de drempelbeloningen overschrijdt over de afgelopen N gebeurtenissen.

Conclusie

In dit artikel hebben we PPO besproken als een manier om RL-problemen op te lossen. Vervolgens hebben we de stappen beschreven om PPO te implementeren met behulp van PyTorch. Ten slotte hebben we enkele prestatietips en beste praktijken voor PPO gepresenteerd.

De beste manier om te leren is door de code zelf te implementeren. Je kunt ook de code aanpassen om te werken met andere klassieke besturingsomgevingen in Gym. Om te leren hoe je RL-agenten implementeert met behulp van Python en OpenAI’s Gymnasium, volg de cursus Reinforcement Learning with Gymnasium in Python!

Source:
https://www.datacamp.com/tutorial/proximal-policy-optimization