Qual è la perdita per i gradienti di policy con azioni continue?
So che con i gradienti dei criteri utilizzati in un ambiente con uno spazio di azione discreto vengono aggiornati $$ \Delta \theta_{t}=\alpha \nabla_{\theta} \log \pi_{\theta}\left(a_{t} \mid s_{t}\right) v_{t} $$dove v potrebbero essere molte cose che rappresentano quanto è stata buona l'azione. E so che questo può essere calcolato eseguendo la perdita di entropia incrociata con l'obiettivo che è ciò che la rete avrebbe prodotto se fosse completamente sicura della sua azione (zeri con l'indice dell'azione scelta uno). Ma non capisco come applicarlo ai gradienti di policy che producono la media e la varianza di una distribuzione gaussiana per uno spazio di azione continua. Qual è la perdita per questi tipi di gradienti di policy?
Ho provato a mantenere la varianza costante e ad aggiornare l'output con la perdita di errore quadratica media e l'obiettivo era l'azione intrapresa. Pensavo che questo avrebbe finito per spingere la media verso azioni con maggiori ricompense totali, ma non ha ottenuto nulla nell'ambiente Pendulum di OpenAI.
Sarebbe anche molto utile se fosse descritto in un modo con una funzione di perdita e un obiettivo, come il modo in cui i gradienti di policy con spazi di azione discreti possono essere aggiornati con la perdita di entropia incrociata. È così che lo capisco meglio, ma va bene se ciò non è possibile.
Modifica : la mia implementazione con PyTorch non funziona per l'ambiente del pendolo. Ho provato a cambiare il tasso di apprendimento e il sigma, utilizzando metodi di critica dell'attore, facendo in modo che la rete produca media e varianza e allenandomi per migliaia di episodi con batch di dimensioni diverse. Ho usato un metodo critico dell'attore che ha funzionato con CartPole e Lunar Lander, quindi ho cambiato un paio di righe inclusa la distribuzione da categoriale a normale, ma l'agente non ha mai imparato. Ecco un esempio riproducibile:
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()
```
Risposte
Questa regola di aggiornamento può ancora essere applicata nel dominio continuo.
Come sottolineato nei commenti, supponiamo di parametrizzare la nostra politica utilizzando una distribuzione gaussiana, dove le nostre reti neurali prendono come input lo stato in cui ci troviamo e forniscono i parametri di una distribuzione gaussiana, la media e la deviazione standard che indicheremo come $\mu(s, \theta)$ e $\sigma(s, \theta)$ dove $s$ mostra la dipendenza dello stato e $\theta$ sono i parametri della nostra rete.
Assumerò un caso unidimensionale per facilità di notazione, ma questo può essere esteso a casi multi-variata. La nostra politica è ora definita come$$\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).$$
Come puoi vedere, possiamo facilmente prendere il logaritmo di questo e trovare la derivata rispetto a $\theta$e quindi non cambia nulla e la perdita che usi è la stessa. Valutate semplicemente la derivata del log della vostra polizza rispetto ai parametri di rete, moltiplicate per$v_t$ e $\alpha$ e fai un gradiente in questa direzione.
Per implementarlo (poiché presumo che tu non voglia calcolare le derivate NN a mano), potresti fare qualcosa sulla falsariga di quanto segue in Pytorch.
Per prima cosa vuoi passare il tuo stato attraverso il tuo NN per ottenere la media e la deviazione standard della distribuzione gaussiana. Quindi vuoi simulare$z \sim N(0,1)$ e calcola $a = \mu(s,\theta) + \sigma(s, \theta) \times z$ così che $a \sim N( \mu(s, \theta), \sigma(s, \theta))$ - questo è il trucco della riparametrizzazione che facilita la backpropagation attraverso la rete in quanto prende la casualità da una sorgente che non dipende dai parametri della rete. $a$è la tua azione che eseguirai nel tuo ambiente e utilizzerai per calcolare il gradiente semplicemente scrivendo il codice torch.log(normal_pdf(a, \mu(s, \theta), \sigma(s, \theta)).backward()- ecco normal_pdf()una qualsiasi funzione in Python che calcola il pdf di una distribuzione normale per un dato punto e parametri.