You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scipy优化失败:为何续保倾向模型无法收敛?

保费优化任务中Scipy优化失败:奇异矩阵C错误(与分段续保倾向模型相关)

问题概述

执行利润最大化的客户保费优化任务,需满足整体续保率目标约束,但优化流程失败,运行result返回奇异矩阵错误,且最优保费与初始值完全一致。

错误输出

fun: -298.2633664479155
     jac: array([-0.82517242, -0.60799408, -0.44937897, -0.82517242, -0.72691727,
       -0.84170532, -0.74860764, -0.18024063, -0.74860764, -0.96907043])
 message: 'Singular matrix C in LSQ subproblem'
    nfev: 11
     nit: 1
    njev: 1
  status: 6
 success: False
       x: array([338.39, 409.57, 453.83, 337.21, 377.76, 330.7 , 366.62, 559.69,
       366.95, 156.04])

Jacobian矩阵符合预期(保费上升续保率下降,不同保单变化有差异)。

代码重现

import numpy as np
import pandas as pd
import scipy.optimize as opt

# 数据
cost_prices = np.array([293.2 , 371.08, 479.59, 328.2 , 295.23, 273.17, 303.83, 368.09,
        229.72, 208.64])
initial_premiums = ([338.39, 409.57, 453.83, 337.21, 377.76, 330.7 , 366.62, 559.69,
        366.95, 156.04])

# 目标函数
def objective(premiums, cost_prices):
    propensities = propensity_to_renew(premiums)
    profit = np.sum((premiums - cost_prices) * propensities)
    return -profit  # Scipy默认最小化,因此取负

# 分段续保倾向模型
Lower = [0, 215,    231.7,  248.4,  265.1,  281.8,  298.5,  315.2,  331.9,  348.6,  365.3,  382,    398.7,  415.4,  432.1,  448.8,  465.5,  482.2,  498.9,  515.6,  532.3,  549,    565.7,  582.4,  599.1,  615.8,  632.5,  649.2,  665.9,  682.6,  699.3,  716,    732.7,  749.4,  766.1,  782.8,  799.5,  816.2,  832.9,  849.6,  866.3,  883,    899.7,  916.4,  933.1,  949.8,  966.5,  983.2,  999.9,  1016.6, 1033.3, 1050]
Upper = [214.999,   231.7,  248.4,  265.1,  281.8,  298.5,  315.2,  331.9,  348.6,  365.3,  382,    398.7,  415.4,  432.1,  448.8,  465.5,  482.2,  498.9,  515.6,  532.3,  549,    565.7,  582.4,  599.1,  615.8,  632.5,  649.2,  665.9,  682.6,  699.3,  716,    732.7,  749.4,  766.1,  782.8,  799.5,  816.2,  832.9,  849.6,  866.3,  883,    899.7,  916.4,  933.1,  949.8,  966.5,  983.2,  999.9,  1016.6, 1033.3, 1050,   1000000]
COMBINED = [0,  -0.07492837,    -0.222324113125,    -0.366118206625,    -0.506502842875,    -0.64346552175, -0.77682655125, -0.906585931125,    -1.03292335375, -1.155851319,   -1.27517763475, -1.39108199325, -1.50357689425, -1.612457645875,    -1.717749248125,    -1.819798585,   -1.91807908025, -2.013117310375,    -2.104553891,   -2.192376322125,    -2.276801796,   -2.357805312375,    -2.4352071795,  -2.509174589125,    -2.579577849375,    -2.646521652,   -2.7098888055,  -2.769834001625,    -2.826369740125,    -2.879291329375,    -2.92881596125, -2.974713943625,    -3.017214968625,    -3.05629403625, -3.0917589545,  -3.123814415375,    -3.15225572675, -3.17731258075, -3.19875528525, -3.216776032625,    -3.231195130375,    -3.24217977075, -3.24977995375, -3.253765987375,    -3.254342563625,    -3.251304990375,    -3.24484545975, -3.234988971875,    -3.2215183345,  -3.204625739625,    -3.1841564955,  -3.17255777875]

