Optimisation de Politique Proximale avec PyTorch et Gymnasium

L’Optimisation de Politique Proximale (PPO) est l’un des algorithmes préférés pour résoudre des problèmes d’Apprentissage par Renforcement (RL). Il a été développé en 2017 par John Schuman, le co-fondateur d’OpenAI.

PPO a été largement utilisé chez OpenAI pour entraîner des modèles à imiter un comportement humain. Il améliore les méthodes antérieures comme l’Optimisation de Politique de Région de Confiance (TRPO) et est devenu populaire en raison de sa robustesse et de son efficacité.

Dans ce tutoriel, nous examinons PPO en profondeur. Nous couvrons la théorie et démontrons comment l’implémenter en utilisant PyTorch.

Comprendre l’Optimisation de Politique Proximale (PPO)

Les algorithmes conventionnels d’apprentissage supervisé mettent à jour les paramètres dans la direction du gradient le plus raide. Si cette mise à jour s’avère excessive, elle est corrigée lors des exemples d’entraînement suivants qui sont indépendants les uns des autres.

Cependant, les exemples d’entraînement en apprentissage par renforcement consistent en les actions et les rendements de l’agent. Ainsi, les exemples d’entraînement sont corrélés les uns aux autres. L’agent explore l’environnement pour déterminer la politique optimale. Ainsi, apporter de grands changements au gradient peut entraîner un blocage de la politique dans une mauvaise région avec des récompenses sous-optimales. Étant donné que l’agent doit explorer l’environnement, de grands changements de politique rendent le processus d’entraînement instable.

Les méthodes basées sur les régions de confiance visent à éviter ce problème en s’assurant que les mises à jour de politique se situent dans une région de confiance. Cette région de confiance est une zone artificiellement contrainte au sein de l’espace des politiques où les mises à jour sont permises. La politique mise à jour ne peut être que dans une région de confiance de l’ancienne politique. S’assurer que les mises à jour de politique sont incrémentales empêche l’instabilité.

Les mises à jour de politique par région de confiance (TRPO)

L’algorithme des mises à jour de politique par région de confiance (TRPO) a été proposé en 2015 par John Schulman (qui a également proposé PPO en 2017). Pour mesurer la différence entre l’ancienne politique et la politique mise à jour, TRPO utilise la divergence de Kullback-Leibler (KL). La divergence KL est utilisée pour mesurer la différence entre deux distributions de probabilité. TRPO s’est avéré efficace pour mettre en œuvre des régions de confiance.

Le problème avec TRPO est la complexité computationnelle associée à la divergence KL. L’application de la divergence KL doit être étendue au second ordre à l’aide de méthodes numériques comme l’expansion de Taylor. Cela est coûteux en termes de calcul. PPO a été proposé comme une alternative plus simple et plus efficace à TRPO. PPO limite le ratio des politiques pour approcher la région de confiance sans recourir à des calculs complexes impliquant la divergence KL.

C’est pourquoi le PPO est devenu préféré par rapport au TRPO dans la résolution des problèmes de RL. Grâce à la méthode plus efficace d’estimation des régions de confiance, le PPO équilibre efficacement les performances et la stabilité.

L’approximation de politique proximale (PPO)

PPO est souvent considérée comme une sous-classe des méthodes acteur-critique, qui mettent à jour les gradients de politique basés sur la fonction de valeur. Les méthodes acteur-critique avantageuses (A2C) utilisent un paramètre appelé l’avantage. Cela mesure la différence entre les rendements prédits par le critique et les rendements réalisés en mettant en œuvre la politique.

