基于均值优化的数据集子集划分:混合整数规划与启发式方法

基于均值优化的数据集子集划分:混合整数规划与启发式方法

本文探讨如何将一个超集(数据集)划分为N个指定大小的子集,同时确保每个子集的均值尽可能接近超集的总均值,且元素不重复使用。我们主要介绍如何将此问题建模为混合整数线性规划(MILP),并使用Python的PuLP库进行求解,以实现精确的均值优化。同时,文章也讨论了在面对大规模数据时的性能挑战及潜在的启发式优化策略。

1. 问题描述与挑战

在数据分析、实验设计或样本分配等场景中,我们经常需要将一个包含m个元素的原始数据集(超集)划分为n个互不重叠、且大小预定的子集。一个常见的优化目标是使每个子集的统计特性(例如均值)尽可能地与原始超集的特性保持一致。具体来说,给定一个超集 s 及其包含的 m 个元素,以及 n 个预期的子集大小 x0, x1, …, xn-1(其中 sum(xi) == m),目标是创建这些子集,使得每个子集的均值与超集的均值最为接近。我们通常通过最小化所有子集均值与超集均值之间绝对差异的总和来量化这一目标。

这是一个典型的组合优化问题,其挑战在于:

无放回抽样: 超集中的每个元素只能被分配到一个子集中,且仅使用一次。固定子集大小: 每个子集必须严格满足其预设的元素数量。均值优化: 这是一个全局优化目标,需要权衡不同子集之间的分配,以达到整体最优。计算复杂度: 随着超集元素数量和子集数量的增加,可能的组合呈指数级增长,导致穷举法不可行。在实际应用中,算法需要在合理的时间内(例如1秒内)完成对中等规模数据的处理。

2. 数学建模:混合整数线性规划 (MILP)

这种类型的分配问题可以被归类为“集合划分问题”(Set Partitioning Problem)的一个变种,其中加入了特定的目标函数(均值优化)和额外的约束。混合整数线性规划(MILP)提供了一种强大的框架来精确解决这类问题。

2.1 目标函数

我们的目标是使每个子集的均值 mean(subset_s) 尽可能接近超集的均值 mean(superset)。这等价于使每个子集的总和 sum(subset_s) 尽可能接近 subset_size_s * mean(superset)。因此,我们可以定义目标函数为最小化所有子集总和与目标总和之间绝对差异的总和:

$$ min sum{s=0}^{N-1} | sum{i in text{subset}_s} text{element}_i – (text{size}_s times text{mean}(text{superset})) | $$

2.2 决策变量

我们引入二元决策变量 x_s_i:x_s_i = 1 如果超集中的第 i 个元素被分配到第 s 个子集中。x_s_i = 0 否则。

2.3 约束条件

子集大小约束: 每个子集 s 必须包含预定数量的元素 size_s。$$ sum{i=0}^{M-1} x{s,i} = text{size}_s quad forall s in {0, dots, N-1} $$元素唯一性约束: 超集中的每个元素 i 只能被分配到一个且仅一个子集中。$$ sum{s=0}^{N-1} x{s,i} = 1 quad forall i in {0, dots, M-1} $$绝对值线性化: 在线性规划中,通常通过引入辅助变量和不等式来处理绝对值。对于每个子集 s,我们定义其总和误差 err_s:$$ text{err}s = sum{i=0}^{M-1} (text{element}i times x{s,i}) – (text{size}_s times text{mean}(text{superset})) $$然后引入一个非负辅助变量 abs_err_s 来表示 |err_s|,并添加以下约束:$$ text{abs_err}_s ge text{err}_s $$$$ text{abs_err}_s ge -text{err}_s $$最终,目标函数变为最小化 sum(abs_err_s)。

3. 使用 PuLP 进行求解

PuLP 是一个用 Python 编写的线性规划建模工具,它允许用户以直观的方式定义优化问题,并调用各种求解器(如CBC、GLPK、Gurobi等)来解决。

以下是一个使用 PuLP 解决上述问题的示例代码:

