4. 目标函数系数尺度差异引发数值警告:诊断与缩放策略
问题定位:系数尺度差异如何触发数值警告
目标函数中同时出现 1e-4 量级与 1e6 量级的系数时,单纯形法或内点法的基矩阵会因行/列尺度悬殊而条件数急剧恶化。求解器通常在预求解或首次迭代后抛出类似 Numerical trouble: matrix is badly scaled 的警告。诊断的核心不是"消除警告",而是判断警告是否由系数尺度引起,并量化缩放前后的条件数变化。
下面用一个可复现的教学模型说明诊断路径。模型含 3 个变量、2 条约束,目标函数系数刻意跨越 10 个数量级:
min f = 1e-4·x1 + 1e6·x2 + 1.0·x3
s.t. 2·x1 + 3·x2 + 1·x3 ≥ 10
1·x1 + 1·x2 + 4·x3 ≥ 8
x1, x2, x3 ≥ 0
将约束写成标准型 A x ≥ b,目标系数向量 c = [1e-4, 1e6, 1.0]。此时目标系数的动态范围为 1e6 / 1e-4 = 1e10,远超多数求解器默认的尺度容忍阈值(常见为 1e6~1e8)。
诊断:提取并比较条件数
条件数诊断分两步:先看目标系数本身的离散程度,再看缩放后基矩阵的条件数。用 NumPy 可直接计算:
import numpy as np
c = np.array([1e-4, 1e6, 1.0])
A = np.array([[2.0, 3.0, 1.0],
[1.0, 1.0, 4.0]])
# 目标系数动态范围
dyn_range = c.max() / c.min()
print("目标系数动态范围:", dyn_range) # 1e10
# 构造含目标行的扩展矩阵,评估整体尺度
M = np.vstack([c, A])
print("缩放前条件数:", np.linalg.cond(M))
# 按列最大值归一化(列缩放)
scale = np.abs(M).max(axis=0)
M_scaled = M / scale
print("缩放后条件数:", np.linalg.cond(M_scaled))
print("列缩放因子:", scale)
示例运行中,缩放前条件数通常在 1e10 量级,缩放后降至 1e1~1e2 量级。条件数下降 8~9 个数量级,说明尺度差异确实是数值警告的主因。注意:以上数值为教学示例的预期量级,实际值取决于具体矩阵,需以本地运行结果为准。
若缩放后条件数仍高于 1e8,说明问题不只是系数尺度,可能还叠加了约束线性相关或边界退化,应转向检查约束矩阵秩与基矩阵主元。
缩放方案选择与验证
常用缩放方案有三种,选择依据是系数分布形态:
| 方案 | 缩放因子 | 适用场景 | 注意事项 |
|---|---|---|---|
| 列最大值归一化 | sⱼ = max(|cⱼ|, maxᵢ|Aᵢⱼ|) | 系数分布不均、个别列主导 | 需回代 x = x'/s |
| 几何平均缩放 | sⱼ = √(min·max) | 系数跨度大但分布连续 | 可能放大中间量级系数 |
| 行+列双向缩放 | rᵢ·sⱼ 组合 | 约束行与变量列同时失衡 | 实现复杂,需迭代平衡 |
对本例,列最大值归一化已足够。验证闭环分三步:
- 缩放后求解:用
M_scaled构造模型,确认求解器不再报数值警告。 - 解回代:将缩放解
x'按x = x'/s还原,与未缩放模型的解(若可求得)比较目标值。 - 条件数复核:确认缩放后
cond(M_scaled) < 1e8,且目标值相对误差在1e-6以内。
# 回代验证
x_scaled = np.array([...]) # 缩放模型求得的解
x_orig = x_scaled / scale
f_orig = c @ x_orig
print("回代目标值:", f_orig)
# 与直接求解结果比较,相对误差应 < 1e-6
缩放只改变数值表示,不改变可行域与最优解。若回代后目标值偏差超过 1e-6,说明缩放因子选取不当或模型本身存在其他数值问题,应回到条件数诊断步骤重新排查。