Added Evolutionary Strategies Network and added more example scripts
This commit is contained in:
parent
26084d4c7c
commit
76a044ace9
14 changed files with 695 additions and 41 deletions
161
examples/acrobot_a2c.py
Normal file
161
examples/acrobot_a2c.py
Normal file
|
@ -0,0 +1,161 @@
|
|||
import gym
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import rltorch
|
||||
import rltorch.network as rn
|
||||
import rltorch.memory as M
|
||||
import rltorch.env as E
|
||||
from rltorch.action_selector import StochasticSelector
|
||||
from tensorboardX import SummaryWriter
|
||||
import torch.multiprocessing as mp
|
||||
import signal
|
||||
from copy import deepcopy
|
||||
|
||||
class Value(nn.Module):
|
||||
def __init__(self, state_size):
|
||||
super(Value, self).__init__()
|
||||
self.state_size = state_size
|
||||
|
||||
self.fc1 = rn.NoisyLinear(state_size, 64)
|
||||
self.fc_norm = nn.LayerNorm(64)
|
||||
|
||||
self.fc2 = rn.NoisyLinear(64, 64)
|
||||
self.fc2_norm = nn.LayerNorm(64)
|
||||
|
||||
self.fc3 = rn.NoisyLinear(64, 1)
|
||||
|
||||
def forward(self, x):
|
||||
x = F.relu(self.fc_norm(self.fc1(x)))
|
||||
|
||||
x = F.relu(self.fc2_norm(self.fc2(x)))
|
||||
|
||||
x = self.fc3(x)
|
||||
|
||||
return x
|
||||
|
||||
class Policy(nn.Module):
|
||||
def __init__(self, state_size, action_size):
|
||||
super(Policy, self).__init__()
|
||||
self.state_size = state_size
|
||||
self.action_size = action_size
|
||||
|
||||
self.fc1 = rn.NoisyLinear(state_size, 64)
|
||||
self.fc_norm = nn.LayerNorm(64)
|
||||
|
||||
self.fc2 = rn.NoisyLinear(64, 64)
|
||||
self.fc2_norm = nn.LayerNorm(64)
|
||||
|
||||
self.fc3 = rn.NoisyLinear(64, action_size)
|
||||
# self.fc3_norm = nn.LayerNorm(action_size)
|
||||
|
||||
# self.value_fc = rn.NoisyLinear(64, 64)
|
||||
# self.value_fc_norm = nn.LayerNorm(64)
|
||||
# self.value = rn.NoisyLinear(64, 1)
|
||||
|
||||
# self.advantage_fc = rn.NoisyLinear(64, 64)
|
||||
# self.advantage_fc_norm = nn.LayerNorm(64)
|
||||
# self.advantage = rn.NoisyLinear(64, action_size)
|
||||
|
||||
def forward(self, x):
|
||||
x = F.relu(self.fc_norm(self.fc1(x)))
|
||||
|
||||
x = F.relu(self.fc2_norm(self.fc2(x)))
|
||||
|
||||
x = F.softmax(self.fc3(x), dim = 1)
|
||||
|
||||
# state_value = F.relu(self.value_fc_norm(self.value_fc(x)))
|
||||
# state_value = self.value(state_value)
|
||||
|
||||
# advantage = F.relu(self.advantage_fc_norm(self.advantage_fc(x)))
|
||||
# advantage = self.advantage(advantage)
|
||||
|
||||
# x = F.softmax(state_value + advantage - advantage.mean(), dim = 1)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
config = {}
|
||||
config['seed'] = 901
|
||||
config['environment_name'] = 'Acrobot-v1'
|
||||
config['memory_size'] = 2000
|
||||
config['total_training_episodes'] = 500
|
||||
config['total_evaluation_episodes'] = 10
|
||||
config['batch_size'] = 32
|
||||
config['learning_rate'] = 1e-3
|
||||
config['target_sync_tau'] = 1e-1
|
||||
config['discount_rate'] = 0.99
|
||||
config['replay_skip'] = 0
|
||||
# How many episodes between printing out the episode stats
|
||||
config['print_stat_n_eps'] = 1
|
||||
config['disable_cuda'] = False
|
||||
|
||||
|
||||
def train(runner, agent, config, logger = None, logwriter = None):
|
||||
finished = False
|
||||
last_episode_num = 1
|
||||
while not finished:
|
||||
runner.run(config['replay_skip'] + 1)
|
||||
agent.learn()
|
||||
if logwriter is not None:
|
||||
if last_episode_num < runner.episode_num:
|
||||
last_episode_num = runner.episode_num
|
||||
agent.value_net.log_named_parameters()
|
||||
agent.policy_net.log_named_parameters()
|
||||
logwriter.write(logger)
|
||||
finished = runner.episode_num > config['total_training_episodes']
|
||||
|
||||
if __name__ == "__main__":
|
||||
torch.multiprocessing.set_sharing_strategy('file_system') # To not hit file descriptor memory limit
|
||||
|
||||
# Setting up the environment
|
||||
rltorch.set_seed(config['seed'])
|
||||
print("Setting up environment...", end = " ")
|
||||
env = E.TorchWrap(gym.make(config['environment_name']))
|
||||
env.seed(config['seed'])
|
||||
print("Done.")
|
||||
|
||||
state_size = env.observation_space.shape[0]
|
||||
action_size = env.action_space.n
|
||||
|
||||
# Logging
|
||||
logger = rltorch.log.Logger()
|
||||
logwriter = rltorch.log.LogWriter(SummaryWriter())
|
||||
|
||||
# Setting up the networks
|
||||
device = torch.device("cuda:0" if torch.cuda.is_available() and not config['disable_cuda'] else "cpu")
|
||||
policy_net = rn.Network(Policy(state_size, action_size),
|
||||
torch.optim.Adam, config, device = device, name = "Policy")
|
||||
value_net = rn.Network(Value(state_size),
|
||||
torch.optim.Adam, config, device = device, name = "DQN")
|
||||
|
||||
|
||||
# Memory stores experiences for later training
|
||||
memory = M.EpisodeMemory()
|
||||
|
||||
# Actor takes a net and uses it to produce actions from given states
|
||||
actor = StochasticSelector(policy_net, action_size, memory, device = device)
|
||||
|
||||
# Agent is what performs the training
|
||||
# agent = rltorch.agents.REINFORCEAgent(net, memory, config, target_net = target_net, logger = logger)
|
||||
agent = rltorch.agents.A2CSingleAgent(policy_net, value_net, memory, config, logger = logger)
|
||||
|
||||
# Runner performs a certain number of steps in the environment
|
||||
runner = rltorch.env.EnvironmentRunSync(env, actor, config, name = "Training", memory = memory, logwriter = logwriter)
|
||||
|
||||
print("Training...")
|
||||
train(runner, agent, config, logger = logger, logwriter = logwriter)
|
||||
|
||||
# For profiling...
|
||||
# import cProfile
|
||||
# cProfile.run('train(runner, agent, config, logger = logger, logwriter = logwriter )')
|
||||
# python -m torch.utils.bottleneck /path/to/source/script.py [args] is also a good solution...
|
||||
|
||||
print("Training Finished.")
|
||||
|
||||
print("Evaluating...")
|
||||
rltorch.env.simulateEnvEps(env, actor, config, total_episodes = config['total_evaluation_episodes'], logger = logger, name = "Evaluation")
|
||||
print("Evaulations Done.")
|
||||
|
||||
logwriter.close() # We don't need to write anything out to disk anymore
|
120
examples/acrobot_es.py
Normal file
120
examples/acrobot_es.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
import gym
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from torch.distributions import Categorical
|
||||
import rltorch
|
||||
import rltorch.network as rn
|
||||
import rltorch.memory as M
|
||||
import rltorch.env as E
|
||||
from rltorch.action_selector import StochasticSelector
|
||||
from tensorboardX import SummaryWriter
|
||||
import torch.multiprocessing as mp
|
||||
|
||||
class Policy(nn.Module):
|
||||
def __init__(self, state_size, action_size):
|
||||
super(Policy, self).__init__()
|
||||
self.state_size = state_size
|
||||
self.action_size = action_size
|
||||
|
||||
self.fc1 = nn.Linear(state_size, 125)
|
||||
self.fc_norm = nn.LayerNorm(125)
|
||||
|
||||
self.fc2 = nn.Linear(125, 125)
|
||||
self.fc2_norm = nn.LayerNorm(125)
|
||||
|
||||
self.action_prob = nn.Linear(125, action_size)
|
||||
|
||||
def forward(self, x):
|
||||
x = F.relu(self.fc_norm(self.fc1(x)))
|
||||
x = F.relu(self.fc2_norm(self.fc2(x)))
|
||||
x = F.softmax(self.action_prob(x), dim = 1)
|
||||
return x
|
||||
|
||||
config = {}
|
||||
config['seed'] = 901
|
||||
config['environment_name'] = 'Acrobot-v1'
|
||||
config['memory_size'] = 2000
|
||||
config['total_training_episodes'] = 50
|
||||
config['total_evaluation_episodes'] = 5
|
||||
config['batch_size'] = 32
|
||||
config['learning_rate'] = 1e-1
|
||||
config['target_sync_tau'] = 1e-1
|
||||
config['discount_rate'] = 0.99
|
||||
config['replay_skip'] = 0
|
||||
# How many episodes between printing out the episode stats
|
||||
config['print_stat_n_eps'] = 1
|
||||
config['disable_cuda'] = False
|
||||
|
||||
|
||||
|
||||
def train(env, net, actor, config, logger = None, logwriter = None):
|
||||
finished = False
|
||||
episode_num = 1
|
||||
while not finished:
|
||||
rltorch.env.simulateEnvEps(env, actor, config, logger = logger, name = "Training")
|
||||
episode_num += 1
|
||||
net.calc_gradients()
|
||||
net.step()
|
||||
# When the episode number changes, log network paramters
|
||||
if logwriter is not None:
|
||||
net.log_named_parameters()
|
||||
logwriter.write(logger)
|
||||
finished = episode_num > config['total_training_episodes']
|
||||
|
||||
def fitness(model):
|
||||
env = gym.make("Acrobot-v1")
|
||||
state = torch.from_numpy(env.reset()).float().unsqueeze(0)
|
||||
total_reward = 0
|
||||
done = False
|
||||
while not done:
|
||||
action_probabilities = model(state)
|
||||
distribution = Categorical(action_probabilities)
|
||||
action = distribution.sample().item()
|
||||
next_state, reward, done, _ = env.step(action)
|
||||
total_reward += reward
|
||||
state = torch.from_numpy(next_state).float().unsqueeze(0)
|
||||
return total_reward
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Setting up the environment
|
||||
rltorch.set_seed(config['seed'])
|
||||
print("Setting up environment...", end = " ")
|
||||
env = E.TorchWrap(gym.make(config['environment_name']))
|
||||
env.seed(config['seed'])
|
||||
print("Done.")
|
||||
|
||||
state_size = env.observation_space.shape[0]
|
||||
action_size = env.action_space.n
|
||||
|
||||
# Logging
|
||||
logger = rltorch.log.Logger()
|
||||
# logwriter = rltorch.log.LogWriter(logger, SummaryWriter())
|
||||
logwriter = rltorch.log.LogWriter(SummaryWriter())
|
||||
|
||||
# Setting up the networks
|
||||
device = torch.device("cuda:0" if torch.cuda.is_available() and not config['disable_cuda'] else "cpu")
|
||||
net = rn.ESNetwork(Policy(state_size, action_size),
|
||||
torch.optim.Adam, 100, fitness, config, device = device, name = "ES", logger = logger)
|
||||
net.model.share_memory()
|
||||
|
||||
# Actor takes a net and uses it to produce actions from given states
|
||||
actor = StochasticSelector(net, action_size, device = device)
|
||||
|
||||
print("Training...")
|
||||
|
||||
train(env, net, actor, config, logger = logger, logwriter = logwriter)
|
||||
|
||||
# For profiling...
|
||||
# import cProfile
|
||||
# cProfile.run('train(runner, agent, config, logger = logger, logwriter = logwriter )')
|
||||
# python -m torch.utils.bottleneck /path/to/source/script.py [args] is also a good solution...
|
||||
|
||||
print("Training Finished.")
|
||||
|
||||
print("Evaluating...")
|
||||
rltorch.env.simulateEnvEps(env, actor, config, total_episodes = config['total_evaluation_episodes'], logger = logger, name = "Evaluation")
|
||||
print("Evaulations Done.")
|
||||
|
||||
logwriter.close() # We don't need to write anything out to disk anymore
|
161
examples/acrobot_ppo.py
Normal file
161
examples/acrobot_ppo.py
Normal file
|
@ -0,0 +1,161 @@
|
|||
import gym
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import rltorch
|
||||
import rltorch.network as rn
|
||||
import rltorch.memory as M
|
||||
import rltorch.env as E
|
||||
from rltorch.action_selector import StochasticSelector
|
||||
from tensorboardX import SummaryWriter
|
||||
import torch.multiprocessing as mp
|
||||
import signal
|
||||
from copy import deepcopy
|
||||
|
||||
class Value(nn.Module):
|
||||
def __init__(self, state_size):
|
||||
super(Value, self).__init__()
|
||||
self.state_size = state_size
|
||||
|
||||
self.fc1 = rn.NoisyLinear(state_size, 64)
|
||||
self.fc_norm = nn.LayerNorm(64)
|
||||
|
||||
self.fc2 = rn.NoisyLinear(64, 64)
|
||||
self.fc2_norm = nn.LayerNorm(64)
|
||||
|
||||
self.fc3 = rn.NoisyLinear(64, 1)
|
||||
|
||||
def forward(self, x):
|
||||
x = F.relu(self.fc_norm(self.fc1(x)))
|
||||
|
||||
x = F.relu(self.fc2_norm(self.fc2(x)))
|
||||
|
||||
x = self.fc3(x)
|
||||
|
||||
return x
|
||||
|
||||
class Policy(nn.Module):
|
||||
def __init__(self, state_size, action_size):
|
||||
super(Policy, self).__init__()
|
||||
self.state_size = state_size
|
||||
self.action_size = action_size
|
||||
|
||||
self.fc1 = rn.NoisyLinear(state_size, 64)
|
||||
self.fc_norm = nn.LayerNorm(64)
|
||||
|
||||
self.fc2 = rn.NoisyLinear(64, 64)
|
||||
self.fc2_norm = nn.LayerNorm(64)
|
||||
|
||||
self.fc3 = rn.NoisyLinear(64, action_size)
|
||||
# self.fc3_norm = nn.LayerNorm(action_size)
|
||||
|
||||
# self.value_fc = rn.NoisyLinear(64, 64)
|
||||
# self.value_fc_norm = nn.LayerNorm(64)
|
||||
# self.value = rn.NoisyLinear(64, 1)
|
||||
|
||||
# self.advantage_fc = rn.NoisyLinear(64, 64)
|
||||
# self.advantage_fc_norm = nn.LayerNorm(64)
|
||||
# self.advantage = rn.NoisyLinear(64, action_size)
|
||||
|
||||
def forward(self, x):
|
||||
x = F.relu(self.fc_norm(self.fc1(x)))
|
||||
|
||||
x = F.relu(self.fc2_norm(self.fc2(x)))
|
||||
|
||||
x = F.softmax(self.fc3(x), dim = 1)
|
||||
|
||||
# state_value = F.relu(self.value_fc_norm(self.value_fc(x)))
|
||||
# state_value = self.value(state_value)
|
||||
|
||||
# advantage = F.relu(self.advantage_fc_norm(self.advantage_fc(x)))
|
||||
# advantage = self.advantage(advantage)
|
||||
|
||||
# x = F.softmax(state_value + advantage - advantage.mean(), dim = 1)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
config = {}
|
||||
config['seed'] = 901
|
||||
config['environment_name'] = 'Acrobot-v1'
|
||||
config['memory_size'] = 2000
|
||||
config['total_training_episodes'] = 500
|
||||
config['total_evaluation_episodes'] = 10
|
||||
config['batch_size'] = 32
|
||||
config['learning_rate'] = 1e-3
|
||||
config['target_sync_tau'] = 1e-1
|
||||
config['discount_rate'] = 0.99
|
||||
config['replay_skip'] = 0
|
||||
# How many episodes between printing out the episode stats
|
||||
config['print_stat_n_eps'] = 1
|
||||
config['disable_cuda'] = False
|
||||
|
||||
|
||||
def train(runner, agent, config, logger = None, logwriter = None):
|
||||
finished = False
|
||||
last_episode_num = 1
|
||||
while not finished:
|
||||
runner.run(config['replay_skip'] + 1)
|
||||
agent.learn()
|
||||
if logwriter is not None:
|
||||
if last_episode_num < runner.episode_num:
|
||||
last_episode_num = runner.episode_num
|
||||
agent.value_net.log_named_parameters()
|
||||
agent.policy_net.log_named_parameters()
|
||||
logwriter.write(logger)
|
||||
finished = runner.episode_num > config['total_training_episodes']
|
||||
|
||||
if __name__ == "__main__":
|
||||
torch.multiprocessing.set_sharing_strategy('file_system') # To not hit file descriptor memory limit
|
||||
|
||||
# Setting up the environment
|
||||
rltorch.set_seed(config['seed'])
|
||||
print("Setting up environment...", end = " ")
|
||||
env = E.TorchWrap(gym.make(config['environment_name']))
|
||||
env.seed(config['seed'])
|
||||
print("Done.")
|
||||
|
||||
state_size = env.observation_space.shape[0]
|
||||
action_size = env.action_space.n
|
||||
|
||||
# Logging
|
||||
logger = rltorch.log.Logger()
|
||||
logwriter = rltorch.log.LogWriter(SummaryWriter())
|
||||
|
||||
# Setting up the networks
|
||||
device = torch.device("cuda:0" if torch.cuda.is_available() and not config['disable_cuda'] else "cpu")
|
||||
policy_net = rn.Network(Policy(state_size, action_size),
|
||||
torch.optim.Adam, config, device = device, name = "Policy")
|
||||
value_net = rn.Network(Value(state_size),
|
||||
torch.optim.Adam, config, device = device, name = "DQN")
|
||||
|
||||
|
||||
# Memory stores experiences for later training
|
||||
memory = M.EpisodeMemory()
|
||||
|
||||
# Actor takes a net and uses it to produce actions from given states
|
||||
actor = StochasticSelector(policy_net, action_size, memory, device = device)
|
||||
|
||||
# Agent is what performs the training
|
||||
# agent = rltorch.agents.REINFORCEAgent(net, memory, config, target_net = target_net, logger = logger)
|
||||
agent = rltorch.agents.PPOAgent(policy_net, value_net, memory, config, logger = logger)
|
||||
|
||||
# Runner performs a certain number of steps in the environment
|
||||
runner = rltorch.env.EnvironmentRunSync(env, actor, config, name = "Training", memory = memory, logwriter = logwriter)
|
||||
|
||||
print("Training...")
|
||||
train(runner, agent, config, logger = logger, logwriter = logwriter)
|
||||
|
||||
# For profiling...
|
||||
# import cProfile
|
||||
# cProfile.run('train(runner, agent, config, logger = logger, logwriter = logwriter )')
|
||||
# python -m torch.utils.bottleneck /path/to/source/script.py [args] is also a good solution...
|
||||
|
||||
print("Training Finished.")
|
||||
|
||||
print("Evaluating...")
|
||||
rltorch.env.simulateEnvEps(env, actor, config, total_episodes = config['total_evaluation_episodes'], logger = logger, name = "Evaluation")
|
||||
print("Evaulations Done.")
|
||||
|
||||
logwriter.close() # We don't need to write anything out to disk anymore
|
|
@ -7,9 +7,10 @@ import rltorch
|
|||
import rltorch.network as rn
|
||||
import rltorch.memory as M
|
||||
import rltorch.env as E
|
||||
from rltorch.action_selector import ArgMaxSelector
|
||||
from rltorch.action_selector import StochasticSelector
|
||||
from tensorboardX import SummaryWriter
|
||||
import torch.multiprocessing as mp
|
||||
from copy import deepcopy
|
||||
|
||||
class Value(nn.Module):
|
||||
def __init__(self, state_size, action_size):
|
||||
|
@ -17,16 +18,16 @@ class Value(nn.Module):
|
|||
self.state_size = state_size
|
||||
self.action_size = action_size
|
||||
|
||||
self.fc1 = rn.NoisyLinear(state_size, 64)
|
||||
self.fc_norm = nn.LayerNorm(64)
|
||||
self.fc1 = rn.NoisyLinear(state_size, 255)
|
||||
self.fc_norm = nn.LayerNorm(255)
|
||||
|
||||
self.value_fc = rn.NoisyLinear(64, 64)
|
||||
self.value_fc_norm = nn.LayerNorm(64)
|
||||
self.value = rn.NoisyLinear(64, 1)
|
||||
self.value_fc = rn.NoisyLinear(255, 255)
|
||||
self.value_fc_norm = nn.LayerNorm(255)
|
||||
self.value = rn.NoisyLinear(255, 1)
|
||||
|
||||
self.advantage_fc = rn.NoisyLinear(64, 64)
|
||||
self.advantage_fc_norm = nn.LayerNorm(64)
|
||||
self.advantage = rn.NoisyLinear(64, action_size)
|
||||
self.advantage_fc = rn.NoisyLinear(255, 255)
|
||||
self.advantage_fc_norm = nn.LayerNorm(255)
|
||||
self.advantage = rn.NoisyLinear(255, action_size)
|
||||
|
||||
def forward(self, x):
|
||||
x = F.relu(self.fc_norm(self.fc1(x)))
|
||||
|
@ -42,12 +43,32 @@ class Value(nn.Module):
|
|||
return x
|
||||
|
||||
|
||||
class Policy(nn.Module):
|
||||
def __init__(self, state_size, action_size):
|
||||
super(Policy, self).__init__()
|
||||
self.state_size = state_size
|
||||
self.action_size = action_size
|
||||
|
||||
self.fc1 = nn.Linear(state_size, 125)
|
||||
self.fc_norm = nn.LayerNorm(125)
|
||||
|
||||
self.fc2 = nn.Linear(125, 125)
|
||||
self.fc2_norm = nn.LayerNorm(125)
|
||||
|
||||
self.action_prob = nn.Linear(125, action_size)
|
||||
|
||||
def forward(self, x):
|
||||
x = F.relu(self.fc_norm(self.fc1(x)))
|
||||
x = F.relu(self.fc2_norm(self.fc2(x)))
|
||||
x = F.softmax(self.action_prob(x), dim = 1)
|
||||
return x
|
||||
|
||||
config = {}
|
||||
config['seed'] = 901
|
||||
config['environment_name'] = 'Acrobot-v1'
|
||||
config['memory_size'] = 2000
|
||||
config['total_training_episodes'] = 50
|
||||
config['total_evaluation_episodes'] = 10
|
||||
config['total_evaluation_episodes'] = 5
|
||||
config['batch_size'] = 32
|
||||
config['learning_rate'] = 1e-3
|
||||
config['target_sync_tau'] = 1e-1
|
||||
|
@ -65,28 +86,24 @@ config['prioritized_replay_sampling_priority'] = 0.6
|
|||
# 1 - Lower the importance of high losses
|
||||
# Should ideally start from 0 and move your way to 1 to prevent overfitting
|
||||
config['prioritized_replay_weight_importance'] = rltorch.scheduler.ExponentialScheduler(initial_value = 0.4, end_value = 1, iterations = 5000)
|
||||
|
||||
|
||||
|
||||
|
||||
def train(runner, agent, config, logger = None, logwriter = None):
|
||||
finished = False
|
||||
last_episode_num = 1
|
||||
while not finished:
|
||||
runner.run()
|
||||
runner.run(config['replay_skip'] + 1)
|
||||
agent.learn()
|
||||
runner.join()
|
||||
# When the episode number changes, log network paramters
|
||||
with runner.episode_num.get_lock():
|
||||
if logwriter is not None and last_episode_num < runner.episode_num.value:
|
||||
last_episode_num = runner.episode_num.value
|
||||
agent.net.log_named_parameters()
|
||||
if logwriter is not None:
|
||||
logwriter.write(logger)
|
||||
finished = runner.episode_num.value > config['total_training_episodes']
|
||||
|
||||
|
||||
if logwriter is not None:
|
||||
if last_episode_num < runner.episode_num:
|
||||
last_episode_num = runner.episode_num
|
||||
agent.value_net.log_named_parameters()
|
||||
agent.policy_net.log_named_parameters()
|
||||
logwriter.write(logger)
|
||||
finished = runner.episode_num > config['total_training_episodes']
|
||||
|
||||
if __name__ == "__main__":
|
||||
torch.multiprocessing.set_sharing_strategy('file_system') # To not hit file descriptor memory limit
|
||||
|
||||
# Setting up the environment
|
||||
rltorch.set_seed(config['seed'])
|
||||
print("Setting up environment...", end = " ")
|
||||
|
@ -104,24 +121,29 @@ if __name__ == "__main__":
|
|||
|
||||
# Setting up the networks
|
||||
device = torch.device("cuda:0" if torch.cuda.is_available() and not config['disable_cuda'] else "cpu")
|
||||
net = rn.Network(Value(state_size, action_size),
|
||||
torch.optim.Adam, config, device = device, name = "DQN")
|
||||
target_net = rn.TargetNetwork(net, device = device)
|
||||
net.model.share_memory()
|
||||
config2 = deepcopy(config)
|
||||
config2['learning_rate'] = 0.01
|
||||
policy_net = rn.ESNetwork(Policy(state_size, action_size),
|
||||
torch.optim.Adam, 500, None, config2, sigma = 0.1, device = device, name = "ES", logger = logger)
|
||||
value_net = rn.Network(Value(state_size, action_size),
|
||||
torch.optim.Adam, config, device = device, name = "DQN", logger = logger)
|
||||
|
||||
target_net = rn.TargetNetwork(value_net, device = device)
|
||||
value_net.model.share_memory()
|
||||
target_net.model.share_memory()
|
||||
|
||||
# Actor takes a net and uses it to produce actions from given states
|
||||
actor = ArgMaxSelector(net, action_size, device = device)
|
||||
actor = StochasticSelector(policy_net, action_size, device = device)
|
||||
# Memory stores experiences for later training
|
||||
memory = M.PrioritizedReplayMemory(capacity = config['memory_size'], alpha = config['prioritized_replay_sampling_priority'])
|
||||
# memory = M.ReplayMemory(capacity = config['memory_size'])
|
||||
|
||||
# Runner performs a certain number of steps in the environment
|
||||
runner = rltorch.mp.EnvironmentRun(env, actor, config, name = "Training", memory = memory, logwriter = logwriter)
|
||||
runner = rltorch.env.EnvironmentRunSync(env, actor, config, name = "Training", memory = memory, logwriter = logwriter)
|
||||
|
||||
# Agent is what performs the training
|
||||
agent = rltorch.agents.DQNAgent(net, memory, config, target_net = target_net, logger = logger)
|
||||
|
||||
# agent = TestAgent(policy_net, value_net, memory, config, target_value_net = target_net, logger = logger)
|
||||
agent = rltorch.agents.QEPAgent(policy_net, value_net, memory, config, target_value_net = target_net, logger = logger)
|
||||
|
||||
print("Training...")
|
||||
|
||||
train(runner, agent, config, logger = logger, logwriter = logwriter)
|
||||
|
@ -132,7 +154,6 @@ if __name__ == "__main__":
|
|||
# python -m torch.utils.bottleneck /path/to/source/script.py [args] is also a good solution...
|
||||
|
||||
print("Training Finished.")
|
||||
runner.terminate() # We don't need the extra process anymore
|
||||
|
||||
print("Evaluating...")
|
||||
rltorch.env.simulateEnvEps(env, actor, config, total_episodes = config['total_evaluation_episodes'], logger = logger, name = "Evaluation")
|
|
@ -48,7 +48,7 @@ config = {}
|
|||
config['seed'] = 901
|
||||
config['environment_name'] = 'Acrobot-v1'
|
||||
config['memory_size'] = 2000
|
||||
config['total_training_episodes'] = 100
|
||||
config['total_training_episodes'] = 500
|
||||
config['total_evaluation_episodes'] = 10
|
||||
config['batch_size'] = 32
|
||||
config['learning_rate'] = 1e-3
|
||||
|
|
|
@ -46,8 +46,8 @@ config = {}
|
|||
config['seed'] = 901
|
||||
config['environment_name'] = 'Acrobot-v1'
|
||||
config['memory_size'] = 2000
|
||||
config['total_training_episodes'] = 5
|
||||
config['total_evaluation_episodes'] = 2
|
||||
config['total_training_episodes'] = 50
|
||||
config['total_evaluation_episodes'] = 5
|
||||
config['batch_size'] = 32
|
||||
config['learning_rate'] = 1e-3
|
||||
config['target_sync_tau'] = 1e-1
|
||||
|
|
14
rltorch/action_selector/IdentitySelector.py
Normal file
14
rltorch/action_selector/IdentitySelector.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from .ArgMaxSelector import ArgMaxSelector
|
||||
import torch
|
||||
class IdentitySelector(ArgMaxSelector):
|
||||
def __init__(self, model, action_size, device = None):
|
||||
super(IdentitySelector, self).__init__(model, action_size, device = device)
|
||||
# random_act is already implemented in ArgMaxSelector
|
||||
def best_act(self, state):
|
||||
with torch.no_grad():
|
||||
if self.device is not None:
|
||||
state = state.to(self.device)
|
||||
action = self.model(state).squeeze(0).item()
|
||||
return action
|
||||
def act(self, state):
|
||||
return self.best_act(state)
|
|
@ -5,7 +5,7 @@ import rltorch
|
|||
from rltorch.action_selector import ArgMaxSelector
|
||||
|
||||
class StochasticSelector(ArgMaxSelector):
|
||||
def __init__(self, model, action_size, memory, device = None):
|
||||
def __init__(self, model, action_size, memory = None, device = None):
|
||||
super(StochasticSelector, self).__init__(model, action_size, device = device)
|
||||
self.model = model
|
||||
self.action_size = action_size
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from .ArgMaxSelector import *
|
||||
from .EpsilonGreedySelector import *
|
||||
from .IdentitySelector import *
|
||||
from .RandomSelector import *
|
||||
from .StochasticSelector import *
|
|
@ -1,5 +1,3 @@
|
|||
# Deprecated since the idea of the idea shouldn't work without having some sort of "mental model" of the environment
|
||||
|
||||
from copy import deepcopy
|
||||
import numpy as np
|
||||
import torch
|
||||
|
|
110
rltorch/agents/QEPAgent.py
Normal file
110
rltorch/agents/QEPAgent.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
from copy import deepcopy
|
||||
import collections
|
||||
import torch
|
||||
from torch.distributions import Categorical
|
||||
import rltorch
|
||||
import rltorch.memory as M
|
||||
|
||||
# Q-Evolutionary Policy Agent
|
||||
# Maximizes the policy with respect to the Q-Value function.
|
||||
# Since function is non-differentiabile, depends on the Evolutionary Strategy algorithm
|
||||
class QEPAgent:
|
||||
def __init__(self, policy_net, value_net, memory, config, target_value_net = None, logger = None):
|
||||
self.policy_net = policy_net
|
||||
assert isinstance(self.policy_net, rltorch.network.ESNetwork)
|
||||
self.policy_net.fitness = self.fitness
|
||||
self.value_net = value_net
|
||||
self.target_value_net = target_value_net
|
||||
self.memory = memory
|
||||
self.config = deepcopy(config)
|
||||
self.logger = logger
|
||||
self.policy_skip = 10
|
||||
|
||||
def fitness(self, policy_net, value_net, state_batch):
|
||||
action_probabilities = policy_net(state_batch)
|
||||
distributions = list(map(Categorical, action_probabilities))
|
||||
actions = torch.tensor([d.sample() for d in distributions])
|
||||
|
||||
with torch.no_grad():
|
||||
state_values = value_net(state_batch)
|
||||
obtained_values = state_values.gather(1, actions.view(len(state_batch), 1)).squeeze(1)
|
||||
|
||||
return -obtained_values.mean().item()
|
||||
|
||||
def learn(self, logger = None):
|
||||
if len(self.memory) < self.config['batch_size']:
|
||||
return
|
||||
|
||||
if (isinstance(self.memory, M.PrioritizedReplayMemory)):
|
||||
weight_importance = self.config['prioritized_replay_weight_importance']
|
||||
# If it's a scheduler then get the next value by calling next, otherwise just use it's value
|
||||
beta = next(weight_importance) if isinstance(weight_importance, collections.Iterable) else weight_importance
|
||||
minibatch = self.memory.sample(self.config['batch_size'], beta = beta)
|
||||
state_batch, action_batch, reward_batch, next_state_batch, not_done_batch, importance_weights, batch_indexes = M.zip_batch(minibatch, priority = True)
|
||||
else:
|
||||
minibatch = self.memory.sample(self.config['batch_size'])
|
||||
state_batch, action_batch, reward_batch, next_state_batch, not_done_batch = M.zip_batch(minibatch)
|
||||
|
||||
# Send to their appropriate devices
|
||||
state_batch = state_batch.to(self.value_net.device)
|
||||
action_batch = action_batch.to(self.value_net.device)
|
||||
reward_batch = reward_batch.to(self.value_net.device)
|
||||
next_state_batch = next_state_batch.to(self.value_net.device)
|
||||
not_done_batch = not_done_batch.to(self.value_net.device)
|
||||
|
||||
state_values = self.value_net(state_batch)
|
||||
obtained_values = state_values.gather(1, action_batch.view(self.config['batch_size'], 1))
|
||||
|
||||
with torch.no_grad():
|
||||
# Use the target net to produce action values for the next state
|
||||
# and the regular net to select the action
|
||||
# That way we decouple the value and action selecting processes (DOUBLE DQN)
|
||||
not_done_size = not_done_batch.sum()
|
||||
next_state_values = torch.zeros_like(state_values, device = self.value_net.device)
|
||||
if self.target_value_net is not None:
|
||||
next_state_values[not_done_batch] = self.target_value_net(next_state_batch[not_done_batch])
|
||||
next_best_action = self.value_net(next_state_batch[not_done_batch]).argmax(1)
|
||||
else:
|
||||
next_state_values[not_done_batch] = self.value_net(next_state_batch[not_done_batch])
|
||||
next_best_action = next_state_values[not_done_batch].argmax(1)
|
||||
|
||||
best_next_state_value = torch.zeros(self.config['batch_size'], device = self.value_net.device)
|
||||
best_next_state_value[not_done_batch] = next_state_values[not_done_batch].gather(1, next_best_action.view((not_done_size, 1))).squeeze(1)
|
||||
|
||||
expected_values = (reward_batch + (self.config['discount_rate'] * best_next_state_value)).unsqueeze(1)
|
||||
|
||||
if (isinstance(self.memory, M.PrioritizedReplayMemory)):
|
||||
value_loss = (torch.as_tensor(importance_weights, device = self.value_net.device) * ((obtained_values - expected_values)**2).squeeze(1)).mean()
|
||||
else:
|
||||
value_loss = F.mse_loss(obtained_values, expected_values)
|
||||
|
||||
if self.logger is not None:
|
||||
self.logger.append("Loss/Value", value_loss.item())
|
||||
|
||||
self.value_net.zero_grad()
|
||||
value_loss.backward()
|
||||
self.value_net.clamp_gradients()
|
||||
self.value_net.step()
|
||||
|
||||
if self.target_value_net is not None:
|
||||
if 'target_sync_tau' in self.config:
|
||||
self.target_value_net.partial_sync(self.config['target_sync_tau'])
|
||||
else:
|
||||
self.target_value_net.sync()
|
||||
|
||||
if (isinstance(self.memory, M.PrioritizedReplayMemory)):
|
||||
td_error = (obtained_values - expected_values).detach().abs()
|
||||
self.memory.update_priorities(batch_indexes, td_error)
|
||||
|
||||
## Policy Training
|
||||
if self.policy_skip > 0:
|
||||
self.policy_skip -= 1
|
||||
return
|
||||
self.policy_skip = 10
|
||||
if self.target_value_net is not None:
|
||||
self.policy_net.calc_gradients(self.target_value_net, state_batch)
|
||||
else:
|
||||
self.policy_net.calc_gradients(self.value_net, state_batch)
|
||||
self.policy_net.clamp_gradients()
|
||||
self.policy_net.step()
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
from .A2CSingleAgent import *
|
||||
from .DQNAgent import *
|
||||
from .PPOAgent import *
|
||||
from .QEPAgent import *
|
||||
from .REINFORCEAgent import *
|
66
rltorch/network/ESNetwork.py
Normal file
66
rltorch/network/ESNetwork.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
import numpy as np
|
||||
import torch
|
||||
from .Network import Network
|
||||
from copy import deepcopy
|
||||
|
||||
class ESNetwork(Network):
|
||||
"""
|
||||
Network that functions from the paper Evolutionary Strategies (https://arxiv.org/abs/1703.03864)
|
||||
fitness_fun := model, *args -> fitness_value (float)
|
||||
We wish to find a model that maximizes the fitness function
|
||||
"""
|
||||
def __init__(self, model, optimizer, population_size, fitness_fn, config, sigma = 0.05, device = None, logger = None, name = ""):
|
||||
super(ESNetwork, self).__init__(model, optimizer, config, device, logger, name)
|
||||
self.population_size = population_size
|
||||
self.fitness = fitness_fn
|
||||
self.sigma = sigma
|
||||
|
||||
# We're not going to be calculating gradients in the traditional way
|
||||
# So there's no need to waste computation time keeping track
|
||||
def __call__(self, *args):
|
||||
with torch.no_grad():
|
||||
result = self.model(*args)
|
||||
return result
|
||||
|
||||
|
||||
def _generate_noise_dicts(self):
|
||||
model_dict = self.model.state_dict()
|
||||
white_noise_dict = {}
|
||||
noise_dict = {}
|
||||
for key in model_dict.keys():
|
||||
white_noise_dict[key] = torch.randn(self.population_size, *model_dict[key].shape)
|
||||
noise_dict[key] = self.sigma * white_noise_dict[key]
|
||||
return white_noise_dict, noise_dict
|
||||
|
||||
def _generate_candidate_solutions(self, noise_dict):
|
||||
model_dict = self.model.state_dict()
|
||||
candidate_solutions = []
|
||||
for i in range(self.population_size):
|
||||
candidate_statedict = {}
|
||||
for key in model_dict.keys():
|
||||
candidate_statedict[key] = model_dict[key] + noise_dict[key][i]
|
||||
candidate = deepcopy(self.model)
|
||||
candidate.load_state_dict(candidate_statedict)
|
||||
candidate_solutions.append(candidate)
|
||||
return candidate_solutions
|
||||
|
||||
def calc_gradients(self, *args):
|
||||
## Generate Noise
|
||||
white_noise_dict, noise_dict = self._generate_noise_dicts()
|
||||
|
||||
## Generate candidate solutions
|
||||
candidate_solutions = self._generate_candidate_solutions(noise_dict)
|
||||
|
||||
## Calculate fitness then mean shift, scale
|
||||
fitness_values = torch.tensor([self.fitness(x, *args) for x in candidate_solutions])
|
||||
if self.logger is not None:
|
||||
self.logger.append(self.name + "/" + "fitness_value", fitness_values.mean().item())
|
||||
fitness_values = (fitness_values - fitness_values.mean()) / (fitness_values.std() + np.finfo('float').eps)
|
||||
|
||||
## Insert adjustments into gradients slot
|
||||
self.zero_grad()
|
||||
for name, param in self.model.named_parameters():
|
||||
if param.requires_grad:
|
||||
noise_dim_n = len(white_noise_dict[name].shape)
|
||||
dim = np.repeat(1, noise_dim_n - 1).tolist() if noise_dim_n > 0 else []
|
||||
param.grad = (white_noise_dict[name] * fitness_values.float().reshape(self.population_size, *dim)).mean(0) / self.sigma
|
|
@ -1,3 +1,4 @@
|
|||
from .ESNetwork import *
|
||||
from .Network import *
|
||||
from .NoisyLinear import *
|
||||
from .TargetNetwork import *
|
Loading…
Reference in a new issue