from statistics import meanimport pulpdef solve_subset_partitioning(superset_elements, subset_sizes):    """    使用 PuLP 解决基于均值优化的数据集子集划分问题。    Args:        superset_elements (list): 超集中的所有元素列表。        subset_sizes (list): N个子集各自的目标大小列表。    Returns:        tuple: (list of lists, list of floats) 分割后的子集元素列表和每个子集的均值。    """    N = len(subset_sizes)    M = len(superset_elements)    # 验证输入    if sum(subset_sizes) != M:        raise ValueError("所有子集大小之和必须等于超集元素总数。")    # 计算超集均值    superset_mean = mean(superset_elements)    # 创建 PuLP 问题实例    set_partitioning_model = pulp.LpProblem("Set_Partitioning_Model", pulp.LpMinimize)    # 决策变量:x_s_i = 1 如果超集中的第 i 个元素被分配到第 s 个子集中    # covering[s] 是一个列表,其中包含子集 s 的 M 个二元变量    covering = {}    for s in range(N):        vals = []        for i, v in enumerate(superset_elements):            vals.append(                pulp.LpVariable(                    f"x_set_{s}_element_idx_{i:>02}_val_{v}",                    lowBound=0,  # 0                    upBound=1,   # 1                    cat=pulp.LpBinary, # 二进制变量                )            )        covering[s] = vals    # 辅助变量:用于处理绝对误差    abs_sum_errs = []    for s_i in range(N):        abs_sum_errs.append(pulp.LpVariable(f"set_{s_i}_sum_error_abs", lowBound=0))    # 目标函数:最小化所有子集绝对误差之和    set_partitioning_model += pulp.lpSum(abs_sum_errs), "Minimize_Absolute_Sum_Errors"    # 添加约束    for s_i, st_vars in covering.items():        # 计算每个子集的实际总和        current_set_sum = pulp.lpSum([p * superset_elements[i] for i, p in enumerate(st_vars)])        # 计算每个子集的目标总和 (子集大小 * 超集均值)        target_set_sum = subset_sizes[s_i] * superset_mean        # 定义子集总和误差变量        set_sum_err = pulp.LpVariable(f"set_{s_i}_sum_error")        set_partitioning_model += set_sum_err == current_set_sum - target_set_sum, f"Set_{s_i}_Sum_Error_Definition"        # 绝对值线性化约束        set_partitioning_model += abs_sum_errs[s_i] >= set_sum_err, f"Abs_Error_Upper_Bound_Pos_{s_i}"        set_partitioning_model += abs_sum_errs[s_i] >= -set_sum_err, f"Abs_Error_Upper_Bound_Neg_{s_i}"    # 约束1: 每个子集的大小必须符合预设    for s_i, st_vars in enumerate(covering.values()):        set_partitioning_model += pulp.lpSum(st_vars) == subset_sizes[s_i], f"Set_{s_i}_Size_Constraint"    # 约束2: 超集中的每个元素只能被使用一次    # zip(*covering.values()) 将所有子集的变量列表转置,以便按元素索引迭代    for i, element_vars in enumerate(zip(*covering.values())):        set_partitioning_model += (            pulp.lpSum(element_vars) == 1,            f"Element_{i}_Used_Once_Constraint",        )    # 求解模型    set_partitioning_model.solve(pulp.PULP_CBC_CMD(msg=False)) # 使用默认的CBC求解器,静默模式    # 提取结果    if pulp.LpStatus[set_partitioning_model.status] == "Optimal":        result_subsets = []        result_means = []        for s_i, st_vars in covering.items():            current_subset_elements = [                superset_elements[i] for i, var in enumerate(st_vars) if var.value() == 1            ]            result_subsets.append(current_subset_elements)            result_means.append(mean(current_subset_elements))        return result_subsets, result_means    else:        print(f"未能找到最优解。状态: {pulp.LpStatus[set_partitioning_model.status]}")        return [], []# 示例 1:完美分配print("--- 示例 1:完美分配 ---")superset1 = [100]*5 + [101]*10 + [102]*5subset_sizes1 = [2, 4, 14]subsets1, means1 = solve_subset_partitioning(superset1, subset_sizes1)print(f"超集均值: {mean(superset1)}")for i, (subset, mean_val) in enumerate(zip(subsets1, means1)):    print(f"子集 {chr(65+i)} ({len(subset)} 元素): {subset}, 均值: {mean_val}")# 预期输出:所有子集均值均为 101# 示例 2:最佳拟合print("n--- 示例 2:最佳拟合 ---")superset2 = [100]*5 + [103]*10 + [104]*5subset_sizes2 = [2, 4, 14]subsets2, means2 = solve_subset_partitioning(superset2, subset_sizes2)print(f"超集均值: {mean(superset2)}")for i, (subset, mean_val) in enumerate(zip(subsets2, means2)):    print(f"子集 {chr(65+i)} ({len(subset)} 元素): {subset}, 均值: {mean_val}")# 预期输出:子集均值尽可能接近 102.5

