
本文介绍如何使用 SciPy 的 milp 求解器,构建整数线性规划模型,精确计算 7 天的每日权重,使其严格递减、和为 7,并确保恰好 7 个用户的加权平均消费值低于指定阈值(如 11)。
本文介绍如何使用 scipy 的 `milp` 求解器,构建整数线性规划模型,精确计算 7 天的每日权重,使其严格递减、和为 7,并确保**恰好 7 个用户**的加权平均消费值低于指定阈值(如 11)。
在时间序列加权分析中,仅靠指数衰减等启发式方法难以同时满足严格单调性、固定和约束与精确达标数量控制(如“恰好 7/10 用户低于阈值”)三大条件。此时,传统优化手段失效,需转向混合整数线性规划(MILP)——它能通过引入二元决策变量(binary predicates),将逻辑条件(如“加权均值 ≤ 阈值?”)转化为可求解的线性约束。
以下为完整实现流程(基于 scipy.optimize.milp):
✅ 核心建模要素
-
决策变量:
-
w₀, w₋₁, ..., w₋₆:7 个连续型权重(要求w₀ > w₋₁ > ... > w₋₆ > 0); -
pᵢ ∈ {0,1}(i = 0..9):10 个二元变量,pᵢ = 1表示第 i 个用户的加权均值 ≤ 阈值。
-
-
关键约束:
-
和约束:
∑wⱼ = 7; -
严格递减:
wⱼ − wⱼ₊₁ ≥ ε(ε = 1e-2,避免数值相等); -
阈值逻辑(大M法):
- 若
pᵢ = 1→(w·df[i]) / 7 ≤ threshold; - 若
pᵢ = 0→(w·df[i]) / 7 > threshold;
等价于:w·df[i] + M·pᵢ ≤ M + 7·threshold(上界)w·df[i] + M·pᵢ ≥ 7·threshold(下界)
其中M取足够大值(如2 * max(row_sum));
- 若
-
精确计数:
∑pᵢ = 7。
-
和约束:
? 示例代码(精确解)
import numpy as np
import pandas as pd
from scipy.optimize import milp, LinearConstraint, Bounds
import scipy.sparse
# 构造示例数据(10用户 × 7天)
df = pd.DataFrame({
'day_0': [10,10,10,12,12,13,8,13,8,14],
'day_-1': [10,13,12,12,13,9,9,10,8,13],
'day_-2': [14,11,9,9,8,8,8,14,10,13],
'day_-3': [8,11,12,11,12,13,14,12,12,9],
'day_-4': [14,8,9,9,8,9,8,8,11,11],
'day_-5': [14,10,10,12,11,12,13,9,14,14],
'day_-6': [14,10,10,13,9,10,14,11,14,13]
}, index=range(10))
df.columns = [0, -1, -2, -3, -4, -5, -6] # 统一列名为整数天数
m, n = df.shape # m=10 users, n=7 days
threshold = 11
n_up_to_threshold = 7 # 恰好7人满足
M = 2 * df.sum(axis=1).max() # 大M值
# === 约束定义 ===
# 1. 权重和为7
sum_con = LinearConstraint(
A=np.concatenate([np.ones(n), np.zeros(m)]),
lb=7, ub=7
)
# 2. 严格递减(w_j - w_{j+1} >= 1e-2)
dec_con = LinearConstraint(
A=scipy.sparse.diags_array(
(np.ones(n-1), -np.ones(n-1)), offsets=(0,1), shape=(n-1, n+m)
),
lb=1e-2
)
# 3. 阈值逻辑约束(大M法)
A_thresh = scipy.sparse.hstack([
scipy.sparse.csr_matrix(df.values), # w·df[i]
scipy.sparse.diags(M * np.ones(m)) # + M·p_i
], format='csc')
thresh_con = LinearConstraint(
A=A_thresh,
lb=7*threshold * np.ones(m),
ub=M + 7*threshold * np.ones(m)
)
# 4. 精确计数:sum(p_i) = 7
count_con = LinearConstraint(
A=np.concatenate([np.zeros(n), np.ones(m)]),
lb=n_up_to_threshold, ub=n_up_to_threshold
)
# === 变量边界 ===
bounds = Bounds(
lb=np.concatenate([np.full(n, 1e-3), np.zeros(m)]),
ub=np.concatenate([np.full(n, np.inf), np.ones(m)])
)
# === 求解(无目标函数,仅可行性)===
result = milp(
c=np.zeros(n + m),
integrality=np.concatenate([np.zeros(n), np.ones(m, dtype=int)]),
bounds=bounds,
constraints=[sum_con, dec_con, thresh_con, count_con]
)
if not result.success:
raise RuntimeError(f"Optimization failed: {result.message}")
weights, preds = result.x[:n], result.x[n:]
means = df @ (weights / 7)
print("✅ 求解成功!")
print(f"每日权重 w₀..w₋₆ = {weights.round(4)}")
print(f"达标用户标识 p₀..p₉ = {preds.astype(int)} → 共{int(preds.sum())}人")
print(f"各用户加权均值:\n{means.round(4)}")
⚠️ 注意事项与进阶建议
-
数值稳定性:严格不等式
>在 LP 中需用≥ ε近似,ε过小易导致数值误差,建议1e-3 ~ 1e-2; -
大M值选择:
M过大会削弱约束紧致性,应取理论最大可能值(如2 * max(sum(row))); -
无解处理:若
milp返回success=False,说明约束冲突(如阈值过低且要求过高达标数),可尝试:- 放宽阈值(软约束法,见答案第二部分);
- 增加最小权重下限;
- 检查数据是否存在极端离群值;
- 软约束扩展:当精确阈值不可行时,可将阈值设为变量并最小化其与目标值(如12)的绝对偏差,实现“近似达标”。
该方法将业务规则精准编码为数学约束,避免启发式调参,是金融风控、动态权重分配等场景的可靠基础工具。