Pour comprendre le PPO, vous devez connaître ses composants :

  1. L’acteur exécute la politique.Il est implémenté sous la forme d’un réseau neuronal. Étant donné un état en entrée, il produit l’action à prendre.
  2. Le critique est un autre réseau neuronal. Il prend l’état en entrée et produit la valeur attendue de cet état. Ainsi, le critique exprime la fonction de valeur de l’état.
  3. Les méthodes basées sur le gradient de politique peuvent choisir d’utiliser différentes fonctions objectif. En particulier, PPO utilise la fonction d’avantage. La fonction d’avantage mesure la quantité par laquelle la récompense cumulative (basée sur la politique mise en œuvre par l’acteur) dépasse la récompense de référence attendue (telle que prédite par le critique). L’objectif de PPO est d’augmenter la probabilité de choisir des actions avec un grand avantage. L’objectif d’optimisation de PPO utilise des fonctions de perte basées sur cette fonction d’avantage.
  4. La fonction objectif tronquée est la principale innovation de PPO.Elle empêche les grandes mises à jour de politique dans une seule itération d’entraînement. Elle limite la quantité de mise à jour de la politique dans une seule itération. Pour mesurer les mises à jour de politique incrémentielles, les méthodes basées sur la politique utilisent le ratio de probabilité de la nouvelle politique par rapport à l’ancienne politique.
  5. La perte de substitution est la fonction objectif de PPO et elle prend en compte les innovations mentionnées précédemment.Elle est calculée comme suit :
    1. Calculer le ratio réel (comme expliqué précédemment) et le multiplier par l’avantage.
    2. Recadrer le ratio pour qu’il se situe dans une plage désirée. Multipliez le ratio recadré par l’avantage.
    3. Prenez la valeur minimale des deux quantités ci-dessus.
  6. En pratique, un terme d’entropie est également ajouté à la perte de substitution. Cela s’appelle le bonus d’entropie. Il est basé sur la distribution mathématique des probabilités d’action. L’idée derrière le bonus d’entropie est d’introduire un peu plus de hasard de manière contrôlée. Cela encourage le processus d’optimisation à explorer l’espace d’action. Un bonus d’entropie élevé favorise l’exploration plutôt que l’exploitation.

Comprendre le mécanisme de recadrage

Supposons qu’avec l’ancienne politique πancienne, la probabilité de prendre l’action a à l’état s est πancienne(a|s). Sous la nouvelle politique, la probabilité de prendre la même action a à partir du même état s est mise à jour à πnouvelle(a|s). Le ratio de ces probabilités, en fonction des paramètres de la politique θ, est r(θ). Lorsque la nouvelle politique rend l’action plus probable (dans le même état), le ratio est supérieur à 1 et vice versa. 

Le mécanisme de découpe restreint ce rapport de probabilité de sorte que les nouvelles probabilités d’action doivent se situer dans un certain pourcentage des anciennes probabilités d’action. Par exemple,r(θ)peut être contraint de se situer entre 0,8 et 1,2. Cela empêche de grands sauts, ce qui garantit à son tour un processus d’entraînement stable.

Dans le reste de cet article, vous apprendrez comment assembler les composants pour une mise en œuvre simple de PPO en utilisant PyTorch.

Avant de mettre en œuvre PPO, nous devons installer les bibliothèques logicielles prérequises et choisir un environnement adapté pour appliquer la politique.

Installation de PyTorch et des bibliothèques requises

Nous devons installer les logiciels suivants :

  • PyTorch et d’autres bibliothèques logicielles, telles que numpy (pour les fonctions mathématiques et statistiques) et matplotlib (pour tracer des graphiques).
  • Le package logiciel Gym en open source d’OpenAI, une bibliothèque Python qui simule différents environnements et jeux, pouvant être résolus à l’aide de l’apprentissage par renforcement. Vous pouvez utiliser l’API Gym pour faire interagir votre algorithme avec l’environnement. Étant donné que la fonctionnalité de gym change parfois lors du processus de mise à jour, dans cet exemple, nous figeons sa version à 0.25.2.

Pour installer sur un serveur ou une machine locale, exécutez :

$ pip install torch numpy matplotlib gym==0.25.2

Pour installer en utilisant un Notebook comme Google Colab ou DataLab, utilisez :

!pip install torch numpy matplotlib gym==0.25.2

Créez l'(les) environnement(s) CartPole

Utilisez OpenAI Gym pour créer deux instances (une pour l’entraînement et une autre pour les tests) de l’environnement CartPole :

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

Maintenant, implémentons PPO en utilisant PyTorch.

Définition du réseau de politique

Comme expliqué précédemment, PPO est implémenté en tant que modèle acteur-critique. L’acteur implémente la politique, et le critique prédit sa valeur estimée. Les réseaux neuronaux acteur et critique prennent la même entrée – l’état à chaque pas de temps. Ainsi, les modèles acteur et critique peuvent partager un réseau neuronal commun, qui est appelé architecture de base. L’acteur et le critique peuvent étendre l’architecture de base avec des couches supplémentaires.

Définir le réseau de base

Les étapes suivantes décrivent le réseau de base :

  • Implémentez un réseau avec 3 couches – une entrée, une cachée et une couche de sortie.
  • Après les couches d’entrée et cachée, nous utilisons une fonction d’activation. Dans ce tutoriel, nous choisissons ReLU car elle est efficace sur le plan computationnel.
  • Nous imposons également une fonction d’abandon après les couches d’entrée et cachées pour obtenir un réseau robuste. La fonction d’abandon met aléatoirement à zéro certains neurones. Cela réduit la dépendance à l’égard de neurones spécifiques et empêche le surajustement, rendant ainsi le réseau plus robuste.

