当前位置:首页 > 科技  > 软件

协方差矩阵适应进化算法实现高效特征选择

来源: 责编: 时间:2024-07-16 16:59:30 534观看
导读在建立模型时,特征选择是一个重要环节,它指通过保留一部分特征子集来拟合模型,而舍弃其余特征。进行特征选择有多重原因:保持模型的可解释性(过多特征会增加解释难度)避免维数灾难优化与模型相关的目标函数(如R平方、AIC

在建立模型时,特征选择是一个重要环节,它指通过保留一部分特征子集来拟合模型,而舍弃其余特征。进行特征选择有多重原因:83928资讯网——每日最新资讯28at.com

  • 保持模型的可解释性(过多特征会增加解释难度)
  • 避免维数灾难
  • 优化与模型相关的目标函数(如R平方、AIC等)
  • 防止过拟合等

如果特征数量N较小,可使用穷举搜索尝试所有可能的特征组合,保留使成本/目标函数最小的那个。但当N较大时,穷举搜索就行不通了,因为需尝试的组合数为2^N,这是指数级增长,N超过几十个就变得极其耗时。83928资讯网——每日最新资讯28at.com

此时需采用启发式算法,以有效方式探索搜索空间,寻找能使目标函数最小化的特征组合。具体来说,需寻找一个长度为N的0/1向量[1,0,0,1,0,1,1,0,...],其中1表示选择该特征,0表示舍弃。目标是找到一个能最小化目标函数的这样一个向量。搜索空间的维度等于特征数量N,每一维只有0/1两种取值可能。83928资讯网——每日最新资讯28at.com

寻找一个好的启发式算法并非易事。R语言中regsubsets()函数提供了一些选项。Scikit-learn库也提供了几种启发式特征选择方法,前提是问题能很好地符合它们的技术假设。然而,要找到一种通用的、高效的启发式算法仍是一个挑战。在本系列文章中,我们将探讨几种即使在特征数量N很大、目标函数可为任意可计算函数(只要不过于缓慢)的情况下,也能给出合理结果的协方差矩阵适应进化算法方法。83928资讯网——每日最新资讯28at.com

数据集

特征选择是机器学习中一个重要的预处理步骤,它通过选择出对预测目标贡献最大的特征子集,从而提高模型的性能和简洁性。常见的特征选择方法包括Filter(过滤式)、Wrapper(包装式)和Embedded(嵌入式)等。其中,协方差矩阵适应进化算法(Covariance Matrix Adaptation Evolution Strategy, CMA-ES)是一种高效的Wrapper式特征选择算法。83928资讯网——每日最新资讯28at.com

在本文中,我们将使用著名的房价预测数据集(来自Kaggle[1] ,共213个特征,1453个样本)对CMA-ES算法的特征选择能力进行说明。我们所使用的模型是线性回归模型,目标是最小化贝叶斯信息准则(BIC),它是一种评估模型质量的指标,值越小表示模型越好。与之类似的指标还有AIC(Akaike信息准则),两者都能有效避免过拟合。当然,我们也可以使用R平方或调整R平方作为目标函数,只是需要注意此时目标是最大化而非最小化。83928资讯网——每日最新资讯28at.com

图片图片83928资讯网——每日最新资讯28at.com

数据清洗

import numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport seaborn as snsimport osimport randomimport copyfrom itertools import repeatimport shutilimport timeimport mathimport arraydf = pd.read_csv('train.csv')# drop columns with NaNdf.dropna(axis=1, inplace=True)# drop irrelevant columnsdf.drop(columns=['Id'], inplace=True)# drop major outliersdf = df[df['BsmtFinSF1'] <= 5000]df = df[df['MiscVal'] <= 5000]df = df[df['LotArea'] <= 100000]columns_non_categorical = df.select_dtypes(exclude='object').columns.to_list()columns_non_categorical.sort()columns_non_categorical.remove('SalePrice')columns_non_categorical = ['SalePrice'] + columns_non_categorical# alphabetically sort columns, keep target firstdf_temp = df[['SalePrice']]df.drop(columns=['SalePrice'], inplace=True)df.sort_index(axis=1, inplace=True)df = pd.concat([df_temp, df], axis=1)del df_temp

无论选择何种评估指标作为目标函数,CMA-ES算法都能通过迭代搜索的方式,找到一个使目标函数值最优的特征子集,从而实现自动高效的特征选择。下面将对算法的原理和应用进行详细阐述。83928资讯网——每日最新资讯28at.com

我们将尝试通过特征选择来最小化 BIC,因此这里是在启用所有特征选择之前,从 statsmodels.api.OLS() 中得到的 BIC 基准值:83928资讯网——每日最新资讯28at.com

X = df.drop(columns=['SalePrice']).copy()y = df['SalePrice'].copy()X = add_constant(X)linear_model = sm.OLS(y, X).fit()print(f'baseline BIC: {linear_model.bic}')
baseline BIC: 34570.166173470934

现在,让我们来看看一种著名的、久经考验的特征选择技术,我们将把它与后面介绍的更复杂的技术进行比较。83928资讯网——每日最新资讯28at.com

顺序特征搜索(SFS)

顺序特征搜索是一种贪婪的特征选择算法,它包含前向搜索和后向搜索两种策略。以前向搜索为例,算法流程如下:83928资讯网——每日最新资讯28at.com

  1. 首先从全部N个特征中选择一个使目标函数值最优的单特征子集。
  2. 在已选特征子集的基础上,再添加一个新特征,形成两个特征的子集,选择能使目标函数进一步最小化的那个组合。
  3. 重复步骤2,每轮迭代都只增加一个新特征,直到所有特征都被尝试加入子集。
  4. 从所有被尝试过的特征子集中,选择使目标函数值最小的那个作为最终输出。

SFS是一种贪婪算法,它每一步的选择都是基于当前最优解的局部决策,无法回头修正之前的决策。但它的搜索复杂度只有O(N^2),相比暴力搜索指数级的复杂度,计算效率大幅提高。当然,这种高效是以潜在的全局最优解被忽略为代价的。83928资讯网——每日最新资讯28at.com

SFS的后向策略则是从全量特征集合出发,每轮迭代移除一个使目标函数值改善最小的特征,直至所有特征被遍历过为止。83928资讯网——每日最新资讯28at.com

使用 mlxtend 库[2] 编写的 SFS 代码在 Python 中是什么样的:83928资讯网——每日最新资讯28at.com

import statsmodels.api as smfrom mlxtend.feature_selection import SequentialFeatureSelector as SFSfrom sklearn.base import BaseEstimatorclass DummyEstimator(BaseEstimator):    # mlxtend 希望使用 sklearn 估计器,但这里不需要(使用 statsmodels OLS 代替)。    def fit(self, X, y=None, **kwargs):        return selfdef neg_bic(m, X, y):    # objective function    lin_mod_res = sm.OLS(y, X, hascnotallow=True).fit()    return -lin_mod_res.bicseq_selector = SFS(    DummyEstimator(),    k_features=(1, X.shape[1]),    forward=True,    floating=False,    scoring=neg_bic,    cv=None,    n_jobs=-1,    verbose=0,    # make sure the intercept is not dropped    fixed_features=['const'],)n_features = X.shape[1] - 1objective_runs_sfs = round(n_features * (n_features + 1) / 2)t_start_seq = time.time()# mlxtend will mess with your dataframes if you don't .copy()seq_res = seq_selector.fit(X.copy(), y.copy())t_end_seq = time.time()run_time_seq = t_end_seq - t_start_seqseq_metrics = seq_res.get_metric_dict()

它可以快速运行各种组合,这些就是汇总结果:83928资讯网——每日最新资讯28at.com

best k:         36best objective: 33708.98602877906R2 @ best k:    0.9075677543649224objective runs: 22791total run time: 42.448 sec

在 213 个特征中,最佳特征数为 36 个。最佳 BIC 为 33708.986(特征选择前的基线值为 34570.166),在我的系统上完成这一过程用时不到 1 分钟。它调用目标函数 22.8k 次。83928资讯网——每日最新资讯28at.com

这些是最佳 BIC 值和 R 方值与所选特征数量的函数关系:83928资讯网——每日最新资讯28at.com

best_objective_seq = -np.infr2_of_best_k = 0r2_list = []best_k = 1best_features_seq = []# also extract R2 from the feature selection searchfor k in tqdm(seq_metrics.keys()):    r2_eval_mod = sm.OLS(        y, X[list(seq_metrics[k]['feature_names'])], hascnotallow=True    )    r2_eval_mod_res = r2_eval_mod.fit()    r2 = r2_eval_mod_res.rsquared    r2_list.append(r2)    score_k = seq_metrics[k]['avg_score']    if score_k > best_objective_seq:        best_objective_seq = score_k        best_k = k        best_features_seq = list(seq_metrics[k]['feature_names'])        r2_of_best_k = r2print(f'best k:         {best_k}')print(f'best objective: {-best_objective_seq}')print(f'R2 @ best k:    {r2_of_best_k}')print(f'objective runs: {objective_runs_sfs}')print(f'total run time: {run_time_seq:.3f} sec')print(f'best features:  {best_features_seq}')sfs_avg = [-seq_metrics[k]['avg_score'] for k in sorted(seq_metrics.keys())]fig, ax = plt.subplots(1, 2, figsize=(10, 4), layout='constrained')ax[0].scatter(sorted(seq_metrics.keys()), sfs_avg, s=1)ax[0].set_xticks(sorted(seq_metrics.keys()), minor=True)ax[0].set_title('BIC')ax[0].set_xlabel('number of features')ax[0].set_ylabel('BIC')ax[1].scatter(sorted(seq_metrics.keys()), r2_list, s=1)ax[1].set_title('R2')ax[1].set_xlabel('number of features')ax[1].set_ylabel('R2')fig.suptitle('sequential forward search')fig.savefig('sfs-performance.png')fig.show()
best k:         36best objective: 33708.98602877906R2 @ best k:    0.9075677543649224objective runs: 22791total run time: 42.448 secbest features:  ['const', 'BedroomAbvGr', 'BldgType_1Fam', 'BsmtFinSF1', 'Condition1_Norm', 'Condition2_PosN', 'ExterQual_Ex', 'Exterior1st_BrkFace', 'Functional_Sev', 'Functional_Typ', 'GarageArea', 'GrLivArea', 'HeatingQC_Ex', 'KitchenQual_Ex', 'LandContour_HLS', 'LotArea', 'LotConfig_CulDSac', 'LowQualFinSF', 'MSSubClass', 'Neighborhood_BrkSide', 'Neighborhood_Crawfor', 'Neighborhood_NWAmes', 'Neighborhood_NoRidge', 'Neighborhood_NridgHt', 'Neighborhood_Somerst', 'Neighborhood_StoneBr', 'OverallCond', 'OverallQual', 'PoolArea', 'RoofMatl_WdShngl', 'SaleCondition_Normal', 'SaleType_New', 'ScreenPorch', 'TotalBsmtSF', 'WoodDeckSF', 'YearBuilt']

BIC and R-squared for SFSBIC and R-squared for SFS83928资讯网——每日最新资讯28at.com

SFS 的 BIC 和 R 平方83928资讯网——每日最新资讯28at.com

有关实际选择的特征名称:83928资讯网——每日最新资讯28at.com

'const', 'BedroomAbvGr', 'BldgType_1Fam', 'BsmtFinSF1', 'Condition1_Norm', 'Condition2_PosN', 'ExterQual_Ex', 'Exterior1st_BrkFace', 'Functional_Sev', 'Functional_Typ', 'GarageArea', 'GrLivArea', 'HeatingQC_Ex', 'KitchenQual_Ex', 'LandContour_HLS', 'LotArea', 'LotConfig_CulDSac', 'LowQualFinSF', 'MSSubClass', 'Neighborhood_BrkSide', 'Neighborhood_Crawfor', 'Neighborhood_NWAmes', 'Neighborhood_NoRidge', 'Neighborhood_NridgHt', 'Neighborhood_Somerst', 'Neighborhood_StoneBr', 'OverallCond', 'OverallQual', 'PoolArea', 'RoofMatl_WdShngl', 'SaleCondition_Normal', 'SaleType_New', 'ScreenPorch', 'TotalBsmtSF', 'WoodDeckSF', 'YearBuilt'

协方差矩阵适应演化策略(CMA-ES)

CMA-ES是一种高效的无约束/约束黑箱连续优化的随机搜索算法。它属于进化计算的一种,但与传统的遗传算法有着明显区别。83928资讯网——每日最新资讯28at.com

与遗传算法直接对解个体进行变异和交叉操作不同,CMA-ES在连续域上对多元正态分布模型的参数(均值和协方差矩阵)进行更新迭代,间接实现对潜在解集群的适应性搜索。83928资讯网——每日最新资讯28at.com

算法不需要目标函数的梯度信息,即无需计算导数,因此具有很强的鲁棒性,可应用于非线性、非凸、多峰、多模态等各种复杂优化问题。同时,CMA-ES通过自适应策略有效利用样本信息,在保证全局性的同时提高了收敛速度。83928资讯网——每日最新资讯28at.com

CMA-ES已被广泛应用于机器学习、计算机视觉、计算生物学等诸多领域,并成为优选的优化算法之一。在Optuna、PyGMO等知名数值优化库中都有CMA-ES的实现版本。由于算法理论较为复杂,这里只是简要介绍,可参考文末的扩展阅读材料进一步学习。83928资讯网——每日最新资讯28at.com

考虑二维 Rastrigin 函数:83928资讯网——每日最新资讯28at.com

Rastrigin 函数Rastrigin 函数83928资讯网——每日最新资讯28at.com

下面的热图显示了该函数的值--颜色越亮表示值越高。该函数的全局最小值位于原点(0,0),但其中有许多局部极值。我们需要通过 CMA-ES 找出全局最小值。83928资讯网——每日最新资讯28at.com

Rastrigin 函数热图Rastrigin 函数热图83928资讯网——每日最新资讯28at.com

CMA-ES利用多元正态分布作为基础。它根据该分布在搜索空间中生成测试点。用户需要猜测分布的原始均值和标准偏差,然后算法会反复调整这些参数,并在搜索空间中扫描分布,以寻找最佳的目标函数值。以下是测试点的初始分布:83928资讯网——每日最新资讯28at.com

CMA-ES 分布CMA-ES 分布83928资讯网——每日最新资讯28at.com

xi 表示算法在搜索空间中产生的每一步的点集。lambda 是产生的点数。该分布的平均值在每一步中都会更新,最终希望收敛到真正的解决方案。sigma 是分布的标准偏差,即测试点的分布。C 是协方差矩阵,它定义了分布的形状。根据 C 的值,分布可能是“圆形”,也可能是拉长的椭圆形。改变 C 可以让CMA-ES进入搜索空间的某些区域,或者避开其他区域。83928资讯网——每日最新资讯28at.com

第一代测试点第一代测试点83928资讯网——每日最新资讯28at.com

在图中创建了一个包含6个点的群体,这是优化器选择的默认群体大小。这是第一步。接下来,算法需要:83928资讯网——每日最新资讯28at.com

  • 测算每个点的目标函数(Rastrigin)
  • 根据从目标函数中获得的知识,更新均值、标准差和协方差矩阵,有效地创建一个新的多元正态分布
  • 使用新的分布产生一组新的测试点
  • 重复这个过程,直到达到某个标准(要么收敛到某个平均值,要么超过最大步数等)。

仅仅更新分布的平均值是非常简单的。工作原理如下:计算每个测试点的目标函数后,给这些点分配权重,目标值较高的点权重较大,然后根据它们的位置计算出加权和,这就是新的平均值。实际上,CMA-ES(协方差矩阵自适应演化策略)将分布均值向目标值较好的点移动。83928资讯网——每日最新资讯28at.com

更新 CMA-ES 分布均值更新 CMA-ES 分布均值83928资讯网——每日最新资讯28at.com

如果算法达到真实解决方案,分布的平均值将趋于该解决方案。协方差矩阵将导致分布的形状发生变化(圆形或椭圆形),这取决于目标函数的地理位置,会向有利的区域扩展,而回避不利的区域。83928资讯网——每日最新资讯28at.com

用于 CMA-ES 特征选择

二维Rastrigin函数相对简单,因为它只有2个维度。然而,对于我们的特征选择问题,我们面临N=213个维度,并且空间并不是连续的。每个测试点都是一个N维向量,分量的值为"{0, 1}"。换句话说,每个测试点看起来像这样:1,0,0,1,1,0,......一个二进制向量。除此之外,问题是相同的:我们需要找到使目标函数(即OLS模型的BIC参数)最小化的点或向量。83928资讯网——每日最新资讯28at.com

对于具有 213 个维度和离散取值(0 或 1)的特征选择问题,您可以考虑使用遗传算法或者模拟退火算法来寻找最优解。这些算法对于高维度和离散空间的优化问题非常有效。83928资讯网——每日最新资讯28at.com

  • 遗传算法是一种启发式搜索算法,通过模拟生物进化的过程来搜索最优解。它适用于高维度问题和离散取值空间。
  • 模拟退火算法则是一种随机搜索算法,通过模拟固体退火过程中的原子运动来搜索最优解。它可以通过适当设置的温度参数来在离散空间中进行搜索。

可以将特征选择问题建模为一个多维的优化问题,然后使用上述算法来寻找最小化目标函数的解。这些算法可以帮助你在高维度和离散空间中寻找较优的特征子集。83928资讯网——每日最新资讯28at.com

下面是使用 cmaes 库[3] 进行特征选择的 CMA-ES 代码的简单版本:83928资讯网——每日最新资讯28at.com

def cma_objective(fs):    features_use = ['const'] + [        f for i, f in enumerate(features_select) if fs[i,] == 1    ]    lin_mod = sm.OLS(y_cmaes, X_cmaes[features_use], hascnotallow=True).fit()    return lin_mod.bicX_cmaes = X.copy()y_cmaes = y.copy()features_select = [f for f in X_cmaes.columns if f != 'const']dim = len(features_select)bounds = np.tile([0, 1], (dim, 1))steps = np.ones(dim)optimizer = CMAwM(    mean=np.full(dim, 0.5),    sigma=1 / 6,    bounds=bounds,    steps=steps,    n_max_resampling=10 * dim,    seed=0,)max_gen = 100best_objective_cmaes_small = np.infbest_sol_raw_cmaes_small = Nonefor gen in tqdm(range(max_gen)):    solutions = []    for _ in range(optimizer.population_size):        x_for_eval, x_for_tell = optimizer.ask()        value = cma_objective(x_for_eval)        solutions.append((x_for_tell, value))        if value < best_objective_cmaes_small:            best_objective_cmaes_small = value            best_sol_raw_cmaes_small = x_for_eval    optimizer.tell(solutions)best_features_cmaes_small = [    features_select[i]    for i, val in enumerate(best_sol_raw_cmaes_small.tolist())    if val == 1.0]print(f'best objective: {best_objective_cmaes_small}')print(f'best features:  {best_features_cmaes_small}')

CMA-ES 优化器的定义涉及对均值和 sigma(标准偏差)进行一些初始猜测。然后,优化器会循环运行多代,创建测试点 x_for_eval ,并根据目标评估其,然后修改分布(均值、sigma、协方差矩阵)等。每个x_for_eval点都是一个二进制向量[1, 1, 1, 0, 0, 1, ...],用于从数据集中选择特征。83928资讯网——每日最新资讯28at.com

请注意,这里使用的是 CMAwM() 优化器(带边际的 CMA),而不是默认的 CMA()。默认优化器在处理常规连续问题时效果很好,但这里的搜索空间是高维的,只允许两个离散值(0 和 1)。默认优化器就会卡在这个空间里。CMAwM()扩大了一点搜索空间(而它返回的解仍然是二进制向量),这似乎足以解除它的阻碍。83928资讯网——每日最新资讯28at.com

这段简单的代码确实有效,但还远未达到最佳状态。下面是一个更复杂、更优化的版本,它能更快地找到更好的解。83928资讯网——每日最新资讯28at.com

def cma_objective(fs):    features_use = ['const'] + [        f for i, f in enumerate(features_select) if fs[i,] == 1    ]    lin_mod = sm.OLS(y_cmaes, X_cmaes[features_use], hascnotallow=True).fit()    return lin_mod.bic# copy the original dataframes into local copies, onceX_cmaes = X.copy()y_cmaes = y.copy()rs = np.random.RandomState(seed=0)features_select = [f for f in X_cmaes.columns if f != 'const']n_features = len(features_select)cma_bounds = np.tile([0, 1], (n_features, 1))cma_steps = np.ones(n_features)n_max_resampling = 10 * n_featuresmean = np.full(n_features, 0.5)sigma = 1 / 6pop_size = 4 + math.floor(3 * math.log(n_features))margin = 1 / (n_features * pop_size)optimizer = CMAwM(    mean=mean,    sigma=sigma,    bounds=cma_bounds,    steps=cma_steps,    n_max_resampling=n_max_resampling,    seed=0,    population_size=pop_size,    margin=margin,)gen_max = 1000best_objective_cmaes = np.infbest_generation_cmaes = 0best_sol_raw_cmaes = Nonehistory_values_cmaes = np.full((gen_max,), np.nan)history_values_best_cmaes = np.full((gen_max,), np.nan)time_to_best_cmaes = np.infobjective_runs_cmaes = 0solutions_avg = np.full((gen_max, n_features), np.nan)n_jobs = os.cpu_count()iterator = tqdm(range(gen_max), desc='generation')t_start_cmaes = time.time()for generation in iterator:    best_value_gen = np.inf    # solutions fed back to the optimizer    solutions_float = []    # binary-truncated solutions - the yes/no answers we're looking for    solutions_binary = np.full((pop_size, n_features), np.nan)    vals = np.full((pop_size,), np.nan)    for i in range(pop_size):        # re-seeding the RNG is very important        # otherwise CMA-ES gets stuck in local extremes        seed = rs.randint(1, 2**16) + generation        optimizer._rng.seed(seed)        fs_for_eval, fs_for_tell = optimizer.ask()        solutions_binary[i,] = fs_for_eval        value = cma_objective(fs_for_eval)        objective_runs_cmaes += 1        vals[i] = value        solutions_float.append((fs_for_tell, value))    optimizer.tell(solutions_float)    solutions_avg[generation,] = solutions_binary.mean(axis=0)    best_value_gen = vals.min()    t_end_loop_cmaes = time.time()    if best_value_gen < best_objective_cmaes:        best_objective_cmaes = best_value_gen        best_generation_cmaes = generation        best_sol_raw_cmaes = solutions_binary[np.argmin(vals),]        time_to_best_cmaes = t_end_loop_cmaes - t_start_cmaes        print(            f'gen: {best_generation_cmaes:5n}, new best objective: {best_objective_cmaes:.4f}'        )    history_values_cmaes[generation] = best_value_gen    history_values_best_cmaes[generation] = best_objective_cmaes    if optimizer.should_stop():        print(f'Optimizer decided to stop.')        iterator.close()        break    if os.path.isfile('break'):        # to gracefully break the loop, manually create a file called 'break'        print(f'Found break file, stopping now.')        iterator.close()        breakgen_completed_cmaes = generationbest_features_cmaes = [    features_select[i]    for i, val in enumerate(best_sol_raw_cmaes.tolist())    if val == 1.0]print(f'best objective:  {best_objective_cmaes}')print(f'best generation: {best_generation_cmaes}')print(f'objective runs:  {objective_runs_cmaes}')print(f'time to best:    {time_to_best_cmaes:.3f} sec')print(f'best features:   {best_features_cmaes}')cma_mv_df = pd.DataFrame(solutions_avg, columns=features_select).iloc[    : gen_completed_cmaes + 1]if gen_completed_cmaes > 20:    x_ticks = list(        range(0, gen_completed_cmaes, round(gen_completed_cmaes / 20))    )else:    x_ticks = list(range(0, gen_completed_cmaes))if x_ticks[-1] != gen_completed_cmaes:    x_ticks.append(gen_completed_cmaes)fig, ax = plt.subplots(    2,    1,    sharex=True,    height_ratios=[20, 1],    figsize=(12, 45),    layout='constrained',)sns.heatmap(    cma_mv_df.T,    vmin=0.0,    vmax=1.0,    cmap='viridis',    cbar=True,    cbar_kws={'fraction': 0.01, 'anchor': (0.0, 1.0)},    ax=ax[0],)ax[0].axvline(x=best_generation_cmaes, color='C7')ax[0].tick_params(axis='both', reset=True)ax[0].set_xticks(x_ticks)ax[0].set_xticklabels(x_ticks)ax[0].set_title('Population average of feature selector values')ax[0].set_xlabel('generation')ax[1].scatter(    range(gen_completed_cmaes + 1),    history_values_cmaes[: gen_completed_cmaes + 1],    s=1,    label='current value',)ax[1].plot(    range(gen_completed_cmaes + 1),    history_values_best_cmaes[: gen_completed_cmaes + 1],    color='C1',    label='best value',)ax[1].vlines(    x=best_generation_cmaes,    ymin=history_values_cmaes[: gen_completed_cmaes + 1].min(),    ymax=history_values_cmaes[: gen_completed_cmaes + 1].max(),    colors='C7',)ax[1].tick_params(axis='both', reset=True)ax[1].legend()ax[1].set_title('Objective value')ax[1].set_xlabel('generation')fig.suptitle('CMA-ES')fig.savefig('cmaes-performance.png')fig.show()
best objective:  33703.070530508514best generation: 921objective runs:  20000time to best:    48.326 secbest features:   ['BedroomAbvGr', 'BldgType_1Fam', 'BsmtFinSF1', 'Condition1_Norm', 'Condition2_PosN', 'ExterQual_Ex', 'Exterior1st_BrkFace', 'Functional_Sev', 'Functional_Typ', 'GarageCars', 'GrLivArea', 'HeatingQC_Ex', 'KitchenQual_Ex', 'LandContour_HLS', 'LotArea', 'LowQualFinSF', 'Neighborhood_BrkSide', 'Neighborhood_Crawfor', 'Neighborhood_NWAmes', 'Neighborhood_NoRidge', 'Neighborhood_NridgHt', 'Neighborhood_Somerst', 'Neighborhood_StoneBr', 'OverallCond', 'OverallQual', 'PoolArea', 'RoofMatl_WdShngl', 'SaleCondition_Normal', 'SaleType_New', 'ScreenPorch', 'TotalBsmtSF', 'WoodDeckSF', 'YearBuilt']

下图显示了复杂、优化的 CMA-ES 代码搜索最佳解决方案的历史。热图显示了每一代中每种功能的流行程度(更亮 = 更流行)。可以看到,有些特征总是非常流行,而另一些则很快过时,还有一些则是后来才 “发现 ”的。根据这个问题的参数,优化器选择的群体大小为 20 个点(个体),因此特征流行度是这 20 个点的平均值。83928资讯网——每日最新资讯28at.com

上下滑动查看更多CMA-ES 优化历史

83928资讯网——每日最新资讯28at.com

这些是经过优化的 CMA-ES 代码的主要统计信息:83928资讯网——每日最新资讯28at.com

best objective:  33703.070530508514best generation: 921objective runs:  20000time to best:    48.326 sec

与 SFS 相比,它能找到更好(更小)的目标值,调用目标函数的次数更少(20k),所用时间也差不多。从所有指标来看,这都是比 SFS 的净收益。83928资讯网——每日最新资讯28at.com

同样,特征选择前的基准 BIC 为83928资讯网——每日最新资讯28at.com

baseline BIC: 34570.166173470934

顺便提一句:在研究了传统优化算法(遗传算法、模拟退火等)之后,CMA-ES 给我带来了惊喜。它几乎没有超参数,计算量小,只需要少量个体(点)来探索搜索空间,而且性能相当不错。如果你需要解决优化问题,值得把它放在你的工具箱里。83928资讯网——每日最新资讯28at.com

参考资料

[1]Kaggle: https://www.kaggle.com/c/house-prices-advanced-regression-techniques/data83928资讯网——每日最新资讯28at.com

[2]mlxtend 库: https://rasbt.github.io/mlxtend/83928资讯网——每日最新资讯28at.com

[3]cmaes 库: https://github.com/CyberAgentAILab/cmaes83928资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-101117-0.html协方差矩阵适应进化算法实现高效特征选择

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 注意力机制中三种掩码技术详解和Pytorch实现

下一篇: 微前端代码隔离方案,手把手实现一个 JS 沙箱隔离!

标签:
  • 热门焦点
Top