Noções básicas sobre derivação de política de gradiente

19

Estou tentando recriar um exemplo muito simples de Gradiente de política, a partir do blog de Andrej Karpathy, recurso de origem . Nesse artigo, você encontrará exemplos com CartPole e Policy Gradient com lista de peso e ativação do Softmax. Aqui está o meu exemplo recriado e muito simples de gradiente de política CartPole, que funciona perfeitamente .

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)

insira a descrição da imagem aqui

.

.

Questão

Estou tentando fazer, quase o mesmo exemplo, mas com a ativação do Sigmoid (apenas por simplicidade). É tudo o que preciso fazer. Alterne a ativação no modelo de softmaxpara sigmoid. O que deve funcionar com certeza (com base na explicação abaixo). Mas meu modelo de gradiente de política não aprende nada e é aleatório. Alguma sugestão?

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)

A plotagem de todo o aprendizado é aleatória. Nada ajuda no ajuste de hiper parâmetros. Abaixo da imagem de amostra.

insira a descrição da imagem aqui

Referências :

1) Aprendizado de reforço profundo: Pong from Pixels

2) Uma introdução aos Gradientes de Política com Cartpole e Doom

3) Obtenção de gradientes de política e implementação do REFORÇO

4) Truque do dia para aprendizado de máquina (5): Log Derivative Trick 12


ATUALIZAR

Parece que a resposta abaixo pode fazer algum trabalho com o gráfico. Mas não é a probabilidade de log e nem o gradiente da política. E altera todo o objetivo da Política de Gradiente da RL. Por favor, verifique as referências acima. Seguindo a imagem, próxima declaração.

insira a descrição da imagem aqui

Preciso fazer um Gradiente da função Log da minha Política (que é simplesmente pesos e sigmoidativação).

GensaGames
fonte
4
Sugiro que você publique esta pergunta no Data Science Stack Exchange, pois é principalmente uma questão teórica (o Stack Overflow é principalmente para perguntas de codificação). Você também alcançará mais pessoas com conhecimento neste domínio.
Gilles-Philippe Paillé
@ Gilles-PhilippePaillé Adicionei código, que representa o problema. O que eu preciso fazer é apenas consertar algumas partes com a ativação. Por favor, verifique a resposta atualizada.
GensaGames 10/11/19
11
Para obter gradientes de política, aqui está um artigo de referência com um exemplo prático do mesmo tipo de arranjos. Espero que você aprenda em detalhes: medium.com/@thechrisyoon/… .
Muhammad Usman
@MuhammadUsman. Obrigado pela informação. Eu vermelho essa fonte. No momento, está claro e, como exemplo acima, estou tentando alterar a ativação de softmaxpara signmoid. Essa é apenas uma coisa que preciso fazer no exemplo acima.
GensaGames 11/11/19
2
@JasonChia sigmoid gera um número real no intervalo [0, 1]que pode ser interpretado como probabilidade de ação positiva (vire à direita no CartPole, por exemplo). Então a probabilidade de ação negativa (vire à esquerda) é 1 - sigmoid. A soma dessas probabilidades é 1. Sim, este é um ambiente padrão de cartões com pólos.
Pavel Tyshevskyi 19/11/19

Respostas:

8

O problema está no 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

No código original, Softmax foi usado junto com a função de perda CrossEntropy. Quando você alterna a ativação para Sigmoid, a função de perda adequada se torna Binário CrossEntropy. Agora, o objetivo do gradmétodo é calcular o gradiente da função de perda wrt. pesos. Poupando os detalhes, o gradiente adequado é dado (probs - action) * statena terminologia do seu programa. A última coisa é adicionar sinal de menos - queremos maximizar o negativo da função de perda.

gradMétodo adequado assim:

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

Outra mudança que você pode querer adicionar é aumentar a taxa de aprendizado. LEARNING_RATE = 0.0001e NUM_EPISODES = 5000produzirá o seguinte gráfico:

Recompensa média correta versus número de episódios

A convergência será muito mais rápida se os pesos forem inicializados usando a distribuição Gaussiana com média zero e pequena variação:

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

insira a descrição da imagem aqui

ATUALIZAR

Adicionado código completo para reproduzir os 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
fonte
Muito obrigado. Vou tentar esta solução mais tarde.
GensaGames 13/11/19
Não tenho certeza de onde você tira essa derivação para minha função. Como você pode conferir na imagem acima. Eu precisaria fazer o gradiente do log da política . Onde Policy no meu caso simplesmente pesa sigmoid. Mas o seu gradiente de resposta não deve ter nada a ver com o meu gradiente. Direita?
GensaGames 13/11/19
Observe que você não incorpora nenhuma informação sobre qual ação foi executada. De acordo com esta palestra sobre gradientes de política (slide 13) , a atualização deve parecer (action - probs) * sigmoid_grad(probs), mas eu o omiti sigmoid_gradpor causa do problema de desaparecimento do gradiente sigmóide.
Pavel Tyshevskyi 14/11/19
O ponto crucial aqui é indicar a direção na qual queremos alterar os pesos. Se action = 1, queremos probsestar mais perto 1, aumentando os pesos (gradiente positivo). Se action=0queremos probsestar mais próximos 0, diminuindo os pesos (gradiente negativo).
Pavel Tyshevskyi 14/11/19
11
De qualquer forma, as alterações acima não funcionam, você pode compartilhar o arquivo inteiro? Ao mesmo tempo, quero fazer uma amostra clara e não me importo com o problema de fuga neste caso. E (action - probs)é apenas outra maneira de mudar as mesmas ideias.
GensaGames