Add docstrings and missing types

This commit is contained in:
Antonin RAFFIN 2020-04-23 14:56:05 +02:00
parent 271a0a7818
commit 71df3c7409
4 changed files with 58 additions and 29 deletions

View file

@ -1,10 +1,13 @@
from typing import List from typing import List, Union
import numpy as np import numpy as np
from gym import Env from gym import Env
from gym.spaces import Discrete, MultiDiscrete, MultiBinary, Box from gym.spaces import Discrete, MultiDiscrete, MultiBinary, Box
from torchy_baselines.common.type_aliases import GymStepReturn, GymObs
class IdentityEnv(Env): class IdentityEnv(Env):
def __init__(self, dim, ep_length=100): def __init__(self, dim, ep_length=100):
""" """
@ -20,30 +23,32 @@ class IdentityEnv(Env):
self.dim = dim self.dim = dim
self.reset() self.reset()
def reset(self): def reset(self) -> GymObs:
self.current_step = 0 self.current_step = 0
self._choose_next_state() self._choose_next_state()
return self.state return self.state
def step(self, action): def step(self, action: Union[int, np.ndarray]) -> GymStepReturn:
reward = self._get_reward(action) reward = self._get_reward(action)
self._choose_next_state() self._choose_next_state()
self.current_step += 1 self.current_step += 1
done = self.current_step >= self.ep_length done = self.current_step >= self.ep_length
return self.state, reward, done, {} return self.state, reward, done, {}
def _choose_next_state(self): def _choose_next_state(self) -> None:
self.state = self.action_space.sample() self.state = self.action_space.sample()
def _get_reward(self, action): def _get_reward(self, action: Union[int, np.ndarray]) -> float:
return 1 if np.all(self.state == action) else 0 return 1.0 if np.all(self.state == action) else 0.0
def render(self, mode='human'): def render(self, mode: str = 'human') -> None:
pass pass
class IdentityEnvBox(IdentityEnv): class IdentityEnvBox(IdentityEnv):
def __init__(self, low=-1, high=1, eps=0.05, ep_length=100): def __init__(self, low: float = -1.0,
high: float = 1.0, eps: float = 0.05,
ep_length: int = 100):
""" """
Identity environment for testing purposes Identity environment for testing purposes
@ -58,27 +63,27 @@ class IdentityEnvBox(IdentityEnv):
self.eps = eps self.eps = eps
self.reset() self.reset()
def reset(self): def reset(self) -> np.ndarray:
self.current_step = 0 self.current_step = 0
self._choose_next_state() self._choose_next_state()
return self.state return self.state
def step(self, action): def step(self, action: np.ndarray) -> GymStepReturn:
reward = self._get_reward(action) reward = self._get_reward(action)
self._choose_next_state() self._choose_next_state()
self.current_step += 1 self.current_step += 1
done = self.current_step >= self.ep_length done = self.current_step >= self.ep_length
return self.state, reward, done, {} return self.state, reward, done, {}
def _choose_next_state(self): def _choose_next_state(self) -> None:
self.state = self.observation_space.sample() self.state = self.observation_space.sample()
def _get_reward(self, action): def _get_reward(self, action: np.ndarray) -> float:
return 1 if (self.state - self.eps) <= action <= (self.state + self.eps) else 0 return 1.0 if (self.state - self.eps) <= action <= (self.state + self.eps) else 0.0
class IdentityEnvMultiDiscrete(IdentityEnv): class IdentityEnvMultiDiscrete(IdentityEnv):
def __init__(self, dim, ep_length=100): def __init__(self, dim: int, ep_length: int = 100):
""" """
Identity environment for testing purposes Identity environment for testing purposes
@ -92,7 +97,7 @@ class IdentityEnvMultiDiscrete(IdentityEnv):
class IdentityEnvMultiBinary(IdentityEnv): class IdentityEnvMultiBinary(IdentityEnv):
def __init__(self, dim, ep_length=100): def __init__(self, dim: int, ep_length: int = 100):
""" """
Identity environment for testing purposes Identity environment for testing purposes
@ -105,35 +110,39 @@ class IdentityEnvMultiBinary(IdentityEnv):
self.reset() self.reset()
class FakeImageEnv(Env): class FakeImageEnv(Env):
"""
Fake image environment for testing purposes, it mimics Atari games.
:param action_dim: (int) Number of discrete actions
:param screen_height: (int) Height of the image
:param screen_width: (int) Width of the image
:param n_channels: (int) Number of color channels
:param discrete: (bool)
"""
def __init__(self, action_dim: int = 6, def __init__(self, action_dim: int = 6,
screen_height: int = 210, screen_height: int = 210,
screen_width: int = 160, screen_width: int = 160,
n_channels: int = 3, n_channels: int = 3,
discrete: bool = True): discrete: bool = True):
"""
Fake atari environment for testing purposes. self.observation_space = Box(low=0, high=255, shape=(screen_height, screen_width,
""" n_channels), dtype=np.uint8)
self.observation_space = Box(low=0, high=255, shape=(screen_height, screen_width, n_channels), dtype=np.uint8)
if discrete: if discrete:
self.action_space = Discrete(action_dim) self.action_space = Discrete(action_dim)
else: else:
self.action_space = Box(low=-1, high=1, shape=(5,), dtype=np.float32) self.action_space = Box(low=-1, high=1, shape=(5,), dtype=np.float32)
self.ep_length = 10 self.ep_length = 10
def reset(self): def reset(self) -> np.ndarray:
self.current_step = 0 self.current_step = 0
return self.observation_space.sample() return self.observation_space.sample()
def step(self, action: int): def step(self, action: Union[np.ndarray, int]) -> GymStepReturn:
reward = 0.0 reward = 0.0
self.current_step += 1 self.current_step += 1
done = self.current_step >= self.ep_length done = self.current_step >= self.ep_length
return self.observation_space.sample(), reward, done, {} return self.observation_space.sample(), reward, done, {}
def render(self, mode='human'): def render(self, mode: str = 'human') -> None:
pass pass
def get_action_meanings(self) -> List[str]:
return ['NOOP']