代码解析:

初始化: 定义超集元素、子集大小,并计算超集均值。LpProblem: 创建一个 PuLP 问题实例,目标是最小化 (pulp.LpMinimize)。决策变量 covering: 这是一个字典,键是子集索引 s,值是一个列表,包含了 M 个 pulp.LpBinary 变量。每个变量 x_s_i 代表超集中的第 i 个元素是否属于第 s 个子集。辅助变量 abs_sum_errs: 用于存储每个子集总和与目标总和之间绝对差异的辅助变量,lowBound=0 确保其非负。目标函数: 设置为最小化 abs_sum_errs 中所有元素的和。误差定义与绝对值约束: 遍历每个子集,计算其目标总和 (subset_size * superset_mean)。然后定义 set_sum_err 为实际总和与目标总和之差。最后,通过两个不等式 abs_sum_errs[s_i] >= set_sum_err 和 abs_sum_errs[s_i] >= -set_sum_err 来实现绝对值的线性化。子集大小约束: 确保每个子集中的元素数量(即对应 x_s_i 变量之和)等于其预设的 subset_sizes[s_i]。元素唯一性约束: 确保超集中的每个元素 i(通过 zip(*covering.values()) 遍历)在所有子集中的 x_s_i 变量之和为1,即每个元素仅被分配一次。求解: 调用 set_partitioning_model.solve() 启动求解器。默认情况下,PuLP 会使用其自带的 CBC 求解器。结果提取: 检查求解状态,如果找到最优解,则遍历决策变量,提取每个子集包含的元素,并计算其均值。

4. 启发式算法:Karmarkar-Karp (Largest Differencing Method)

当精确求解(如MILP)因问题规模过大而变得不可行时,启发式算法提供了一种快速获得近似解的方法。Karmarkar-Karp 算法(也称为最大差分法)是解决数集划分问题的一种著名启发式算法,其目标是将一个数集划分为 k 个子集,使这些子集的和尽可能接近,即最小化最大子集和与最小子集和之间的差异。

优点: 速度快,易于实现。局限性:

无法指定子集大小: Karmarkar-Karp 算法的主要目的是平衡子集和,而不是严格控制子集中的元素数量。这与我们问题中“固定子集大小”的要求不符。不直接优化均值: 尽管它试图使子集和接近,但这并不直接等同于使子集均值接近超集均值,尤其是在子集大小不固定的情况下。

因此,Karmarkar-Karp 算法不适用于严格满足本教程最初提出的所有约束条件。然而,作为一种通用的数集划分启发式方法,它在某些场景下仍有其价值,例如当我们只需要大致平衡子集总和,而对子集大小没有严格要求时。

以下是一个使用 numberpartitioning 库实现 Karmarkar-Karp 算法的示例:

from statistics import meanfrom numberpartitioning import karmarkar_karp# 示例 2 的超集数据superset = [100, 100, 100, 100, 100, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 104, 104, 104, 104, 104]print("n--- 启发式方法:Karmarkar-Karp ---")print("超集均值:", mean(superset))# 使用 Karmarkar-Karp 划分成 3 个部分# 注意:此方法不接受预设的子集大小for p in karmarkar_karp(superset, num_parts=3).partition:    print(f"子集 ({len(p)} 元素): {p}, 均值: {mean(p)}")

从输出可以看出,Karmarkar-Karp 算法生成的子集大小不固定,且其均值与超集均值的接近程度可能不如 MILP 得到的精确解。

5. 性能考量与优化策略

尽管 MILP 可以提供最优解,但其计算复杂度是 NP-hard 的。对于大规模问题,求解时间可能过长。在实际应用中,我们需要根据具体情况权衡求解精度和计算效率。

问题规模:

N (子集数量): 10-25 个子集通常是 MILP 可处理的范围,但达到 100 个子集时,问题会变得非常困难。M (超集元素数量): 100-1000 个元素是常见情况,10000 个唯一元素则非常庞大。唯一元素数量: 如果超集中有大量重复元素,可以考虑预处理,将相同元素视为一个“类别”,并为每个类别分配一定数量的元素到子集,这可能简化问题。

优化策略:

启发式预分配 + 精确调整:初步均匀分配: 按照子集大小比例,从超集中随机(或均匀)抽取元素填充子集 50%-7

以上就是基于均值优化的数据集子集划分:混合整数规划与启发式方法的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1372699.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
Python模块导入策略:直接引用类名与通配符导入
上一篇 2025年12月14日 12:32:37
Python模块导入进阶:直接引用模块内成员的技巧
下一篇 2025年12月14日 12:32:48

