Add random initial action in ddpg (#13)

* Add random initial actions in ddpg

* Add reacher-v2 example of ddpg
This commit is contained in:
Jinwoo Park (Curt)
2019-02-18 08:57:36 +09:00
committed by GitHub
parent ecb42d30d2
commit d2b670015c
9 changed files with 272 additions and 40 deletions
@@ -36,18 +36,17 @@ class PrioritizedReplayBuffer(ReplayBuffer):
"""
def __init__(
self, buffer_size: int, batch_size: int, demo: list = None, alpha: float = 0.6
self, buffer_size: int, batch_size: int, alpha: float = 0.6
):
"""Initialization.
Args:
buffer_size (int): size of replay buffer for experience
batch_size (int): size of a batched sampled from replay buffer for training
demo (list): demonstration
alpha (float): alpha parameter for prioritized replay buffer
"""
super(PrioritizedReplayBuffer, self).__init__(buffer_size, batch_size, demo)
super(PrioritizedReplayBuffer, self).__init__(buffer_size, batch_size)
assert alpha >= 0
self.buffer_size = buffer_size
self.alpha = alpha
@@ -62,13 +61,6 @@ class PrioritizedReplayBuffer(ReplayBuffer):
self.min_tree = MinSegmentTree(tree_capacity)
self.init_priority = 1.0
# for init priority of demo
if demo:
for _ in range(len(demo)):
self.sum_tree[self.tree_idx] = self.init_priority ** self.alpha
self.min_tree[self.tree_idx] = self.init_priority ** self.alpha
self.tree_idx += 1
def add(
self,
state: np.ndarray,
@@ -22,7 +22,7 @@ class ReplayBuffer:
"""
def __init__(self, buffer_size: int, batch_size: int, demo: list = None):
def __init__(self, buffer_size: int, batch_size: int):
"""Initialize a ReplayBuffer object.
Args:
@@ -31,7 +31,7 @@ class ReplayBuffer:
demo (list) : demonstration list
"""
self.buffer = list() if not demo else demo
self.buffer: list = list()
self.buffer_size = buffer_size
self.batch_size = batch_size
self.idx = 0
@@ -55,13 +55,14 @@ class ReplayBuffer:
def extend(self, transitions: list):
"""Add experiences to memory."""
self.buffer.extend(transitions)
for transition in transitions:
self.add(*transition)
def sample(
self
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
"""Randomly sample a batch of experiences from memory."""
idxs = np.random.randint(0, len(self.buffer), size=self.batch_size)
idxs = np.random.choice(len(self.buffer), size=self.batch_size, replace=False)
states, actions, rewards, next_states, dones = [], [], [], [], []
+30 -3
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
"""Noise classes for baselines."""
"""Noise classes for algorithms."""
import copy
import random
@@ -7,6 +7,31 @@ import random
import numpy as np
class GaussianNoise:
"""Gaussian Noise.
Taken from https://github.com/vitchyr/rlkit
"""
def __init__(
self,
min_sigma: float = 1.0,
max_sigma: float = 1.0,
decay_period: int = 1000000,
):
"""Initialization."""
self.max_sigma = max_sigma
self.min_sigma = min_sigma
self.decay_period = decay_period
def sample(self, action_size: int, t: int = 0) -> float:
"""Get an action with gaussian noise."""
sigma = self.max_sigma - (self.max_sigma - self.min_sigma) * min(
1.0, t / self.decay_period
)
return np.random.normal(0, sigma, size=action_size)
class OUNoise:
"""Ornstein-Uhlenbeck process.
@@ -15,7 +40,9 @@ class OUNoise:
ddpg-pendulum/ddpg_agent.py
"""
def __init__(self, size, mu=0.0, theta=0.15, sigma=0.2):
def __init__(
self, size: int, mu: float = 0.0, theta: float = 0.15, sigma: float = 0.2
):
"""Initialize parameters and noise process."""
self.state = np.float64(0.0)
self.mu = mu * np.ones(size)
@@ -27,7 +54,7 @@ class OUNoise:
"""Reset the internal state (= noise) to mean (mu)."""
self.state = copy.copy(self.mu)
def sample(self):
def sample(self) -> float:
"""Update internal state and return it as a noise sample."""
x = self.state
dx = self.theta * (self.mu - x) + self.sigma * np.array(
+40 -12
View File
@@ -38,6 +38,8 @@ class Agent(AbstractAgent):
actor_optimizer (Optimizer): optimizer for training actor
critic_optimizer (Optimizer): optimizer for training critic
curr_state (np.ndarray): temporary storage of the current state
total_step (int): total step numbers
episode_step (int): step number of the current episode
"""
@@ -68,20 +70,30 @@ class Agent(AbstractAgent):
self.hyper_params = hyper_params
self.curr_state = np.zeros((1,))
self.noise = noise
self.total_step = 0
self.episode_step = 0
# load the optimizer and model parameters
if args.load_from is not None and os.path.exists(args.load_from):
self.load_params(args.load_from)
# replay memory
self.memory = ReplayBuffer(
hyper_params["BUFFER_SIZE"], hyper_params["BATCH_SIZE"]
)
if not self.args.test:
# replay memory
self.memory = ReplayBuffer(
hyper_params["BUFFER_SIZE"], hyper_params["BATCH_SIZE"]
)
def select_action(self, state: np.ndarray) -> torch.Tensor:
def select_action(self, state: np.ndarray) -> np.ndarray:
"""Select an action from the input space."""
self.curr_state = state
# if initial random action should be conducted
if (
self.total_step < self.hyper_params["INITIAL_RANDOM_ACTION"]
and not self.args.test
):
return self.env.action_space.sample()
state = torch.FloatTensor(state).to(device)
selected_action = self.actor(state)
@@ -89,14 +101,21 @@ class Agent(AbstractAgent):
selected_action += torch.FloatTensor(self.noise.sample()).to(device)
selected_action = torch.clamp(selected_action, -1.0, 1.0)
return selected_action
return selected_action.detach().cpu().numpy()
def step(self, action: torch.Tensor) -> Tuple[np.ndarray, np.float64, bool]:
def step(self, action: np.ndarray) -> Tuple[np.ndarray, np.float64, bool]:
"""Take an action and return the response of the env."""
action = action.detach().cpu().numpy()
self.total_step += 1
self.episode_step += 1
next_state, reward, done, _ = self.env.step(action)
self.memory.add(self.curr_state, action, reward, next_state, done)
if not self.args.test:
# if the last state is not a terminal state, store done as false
done_bool = (
False if self.episode_step == self.args.max_episode_steps else done
)
self.memory.add(self.curr_state, action, reward, next_state, done_bool)
return next_state, reward, done
@@ -171,9 +190,17 @@ class Agent(AbstractAgent):
total_loss = loss.sum()
print(
"[INFO] episode %d total score: %d, total loss: %f\n"
"actor_loss: %.3f critic_loss: %.3f\n"
% (i, score, total_loss, loss[0], loss[1]) # actor loss # critic loss
"[INFO] episode %d, episode step: %d, total step: %d, total score: %d\n"
"total loss: %f actor_loss: %.3f critic_loss: %.3f\n"
% (
i,
self.episode_step,
self.total_step,
score,
total_loss,
loss[0],
loss[1],
) # actor loss # critic loss
)
if self.args.log:
@@ -198,6 +225,7 @@ class Agent(AbstractAgent):
state = self.env.reset()
done = False
score = 0
self.episode_step = 0
loss_episode = list()
while not done:
+11 -10
View File
@@ -75,24 +75,24 @@ class Agent(AbstractAgent):
self.load_params(args.load_from)
# replay memory
self.beta = self.hyper_params["PER_BETA"]
self.memory = PrioritizedReplayBuffer(
self.hyper_params["BUFFER_SIZE"],
self.hyper_params["BATCH_SIZE"],
alpha=self.hyper_params["PER_ALPHA"],
)
if not self.args.test:
self.beta = self.hyper_params["PER_BETA"]
self.memory = PrioritizedReplayBuffer(
self.hyper_params["BUFFER_SIZE"],
self.hyper_params["BATCH_SIZE"],
alpha=self.hyper_params["PER_ALPHA"],
)
def select_action(self, state: np.ndarray) -> torch.Tensor:
"""Select an action from the input space."""
self.curr_state = state
state = torch.FloatTensor(state).to(device)
selected_action = self.actor(state)
if not self.args.test:
selected_action = self.actor(state)
selected_action += torch.FloatTensor(self.noise.sample()).to(device)
selected_action = torch.clamp(selected_action, -1.0, 1.0)
selected_action = torch.clamp(selected_action, -1.0, 1.0)
return selected_action
@@ -101,7 +101,8 @@ class Agent(AbstractAgent):
action = action.detach().cpu().numpy()
next_state, reward, done, _ = self.env.step(action)
self.memory.add(self.curr_state, action, reward, next_state, done)
if not self.args.test:
self.memory.add(self.curr_state, action, reward, next_state, done)
return next_state, reward, done
@@ -28,6 +28,7 @@ hyper_params = {
"OU_NOISE_THETA": 0.0,
"OU_NOISE_SIGMA": 0.0,
"WEIGHT_DECAY": 1e-6,
"INITIAL_RANDOM_ACTION": 10000,
}
+109
View File
@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
"""Run module for DDPG on Reacher-v2.
- Author: Curt Park
- Contact: curt.park@medipixel.io
"""
import argparse
import gym
import torch
import torch.optim as optim
from algorithms.common.networks.mlp import MLP
from algorithms.common.noise import OUNoise
from algorithms.ddpg.agent import Agent
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# hyper parameters
hyper_params = {
"GAMMA": 0.99,
"TAU": 1e-3,
"BUFFER_SIZE": int(1e5),
"BATCH_SIZE": 128,
"LR_ACTOR": 1e-3,
"LR_CRITIC": 1e-3,
"OU_NOISE_THETA": 0.0,
"OU_NOISE_SIGMA": 0.0,
"WEIGHT_DECAY": 1e-6,
"INITIAL_RANDOM_ACTION": 10000,
}
def run(env: gym.Env, args: argparse.Namespace, state_dim: int, action_dim: int):
"""Run training or test.
Args:
env (gym.Env): openAI Gym environment with continuous action space
args (argparse.Namespace): arguments including training settings
state_dim (int): dimension of states
action_dim (int): dimension of actions
"""
hidden_sizes_actor = [256, 256]
hidden_sizes_critic = [256, 256]
# create actor
actor = MLP(
input_size=state_dim,
output_size=action_dim,
hidden_sizes=hidden_sizes_actor,
output_activation=torch.tanh,
).to(device)
actor_target = MLP(
input_size=state_dim,
output_size=action_dim,
hidden_sizes=hidden_sizes_actor,
output_activation=torch.tanh,
).to(device)
actor_target.load_state_dict(actor.state_dict())
# create critic
critic = MLP(
input_size=state_dim + action_dim,
output_size=1,
hidden_sizes=hidden_sizes_critic,
).to(device)
critic_target = MLP(
input_size=state_dim + action_dim,
output_size=1,
hidden_sizes=hidden_sizes_critic,
).to(device)
critic_target.load_state_dict(critic.state_dict())
# create optimizer
actor_optim = optim.Adam(
actor.parameters(),
lr=hyper_params["LR_ACTOR"],
weight_decay=hyper_params["WEIGHT_DECAY"],
)
critic_optim = optim.Adam(
critic.parameters(),
lr=hyper_params["LR_CRITIC"],
weight_decay=hyper_params["WEIGHT_DECAY"],
)
# noise
noise = OUNoise(
action_dim,
theta=hyper_params["OU_NOISE_THETA"],
sigma=hyper_params["OU_NOISE_SIGMA"],
)
# make tuples to create an agent
models = (actor, actor_target, critic, critic_target)
optims = (actor_optim, critic_optim)
# create an agent
agent = Agent(env, args, hyper_params, models, optims, noise)
# run
if args.test:
agent.test()
else:
agent.train()
+1 -1
View File
@@ -1,5 +1,5 @@
gym
numpy
torch==1.0.0
torch==0.4.1
typing
wandb
+73
View File
@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
"""Train or test algorithms on Reacher-v2 of Mujoco.
- Author: Kh Kim
- Contact: kh.kim@medipixel.io
"""
import argparse
import importlib
import gym
import algorithms.common.helper_functions as common_utils
# configurations
parser = argparse.ArgumentParser(description="Pytorch RL algorithms")
parser.add_argument(
"--seed", type=int, default=777, help="random seed for reproducibility"
)
parser.add_argument("--algo", type=str, default="ddpg", help="choose an algorithm")
parser.add_argument(
"--test", dest="test", action="store_true", help="test mode (no training)"
)
parser.add_argument(
"--load-from", type=str, help="load the saved model and optimizer at the beginning"
)
parser.add_argument(
"--off-render", dest="render", action="store_false", help="turn off rendering"
)
parser.add_argument(
"--render-after",
type=int,
default=0,
help="start rendering after the input number of episode",
)
parser.add_argument("--log", dest="log", action="store_true", help="turn on logging")
parser.add_argument("--save-period", type=int, default=200, help="save model period")
parser.add_argument("--episode-num", type=int, default=20000, help="total episode num")
parser.add_argument(
"--max-episode-steps", type=int, default=-1, help="max episode step"
)
parser.add_argument(
"--demo-path",
type=str,
default="data/lunarlander_continuous_demo.pkl",
help="demonstration path",
)
parser.set_defaults(test=False)
parser.set_defaults(load_from=None)
parser.set_defaults(render=True)
parser.set_defaults(log=False)
args = parser.parse_args()
def main():
"""Main."""
# env initialization
env = gym.make("Reacher-v2")
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.shape[0]
# set a random seed
common_utils.set_random_seed(args.seed, env)
# run
module_path = "examples.reacher-v2." + args.algo
example = importlib.import_module(module_path)
example.run(env, args, state_dim, action_dim)
if __name__ == "__main__":
main()