Python线性规划:如何实现权重取值为0或[0.2,1]的边界约束
实现非连续权重边界的MILP适配方案
你之前使用的scipy.optimize.minimize是纯连续优化求解器,本身不支持分段非连续边界的约束,无法直接实现“权重要么取0、要么落在指定下限到1的区间”的要求,需要用标准的混合整数规划建模逻辑把非连续边界转换成可求解的线性约束,再替换对应支持整数变量的求解器即可。
核心建模逻辑
对每一个权重变量xi,引入一个二值辅助变量zi ∈ {0,1},用来切换权重的取值分支:
- 当
zi=0时,强制xi=0 - 当
zi=1时,强制xi落在你设置的阈值下限(比如0.2/0.3)到1的区间内
对应的绑定约束可以写为:阈值 * zi ≤ xi ≤ zi
这个约束完全等价于你要求的两种可选边界:如果zi=0,xi被上下限夹在0的位置,必然取0;如果zi=1,xi的合法取值刚好就是[阈值, 1],没有额外自由度。
代码改造步骤
1. 替换求解器
Scipy 1.9.0及以上版本内置了scipy.optimize.milp求解器,原生支持混合整数变量、线性约束,不需要额外引入第三方库。
你原来的目标函数是最小化加权向量和目标向量的欧氏距离,等价于最小化欧氏距离的平方(因为平方根是单调递增函数,两者的最优解完全一致),转换后的目标是凸二次形式,可以适配对应求解器的二次目标接口。
2. 约束对齐
保留你原来的两个业务规则:
- 所有权重和为1:
sum(xi) = 1 - 每个xi和对应zi的绑定约束
- 把辅助变量zi标记为整数类型,限制只能取0或1
参考实现代码
from scipy.optimize import milp, LinearConstraint, Bounds import numpy as np # 基础参数配置 lower_threshold = 0.2 # 可按需调整为0.3 n_weights = input_matrix.shape[1] n_total_vars = 2 * n_weights # 前半段是连续权重xi,后半段是二值辅助zi # 1. 权重和为1的等式约束 weight_sum_row = np.zeros(n_total_vars) weight_sum_row[:n_weights] = 1 constraint_weight_sum = LinearConstraint(weight_sum_row, lb=1, ub=1) # 2. xi和zi的绑定约束 bind_rows = [] for i in range(n_weights): # 约束1: xi - lower_threshold*zi >= 0 row_low = np.zeros(n_total_vars) row_low[i] = 1 row_low[n_weights + i] = -lower_threshold bind_rows.append(row_low) # 约束2: -xi + zi >= 0 row_high = np.zeros(n_total_vars) row_high[i] = -1 row_high[n_weights + i] = 1 bind_rows.append(row_high) bind_matrix = np.array(bind_rows) constraint_bind = LinearConstraint(bind_matrix, lb=0, ub=np.inf) # 3. 变量全局边界 var_bounds = Bounds( lb=np.concatenate([np.zeros(n_weights), np.zeros(n_weights)]), ub=np.concatenate([np.ones(n_weights), np.ones(n_weights)]) ) # 4. 整数变量标记:zi为整数,xi为连续变量 var_integrality = np.concatenate([ np.zeros(n_weights), np.ones(n_weights) ]) # 5. 二次目标等价转换:原目标min ||A·w - t||₂ 等价于 min w^T(A^T A)w - 2(A^T t)^T w A = input_matrix t = target_vector linear_obj_coeff = -2 * A.T @ t # 求解 result = milp( fun=linear_obj_coeff, integrality=var_integrality, bounds=var_bounds, constraints=[constraint_weight_sum, constraint_bind], # 传入二次项矩阵适配求解器接口 # quad=A.T @ A ) # 提取最终权重,清除浮点误差带来的微小非零值 final_weights = result.x[:n_weights] final_weights[final_weights < 1e-6] = 0
如果使用的Scipy版本不支持二次目标的MILP求解,保持上述变量、约束的建模逻辑不变,替换为支持凸二次混合整数规划的求解器即可,不需要调整业务规则的定义。
内容的提问来源于stack exchange,提问作者Marcel
相关产品推荐
相关产品推荐

