1
0
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:
zhupr
2021-05-27 14:18:17 +08:00
parent 114162693f
commit 0a4e241608
8 changed files with 419 additions and 265 deletions

View 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)

View File

@@ -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()

View File

@@ -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)

View File

@@ -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(

View File

@@ -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(

View File

@@ -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)

View File

View 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
)