update algos and exploration (need to finish cleaning)

This commit is contained in:
Brian Delhaisse
2019-04-01 00:48:27 +02:00
parent 2b1b255a67
commit 45a6be9964
24 changed files with 2026 additions and 24 deletions
+26 -2
View File
@@ -1,6 +1,17 @@
import sys
# logging
import logging
# create logger
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(name)s (%(levelname)s): %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
# import simulators
from . import simulators
@@ -10,6 +21,9 @@ from . import robots
# import worlds
from . import worlds
# import physics randomizer
from . import physics
# import states
from . import states
@@ -32,21 +46,31 @@ from . import approximators
from . import policies
# import values
from . import values
# import actor-critics
from . import actorcritics
# import dynamical models
from . import dynamics
# import tools (interfaces and bridges)
# from . import tools # uncommenting this will oblige the user to install a bunch of libraries which are not straightforward to install...
from . import tools
# import recorders
from . import recorders
# import tasks
from . import tasks
# import metrics
from . import metrics
# import losses
from . import losses
# import optimizers
# from . import optimizers
from . import optimizers
# import algos
from . import algos
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env python
"""Provide the Evaluator class used in the second step of RL algorithms
The evaluator assesses the quality of the actions/trajectories performed by the policy using the given estimators.
It is the step performed after the exploration phase, and before the update step.
"""
from pyrobolearn.estimators import Estimator
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class Evaluator(object):
r"""Evaluator
(Model-free) reinforcement learning algorithms requires 3 steps:
1. Explore: Explore and collect samples in the environment using the policy. The samples are stored in the
given memory/storage unit.
2. Evaluate: Assess the quality of the actions/trajectories using the estimators.
3. Update: Update the policy (and/or value function) parameters based on the loss
This class focuses on the second step of RL algorithms.
"""
def __init__(self, estimator):
"""
Initialize the Evaluation phase.
Args:
estimator (Estimator): estimator used to evaluate the actions performed by the policy.
"""
self.estimator = estimator
##############
# Properties #
##############
@property
def estimator(self):
"""Return the estimator used to evaluate the policy."""
return self._estimator
@estimator.setter
def estimator(self, estimator):
"""Set the estimator."""
if not isinstance(estimator, Estimator):
raise TypeError("Expecting estimator to be an instance of `Estimator`, instead got: "
"{}".format(type(estimator)))
self._estimator = estimator
@property
def storage(self):
"""Return the storage unit."""
return self.estimator.storage
###########
# Methods #
###########
def evaluate(self): # , storage):
"""
Evaluate the actions.
"""
self.estimator.evaluate(self.storage)
#############
# Operators #
#############
def __repr__(self):
"""Return the representation string."""
return self.__class__.__name__
def __str__(self):
"""Return the class string."""
return self.__class__.__name__
def __call__(self):
"""Evaluate the estimator on the storage."""
self.evaluate()
+184
View File
@@ -0,0 +1,184 @@
#!/usr/bin/env python
"""Provide the Explorer class used in the first step of RL algorithms
It consists to explore and collect samples in the environment using the policy. The samples are stored in the
given memory/storage unit which will be used to evaluate the policy, and then update its parameters.
"""
import inspect
from pyrobolearn.tasks import RLTask
from pyrobolearn.envs import Env
from pyrobolearn.policies import Policy
from pyrobolearn.exploration import Exploration
from pyrobolearn.storages import RolloutStorage
from pyrobolearn import logger
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class Explorer(object):
r"""Explorer
(Model-free) reinforcement learning algorithms requires 3 steps:
1. Explore: Explore and collect samples in the environment using the policy. The samples are stored in the
given memory/storage unit.
2. Evaluate: Assess the quality of the actions/trajectories using the estimators.
3. Update: Update the policy (and/or value function) parameters based on the loss
This class focuses on the first step of RL algorithms. It accepts the environment, and the exploration strategy
which wraps the policy.
"""
def __init__(self, task, explorer, storage, num_workers=1):
"""
Initialize the exploration phase.
Args:
task (Task, Env, tuple of Env and Policy): RL task or environment.
explorer (Exploration): policies.
storage (RolloutStorage): Rollout storage unit (=replay memory). It will save the rollouts in the storage
while exploring.
num_workers (int): number of processes / workers to run in parallel.
"""
self.task = task
self.explorer = explorer
self.storage = storage
self.num_workers = int(num_workers)
##############
# Properties #
##############
@property
def task(self):
"""Return the RL task."""
return self._task
@task.setter
def task(self, task):
"""Set the RL task."""
if isinstance(task, (tuple, list)):
env, policy = None, None
for t in task:
if isinstance(t, Env):
env = t
if isinstance(t, Policy): # TODO if multiple policies
policy = t
if env is None or policy is None:
raise ValueError("Expecting the task to be an instance of `RLTask` or a list/tuple of an environment "
"and policy.")
task = RLTask(env, policy)
if not isinstance(task, RLTask):
raise TypeError("Expecting the task to be an instance of `RLTask`, instead got: {}".format(type(task)))
self._task = task
@property
def policy(self):
"""Return the policy."""
return self.task.policy
@property
def env(self):
"""Return the environment."""
return self.task.environment
@property
def explorer(self):
"""Return the exploration strategy."""
return self._explorer
@explorer.setter
def explorer(self, explorer):
"""Set the exploration strategy."""
if inspect.isclass(explorer): # if it is a class
explorer = explorer(self.policy)
if not isinstance(explorer, Exploration):
raise TypeError("Expecting explorer to be an instance of Exploration")
self._explorer = explorer
@property
def storage(self):
"""Return the storage unit."""
return self._storage
@storage.setter
def storage(self, storage):
"""Set the storage unit."""
if not isinstance(storage, RolloutStorage):
raise TypeError("Expecting the storage to be an instance of `RolloutStorage`, instead got: "
"{}".format(type(storage)))
self._storage = storage
###########
# Methods #
###########
def explore(self, num_steps, deterministic=False):
"""
Explore the environment.
Args:
num_steps (int): number of steps
deterministic (bool): if deterministic is True, then it does not explore in the environment.
Returns:
Rollout: memory storage
"""
# reset environment
obs = self.env.reset()
print("\nExplorer - initial state: {}".format(obs))
# reset explorer
self.explorer.reset()
# run RL task for T steps
for step in range(num_steps):
# get action and corresponding distribution from policy
act, dist = self.explorer.act(obs, deterministic)
# perform one step in the environment
next_obs, reward, done, info = self.env.step(act)
# insert in storage
# print("\nExplorer:")
# print("1. Observation data: {}".format(obs.merged_torch_data))
# print("2. Action data: {}".format(act))
# print("3. Next observation data: {}".format(next_obs.merged_torch_data))
# print("4. Reward: {}".format(reward))
# print("5. \\pi(.|s): {}".format(dist))
# print("6. log \\pi(a|s): {}".format(dist.log_prob(act)))
self.storage.insert(obs.merged_torch_data, act.merged_torch_data, next_obs.data, reward, dist)
obs = next_obs
if done:
break
# # clear explorer
# self.explorer.clear()
return self.storage
#############
# Operators #
#############
def __repr__(self):
"""Return a representation string about the class."""
return self.__class__.__name__
def __str__(self):
"""Return a string describing the class."""
return self.__class__.__name__
def __call__(self, num_steps):
"""Explore in the environment with the specified number of time steps."""
self.explore(num_steps)
+401
View File
@@ -0,0 +1,401 @@
#!/usr/bin/env python
"""Provide the basic components for RL algorithms
Dependencies:
- `pyrobolearn.tasks`
- `pyrobolearn.approximators` (e.g. `pyrobolean.policies`, `pyrobolearn.values`, `pyrobolearn.models`,...)
- `pyrobolearn.envs`
"""
import numpy as np
# from pathos.multiprocessing import Pool
from pyrobolearn.algos.explorer import Explorer
from pyrobolearn.algos.evaluator import Evaluator
from pyrobolearn.algos.updater import Updater
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class RLAlgo(object): # Algo):
r"""Reinforcement Learning Algorithm.
The RL algorithm takes as input a task (i.e. environment + policy), an optimizer, some hyperparameters (such
as the number of episodes, rollouts, and timesteps). Optional arguments include a learnable dynamic/transition
model, a value function approximator, a memory, and an exploration strategy.
RL problem
----------
The RL problem can be depicted as:
s_t \pi(a_t|s_t;\theta) a_t
---> Agent's policy ----
| |
| |
| |
s_{t+1}, ------ Environment <---
r_t p(s_{t+1}|s_t,a_t)
where :math:`s_t` is the state/observation, :math:`a_t` is the action, :math:`\pi_{\theta}` is the policy
parametrized by the parameters :math:`\theta`, :math:`r_t` is the reward returend by the environment,
and :math:`p(s_{t+1}|s_t,a_t)` is the dynamic model of the environment.
Note that normally there is a clear distinction between the observation and the state. Also, there is a difference
between the notation used in optimal control and model-based RL.
Markov Decision Process (MDP)
----------------------------
A RL problem can be formally formulated as a Markov Decision Process (MDP) which is given as a 6-tuple
:math:`\{\rho_0, S, A, R, P, \gamma\}`, where :math:`\rho_0` is the initial state distribution, :math:`S` is
the set of of all valide states, :math:`A` is the set of all valid actions,
:math:`R: S \times A \times S \rightarrow \mathcal{R}` is the reward function with
:math:`r_t = R(s_t, a_t, s_{t+1})`, :math:`P: S \times A \rightarrow P(S)` is the state transition probability
function which describes the dynamic model of the environment, with :math:`p(s_{t+1} | s_t, a_t)` being the
probability, and :math:`\gamma \in [0,1]` is the discount factor used to calculate the return.
Goal of RL
----------
The goal of RL is to maximize the expected return given by:
.. math::
\max_{\theta} J(\theta) &= \max_{\theta} \int_{\mathcal{T}} p_{\theta}(\tau) R(\tau) d\tau \\
&= \mathcal{E}_{\pi_\theta}[]
where :math:`\mathcal{T}` represents the set of all possible trajectories covered by the policy :math:`\pi_\theta`,
:math:`p(\tau)` is the probability distribution over the trajectories :math:`\tau = (s_0, a_0, s_1, a_1, ...)`,
and :math:`R(\tau)` is the total return associated with the trajectory.
.. math::
p(\tau) &= p(s_0, a_0, ..., s_{T-1}, a_{T-1}, s_T) \\
&= p(s_0) \prod_{t=0}^{T-1} p(s_{t+1} | s_t, a_t) \pi_\theta(a_t | s_t)
where we used the product/chain rule of probability and the Markov property. :math:`p(s_{t+1}|s_t,a_t)`
represents the dynamics of the environment over which we have no control, and
:math:`\pi_{\theta}(a_t | s_t) = \pi(a_t | s_t; \theta)` is the policy that is parametrized by :math:`\theta`
on which we have control over it.
Taxonomy
--------
RL problems can be classified into different categories. We follow the taxonomy described in [3].
* Model-based vs Model-free
* Model-based: it knows about the dynamics of the environment (i.e. the transition function :math:`P`), i.e.
it knows :math:`p(s_{t+1} | s_t, a_t)`. This transition dynamics probability could have been computed
using mathematical equations or learned from data.
* Model-free: The dynamic model is not known. Model-free policy search consists of 3 main steps: explore,
evaluate, and update.
* Value-based <-- Actor-Critic --> Policy-based
* Value-based: Value-based means that it determines how good it is to be in a certain state. The policy is
then inferred from this knowledge.
* Policy-based: Policy-based (aka Policy search) directly optimizes the agent's policy
:math:`\pi_\theta(a | s)` which maps the state to action. Policy-based methods can further be subdivided
into 3 categories: Policy Gradient (PG) vs Expectation-Maximization (EM) vs Information Theory (Inf.Th.)
* Actor-critic: Actor-Critic combines both previous approaches.
* On-policy vs Off-policy
* On-policy: the collected data and the data on which we train the policy is the one collected by the same
policy.
* Off-policy: the policy that is being optimized and the policy that explores in the environment are different.
The former is called the target policy while the latter is the behavior policy which collects the data.
Off-policy tends to be a little bit slower than on-policy methods.
* Step-based vs Episode-based
* Step-based: the exploration is performed in the action space
* Episode-based: the exploration is performed in the parameter space
Pseudo-algo
-----------
The basic steps of RL algorithms are:
1. explore in the environment with the current policy and generate samples
1.5 if model-based, learn a model of the environment
2. evaluate the policy performance
3. Update the policy
Open Problems
-------------
- hierarchical
- exploration
- algorithms
- sample efficiency
- simulation vs reality
References (tutorials):
[1] "Reinforcement Learning: An Introduction", Sutton and Barto, 2018
[2] "Reinforcement Learning", Silver, UC London, 2015
[3] "A Survey on Policy Search for Robotics", Deisenroth et al., 2013
[4] "CS294: Deep Reinforcement Learning", Levine et al., UC Berkeley, 2017
[5] OpenAI - Spinning Up: https://spinningup.openai.com/
"""
# def __init__(self, rlTask, exploration_strategy, storage, hyperparameters, optimizer=None, dynamic_model=None,
# num_workers=1):
def __init__(self, explorer, evaluator, updater, hyperparameters={}, dynamic_model=None): # , num_workers=1):
"""
Initialize the reinforcement learning algorithm.
Args:
explorer (Explorer): explorer that specifies how to explore in the environment
evaluator (Evaluator): evaluate the actions
updater (Updater): update the approximators (rl, value-functions,...)
hyperparameters (dict): dictionary containing the hyperparameters
dynamic_model (None): dynamical model
num_workers (int): number of workers (useful when parallelizing the code)
"""
super(RLAlgo, self).__init__()
# TODO: think about multiple agents/rewards
self.explorer = explorer
self.evaluator = evaluator
self.updater = updater
self.env = self.environment
self.dynamic_model = dynamic_model
# self.episodes = hyperparameters.get('episodes', 1) # nb of episodes
# self.rollouts = hyperparameters.get('rollouts', 1) # nb of rollouts per episode
# self.timesteps = hyperparameters.get('timesteps', 1000) # nb of timesteps per rollout
# TODO: define the number of iterations
self.best_reward = -np.infty
self.best_parameters = None
##############
# Properties #
##############
@property
def explorer(self):
"""Return the explorer instance."""
return self._explorer
@explorer.setter
def explorer(self, explorer):
"""Set the exploration phase."""
if not isinstance(explorer, Explorer):
raise TypeError("Expecting the explorer to be an instance of `Explorer`, instead got: "
"{}".format(type(explorer)))
self._explorer = explorer
@property
def evaluator(self):
"""Return the evaluator used to evaluate the actions taken by the policy."""
return self._evaluator
@evaluator.setter
def evaluator(self, evaluator):
"""Set the evaluator for the 2nd phase of RL algorithms."""
if not isinstance(evaluator, Evaluator):
raise TypeError("Expecting the evaluator to be an instance of `Evaluator`, instead got: "
"{}".format(type(evaluator)))
self._evaluator = evaluator
@property
def updater(self):
"""Return the updater instance that is used to update the various approximator parameters."""
return self._updater
@updater.setter
def updater(self, updater):
"""Set the updater for the 3rd phase of RL algorithms."""
if not isinstance(updater, Updater):
raise TypeError("Expecting the updater to be an instance of `Updater`, instead got: "
"{}".format(type(updater)))
self._updater = updater
@property
def task(self):
"""Return the RL task."""
return self.explorer.task
@property
def environment(self):
return self.task.environment
@property
def policy(self):
"""Return the policy."""
return self.task.policy
@property
def exploration_strategy(self):
"""Return the exploration strategy."""
return self.explorer.explorer
@property
def estimator(self):
"""Return the estimator."""
return self.evaluator.estimator
@property
def storage(self):
"""Return the storage unit."""
return self.explorer.storage
@property
def optimizers(self):
"""Return the optimizers."""
return self.updater.optimizers
@property
def losses(self):
"""Return the losses."""
return self.updater.losses
###########
# Methods #
###########
def init(self, explorer, evaluator, updater):
"""Initialize the RL algo."""
self.explorer = explorer
self.evaluator = evaluator
self.updater = updater
def rollout(self, deterministic=True):
"""
Run the policy in the environment.
"""
# Reset the environment
state = self.env.reset()
if deterministic:
self.explorer.disable()
# Run policy in environment for T time steps
total_reward = 0
for _ in range(self.timesteps):
# run policy given the state
prev_state = state
action = self.policy.act(state, self.exploration)
# run one step in the environment
state, reward, done, info = self.env.step(action)
total_reward += reward
# save (s,a,s',r) in storage
self.storage.add(prev_state, action, state, reward)
# if episode is done
if done:
break
return total_reward
def train(self, num_steps, num_rollouts=1, num_episodes=1, verbose=False, seed=None):
"""
Train the policy in the provided environment.
Args:
num_steps (int): number of step per rollout
num_rollouts (int): number of rollouts per episode (default: 1)
num_episodes (int): number of episodes (default: 1)
verbose(bool): if True, print details about the optimization process
seed (int): random seed
Returns:
dict: history
"""
history = {}
# set the policy in training mode
self.policy.train(mode=True)
# for each episode
for ep in range(num_episodes):
# for each rollout
for rollout in range(num_rollouts):
# TODO: consider to learn the dynamic model if provided
# Explore, evaluate, and update
self.explorer(num_steps)
self.evaluator()
loss = self.updater()
# add the loss in the history
history.setdefault('loss', []).append(loss)
# set the policy in test mode
self.policy.train(mode=False)
return history
def test(self, num_steps, dt=0., use_terminating_condition=False, render=True): # , storage):
"""
Test the policy in the environment.
Args:
num_steps (int): number of steps
dt (float): time step
use_terminating_condition (bool): if we should use the terminating condition to end preemptively the
task if the policy succeeded or failed this last one
render (bool): render the test phase (default: True)
Returns:
list: list of results for each policy at each time step
"""
results = self.task.run(self, num_steps=num_steps, dt=dt, use_terminating_condition=use_terminating_condition,
render=render)
return results
#############
# Operators #
#############
def __repr__(self):
return self.__class__.__name__
def __str__(self):
return self.__class__.__name__
class GradientRLAlgo(RLAlgo):
r"""Gradient based reinforcement learning algorithm.
These methods optimizes directly the policy by computing the gradient of the expected reward.
.. math:: g = \mathbb{E}[ \sum_{t=0}^\infty \psi_t \nabla_\theta \log \pi_\theta(a_t | s_t) ]
where:
* :math:`\pi_\theta(a_t | s_t)` is the policy parametrized by the vector :math:`\theta`. The policy predicts the
action :math:`a_t` given the state :math:`s_t`.
* :math:`\psi_t` is a type of return function (such as total reward, value function, advantage function,
TD residual,...)
"""
# def __init__(self, task, exploration_strategy, memory, hyperparameters):
# super(GradientRLAlgo, self).__init__(task, exploration_strategy, memory, hyperparameters)
def __init__(self, explorer, evaluator, updater, hyperparameters=None, dynamic_model=None): # , num_workers=1):
super(GradientRLAlgo, self).__init__(explorer, evaluator, updater, hyperparameters, dynamic_model)
# , num_workers)
# self.optimizer = hyperparameters.get('optimizer') #TODO
class EMRLAlgo(RLAlgo):
r"""Expectation-Maximization reinforcement learning algorithm.
"""
def __init__(self, task, exploration_strategy, storage, hyperparameters):
super(EMRLAlgo, self).__init__(task, exploration_strategy, storage, hyperparameters)
+512
View File
@@ -0,0 +1,512 @@
#!/usr/bin/env python
"""Provide the Updater class used in the third and final step of RL algorithms
The updater update the approximator (such as the policy and/or value function) parameters based on the loss, and
using the specified optmizer.
"""
# TODO: makes the 5 following classes inherit from the same Parent class
from pyrobolearn.approximators import Approximator
from pyrobolearn.policies import Policy
from pyrobolearn.values import Value
from pyrobolearn.dynamics import DynamicModel
from pyrobolearn.actorcritics import ActorCritic
from pyrobolearn.exploration import Exploration # TODO change that name to Explorer instead
from pyrobolearn.losses import Loss
from pyrobolearn.optimizers import Optimizer
from pyrobolearn.storages import Storage, RolloutStorage, Batch
from pyrobolearn.samplers import StorageSampler
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class Updater(object):
r"""Updater
(Model-free) reinforcement learning algorithms requires 3 steps:
1. Explore: Explore and collect samples in the environment using the policy. The samples are stored in the
given memory/storage unit.
2. Evaluate: Assess the quality of the actions/trajectories using the estimators.
3. Update: Update the policy (and/or value function) parameters based on the loss
This class focuses on the third step of RL algorithms.
"""
def __init__(self, approximators, sampler, losses, optimizers):
"""
Initialize the update phase.
Args:
approximators (list of Policy, Value, ActorCritic,...): approximators to update based on the given losses.
sampler (StorageSampler): sampler associated with the storage.
losses (Loss, list/dict of losses): losses. If dict: key=approximator, value=loss.
optimizers (Optimizer, or list/dict of optimizers): optimizer to use. If dict: key=approximator,
value=optimizer.
"""
self.approximators = approximators
self.sampler = sampler
self.losses = losses
self.optimizers = optimizers
self._evaluator = ApproximatorEvaluator(approximators)
##############
# Properties #
##############
@property
def approximators(self):
"""Return the list of approximators to update."""
return self._approximators
@approximators.setter
def approximators(self, approximators):
"""Set the approximator instances."""
if not isinstance(approximators, list):
approximators = [approximators]
for approximator in approximators:
if not isinstance(approximator, (Approximator, Policy, Value, ActorCritic, DynamicModel, Exploration)):
raise TypeError("Expecting the approximator to be an instance of `Approximator`, `Policy`, `Value`, "
"`ActorCritic`, `DynamicModel`, or `Exploration`. Instead got: "
"{}".format(type(approximator)))
self._approximators = approximators
self._evaluator = ApproximatorEvaluator(self._approximators)
@property
def sampler(self):
"""Return the sampler instance."""
return self._sampler
@sampler.setter
def sampler(self, sampler):
"""Set the sampler."""
if not isinstance(sampler, StorageSampler):
raise TypeError("Expecting the sampler to be an instance of `StorageSampler`, instead got: "
"{}".format(type(sampler)))
self._sampler = sampler
# self.storage = self._sampler.storage
@property
def storage(self):
"""Return the storage unit."""
# return self._storage
return self.sampler.storage
@storage.setter
def storage(self, storage):
"""Set the storage unit."""
self.sampler.storage = storage
# if not isinstance(storage, RolloutStorage):
# raise TypeError("Expecting the storage to be an instance of `Storage`, instead got: "
# "{}".format(type(storage)))
# self._storage = storage
@property
def losses(self):
"""Return the losses (one for each approximator)."""
return self._losses
@losses.setter
def losses(self, losses):
"""Set the losses."""
# check that the losses are the correct data type
if not isinstance(losses, list):
losses = [losses]
for loss in losses:
if not isinstance(loss, Loss):
raise TypeError("Expecting the loss to be an instance of `Loss`, instead got: {}".format(type(loss)))
# check that the number of losses matches the number of approximators
if len(losses) != len(self.approximators):
raise ValueError("The number of losses does not match up with the number of approximators to update.")
# set the losses
self._losses = losses
@property
def optimizers(self):
"""Return the optimizers used to optimize the parameters of the approximators."""
return self._optimizers
@optimizers.setter
def optimizers(self, optimizers):
"""Set the optimizers."""
# check that the optimizers are the correct data type
if not isinstance(optimizers, list):
optimizers = [optimizers]
for optimizer in optimizers:
if not isinstance(optimizer, Optimizer):
raise TypeError("Expecting optimizer to be an instance of `Optimizer`, instead got: "
"{}".format(type(optimizer)))
# check that the number of optimizers match the number of approximators / losses
if len(optimizers) != len(self.approximators):
if len(optimizers) == 1:
optimizers = optimizers * len(self.approximators)
else:
raise ValueError("Expecting the number of optimizers (={}) to match up with the number of "
"approximators / losses (={})".format(len(optimizers), len(self.approximators)))
# set the optimizers
self._optimizers = optimizers
@property
def evaluator(self):
"""Return the evaluator instance which evaluates the approximators on the given batch.."""
return self._evaluator
###########
# Methods #
###########
def update(self, num_batches=10):
"""
Update the given approximators (policies, value functions, etc).
Args:
num_batches (int): number of batches
Returns:
list: list of losses
"""
# set the number of batches
self.sampler.num_batches = num_batches
# for each batch
for batch in self.sampler:
# evaluation with the current parameters
self.evaluator.evaluate(batch)
# update each approximator based on the loss on which it is evaluated and using the specified optimizer
for approximator, loss, optimizer in zip(self.approximators, self.losses, self.optimizers):
# compute loss on the data (the loss knows what to do)
loss = loss.compute(batch)
# update parameters
optimizer.optimize(approximator.parameters(), loss)
return self.losses
#############
# Operators #
#############
def __repr__(self):
"""Return a representation string."""
return self.__class__.__name__
def __str__(self):
"""Return a string describing the class."""
return self.__class__.__name__
def __call__(self, num_batches=10): # , storage, losses):
"""Update the approximators."""
self.update(num_batches=num_batches)
class ApproximatorEvaluator(object):
r"""Approximators evaluator
Approximators evaluator used mostly during the update phase. Evaluate the various approximators on the given batch.
This consists:
- for policies, to compute :math:`\pi_{\theta}(a|s)` and :math:`\pi_{\theta}(.|s)` if possible.
- for value functions, to compute :math:`V_{\phi}(s)`, :math:`Q_{\phi}(s,a)`, and/or :math:`A_{\phi}`(s,a)
- for dynamic models, to compute :math:``
"""
def __init__(self, approximators):
"""
Initialize the evaluator for the approximators.
Args:
approximators ((list of) Approximator): approximators
"""
self.approximators = approximators
##############
# Properties #
##############
@property
def approximators(self):
"""Return the list of approximators to update."""
return self._approximators
@approximators.setter
def approximators(self, approximators):
"""Set the list of approximators to update."""
if not isinstance(approximators, list):
approximators = [approximators]
for approximator in approximators:
if not isinstance(approximator, (Approximator, Policy, Value, ActorCritic, DynamicModel, Exploration)):
raise TypeError("Expecting the approximator to be an instance of `Approximator`, `Policy`, `Value`, "
"`ActorCritic`, `DynamicModel`, or `Exploration`. Instead got: "
"{}".format(type(approximator)))
self._approximators = approximators
###########
# Methods #
###########
def evaluate(self, batch):
"""Evaluate the various approximators."""
if not isinstance(batch, Batch):
raise TypeError("Expecting the given batch storage to be an instance of `Batch`, instead got: "
"{}".format(type(batch)))
# sub-evaluation with the current parameter
for approximator in self.approximators:
if isinstance(approximator, (Policy, Exploration)):
actions, action_distributions = approximator.evaluate(batch['observations'])
batch.current['actions'] = actions
batch.current['action_distributions'] = action_distributions
elif isinstance(approximator, Value):
values = approximator.evaluate(batch['observations'], batch['actions'])
batch.current['values'] = values
elif isinstance(approximator, ActorCritic):
actions, action_distributions, values = approximator.evaluate(batch['observations'], batch['actions'])
batch.current['actions'] = actions
batch.current['action_distributions'] = action_distributions
batch.current['values'] = values
elif isinstance(approximator, DynamicModel):
next_states, state_distributions = approximator.evaluate(batch['observations'], batch['actions'])
batch.current['next_states'] = next_states
batch.current['state_distributions'] = state_distributions
else:
raise TypeError("Expecting the approximator to be an instance of `Policy`, `Value`, `ActorCritic`, or "
"`DynamicModel`, instead got: {}".format(type(approximator)))
return batch
class PolicyEvaluator(object):
r"""Policy evaluator
Evaluate a policy by computing :math:`\pi_{\theta}(a|s)` and if possible the distribution :math:`\pi(.|s)`. The
policy is evaluated on a batch.
"""
def __init__(self, policy, batch=None):
"""Initialize the policy evaluator.
policy (Policy): policy to evaluate.
batch (None, Batch): initial batch.
"""
self.policy = policy
self.batch = batch
##############
# Properties #
##############
@property
def policy(self):
"""Return the policy instance."""
return self._policy
@policy.setter
def policy(self, policy):
"""Set the policy."""
if not isinstance(policy, Policy):
raise TypeError("Expecting the given policy to be an instance of `Policy`, instead got: "
"{}".format(type(policy)))
self._policy = policy
###########
# Methods #
###########
def evaluate(self, batch=None):
"""Evaluate the policy on the given batch. If None, it will evaluate on the previous batch."""
# check batch
if batch is None:
batch = self.batch
if batch is None:
raise ValueError("Expecting a batch to be given.")
# evaluate policy
actions, action_distributions = self.policy.evaluate(batch['observations'])
# put them in the batch
batch.current['actions'] = actions
batch.current['action_distributions'] = action_distributions
# return batch
return batch
class ValueEvaluator(object):
r"""Value evaluator
Evaluate a value by computing :math:`V_{\phi}(s)`, :math:`Q_{\phi}(s,a)`, and / or :math:`A_{\phi}(s,a)`.
The value is evaluated on a batch.
"""
def __init__(self, value, batch=None):
"""Initialize the value evaluator.
value (Value): value to evaluate.
batch (None, Batch): initial batch.
"""
self.value = value
self.batch = batch
##############
# Properties #
##############
@property
def value(self):
"""Return the value instance."""
return self._value
@value.setter
def value(self, value):
"""Set the value function approximator."""
if not isinstance(value, Value):
raise TypeError("Expecting the given value to be an instance of `Value`, instead got: "
"{}".format(type(value)))
self._value = value
###########
# Methods #
###########
def evaluate(self, batch=None):
"""Evaluate the value on the given batch. If None, it will evaluate on the previous batch."""
# check batch
if batch is None:
batch = self.batch
if batch is None:
raise ValueError("Expecting a batch to be given.")
# evaluate value
values = self.value.evaluate(batch['observations'], batch['actions'])
# put them in the batch
batch.current['values'] = values
# return batch
return batch
class ActorCriticEvaluator(object):
r"""ActorCritic evaluator
Evaluate an action by computing :math:`\pi_{\theta}(a|s)` and if possible the distribution :math:`\pi(.|s)`. It
also evaluates the value by computing :math:`V_{\phi}(s)`, :math:`Q_{\phi}(s,a)`, and / or :math:`A_{\phi}(s,a)`.
Both are evaluated on a batch.
"""
def __init__(self, actorcritic, batch=None):
"""Initialize the actorcritic evaluator.
actorcritic (ActorCritic): actorcritic to evaluate.
batch (None, Batch): initial batch.
"""
self.actorcritic = actorcritic
self.batch = batch
##############
# Properties #
##############
@property
def actorcritic(self):
"""Return the actor-critic instance."""
return self._actorcritic
@actorcritic.setter
def actorcritic(self, actorcritic):
"""Set the actor-critic."""
if not isinstance(actorcritic, ActorCritic):
raise TypeError("Expecting the given actorcritic to be an instance of `ActorCritic`, instead got: "
"{}".format(type(actorcritic)))
self._actorcritic = actorcritic
###########
# Methods #
###########
def evaluate(self, batch=None):
"""Evaluate the actorcritic on the given batch. If None, it will evaluate on the previous batch."""
# check batch
if batch is None:
batch = self.batch
if batch is None:
raise ValueError("Expecting a batch to be given.")
# evaluate actorcritic
actions, action_distributions, values = self.actorcritic.evaluate(batch['observations']) # , batch['actions'])
# put them in the batch
batch.current['actions'] = actions
batch.current['action_distributions'] = action_distributions
batch.current['values'] = values
# return batch
return batch
class DynamicModelEvaluator(object):
r"""Dynamic model evaluator
Evaluate the next state given the current state and action.
"""
def __init__(self, dynamic_model, batch=None):
"""Initialize the dynamic_model evaluator.
dynamic_model (ActorCritic): dynamic_model to evaluate.
batch (None, Batch): initial batch.
"""
self.dynamic_model = dynamic_model
self.batch = batch
##############
# Properties #
##############
@property
def dynamic_model(self):
"""Return the dynamic_model instance."""
return self._dynamic_model
@dynamic_model.setter
def dynamic_model(self, dynamic_model):
"""Set the dynamic model."""
if not isinstance(dynamic_model, DynamicModel):
raise TypeError("Expecting the given dynamic_model to be an instance of `ActorCritic`, instead got: "
"{}".format(type(dynamic_model)))
self._dynamic_model = dynamic_model
###########
# Methods #
###########
def evaluate(self, batch=None):
"""Evaluate the dynamic_model on the given batch. If None, it will evaluate on the previous batch."""
# check batch
if batch is None:
batch = self.batch
if batch is None:
raise ValueError("Expecting a batch to be given.")
# evaluate dynamic_model
next_states, state_distributions = self.dynamic_model.evaluate(batch['observations'], batch['actions'])
# put them in the batch
batch.current['next_states'] = next_states
batch.current['state_distributions'] = state_distributions
# return batch
return batch
+1 -1
View File
@@ -142,7 +142,7 @@ class Estimator(object):
class TotalRewardEstimator(Estimator):
r"""Total reward Estimator (aka finite-horizon undiscounted return)
r"""Total reward Estimator (aka (finite-horizon) discounted return)
Return the total reward of the trajectory given by:
+9
View File
@@ -0,0 +1,9 @@
## Exploration strategies
This folder provides the various exploration strategies used in reinforcement learning. It basically wraps the policy,
and defines how the policy should explore in the environment. Exploration is mainly performed in the action space or
the (hyper-)parameter space.
## what to look/check next?
Check `distributions`, and `policies` folders.
+19
View File
@@ -0,0 +1,19 @@
import logging
# import exploration
from .exploration import *
# import action exploration
from .actions import *
# import parameter exploration
from .parameters import *
# create logger
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(name)s (%(levelname)s): %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
@@ -0,0 +1,12 @@
# import action exploration
from .action_exploration import *
# import discrete action exploration
from .discrete import *
from .eps_greedy import *
from .boltzmann import *
# import continuous action exploration
from .continuous import *
from .gaussian import *
@@ -0,0 +1,195 @@
#!/usr/bin/env python
r"""Provide the action exploration strategies.
Action exploration is used in reinforcement learning algorithms and describe how the policy explores in the
environment. Note that the policy is the only (probability) function that we have control over; we do not control the
dynamic transition (probability) function nor the reward function. In action exploration, a probability distribution
is put on the outputted action space :math:`a_t \sim \pi_{\theta}(\cdot|s_t)`. There are mainly two categories:
exploration for discrete actions (which uses discrete probability distribution) and exploration for continuous action
(which uses continuous probability distribution).
Note that action exploration is a step-based exploration strategy where at each time step of an episode, an action is
sampled based on the specified distribution.
Action exploration might change a bit the structure of the policy while running.
References:
[1] "Reinforcement Learning: An Introduction", Sutton and Barto, 2018
"""
import collections
import torch
from pyrobolearn.actions import Action
import pyrobolearn as prl
from pyrobolearn.exploration import Exploration
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class ActionExploration(Exploration):
r"""Action Exploration (aka Step-based RL)
Explore in the action space of the policy. At each time step, the action is sampled from the policy based
on the given distribution (hence the name 'step-based' RL).
Assume a policy is denoted by :math:`\pi_{\theta}(a|s)` which maps states :math:`s` to action :math`a`, and
is parametrized by :math:`\theta` which are the parameters that can be learned/optimized/trained. In action
space exploration, the actions :math:`a` are sampled from a probability distribution, such as a Gaussian
distribution such that :math:`a \sim \mathcal{N}(\pi_{\theta}(a|s), \Sigma)`.
This way of exploring is notably used in:
- several reinforcement learning algorithms (REINFORCE, TRPO, PPO, etc)
"""
def __init__(self, policy, action=None, explorations=None):
"""
Initialize the action exploration strategy.
Args:
policy (Policy): Policy to wrap.
action (Action, None): action space to explore.
explorations ((list of) ActionExploration, None): (list of) action exploration strategies. Each action
exploration strategy describes how the policy explores in the specified (discrete or continuous)
action space.
"""
super(ActionExploration, self).__init__(policy)
# check action
if action is not None and not isinstance(action, Action):
raise TypeError("Expecting the given action to be an instance of `Action`, instead got: "
"{}".format(type(action)))
self._action = action
# check exploration strategies
# if no exploration strategies set
if explorations is None:
explorations = []
# if no action has been defined
if self.action is None:
# go over each action of the policy, and add the corresponding exploration strategy based on the
# action type
for action in self.policy.actions:
if action.is_discrete():
prl.logger.debug('creating a Boltzmann action exploration with action of size: %d',
action.space[0].n)
exploration = prl.exploration.actions.BoltzmannActionExploration(self.policy, action)
elif action.is_continuous():
exploration = prl.exploration.actions.GaussianActionExploration(self.policy, action)
else:
raise ValueError("Expecting an action to be discrete or continuous.")
explorations.append(exploration)
else:
# check if an action has already been set
if self.action is not None:
raise ValueError("Expecting to be given an action or a list of explorations, not both.")
# transform the explorations to a list if not iterable
if not isinstance(explorations, collections.Iterable):
explorations = [explorations]
# check the length of exploration strategies and the number of actions in the policy
if len(explorations) != len(self.policy.actions):
raise ValueError("Expecting the number of actions (={}) to be the same as the number of exploration "
"strategy (={}).".format(len(self.policy.actions), len(explorations)))
actions = set([exploration.action for exploration in explorations])
# check that each exploration is set for each action
for action in self.policy.actions:
if action not in actions:
raise ValueError("Expecting for each action in the policy to have its corresponding exploration "
"strategy. The following action was not found in the exploration strategies: "
"{}".format(action))
# set the exploration strategies
self._explorations = explorations
##############
# Properties #
##############
@property
def action(self):
"""Return the specific action to explore. This might return None."""
return self._action
@property
def explorations(self):
"""Return the list of exploration strategies."""
return self._explorations
###########
# Methods #
###########
def act(self, state=None, deterministic=True, to_numpy=True, return_logits=False, apply_action=True):
r"""
Act/Explore in the environment given the states.
Args:
state (State): current state
deterministic (bool): True by default. It can only be set to False, if the policy is stochastic.
to_numpy (bool): If True, it will convert the data (torch.Tensors) to numpy arrays.
return_logits (bool): If True, in the case of discrete outputs, it will return the logits.
apply_action (bool): If True, it will call and execute the action.
Returns:
(list of) torch.Tensor: action(s)
(list of) torch.distributions.Distribution: policy distribution(s) :math:`\pi_{\theta}(\cdot | s)`
"""
# TODO: finish to clean
print(state)
actions = self.policy.act(state, to_numpy=False, return_logits=True)
if deterministic:
return actions, None
# From deterministic output into stochastic outputs
# print("Actions before dist: {}").format(actions.train_data)
print("Exploration strategy - actions: {}".format(actions))
self.dist = self.distribution(actions)
actions = self.dist.sample()
print("Exploration strategy - sampled action: {}".format(actions))
if isinstance(actions, torch.Tensor):
if actions.requires_grad:
self.policy.actions.data = actions.detach().numpy()
else:
self.policy.actions.data = actions.numpy()
else:
self.policy.actions.data = actions
return actions, self.dist
def mode(self):
"""Return the mode of the distributions."""
actions = self.dist.mode()
return actions
def sample(self):
"""Sample an action from the distribution."""
actions = self.dist.sample()
return actions
def action_log_prob(self, actions):
"""Return the log probability evaluated at the given actions."""
return self.dist.log_probs(actions)
def action_prob(self, actions):
"""Return the probability evaluated at the given actions."""
return torch.exp(self.dist.log_probs(actions))
def entropy(self):
"""Return the entropy of the distribution."""
return self.dist.entropy().mean()
@@ -0,0 +1,50 @@
#!/usr/bin/env python
r"""Provide the discrete Boltzmann action exploration.
The Boltzmann exploration strategy consists to explore in the discrete action space of policies by using a
categorical distribution on action probabilities. The probabilities are often computed using a softmax function
which maps the values of the logits to correct probabilities (i.e. each probability is between 0 and 1, and the
sum of them sums to 1). That is, compared to epsilon-greedy which selects another action uniformly, Boltzmann
exploration selects an action based on its weight which is outputted by the policy.
"""
import torch
from pyrobolearn.distributions.modules import CategoricalModule, IdentityModule
from pyrobolearn.exploration.actions.discrete import DiscreteActionExploration
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class BoltzmannActionExploration(DiscreteActionExploration):
r"""Boltzmann action exploration.
The Boltzmann exploration strategy consists to explore in the discrete action space of policies by using a
categorical distribution on action probabilities. The probabilities are often computed using a softmax function
which maps the values of the logits to correct probabilities (i.e. each probability is between 0 and 1, and the
sum of them sums to 1). That is, compared to epsilon-greedy which selects another action uniformly, Boltzmann
exploration selects an action based on its weight which is outputted by the policy.
"""
def __init__(self, policy, action):
"""
Initialize the Boltzmann action exploration strategy.
Args:
policy (Policy): policy to wrap.
action (Action): discrete actions.
"""
super(BoltzmannActionExploration, self).__init__(policy, action=action)
# create Categorical module
logits = IdentityModule()
self._module = CategoricalModule(logits=logits)
@@ -0,0 +1,48 @@
#!/usr/bin/env python
r"""Provide the continuous action exploration strategies.
Action exploration is used in reinforcement learning algorithms and describe how the policy explores in the
environment. Note that the policy is the only (probability) function that we have control over; we do not control the
dynamic transition (probability) function nor the reward function. In action exploration, a probability distribution
is put on the outputted action space :math:`a_t \sim \pi_{\theta}(.|s_t)`. There are mainly two categories:
exploration for discrete actions (which uses discrete probability distribution) and exploration for continuous action
(which uses continuous probability distribution).
Note that action exploration is a step-based exploration strategy where at each time step of an episode, an action is
sampled based on the specified distribution.
Action exploration might change a bit the structure of the policy while running.
References:
[1] "Reinforcement Learning: An Introduction", Sutton and Barto, 2018
"""
from pyrobolearn.exploration.actions.action_exploration import ActionExploration
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class ContinuousActionExploration(ActionExploration):
r"""Continuous action exploration.
Continuous action exploration strategies use continuous probability distributions on the (continuous) actions.
"""
def __init__(self, policy, action):
"""
Initialize the continuous action exploration strategy.
Args:
policy (Policy): policy to wrap.
action (action): continuous action.
"""
super(ContinuousActionExploration, self).__init__(policy, action=action)
if not self.action.is_continuous():
raise ValueError("Expecting the given action to be continuous.")
@@ -0,0 +1,48 @@
#!/usr/bin/env python
r"""Provide the discrete action exploration strategies.
Action exploration is used in reinforcement learning algorithms and describe how the policy explores in the
environment. Note that the policy is the only (probability) function that we have control over; we do not control the
dynamic transition (probability) function nor the reward function. In action exploration, a probability distribution
is put on the outputted action space :math:`a_t \sim \pi_{\theta}(.|s_t)`. There are mainly two categories:
exploration for discrete actions (which uses discrete probability distribution) and exploration for continuous action
(which uses continuous probability distribution).
Note that action exploration is a step-based exploration strategy where at each time step of an episode, an action is
sampled based on the specified distribution.
Action exploration might change a bit the structure of the policy while running.
References:
[1] "Reinforcement Learning: An Introduction", Sutton and Barto, 2018
"""
from pyrobolearn.exploration.actions.action_exploration import ActionExploration
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class DiscreteActionExploration(ActionExploration):
r"""Discrete action exploration
Discrete action exploration strategies use discrete probability distributions on the (discrete) actions.
"""
def __init__(self, policy, action):
"""
Initialize the discrete action exploration strategy.
Args:
policy (Policy): policy to wrap.
action (action): discrete action.
"""
super(DiscreteActionExploration, self).__init__(policy, action=action)
if not self.action.is_discrete():
raise ValueError("Expecting the given action to be discrete.")
@@ -0,0 +1,40 @@
#!/usr/bin/env python
r"""Provide the discrete epsilon-greedy action exploration.
The epsilon-greedy exploration strategy consists to explore in the discrete action space of policies. Specifically,
it selects the best action :math:`a*` with probability :math:`p = (1 - \epsilon)`, or another action
:math:`a \in A\{a*}` randomly (based on uniform distribution) with probability :math:`p = \frac{\epsilon}{|A|-1}`.
"""
import torch
from pyrobolearn.exploration.actions.discrete import DiscreteActionExploration
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class EpsilonGreedyActionExploration(DiscreteActionExploration):
r"""Epsilon-greedy action exploration.
The epsilon-greedy exploration strategy consists to explore in the discrete action space of policies. Specifically,
it selects the best action :math:`a*` with probability :math:`p = (1 - \epsilon)`, or another action
:math:`a \in A\{a*}` randomly (based on uniform distribution) with probability :math:`p = \frac{\epsilon}{|A|-1}`.
"""
def __init__(self, policy, action):
"""
Initialize the epsilon-greedy action exploration strategy.
Args:
policy (Policy): policy to wrap.
action (Action): discrete actions.
"""
super(EpsilonGreedyActionExploration, self).__init__(policy, action=action)
@@ -0,0 +1,54 @@
#!/usr/bin/env python
"""Provide the continuous Gaussian action exploration strategies.
The Gaussian exploration strategy consists to explore in the continuous action space of policies by using a
gaussian distribution on action probabilities.
"""
import torch
from pyrobolearn.distributions.modules import *
from pyrobolearn.exploration.actions.continuous import ContinuousActionExploration
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class GaussianActionExploration(ContinuousActionExploration):
r"""Gaussian continuous action exploration.
The Gaussian exploration strategy consists to explore in the continuous action space of policies by using a
gaussian distribution on action probabilities.
"""
def __init__(self, policy, action, module=None):
"""
Initialize the Gaussian action exploration strategy.
Args:
policy (Policy): policy to wrap.
action (Action): continuous actions.
module (None, GaussianModule): Gaussian module.
"""
super(GaussianActionExploration, self).__init__(policy, action=action)
# create Gaussian module if necessary
if module is None:
mean = IdentityModule()
covariance = DiagonalCovarianceModule(num_inputs=self.policy.base_output.size(-1),
num_outputs=action.size)
module = GaussianModule(mean=mean, covariance=covariance)
# check that the module is a Gaussian Module
if not isinstance(module, GaussianModule):
raise TypeError("Expecting the given 'module' to be an instance of `GaussianModule`, instead got: "
"{}".format(type(module)))
self._module = module
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python
"""Provide the various exploration strategies.
Specifically, this file provides the main abstract `Exploration` class from which all the other exploration strategies
inherit from. Exploration is mainly useful in reinforcement learning, and can be carried out in the action or parameter
space.
References:
[1] "Reinforcement Learning: An Introduction", Sutton and Barto, 2018
"""
import torch
from pyrobolearn.policies import Policy
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class Exploration(object):
r"""Exploration class
Exploration strategy: it wraps the policy, and defines how the policy should explore in the environment.
There are 2 main exploration strategies:
1. exploration in the action space of the policy
2. exploration in the parameter space of the policy
Exploration can be, for instance, carried out based on the uncertainty of an action, a dynamic model (which
predicts the next state given the current state and action), a value fct evaluated at the given state, and so on.
This is also called "curiosity" and is linked to the notion of entropy (as the entropy is related to the notion
of uncertainty).
Exploration is crucial because it defines a stochastic policy, and thus a probability distribution. This in turn
enables the use of probability concepts such as 'maximizing the likelihood or marginal likelihood'.
Policy search algorithms currently only works with stochastic policies.
"""
def __init__(self, policy):
"""
Initialize the Exploration strategist.
Args:
policy (Policy): Policy to wrap.
"""
self.policy = policy
##############
# Properties #
##############
@property
def policy(self):
"""Return the policy instance"""
return self._policy
@policy.setter
def policy(self, policy):
"""Set the policy instance."""
if not isinstance(policy, Policy):
raise TypeError("Expecting policy to be an instance of `Policy`, instead got: {}.".format(type(policy)))
self._policy = policy
###########
# Methods #
###########
def reset(self):
"""Reset the exploration strategy, which can be useful at the beginning of an episode."""
self.policy.reset()
def _act(self, state=None, to_numpy=True, return_logits=False, apply_action=True):
"""Perform the exploratory action."""
raise NotImplementedError
def act(self, state=None, deterministic=False, to_numpy=True, return_logits=False, apply_action=True):
"""Perform the action given the state.
Args:
state (State, list of np.array, list of torch.Tensor): current state.
deterministic (bool): If True, it will return a deterministic action. If False, it will explore.
to_numpy (bool): If True, it will convert the data (torch.Tensors) to numpy arrays.
return_logits (bool): If True, in the case of discrete outputs, it will return the logits.
apply_action (bool): If True, it will call and execute the action.
Returns:
(list of) np.array / torch.Tensor: action data
"""
if deterministic:
return self.policy.act(state, deterministic=True, to_numpy=to_numpy, return_logits=return_logits,
apply_action=apply_action)
else: # explore using the distribution
return self._act(state, to_numpy=to_numpy, return_logits=return_logits, apply_action=apply_action)
# def step(self, states):
# """Perform one step using the policy with the corresponding exploration strategy."""
# pass
# def clear(self):
# """Called at the end of an episode to reset a policy or clear whatever has been done."""
# pass
# class ModelUncertaintyExploration(Exploration):
# r"""Model Uncertainty Exploration
#
# Exploration based on the uncertainty of a learned dynamic model.
#
# References:
# [1]
# """
#
# def __init__(self, policy):
# super(ModelUncertaintyExploration, self).__init__(policy)
@@ -0,0 +1,68 @@
#!/usr/bin/env python
"""Provide the continuous Gaussian parameter exploration strategies.
The Gaussian parameter exploration strategy consists to explore in the parameter space of policies by using a gaussian
distribution on the parameters.
"""
import torch
# from pyrobolearn.distributions.modules import FixedMeanModule, FixedCovarianceModule, GaussianModule
from pyrobolearn.distributions.gaussian import Gaussian
from pyrobolearn.exploration.parameters.parameter_exploration import ParameterExploration
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class GaussianParameterExploration(ParameterExploration):
r"""Gaussian continuous parameter exploration.
The Gaussian exploration strategy consists to explore in the continuous parameter space of policies by using a
gaussian distribution on parameters.
"""
def __init__(self, policy, variance=1., module=None):
"""
Initialize the Gaussian action exploration strategy.
Args:
policy (Policy): policy to wrap.
variance (float): variance parameter for the covariance.
module (None, GaussianModule): Gaussian module.
"""
super(GaussianParameterExploration, self).__init__(policy)
# create Gaussian module if necessary
if module is None:
mean = torch.tensor(self.parameters, requires_grad=True)
# mean = FixedMeanModule(mean=mean)
covariance = variance * torch.eye(self.size, requires_grad=True)
# covariance = FixedCovarianceModule(covariance=covariance)
# module = GaussianModule(mean=mean, covariance=covariance)
module = Gaussian(mean=mean, covariance=covariance)
# check that the module is a Gaussian Module
if not isinstance(module, GaussianModule):
raise TypeError("Expecting the given 'module' to be an instance of `GaussianModule`, instead got: "
"{}".format(type(module)))
self._module = module
@property
def module(self):
"""Return the module instance."""
return self._module
def sample(self):
"""Sample the parameters from the """
parameters = self.module.rsample((1,)) # rsample allows to get the gradients
self.policy.set_vectorized_parameters(vector=parameters)
@@ -0,0 +1,100 @@
#!/usr/bin/env python
"""Provide the parameter exploration strategies.
Parameter exploration is used in reinforcement learning algorithms, and describes how the policy explores in the
environment. Note that the policy is the only (probability) function that we have control over; we do not control the
dynamic transition (probability) function nor the reward function. In parameter exploration, we explore the parameter
space of the policy.
Note that parameter exploration is an episode-based exploration strategy where the parameters of the policy are only
perturbed at the beginning of an episode, and unchanged during that particular episode.
Parameter exploration might change a bit the structure of the policy while running.
References:
[1] "Evolution strategies as a scalable alternative to reinforcement learning", Salimans et al., 2017
[2] "Parameter Space Noise for Exploration", Plappert et al., 2018
"""
import torch
from pyrobolearn.exploration import Exploration
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class ParameterExploration(Exploration):
r"""Parameter Exploration (aka Episode-based RL)
Explore in the parameter space of the policy. At each episode, we apply a small variation on the parameters
of the policy and keep it fixed during the whole episode (hence the name 'episode-based' RL).
Assume a policy is denoted by :math:`\pi_{\theta}(a|s)` which maps states :math:`s` to action :math`a`, and
is parametrized by :math:`\theta` which are the parameters that can be learned/optimized/trained. In parameter
space exploration, the parameters :math:`\theta` are sampled from a probability distribution, such as a
Gaussian distribution such that :math:`\theta \sim \mathcal{N}(\theta_k, \Sigma)`.
This way of exploring is notably used in:
- population-based algorithms such as evolutionary algorithms (ES, NEAT). Note that in `NEAT`, the topology
of the learning model (i.e. neural network) is also explored along with its weights.
- reinforcement learning algorithms like PoWER, and others.
Pros:
- when sampling several parameters (and thus policies), each one of them can be evaluated in a parallel manner.
These policies are thus independent.
- giving the same state to a policy results in the same action (in contrast to action exploration).
References:
[1] "Parameter Space Noise for Exploration", Plappert et al., 2018
"""
def __init__(self, policy):
super(ParameterExploration, self).__init__(policy)
self._parameters = policy.get_vectorized_parameters(to_numpy=False)
##############
# Properties #
##############
@property
def parameters(self):
"""Returns the parameters."""
return self._parameters
@property
def size(self):
"""Returns the dimension of the parameters."""
return self.parameters.size(-1)
###########
# Methods #
###########
def reset(self):
# sample new set of parameters for policy
pass
def act(self, state=None, deterministic=True, to_numpy=True, return_logits=False, apply_action=True):
"""Perform the action given the state."""
actions = self.policy.act(state)
# class ModelUncertaintyExploration(Exploration):
# r"""Model Uncertainty Exploration
#
# Exploration based on the uncertainty of a learned dynamic model.
#
# References:
# [1]
# """
#
# def __init__(self, policy):
# super(ModelUncertaintyExploration, self).__init__(policy)
+2 -2
View File
@@ -1,6 +1,6 @@
# import optimizer
from optimizer import Optimizer
from .optimizer import Optimizer
# import scipy optimizer
# from scipy_optimizer import Scipy
@@ -21,7 +21,7 @@ from optimizer import Optimizer
# from gpyopt_optimizer import BayesianOptimizer
# import torch optimizers
from torch_optimizer import *
from .torch_optimizer import *
# import Contact-Invariant Optimizer
# from cio import CIO
+1 -1
View File
@@ -12,7 +12,7 @@ References:
import torch.nn as nn
import torch.optim as optim
from optimizer import Optimizer
from pyrobolearn.optimizers.optimizer import Optimizer
__author__ = "Brian Delhaisse"
+7 -1
View File
@@ -512,13 +512,19 @@ class Policy(object):
def train(self, mode=True):
"""
Set the policy to train mode.
Set the policy in training mode.
Args:
mode (bool): if True, set the policy in train mode.
"""
self.train_mode = mode
def eval(self):
"""
Set the policy in evaluation mode.
"""
self.train(mode=False)
def reset(self, reset_processors=False, *args, **kwargs):
"""
Reset the policy.
+21 -3
View File
@@ -169,30 +169,36 @@ class Reward(object):
@property
def state(self):
"""Return the state instance."""
return self._state
@state.setter
def state(self, state):
"""Set the state."""
if state is not None and not isinstance(state, State):
raise TypeError("Expecting state to be None or an instance of State.")
self._state = state
@property
def action(self):
"""Return the action instance."""
return self._action
@action.setter
def action(self, action):
"""Set the action."""
if action is not None and not isinstance(action, Action):
raise TypeError("Expecting action to be None or an instance of Action.")
self._action = action
@property
def rewards(self):
"""Return the inner rewards."""
return self._rewards
@rewards.setter
def rewards(self, rewards):
"""Set the inner rewards."""
if rewards is None:
rewards = []
elif isinstance(rewards, collections.Iterable):
@@ -210,17 +216,29 @@ class Reward(object):
###########
def has_rewards(self):
"""Check if there are inner rewards."""
return len(self._rewards) > 0
@staticmethod
def is_maximized():
"""Check if it is maximized."""
return True
def reset(self):
"""Reset the rewards."""
for reward in self.rewards:
reward.reset()
def compute(self):
def compute(self): # **kwargs):
"""Compute the reward and return the scalar value
Warnings: by default, *args and **kwargs are disabled as it could lead to several problems:
1. As more and more rewards will become available, there might share the same argument name if the programmer
is not careful, which could lead to bugs that are difficult to detect.
2. It is better to provide the arguments during the initialization of the reward class. If the user has
a variable that might change, create a class for that variable and in the corresponding reward's compute
method, check what is its value.
"""
pass
#############
@@ -234,8 +252,8 @@ class Reward(object):
lst = [reward.__repr__() for reward in self.rewards]
return ' + '.join(lst)
def __call__(self, *args, **kwargs):
return self.compute()
def __call__(self): # **kwargs):
return self.compute() # **kwargs)
# for unary and binary operators, see `__init__()` method.
+20 -14
View File
@@ -16,6 +16,8 @@ import numpy as np
import torch
from torch.utils.data.sampler import BatchSampler, SubsetRandomSampler
from pyrobolearn import logger
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
@@ -114,7 +116,7 @@ class PyTorchStorage(Storage):
def _to(self, item, device=None, dtype=None):
"""Send the item to the specified device and convert it to the specified data type."""
if isinstance(item, torch.Tensor):
print('setting {} to {} with dtype={}'.format(item, device, dtype))
logger.debug('setting tensor of size {} to {} with dtype={}'.format(item.size(), device, dtype))
item = item.to(device=device, dtype=dtype)
elif isinstance(item, dict):
for key, value in item.items():
@@ -328,10 +330,10 @@ class DictStorage(dict, PyTorchStorage):
"""Get the attribute using the key name. That is, instead of `D['name']`, you can do `D.name`."""
return self[name]
def __setattr__(self, key, value):
"""Set the attribute using the given key and value. That is, instead of `D[key] = value`, you can do
`D.key = value`"""
self[key] = value
# def __setattr__(self, key, value):
# """Set the attribute using the given key and value. That is, instead of `D[key] = value`, you can do
# `D.key = value`"""
# self[key] = value
# alias
@@ -382,13 +384,12 @@ class RolloutStorage(DictStorage):
# recurrent_hidden_state_size (int): size of the internal state
print("\nStorage: observation shape: {}".format(observation_shapes))
print("Storage: action shape: {}".format(action_shapes))
super(RolloutStorage, self).__init__()
self._step = 0
self._num_steps = int(num_steps)
self._num_processes = int(num_processes)
self._shifts = {} # dictionary that maps the key to the time shift; this is add to the current time step
self.init(self.num_steps, observation_shapes, action_shapes, self.num_processes)
super(RolloutStorage, self).__init__()
##############
# Properties #
@@ -502,20 +503,25 @@ class RolloutStorage(DictStorage):
self._num_processes = int(num_processes)
# allocate space for observations / states
logger.debug('creating space for observations of shapes: {}'.format(observation_shapes))
if not isinstance(observation_shapes, list):
observation_shapes = [observation_shapes]
self.create_new_entry('observations', shapes=observation_shapes, num_steps=self.num_steps+1)
# allocate space for actions
logger.debug('creating space for actions of shapes: {}'.format(action_shapes))
if not isinstance(action_shapes, list):
action_shapes = [action_shapes]
self.create_new_entry('actions', shapes=action_shapes, num_steps=self.num_steps)
# allocate space for rewards
logger.debug('creating space for rewards')
self.create_new_entry('rewards', shapes=1, num_steps=self.num_steps)
# allocate space for the returns and masks
logger.debug('creating space for returns')
self.create_new_entry('returns', shapes=1, num_steps=self.num_steps + 1)
logger.debug('creating space for masks')
self.create_new_entry('masks', shapes=1, num_steps=self.num_steps + 1)
# space for log probabilities on policy, distributions, scalar values from value functions,
@@ -680,13 +686,13 @@ class RolloutStorage(DictStorage):
"{}".format(key, type(key)))
super(RolloutStorage, self).__setitem__(key, value)
def __setattr__(self, key, value):
"""Set the attribute using the given key and value. That is, instead of `D[key] = value`, you can do
`D.key = value`. By default, this creates a tensor with shape (num_steps + 1, self.num_processes, 1).
Warnings: avoid to use this.
"""
self.create_new_entry(key, shapes=1, num_steps=self.num_steps + 1)
# def __setattr__(self, key, value):
# """Set the attribute using the given key and value. That is, instead of `D[key] = value`, you can do
# `D.key = value`. By default, this creates a tensor with shape (num_steps + 1, self.num_processes, 1).
#
# Warnings: avoid to use this.
# """
# self.create_new_entry(key, shapes=1, num_steps=self.num_steps + 1)
# Tests