Comprendre la dérivation d'une politique de dégradé

19

J'essaie de recréer un exemple très simple de Gradient politique, à partir de son blog d'origine Andrej Karpathy . Dans cet article, vous trouverez un exemple avec CartPole et Policy Gradient avec une liste de poids et d'activation Softmax. Voici mon exemple recréé et très simple de gradient de politique CartPole, qui fonctionne parfaitement .

import gym
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
import copy

NUM_EPISODES = 4000
LEARNING_RATE = 0.000025
GAMMA = 0.99


# noinspection PyMethodMayBeStatic
class Agent:
    def __init__(self):
        self.poly = PolynomialFeatures(1)
        self.w = np.random.rand(5, 2)

    def policy(self, state):
        z = state.dot(self.w)
        exp = np.exp(z)
        return exp/np.sum(exp)

    def __softmax_grad(self, softmax):
        s = softmax.reshape(-1,1)
        return np.diagflat(s) - np.dot(s, s.T)

    def grad(self, probs, action, state):
        dsoftmax = self.__softmax_grad(probs)[action,:]
        dlog = dsoftmax / probs[0,action]
        grad = state.T.dot(dlog[None,:])
        return grad

    def update_with(self, grads, rewards):

        for i in range(len(grads)):
            # Loop through everything that happend in the episode
            # and update towards the log policy gradient times **FUTURE** reward

            total_grad_effect = 0
            for t, r in enumerate(rewards[i:]):
                total_grad_effect += r * (GAMMA ** r)
            self.w += LEARNING_RATE * grads[i] * total_grad_effect
            print("Grads update: " + str(np.sum(grads[i])))



def main(argv):
    env = gym.make('CartPole-v0')
    np.random.seed(1)

    agent = Agent()
    complete_scores = []

    for e in range(NUM_EPISODES):
        state = env.reset()[None, :]
        state = agent.poly.fit_transform(state)

        rewards = []
        grads = []
        score = 0

        while True:

            probs = agent.policy(state)
            action_space = env.action_space.n
            action = np.random.choice(action_space, p=probs[0])

            next_state, reward, done,_ = env.step(action)
            next_state = next_state[None,:]
            next_state = agent.poly.fit_transform(next_state.reshape(1, 4))
            grad = agent.grad(probs, action, state)

            grads.append(grad)
            rewards.append(reward)

            score += reward
            state = next_state

            if done:
                break

        agent.update_with(grads, rewards)
        complete_scores.append(score)

    env.close()
    plt.plot(np.arange(NUM_EPISODES),
             complete_scores)
    plt.savefig('image1.png')


if __name__ == '__main__':
    main(None)

entrez la description de l'image ici

.

.

Question

J'essaie de faire, presque le même exemple mais avec l'activation Sigmoid (juste pour plus de simplicité). C'est tout ce que je dois faire. Basculez l'activation dans le modèle de softmaxvers sigmoid. Ce qui devrait fonctionner à coup sûr (basé sur l'explication ci-dessous). Mais mon modèle Policy Gradient n'apprend rien et reste aléatoire. Toute suggestion?

import gym
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures

NUM_EPISODES = 4000
LEARNING_RATE = 0.000025
GAMMA = 0.99


# noinspection PyMethodMayBeStatic
class Agent:
    def __init__(self):
        self.poly = PolynomialFeatures(1)
        self.w = np.random.rand(5, 1) - 0.5

    # Our policy that maps state to action parameterized by w
    # noinspection PyShadowingNames
    def policy(self, state):
        z = np.sum(state.dot(self.w))
        return self.sigmoid(z)

    def sigmoid(self, x):
        s = 1 / (1 + np.exp(-x))
        return s

    def sigmoid_grad(self, sig_x):
        return sig_x * (1 - sig_x)

    def grad(self, probs, action, state):
        dsoftmax = self.sigmoid_grad(probs)
        dlog = dsoftmax / probs
        grad = state.T.dot(dlog)
        grad = grad.reshape(5, 1)
        return grad

    def update_with(self, grads, rewards):
        if len(grads) < 50:
            return
        for i in range(len(grads)):
            # Loop through everything that happened in the episode
            # and update towards the log policy gradient times **FUTURE** reward

            total_grad_effect = 0
            for t, r in enumerate(rewards[i:]):
                total_grad_effect += r * (GAMMA ** r)
            self.w += LEARNING_RATE * grads[i] * total_grad_effect


def main(argv):
    env = gym.make('CartPole-v0')
    np.random.seed(1)

    agent = Agent()
    complete_scores = []

    for e in range(NUM_EPISODES):
        state = env.reset()[None, :]
        state = agent.poly.fit_transform(state)

        rewards = []
        grads = []
        score = 0

        while True:

            probs = agent.policy(state)
            action_space = env.action_space.n
            action = np.random.choice(action_space, p=[1 - probs, probs])

            next_state, reward, done, _ = env.step(action)
            next_state = next_state[None, :]
            next_state = agent.poly.fit_transform(next_state.reshape(1, 4))

            grad = agent.grad(probs, action, state)
            grads.append(grad)
            rewards.append(reward)

            score += reward
            state = next_state

            if done:
                break

        agent.update_with(grads, rewards)
        complete_scores.append(score)

    env.close()
    plt.plot(np.arange(NUM_EPISODES),
             complete_scores)
    plt.savefig('image1.png')


if __name__ == '__main__':
    main(None)

Tracer tout l'apprentissage reste aléatoire. Rien ne sert à régler les hyper paramètres. Sous l'image d'exemple.

entrez la description de l'image ici

Références :

1) Apprentissage par renforcement profond: Pong des pixels

