【特征选择】CMA-ES(协方差矩阵适应进化策略)

news2024/11/26 12:43:10

导 读

当将模型拟合到数据集时,可能需要执行特征选择:由于多种原因,仅保留某些特征子集来拟合模型,而丢弃其余特征具有一定的必要性,如下:

  • 保持模型的可解释性(特征太多会使解释变得更加困难)

  • 避免维度过大

  • 最大化/最小化与模型相关的一些目标函数(R 平方、AIC 等)

  • 以避免不合适等。

有需要的朋友关注公众号【小Z的科研日常】,获取更多内容

01、协方差矩阵适应进化策略

如果特征数量 N 很小,则可以进行详尽的搜索:可以逐个尝试所有可能的特征组合,并只保留使成本/目标函数最小化的组合。

但如果 N 很大,那么详尽的搜索可能是不可能的。2^N中,如果 N 大于几十,则要尝试的组合种类使计算资源受限(它是一个指数函数)。

在这种情况下,启发式方算法便是很好的解决方案:以有效的方式探索搜索空间,寻找能够最小化用于执行搜索的目标函数的特征组合。

假设我们正在寻找的是长度为 N 的向量[1, 0, 0, 1, 0, 1, 1, 1, 0, ...],其中元素取值于{0, 1}。向量中的每个元素被分配给一个特征。

如果元素为1,则选择该特征;如果元素为0,则丢弃该特征。我们需要找到最小化目标函数的向量。搜索空间的维度 N 与特征的数量一样多;任何维度上唯一可能的值是 0 和 1。

找到一个好的启发式算法并非易事。scikit-learn 提供了多种方法来执行启发式特征选择,前提是我们的问题非常适合。

但找到一个好的、通用的启发式是一个难题。在本系列文章中,我们将探讨一些即使在 N 很大时也可以解决问题,并且目标函数实际上可以是代码中计算的任何内容。

02、数据集和完整代码

本文中的所有优化方法,使用Kaggle 的房价数据集。经过一些简单的特征转换后,数据最终有 213 个特征 (N=213) 和 1453 个观察值。

我使用的模型是线性回归,并且试图最小化的目标函数是 BIC(贝叶斯信息准则),这是一种信息损失的度量,因此 BIC 越低越好。它类似于 AIC(Akaike 信息准则),但 BIC 倾向于产生更简约的模型:它更喜欢特征较少的模型。

最小化 AIC 或 BIC 往往会减少过度拟合。但也可以使用其他目标函数,例如 R 平方或调整后的 R 平方。

最终,目标函数的选择在这里无关紧要。重要的是我们有一个目标函数,我们一直在尝试使用各种技术来优化它。

我们将尝试通过功能选择来最小化 BIC,因此这是在statsmodels.api.OLS()完成任何功能选择之前的 BIC 的基线值 - 启用所有功能:

baseline BIC: 34570.166173470934

现在让我们开始本文的的特征选择技术。

03、SFS:顺序特征搜索

SFS(前向版本)相当简单。它首先尝试选择单个特征,然后选择最小化目标函数的特征。一旦选择了某个功能,它就会永远保持选中状态。

然后它尝试向其中添加另一个特征(总共 2 个特征),以再次最小化目标。每次尝试找到最佳的新特征以添加到现有集合中时,它都会将所选特征的数量增加 1。

当所有功能一起尝试后,搜索结束。无论哪种组合能够最小化目标,都会获胜。

SFS 是一种贪婪算法——每个选择都是局部最优的——并且它永远不会回去纠正自己的错误。但即使 N 很大,它也相当快。它尝试的组合总数是 N(N+1)/2 一个二次多项式(而穷举搜索需要执行指数次数的尝试)。

让我们使用mlxtend 库看看 Python 中的 SFS 代码是什么样子:

import statsmodels.api as sm
from mlxtend.feature_selection import SequentialFeatureSelector as SFS
from sklearn.base import BaseEstimator

class DummyEstimator(BaseEstimator):
    # mlxtend 希望使用 sklearn ,但这里不需要它
    # (使用统计模型 OLS 代替)。
    def fit(self, X, y=None, **kwargs):
        return self

def neg_bic(m, X, y):
    # 目标函数
    lin_mod_res = sm.OLS(y, X, hasconst=True).fit()
    return -lin_mod_res.bic