Le code ci-dessous met en œuvre l’épine dorsale :

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

Définir le réseau acteur-critique

Maintenant, nous pouvons utiliser ce réseau pour définir la classe acteur-critique, ActorCritic. L’acteur modélise la politique et prédit l’action. Le critique modélise la fonction de valeur et prédit la valeur. Ils prennent tous deux l’état en entrée.

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

Instancier les réseaux acteur et critique

Nous utiliserons les réseaux définis ci-dessus pour créer un acteur et un critique. Ensuite, nous créerons un agent, comprenant l’acteur et le critique.

Avant de créer l’agent, initialiser les paramètres du réseau :

  • Les dimensions de la couche cachée, H, qui est un paramètre configurable. La taille et le nombre de couches cachées dépendent de la complexité du problème. Nous utiliserons une couche cachée de dimensions 64 X 64.
  • Les fonctionnalités d’entrée, N, où N est la taille du tableau d’état. La couche d’entrée a des dimensions N X H. Dans l’environnement CartPole, l’état est un tableau à 4 éléments. Donc N est égal à 4.
  • Les fonctionnalités de sortie du réseau acteur, O, où O est le nombre d’actions dans l’environnement. La couche de sortie de l’acteur a des dimensions H x O. L’environnement CartPole a 2 actions.
  • Les fonctionnalités de sortie du réseau critique. Étant donné que le réseau critique ne prédit que la valeur attendue (pour un état donné en entrée), le nombre de fonctionnalités de sortie est de 1.
  • Dropout en tant que fraction.

Le code suivant montre comment déclarer les réseaux d’acteur et de critique basés sur le réseau principal:

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

Calcul des retours

L’environnement donne une récompense à chaque étape suivante, en fonction de l’action de l’agent. La récompense, R, est exprimée comme suit:

Le retour est défini comme la valeur accumulée des récompenses futures attendues. Les récompenses des pas de temps plus éloignés dans le futur sont moins précieuses que les récompenses immédiates. Ainsi, le retour est couramment calculé comme le retour actualisé, G, défini comme suit:

Dans ce tutoriel (et de nombreuses autres références), le retour fait référence au retour actualisé. 

Pour calculer le retour:

  • Commencez avec les récompenses attendues de tous les états futurs.
  • Multipliez chaque récompense future par un exposant du facteur de remise, . Par exemple, la récompense attendue après 2 pas de temps (à partir du présent) est multipliée par 2. 
  • Sommez toutes les récompenses futures actualisées pour calculer le retour. 
  • Normalisez la valeur du retour. 

La fonction calculate_returns() effectue ces calculs, comme indiqué ci-dessous:

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) # normaliser le retour returns = (returns - returns.mean()) / returns.std() return returns

Mise en œuvre de la fonction avantage

L’avantage est calculé comme la différence entre la valeur prédite par le critique et le retour attendu des actions choisies par l’acteur selon la politique. Pour une action donnée, l’avantage exprime le bénéfice de prendre cette action spécifique par rapport à une action arbitraire (moyenne).

Dans le document PPO original (équation 10), l’avantage, en regardant jusqu’à l’instant T, est exprimé comme suit :

En codant l’algorithme, la contrainte de regarder jusqu’à un nombre défini d’instant est imposée via la taille du lot. Ainsi, l’équation ci-dessus peut être simplifiée comme la différence entre la valeur et les retours attendus. Les retours attendus sont quantifiés dans la fonction de valeur état-action, Q. 

Par conséquent, la formule simplifiée ci-dessous exprime l’avantage de choisir : 

  • une action particulière 
  • dans un état donné 
  • sous une politique particulière 
  • à un instant particulier 

Cela est exprimé comme suit : 

OpenAI utilise également cette formule pour implémenter l’apprentissage par renforcement. La fonction calculate_advantages() ci-dessous calcule l’avantage :

def calculate_advantages(returns, values): advantages = returns - values # Normaliser l'avantage advantages = (advantages - advantages.mean()) / advantages.std() return advantages

Perte de substitution et mécanisme de rognage

La perte de politique serait la perte de gradient de politique standard sans techniques spéciales comme PPO. La perte de gradient de politique standard est calculée comme le produit de:

  • Les probabilités d’action de la politique
  • La fonction avantage, qui est calculée comme la différence entre:
    • Le retour de la politique
    • La valeur attendue

