Критик никогда не сходится в A2C - PullRequest
0 голосов
/ 18 января 2019

Я пытаюсь реализовать A2C с Lasagne + Theano (Python), чтобы решить стандартные проблемы с тренажерным залом OpenAI. Однако мой код, похоже, не сходится ни к чему полезному.

Я уже пробовал разные вещи: я играл с гиперпараметрами, пробовал различные значения для скорости обучения и топологии сети как для актера, так и для критика, я пытался использовать одну и ту же сеть только с двумя выходными данными слои как для актера, так и для критика вместе (и суммируя их потери), как это сделали многие, я пробовал использовать градиентное отсечение или нет, я пробовал другой метод градиентного спуска. Если я избавляюсь от критика и использую дисконтированный доход в качестве сигнала для управления моим градиентом политики (алгоритм REINFORCE), он работает нормально. Просто подключив критику снова, дела идут не так, как надо. Если я избавлюсь от актера и адаптирую архитектуру критики в DQN (таким образом, получая выходные данные для каждого действия вместо одного), он будет работать нормально.

Вот код для создания сети и фактического процесса обучения.

from gym.spaces import Box, Discrete
import lasagne
import numpy as np

class ActorNetwork:
    def __init__(self, x, observation_space, action_space, num_hidden = 2, hidden_units = [64, 64]):
        assert type(observation_space) == Box, "Incompatible observation_space type."
        assert type(action_space) == Discrete, "Incompatible action_space type."
        assert len(hidden_units) == num_hidden, "Number of hidden layers and dimensions mismatch."
        net = lasagne.layers.InputLayer([None] + list(observation_space.shape), x)
        for l in range(num_hidden):
            net = lasagne.layers.DenseLayer(net, hidden_units[l], nonlinearity = lasagne.nonlinearities.leaky_rectify)
        self.out = lasagne.layers.DenseLayer(net, action_space.n,  nonlinearity = lasagne.nonlinearities.softmax)
    def get_output(self):
        return lasagne.layers.get_output(self.out)
    def get_param_values(self):
        return lasagne.layers.get_all_param_values(self.out)
    def set_param_values(self, values):
        return lasagne.layers.set_all_param_values(self.out, values)
    def get_params(self):
        return lasagne.layers.get_all_params(self.out, trainable = True)
    def save_model(self, name):
        np.save(name, lasagne.layers.get_all_param_values(self.out))
    def load_model(self, name):
        lasagne.layers.set_all_param_values(self.out, np.load(name))

class CriticNetwork:
    def __init__(self, x, observation_space, num_hidden = 2, hidden_units = [64, 64]):
        assert type(observation_space) == Box, "Incompatible observation_space type."
        assert len(hidden_units) == num_hidden, "Number of hidden layers and dimensions mismatch."
        net = lasagne.layers.InputLayer([None] + list(observation_space.shape), x)
        for l in range(num_hidden):
            net = lasagne.layers.DenseLayer(net, hidden_units[l], nonlinearity = lasagne.nonlinearities.leaky_rectify)
        self.out = lasagne.layers.DenseLayer(net, 1,  nonlinearity = lasagne.nonlinearities.linear)
    def get_output(self):
        return lasagne.layers.get_output(self.out)
    def get_param_values(self):
        return lasagne.layers.get_all_param_values(self.out)
    def set_param_values(self, values):
        return lasagne.layers.set_all_param_values(self.out, values)
    def get_params(self):
        return lasagne.layers.get_all_params(self.out, trainable = True)
    def save_model(self, name):
        np.save(name, lasagne.layers.get_all_param_values(self.out))
    def load_model(self, name):
        lasagne.layers.set_all_param_values(self.out, np.load(name))
import time
from gym.spaces import Box, Discrete
import lasagne
import numpy as np
import theano
from theano import tensor as T
from theano.gradient import disconnected_grad
import gym
import gym.wrappers
from Networks import ActorNetwork, CriticNetwork

def discount_rewards(rewards, gamma):
    discounted_rewards = []
    rew_sum = 0.
    for r in reversed(rewards):
        rew_sum = r + gamma * rew_sum
        discounted_rewards.append(rew_sum)
    return discounted_rewards[::-1]

def target_network_update(original, target):
    values = original.get_param_values()
    target.set_param_values(values)

