超参数调优#
在 skfolio 中进行超参数调优遵循与 scikit-learn 相同的API。
超参数是指在估计器中未直接学习的参数。它们作为参数传递给估计器类的构造函数。
搜索超参数空间以找到最佳交叉验证得分是可能的并且是推荐的。
在构造估算器时提供的任何参数都可以以这种方式进行优化。具体而言,要查找给定估算器的所有参数的名称和当前值,请使用:
estimator.get_params()
搜索由以下内容组成:
在scikit-learn中提供了两种通用的参数搜索方法:对于给定的值,GridSearchCV 详尽地考虑所有参数组合,而 RandomizedSearchCV 可以从具有指定分布的参数空间中随机抽取一定数量的候选者。
在描述这些工具之后,我们详细介绍了适用于这些方法的 最佳实践。
详尽的网格搜索#
通过 GridSearchCV 提供的网格搜索,彻底生成指定的参数值网格中的候选项,使用 param_grid 参数。例如,以下 param_grid:
param_grid = [
{'l1_coef': [0.001, 0.01, 0.1], 'risk_measure': [RiskMeasure.SEMI_VARIANCE]},
{'l1_coef': [0.001, 0.01, 0.1], 'l2_coef': [0.01, 0.1, 1], 'risk_measure': [RiskMeasure.CVAR]},
]
指定应该探索两个网格:一个使用半方差风险度量,l1_coef 值在 [0.001, 0.01, 0.1] 范围内,第二个使用 CVaR 风险度量,以及 l1_coef 值范围在 [0.001, 0.01, 0.1] 和 l2_coef 值在 [0.01, 0.1, 1] 的交叉乘积。
该 GridSearchCV 实例实现了通常的估计器 API:当在数据集上“拟合”时,将评估所有可能的参数值组合,并保留最佳组合。
示例:
from sklearn.model_selection import GridSearchCV, KFold, train_test_split
from skfolio import RiskMeasure
from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import MeanRisk
from skfolio.preprocessing import prices_to_returns
prices = load_sp500_dataset()
X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)
param_grid = [
{'l1_coef': [0.001, 0.01, 0.1], 'risk_measure': [RiskMeasure.SEMI_VARIANCE]},
{'l1_coef': [0.001, 0.01, 0.1], 'l2_coef': [0.01, 0.1, 1], 'risk_measure': [RiskMeasure.CVAR]},
]
grid_search = GridSearchCV(
estimator=MeanRisk(min_weights=-1),
cv=KFold(),
param_grid=param_grid,
n_jobs=-1 # using all cores
)
grid_search.fit(X_train)
print(grid_search.cv_results_)
best_model = grid_search.best_estimator_
print(best_model.weights_)
随机参数优化#
虽然使用参数设置的网格方法是目前最广泛使用的参数优化方法,但其他搜索方法具有更有利的特性。RandomizedSearchCV 实现了对参数的随机搜索, 每个设置都是从可能参数值的分布中采样。这比全面搜索有两个主要优点:
预算可以独立于参数的数量和可能的值进行选择。
添加不影响性能的参数不会降低效率。
指定如何对参数进行采样是使用字典完成的,这与为 GridSearchCV 指定参数非常相似。此外,计算预算,即采样候选者的数量或采样迭代次数,是通过 n_iter 参数指定的。对于每个参数,可以指定一个可能值的分布或一组离散选择(将均匀采样)。
原则上,任何提供 rvs(随机变量样本)方法以获取值的函数都可以传递。对 rvs 函数的调用应该在连续调用中提供可能参数值的独立随机样本。
该 scipy.stats 模块包含许多用于抽样参数的有用分布,例如 expon、gamma、uniform、loguniform 或 randint。
对于连续参数,例如 l1_coef,指定连续分布以充分利用随机化是很重要的。这样,增加 n_iter 将总是导致更细致的搜索。
连续的对数均匀随机变量是对数间隔参数的连续版本。例如,指定上面的 l2_coef 的等效项时,可以使用 loguniform(0.01, 1) 来代替 [0.01, 0.1, 1]。
与上面的网格搜索示例相对应,我们可以指定一个在 0.01 和 1 之间对数均匀分布的连续随机变量:
import scipy.stats as stats
{'l1_coef': stats.loguniform(0.01, 1), 'risk_measure': [RiskMeasure.SEMI_VARIANCE]}
示例:
import scipy.stats as stats
from sklearn.model_selection import KFold, RandomizedSearchCV, train_test_split
from skfolio import RiskMeasure
from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import MeanRisk
from skfolio.preprocessing import prices_to_returns
prices = load_sp500_dataset()
X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)
param_dist = {'l2_coef': stats.loguniform(0.01, 1), 'risk_measure': [RiskMeasure.CVAR]}
rd_search = RandomizedSearchCV(
estimator=MeanRisk(min_weights=-1),
cv=KFold(),
n_iter=10,
param_distributions=param_dist,
n_jobs=-1 # using all cores
)
rd_search.fit(X_train)
print(rd_search.cv_results_)
best_model = rd_search.best_estimator_
print(best_model.weights_)
参数搜索提示#
指定一个目标指标#
默认情况下,所有的投资组合优化估计器具有相同的评分函数,即夏普比率。这个评分函数可以通过使用其他测量或编写自己的评分函数来使用make_scorer进行自定义。
示例:
在下面的示例中,使用了Sortino Ratio而不是默认的Sharpe Ratio:
from sklearn.model_selection import GridSearchCV, KFold, train_test_split
from skfolio import RatioMeasure
from skfolio.datasets import load_sp500_dataset
from skfolio.metrics import make_scorer
from skfolio.optimization import MeanRisk
from skfolio.preprocessing import prices_to_returns
prices = load_sp500_dataset()
X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)
scoring = make_scorer(RatioMeasure.SORTINO_RATIO)
grid_search = GridSearchCV(
estimator=MeanRisk(min_weights=-1),
cv=KFold(),
param_grid={'l2_coef': [0.0001, 0.001, 0.01, 1]},
scoring=scoring
)
grid_search.fit(X_train)
print(grid_search.cv_results_)
best_model = grid_search.best_estimator_
print(best_model.weights_)
pred = best_model.predict(X_test)
print(pred.sortino_ratio)
示例:
在此示例中,我们使用自定义评分函数:
def custom_score(pred):
return pred.mean - 2 * pred.variance - 3 * pred.semi_variance
scoring = make_scorer(custom_score)
复合估计器和参数空间#
GridSearchCV 和 RandomizedSearchCV 允许使用专门的
语法搜索复合或嵌套估计器的参数。
示例:
在下面的例子中,我们搜索嵌套估计器 EWMu 的最佳参数 alpha:
from sklearn.model_selection import GridSearchCV, KFold, train_test_split
from skfolio.datasets import load_sp500_dataset
from skfolio.moments import EWMu
from skfolio.optimization import MeanRisk, ObjectiveFunction
from skfolio.preprocessing import prices_to_returns
from skfolio.prior import EmpiricalPrior
prices = load_sp500_dataset()
X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)
model = MeanRisk(
objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
prior_estimator=EmpiricalPrior(mu_estimator=EWMu(alpha=0.2)),
)
print(model.get_params(deep=True))
param_grid = {"prior_estimator__mu_estimator__alpha": [0.001, 0.01, 0.01, 0.1]}
grid_search = GridSearchCV(
estimator=model,
cv=KFold(),
param_grid=param_grid,
)
grid_search.fit(X_train)
print(grid_search.best_estimator_)
示例:
相同的逻辑适用于 Pipeline。在这里,我们搜索的是
MeanRisk 的最佳风险度量,它是 Pipeline 的一部分:
from sklearn.model_selection import GridSearchCV, KFold, train_test_split
from sklearn.pipeline import Pipeline
from skfolio import RiskMeasure
from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import MeanRisk
from skfolio.pre_selection import SelectKExtremes
from skfolio.preprocessing import prices_to_returns
prices = load_sp500_dataset()
X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)
model = Pipeline(
[
("pre_selection", SelectKExtremes(k=10, highest=True)),
("optimization", MeanRisk()),
]
)
param_grid = {
"optimization__risk_measure": [RiskMeasure.SEMI_VARIANCE, RiskMeasure.CVAR]
}
grid_search = GridSearchCV(
estimator=model,
cv=KFold(),
param_grid=param_grid,
)
grid_search.fit(X_train)
print(grid_search.best_estimator_)
并行性#
参数搜索工具独立评估每个数据折叠上的每个参数组合。可以通过使用关键字n_jobs=-1并行运行计算。有关更多详情,请参见函数签名。