La perte de gradient de politique standard ne peut pas apporter de corrections pour les changements brusques de politique. La perte substitutive modifie la perte standard pour limiter la quantité de changement de politique à chaque itération. C’est le minimum de deux quantités:

  • Le produit de:
    • Le ratio de politique. Ce ratio exprime la différence entre les anciennes et nouvelles probabilités d’action.
    • La fonction avantage
  • Le produit de:
    • La valeur clippée du ratio de politique. Ce ratio est rogné de sorte que la politique mise à jour soit dans un certain pourcentage de l’ancienne politique.
    • La fonction avantage

Pour le processus d’optimisation, la perte de substitution est utilisée comme un substitut de la perte réelle.

Le mécanisme de rognage

Le ratio de politique, R, est la différence entre les nouvelles et anciennes politiques et est donné comme le rapport des logarithmes des probabilités de la politique sous les nouveaux et anciens paramètres:

Le ratio de politique rogné, R’, est contraint de la manière suivante:

Étant donné l’avantage, At, comme indiqué dans la section précédente, et le ratio de la politique, comme indiqué ci-dessus, la perte de substitution est calculée comme suit:

Le code ci-dessous montre comment implémenter le mécanisme de rognage et la perte de substitution.

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

Maintenant, entraînons l’agent.

Calcul des pertes de politique et de valeur

Nous sommes maintenant prêts à calculer les pertes de politique et de valeur:

  • La perte de la politique est la somme de la perte de substitution et du bonus d’entropie.
  • La perte de valeur est basée sur la différence entre la valeur prédite par le critique et les rendements (récompense cumulative) générés par la politique. Le calcul de la perte de valeur utilise la fonction Smooth L1 Loss. Cela permet de lisser la fonction de perte et de la rendre moins sensible aux valeurs aberrantes.

Les deux pertes, telles que calculées ci-dessus, sont des tenseurs. La descente de gradient est basée sur des valeurs scalaires. Pour obtenir une seule valeur scalaire représentant la perte, utilisez la fonction .sum() pour sommer les éléments du tenseur. La fonction ci-dessous montre comment faire cela :

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

Définition de la boucle d’entraînement

Avant de commencer le processus de formation, créez un ensemble de tampons en tant que tableaux vides. L’algorithme de formation utilisera ces tampons pour stocker des informations sur les actions de l’agent, les états de l’environnement et les récompenses à chaque étape de temps. La fonction ci-dessous initialise ces tampons:

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

Chaque itération de formation exécute l’agent avec les paramètres de politique pour cette itération. L’agent interagit avec l’environnement par pas de temps dans une boucle jusqu’à ce qu’il atteigne une condition terminale.

Après chaque pas de temps, l’action, la récompense et la valeur de l’agent sont ajoutées aux tampons respectifs. Lorsque l’épisode se termine, la fonction renvoie l’ensemble mis à jour de tampons, qui résume les résultats de l’épisode.

Avant d’exécuter la boucle de formation:

  • Définissez le modèle en mode entraînement en utilisant agent.train().
  • Réinitialisez l’environnement à un état aléatoire en utilisant env.reset(). C’est l’état de départ pour cette itération d’entraînement.

Les étapes suivantes expliquent ce qui se passe à chaque pas de temps dans la boucle d’entraînement :

  • Transmettez l’état à l’agent.
  • L’agent renvoie :
    • L’action prédite étant donné l’état, basée sur la politique (acteur). Passez ce tenseur d’action prédite à travers la fonction softmax pour obtenir l’ensemble des probabilités d’action.
    • La valeur prédite de l’état, basée sur le critique.
  • L’agent sélectionne l’action à prendre :
    • Utilise les probabilités d’action pour estimer la distribution de probabilité.
    • Sélectionne aléatoirement une action en choisissant un échantillon de cette distribution. La fonction dist.sample() fait cela.
  • Utilise la fonction env.step() pour transmettre cette action à l’environnement afin de simuler la réponse de l’environnement pour cette étape. En fonction de l’action de l’agent, l’environnement génère :
    • Le nouvel état
    • La récompense
    • La valeur de retour booléenne done (cela indique si l’environnement a atteint un état terminal)
  • Ajoutez aux tampons respectifs les valeurs de l’action de l’agent, des récompenses, des valeurs prédites et du nouvel état.

L’épisode d’entraînement se termine lorsque la fonction env.step() renvoie true pour la valeur de retour booléenne de done.

Après la fin de l’épisode, utilisez les valeurs cumulées de chaque pas de temps pour calculer les rendements cumulatifs de cet épisode en ajoutant les récompenses de chaque pas de temps. Nous utilisons la fonction calculate_returns() décrite précédemment pour cela. Les entrées de cette fonction sont le facteur d’escompte et le tampon contenant les récompenses de chaque pas de temps. Nous utilisons ces rendements et les valeurs cumulées de chaque pas de temps pour calculer les avantages à l’aide de la fonction calculate_advantages().

