mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-15 08:46:56 +08:00
add get_feature_importance to model interpret
This commit is contained in:
81
examples/model_interpreter.py
Normal file
81
examples/model_interpreter.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Copyright (c) Microsoft Corporation.
|
||||||
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
|
|
||||||
|
import qlib
|
||||||
|
from qlib.config import REG_CN
|
||||||
|
|
||||||
|
from qlib.utils import exists_qlib_data, init_instance_by_config
|
||||||
|
from qlib.tests.data import GetData
|
||||||
|
|
||||||
|
market = "csi300"
|
||||||
|
benchmark = "SH000300"
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# config
|
||||||
|
###################################
|
||||||
|
data_handler_config = {
|
||||||
|
"start_time": "2008-01-01",
|
||||||
|
"end_time": "2020-08-01",
|
||||||
|
"fit_start_time": "2008-01-01",
|
||||||
|
"fit_end_time": "2014-12-31",
|
||||||
|
"instruments": market,
|
||||||
|
}
|
||||||
|
|
||||||
|
task = {
|
||||||
|
"model": {
|
||||||
|
"class": "LGBModel",
|
||||||
|
"module_path": "qlib.contrib.model.gbdt",
|
||||||
|
"kwargs": {
|
||||||
|
"loss": "mse",
|
||||||
|
"colsample_bytree": 0.8879,
|
||||||
|
"learning_rate": 0.0421,
|
||||||
|
"subsample": 0.8789,
|
||||||
|
"lambda_l1": 205.6999,
|
||||||
|
"lambda_l2": 580.9768,
|
||||||
|
"max_depth": 8,
|
||||||
|
"num_leaves": 210,
|
||||||
|
"num_threads": 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"dataset": {
|
||||||
|
"class": "DatasetH",
|
||||||
|
"module_path": "qlib.data.dataset",
|
||||||
|
"kwargs": {
|
||||||
|
"handler": {
|
||||||
|
"class": "Alpha158",
|
||||||
|
"module_path": "qlib.contrib.data.handler",
|
||||||
|
"kwargs": data_handler_config,
|
||||||
|
},
|
||||||
|
"segments": {
|
||||||
|
"train": ("2008-01-01", "2014-12-31"),
|
||||||
|
"valid": ("2015-01-01", "2016-12-31"),
|
||||||
|
"test": ("2017-01-01", "2020-08-01"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
# use default data
|
||||||
|
provider_uri = "~/.qlib/qlib_data/cn_data" # target_dir
|
||||||
|
if not exists_qlib_data(provider_uri):
|
||||||
|
print(f"Qlib data is not found in {provider_uri}")
|
||||||
|
GetData().qlib_data(target_dir=provider_uri, region=REG_CN)
|
||||||
|
|
||||||
|
qlib.init(provider_uri=provider_uri, region=REG_CN)
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# train model
|
||||||
|
###################################
|
||||||
|
# model initialization
|
||||||
|
model = init_instance_by_config(task["model"])
|
||||||
|
dataset = init_instance_by_config(task["dataset"])
|
||||||
|
model.fit(dataset)
|
||||||
|
|
||||||
|
# get model feature importance
|
||||||
|
feature_importance = model.get_feature_importance()
|
||||||
|
print("feature importance:")
|
||||||
|
print(feature_importance)
|
||||||
@@ -10,9 +10,10 @@ from catboost.utils import get_gpu_device_count
|
|||||||
from ...model.base import Model
|
from ...model.base import Model
|
||||||
from ...data.dataset import DatasetH
|
from ...data.dataset import DatasetH
|
||||||
from ...data.dataset.handler import DataHandlerLP
|
from ...data.dataset.handler import DataHandlerLP
|
||||||
|
from ...model.interpret.base import FeatureInt
|
||||||
|
|
||||||
|
|
||||||
class CatBoostModel(Model):
|
class CatBoostModel(Model, FeatureInt):
|
||||||
"""CatBoost Model"""
|
"""CatBoost Model"""
|
||||||
|
|
||||||
def __init__(self, loss="RMSE", **kwargs):
|
def __init__(self, loss="RMSE", **kwargs):
|
||||||
@@ -69,6 +70,18 @@ class CatBoostModel(Model):
|
|||||||
x_test = dataset.prepare(segment, col_set="feature", data_key=DataHandlerLP.DK_I)
|
x_test = dataset.prepare(segment, col_set="feature", data_key=DataHandlerLP.DK_I)
|
||||||
return pd.Series(self.model.predict(x_test.values), index=x_test.index)
|
return pd.Series(self.model.predict(x_test.values), index=x_test.index)
|
||||||
|
|
||||||
|
def get_feature_importance(self, *args, **kwargs) -> pd.Series:
|
||||||
|
"""get feature importance
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
parameters references:
|
||||||
|
https://catboost.ai/docs/concepts/python-reference_catboost_get_feature_importance.html#python-reference_catboost_get_feature_importance
|
||||||
|
"""
|
||||||
|
return pd.Series(
|
||||||
|
data=self.model.get_feature_importance(*args, **kwargs), index=self.model.feature_names_
|
||||||
|
).sort_values(ascending=False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
cat = CatBoostModel()
|
cat = CatBoostModel()
|
||||||
|
|||||||
@@ -1,251 +1,265 @@
|
|||||||
# Copyright (c) Microsoft Corporation.
|
# Copyright (c) Microsoft Corporation.
|
||||||
# Licensed under the MIT License.
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
import lightgbm as lgb
|
import lightgbm as lgb
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from typing import Text, Union
|
from typing import Text, Union
|
||||||
from ...model.base import Model
|
from ...model.base import Model
|
||||||
from ...data.dataset import DatasetH
|
from ...data.dataset import DatasetH
|
||||||
from ...data.dataset.handler import DataHandlerLP
|
from ...data.dataset.handler import DataHandlerLP
|
||||||
from ...log import get_module_logger
|
from ...model.interpret.base import FeatureInt
|
||||||
|
from ...log import get_module_logger
|
||||||
|
|
||||||
class DEnsembleModel(Model):
|
|
||||||
"""Double Ensemble Model"""
|
class DEnsembleModel(Model, FeatureInt):
|
||||||
|
"""Double Ensemble Model"""
|
||||||
def __init__(
|
|
||||||
self,
|
def __init__(
|
||||||
base_model="gbm",
|
self,
|
||||||
loss="mse",
|
base_model="gbm",
|
||||||
num_models=6,
|
loss="mse",
|
||||||
enable_sr=True,
|
num_models=6,
|
||||||
enable_fs=True,
|
enable_sr=True,
|
||||||
alpha1=1.0,
|
enable_fs=True,
|
||||||
alpha2=1.0,
|
alpha1=1.0,
|
||||||
bins_sr=10,
|
alpha2=1.0,
|
||||||
bins_fs=5,
|
bins_sr=10,
|
||||||
decay=None,
|
bins_fs=5,
|
||||||
sample_ratios=None,
|
decay=None,
|
||||||
sub_weights=None,
|
sample_ratios=None,
|
||||||
epochs=100,
|
sub_weights=None,
|
||||||
**kwargs
|
epochs=100,
|
||||||
):
|
**kwargs
|
||||||
self.base_model = base_model # "gbm" or "mlp", specifically, we use lgbm for "gbm"
|
):
|
||||||
self.num_models = num_models # the number of sub-models
|
self.base_model = base_model # "gbm" or "mlp", specifically, we use lgbm for "gbm"
|
||||||
self.enable_sr = enable_sr
|
self.num_models = num_models # the number of sub-models
|
||||||
self.enable_fs = enable_fs
|
self.enable_sr = enable_sr
|
||||||
self.alpha1 = alpha1
|
self.enable_fs = enable_fs
|
||||||
self.alpha2 = alpha2
|
self.alpha1 = alpha1
|
||||||
self.bins_sr = bins_sr
|
self.alpha2 = alpha2
|
||||||
self.bins_fs = bins_fs
|
self.bins_sr = bins_sr
|
||||||
self.decay = decay
|
self.bins_fs = bins_fs
|
||||||
if sample_ratios is None: # the default values for sample_ratios
|
self.decay = decay
|
||||||
sample_ratios = [0.8, 0.7, 0.6, 0.5, 0.4]
|
if sample_ratios is None: # the default values for sample_ratios
|
||||||
if sub_weights is None: # the default values for sub_weights
|
sample_ratios = [0.8, 0.7, 0.6, 0.5, 0.4]
|
||||||
sub_weights = [1.0, 0.2, 0.2, 0.2, 0.2, 0.2]
|
if sub_weights is None: # the default values for sub_weights
|
||||||
if not len(sample_ratios) == bins_fs:
|
sub_weights = [1.0, 0.2, 0.2, 0.2, 0.2, 0.2]
|
||||||
raise ValueError("The length of sample_ratios should be equal to bins_fs.")
|
if not len(sample_ratios) == bins_fs:
|
||||||
self.sample_ratios = sample_ratios
|
raise ValueError("The length of sample_ratios should be equal to bins_fs.")
|
||||||
if not len(sub_weights) == num_models:
|
self.sample_ratios = sample_ratios
|
||||||
raise ValueError("The length of sub_weights should be equal to num_models.")
|
if not len(sub_weights) == num_models:
|
||||||
self.sub_weights = sub_weights
|
raise ValueError("The length of sub_weights should be equal to num_models.")
|
||||||
self.epochs = epochs
|
self.sub_weights = sub_weights
|
||||||
self.logger = get_module_logger("DEnsembleModel")
|
self.epochs = epochs
|
||||||
self.logger.info("Double Ensemble Model...")
|
self.logger = get_module_logger("DEnsembleModel")
|
||||||
self.ensemble = [] # the current ensemble model, a list contains all the sub-models
|
self.logger.info("Double Ensemble Model...")
|
||||||
self.sub_features = [] # the features for each sub model in the form of pandas.Index
|
self.ensemble = [] # the current ensemble model, a list contains all the sub-models
|
||||||
self.params = {"objective": loss}
|
self.sub_features = [] # the features for each sub model in the form of pandas.Index
|
||||||
self.params.update(kwargs)
|
self.params = {"objective": loss}
|
||||||
self.loss = loss
|
self.params.update(kwargs)
|
||||||
|
self.loss = loss
|
||||||
def fit(self, dataset: DatasetH):
|
|
||||||
df_train, df_valid = dataset.prepare(
|
def fit(self, dataset: DatasetH):
|
||||||
["train", "valid"], col_set=["feature", "label"], data_key=DataHandlerLP.DK_L
|
df_train, df_valid = dataset.prepare(
|
||||||
)
|
["train", "valid"], col_set=["feature", "label"], data_key=DataHandlerLP.DK_L
|
||||||
x_train, y_train = df_train["feature"], df_train["label"]
|
)
|
||||||
# initialize the sample weights
|
x_train, y_train = df_train["feature"], df_train["label"]
|
||||||
N, F = x_train.shape
|
# initialize the sample weights
|
||||||
weights = pd.Series(np.ones(N, dtype=float))
|
N, F = x_train.shape
|
||||||
# initialize the features
|
weights = pd.Series(np.ones(N, dtype=float))
|
||||||
features = x_train.columns
|
# initialize the features
|
||||||
pred_sub = pd.DataFrame(np.zeros((N, self.num_models), dtype=float), index=x_train.index)
|
features = x_train.columns
|
||||||
# train sub-models
|
pred_sub = pd.DataFrame(np.zeros((N, self.num_models), dtype=float), index=x_train.index)
|
||||||
for k in range(self.num_models):
|
# train sub-models
|
||||||
self.sub_features.append(features)
|
for k in range(self.num_models):
|
||||||
self.logger.info("Training sub-model: ({}/{})".format(k + 1, self.num_models))
|
self.sub_features.append(features)
|
||||||
model_k = self.train_submodel(df_train, df_valid, weights, features)
|
self.logger.info("Training sub-model: ({}/{})".format(k + 1, self.num_models))
|
||||||
self.ensemble.append(model_k)
|
model_k = self.train_submodel(df_train, df_valid, weights, features)
|
||||||
# no further sample re-weight and feature selection needed for the last sub-model
|
self.ensemble.append(model_k)
|
||||||
if k + 1 == self.num_models:
|
# no further sample re-weight and feature selection needed for the last sub-model
|
||||||
break
|
if k + 1 == self.num_models:
|
||||||
|
break
|
||||||
self.logger.info("Retrieving loss curve and loss values...")
|
|
||||||
loss_curve = self.retrieve_loss_curve(model_k, df_train, features)
|
self.logger.info("Retrieving loss curve and loss values...")
|
||||||
pred_k = self.predict_sub(model_k, df_train, features)
|
loss_curve = self.retrieve_loss_curve(model_k, df_train, features)
|
||||||
pred_sub.iloc[:, k] = pred_k
|
pred_k = self.predict_sub(model_k, df_train, features)
|
||||||
pred_ensemble = pred_sub.iloc[:, : k + 1].mean(axis=1)
|
pred_sub.iloc[:, k] = pred_k
|
||||||
loss_values = pd.Series(self.get_loss(y_train.values.squeeze(), pred_ensemble.values))
|
pred_ensemble = pred_sub.iloc[:, : k + 1].mean(axis=1)
|
||||||
|
loss_values = pd.Series(self.get_loss(y_train.values.squeeze(), pred_ensemble.values))
|
||||||
if self.enable_sr:
|
|
||||||
self.logger.info("Sample re-weighting...")
|
if self.enable_sr:
|
||||||
weights = self.sample_reweight(loss_curve, loss_values, k + 1)
|
self.logger.info("Sample re-weighting...")
|
||||||
|
weights = self.sample_reweight(loss_curve, loss_values, k + 1)
|
||||||
if self.enable_fs:
|
|
||||||
self.logger.info("Feature selection...")
|
if self.enable_fs:
|
||||||
features = self.feature_selection(df_train, loss_values)
|
self.logger.info("Feature selection...")
|
||||||
|
features = self.feature_selection(df_train, loss_values)
|
||||||
def train_submodel(self, df_train, df_valid, weights, features):
|
|
||||||
dtrain, dvalid = self._prepare_data_gbm(df_train, df_valid, weights, features)
|
def train_submodel(self, df_train, df_valid, weights, features):
|
||||||
evals_result = dict()
|
dtrain, dvalid = self._prepare_data_gbm(df_train, df_valid, weights, features)
|
||||||
model = lgb.train(
|
evals_result = dict()
|
||||||
self.params,
|
model = lgb.train(
|
||||||
dtrain,
|
self.params,
|
||||||
num_boost_round=self.epochs,
|
dtrain,
|
||||||
valid_sets=[dtrain, dvalid],
|
num_boost_round=self.epochs,
|
||||||
valid_names=["train", "valid"],
|
valid_sets=[dtrain, dvalid],
|
||||||
verbose_eval=20,
|
valid_names=["train", "valid"],
|
||||||
evals_result=evals_result,
|
verbose_eval=20,
|
||||||
)
|
evals_result=evals_result,
|
||||||
evals_result["train"] = list(evals_result["train"].values())[0]
|
)
|
||||||
evals_result["valid"] = list(evals_result["valid"].values())[0]
|
evals_result["train"] = list(evals_result["train"].values())[0]
|
||||||
return model
|
evals_result["valid"] = list(evals_result["valid"].values())[0]
|
||||||
|
return model
|
||||||
def _prepare_data_gbm(self, df_train, df_valid, weights, features):
|
|
||||||
x_train, y_train = df_train["feature"].loc[:, features], df_train["label"]
|
def _prepare_data_gbm(self, df_train, df_valid, weights, features):
|
||||||
x_valid, y_valid = df_valid["feature"].loc[:, features], df_valid["label"]
|
x_train, y_train = df_train["feature"].loc[:, features], df_train["label"]
|
||||||
|
x_valid, y_valid = df_valid["feature"].loc[:, features], df_valid["label"]
|
||||||
# Lightgbm need 1D array as its label
|
|
||||||
if y_train.values.ndim == 2 and y_train.values.shape[1] == 1:
|
# Lightgbm need 1D array as its label
|
||||||
y_train, y_valid = np.squeeze(y_train.values), np.squeeze(y_valid.values)
|
if y_train.values.ndim == 2 and y_train.values.shape[1] == 1:
|
||||||
else:
|
y_train, y_valid = np.squeeze(y_train.values), np.squeeze(y_valid.values)
|
||||||
raise ValueError("LightGBM doesn't support multi-label training")
|
else:
|
||||||
|
raise ValueError("LightGBM doesn't support multi-label training")
|
||||||
dtrain = lgb.Dataset(x_train.values, label=y_train, weight=weights)
|
|
||||||
dvalid = lgb.Dataset(x_valid.values, label=y_valid)
|
dtrain = lgb.Dataset(x_train, label=y_train, weight=weights)
|
||||||
return dtrain, dvalid
|
dvalid = lgb.Dataset(x_valid, label=y_valid)
|
||||||
|
return dtrain, dvalid
|
||||||
def sample_reweight(self, loss_curve, loss_values, k_th):
|
|
||||||
"""
|
def sample_reweight(self, loss_curve, loss_values, k_th):
|
||||||
the SR module of Double Ensemble
|
"""
|
||||||
:param loss_curve: the shape is NxT
|
the SR module of Double Ensemble
|
||||||
the loss curve for the previous sub-model, where the element (i, t) if the error on the i-th sample
|
:param loss_curve: the shape is NxT
|
||||||
after the t-th iteration in the training of the previous sub-model.
|
the loss curve for the previous sub-model, where the element (i, t) if the error on the i-th sample
|
||||||
:param loss_values: the shape is N
|
after the t-th iteration in the training of the previous sub-model.
|
||||||
the loss of the current ensemble on the i-th sample.
|
:param loss_values: the shape is N
|
||||||
:param k_th: the index of the current sub-model, starting from 1
|
the loss of the current ensemble on the i-th sample.
|
||||||
:return: weights
|
:param k_th: the index of the current sub-model, starting from 1
|
||||||
the weights for all the samples.
|
:return: weights
|
||||||
"""
|
the weights for all the samples.
|
||||||
# normalize loss_curve and loss_values with ranking
|
"""
|
||||||
loss_curve_norm = loss_curve.rank(axis=0, pct=True)
|
# normalize loss_curve and loss_values with ranking
|
||||||
loss_values_norm = (-loss_values).rank(pct=True)
|
loss_curve_norm = loss_curve.rank(axis=0, pct=True)
|
||||||
|
loss_values_norm = (-loss_values).rank(pct=True)
|
||||||
# calculate l_start and l_end from loss_curve
|
|
||||||
N, T = loss_curve.shape
|
# calculate l_start and l_end from loss_curve
|
||||||
part = np.maximum(int(T * 0.1), 1)
|
N, T = loss_curve.shape
|
||||||
l_start = loss_curve_norm.iloc[:, :part].mean(axis=1)
|
part = np.maximum(int(T * 0.1), 1)
|
||||||
l_end = loss_curve_norm.iloc[:, -part:].mean(axis=1)
|
l_start = loss_curve_norm.iloc[:, :part].mean(axis=1)
|
||||||
|
l_end = loss_curve_norm.iloc[:, -part:].mean(axis=1)
|
||||||
# calculate h-value for each sample
|
|
||||||
h1 = loss_values_norm
|
# calculate h-value for each sample
|
||||||
h2 = (l_end / l_start).rank(pct=True)
|
h1 = loss_values_norm
|
||||||
h = pd.DataFrame({"h_value": self.alpha1 * h1 + self.alpha2 * h2})
|
h2 = (l_end / l_start).rank(pct=True)
|
||||||
|
h = pd.DataFrame({"h_value": self.alpha1 * h1 + self.alpha2 * h2})
|
||||||
# calculate weights
|
|
||||||
h["bins"] = pd.cut(h["h_value"], self.bins_sr)
|
# calculate weights
|
||||||
h_avg = h.groupby("bins")["h_value"].mean()
|
h["bins"] = pd.cut(h["h_value"], self.bins_sr)
|
||||||
weights = pd.Series(np.zeros(N, dtype=float))
|
h_avg = h.groupby("bins")["h_value"].mean()
|
||||||
for i_b, b in enumerate(h_avg.index):
|
weights = pd.Series(np.zeros(N, dtype=float))
|
||||||
weights[h["bins"] == b] = 1.0 / (self.decay ** k_th * h_avg[i_b] + 0.1)
|
for i_b, b in enumerate(h_avg.index):
|
||||||
return weights
|
weights[h["bins"] == b] = 1.0 / (self.decay ** k_th * h_avg[i_b] + 0.1)
|
||||||
|
return weights
|
||||||
def feature_selection(self, df_train, loss_values):
|
|
||||||
"""
|
def feature_selection(self, df_train, loss_values):
|
||||||
the FS module of Double Ensemble
|
"""
|
||||||
:param df_train: the shape is NxF
|
the FS module of Double Ensemble
|
||||||
:param loss_values: the shape is N
|
:param df_train: the shape is NxF
|
||||||
the loss of the current ensemble on the i-th sample.
|
:param loss_values: the shape is N
|
||||||
:return: res_feat: in the form of pandas.Index
|
the loss of the current ensemble on the i-th sample.
|
||||||
|
:return: res_feat: in the form of pandas.Index
|
||||||
"""
|
|
||||||
x_train, y_train = df_train["feature"], df_train["label"]
|
"""
|
||||||
features = x_train.columns
|
x_train, y_train = df_train["feature"], df_train["label"]
|
||||||
N, F = x_train.shape
|
features = x_train.columns
|
||||||
g = pd.DataFrame({"g_value": np.zeros(F, dtype=float)})
|
N, F = x_train.shape
|
||||||
M = len(self.ensemble)
|
g = pd.DataFrame({"g_value": np.zeros(F, dtype=float)})
|
||||||
|
M = len(self.ensemble)
|
||||||
# shuffle specific columns and calculate g-value for each feature
|
|
||||||
x_train_tmp = x_train.copy()
|
# shuffle specific columns and calculate g-value for each feature
|
||||||
for i_f, feat in enumerate(features):
|
x_train_tmp = x_train.copy()
|
||||||
x_train_tmp.loc[:, feat] = np.random.permutation(x_train_tmp.loc[:, feat].values)
|
for i_f, feat in enumerate(features):
|
||||||
pred = pd.Series(np.zeros(N), index=x_train_tmp.index)
|
x_train_tmp.loc[:, feat] = np.random.permutation(x_train_tmp.loc[:, feat].values)
|
||||||
for i_s, submodel in enumerate(self.ensemble):
|
pred = pd.Series(np.zeros(N), index=x_train_tmp.index)
|
||||||
pred += (
|
for i_s, submodel in enumerate(self.ensemble):
|
||||||
pd.Series(
|
pred += (
|
||||||
submodel.predict(x_train_tmp.loc[:, self.sub_features[i_s]].values), index=x_train_tmp.index
|
pd.Series(
|
||||||
)
|
submodel.predict(x_train_tmp.loc[:, self.sub_features[i_s]].values), index=x_train_tmp.index
|
||||||
/ M
|
)
|
||||||
)
|
/ M
|
||||||
loss_feat = self.get_loss(y_train.values.squeeze(), pred.values)
|
)
|
||||||
g.loc[i_f, "g_value"] = np.mean(loss_feat - loss_values) / (np.std(loss_feat - loss_values) + 1e-7)
|
loss_feat = self.get_loss(y_train.values.squeeze(), pred.values)
|
||||||
x_train_tmp.loc[:, feat] = x_train.loc[:, feat].copy()
|
g.loc[i_f, "g_value"] = np.mean(loss_feat - loss_values) / (np.std(loss_feat - loss_values) + 1e-7)
|
||||||
|
x_train_tmp.loc[:, feat] = x_train.loc[:, feat].copy()
|
||||||
# one column in train features is all-nan # if g['g_value'].isna().any()
|
|
||||||
g["g_value"].replace(np.nan, 0, inplace=True)
|
# one column in train features is all-nan # if g['g_value'].isna().any()
|
||||||
|
g["g_value"].replace(np.nan, 0, inplace=True)
|
||||||
# divide features into bins_fs bins
|
|
||||||
g["bins"] = pd.cut(g["g_value"], self.bins_fs)
|
# divide features into bins_fs bins
|
||||||
|
g["bins"] = pd.cut(g["g_value"], self.bins_fs)
|
||||||
# randomly sample features from bins to construct the new features
|
|
||||||
res_feat = []
|
# randomly sample features from bins to construct the new features
|
||||||
sorted_bins = sorted(g["bins"].unique(), reverse=True)
|
res_feat = []
|
||||||
for i_b, b in enumerate(sorted_bins):
|
sorted_bins = sorted(g["bins"].unique(), reverse=True)
|
||||||
b_feat = features[g["bins"] == b]
|
for i_b, b in enumerate(sorted_bins):
|
||||||
num_feat = int(np.ceil(self.sample_ratios[i_b] * len(b_feat)))
|
b_feat = features[g["bins"] == b]
|
||||||
res_feat = res_feat + np.random.choice(b_feat, size=num_feat).tolist()
|
num_feat = int(np.ceil(self.sample_ratios[i_b] * len(b_feat)))
|
||||||
return pd.Index(res_feat)
|
res_feat = res_feat + np.random.choice(b_feat, size=num_feat, replace=False).tolist()
|
||||||
|
return pd.Index(set(res_feat))
|
||||||
def get_loss(self, label, pred):
|
|
||||||
if self.loss == "mse":
|
def get_loss(self, label, pred):
|
||||||
return (label - pred) ** 2
|
if self.loss == "mse":
|
||||||
else:
|
return (label - pred) ** 2
|
||||||
raise ValueError("not implemented yet")
|
else:
|
||||||
|
raise ValueError("not implemented yet")
|
||||||
def retrieve_loss_curve(self, model, df_train, features):
|
|
||||||
if self.base_model == "gbm":
|
def retrieve_loss_curve(self, model, df_train, features):
|
||||||
num_trees = model.num_trees()
|
if self.base_model == "gbm":
|
||||||
x_train, y_train = df_train["feature"].loc[:, features], df_train["label"]
|
num_trees = model.num_trees()
|
||||||
# Lightgbm need 1D array as its label
|
x_train, y_train = df_train["feature"].loc[:, features], df_train["label"]
|
||||||
if y_train.values.ndim == 2 and y_train.values.shape[1] == 1:
|
# Lightgbm need 1D array as its label
|
||||||
y_train = np.squeeze(y_train.values)
|
if y_train.values.ndim == 2 and y_train.values.shape[1] == 1:
|
||||||
else:
|
y_train = np.squeeze(y_train.values)
|
||||||
raise ValueError("LightGBM doesn't support multi-label training")
|
else:
|
||||||
|
raise ValueError("LightGBM doesn't support multi-label training")
|
||||||
N = x_train.shape[0]
|
|
||||||
loss_curve = pd.DataFrame(np.zeros((N, num_trees)))
|
N = x_train.shape[0]
|
||||||
pred_tree = np.zeros(N, dtype=float)
|
loss_curve = pd.DataFrame(np.zeros((N, num_trees)))
|
||||||
for i_tree in range(num_trees):
|
pred_tree = np.zeros(N, dtype=float)
|
||||||
pred_tree += model.predict(x_train.values, start_iteration=i_tree, num_iteration=1)
|
for i_tree in range(num_trees):
|
||||||
loss_curve.iloc[:, i_tree] = self.get_loss(y_train, pred_tree)
|
pred_tree += model.predict(x_train.values, start_iteration=i_tree, num_iteration=1)
|
||||||
else:
|
loss_curve.iloc[:, i_tree] = self.get_loss(y_train, pred_tree)
|
||||||
raise ValueError("not implemented yet")
|
else:
|
||||||
return loss_curve
|
raise ValueError("not implemented yet")
|
||||||
|
return loss_curve
|
||||||
def predict(self, dataset: DatasetH, segment: Union[Text, slice] = "test"):
|
|
||||||
if self.ensemble is None:
|
def predict(self, dataset: DatasetH, segment: Union[Text, slice] = "test"):
|
||||||
raise ValueError("model is not fitted yet!")
|
if self.ensemble is None:
|
||||||
x_test = dataset.prepare(segment, col_set="feature", data_key=DataHandlerLP.DK_I)
|
raise ValueError("model is not fitted yet!")
|
||||||
pred = pd.Series(np.zeros(x_test.shape[0]), index=x_test.index)
|
x_test = dataset.prepare(segment, col_set="feature", data_key=DataHandlerLP.DK_I)
|
||||||
for i_sub, submodel in enumerate(self.ensemble):
|
pred = pd.Series(np.zeros(x_test.shape[0]), index=x_test.index)
|
||||||
feat_sub = self.sub_features[i_sub]
|
for i_sub, submodel in enumerate(self.ensemble):
|
||||||
pred += (
|
feat_sub = self.sub_features[i_sub]
|
||||||
pd.Series(submodel.predict(x_test.loc[:, feat_sub].values), index=x_test.index)
|
pred += (
|
||||||
* self.sub_weights[i_sub]
|
pd.Series(submodel.predict(x_test.loc[:, feat_sub].values), index=x_test.index)
|
||||||
)
|
* self.sub_weights[i_sub]
|
||||||
return pred
|
)
|
||||||
|
return pred
|
||||||
def predict_sub(self, submodel, df_data, features):
|
|
||||||
x_data, y_data = df_data["feature"].loc[:, features], df_data["label"]
|
def predict_sub(self, submodel, df_data, features):
|
||||||
pred_sub = pd.Series(submodel.predict(x_data.values), index=x_data.index)
|
x_data, y_data = df_data["feature"].loc[:, features], df_data["label"]
|
||||||
return pred_sub
|
pred_sub = pd.Series(submodel.predict(x_data.values), index=x_data.index)
|
||||||
|
return pred_sub
|
||||||
|
|
||||||
|
def get_feature_importance(self, *args, **kwargs) -> pd.Series:
|
||||||
|
"""get feature importance
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
parameters reference:
|
||||||
|
https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.Booster.html?highlight=feature_importance#lightgbm.Booster.feature_importance
|
||||||
|
"""
|
||||||
|
res = []
|
||||||
|
for _model, _weight in zip(self.ensemble, self.sub_weights):
|
||||||
|
res.append(pd.Series(_model.feature_importance(*args, **kwargs), index=_model.feature_name()) * _weight)
|
||||||
|
return pd.concat(res, axis=1, sort=False).sum(axis=1).sort_values(ascending=False)
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ from typing import Text, Union
|
|||||||
from ...model.base import ModelFT
|
from ...model.base import ModelFT
|
||||||
from ...data.dataset import DatasetH
|
from ...data.dataset import DatasetH
|
||||||
from ...data.dataset.handler import DataHandlerLP
|
from ...data.dataset.handler import DataHandlerLP
|
||||||
|
from ...model.interpret.base import LightGBMFInt
|
||||||
|
|
||||||
|
|
||||||
class LGBModel(ModelFT):
|
class LGBModel(ModelFT, LightGBMFInt):
|
||||||
"""LightGBM Model"""
|
"""LightGBM Model"""
|
||||||
|
|
||||||
def __init__(self, loss="mse", **kwargs):
|
def __init__(self, loss="mse", **kwargs):
|
||||||
@@ -33,8 +34,8 @@ class LGBModel(ModelFT):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("LightGBM doesn't support multi-label training")
|
raise ValueError("LightGBM doesn't support multi-label training")
|
||||||
|
|
||||||
dtrain = lgb.Dataset(x_train.values, label=y_train)
|
dtrain = lgb.Dataset(x_train, label=y_train)
|
||||||
dvalid = lgb.Dataset(x_valid.values, label=y_valid)
|
dvalid = lgb.Dataset(x_valid, label=y_valid)
|
||||||
return dtrain, dvalid
|
return dtrain, dvalid
|
||||||
|
|
||||||
def fit(
|
def fit(
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
# Copyright (c) Microsoft Corporation.
|
# Copyright (c) Microsoft Corporation.
|
||||||
# Licensed under the MIT License.
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
|
import warnings
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import lightgbm as lgb
|
import lightgbm as lgb
|
||||||
|
|
||||||
from qlib.model.base import ModelFT
|
from ...model.base import ModelFT
|
||||||
from qlib.data.dataset import DatasetH
|
from ...data.dataset import DatasetH
|
||||||
from qlib.data.dataset.handler import DataHandlerLP
|
from ...data.dataset.handler import DataHandlerLP
|
||||||
import warnings
|
from ...model.interpret.base import LightGBMFInt
|
||||||
|
|
||||||
|
|
||||||
class HFLGBModel(ModelFT):
|
class HFLGBModel(ModelFT, LightGBMFInt):
|
||||||
"""LightGBM Model for high frequency prediction"""
|
"""LightGBM Model for high frequency prediction"""
|
||||||
|
|
||||||
def __init__(self, loss="mse", **kwargs):
|
def __init__(self, loss="mse", **kwargs):
|
||||||
@@ -97,8 +98,8 @@ class HFLGBModel(ModelFT):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("LightGBM doesn't support multi-label training")
|
raise ValueError("LightGBM doesn't support multi-label training")
|
||||||
|
|
||||||
dtrain = lgb.Dataset(x_train.values, label=y_train)
|
dtrain = lgb.Dataset(x_train, label=y_train)
|
||||||
dvalid = lgb.Dataset(x_valid.values, label=y_valid)
|
dvalid = lgb.Dataset(x_valid, label=y_valid)
|
||||||
return dtrain, dvalid
|
return dtrain, dvalid
|
||||||
|
|
||||||
def fit(
|
def fit(
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ from typing import Text, Union
|
|||||||
from ...model.base import Model
|
from ...model.base import Model
|
||||||
from ...data.dataset import DatasetH
|
from ...data.dataset import DatasetH
|
||||||
from ...data.dataset.handler import DataHandlerLP
|
from ...data.dataset.handler import DataHandlerLP
|
||||||
|
from ...model.interpret.base import FeatureInt
|
||||||
|
|
||||||
|
|
||||||
class XGBModel(Model):
|
class XGBModel(Model, FeatureInt):
|
||||||
"""XGBModel Model"""
|
"""XGBModel Model"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@@ -42,8 +43,8 @@ class XGBModel(Model):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("XGBoost doesn't support multi-label training")
|
raise ValueError("XGBoost doesn't support multi-label training")
|
||||||
|
|
||||||
dtrain = xgb.DMatrix(x_train.values, label=y_train_1d)
|
dtrain = xgb.DMatrix(x_train, label=y_train_1d)
|
||||||
dvalid = xgb.DMatrix(x_valid.values, label=y_valid_1d)
|
dvalid = xgb.DMatrix(x_valid, label=y_valid_1d)
|
||||||
self.model = xgb.train(
|
self.model = xgb.train(
|
||||||
self._params,
|
self._params,
|
||||||
dtrain=dtrain,
|
dtrain=dtrain,
|
||||||
@@ -62,3 +63,13 @@ class XGBModel(Model):
|
|||||||
raise ValueError("model is not fitted yet!")
|
raise ValueError("model is not fitted yet!")
|
||||||
x_test = dataset.prepare(segment, col_set="feature", data_key=DataHandlerLP.DK_I)
|
x_test = dataset.prepare(segment, col_set="feature", data_key=DataHandlerLP.DK_I)
|
||||||
return pd.Series(self.model.predict(xgb.DMatrix(x_test.values)), index=x_test.index)
|
return pd.Series(self.model.predict(xgb.DMatrix(x_test.values)), index=x_test.index)
|
||||||
|
|
||||||
|
def get_feature_importance(self, *args, **kwargs) -> pd.Series:
|
||||||
|
"""get feature importance
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-------
|
||||||
|
parameters reference:
|
||||||
|
https://xgboost.readthedocs.io/en/latest/python/python_api.html#xgboost.Booster.get_score
|
||||||
|
"""
|
||||||
|
return pd.Series(self.model.get_score(*args, **kwargs)).sort_values(ascending=False)
|
||||||
|
|||||||
0
qlib/model/interpret/__init__.py
Normal file
0
qlib/model/interpret/__init__.py
Normal file
33
qlib/model/interpret/base.py
Normal file
33
qlib/model/interpret/base.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Copyright (c) Microsoft Corporation.
|
||||||
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Interfaces to interpret models
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class FeatureInt:
|
||||||
|
"""Feature (Int)erpreter"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_feature_importance(self) -> pd.Series:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class LightGBMFInt(FeatureInt):
|
||||||
|
"""LightGBM (F)eature (Int)erpreter"""
|
||||||
|
|
||||||
|
def get_feature_importance(self, *args, **kwargs) -> pd.Series:
|
||||||
|
"""get feature importance
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
parameters reference:
|
||||||
|
https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.Booster.html?highlight=feature_importance#lightgbm.Booster.feature_importance
|
||||||
|
"""
|
||||||
|
return pd.Series(self.model.feature_importance(*args, **kwargs), index=self.model.feature_name()).sort_values(
|
||||||
|
ascending=False
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user