learning_rate = 7e-4
learning_rate_cc = 7e-4
gamma = 0.99
ent_lambda = -1e-2
lambda_gae = 1.0
gae = True
grad_clip = 0.5
training_episodes = 10000
episode_max_length = 1000
target_update_every = 100
print_freq = 100
render = False

env = gym.make("CartPole-v0")
#env = gym.wrappers.Monitor(gym.make("CartPole-v0"), directory="videos", force=True)

obs_t = T.dmatrix("obs_t")
critic_targets_t = T.dvector("critic_targets_t")
advantages_t = T.dvector("advantages_t")
act_t = T.ivector("act_t")

actor = ActorNetwork(obs_t, env.observation_space, env.action_space)
actor_out = actor.get_output()
actions_prob = theano.function([obs_t], actor_out, allow_input_downcast = True)

critic = CriticNetwork(obs_t, env.observation_space)
critic_target = CriticNetwork(obs_t, env.observation_space)
target_network_update(critic, critic_target)
critic_out = critic.get_output()
target_out = critic_target.get_output()
value_function = theano.function([obs_t], critic_out, allow_input_downcast = True)
value_target = theano.function([obs_t], target_out, allow_input_downcast = True)

critic_loss = T.mean(lasagne.objectives.squared_error(disconnected_grad(critic_targets_t), critic_out))
critic_params = critic.get_params()
critic_grads = lasagne.updates.total_norm_constraint(T.grad(critic_loss, critic_params), grad_clip)
critic_updates = lasagne.updates.rmsprop(critic_grads, critic_params, learning_rate_cc)
train_critic = theano.function([obs_t, critic_targets_t], updates = critic_updates, allow_input_downcast = True)

actor_loss = T.mean(T.log(actor_out[T.arange(act_t.shape[0]),act_t] + 1e-20) * disconnected_grad(advantages_t) + ent_lambda * (np.log(actor_out + 1e-20) * actor_out).sum(-1))
actor_params = actor.get_params()
actor_grads = lasagne.updates.total_norm_constraint(T.grad(-actor_loss, actor_params), grad_clip)
actor_updates = lasagne.updates.rmsprop(actor_grads, actor_params, learning_rate)
train_actor = theano.function([obs_t, advantages_t, act_t], updates = actor_updates, allow_input_downcast = True)

start = time.time()
episodes_rewards = []

for e in range(training_episodes):
    ep_start = time.time()
    obs_batch = []
    new_obs_batch = []
    actions_batch = []
    rewards_batch = []
    dones_batch = []

    obs = env.reset()
    for t in range(episode_max_length):
        if render:
            env.render()
        obs_batch.append(obs)
        action = np.random.choice(env.action_space.n, p = actions_prob([obs])[0])
        actions_batch.append(action)

        new_obs, rewards, dones, _ = env.step(action)
        new_obs_batch.append(new_obs)
        rewards_batch.append(rewards)
        dones_batch.append(dones)
        obs = new_obs
        if dones:
            break

    episodes_rewards.append(sum(rewards_batch))
    if e % print_freq == 0:
        print("Episode {}/{} ended in {} steps: mean reward is {}".format(e, training_episodes, t + 1, np.mean(episodes_rewards[-print_freq:])))

    if np.mean(episodes_rewards[-100:]) >= env.spec.reward_threshold:
        print("You won!")
        break

    critic_targets = rewards_batch + gamma * np.squeeze(value_target(new_obs_batch)) * np.logical_not(dones_batch)
    train_critic(obs_batch, critic_targets)
    advantages = rewards_batch + gamma * np.squeeze(value_function(new_obs_batch)) * np.logical_not(dones_batch) - np.squeeze(value_function(obs_batch))
    if gae:
        advantages = discount_rewards(advantages, gamma * lambda_gae)
    train_actor(obs_batch, advantages, actions_batch)

    if e % target_update_every == 0:
        target_network_update(critic, critic_target)

print("Total training time: {}".format(round(time.time() - start, 3)))

#env.close()
actor.save_model("actor")
critic.save_model("critic")

Производительность в очень простой среде CartPole ужасна (она может покачивать полюс примерно на 10 шагов, в то время как с DQN и REINFORCE мне легко удавалось последовательно достигать 200 и решать игру). Любое предложение о том, что я делаю неправильно в коде?

...