La fonction Python suivante montre comment implémenter ces étapes:

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

Mise à jour des paramètres du modèle

À chaque itération d’entraînement, le modèle parcourt un épisode complet composé de nombreux pas de temps (jusqu’à ce qu’il atteigne une condition terminale). À chaque pas de temps, nous stockons les paramètres de la politique, l’action de l’agent, les rendements et les avantages. Après chaque itération, nous mettons à jour le modèle en fonction des performances de la politique à travers tous les pas de temps de cette itération.

Le nombre maximum d’étapes dans l’environnement CartPole est de 500. Dans des environnements plus complexes, il y a plus d’étapes, même des millions. Dans de tels cas, l’ensemble des résultats de l’entraînement doit être divisé en lots. Le nombre d’étapes dans chaque lot est appelé la taille du lot d’optimisation.

Ainsi, les étapes pour mettre à jour les paramètres du modèle sont:

  • Diviser l’ensemble des résultats de l’entraînement en lots.
  • Pour chaque lot:
    • Obtenir l’action de l’agent et la valeur prédite pour chaque état.
    • Utiliser ces actions prédites pour estimer la nouvelle distribution de probabilité des actions.
    • Utilisez cette distribution pour calculer l’entropie.
    • Utilisez cette distribution pour obtenir la probabilité logarithmique des actions dans l’ensemble de résultats d’entraînement. C’est le nouvel ensemble de probabilités logarithmiques des actions dans l’ensemble de résultats d’entraînement. L’ancien ensemble de probabilités logarithmiques de ces mêmes actions a été calculé dans la boucle d’entraînement expliquée dans la section précédente.
    • Calculez la perte de substitution en utilisant les anciennes et nouvelles distributions de probabilités des actions.
    • Calculez la perte de politique et la perte de valeur en utilisant la perte de substitution, l’entropie et les avantages.
    • Exécutez .backward() séparément sur les pertes de politique et de valeur. Cela met à jour les gradients sur les fonctions de perte.
    • Exécutez .step() sur l’optimiseur pour mettre à jour les paramètres de la politique. Dans ce cas, nous utilisons l’optimiseur Adam pour équilibrer la vitesse et la robustesse.
    • Accumulez les pertes de politique et de valeur.
  • Répétez la passe arrière (les opérations ci-dessus) sur chaque lot plusieurs fois, en fonction de la valeur du paramètre PPO_STEPS. Répéter la passe arrière sur chaque lot est efficace sur le plan computationnel car cela augmente efficacement la taille de l’ensemble d’entraînement sans avoir à exécuter des passes avant supplémentaires. Le nombre d’étapes de l’environnement dans chaque alternance entre l’échantillonnage et l’optimisation est appelé la taille du lot d’itération.
  • Renvoyez la perte moyenne de politique et la perte de valeur.

Le code ci-dessous met en œuvre ces étapes :

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): # obtenir la nouvelle probabilité logarithmique des actions pour tous les états d'entrée 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() # estimer les nouvelles probabilités logarithmiques en utilisant les anciennes actions 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

Enfin, exécutons l’agent PPO.

Évaluation des performances 

Pour évaluer les performances de l’agent, créez un nouvel environnement et calculez les récompenses cumulatives en exécutant l’agent dans ce nouvel environnement. Vous devez définir le mode évaluation de l’agent en utilisant la fonction .eval(). Les étapes sont les mêmes que pour la boucle d’entraînement. L’extrait de code ci-dessous met en œuvre la fonction d’évaluation : 

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

Visualisation des résultats de l’entraînement

Nous utiliserons la bibliothèque Matplotlib pour visualiser le progrès du processus d’entraînement. La fonction ci-dessous montre comment tracer les récompenses des boucles d’entraînement et de test :

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()

Dans les parcelles d’exemple ci-dessous, nous montrons les récompenses d’entraînement et de test, obtenues en appliquant la politique dans les environnements d’entraînement et de test respectivement. Notez que la forme de ces parcelles apparaîtra différente à chaque fois que vous exécutez le code. Cela est dû à la randomité inhérente au processus d’entraînement.

Récompenses d’entraînement (obtenues en appliquant la politique dans l’environnement d’entraînement). Image par Auteur.

Récompenses de test (obtenues en appliquant la politique dans l’environnement de test). Image par Auteur.