seq_selector = SFS(
    DummyEstimator(),
    k_features=(1, X.shape[1]),
    forward=True,
    floating=False,
    scoring=neg_bic,
    cv=None,
    n_jobs=-1,
    verbose=0,
    # 确保拦截不中断
    fixed_features=['const'],
)

n_features = X.shape[1] - 1
objective_runs_sfs = round(n_features * (n_features + 1) / 2)
t_start_seq = time.time()
# 如果不进行 .copy(),mlxtend 会弄乱数据帧。
seq_res = seq_selector.fit(X.copy(), y.copy())
t_end_seq = time.time()
run_time_seq = t_end_seq - t_start_seq
seq_metrics = seq_res.get_metric_dict()

它快速运行组合,下面是总结结果:

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

最佳特征数量是 213 个中的 36 个。最佳 BIC 是 33708.986(特征选择之前的基线值为 34570.166),在我的系统上完成需要不到 1 分钟。它调用目标函数 22.8k 次。

这些是最佳 BIC 和 R 平方值,作为所选特征数量的函数:

SFS 的 BIC 和 R 平方

03、CMA-ES(协方差矩阵适应进化策略)原理

CMA-ES是一种数值优化算法。它与遗传算法属于同一类(它们都是进化算法),但 CMA-ES 与 GA 截然不同。该算法是随机的,不需要计算目标函数的导数(与依赖于偏导数的梯度下降不同)。

它的计算效率很高,并且用于各种数值优化库(例如 Optuna)。我在这里仅尝试对 CMA-ES 进行简要概述。

考虑二维 Rastrigin 函数:

下面的热图显示了该函数的值(颜色越亮意味着值越高)。该函数在原点 (0, 0) 处具有全局最小值,但也充满了许多局部极值。我们需要通过 CMA-ES 找到全局最小值。

Rastrigin 函数

CMA-ES是基于多元正态分布。它根据该分布在搜索空间中生成测试点。我们需要初始化分布的原始平均值及其标准差,但之后算法将迭代修改所有这些参数,在搜索空间中扫描分布,寻找最佳目标函数值。这是测试点的原始分布:

xi是算法在搜索空间中每一步生成的点集。lambda是生成的点数。分布的平均值将在每一步中更新,并希望最终收敛于真正的解决方案。sigma是分布的标准差——测试点的分布。

C是协方差矩阵:它定义分布的形状。根据C分布的值,可能具有“圆形”形状或更细长的椭圆形形状。进行更改以C允许 CMA-ES“潜入”搜索空间中的某些区域,或避开其他区域。

上图中生成了 6 个点的总体,这是优化器针对此问题选择的默认总体大小。这是第一步。之后,算法需要:

  • 计算每个点的目标函数 (Rastrigin)

  • 根据从目标函数中学到的知识,更新均值、标准差和协方差矩阵,有效地创建新的多元正态分布

  • 从新分布生成一组新的测试点

  • 重复直到满足某些标准(收敛于某个平均值,或超过最大步数等)

由于篇幅限制,本文将不展示所有分布参数的更新。但仅更新分布的平均值非常简单,其工作原理如下:计算每个测试点的目标函数后,为这些点分配权重,目标值更好的点赋予较大的权重,并根据它们的位置计算加权和,这成为新的平均值。实际上,CMA-ES 将分布平均值移向具有更好目标值的点:

如果算法收敛到真实解,则分布的平均值将收敛到该解。标准差将收敛到 0。协方差矩阵将根据目标函数的地理位置改变分布的形状(圆形或椭圆形),扩展到有希望的区域,并避开不良区域。

这是一个动画 GIF,显示了 CMA-ES 解决 Rastrigin 问题时测试点随时间的演变:

04、CMA-ES(协方差矩阵适应进化策略)代码

2D Rastrigin 函数相对简单,因为它只有 2 维。对于我们的特征选择问题,我们有 N=213 维。而且,空间并不是连续的。每个测试点都是一个 N 维向量,其分量值来自{0, 1}

换句话说,每个测试点看起来像这样:[1, 0, 0, 1, 1, 1, 0, ...]— 一个二进制向量。但除此之外,问题是相同的:我们需要找到最小化目标函数的点(或向量):OLS 模型的 BIC 参数。

