Qual é a perda de gradientes de política com ações contínuas?

Oct 01 2020

Eu sei que os gradientes de política usados ​​em um ambiente com um espaço de ação discreto são atualizados com $$ \Delta \theta_{t}=\alpha \nabla_{\theta} \log \pi_{\theta}\left(a_{t} \mid s_{t}\right) v_{t} $$onde v pode ser muitas coisas que representam o quão boa foi a ação. E eu sei que isso pode ser calculado realizando a perda de entropia cruzada com o alvo sendo o que a rede teria produzido se estivesse completamente confiante em sua ação (zeros com o índice da ação escolhida sendo um). Mas eu não entendo como aplicar isso a gradientes de política que geram a média e a variância de uma distribuição gaussiana para um espaço de ação contínua. Qual é a perda para esses tipos de gradientes de política?

Tentei manter a variância constante e atualizar a saída com a perda de erro quadrática média e o objetivo sendo a ação realizada. Achei que isso acabaria empurrando a média para ações com maior recompensa total, mas não chegou a lugar nenhum no ambiente Pendulum do OpenAI.

Também seria muito útil se fosse descrito de uma forma com uma função de perda e um alvo, como como gradientes de política com espaços de ação discretos podem ser atualizados com perda de entropia cruzada. É assim que eu entendo melhor, mas tudo bem se isso não for possível.

Edit : Minha implementação com PyTorch não está funcionando para o ambiente de pêndulo. Tentei mudar a taxa de aprendizagem e o sigma, usando métodos de crítico de ator, fazendo com que a rede gerasse a média e a variância e treinando para milhares de episódios com tamanhos de lote diferentes. Usei um método de crítico de ator que funcionou com CartPole e Lunar Lander e, em seguida, mudei algumas linhas, incluindo a distribuição de categórica para normal, mas o agente nunca aprendeu. Aqui está um exemplo reproduzível:


import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions.normal import Normal
import numpy as np
import gym
import matplotlib.pyplot as plt

class Agent(nn.Module):
    def __init__(self,lr):
        super(Agent,self).__init__()
        self.fc1 = nn.Linear(3,64)
        self.fc2 = nn.Linear(64,32)
        self.fc3 = nn.Linear(32,1) #neural network with layers 3,64,32,1

        self.optimizer = optim.Adam(self.parameters(),lr=lr)

    def forward(self,x):
        x = torch.relu(self.fc1(x)) #relu and tanh for output
        x = torch.relu(self.fc2(x))
        x = torch.tanh(self.fc3(x))
        return x

env = gym.make('Pendulum-v0')
agent = Agent(0.0001) #hyperparameters
SIGMA = 0.15
DISCOUNT = 0.99
total = []

for e in range(500): 
    log_probs, rewards = [], []
    done = False
    state = env.reset()
    while not done:
        mu = agent(torch.from_numpy(state).float()) #mean of gaussian distribution
        distribution = Normal(mu*2,0.15) #create distribution with constant sigma and mean multiplied by 2
        action = distribution.sample() #randomly sample from distribution
        state,reward,done,info = env.step([action])
        log_probs.append(distribution.log_prob(action)) #log prob of action
        rewards.append(reward)
    total.append(sum(rewards))

    cumulative = 0
    d_rewards = np.zeros(len(rewards))
    for t in reversed(range(len(rewards))): #get discounted rewards
        cumulative = cumulative * DISCOUNT + rewards[t]
        d_rewards[t] = cumulative
    d_rewards -= np.mean(d_rewards) #normalize
    d_rewards /= np.std(d_rewards)

    loss = 0
    for t in range(len(rewards)):
        loss += -log_probs[t] * d_rewards[t] #loss is - log prob * total reward

    agent.optimizer.zero_grad()
    loss.backward() #update
    agent.optimizer.step()

    if e%10==0:
        print(e,sum(rewards)) 
        plt.plot(total,color='blue') #plot
        plt.pause(0.0001)    


def run(i): #to visualize performance
    for _ in range(i):
        done = False
        state = env.reset()
        while not done:
            env.render()
            mu = agent(torch.from_numpy(state).float())
            distribution = Normal(mu*2,0.15)
            action = distribution.sample()
            state,reward,done,info = env.step([action])
        env.close()  

```

Respostas

2 DavidIreland Oct 02 2020 at 06:44

Esta regra de atualização ainda pode ser aplicada no domínio contínuo.

Conforme apontado nos comentários, suponha que estejamos parametrizando nossa política usando uma distribuição Gaussiana, onde nossas redes neurais tomam como entrada o estado em que estamos e geram os parâmetros de uma distribuição Gaussiana, a média e o desvio padrão que denotaremos como $\mu(s, \theta)$ e $\sigma(s, \theta)$ Onde $s$ mostra a dependência do estado e $\theta$ são os parâmetros da nossa rede.

Vou assumir um caso unidimensional para facilitar a notação, mas isso pode ser estendido para casos multivariados. Nossa política agora é definida como$$\pi(a_t | s_t) = \frac{1}{\sqrt{2\pi \sigma(s_t, \theta)^2}} \exp\left(-\frac{1}{2}\left(\frac{a_t - \mu(s_t, \theta)}{\sigma(s_t, \theta)}\right)^2\right).$$

Como você pode ver, podemos facilmente pegar o logaritmo disso e encontrar a derivada em relação a $\theta$, então nada muda e a perda que você usa é a mesma. Você simplesmente avalia a derivada do log de sua política em relação aos parâmetros de rede, multiplique por$v_t$ e $\alpha$ e dê um passo gradiente nessa direção.

Para implementar isso (como estou assumindo que você não deseja calcular as derivadas NN manualmente), você poderia fazer algo ao longo das linhas a seguir em Pytorch.

Primeiro, você deseja passar seu estado por meio de seu NN para obter a média e o desvio padrão da distribuição gaussiana. Então você quer simular$z \sim N(0,1)$ e calcular $a = \mu(s,\theta) + \sigma(s, \theta) \times z$ de modo a $a \sim N( \mu(s, \theta), \sigma(s, \theta))$ - este é o truque de reparametrização que torna mais fácil a retropropagação através da rede, pois tira a aleatoriedade de uma fonte que não depende dos parâmetros da rede. $a$é sua ação que você executará em seu ambiente e usará para calcular o gradiente simplesmente escrevendo o código torch.log(normal_pdf(a, \mu(s, \theta), \sigma(s, \theta)).backward()- aqui normal_pdf()está qualquer função em Python que calcule o pdf de uma distribuição normal para um determinado ponto e parâmetros.