Dans les graphiques de sortie ci-dessus, observez la progression du processus d’entraînement:

  • La récompense commence à des valeurs basses. Au fur et à mesure de l’entraînement, les récompenses augmentent.
  • Les récompenses fluctuent de manière aléatoire tout en augmentant. Cela est dû à l’exploration de l’espace de la politique par l’agent.
  • L’entraînement se termine, et les récompenses de test se sont stabilisées autour du seuil (475) depuis de nombreuses itérations.
  • Les récompenses sont plafonnées à 500. Ce sont des contraintes imposées par l’environnement (Gym CartPole v1).

De même, vous pouvez tracer les pertes de valeur et de politique au fil des itérations :

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()

L’exemple de graphique ci-dessous montre les pertes suivies à travers les épisodes d’entraînement :

Valeur et pertes de politique à travers le processus de formation. Image par l’auteur

Observez le graphique et remarquez :

  • Les pertes semblent être réparties de manière aléatoire et ne suivent aucun motif.
  • C’est typique de l’entraînement RL, où le but n’est pas de minimiser la perte mais de maximiser les récompenses.

Exécutez l’algorithme PPO

Vous avez maintenant tous les composants pour entraîner l’agent en utilisant PPO. Pour mettre tout cela ensemble, vous devez :

  • Déclarer des hyperparamètres comme le facteur d’escompte, la taille du lot, le taux d’apprentissage, etc.
  • Instanciez des tampons sous forme de tableaux nuls pour stocker les récompenses et les pertes de chaque itération.
  • Créez une instance d’agent en utilisant la fonction create_agent().
  • Exécutez de manière itérative des passes avant et arrière en utilisant les fonctions forward_pass() et update_policy().
  • Testez les performances de la politique en utilisant la fonction evaluate().
  • Ajoutez la politique, les pertes de valeur et les récompenses des fonctions d’entraînement et d’évaluation aux tampons respectifs.
  • Calculer la moyenne des récompenses et des pertes sur les dernières étapes. L’exemple ci-dessous calcule la moyenne des récompenses et des pertes sur les 40 dernières étapes.
  • Afficher les résultats de l’évaluation toutes les quelques étapes. L’exemple ci-dessous affiche tous les 10 pas.
  • Arrêter le processus lorsque la récompense moyenne dépasse un certain seuil.

Le code ci-dessous montre comment déclarer une fonction qui le fait en 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)

Exécuter le programme :

run_ppo()

La sortie devrait ressembler à l’exemple ci-dessous :

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

Vous pouvez consulter et exécuter le programme de travail sur ce notebook DataLab !

En apprentissage automatique, les hyperparamètres contrôlent le processus d’entraînement. Ci-dessous, j’explique certains des hyperparamètres importants utilisés dans PPO :

  • Taux d’apprentissage : Le taux d’apprentissage décide dans quelle mesure les paramètres de la politique peuvent varier à chaque itération. En descente de gradient stochastique, la quantité par laquelle les paramètres de la politique sont mis à jour à chaque itération est décidée par le produit du taux d’apprentissage et du gradient.
  • Paramètre de rognage: Cela est également appelé epsilon, ε. Il décide dans quelle mesure le ratio de la politique est rogné. Le ratio des nouvelles et anciennes politiques est autorisé à varier dans la plage [1-ε, 1+ε]. Lorsqu’il est en dehors de cette plage, il est artificiellement rogné pour rester dans la plage. 
  • Taille du lot: Cela fait référence au nombre d’étapes à considérer pour chaque mise à jour de gradient. Dans PPO, la taille du lot est le nombre d’étapes de temps nécessaires pour appliquer la politique et calculer la perte de substitut pour mettre à jour les paramètres de la politique. Dans cet article, nous avons utilisé une taille de lot de 64.
  • Étapes d’itération: Il s’agit du nombre de fois où chaque lot est réutilisé pour exécuter la passe en arrière. Le code dans cet article se réfère à cela comme PPO_STEPS. Dans des environnements complexes, exécuter la passe avant de nombreuses fois est coûteux en termes de calcul. Une alternative plus efficace est de réexécuter chaque lot quelques fois. Il est généralement recommandé d’utiliser une valeur entre 5 et 10.
  • Facteur de réduction: Il est également appelé gamma, γ. Il exprime dans quelle mesure les récompenses immédiates sont plus précieuses que les récompenses futures. Cela est similaire au concept des taux d’intérêt dans le calcul de la valeur temporelle de l’argent. Lorsque est plus proche de 0, cela signifie que les récompenses futures sont moins précieuses et que l’agent devrait privilégier les récompenses immédiates. Lorsque est plus proche de 1, cela signifie que les récompenses futures sont importantes. 
  • Coefficient d’entropie:Le coefficient d’entropie décide du bonus d’entropie, qui est calculé comme le produit du coefficient d’entropie et de l’entropie de la distribution. Le rôle du bonus d’entropie est d’introduire plus de hasard dans la politique. Cela encourage l’agent à explorer l’espace de la politique. Cependant, l’entraînement échoue à converger vers une politique optimale lorsque ce hasard est trop élevé.
  • Les critères de réussite de la formation : Vous devez définir les critères pour décider quand la formation est réussie. Une façon courante de le faire est de fixer une condition selon laquelle les récompenses moyennes des derniers essais (épisodes) doivent être au-dessus d’un certain seuil. Dans l’exemple de code ci-dessus, cela est exprimé avec la variable N_TRIALS. Lorsque cette valeur est fixée à une valeur plus élevée, la formation prend plus de temps car la politique doit atteindre la récompense seuil sur un plus grand nombre d’épisodes. Cela donne également lieu à une politique plus robuste tout en étant plus coûteux en termes de calcul. Notez que PPO est une politique stochastique, et il y aura des épisodes où l’agent ne franchira pas le seuil. Donc, si la valeur de N_TRIALS est trop élevée, votre formation peut ne pas se terminer.

