news 2026/9/11 0:20:04

线性规划与分支定界算法在金融风控信用评分卡优化中的应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
线性规划与分支定界算法在金融风控信用评分卡优化中的应用

1. 项目概述:当数学建模遇上金融风控

去年带学生打MathorCup,A题“信用评分卡优化”一出来,我们团队就意识到,这绝不是一个简单的套模型题。它本质上是一个典型的、在强约束下的资源分配与决策优化问题。题目要求我们基于给定的客户数据,构建并优化信用评分卡,核心目标是最大化银行的利润(或最小化风险损失),同时必须满足一系列现实业务规则,比如通过率、坏账率、风险敞口等限制。这听起来像是机器学习问题,但当你真正开始构建目标函数和约束条件时,你会发现,它的最优解往往藏在线性规划(Linear Programming, LP)及其扩展形式(如混合整数线性规划,MILP)的求解器里。

为什么是线性规划?因为评分卡的“优化”动作,无论是调整评分阈值、决定授信额度,还是组合不同的风控策略,其决策变量(如是否给某个分数段的客户放款)与最终的目标(利润)和约束(通过率)之间,在题目设定的框架下,通常可以表达为线性关系。你的目标函数(总利润)是各客户群利润的线性加权和,你的约束(总通过人数、总坏账金额)也是线性不等式。这就把一个复杂的金融风控问题,转化成了一个标准的、可求解的数学优化问题。

网络上热词里提到的“分支定界/分支切割算法求解MILP”,正是我们当时求解核心模型的关键。因为有些决策变量必须是整数(比如,选择哪几张评分卡,或者某个策略是否启用),这就构成了混合整数线性规划。直接求解MILP是NP-Hard问题,而分支定界(Branch-and-Bound)框架是求解它的主流精确算法。简单来说,它通过不断“分支”将问题分解为更小的子问题,并“定界”快速排除那些不可能包含最优解的分支,从而在可接受的时间内找到全局最优解或近似最优解。

所以,这个项目的核心路径非常清晰:将信用评分卡的业务优化问题,严谨地建模成一个线性规划(或混合整数线性规划)模型,然后利用高效的优化求解器(如Gurobi, CPLEX,或开源的OR-Tools、SCIP)进行计算,最终得到一整套可执行的、最优的信贷决策方案。下面,我就把这个从业务理解到模型求解的完整链条拆开,结合我们踩过的坑和实战心得,给大家捋清楚。

2. 问题拆解:从风控业务到数学模型

拿到题目和数据,千万别急着套代码。第一步,也是最关键的一步,是把模糊的业务需求翻译成精确的数学语言。这决定了你的模型是否合理,求解是否可行。

2.1 核心决策变量定义

决策变量是你的“操作手柄”。在信用评分卡优化中,通常有两种定义方式,对应不同的优化粒度:

方式一:基于客户分箱的决策这是最直观的方式。信用评分卡通常会将客户根据分数划分为若干个风险等级(例如,A、B、C、D、F五个等级)。那么,你的决策变量可以是:

  • x_A, x_B, ..., x_F:连续变量,表示对每个分数段客户的通过率(0到1之间)。例如,x_A = 0.95表示A级客户95%通过审批。
  • 或者,y_A, y_B, ..., y_F整数变量(0或1),表示是否对某个分数段采取“通过”策略。这常用于策略组合优化。

方式二:基于评分卡阈值的决策更精细的优化是直接调整评分卡的审批阈值(Cut-off Score)。例如,原阈值是600分,高于600的通过。优化后可能变为610分。这时,决策变量可以是阈值S本身。但这样目标函数和约束关于S通常是非线性的(因为客户分布是离散的),需要做一些线性化处理或将其转化为第一种方式。

在我们的解题中,采用了第一种方式,因为它能更自然地与线性规划结合。我们将客户按初始评分分成了20个组(bin),决策变量x_i表示第i组客户的放款比例。

2.2 目标函数构建:利润最大化

银行的最终目的是盈利。因此,目标函数通常是期望利润最大化。对于每一组客户i,我们需要计算:

  1. 单客期望收入:贷款利息收入。假设贷款额度为L_i,利率为r,则收入为L_i * r
  2. 单客期望损失:即坏账损失。这需要用到题目提供的违约概率(PD)违约损失率(LGD)。期望损失 =L_i * PD_i * LGD_i
  3. 单客期望利润利润_i = L_i * r - L_i * PD_i * LGD_i - 运营成本。其中运营成本可能是一个固定值或比例。