2) Une introduction aux gradients politiques avec Cartpole et Doom

3) Dérivation des gradients de politique et mise en œuvre de RENFORCEMENT

4) Astuce d'apprentissage automatique du jour (5): Astuce dérivée de journal 12


MISE À JOUR

On dirait que la réponse ci-dessous pourrait faire du travail à partir du graphique. Mais ce n'est pas une probabilité logarithmique, ni même un gradient de la politique. Et change tout l'objet de la politique de dégradé RL. Veuillez vérifier les références ci-dessus. Après l'image, nous avons la déclaration suivante.

entrez la description de l'image ici

J'ai besoin de prendre un dégradé de la fonction de journal de ma politique (qui est simplement des poids et une sigmoidactivation).

GensaGames
la source
4
Je vous suggère de poster cette question sur Data Science Stack Exchange car il s'agit principalement d'une question théorique (Stack Overflow est principalement destiné aux questions de codage). Vous atteindrez également plus de personnes qui connaissent bien ce domaine.
Gilles-Philippe Paillé
@ Gilles-PhilippePaillé J'ai ajouté du code, qui représente le problème. Ce que je dois faire, c'est juste pour réparer certaines parties avec l'activation. Veuillez vérifier la réponse mise à jour.
GensaGames
1
Pour dériver des gradients de politique, voici un article de référence avec un exemple de travail du même type d'arrangements, j'espère que vous apprendrez en détail: medium.com/@thechrisyoon/… .
Muhammad Usman
@MuhammadUsman. Merci pour l'info. J'ai rouge cette source. Pour l'instant, c'est clair et l'exemple ci-dessus, j'essaye de changer l'activation de softmaxà signmoid. C'est seulement une chose que je dois faire dans l'exemple ci-dessus.
GensaGames
2
@JasonChia sigmoid affiche un nombre réel dans une plage [0, 1]qui peut être interprétée comme une probabilité d'action positive (tourner à droite dans CartPole, par exemple). La probabilité d'une action négative (tourner à gauche) est alors 1 - sigmoid. La somme de ces probabilités est 1. Oui, il s'agit d'un environnement de carte polaire standard.
Pavel Tyshevskyi

Réponses:

8

Le problème vient de la gradméthode.

def grad(self, probs, action, state):
    dsoftmax = self.sigmoid_grad(probs)
    dlog = dsoftmax / probs
    grad = state.T.dot(dlog)
    grad = grad.reshape(5, 1)
    return grad

Dans le code d'origine, Softmax était utilisé avec la fonction de perte CrossEntropy. Lorsque vous basculez l'activation sur Sigmoid, la fonction de perte appropriée devient CrossEntropy binaire. Maintenant, le but de la gradméthode est de calculer le gradient de la fonction de perte wrt. poids. En épargnant les détails, un gradient approprié est donné par (probs - action) * statedans la terminologie de votre programme. La dernière chose est d'ajouter le signe moins - nous voulons maximiser le négatif de la fonction de perte.

Une gradméthode appropriée donc:

def grad(self, probs, action, state):
    grad = state.T.dot(probs - action)
    return -grad

Un autre changement que vous voudrez peut-être ajouter est d'augmenter le taux d'apprentissage. LEARNING_RATE = 0.0001et NUM_EPISODES = 5000produira l'intrigue suivante:

Récompense moyenne correcte vs nombre d'épisodes

La convergence sera beaucoup plus rapide si les poids sont initialisés en utilisant la distribution gaussienne avec une moyenne nulle et une petite variance:

def __init__(self):
    self.poly = PolynomialFeatures(1)
    self.w = np.random.randn(5, 1) * 0.01

entrez la description de l'image ici

MISE À JOUR

Ajout d'un code complet pour reproduire les résultats:

import gym
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures

NUM_EPISODES = 5000
LEARNING_RATE = 0.0001
GAMMA = 0.99


# noinspection PyMethodMayBeStatic
class Agent:
    def __init__(self):
        self.poly = PolynomialFeatures(1)
        self.w = np.random.randn(5, 1) * 0.01

    # Our policy that maps state to action parameterized by w
    # noinspection PyShadowingNames
    def policy(self, state):
        z = np.sum(state.dot(self.w))
        return self.sigmoid(z)

    def sigmoid(self, x):
        s = 1 / (1 + np.exp(-x))
        return s

    def sigmoid_grad(self, sig_x):
        return sig_x * (1 - sig_x)

    def grad(self, probs, action, state):
        grad = state.T.dot(probs - action)
        return -grad

    def update_with(self, grads, rewards):
        if len(grads) < 50:
            return
        for i in range(len(grads)):
            # Loop through everything that happened in the episode
            # and update towards the log policy gradient times **FUTURE** reward

            total_grad_effect = 0
            for t, r in enumerate(rewards[i:]):
                total_grad_effect += r * (GAMMA ** r)
            self.w += LEARNING_RATE * grads[i] * total_grad_effect


def main(argv):
    env = gym.make('CartPole-v0')
    np.random.seed(1)

    agent = Agent()
    complete_scores = []

    for e in range(NUM_EPISODES):
        state = env.reset()[None, :]
        state = agent.poly.fit_transform(state)

        rewards = []
        grads = []
        score = 0

        while True:

            probs = agent.policy(state)
            action_space = env.action_space.n
            action = np.random.choice(action_space, p=[1 - probs, probs])

            next_state, reward, done, _ = env.step(action)
            next_state = next_state[None, :]
            next_state = agent.poly.fit_transform(next_state.reshape(1, 4))

            grad = agent.grad(probs, action, state)
            grads.append(grad)
            rewards.append(reward)

            score += reward
            state = next_state

            if done:
                break

        agent.update_with(grads, rewards)
        complete_scores.append(score)

    env.close()
    plt.plot(np.arange(NUM_EPISODES),
             complete_scores)
    plt.savefig('image1.png')


if __name__ == '__main__':
    main(None)
Pavel Tyshevskyi
la source
Merci beaucoup. J'essaierai cette solution plus tard.
GensaGames
Je ne sais pas, où vous prenez une telle dérivation pour ma fonction. Comme vous pouvez vérifier l'image ci-dessus. J'aurais besoin de prendre un gradient du journal de la politique . Où la politique dans mon cas pèse simplement sigmoid. Mais votre gradient en réponse ne devrait rien à voir avec mon gradient. Droite?
GensaGames
Notez que vous n'incorporez aucune information sur l'action effectuée. Selon cette conférence sur les gradients de politique (diapositive 13) , la mise à jour devrait ressembler (action - probs) * sigmoid_grad(probs), mais j'ai omis en sigmoid_gradraison du problème de fuite avec le gradient sigmoïde.
Pavel Tyshevskyi
L'essentiel ici est d'indiquer la direction dans laquelle nous voulons changer les poids. Si action = 1, on veut probsse rapprocher 1, augmenter les poids (gradient positif). Si action=0nous voulonsprobs être plus proches 0, donc diminuer les poids (gradient négatif).
Pavel Tyshevskyi
1
Dans tous les cas, les modifications ci-dessus ne fonctionnent pas du tout, pouvez-vous partager tout le fichier? En même temps, je veux faire un échantillon clair et je ne me soucie pas de la disparition du problème dans ce cas. Et (action - probs)c'est juste une autre façon de changer les mêmes poids.
GensaGames