elas = pd.DataFrame(
    {'Lower': Lower,
     'Upper': Upper,
     'COMBINED': COMBINED
    })

def propensity_to_renew(premium):
    prop_vec = []
    for prem in premium:
        val = prem
        lp = float(elas.query(' @val >= Lower and @val < Upper')['COMBINED'])
        prop_vec.append(1/(1+np.exp(-(-1.496+lp))))
    return prop_vec

# 续保率约束
def retention_constraint(premiums):
    propensities = propensity_to_renew(premiums)
    target_retention = 0.07  # 示例目标续保率
    return np.mean(propensities) - target_retention

# 约束与边界
constraints = [{'type': 'eq', 'fun': retention_constraint}]
bounds = [(0.7*premium,1.3*premium) for premium in initial_premiums]

# 执行优化
result = opt.minimize(objective, initial_premiums, args=(cost_prices), bounds=bounds, constraints=constraints)

optimal_premiums = result.x
optimal_profit = -result.fun

print("Optimal Premiums:", optimal_premiums)
print("Optimal Profit:", optimal_profit)

排查发现

替换为连续可导的logistic模型时,优化可正常完成:

def propensity_to_renew(premium):
    return 1 / (1 + np.exp(0.01 * (premium - 100)))

两种模型的续保倾向随保费变化趋势高度相似:均随保费上升单调下降,整体呈logistic曲线形态。

解决建议

1. 根本原因分析

原分段续保倾向模型是分段常数函数,在分段边界处不可导,而Scipy默认的SLSQP优化器依赖目标函数和约束函数的光滑性(连续可导)。非光滑点会导致雅可比矩阵计算异常,进而出现“奇异矩阵C”的错误,优化器无法找到有效搜索方向,直接返回初始值。

2. 具体修复方案

方案一:光滑化分段模型

将分段常数的COMBINED值插值为连续可导的函数,消除非光滑点:

from scipy.interpolate import CubicSpline

# 用区间中点作为插值节点
mid_points = [(l + u)/2 for l, u in zip(Lower[:-1], Upper[:-1])]
# 三次样条插值生成连续可导的lp函数
cs = CubicSpline(mid_points, COMBINED[:-1])

def propensity_to_renew_smoothed(premium):
    premium = np.asarray(premium)
    lp = cs(premium)
    # 处理超出插值范围的保费,使用边界值
    lp[premium < mid_points[0]] = COMBINED[0]
    lp[premium > mid_points[-1]] = COMBINED[-1]
    return 1/(1+np.exp(-(-1.496 + lp)))

替换原propensity_to_renew函数后,SLSQP优化器可正常运行。

方案二:更换支持非光滑优化的算法

使用COBYLA算法(无需计算雅可比矩阵),需将边界条件转化为不等式约束:

# 将边界转化为COBYLA兼容的不等式约束
bounds_constraints = []
for idx, (low_bound, high_bound) in enumerate(bounds):
    # 下限约束:x[idx] >= low_bound
    bounds_constraints.append({
        'type': 'ineq',
        'fun': lambda x, idx=idx, low=low_bound: x[idx] - low
    })
    # 上限约束:x[idx] <= high_bound
    bounds_constraints.append({
        'type': 'ineq',
        'fun': lambda x, idx=idx, high=high_bound: high - x[idx]
    })

# 合并续保率约束与边界约束
total_constraints = constraints + bounds_constraints

# 使用COBYLA执行优化
result = opt.minimize(
    objective, 
    initial_premiums, 
    args=(cost_prices), 
    method='COBYLA', 
    constraints=total_constraints,
    options={'maxiter': 1000}  # 可适当调整迭代次数
)

方案三:微调初始点避免边界问题

检查初始保费是否刚好落在Lower/Upper的分界点上,数值精度问题可能导致查询异常。可对初始值进行微小调整(如±0.01),避免落在分段边界。

内容的提问来源于stack exchange,提问作者JDSH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 14:05:54