那么,对于该组所有客户,总期望利润就是:总利润 = Σ (客户数_i * x_i * 利润_i)。 这个Σ (客户数_i * x_i * 利润_i)就是一个关于决策变量x_i线性函数,完美符合线性规划的要求。

注意:这里的PD_iLGD_i需要从题目数据中估计。通常,同一分数段内的客户具有相似的PD。我们可以用该分数段历史违约客户的占比来估算PD,LGD有时题目会直接给出或假设一个固定值(如45%)。这是建模的第一个关键假设,需要明确说明。

2.3 约束条件梳理:业务的紧箍咒

光追求利润不行,银行经营有严格的风险控制和合规要求。这些就构成了模型的约束条件,同样是线性的。

  1. 总体通过率约束:银行可能希望控制整体业务规模或通过率在一个范围内。

    • 总体通过客户数 = Σ (客户数_i * x_i)
    • 约束可能为:总体通过率下限 <= (总体通过客户数 / 总客户数) <= 总体通过率上限
  2. 坏账率约束:控制整体风险水平。

    • 总坏账金额 = Σ (客户数_i * x_i * L_i * PD_i * LGD_i)
    • 总放贷金额 = Σ (客户数_i * x_i * L_i)
    • 约束为:(总坏账金额 / 总放贷金额) <= 坏账率上限。注意,这是一个比值约束,但可以通过变形转化为线性约束:总坏账金额 <= 坏账率上限 * 总放贷金额
  3. 高风险客户限制:对评分最低的几组客户(如F级),可能严格限制其通过率甚至禁止通过。

    • x_F <= 0.05x_F = 0
  4. 预算或资本约束:总放贷金额不能超过可用资金总额B

    • Σ (客户数_i * x_i * L_i) <= B
  5. 决策变量自身约束0 <= x_i <= 1,对于某些需要整数决策的,则y_i ∈ {0, 1}

将这些目标函数和约束条件用数学公式写出来,一个完整的信用评分卡优化线性规划模型就诞生了。它看起来就像这样:

Maximize: Σ_i (N_i * x_i * P_i) Subject to: Σ_i (N_i * x_i) / N_total >= P_min Σ_i (N_i * x_i * L_i * PD_i * LGD_i) <= LR_max * Σ_i (N_i * x_i * L_i) Σ_i (N_i * x_i * L_i) <= B 0 <= x_i <= 1, for all i (可能还有 x_j <= C_j 对于特定高风险组j)

其中,N_i是i组客户数,P_i是单客利润。

3. 模型求解:算法选择与实现细节

模型建好了,怎么解?对于纯线性规划(LP),有成熟的单纯形法(Simplex)和内点法(Interior-Point)可以高效求解全局最优解。但我们的模型往往包含整数变量(比如必须选择3张评分卡中的2张),这就变成了MILP,需要更专门的算法。

3.1 求解器:你的计算引擎

不要试图自己从头实现单纯形法或分支定界算法,那是科研人员的工作。我们应该站在巨人的肩膀上,使用成熟的优化求解器:

  • 商业求解器(性能最强)

    • Gurobi:学术界和工业界公认的标杆,对学术免费,速度和稳定性极佳。MathorCup这类比赛通常允许使用。
    • CPLEX:IBM的老牌产品,同样非常强大。
    • 它们都提供了Python、Java、C++等接口,集成非常方便。
  • 开源求解器(免费可选)

    • OR-Tools (Google):谷歌推出的优化工具套件,内置了多个求解器,对MILP支持良好,文档丰富,是比赛中的热门选择。
    • SCIP:目前最强大的非商业开源混合整数规划求解器之一。
    • PuLP (Python):一个建模库,可以调用多种后端求解器(包括CBC、GLPK等)。

我们的选择是:Python + PuLP + Gurobi。PuLP提供了非常直观的建模语法,而Gurobi作为后端求解器保证了求解效率。对于无法获得Gurobi许可的情况,PuLP默认的CBC求解器也能应付中小规模问题。

3.2 求解过程与代码框架

下面是一个高度简化的、基于PuLP的核心代码框架,展示了如何将上述数学模型“翻译”成代码:

import pulp import pandas as pd # 1. 读取和处理数据 data = pd.read_csv('customer_data.csv') # 假设数据已按评分分组并计算好所需字段 # data 中包含列:bin, customer_count, avg_loan, pd, lgd, interest_rate, operation_cost # 2. 创建问题实例 # 最大化问题 prob = pulp.LpProblem('Credit_Scorecard_Optimization', pulp.LpMaximize) # 3. 定义决策变量 # 为每个分箱创建一个连续变量,代表放款比例,范围在[0, 1] x = pulp.LpVariable.dicts('x', data['bin'].tolist(), lowBound=0, upBound=1) # 4. 构建目标函数 # 计算每个分箱的单客期望利润 data['profit_per_customer'] = data['avg_loan'] * data['interest_rate'] - \ data['avg_loan'] * data['pd'] * data['lgd'] - \ data['operation_cost'] # 总利润 = sum(客户数 * 放款比例 * 单客利润) prob += pulp.lpSum([data.loc[i, 'customer_count'] * x[data.loc[i, 'bin']] * data.loc[i, 'profit_per_customer'] for i in data.index]) # 5. 添加约束条件 # 5.1 总体通过率约束 (例如,不低于30%) total_customers = data['customer_count'].sum() prob += pulp.lpSum([data.loc[i, 'customer_count'] * x[data.loc[i, 'bin']] for i in data.index]) >= 0.3 * total_customers # 5.2 坏账率约束 (例如,不超过5%) # 总坏账金额 total_bad_debt = pulp.lpSum([data.loc[i, 'customer_count'] * x[data.loc[i, 'bin']] * \ data.loc[i, 'avg_loan'] * data.loc[i, 'pd'] * data.loc[i, 'lgd'] for i in data.index]) # 总放贷金额 total_loan = pulp.lpSum([data.loc[i, 'customer_count'] * x[data.loc[i, 'bin']] * data.loc[i, 'avg_loan'] for i in data.index]) prob += total_bad_debt <= 0.05 * total_loan # 5.3 高风险组约束 (例如,最低分箱通过率不超过1%) lowest_bin = data['bin'].min() # 假设bin是数值或可排序 prob += x[lowest_bin] <= 0.01 # 6. 求解问题 # 使用CBC求解器(开源),如果安装了Gurobi,可以替换为 pulp.GUROBI() prob.solve(pulp.PULP_CBC_CMD(msg=False)) # 7. 输出结果 print(f"求解状态: {pulp.LpStatus[prob.status]}") print(f"最大化总利润: {pulp.value(prob.objective):.2f}") for b in data['bin']: if x[b].varValue > 1e-5: # 忽略接近零的值 print(f"分箱 {b}: 建议放款比例 = {x[b].varValue:.3f}")

这段代码清晰地展示了从建模到求解的流水线。关键在于第4步和第5步,如何正确地将业务逻辑转化为线性表达式。

3.3 关于“分支定界”算法

当我们引入整数变量时(例如y_i ∈ {0,1}表示是否对第i组客户采用高利率策略),PuLP和Gurobi在调用prob.solve()时,会自动启用分支定界算法来求解这个MILP问题。你不需要手动实现它,但理解其原理对调试和解释结果很有帮助:

  1. 松弛:首先忽略整数约束,求解对应的线性规划松弛问题(LP Relaxation)。这会得到一个目标值上界(对于最大化问题)。
  2. 分支:如果松弛解中某个整数变量y_i的值是小数(比如0.7),就创建两个新的子问题:一个强制y_i = 0,另一个强制y_i = 1。这就像一棵树的分叉。
  3. 定界与剪枝:求解每个子问题的松弛解。如果某个子问题的解比当前已知的整数解还差,或者它的松弛解都无法超过当前最优整数解,那么这个分支就可以被“剪掉”(不再探索),因为它不可能产生更好的整数解。
  4. 迭代:不断分支、求解、定界、剪枝,直到找到满足整数要求的、且被证明是最优或接近最优的解。

在求解日志中,你可能会看到“Gap”在逐渐缩小,这个Gap就是当前最优整数解与全局上界之间的差距,是衡量求解进度和精度的关键指标。

4. 方案优化与策略分析

得到一组最优的x_i(放款比例)只是第一步。更重要的是如何解读这个结果,并形成可执行的业务策略。

4.1 结果解读与策略生成