相关推荐

  • composer require-dev和require有什么不同_Composer Require与Require-Dev区别解析

    require用于声明项目运行必需的依赖,如框架、数据库组件和第三方SDK,这些包会随项目部署到生产环境;2. require-dev用于声明仅在开发和测试阶段需要的工具,如PHPUnit、PHPStan、Faker等,不会默认部署到生产环境;3. 安装时composer install根据环境决定…

    2026年5月10日
    1000
  • Matplotlib 地图中多类型图例的创建与优化

    Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化

    本教程旨在解决matplotlib地图可视化中,如何在一个图例中同时展示颜色块(如区域分类)和自定义标记(如特定兴趣点)的问题。文章详细介绍了当传统`patch`对象无法正确显示标记时,如何利用`matplotlib.lines.line2d`创建标记图例句柄,并将其与颜色块图例句柄合并,从而生成一…

    2026年5月10日 用户投稿
    900
  • Golang JSON序列化:控制敏感字段暴露的最佳实践

    本教程探讨golang中如何高效控制结构体字段在json序列化时的可见性。当需要将包含敏感信息的结构体数组转换为json响应时,通过利用`encoding/json`包提供的结构体标签,特别是`json:”-“`,可以轻松实现对特定字段的忽略,从而避免敏感数据泄露,确保api…

    2026年5月10日
    300
  • 利用海象运算符简化条件赋值:Python教程与最佳实践

    本文旨在探讨Python中海象运算符(:=)在条件赋值场景下的应用。通过对比传统if/else语句与海象运算符,以及条件表达式,分析海象运算符在简化代码、提高可读性方面的优势与局限性。并通过具体示例,展示如何在列表推导式等场景下合理使用海象运算符,同时强调其潜在的复杂性及替代方案,帮助开发者更好地掌…

    2026年5月10日
    300
  • Debian syslog性能优化技巧有哪些

    提升Debian系统syslog (通常基于rsyslog)性能,关键在于精简配置和高效处理日志。以下策略能有效优化日志管理,提升系统整体性能: 精简配置,高效加载: 在rsyslog配置文件中,仅加载必要的输入、输出和解析模块。 使用全局指令设置日志级别和格式,避免不必要的处理。 自定义模板: 创…

    2026年5月10日
    000
  • 比特币新手教程 比特币交易平台有哪些

    比特币是一种去中心化的数字货币,基于区块链技术实现点对点交易,具有匿名性、有限发行和不可篡改等特点;新手可通过交易所购买,P2P交易获得比特币,常用平台包括Binance、OKX和Huobi;交易流程包括注册账户、实名认证、绑定支付方式、充值法币并下单购买,可选择市价单或限价单;比特币存储方式有交易…

    2026年5月10日
    000
  • c++中的SFINAE技术是什么_c++模板编程中的SFINAE原理与应用

    SFINAE 是“替换失败不是错误”的原则,指模板实例化时若参数替换导致错误,只要存在其他合法候选,编译器不报错而是继续重载决议。它用于条件启用模板、类型检测等场景,如通过 decltype 或 enable_if 控制函数重载,实现类型特征判断。尽管 C++20 引入 Concepts 简化了部分…

    2026年5月10日
    000
  • Go语言mgo查询构建:深入理解bson.M与日期范围查询的正确实践

    本文旨在解决go语言mgo库中构建复杂查询时,特别是涉及嵌套`bson.m`和日期范围筛选的常见错误。我们将深入剖析`bson.m`的类型特性,解释为何直接索引`interface{}`会导致“invalid operation”错误,并提供一种推荐的、结构清晰的代码重构方案,以确保查询条件能够正确…

    2026年5月10日
    100
  • RichHandler与Rich Progress集成:解决显示冲突的教程

    在使用rich库的`richhandler`进行日志输出并同时使用`progress`组件时,可能会遇到显示错乱或溢出问题。这通常是由于为`richhandler`和`progress`分别创建了独立的`console`实例导致的。解决方案是确保日志处理器和进度条组件共享同一个`console`实例…

    2026年5月10日
    300
  • Golang goroutine与channel调试技巧

    使用go run -race检测数据竞争,结合runtime.NumGoroutine监控协程数量,通过pprof分析阻塞调用栈,利用select超时避免永久阻塞,有效排查goroutine泄漏、死锁和数据竞争问题。 Go语言的goroutine和channel是并发编程的核心,但它们也带来了调试上…

    2026年5月10日
    000
  • 使用 Jupyter Notebook 进行探索性数据分析

    Jupyter Notebook通过单元格实现代码与Markdown结合,支持数据导入(pandas)、清洗(fillna)、探索(matplotlib/seaborn可视化)、统计分析(describe/corr)和特征工程,便于记录与分享分析过程。 Jupyter Notebook 是进行探索性…

    2026年5月10日
    000
  • 《魔兽世界》将于6月11日开启国服回归技术测试

    《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试

    《%ign%ignore_a_1%re_a_1%》官方宣布,将于6月11日开启国服回归技术测试,时间为7天,并称可以在6月内正式开服,玩家们可以访问官网下载战网客户端并预下载“巫妖王之怒”客户端,技术测试详情见下图。 WordAi WordAI是一个AI驱动的内容重写平台 53 查看详情 以上就是《…

    2026年5月10日 用户投稿
    400
  • 如何在HTML中插入表单元素_HTML表单控件与输入类型使用指南

    HTML表单通过标签构建,包含action和method属性定义数据提交目标与方式,常用input类型如text、password、email等适配不同输入需求,配合label、required、placeholder提升可用性,结合textarea、select、button等控件实现完整交互,是…

    2026年5月10日
    300
  • 网站标题关键词更新后,搜索引擎为何仍显示旧标题?

    网站标题更新后,搜索引擎为何显示旧标题? 网站SEO优化中,站长常修改网站标题关键词,期望搜索结果显示自定义标题。然而,即使更新标签、meta keywords、meta description和结构化数据中的name属性后,搜索结果仍显示旧标题,这令人费解。本文将对此进行解释。 问题:站长修改了网…

    2026年5月10日
    300
  • 创建指定大小并填充特定数据的Golang文件教程

    本文将介绍如何使用Golang创建一个指定大小的文件,并用特定数据填充它。我们将使用 `os` 包提供的函数来创建和截断文件,从而实现快速生成大文件的目的。示例代码展示了如何创建一个10MB的文件,并将其填充为全零数据。掌握这些方法,可以方便地在例如日志系统或磁盘队列等场景中,预先创建测试文件或初始…

    2026年5月10日
    000
  • 深入理解 Express.js 中 next() 参数的作用与中间件机制

    本文深入探讨 express.js 中间件函数中的 `next()` 参数。它负责将控制权传递给请求-响应周期中的下一个中间件或路由处理程序。文章将详细解释 `next()` 的工作原理、中间件的注册与执行顺序,以及不正确使用 `next()` 可能导致请求挂起的风险,并通过代码示例和实际应用场景,…

    2026年5月10日
    000
  • Python命令怎样使用profile分析脚本性能 Python命令性能分析的基础教程

    使用Python的cProfile模块分析脚本性能最直接的方式是通过命令行执行python -m cProfile your_script.py,它会输出每个函数的调用次数、总耗时、累积耗时等关键指标,帮助定位性能瓶颈;为进一步分析,可将结果保存为文件python -m cProfile -o ou…

    2026年5月10日
    000
  • 使用 WebCodecs VideoDecoder 实现精确逐帧回退

    本文档旨在解决在使用 WebCodecs VideoDecoder 进行视频解码时,实现精确逐帧回退的问题。通过比较帧的时间戳与目标帧的时间戳,可以避免渲染中间帧,从而提高用户体验。本文将提供详细的解决方案和示例代码,帮助开发者实现精确的视频帧控制。 在使用 WebCodecs VideoDecod…

    2026年5月10日
    300
  • 如何插入查询结果数据_SQL插入Select查询结果方法

    如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法

    使用INSERT INTO…SELECT语句可高效插入数据,通过NOT EXISTS、LEFT JOIN、MERGE语句或唯一约束避免重复;表结构不一致时可通过别名、类型转换、默认值或计算字段处理;结合存储过程可提升可维护性,支持参数化与动态SQL。 将查询结果数据插入到另一个表中,可以…

    2026年5月10日 用户投稿
    400
  • Debian Copilot的社区活跃度如何

    debian copilot是codeberg社区维护的ai助手,旨在为debian用户提供服务。尽管搜索结果中没有直接提供关于debian copilot社区支持活跃度的具体数据,但我们可以通过debian社区的整体活跃度和特点来推断其活跃性。 Debian社区的一般情况: Debian拥有详尽的…

    2026年5月10日
    000

发表回复

登录后才能评论
关注微信