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,说明问题不只是系数尺度,可能还叠加了约束线性相关或边界退化,应转向检查约束矩阵秩与基矩阵主元。
原始目标系数 c c₁ = 1e-4 c₂ = 1e6 c₃ = 1.0 动态范围 = 1e10 条件数诊断 cond(M) 缩放前 ≈ 1e10 cond(M) 缩放后 ≈ 1e1~1e2 阈值参考:1e8 判定:尺度差异为主因 列缩放策略 sⱼ = max|cⱼ|, |Aᵢⱼ| c' = c / s A' = A / s 解 x' 后回代 x = x'/s 缩放前后条件数对比(对数刻度示意) 缩放前 cond ≈ 1e10(超出阈值) 缩放后 cond ≈ 1e1~1e2(安全区) 阈值 1e8 结论:列缩放将条件数降低约 8~9 个数量级,数值警告消除,解经回代后与原始模型一致

缩放方案选择与验证

常用缩放方案有三种,选择依据是系数分布形态:

方案缩放因子适用场景注意事项
列最大值归一化sⱼ = max(|cⱼ|, maxᵢ|Aᵢⱼ|)系数分布不均、个别列主导需回代 x = x'/s
几何平均缩放sⱼ = √(min·max)系数跨度大但分布连续可能放大中间量级系数
行+列双向缩放rᵢ·sⱼ 组合约束行与变量列同时失衡实现复杂,需迭代平衡

对本例,列最大值归一化已足够。验证闭环分三步:

  1. 缩放后求解:用 M_scaled 构造模型,确认求解器不再报数值警告。
  2. 解回代:将缩放解 x' 按 x = x'/s 还原,与未缩放模型的解(若可求得)比较目标值。
  3. 条件数复核:确认缩放后 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,说明缩放因子选取不当或模型本身存在其他数值问题,应回到条件数诊断步骤重新排查。