1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-17 17:34:35 +08:00

Supporting shared processor (#596)

* Supporting shared processor

* fix readonly reverse bug

* remove pytests dependency

* with fit bug

* fix parameter error
This commit is contained in:
you-n-g
2021-09-13 17:11:08 +08:00
committed by GitHub
parent 28c99c77be
commit 51709c20d8
3 changed files with 92 additions and 33 deletions

View File

@@ -295,11 +295,14 @@ class DataHandlerLP(DataHandler):
# process type # process type
PTYPE_I = "independent" PTYPE_I = "independent"
# - self._infer will be processed by infer_processors # - self._infer will be processed by shared_processors + infer_processors
# - self._learn will be processed by learn_processors # - self._learn will be processed by shared_processors + learn_processors
# NOTE:
PTYPE_A = "append" PTYPE_A = "append"
# - self._infer will be processed by infer_processors
# - self._learn will be processed by infer_processors + learn_processors # - self._infer will be processed by shared_processors + infer_processors
# - self._learn will be processed by shared_processors + infer_processors + learn_processors
# - (e.g. self._infer processed by learn_processors ) # - (e.g. self._infer processed by learn_processors )
def __init__( def __init__(
@@ -308,8 +311,9 @@ class DataHandlerLP(DataHandler):
start_time=None, start_time=None,
end_time=None, end_time=None,
data_loader: Union[dict, str, DataLoader] = None, data_loader: Union[dict, str, DataLoader] = None,
infer_processors=[], infer_processors: List = [],
learn_processors=[], learn_processors: List = [],
shared_processors: List = [],
process_type=PTYPE_A, process_type=PTYPE_A,
drop_raw=False, drop_raw=False,
**kwargs, **kwargs,
@@ -360,7 +364,8 @@ class DataHandlerLP(DataHandler):
# Setup preprocessor # Setup preprocessor
self.infer_processors = [] # for lint self.infer_processors = [] # for lint
self.learn_processors = [] # for lint self.learn_processors = [] # for lint
for pname in "infer_processors", "learn_processors": self.shared_processors = [] # for lint
for pname in "infer_processors", "learn_processors", "shared_processors":
for proc in locals()[pname]: for proc in locals()[pname]:
getattr(self, pname).append( getattr(self, pname).append(
init_instance_by_config( init_instance_by_config(
@@ -375,9 +380,12 @@ class DataHandlerLP(DataHandler):
super().__init__(instruments, start_time, end_time, data_loader, **kwargs) super().__init__(instruments, start_time, end_time, data_loader, **kwargs)
def get_all_processors(self): def get_all_processors(self):
return self.infer_processors + self.learn_processors return self.shared_processors + self.infer_processors + self.learn_processors
def fit(self): def fit(self):
"""
fit data without processing the data
"""
for proc in self.get_all_processors(): for proc in self.get_all_processors():
with TimeInspector.logt(f"{proc.__class__.__name__}"): with TimeInspector.logt(f"{proc.__class__.__name__}"):
proc.fit(self._data) proc.fit(self._data)
@@ -390,30 +398,68 @@ class DataHandlerLP(DataHandler):
""" """
self.process_data(with_fit=True) self.process_data(with_fit=True)
@staticmethod
def _run_proc_l(
df: pd.DataFrame, proc_l: List[processor_module.Processor], with_fit: bool, check_for_infer: bool
) -> pd.DataFrame:
for proc in proc_l:
if check_for_infer and not proc.is_for_infer():
raise TypeError("Only processors usable for inference can be used in `infer_processors` ")
with TimeInspector.logt(f"{proc.__class__.__name__}"):
if with_fit:
proc.fit(df)
df = proc(df)
return df
@staticmethod
def _is_proc_readonly(proc_l: List[processor_module.Processor]):
"""
NOTE: it will return True if `len(proc_l) == 0`
"""
for p in proc_l:
if not p.readonly():
return False
return True
def process_data(self, with_fit: bool = False): def process_data(self, with_fit: bool = False):
""" """
process_data data. Fun `processor.fit` if necessary process_data data. Fun `processor.fit` if necessary
Notation: (data) [processor]
# data processing flow of self.process_type == DataHandlerLP.PTYPE_I
(self._data)-[shared_processors]-(_shared_df)-[learn_processors]-(_learn_df)
\
-[infer_processors]-(_infer_df)
# data processing flow of self.process_type == DataHandlerLP.PTYPE_A
(self._data)-[shared_processors]-(_shared_df)-[infer_processors]-(_infer_df)-[learn_processors]-(_learn_df)
Parameters Parameters
---------- ----------
with_fit : bool with_fit : bool
The input of the `fit` will be the output of the previous processor The input of the `fit` will be the output of the previous processor
""" """
# data for inference # shared data processors
_infer_df = self._data # 1) assign
if len(self.infer_processors) > 0 and not self.drop_raw: # avoid modifying the original data _shared_df = self._data
_infer_df = _infer_df.copy() if not self._is_proc_readonly(self.shared_processors): # avoid modifying the original data
_shared_df = _shared_df.copy()
# 2) process
_shared_df = self._run_proc_l(_shared_df, self.shared_processors, with_fit=with_fit, check_for_infer=True)
# data for inference
# 1) assign
_infer_df = _shared_df
if not self._is_proc_readonly(self.infer_processors): # avoid modifying the original data
_infer_df = _infer_df.copy()
# 2) process
_infer_df = self._run_proc_l(_infer_df, self.infer_processors, with_fit=with_fit, check_for_infer=True)
for proc in self.infer_processors:
if not proc.is_for_infer():
raise TypeError("Only processors usable for inference can be used in `infer_processors` ")
with TimeInspector.logt(f"{proc.__class__.__name__}"):
if with_fit:
proc.fit(_infer_df)
_infer_df = proc(_infer_df)
self._infer = _infer_df self._infer = _infer_df
# data for learning # data for learning
# 1) assign
if self.process_type == DataHandlerLP.PTYPE_I: if self.process_type == DataHandlerLP.PTYPE_I:
_learn_df = self._data _learn_df = self._data
elif self.process_type == DataHandlerLP.PTYPE_A: elif self.process_type == DataHandlerLP.PTYPE_A:
@@ -421,14 +467,11 @@ class DataHandlerLP(DataHandler):
_learn_df = _infer_df _learn_df = _infer_df
else: else:
raise NotImplementedError(f"This type of input is not supported") raise NotImplementedError(f"This type of input is not supported")
if not self._is_proc_readonly(self.learn_processors): # avoid modifying the original data
if len(self.learn_processors) > 0: # avoid modifying the original data
_learn_df = _learn_df.copy() _learn_df = _learn_df.copy()
for proc in self.learn_processors: # 2) process
with TimeInspector.logt(f"{proc.__class__.__name__}"): _learn_df = self._run_proc_l(_learn_df, self.learn_processors, with_fit=with_fit, check_for_infer=False)
if with_fit:
proc.fit(_learn_df)
_learn_df = proc(_learn_df)
self._learn = _learn_df self._learn = _learn_df
if self.drop_raw: if self.drop_raw:

View File

@@ -73,6 +73,14 @@ class Processor(Serializable):
""" """
return True return True
def readonly(self) -> bool:
"""
Does the processor treat the input data readonly (i.e. does not write the input data) when processsing
Knowning the readonly information is helpful to the Handler to avoid uncessary copy
"""
return False
def config(self, **kwargs): def config(self, **kwargs):
attr_list = {"fit_start_time", "fit_end_time"} attr_list = {"fit_start_time", "fit_end_time"}
for k, v in kwargs.items(): for k, v in kwargs.items():
@@ -92,6 +100,9 @@ class DropnaProcessor(Processor):
def __call__(self, df): def __call__(self, df):
return df.dropna(subset=get_group_columns(df, self.fields_group)) return df.dropna(subset=get_group_columns(df, self.fields_group))
def readonly(self):
return True
class DropnaLabel(DropnaProcessor): class DropnaLabel(DropnaProcessor):
def __init__(self, fields_group="label"): def __init__(self, fields_group="label"):
@@ -113,6 +124,9 @@ class DropCol(Processor):
mask = df.columns.isin(self.col_list) mask = df.columns.isin(self.col_list)
return df.loc[:, ~mask] return df.loc[:, ~mask]
def readonly(self):
return True
class FilterCol(Processor): class FilterCol(Processor):
def __init__(self, fields_group="feature", col_list=[]): def __init__(self, fields_group="feature", col_list=[]):
@@ -128,6 +142,9 @@ class FilterCol(Processor):
mask = df.columns.get_level_values(-1).isin(self.col_list) mask = df.columns.get_level_values(-1).isin(self.col_list)
return df.loc[:, mask] return df.loc[:, mask]
def readonly(self):
return True
class TanhProcess(Processor): class TanhProcess(Processor):
"""Use tanh to process noise data""" """Use tanh to process noise data"""

View File

@@ -5,7 +5,6 @@
from pathlib import Path from pathlib import Path
from collections.abc import Iterable from collections.abc import Iterable
import pytest
import numpy as np import numpy as np
from qlib.tests import TestAutoData from qlib.tests import TestAutoData
@@ -33,13 +32,13 @@ class TestStorage(TestAutoData):
print(f"calendar[-1]: {calendar[-1]}") print(f"calendar[-1]: {calendar[-1]}")
calendar = CalendarStorage(freq="1min", future=False, provider_uri="not_found") calendar = CalendarStorage(freq="1min", future=False, provider_uri="not_found")
with pytest.raises(ValueError): with self.assertRaises(ValueError):
print(calendar.data) print(calendar.data)
with pytest.raises(ValueError): with self.assertRaises(ValueError):
print(calendar[:]) print(calendar[:])
with pytest.raises(ValueError): with self.assertRaises(ValueError):
print(calendar[0]) print(calendar[0])
def test_instrument_storage(self): def test_instrument_storage(self):
@@ -90,10 +89,10 @@ class TestStorage(TestAutoData):
print(f"instrument['SH600000']: {instrument['SH600000']}") print(f"instrument['SH600000']: {instrument['SH600000']}")
instrument = InstrumentStorage(market="csi300", provider_uri="not_found") instrument = InstrumentStorage(market="csi300", provider_uri="not_found")
with pytest.raises(ValueError): with self.assertRaises(ValueError):
print(instrument.data) print(instrument.data)
with pytest.raises(ValueError): with self.assertRaises(ValueError):
print(instrument["sSH600000"]) print(instrument["sSH600000"])
def test_feature_storage(self): def test_feature_storage(self):
@@ -152,7 +151,7 @@ class TestStorage(TestAutoData):
feature = FeatureStorage(instrument="SH600004", field="close", freq="day", provider_uri=self.provider_uri) feature = FeatureStorage(instrument="SH600004", field="close", freq="day", provider_uri=self.provider_uri)
with pytest.raises(IndexError): with self.assertRaises(IndexError):
print(feature[0]) print(feature[0])
assert isinstance( assert isinstance(
feature[815][1], (float, np.float32) feature[815][1], (float, np.float32)