以下是使用cmaes 库进行特征选择的 CMA-ES 代码的简单版本:

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], hasconst=True).fit()
    return lin_mod.bic


X_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 = 100
best_objective_cmaes_small = np.inf
best_sol_raw_cmaes_small = None
for 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,用目标评估它们,修改分布(均值、西格玛、协方差矩阵)等。每个x_for_eval点都是一个二进制向量,[1, 1, 1, 0, 0, 1, ...]用于从数据集中选择特征。

请注意,优化器使用的是CMAWM()而不是默认的CMA()。默认优化器对于常规的连续问题效果很好,但这里的搜索空间是高维的,并且只允许两个离散值(0 和 1)。默认优化器陷入了这个空间。CMAwM()扩大了一点搜索空间(虽然它返回的解决方案仍然是二进制向量)。

这些是优化的 CMA-ES 代码的主要统计数据:

最佳目标:33703.070530508514
最佳生成:921
目标运行:20000
最佳时间:48.326 秒

它能够找到比 SFS 更好(更小)的目标值,它调用目标函数的次数更少(20k),并且花费的时间大约相同。从所有指标来看,这都是相对于 SFS 的净收益。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1494050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

css-解决Flex布局下居中溢出滚动截断问题

css-解决Flex布局下居中溢出滚动截断问题 1.出现的问题2.解决方法2.1 Flex 布局下关键字 safe、unsafe2.2 使用 margin: auto 替代 justify-content: center2.3 额外嵌套一层 1.出现的问题 在页面布局中&#xff0c;我们经常会遇到/使用列表内容水平居中于容器中&#xff0c;一…

Codesys自定义库的帮助文档的美化

文章目录 1.前言2.美化的方式2.1.利用html标签2.2.利用reStructuredText 3.相关说明3.1.使用reStructuredText时&#xff0c;中文注释的问题3.2.将文档需要的图片包含到库中3.3.文档的作用区域 1.前言 当我们在codesys中写好自己的库&#xff0c;并且发布给别人使用时&#xf…

overleaf latex 笔记

overleaf: www.overleaf.com 导入.tex文件 1.代码空一行&#xff0c;代表文字另起一段 2. 1 2 3 排序 \begin{enumerate} \item \item \item \end{enumerate} 3.插入图片 上传图片并命名 \usepackage{float}导包\begin{figure}[H]&#xff1a;表示将图…

大数据最佳实践

本文主要收录一些大数据不错的实践文章 1、数禾云上数据湖最佳实践 https://blog.51cto.com/u_15089766/2601706 该文章介绍了数禾云的数据胡实践&#xff0c;包含presto以及数据湖等组件的一些部署架构&#xff0c;文章听不错的&#xff0c;里面提到了为了避免presto与yarn计…

汽车中网上的logo不能改,需要到车管所备案

需要备案。 车辆改装需到车辆管理所办理登记。 机动车每年检验时&#xff0c;需要对外观进行检测。 中国在线的标志不能更改。 汽车格栅是汽车前部进气口附近相关部件的总称。 汽车的中网主要位于水箱、发动机、空调等设备的前面&#xff0c;控制进气和通风&#xff0c;防止行…

3d怎么拖模型---模大狮模型网

在3D建模软件中拖动(移动)模型通常是一种基本的操作&#xff0c;用来调整模型的位置或布局。以下是一般情况下在3D建模软件中拖动模型的基本步骤&#xff1a; 3d拖模型的步骤&#xff1a; 选择模型&#xff1a;在3D建模软件中选中你要拖动的模型。通常可以通过单击模型来选中它…

#define MODIFY_REG(REG, CLEARMASK, SETMASK)

#define MODIFY_REG(REG, CLEARMASK, SETMASK) WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK))) 这个宏 MODIFY_REG 是在嵌入式编程中&#xff0c;它用于修改一个寄存器的特定位&#xff0c;而不影响其他位。这个宏接受三个参数&#xff…

无代理方式实现VMware的迁移?详细解析

在当今数字化时代&#xff0c;数据的安全性和可用性对于企业至关重要。尤其是在VMware转变订阅策略后&#xff0c;原本永久订阅的产品转变为以年付费订阅的形式&#xff0c;导致客户不得不支付更多的费用&#xff0c;大幅增加了成本。同时&#xff0c;客户也对VMware未来发展前…