求解器输出的x_i可能是一些小数,如x_A=0.95, x_B=0.8, x_C=0.3, x_D=0.01, x_F=0.0。这直接翻译成业务策略:

  • A级客户:优质客户,给予95%的通过率,几乎全部放行。
  • B级客户:次优客户,通过率80%,可以适当收紧。
  • C级客户:中等风险客户,通过率30%,需要严格筛选。
  • D级客户:高风险客户,仅1%通过,近乎拒绝。
  • F级客户:极高风险客户,完全拒绝。

基于此,可以制定差异化审批策略

  1. 自动通过:评分高于A阈值的,系统自动通过。
  2. 人工复审:评分在B和C区间的,转入人工审批流程,结合其他信息综合判断。
  3. 自动拒绝:评分低于D阈值的,系统自动拒绝。

4.2 敏感性分析与“What-If”场景

线性规划的一个巨大优势是便于进行敏感性分析(Sensitivity Analysis)。这能回答业务非常关心的问题:“如果某个条件变化了,我的最优策略和最大利润会怎样变?”

  • 约束右端值变化的影响:例如,如果监管要求的坏账率上限从5%收紧到4%,我的最大利润会下降多少?这个信息在求解报告中称为“影子价格(Shadow Price)”或“对偶价格(Dual Price)”。它量化了放松或收紧一单位约束所带来的边际利润变化。这对于资源分配和谈判极具价值。
  • 目标函数系数变化的影响:如果某个客户群的利率(r)或违约率(PD)预估发生了变化,当前的最优解是否依然最优?求解器提供的“目标系数允许增减范围”可以告诉你答案。

在比赛中,进行深入的敏感性分析,并据此提出动态策略调整建议,是论文的重要加分项。例如,你可以指出:“在当前市场环境下,坏账率约束是限制利润的主要瓶颈,其影子价格为X。这意味着,如果银行能通过提升催收能力将坏账损失降低1个百分点,理论上可增加Y百万元利润。”

4.3 模型扩展:多评分卡与策略组合

原题可能更复杂,比如提供多张基础评分卡(如“收益优先型”、“风险规避型”),要求你从中选择几张进行组合,并对不同客户群体应用不同的评分卡。这就引入了0-1整数变量

  • 定义变量z_k ∈ {0,1}:是否选用第k张评分卡。
  • 定义变量y_{i,k} ∈ {0,1}:是否对第i组客户使用第k张评分卡。
  • 需要添加约束:Σ_k z_k <= K(最多选K张卡),且y_{i,k} <= z_k(只有被选中的卡才能被使用)。
  • 目标函数变为:Max Σ_i Σ_k (客户数_i * y_{i,k} * 利润_{i,k})

这个模型明显更复杂,变量更多,求解时间更长。但建模思路一脉相承,只是约束条件更丰富了。这时,求解器的性能差异就会体现出来。

5. 实战心得与避坑指南

最后,分享一些在实战中总结出来的、教科书上不会写的经验。

5.1 数据预处理是地基

模型再漂亮,数据不准全白搭。

  • 缺失值处理:对于关键的PD、LGD字段,如果缺失,不能简单删除或填0。需要根据业务逻辑,用同组均值、中位数,或通过简单模型(如基于其他特征的回归)进行插补,并说明处理方法。
  • 异常值处理:对于明显不符合逻辑的极端值(如贷款额度为负),要查明原因,是数据错误还是特殊业务(如冲正交易)。通常需要与业务方确认,或采用盖帽法(Capping)进行处理。
  • 变量转换:线性规划要求线性关系。如果原始变量与目标之间可能存在非线性,可以考虑分段线性化或引入辅助变量。但在信用评分中,经过分箱处理后,组内用线性近似通常是可接受的。

5.2 模型假设必须清晰

所有模型都是对现实的简化,必须明确你的假设。

  • 独立性假设:我们假设不同客户之间的违约是独立的。现实中可能存在系统性风险导致违约相关,但题目数据通常不包含这类信息。
  • 参数稳定性假设:我们使用历史数据估计的PD、LGD来预测未来,假设这些参数在未来一段时间内是稳定的。
  • 明确写下这些假设,并在论文的“模型评价与推广”部分讨论这些假设不成立时的影响,这体现了建模的严谨性。