View file

@ -59,7 +59,6 @@ class NatureCNN(BaseFeaturesExtractor):
def __init__(self, observation_space: gym.spaces.Box, def __init__(self, observation_space: gym.spaces.Box,
features_dim: int = 512): features_dim: int = 512):
super(NatureCNN, self).__init__(observation_space, features_dim) super(NatureCNN, self).__init__(observation_space, features_dim)
# TODO: custom init?
# We assume CxWxH images (channels first) # We assume CxWxH images (channels first)
# Re-ordering will be done by pre-preprocessing or wrapper # Re-ordering will be done by pre-preprocessing or wrapper
assert is_image_space(observation_space), ('You should use NatureCNN ' assert is_image_space(observation_space), ('You should use NatureCNN '

View file

@ -1,7 +1,7 @@
""" """
Common aliases for type hint Common aliases for type hint
""" """
from typing import Union, Dict, Any, NamedTuple, Optional, List, Callable from typing import Union, Dict, Any, NamedTuple, Optional, List, Callable, Tuple
import numpy as np import numpy as np
import torch as th import torch as th
@ -12,6 +12,8 @@ from torchy_baselines.common.callbacks import BaseCallback
GymEnv = Union[gym.Env, VecEnv] GymEnv = Union[gym.Env, VecEnv]
GymObs = Union[Tuple, Dict[str, Any], np.ndarray, int]
GymStepReturn = Tuple[GymObs, float, bool, Dict]
TensorDict = Dict[str, th.Tensor] TensorDict = Dict[str, th.Tensor]
OptimizerStateDict = Dict[str, Any] OptimizerStateDict = Dict[str, Any]
MaybeCallback = Union[None, Callable, List[BaseCallback], BaseCallback] MaybeCallback = Union[None, Callable, List[BaseCallback], BaseCallback]

View file

@ -1,15 +1,22 @@
import warnings import warnings
import typing
import numpy as np import numpy as np
from gym import spaces from gym import spaces
from torchy_baselines.common.vec_env.base_vec_env import VecEnv, VecEnvWrapper from torchy_baselines.common.vec_env.base_vec_env import VecEnv, VecEnvWrapper
from torchy_baselines.common.preprocessing import is_image_space from torchy_baselines.common.preprocessing import is_image_space
if typing.TYPE_CHECKING:
from torchy_baselines.common.type_aliases import GymStepReturn
class VecTransposeImage(VecEnvWrapper): class VecTransposeImage(VecEnvWrapper):
""" """
Re-order channels, from WxHxC to CxWxH. Re-order channels, from WxHxC to CxWxH.
It is required for PyTorch convolution layers.
:param venv: (VecEnv) :param venv: (VecEnv)
""" """
def __init__(self, venv: VecEnv): def __init__(self, venv: VecEnv):
@ -20,6 +27,12 @@ class VecTransposeImage(VecEnvWrapper):
@staticmethod @staticmethod
def transpose_space(observation_space: spaces.Box) -> spaces.Box: def transpose_space(observation_space: spaces.Box) -> spaces.Box:
"""
Transpose an observation space (re-order channels).
:param observation_space: (spaces.Box)
:return: (spaces.Box)
"""
assert is_image_space(observation_space), 'The observation space must be an image' assert is_image_space(observation_space), 'The observation space must be an image'
width, height, channels = observation_space.shape width, height, channels = observation_space.shape
new_shape = (channels, width, height) new_shape = (channels, width, height)
@ -27,11 +40,17 @@ class VecTransposeImage(VecEnvWrapper):
@staticmethod @staticmethod
def transpose_image(image: np.ndarray) -> np.ndarray: def transpose_image(image: np.ndarray) -> np.ndarray:
"""
Transpose an image or batch of images (re-order channels).
:param image: (np.ndarray)
:return: (np.ndarray)
"""
if len(image.shape) == 3: if len(image.shape) == 3:
return np.transpose(image, (2, 0, 1)) return np.transpose(image, (2, 0, 1))
return np.transpose(image, (0, 3, 1, 2)) return np.transpose(image, (0, 3, 1, 2))
def step_wait(self): def step_wait(self) -> 'GymStepReturn':
observations, rewards, dones, infos = self.venv.step_wait() observations, rewards, dones, infos = self.venv.step_wait()
return self.transpose_image(observations), rewards, dones, infos return self.transpose_image(observations), rewards, dones, infos