Stratégies pour optimiser les performances de PPO

Optimiser les performances des algorithmes d’entraînement PPO implique des essais et erreurs et des expérimentations avec différentes valeurs d’hyperparamètres. Cependant, il existe quelques directives générales:

  • Facteur de réduction: Lorsque les récompenses à long terme sont importantes, comme dans l’environnement CartPole, où le poteau doit rester stable dans le temps, commencez avec une valeur gamma modérée, comme 0,99.
  • Bonus d’entropie: Dans des environnements complexes, l’agent doit explorer l’espace d’actions pour trouver la politique optimale. Le bonus d’entropie favorise l’exploration. Le bonus d’entropie est ajouté à la perte substitutive. Vérifiez l’ampleur de la perte substitutive et l’entropie de la distribution avant de décider du coefficient d’entropie. Dans cet article, nous avons utilisé un coefficient d’entropie de 0,01.
  • Paramètre de découpe : Le paramètre de découpe détermine à quel point la politique mise à jour peut être différente de la politique actuelle. Une grande valeur du paramètre de découpe encourage une meilleure exploration de l’environnement, mais elle risque de déstabiliser l’apprentissage. Vous voulez un paramètre de découpe qui permet une exploration progressive tout en empêchant des mises à jour déstabilisantes. Dans cet article, nous avons utilisé un paramètre de découpe de 0,2.
  • Taux d’apprentissage : Lorsque le taux d’apprentissage est trop élevé, la politique est mise à jour en grands pas à chaque itération, ce qui peut rendre le processus d’apprentissage instable. S’il est trop bas, l’apprentissage prend trop de temps. Ce tutoriel a utilisé un taux d’apprentissage de 0,001, qui fonctionne bien pour l’environnement. Dans de nombreux cas, il est recommandé d’utiliser un taux d’apprentissage de 1e-5.

Défis et Meilleures Pratiques en PPO

Après avoir expliqué les concepts et les détails de mise en œuvre de PPO, parlons des défis et des meilleures pratiques.

Défis courants dans la formation de PPO

Même si le PPO est largement utilisé, vous devez être conscient des défis potentiels pour résoudre avec succès des problèmes du monde réel en utilisant cette technique. Certains de ces défis sont :

  • Convergence lente :Dans des environnements complexes, le PPO peut être inefficace en termes d’échantillonnage et nécessite de nombreuses interactions avec l’environnement pour converger vers la politique optimale. Cela le rend lent et coûteux à former.
  • Sensibilité aux hyperparamètres: PPO repose sur l’exploration efficace de l’espace des politiques. La stabilité du processus d’entraînement et la vitesse de convergence sont sensibles aux valeurs des hyperparamètres. Les valeurs optimales de ces hyperparamètres peuvent souvent être déterminées uniquement par essais et erreurs. 
  • Surapprentissage:  Les environnements RL sont généralement initialisés avec des paramètres aléatoires. L’entraînement PPO est basé sur la recherche de la politique optimale en fonction de l’environnement de l’agent. Parfois, le processus d’entraînement converge vers un ensemble de paramètres optimaux pour un environnement spécifique, mais pas pour un environnement randomisé. Cela est généralement résolu en ayant de nombreuses itérations, chacune avec un environnement d’entraînement randomisé différent. 
  • Environnements dynamiques: Les environnements RL simples, tels que l’environnement CartPole, sont statiques – les règles restent les mêmes au fil du temps. De nombreux autres environnements, comme un robot apprenant à marcher sur une surface instable en mouvement, sont dynamiques – les règles de l’environnement changent avec le temps. Pour bien performer dans de tels environnements, PPO a souvent besoin d’un ajustement fin supplémentaire.
  • Exploration vs exploitation: Le mécanisme de rognage de PPO garantit que les mises à jour de la politique restent dans une région de confiance. Cependant, cela empêche également l’agent d’explorer l’espace d’actions. Cela peut conduire à une convergence vers des optima locaux, notamment dans des environnements complexes. D’un autre côté, permettre à l’agent d’explorer trop peut l’empêcher de converger vers une politique optimale.

