Comprender la derivación de políticas de gradiente

19

Estoy tratando de recrear un ejemplo muy simple de Policy Gradient, desde su blog de origen Andrej Karpathy . En ese artículo, encontrará ejemplos con CartPole y Policy Gradient con una lista de peso y activación de Softmax. Aquí está mi ejemplo recreado y muy simple del gradiente de políticas de CartPole, que funciona perfectamente .

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)

ingrese la descripción de la imagen aquí

.

.

Pregunta

Estoy tratando de hacer, casi el mismo ejemplo pero con la activación Sigmoid (solo por simplicidad). Eso es todo lo que necesito hacer. Cambie la activación en el modelo de softmaxa sigmoid. Lo que debería funcionar con seguridad (según la explicación a continuación). Pero mi modelo Policy Gradient no aprende nada y se mantiene al azar. ¿Cualquier sugerencia?

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)

Trazar todo el aprendizaje se mantiene al azar. Nada ayuda con el ajuste de hiperparámetros. Debajo de la imagen de muestra.

ingrese la descripción de la imagen aquí

referencias :

1) Aprendizaje de refuerzo profundo: Pong de píxeles

2) Una introducción a los gradientes de políticas con Cartpole y Doom

3) Derivar gradientes de políticas e implementar REINFORCE

4) Truco de aprendizaje automático del día (5): Log Derivative Trick 12


ACTUALIZAR

Parece que la respuesta a continuación podría hacer algo de trabajo desde el gráfico. Pero no es la probabilidad de registro y ni siquiera el gradiente de la política. Y cambia todo el propósito de la Política de gradiente de RL. Por favor, consulte las referencias anteriores. Siguiendo la imagen, la siguiente declaración.

ingrese la descripción de la imagen aquí

Necesito tomar un gradiente de la función de registro de mi política (que es simplemente pesos y sigmoidactivación).

GensaGames
fuente
44
Le sugiero que publique esta pregunta en Data Science Stack Exchange, ya que es principalmente una pregunta teórica (Stack Overflow es principalmente para preguntas de codificación). También llegará a más personas con conocimientos en este dominio.
Gilles-Philippe Paillé
@ Gilles-PhilippePaillé Agregué código, que representa el problema. Lo que necesito hacer es arreglar algunas partes con la activación. Por favor verifique la respuesta actualizada.
GensaGames
1
Para derivar gradientes de políticas, aquí hay un artículo de referencia con ejemplos de trabajo del mismo tipo de arreglos. Esperemos que aprenda en detalle: medium.com/@thechrisyoon/… .
Muhammad Usman
@MuhammadUsman. Gracias por la informacion. Yo rojo esa fuente. En este momento está claro y forma el ejemplo anterior, estoy tratando de cambiar la activación de softmaxa signmoid. Eso es solo una cosa que necesito hacer en el ejemplo anterior.
GensaGames
2
@JasonChia sigmoid genera un número real en el rango [0, 1]que puede interpretarse como probabilidad de acción positiva (por ejemplo, gire a la derecha en CartPole). Entonces la probabilidad de acción negativa (girar a la izquierda) es 1 - sigmoid. La suma de estas probabilidades es 1. Sí, este es un entorno de tarjeta de poste estándar.
Pavel Tyshevskyi

Respuestas:

8

El problema es con el gradmétodo.

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

En el código original se utilizó Softmax junto con la función de pérdida CrossEntropy. Cuando cambia la activación a Sigmoid, la función de pérdida adecuada se convierte en Binary CrossEntropy. Ahora, el propósito del gradmétodo es calcular el gradiente de la función de pérdida wrt. pesas Ahorrando los detalles, el gradiente adecuado viene dado por (probs - action) * statela terminología de su programa. Lo último es agregar un signo menos: queremos maximizar lo negativo de la función de pérdida.

gradMétodo apropiado así:

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

Otro cambio que es posible que desee agregar es aumentar la tasa de aprendizaje. LEARNING_RATE = 0.0001y NUM_EPISODES = 5000producirá la siguiente trama:

Recompensa media correcta vs número de episodios

La convergencia será mucho más rápida si los pesos se inicializan utilizando distribución gaussiana con media cero y varianza pequeña:

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

ingrese la descripción de la imagen aquí

ACTUALIZAR

Código completo agregado para reproducir los resultados:

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
fuente
Muchas gracias. Probaré esta solución más tarde.
GensaGames
No estoy seguro de dónde tomas esa derivación para mi función. Como puedes consultar la imagen de arriba. Tendría que tomar gradiente del registro de la política . Donde la política en mi caso simplemente pesa sigmoid. Pero su gradiente en respuesta no debería tener nada que ver con mi gradiente. ¿Derecha?
GensaGames
Tenga en cuenta que no incorpora ninguna información sobre qué acción se realizó. De acuerdo con esta conferencia sobre Gradientes de políticas (diapositiva 13) , la actualización debería ser similar (action - probs) * sigmoid_grad(probs), pero omití sigmoid_graddebido al problema de desaparición del gradiente sigmoide.
Pavel Tyshevskyi
El quid aquí es indicar la dirección en la que queremos cambiar los pesos. Si action = 1, queremos probsestar más cerca 1, aumentando los pesos (gradiente positivo). Si action=0, queremos probsestar más cerca de 0, por lo tanto, disminuir los pesos (gradiente negativo).
Pavel Tyshevskyi
1
En cualquier caso, los cambios anteriores no funcionan en absoluto, ¿puede compartir el archivo completo? Al mismo tiempo, quiero hacer una muestra clara, y no me importa el problema de desaparición en este caso. Y (action - probs)es solo otra forma de cambiar los mismos wights.
GensaGames