5.3 求解效率与规模平衡

  • 问题规模:客户分箱数(决策变量数)不宜过多也不宜过少。过多(如超过1000个)可能导致求解变慢;过少(如少于10个)则策略过于粗糙,失去优化意义。通常20-50个分箱是一个合理的范围。
  • 整数变量带来的计算挑战:MILP的求解时间随整数变量数量指数级增长。如果模型中有大量0-1变量,求解可能非常耗时。可以尝试:
    1. 先求解LP松弛问题,观察哪些变量在松弛解中已经是0或1,将其固定。
    2. 设置合理的求解时间限制(Time Limit)和最优间隙(MIP Gap)。比如,设置“在1小时内找到Gap小于1%的解即可”,这在商业应用中很常见。
    3. 使用启发式方法(如贪心算法)先找到一个较好的可行解,作为求解器的初始解(Warm Start),可以大幅加速求解过程。

5.4 结果验证与业务合理性检查

求解器给出的“数学最优解”未必是“业务可行解”。

  • 检查极端值:是否出现了某个分箱通过率为99.9%,而相邻分箱为0%这种跳跃过大的情况?这可能在数学上最优,但业务上难以解释(风险是连续的)。可以考虑添加平滑性约束,如|x_i - x_{i+1}| <= δ
  • 进行压力测试:用另一份验证集(Out-of-Sample)数据,按照优化后的策略模拟运行,计算实际的关键指标(利润、坏账率),看是否与模型预测相符。这是检验模型泛化能力的金标准。
  • 与基准策略对比:一定要设置一个基准策略,例如“所有分数高于600的客户全部通过”。清晰地展示你的优化策略相比基准策略,在利润、风险等指标上提升了多少百分比,用数据说话。

信用评分卡优化是一个完美的交叉领域课题,它要求你既懂金融风控的业务逻辑,又能熟练运用运筹优化的数学工具。通过线性规划建模,我们可以将复杂的业务决策问题转化为清晰的计算问题,从而找到在既定规则下的“最优解”。这个过程本身,就是数据驱动决策的核心体现。希望这份从实战中总结的指南,能帮你下次面对类似问题时,思路更清晰,下手更有力。记住,建模的关键不在于用了多复杂的算法,而在于你是否准确地把业务问题“翻译”成了数学语言。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 4:12:58

从内存计算走向 PB 级数据湖,深入理解 SAP HANA Cloud, data lake 的架构、SQL 分析与企业级数据分层

很多熟悉 SAP HANA 的开发者和架构师,第一次看到 SAP HANA Cloud, data lake 时,很容易把它理解成给 HANA 增加了一块容量更大的廉价磁盘。真正进入 SAP HANA Cloud 的架构以后,会发现这种理解明显低估了它。 SAP HANA Cloud, data lake 不是单纯的归档空间,也不是只能保存…

作者头像 李华
网站建设 2026/9/2 20:25:25

AI 内容创作时代:在生成与把关之间找回人味儿

AI 把内容生产的门槛压得很低。过去需要一个小团队分工完成的视频、文案、代码初稿&#xff0c;现在一个人守着几个生成式工具就能产出像模像样的结果。这种变化对每一位内容创作者、开发者和运营者来说都是现实&#xff1a;文字可以生成&#xff0c;图片可以生成&#xff0c;视…

作者头像 李华
网站建设 2026/9/2 1:27:16

索引与sql优化

索引概述索引是一种数据结构,帮助mysql高效获取数据无索引:全表扫描,效率低有索引:索引优点:提高数据查询效率通过索引的排序,降低了数据排序成本索引缺点:占用更多空间但增删改的效率会降低结构Btree最多4个,如果迎来第五个,则则中间的向上Btree与Btree的区别:1.所有数据都会在…

作者头像 李华
网站建设 2026/9/1 23:33:06

机器人开发全链路技术地图:从仿真到真机部署实战指南

这次我们换个角度聊机器人。它看起来是一堆电机、传感器和金属结构&#xff0c;但真正把机器人跑起来&#xff0c;牵涉到仿真、算法、硬件驱动、工业通讯和运维集成。无论你玩的是 ROS 2 移动机器人底盘、ABB 机械臂&#xff0c;还是宇树四足机器人&#xff0c;底层逻辑都一样&…

作者头像 李华
网站建设 2026/9/2 18:48:16

数学建模解题操作系统:认知-工具-逻辑-反思四层架构

1. 这不是“押题秘籍”&#xff0c;而是一套可复用的建模解题操作系统“2023亚太杯数学建模ABC题思路代码模型分析”——看到这个标题&#xff0c;很多同学第一反应是&#xff1a;赶紧找现成答案抄&#xff01;但作为连续带队参加亚太杯、美赛、国赛十年的指导老师&#xff0c;…

作者头像 李华