mirror of
https://github.com/wassname/pyrobolearn.git
synced 2026-10-27 13:11:10 +08:00
524 lines
17 KiB
Python
524 lines
17 KiB
Python
#!/usr/bin/env python
|
|
"""Provides the various abstract value function approximators.
|
|
|
|
It can use learning models as most of these models learn a function (that is how to map inputs to outputs).
|
|
For instance, a value network is a function represented by a neural network that maps a state to a value (real number).
|
|
|
|
Dependencies:
|
|
- `pyrobolearn.states`
|
|
- `pyrobolearn.actions`
|
|
- `pyrobolearn.approximators` (and thus `pyrobolearn.models`)
|
|
"""
|
|
|
|
from abc import ABCMeta, abstractmethod
|
|
import numpy as np
|
|
import torch
|
|
|
|
from pyrobolearn.states import State
|
|
from pyrobolearn.actions import Action
|
|
from pyrobolearn.approximators import Approximator
|
|
|
|
|
|
__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 ValueApproximator(object):
|
|
r"""Value Function Approximator
|
|
|
|
In reinforcement learning, this is known as value-based reinforcement learning. The other end being policy
|
|
search. Several methods fall in the spectrum between these 2 approaches. For instance, actor-critic methods
|
|
optimize a policy (aka the actor) using the value function (aka critic).
|
|
|
|
As for the Policy (see `policy.py`), the value function approximator groups a model (which can be learned as a
|
|
neural network, or built as a table) with the states and actions.
|
|
|
|
There are mainly two types of value function:
|
|
1. state value function denoted by :math:`V(s)` which represents the expected reward accumulated when
|
|
starting at state :math:`s`.
|
|
2. (state-)action value function denoted by :math:`Q(s,a)`, which represents the expected reward when
|
|
starting at state :math:`s`, and performing action :math:`a`.
|
|
|
|
In both cases, they use the state. This class computes :math:`V(s)`.
|
|
"""
|
|
__metaclass__ = ABCMeta
|
|
|
|
def __init__(self, state):
|
|
"""
|
|
Initialize the value function approximator.
|
|
|
|
Args:
|
|
state (State, np.array, torch.Tensor): state input
|
|
"""
|
|
self.state = state
|
|
self.value = None
|
|
|
|
##############
|
|
# Properties #
|
|
##############
|
|
|
|
@property
|
|
def state(self):
|
|
"""Return the state instance."""
|
|
return self._state
|
|
|
|
@state.setter
|
|
def state(self, state):
|
|
"""Set the state input."""
|
|
if isinstance(state, (int, float)):
|
|
state = np.array([state])
|
|
elif not isinstance(state, (State, torch.Tensor, np.ndarray)):
|
|
raise TypeError("Expecting the state to be a State, torch.Tensor, or np.ndarray.")
|
|
self._state = state
|
|
|
|
###########
|
|
# Methods #
|
|
###########
|
|
|
|
def evaluate(self, *args, **kwargs):
|
|
"""Predict the value."""
|
|
pass
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
"""Predict the value."""
|
|
return self.evaluate(*args, **kwargs)
|
|
|
|
|
|
class QValueApproximator(ValueApproximator):
|
|
r"""Q-Value Approximator
|
|
|
|
Compute :math:`Q(s,a)`.
|
|
"""
|
|
__metaclass__ = ABCMeta
|
|
|
|
def __init__(self, state, action):
|
|
# super(QValueApproximator, self).__init__(state)
|
|
ValueApproximator.__init__(self, state)
|
|
self.action = action
|
|
|
|
@property
|
|
def action(self):
|
|
"""Return the action instance."""
|
|
return self._action
|
|
|
|
@action.setter
|
|
def action(self, action):
|
|
"""Set the action input."""
|
|
if isinstance(action, (int, float)):
|
|
action = np.array([action])
|
|
elif not isinstance(action, (Action, torch.Tensor, np.ndarray)):
|
|
raise TypeError("Expecting the action to be an Action, torch.Tensor, or np.ndarray.")
|
|
self._action = action
|
|
|
|
|
|
class ParametrizedValue(ValueApproximator):
|
|
r"""Parametrized (Learnable) Value Function Approximator
|
|
|
|
This value function approximator has parameters that can be optimized. By default it predicts :math:`V_{\phi}(s)`.
|
|
"""
|
|
|
|
def __init__(self, state, model):
|
|
"""
|
|
Initialize the parametrized value function approximator.
|
|
|
|
Args:
|
|
state (State): input state.
|
|
model (Approximator): value function approximator.
|
|
"""
|
|
# ValueApproximator.__init__(self, state)
|
|
super(ParametrizedValue, self).__init__(state)
|
|
self.model = model
|
|
|
|
##############
|
|
# Properties #
|
|
##############
|
|
|
|
@property
|
|
def model(self):
|
|
"""Return the model instance."""
|
|
return self._model
|
|
|
|
@model.setter
|
|
def model(self, model):
|
|
"""Set the model / approximator instance."""
|
|
if not isinstance(model, Approximator):
|
|
raise TypeError("Expecting the model to be an instance of `Approximator`, instead got: "
|
|
"{}".format(type(model)))
|
|
self._model = model
|
|
|
|
@property
|
|
def input_size(self):
|
|
"""Return the policy input size."""
|
|
return self.model.input_size
|
|
|
|
@property
|
|
def output_size(self):
|
|
"""Return the policy output size."""
|
|
return self.model.output_size
|
|
|
|
@property
|
|
def input_shape(self):
|
|
"""Return the policy input shape."""
|
|
return self.model.input_shape
|
|
|
|
@property
|
|
def output_shape(self):
|
|
"""Return the policy output shape."""
|
|
return self.model.output_shape
|
|
|
|
@property
|
|
def input_dim(self):
|
|
"""Return the input dimension of the policy; i.e. len(input_shape)."""
|
|
return self.model.input_dim
|
|
|
|
@property
|
|
def output_dim(self):
|
|
"""Return the output dimension of the policy; i.e. len(output_shape)."""
|
|
return self.model.output_dim
|
|
|
|
@property
|
|
def num_parameters(self):
|
|
"""Return the total number of parameters"""
|
|
return self.model.num_parameters
|
|
|
|
###########
|
|
# Methods #
|
|
###########
|
|
|
|
def parameters(self):
|
|
"""
|
|
Return an iterator over the learning model parameters.
|
|
"""
|
|
return self.model.parameters()
|
|
|
|
def named_parameters(self):
|
|
"""
|
|
Return an iterator over the learning model parameters; yielding both the name and the parameter itself.
|
|
"""
|
|
return self.model.named_parameters()
|
|
|
|
def list_parameters(self):
|
|
"""
|
|
Return the learning model parameters.
|
|
"""
|
|
return self.model.list_parameters()
|
|
|
|
def hyperparameters(self):
|
|
"""
|
|
Return an iterator over the learning model hyper-parameters.
|
|
"""
|
|
return self.model.hyperparameters()
|
|
|
|
def named_hyperparameters(self):
|
|
"""
|
|
Return an iterator over the learning model hyper-parameters; yielding both the name and the hyper-parameter
|
|
itself.
|
|
"""
|
|
return self.model.named_hyperparameters()
|
|
|
|
def list_hyperparameters(self):
|
|
"""
|
|
Return the learning model hyper-parameters
|
|
"""
|
|
return self.model.list_hyperparameters()
|
|
|
|
def get_vectorized_parameters(self, to_numpy=True):
|
|
"""
|
|
Get the parameters in a vectorized form.
|
|
|
|
Args:
|
|
to_numpy (bool): if True, it will convert the 1D parameter vector into a numpy array.
|
|
"""
|
|
return self.model.get_vectorized_parameters(to_numpy=to_numpy)
|
|
|
|
def set_vectorized_parameters(self, vector):
|
|
"""
|
|
Set the vectorized parameters.
|
|
|
|
Args:
|
|
np.array, torch.Tensor: 1D parameter vector.
|
|
"""
|
|
self.model.set_vectorized_parameters(vector=vector)
|
|
|
|
def evaluate(self, state=None, to_numpy=False):
|
|
"""Compute the output of the value function.
|
|
|
|
Args:
|
|
state (None, State, (list of) np.array, (list of) torch.Tensor): state input data. If None, it will get
|
|
the data from the inputs that were given at the initialization.
|
|
to_numpy (bool): If True, it will convert the data (torch.Tensors) to numpy arrays.
|
|
"""
|
|
# if no input is given, take the provided inputs at the beginning
|
|
if state is None:
|
|
state = self.state
|
|
|
|
# if the input is an instance of State, get the inner merged data.
|
|
if isinstance(state, State):
|
|
state = state.merged_data
|
|
if len(state) == 1:
|
|
state = state[0]
|
|
|
|
self.value = self.model.predict(state, to_numpy=to_numpy, return_logits=True, set_output_data=False)
|
|
return self.value
|
|
|
|
def __call__(self, state=None, to_numpy=False):
|
|
"""Predict the value."""
|
|
return self.evaluate(state=state, to_numpy=to_numpy)
|
|
|
|
|
|
# alias
|
|
Value = ParametrizedValue
|
|
|
|
|
|
class ParametrizedQValue(QValueApproximator): # ParametrizedValue, QValueApproximator):
|
|
r"""Parametrized Q-Value Function Approximator (which accepts as inputs the states and actions)
|
|
|
|
This value function approximator predicts the state-action value :math:`Q_{\phi}(s,a)`. Two different kind of
|
|
state-action value function approximators can be defined:
|
|
- where the action is given as input to the value function approximator. The action can be continuous or discrete.
|
|
- where the action is given as output to the value function approximator. The output of such model is the
|
|
state-action value for each DISCRETE action.
|
|
|
|
By default, this class implements the value function approximator :math:`Q_{\phi}(s,a)` where the states and
|
|
actions are given as inputs.
|
|
"""
|
|
|
|
def __init__(self, state, action, model):
|
|
"""
|
|
Initialize the parametrized state-action value function approximator.
|
|
|
|
Args:
|
|
state (State): input state.
|
|
action (Action): input / output action.
|
|
model (Approximator): value function approximator.
|
|
"""
|
|
# ParametrizedValue.__init__(self, state, model)
|
|
# QValueApproximator.__init__(self, state, action)
|
|
super(ParametrizedQValue, self).__init__(state, action)
|
|
self.model = model
|
|
|
|
##############
|
|
# Properties #
|
|
##############
|
|
|
|
@property
|
|
def model(self):
|
|
"""Return the model instance."""
|
|
return self._model
|
|
|
|
@model.setter
|
|
def model(self, model):
|
|
"""Set the model / approximator instance."""
|
|
if not isinstance(model, Approximator):
|
|
raise TypeError("Expecting the model to be an instance of `Approximator`, instead got: "
|
|
"{}".format(type(model)))
|
|
self._model = model
|
|
|
|
@property
|
|
def input_size(self):
|
|
"""Return the policy input size."""
|
|
return self.model.input_size
|
|
|
|
@property
|
|
def output_size(self):
|
|
"""Return the policy output size."""
|
|
return self.model.output_size
|
|
|
|
@property
|
|
def input_shape(self):
|
|
"""Return the policy input shape."""
|
|
return self.model.input_shape
|
|
|
|
@property
|
|
def output_shape(self):
|
|
"""Return the policy output shape."""
|
|
return self.model.output_shape
|
|
|
|
@property
|
|
def input_dim(self):
|
|
"""Return the input dimension of the policy; i.e. len(input_shape)."""
|
|
return self.model.input_dim
|
|
|
|
@property
|
|
def output_dim(self):
|
|
"""Return the output dimension of the policy; i.e. len(output_shape)."""
|
|
return self.model.output_dim
|
|
|
|
@property
|
|
def num_parameters(self):
|
|
"""Return the total number of parameters"""
|
|
return self.model.num_parameters
|
|
|
|
###########
|
|
# Methods #
|
|
###########
|
|
|
|
def parameters(self):
|
|
"""
|
|
Return an iterator over the learning model parameters.
|
|
"""
|
|
return self.model.parameters()
|
|
|
|
def named_parameters(self):
|
|
"""
|
|
Return an iterator over the learning model parameters; yielding both the name and the parameter itself.
|
|
"""
|
|
return self.model.named_parameters()
|
|
|
|
def list_parameters(self):
|
|
"""
|
|
Return the learning model parameters.
|
|
"""
|
|
return self.model.list_parameters()
|
|
|
|
def hyperparameters(self):
|
|
"""
|
|
Return an iterator over the learning model hyper-parameters.
|
|
"""
|
|
return self.model.hyperparameters()
|
|
|
|
def named_hyperparameters(self):
|
|
"""
|
|
Return an iterator over the learning model hyper-parameters; yielding both the name and the hyper-parameter
|
|
itself.
|
|
"""
|
|
return self.model.named_hyperparameters()
|
|
|
|
def list_hyperparameters(self):
|
|
"""
|
|
Return the learning model hyper-parameters
|
|
"""
|
|
return self.model.list_hyperparameters()
|
|
|
|
def get_vectorized_parameters(self, to_numpy=True):
|
|
"""
|
|
Get the parameters in a vectorized form.
|
|
|
|
Args:
|
|
to_numpy (bool): if True, it will convert the 1D parameter vector into a numpy array.
|
|
"""
|
|
return self.model.get_vectorized_parameters(to_numpy=to_numpy)
|
|
|
|
def set_vectorized_parameters(self, vector):
|
|
"""
|
|
Set the vectorized parameters.
|
|
|
|
Args:
|
|
np.array, torch.Tensor: 1D parameter vector.
|
|
"""
|
|
self.model.set_vectorized_parameters(vector=vector)
|
|
|
|
def evaluate(self, state=None, action=None, to_numpy=False):
|
|
"""Compute the output of the value function.
|
|
|
|
Args:
|
|
state (None, State, (list of) np.array, (list of) torch.Tensor): state input data. If None, it will get
|
|
the data from the states that were given at the initialization.
|
|
action (None, Action, (list of) np.array, (list of) torch.Tensor): input actions. If None, it will get
|
|
the data from the actions that were given at the initialization.
|
|
to_numpy (bool): If True, it will convert the data (torch.Tensors) to numpy arrays.
|
|
"""
|
|
# if no input is given, take the provided inputs at the beginning
|
|
if state is None:
|
|
state = self.state
|
|
if action is None:
|
|
action = self.action
|
|
|
|
# if the input is an instance of State, get the inner merged data.
|
|
if isinstance(state, State):
|
|
state = state.merged_data
|
|
if len(state) == 1:
|
|
state = state[0]
|
|
|
|
if isinstance(action, Action):
|
|
action = action.merged_data
|
|
if len(action) == 1:
|
|
action = action[0]
|
|
|
|
self.value = self.model.predict([state, action], to_numpy=to_numpy, return_logits=True, set_output_data=False)
|
|
return self.value
|
|
|
|
def __call__(self, state=None, action=None, to_numpy=False):
|
|
"""Predict the value."""
|
|
return self.evaluate(state=state, action=action, to_numpy=to_numpy)
|
|
|
|
|
|
# alias
|
|
QValue = ParametrizedQValue
|
|
|
|
|
|
class ParametrizedQValueOutput(ParametrizedQValue):
|
|
r"""Parametrized Q-Value Function Approximator (which accepts as inputs the states and outputs a Q-value for each
|
|
discrete action)
|
|
|
|
This value function approximator predicts the state-action value :math:`Q_{\phi}(s,a)` for each discrete action.
|
|
"""
|
|
|
|
def __init__(self, state, action, model):
|
|
"""
|
|
Initialize the parametrized state-action value function approximator.
|
|
|
|
Args:
|
|
state (State): input state.
|
|
action (Action): output DISCRETE state.
|
|
model (Approximator): value function approximator.
|
|
"""
|
|
super(ParametrizedQValueOutput, self).__init__(state, action, model)
|
|
|
|
##############
|
|
# Properties #
|
|
##############
|
|
|
|
@property
|
|
def action(self):
|
|
"""Return the action instance."""
|
|
return self._action
|
|
|
|
@action.setter
|
|
def action(self, action):
|
|
"""Set the action input."""
|
|
if isinstance(action, (int, float)):
|
|
action = np.array([action])
|
|
elif isinstance(action, Action):
|
|
if not action.is_discrete():
|
|
raise ValueError("The given actions are not discrete: {}".format(action))
|
|
elif not isinstance(action, (torch.Tensor, np.ndarray)):
|
|
raise TypeError("Expecting the action to be an int, float, Action, torch.Tensor, or np.ndarray.")
|
|
self._action = action
|
|
|
|
def evaluate(self, state=None, action=None, to_numpy=False):
|
|
"""Compute the output of the value function.
|
|
|
|
Args:
|
|
state (None, State, (list of) np.array, (list of) torch.Tensor): state input data. If None, it will get
|
|
the data from the states that were given at the initialization.
|
|
action (None): this argument is discarded.
|
|
to_numpy (bool): If True, it will convert the data (torch.Tensors) to numpy arrays.
|
|
"""
|
|
# if no input is given, take the provided inputs at the beginning
|
|
if state is None:
|
|
state = self.state
|
|
|
|
# if the input is an instance of State, get the inner merged data.
|
|
if isinstance(state, State):
|
|
state = state.merged_data
|
|
if len(state) == 1:
|
|
state = state[0]
|
|
|
|
self.value = self.model.predict(state, to_numpy=to_numpy, return_logits=True, set_output_data=False)
|
|
return self.value
|
|
|
|
def __call__(self, state=None, action=None, to_numpy=False):
|
|
"""Predict the value."""
|
|
return self.evaluate(state=state, to_numpy=to_numpy)
|
|
|
|
|
|
# alias
|
|
QValueOutput = ParametrizedQValueOutput
|