1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-06 12:30:57 +08:00
Files
qlib/qlib/rl/trainer/vessel.py
Di 5387ea5c1f Add exploration noise to rl training collector (#1481)
* Update vessel.py

Add exploration_noise=True  to training collector

* Update vessel.py

Reformat
2023-08-18 17:41:02 +08:00

219 lines
9.7 KiB
Python

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
from __future__ import annotations
import weakref
from typing import TYPE_CHECKING, Any, Callable, ContextManager, Dict, Generic, Iterable, Sequence, TypeVar, cast
import numpy as np
from tianshou.data import Collector, VectorReplayBuffer
from tianshou.env import BaseVectorEnv
from tianshou.policy import BasePolicy
from qlib.constant import INF
from qlib.log import get_module_logger
from qlib.rl.interpreter import ActionInterpreter, ActType, ObsType, PolicyActType, StateInterpreter, StateType
from qlib.rl.reward import Reward
from qlib.rl.simulator import InitialStateType, Simulator
from qlib.rl.utils import DataQueue
from qlib.rl.utils.finite_env import FiniteVectorEnv
if TYPE_CHECKING:
from .trainer import Trainer
T = TypeVar("T")
_logger = get_module_logger(__name__)
class SeedIteratorNotAvailable(BaseException):
pass
class TrainingVesselBase(Generic[InitialStateType, StateType, ActType, ObsType, PolicyActType]):
"""A ship that contains simulator, interpreter, and policy, will be sent to trainer.
This class controls algorithm-related parts of training, while trainer is responsible for runtime part.
The ship also defines the most important logic of the core training part,
and (optionally) some callbacks to insert customized logics at specific events.
"""
simulator_fn: Callable[[InitialStateType], Simulator[InitialStateType, StateType, ActType]]
state_interpreter: StateInterpreter[StateType, ObsType]
action_interpreter: ActionInterpreter[StateType, PolicyActType, ActType]
policy: BasePolicy
reward: Reward
trainer: Trainer
def assign_trainer(self, trainer: Trainer) -> None:
self.trainer = weakref.proxy(trainer) # type: ignore
def train_seed_iterator(self) -> ContextManager[Iterable[InitialStateType]] | Iterable[InitialStateType]:
"""Override this to create a seed iterator for training.
If the iterable is a context manager, the whole training will be invoked in the with-block,
and the iterator will be automatically closed after the training is done."""
raise SeedIteratorNotAvailable("Seed iterator for training is not available.")
def val_seed_iterator(self) -> ContextManager[Iterable[InitialStateType]] | Iterable[InitialStateType]:
"""Override this to create a seed iterator for validation."""
raise SeedIteratorNotAvailable("Seed iterator for validation is not available.")
def test_seed_iterator(self) -> ContextManager[Iterable[InitialStateType]] | Iterable[InitialStateType]:
"""Override this to create a seed iterator for testing."""
raise SeedIteratorNotAvailable("Seed iterator for testing is not available.")
def train(self, vector_env: BaseVectorEnv) -> Dict[str, Any]:
"""Implement this to train one iteration. In RL, one iteration usually refers to one collect."""
raise NotImplementedError()
def validate(self, vector_env: FiniteVectorEnv) -> Dict[str, Any]:
"""Implement this to validate the policy once."""
raise NotImplementedError()
def test(self, vector_env: FiniteVectorEnv) -> Dict[str, Any]:
"""Implement this to evaluate the policy on test environment once."""
raise NotImplementedError()
def log(self, name: str, value: Any) -> None:
# FIXME: this is a workaround to make the log at least show somewhere.
# Need a refactor in logger to formalize this.
if isinstance(value, (np.ndarray, list)):
value = np.mean(value)
_logger.info(f"[Iter {self.trainer.current_iter + 1}] {name} = {value}")
def log_dict(self, data: Dict[str, Any]) -> None:
for name, value in data.items():
self.log(name, value)
def state_dict(self) -> Dict:
"""Return a checkpoint of current vessel state."""
return {"policy": self.policy.state_dict()}
def load_state_dict(self, state_dict: Dict) -> None:
"""Restore a checkpoint from a previously saved state dict."""
self.policy.load_state_dict(state_dict["policy"])
class TrainingVessel(TrainingVesselBase):
"""The default implementation of training vessel.
``__init__`` accepts a sequence of initial states so that iterator can be created.
``train``, ``validate``, ``test`` each do one collect (and also update in train).
By default, the train initial states will be repeated infinitely during training,
and collector will control the number of episodes for each iteration.
In validation and testing, the val / test initial states will be used exactly once.
Extra hyper-parameters (only used in train) include:
- ``buffer_size``: Size of replay buffer.
- ``episode_per_iter``: Episodes per collect at training. Can be overridden by fast dev run.
- ``update_kwargs``: Keyword arguments appearing in ``policy.update``.
For example, ``dict(repeat=10, batch_size=64)``.
"""
def __init__(
self,
*,
simulator_fn: Callable[[InitialStateType], Simulator[InitialStateType, StateType, ActType]],
state_interpreter: StateInterpreter[StateType, ObsType],
action_interpreter: ActionInterpreter[StateType, PolicyActType, ActType],
policy: BasePolicy,
reward: Reward,
train_initial_states: Sequence[InitialStateType] | None = None,
val_initial_states: Sequence[InitialStateType] | None = None,
test_initial_states: Sequence[InitialStateType] | None = None,
buffer_size: int = 20000,
episode_per_iter: int = 1000,
update_kwargs: Dict[str, Any] = cast(Dict[str, Any], None),
):
self.simulator_fn = simulator_fn # type: ignore
self.state_interpreter = state_interpreter
self.action_interpreter = action_interpreter
self.policy = policy
self.reward = reward
self.train_initial_states = train_initial_states
self.val_initial_states = val_initial_states
self.test_initial_states = test_initial_states
self.buffer_size = buffer_size
self.episode_per_iter = episode_per_iter
self.update_kwargs = update_kwargs or {}
def train_seed_iterator(self) -> ContextManager[Iterable[InitialStateType]] | Iterable[InitialStateType]:
if self.train_initial_states is not None:
_logger.info("Training initial states collection size: %d", len(self.train_initial_states))
# Implement fast_dev_run here.
train_initial_states = self._random_subset("train", self.train_initial_states, self.trainer.fast_dev_run)
return DataQueue(train_initial_states, repeat=-1, shuffle=True)
return super().train_seed_iterator()
def val_seed_iterator(self) -> ContextManager[Iterable[InitialStateType]] | Iterable[InitialStateType]:
if self.val_initial_states is not None:
_logger.info("Validation initial states collection size: %d", len(self.val_initial_states))
val_initial_states = self._random_subset("val", self.val_initial_states, self.trainer.fast_dev_run)
return DataQueue(val_initial_states, repeat=1)
return super().val_seed_iterator()
def test_seed_iterator(self) -> ContextManager[Iterable[InitialStateType]] | Iterable[InitialStateType]:
if self.test_initial_states is not None:
_logger.info("Testing initial states collection size: %d", len(self.test_initial_states))
test_initial_states = self._random_subset("test", self.test_initial_states, self.trainer.fast_dev_run)
return DataQueue(test_initial_states, repeat=1)
return super().test_seed_iterator()
def train(self, vector_env: FiniteVectorEnv) -> Dict[str, Any]:
"""Create a collector and collects ``episode_per_iter`` episodes.
Update the policy on the collected replay buffer.
"""
self.policy.train()
with vector_env.collector_guard():
collector = Collector(
self.policy, vector_env, VectorReplayBuffer(self.buffer_size, len(vector_env)), exploration_noise=True
)
# Number of episodes collected in each training iteration can be overridden by fast dev run.
if self.trainer.fast_dev_run is not None:
episodes = self.trainer.fast_dev_run
else:
episodes = self.episode_per_iter
col_result = collector.collect(n_episode=episodes)
update_result = self.policy.update(sample_size=0, buffer=collector.buffer, **self.update_kwargs)
res = {**col_result, **update_result}
self.log_dict(res)
return res
def validate(self, vector_env: FiniteVectorEnv) -> Dict[str, Any]:
self.policy.eval()
with vector_env.collector_guard():
test_collector = Collector(self.policy, vector_env)
res = test_collector.collect(n_step=INF * len(vector_env))
self.log_dict(res)
return res
def test(self, vector_env: FiniteVectorEnv) -> Dict[str, Any]:
self.policy.eval()
with vector_env.collector_guard():
test_collector = Collector(self.policy, vector_env)
res = test_collector.collect(n_step=INF * len(vector_env))
self.log_dict(res)
return res
@staticmethod
def _random_subset(name: str, collection: Sequence[T], size: int | None) -> Sequence[T]:
if size is None:
# Size = None -> original collection
return collection
order = np.random.permutation(len(collection))
res = [collection[o] for o in order[:size]]
_logger.info(
"Fast running in development mode. Cut %s initial states from %d to %d.",
name,
len(collection),
len(res),
)
return res