Meilleures pratiques pour l’entraînement des modèles PPO

Pour obtenir de bons résultats avec PPO, je recommande quelques bonnes pratiques, telles que :

  • Normaliser les caractéristiques en entrée :La normalisation des valeurs de rendement et d’avantages réduit la variabilité des données et conduit à des mises à jour de gradient stables. La normalisation des données amène toutes les valeurs à une plage numérique cohérente. Cela aide à réduire l’effet des valeurs aberrantes et extrêmes, qui pourraient autrement déformer les mises à jour de gradient et ralentir la convergence.
  • Utilisez des tailles de lot suffisamment grandes : Les petits lots permettent des mises à jour et des entraînements plus rapides mais peuvent conduire à la convergence vers des optima locaux et à l’instabilité du processus d’entraînement. Des tailles de lot plus grandes permettent à l’agent d’apprendre des politiques robustes, conduisant à un processus d’entraînement stable. Cependant, des tailles de lot trop grandes sont également suboptimales. En plus d’augmenter les coûts computationnels, elles rendent les mises à jour des politiques moins réactives à la fonction de valeur car les mises à jour de gradient sont basées sur des moyennes estimées sur de grands lots. De plus, cela peut conduire à un surajustement des mises à jour à ce lot spécifique.
  • Étapes d’itération : Il est généralement conseillé de réutiliser chaque lot pour 5 à 10 itérations. Cela rend le processus d’entraînement plus efficace. Réutiliser le même lot trop souvent conduit à un surajustement. Le code se réfère à ce paramètre comme PPO_STEPS.
  • Effectuer une évaluation régulière: Pour détecter le surapprentissage, il est essentiel de surveiller périodiquement l’efficacité de la politique. Si la politique s’avère inefficace dans certaines situations, une formation supplémentaire ou un ajustement fin peuvent être nécessaires.
  • Ajustez les hyperparamètres: Comme expliqué précédemment, l’entraînement PPO est sensible aux valeurs des hyperparamètres. Expérimentez avec différentes valeurs d’hyperparamètres pour déterminer le bon ensemble de valeurs pour votre problème spécifique.
  • Réseau dorsal partagé: Comme illustré dans cet article, l’utilisation d’un réseau dorsal partagé empêche les déséquilibres entre les réseaux acteur et critique. Le partage d’un réseau dorsal entre l’acteur et le critique aide à l’extraction de caractéristiques partagées et à une compréhension commune de l’environnement. Cela rend le processus d’apprentissage plus efficace et stable. Cela aide également à réduire l’espace computationnel et la complexité temporelle de l’algorithme. 
  • Nombre et taille des couches cachées: Augmentez le nombre de couches cachées et de dimensions pour des environnements plus complexes. Des problèmes plus simples comme CartPole peuvent être résolus avec une seule couche cachée. La couche cachée utilisée dans cet article a 64 dimensions. Rendre le réseau beaucoup plus grand que nécessaire est une perte de ressources computationnelles et peut le rendre instable. 
  • Arrêt anticipé: Arrêter l’entraînement lorsque les métriques d’évaluation sont atteintes aide à éviter le surapprentissage et à éviter le gaspillage de ressources. Une métrique d’évaluation courante est lorsque l’agent dépasse les récompenses seuil sur les N événements passés.

Conclusion

Dans cet article, nous avons discuté de PPO comme moyen de résoudre les problèmes de RL. Nous avons ensuite détaillé les étapes pour implémenter PPO en utilisant PyTorch. Enfin, nous avons présenté quelques conseils de performance et meilleures pratiques pour PPO.

La meilleure façon d’apprendre est de mettre en œuvre le code vous-même. Vous pouvez également modifier le code pour qu’il fonctionne avec d’autres environnements de contrôle classiques dans Gym. Pour apprendre comment implémenter des agents RL en utilisant Python et la Gymnase d’OpenAI, suivez le cours Apprentissage par Renforcement avec Gymnase en Python!

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