鹅厂打工8年,我为啥突然裸辞?

公众号&#xff1a;程序员白特&#xff0c;欢迎一起交流学习~ 原文&#xff1a;以下文章来源于沐洒 &#xff0c;作者ASCII26 今天跟大家分享一个重磅消息&#xff0c;沐洒终于从腾讯离职了&#xff01; 不知不觉已经在鹅厂打了8年工&#xff0c;如果说在大厂里工作如同在高校…

DAY 12滑动串口最大值【单调队列】 前K个高频元素【优先级队列】

6.滑动窗口最大值 队列的应用&#xff01;&#xff01; 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff…

deep image matting

[Matting]论文阅读&#xff1a;Deep Image Matting 详细解读-CSDN博客文章浏览阅读3.5k次&#xff0c;点赞2次&#xff0c;收藏10次。[Matting]论文阅读&#xff1a;Deep Image Matting 详细解读一 、摘要二、方法2.1 第一部分&#xff08;Matting encoder-decoder stage&#…

ThingsBoard社区版入门介绍

介绍 本教程主要演示ThingsBoard的基本用法并掌握: 设备连接&#xff1b;数据发布&#xff1b;数据展示&#xff1b;警报触发&#xff1b;通知推送。 教程将连接和可视化来自温度传感器的数据以及简单使用。 必备条件 你需要启动并运行ThingsBoard服务&#xff0c; 建议使…

java spring 02. AbstractApplicationContext的refresh

spring创建对象的顺序&#xff0c;先创建beanfactory&#xff0c;再会把xml文件读取到spring。 public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, Nullable ApplicationContext parent)throws BeansException {//调用父类的构造方法super(p…

05_Mongooes

Mongooes Mongoose是通过Node来操作MongoDB的一个模块。是基于Node.js的第三方模块。 一、Node.js安装 1.解压 2.创建文件夹 解压路径下&#xff0c;创建两个文件夹 node_global&#xff1a;全局安装位置 node_cache&#xff1a;缓存 3.配置 配置环境变量 在path路径…

挂耳式运动蓝牙耳机什么牌子好?六大选购技巧大揭秘

随着蓝牙耳机的普及&#xff0c;越来越多的人选择它们来满足日常的娱乐需求。这些耳机以时尚的设计和舒适的佩戴体验而受到青睐。蓝牙耳机主要分为挂耳式和入耳式两大类。尽管入耳式耳机功能全面&#xff0c;但对于热衷运动的用户来说不够稳固&#xff0c;因为在运动时很容易掉…

蓝桥杯-大小写转换

转换方法 toLowerCase() String类的toLowerCase()方法可以将字符串中的所有字符全部转换成小写&#xff0c;而非字母的字符不受影响&#xff0c;语法格式如下&#xff1a; 字符串名.toLowerCase() //将字符串中的字母全部转成小写&#xff0c;非字母不受影响。 package chap…

闫震海:腾讯音乐空间音频技术的发展和应用 | 演讲嘉宾公布

一、3D 音频 3D 音频分论坛将于3月27日同期举办&#xff01; 3D音频技术不仅能够提供更加真实、沉浸的虚拟世界体验&#xff0c;跨越时空的限制&#xff0c;探索未知的世界。同时&#xff0c;提供更加丰富、立体的情感表达和交流方式&#xff0c;让人类能够更加深入地理解彼此&…

游戏力:竞技游戏设计实战教程

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 游戏力&#xff1a;竞技游戏设计实战教程 引言…

pinia报错does not provide an export named ‘hasInjectionContext

你们好&#xff0c;我是金金金。 场景 我这里是uniappvue3编写的一个小程序项目&#xff0c;在集成pinia过程当中遇到此问题&#xff0c;报错请求的模块 未提供 导出名hasInjectionContext&#xff08;位于 pinia.mjs:6:10&#xff09; 以下我项目当中vue和pinia的具体依赖版本…

RAG全解析和LangChain代码实现

大家好&#xff0c;自从人们意识到可以用自有数据为大型语言模型&#xff08;LLM&#xff09;增效之后&#xff0c;就开始讨论如何最有效地弥合 LLM 的通用知识与专有数据之间的差距。围绕着微调还是检索增强生成&#xff08;RAG&#xff09;哪个更适合这一问题&#xff0c;人们…