尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

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

信用评分卡优化:线性规划与分支定界算法在金融风控中的应用 1. 从业务痛点到数学建模为什么信用评分卡优化绕不开线性规划在金融风控领域信用评分卡是决定一个人能否获得贷款、信用卡额度的核心工具。它的本质是将申请人的各项信息如年龄、收入、负债、历史信用记录等转化为一个分数分数越高违约风险越低。听起来很直接但实际操作中风控团队每年都要面临一个灵魂拷问我们现有的评分卡真的把风险识别和业务利润的平衡做到最好了吗这就是“信用评分卡优化”要解决的问题。它不是一个简单的调参而是一个复杂的资源分配问题。想象一下你手头有100万的营销预算目标是找到1000个新客户。市场上的人群千差万别有收入高但负债也高的“高潜高风险”客户有收入稳定但保守的“低潜低风险”客户。你的评分卡就像一把筛子决定了哪些人能通过。优化这把筛子意味着在不增加整体违约风险的前提下最大化通过客户的总价值如利息收入或者反过来在保证一定业务规模的前提下最小化预期的坏账损失。传统的逻辑回归评分卡其权重即每个变量的系数是通过历史数据统计拟合得到的它追求的是模型对历史数据的预测准确性如AUC最高。但这未必是业务上的最优解。业务最优解需要直接对利润、风险、通过率等业务指标进行建模和优化。这时线性规划Linear Programming, LP及其混合整数变体MILP就登场了。它们能将这些业务目标和约束如“坏账率不能超过2%”、“通过人数不能少于1000人”清晰地表达为数学方程并寻找那个在约束边界上的“最佳点”。所以当看到“信用评分卡优化”和“线性规划”联系在一起时其核心逻辑是将风控决策从一个统计预测问题转变为一个在明确业务规则下的资源优化问题。2023年的Mathorcup A题正是抓住了这个业界核心痛点要求参赛者用数学建模的思维为虚拟的银行设计一套更“聪明”的评分策略。2. 问题拆解构建信用评分卡优化模型的四大核心模块要动手解决这个问题我们不能一头扎进代码里必须先厘清模型的结构。一个完整的信用评分卡优化线性规划模型通常包含以下四个不可分割的模块。2.1 决策变量定义我们的“操作手柄”是什么决策变量是模型中可以调整的“旋钮”。在信用评分卡场景中主要有两类客户选择变量0-1变量对于每一个客户i定义一个二元决策变量x_i∈ {0, 1}。x_i 1表示给予该客户贷款通过x_i 0表示拒绝。这是最核心的决策也是问题复杂度的来源因为它引入了整数约束将问题从线性规划LP升级为混合整数线性规划MILP。评分卡阈值变量连续变量如果我们优化的是评分卡本身的阈值而非直接选人那么阈值T就是一个连续决策变量。当客户分数 T时通过否则拒绝。但更常见的优化是直接对客户进行选择因为这样能更精细地控制结果。在Mathorcup A题这类比赛中通常会给出一批客户样本数据包含每个客户的预测违约概率p_i、潜在收益r_i如利息、授信额度c_i等。此时决策变量就是针对这一个个具体客户的x_i。我们的目标就是决定哪些x_i该设为1。2.2 目标函数构建我们到底要最大化什么目标函数定义了“好”的标准。在商业场景中最常见的目标是最大化总期望利润。对于每个客户i如果通过他我们期望获得的利润是他正常还款带来的收益r_i减去他违约带来的损失违约概率p_i× 损失金额L_i通常L_i与额度c_i相关。因此总期望利润可以表示为Maximize Z Σ (x_i * (r_i - p_i * L_i))有时目标也可能是在风险约束下最大化通过人数抢占市场或者在业务规模约束下最小化期望损失保守经营。目标函数的选择直接决定了优化方向。2.3 约束条件刻画游戏的规则有哪些没有约束的优化是空中楼阁。信用评分卡优化必须考虑现实中的各种限制这些限制构成了模型的约束条件总体风险约束这是最重要的约束。银行管理层能容忍的坏账率有一个上限比如2%。这意味着所有通过客户的期望违约总额不能超过通过客户总额度的一个比例。Σ (x_i * p_i * c_i) / Σ (x_i * c_i) ≤ 2%注意这个约束是非线性的分母含有决策变量x_i。一个标准的线性化技巧是将其转化为线性约束Σ (x_i * p_i * c_i) ≤ 0.02 * Σ (x_i * c_i)然后移项得到Σ (x_i * (p_i * c_i - 0.02 * c_i)) ≤ 0。这样就变成了一个关于x_i的线性约束。业务规模约束银行可能有最低放款额度或客户数要求。Σ (x_i * c_i) ≥ 最低总额度或Σ x_i ≥ 最低客户数预算或资源约束总授信额度可能受到资本金限制。Σ (x_i * c_i) ≤ 总可用资金策略性约束例如为了客群多样性要求来自某个收入阶层的客户比例不能低于某个值。这同样可以通过线性不等式来表达。2.4 模型线性化技巧处理“非此即彼”的智慧如前所述我们的核心决策变量x_i是0或1的整数这天然构成了一个MILP问题。但有些业务逻辑会带来更复杂的非线性。例如一个常见的风控规则是“如果客户A和客户B属于同一家庭则不能同时给予两人大额贷款。”这会产生x_A * x_B这样的非线性项。处理这类问题的核心技巧是线性化。对于上述情况我们可以引入一个新的二元变量y来表示“是否同时给A和B贷款”并添加一组线性约束来等价描述原逻辑y ≤ x_Ay ≤ x_Bx_A x_B - 1 ≤ y然后将原约束x_A * x_B ≤ 0转化为对y的约束。这些技巧是将复杂的业务规则“翻译”成线性规划求解器能听懂的语言的关键。构建模型的过程就是一个不断将业务语言转化为数学语言的过程。3. 算法核心为什么分支定界/分支切割是求解此类MILP的利器当我们建立好一个包含0-1变量x_i的MILP模型后直接找到全局最优解并非易事。穷举所有2^n种客户组合n为客户数在数据量稍大时就是天文数字。这时就需要采用系统性的优化算法框架其中最经典、最有效的就是分支定界Branch-and-Bound, BB以及它的增强版分支切割Branch-and-Cut。3.1 分支定界算法框架像一棵决策树一样搜索你可以把分支定界想象成在一个巨大的迷宫里寻找最高点的宝藏最大化的目标函数。我们无法一眼看全但可以系统地探索。松弛与定界首先我们“放松”整数约束允许x_i在[0, 1]区间内取任何值这就得到了原问题的线性规划松弛问题。这个问题很容易用单纯形法等快速求解。它的最优解值比如最大利润一定是原整数问题最优解值的上界因为约束更少了。同时我们任意找一个可行的整数解比如一个简单的启发式策略得到的解其目标值作为下界。分支如果松弛解中某个x_k的值是0.3不是0或1这说明我们还没有找到整数解。我们就在这个节点上进行“分支”创建两个子问题一个强制x_k 0另一个强制x_k 1。这就像在迷宫里遇到了岔路口你决定先探索左边设x_k0的路再探索右边设x_k1的路。剪枝这是提高效率的关键。在探索子树时我们会不断求解子问题的松弛问题。界限剪枝如果某个子问题的松弛解值上界已经低于当前找到的最好整数解值下界那么这整条分支都不可能找到更好的整数解了直接剪掉不再探索。整数解剪枝如果某个子问题的松弛解碰巧全是整数那它就是该分支下的最优整数解。用它更新全局下界。不可行剪枝如果子问题的松弛问题本身就无解那这条分支也是死路剪掉。通过不断地分支、求解松弛问题、更新界限和剪枝搜索树的空间被大幅缩减最终我们能找到证明是最优的整数解或者一个在可接受误差内的近似最优解。3.2 分支切割算法的进阶用“切割平面”收紧搜索范围分支定界虽然有效但当问题规模很大时搜索树可能依然非常庞大。分支切割算法在分支定界的基础上增加了一个强大的武器切割平面Cutting Plane。问题的关键在于线性规划松弛问题的解空间一个多面体和整数解的解空间该多面体内的整数点集合之间通常有很大的“空隙”。松弛解如0.3, 0.7就在这个空隙里它给了我们一个过于“乐观”的上界。切割平面就是试图在松弛解空间上“砍”掉一块不包含任何整数最优解的区域从而让松弛问题的解空间更紧地包裹住整数解空间。这样得到的上界会更小、更准确。如何生成切割例如对于解x_k 0.3我们可以推导出一个线性不等式即切割使得所有整数解都满足它但这个分数解 (0.3) 不满足。加上这个切割后重新求解松弛问题解可能会被“推”向一个整数点或者至少得到一个更紧的上界。与分支结合分支切割算法在分支定界树的每个节点上都可能尝试添加多个切割平面来强化松弛问题然后再决定是否分支。这能极大地减少需要探索的分支数量。在实际求解中如使用CPLEX, Gurobi等商业求解器我们无需自己实现复杂的切割生成逻辑。求解器内置了强大的预求解和切割生成器如Gomory割平面、覆盖割平面等会自动应用分支切割框架。我们需要做的只是正确地建立模型然后调用求解器并理解其背后的工作原理以便在模型求解缓慢时能进行有效调优。4. 实战求解基于Python与PuLP/Gurobi的完整代码实现与解析理论说得再多不如一行代码。这里我将以一个简化的示例展示如何使用Python的优化库来构建并求解信用评分卡优化模型。我们将使用一个流行的开源库PuLP它提供了调用多种求解器的统一接口并模拟一个类似Mathorcup A题的小规模数据集。4.1 环境准备与问题数据模拟首先确保安装必要的库。PuLP默认带有一个基础的LP求解器但对于MILP问题建议配置更强大的求解器如开源的CBC可通过pip install pulp自动获取或者商业求解器Gurobi、CPLEX需单独安装并获取许可。# 导入必要的库 import pulp import numpy as np import pandas as pd # 设置随机种子确保结果可复现 np.random.seed(2023) # 模拟生成100个客户的数据 n_customers 100 customer_ids range(n_customers) # 随机生成违约概率0~0.3 收益100~500 额度1000~50000 default_probs np.random.uniform(0, 0.3, n_customers) revenues np.random.uniform(100, 500, n_customers) credits np.random.uniform(1000, 50000, n_customers) # 假设损失率为100%违约即损失全部额度 loss_given_default 1.0 expected_loss default_probs * credits * loss_given_default expected_profit revenues - expected_loss # 创建DataFrame便于查看 df pd.DataFrame({ Customer_ID: customer_ids, Default_Prob: default_probs, Revenue: revenues, Credit: credits, Expected_Profit: expected_profit }) print(df.head())4.2 构建线性规划模型定义问题、变量、目标与约束接下来我们使用PuLP来一步步构建MILP模型。我们的业务目标是在总体风险率期望损失/总授信额度不超过2%的前提下最大化总期望利润。# 1. 初始化问题指定为最大化问题 prob pulp.LpProblem(Credit_Scoring_Optimization, pulp.LpMaximize) # 2. 定义决策变量为每个客户创建一个0-1变量变量名格式为 x_i x_vars pulp.LpVariable.dicts(x, customer_ids, lowBound0, upBound1, catBinary) # 3. 定义目标函数最大化总期望利润 prob pulp.lpSum([x_vars[i] * expected_profit[i] for i in customer_ids]), Total_Expected_Profit # 4. 定义风险约束期望总损失 2% * 总授信额度 # 即 sum(x_i * p_i * c_i) 0.02 * sum(x_i * c_i) # 移项得到线性约束 sum(x_i * (p_i * c_i - 0.02 * c_i)) 0 prob (pulp.lpSum([x_vars[i] * (default_probs[i] * credits[i] - 0.02 * credits[i]) for i in customer_ids]) 0), Risk_Constraint_2Percent # 5. 可选定义业务规模约束例如至少通过30个客户 min_customers 30 prob (pulp.lpSum([x_vars[i] for i in customer_ids]) min_customers), Min_Customer_Constraint print(问题已构建完成。) print(prob)4.3 模型求解与结果分析现在我们可以调用求解器来求解这个MILP问题。PuLP会自动选择可用的求解器。# 使用CBC求解器进行求解PuLP默认会尝试使用CBC solver pulp.PULP_CBC_CMD(msgTrue, timeLimit30) # msgTrue显示求解日志 timeLimit设置时间限制秒 prob.solve(solver) # 打印求解状态 print(f求解状态: {pulp.LpStatus[prob.status]}) print(f最大化总期望利润 {pulp.value(prob.objective):.2f}) # 提取并分析结果 selected_customers [i for i in customer_ids if pulp.value(x_vars[i]) 1] rejected_customers [i for i in customer_ids if pulp.value(x_vars[i]) 0] print(f\n通过的客户数: {len(selected_customers)}) print(f拒绝的客户数: {len(rejected_customers)}) # 计算实际的风险率验证约束 total_expected_loss sum(default_probs[i] * credits[i] for i in selected_customers) total_credit sum(credits[i] for i in selected_customers) actual_risk_rate total_expected_loss / total_credit if total_credit 0 else 0 print(f实际风险率期望损失/总额度: {actual_risk_rate:.4%}) print(f约束要求风险率 2.000%) # 查看部分被选中和拒绝的客户特征例如按期望利润排序 df[Selected] [pulp.value(x_vars[i]) for i in customer_ids] print(\n--- 被选中的客户中期望利润最高的5位 ---) print(df[df[Selected]1].nlargest(5, Expected_Profit)[[Customer_ID, Default_Prob, Revenue, Credit, Expected_Profit]]) print(\n--- 被拒绝的客户中期望利润最低的5位 ---) print(df[df[Selected]0].nsmallest(5, Expected_Profit)[[Customer_ID, Default_Prob, Revenue, Credit, Expected_Profit]])4.4 结果解读与业务洞察运行上述代码后你会得到一份优化后的客户名单。分析这个结果远比得到一个数字更有价值模型有效性验证首先检查actual_risk_rate是否真的满足小于等于2%的约束。这是检验模型构建是否正确的基本步骤。洞察优化策略观察哪些客户被选中哪些被拒绝。通常你会发现**高利润、低风险的“明星客户”**几乎全部被选中。**低利润、高风险的“劣质客户”**几乎全部被拒绝。最具启发性的是中间地带那些“利润尚可但风险略高”或“利润较低但风险极低”的客户。模型的决策揭示了在给定风险约束下如何权衡“利润”和“风险”这两个维度。它可能为了满足“至少通过30人”的约束而选择了一些利润不高但风险极低的客户来稀释整体风险。与简单阈值法的对比一个常见的业务做法是设定一个分数阈值或违约概率阈值高于阈值就拒绝。你可以尝试按“期望利润”从高到低排序然后从高到低选择客户直到风险约束被打破。对比这种“贪婪算法”的结果和线性规划的结果你会发现线性规划的解通常更优因为它能进行全局统筹而不是局部贪心。注意在实际业务中客户数据量可能是数十万乃至百万级。直接对百万个0-1变量进行优化即使使用商业求解器也可能非常耗时。此时需要考虑采用启发式算法如遗传算法、模拟退火求取高质量可行解或者利用问题特性如客户可分群进行分解优化。但在像Mathorcup这类建模比赛中数据规模通常可控分支定界/切割框架足以在合理时间内求得精确最优解。5. 超越基础模型优化与高级考量在掌握了基础模型构建和求解后要真正让模型具备实用价值还需要考虑更多现实世界的复杂性。5.1 处理非线性约束与更复杂的业务规则我们之前将风险约束线性化了。但有些约束线性化起来更复杂。例如通过率波动约束要求最终通过的客户比例与当前通过率基于旧评分卡的偏差不能超过10%。这涉及到绝对值约束|Σx_i / N - P_old| ≤ 0.1。处理方法是引入辅助变量将其转化为两个线性约束。多个风险约束除了总体坏账率可能还有分段风险约束如对额度大于5万的客户群体其坏账率要求更严≤1.5%。这需要定义子集并添加额外的约束。依赖关系约束如前文提到的家庭关联约束需要引入辅助二元变量并进行线性化。构建一个健壮的模型需要耐心地将每一条业务规则“翻译”成线性的等式或不等式。5.2 模型稳定性与鲁棒性分析我们的模型严重依赖于输入数据尤其是违约概率p_i。这些概率来自一个预测模型如逻辑回归、XGBoost本身就有预测误差。如果预测不准优化结果可能严重偏离预期。敏感性分析这是关键一步。可以系统地微调关键参数如风险上限从2%变为1.8%或2.2%观察最优解总利润、通过客户数如何变化。这能帮助业务方理解风险容忍度对利润的边际影响。场景测试使用历史的、不同时间窗口的数据来训练预测模型和优化模型检验策略是否稳定。避免模型过度拟合到某一段特殊时期的数据上。鲁棒优化更高级的方法是采用鲁棒优化Robust Optimization或随机规划Stochastic Programming将违约概率的不确定性直接建模到优化框架中寻求一个在所有可能情景下都“不太坏”的策略。但这会极大增加模型复杂度和求解难度。5.3 与机器学习评分模型的协同迭代线性规划优化和机器学习预测不是孤立的它们可以形成一个闭环第一轮用历史数据训练信用评分模型如逻辑回归得到违约概率预测值p_i。第二轮基于预测的p_i和业务目标构建并求解上述线性规划模型得到最优的客户选择策略即哪些x_i1。第三轮将线性规划选择的结果一种“决策”作为新的标签或权重反馈回去重新训练或调整信用评分模型。例如那些被模型“艰难”拒绝的客户期望利润接近零可能是评分模型需要重点学习区分的样本。通过这种迭代评分模型不仅学习预测风险还会潜移默化地学习与最终业务目标利润对齐的特征模式。6. 参赛指南与常见陷阱从建模到求解的完整心路如果你正在准备Mathorcup或类似赛事基于这个题目的实战经验我想分享几条关键的避坑指南。6.1 数据预处理与特征工程的再思考比赛提供的数据通常需要清洗。对于信用评分卡问题要特别注意缺失值处理对于关键字段如收入、负债的缺失不能简单删除或填零。需要考虑使用中位数、均值填充或增加一个“是否缺失”的指示变量。在优化模型中如果某个客户的关键特征缺失其预测违约概率p_i的可靠性会下降在优化时可能需要更谨慎地对待例如在目标函数中给予一个保守的调整。特征标准化/分箱线性规划模型本身不要求特征标准化但你的预测模型用于生成p_i可能需要。更重要的是业务规则可能基于分箱后的特征如“年龄在30-40岁区间”。如果优化模型中需要直接使用这些特征来定义约束如“年轻客户占比”那么特征的分箱处理必须在建模前完成并保持一致。6.2 模型假设的检验与敏感性分析报告在论文中绝不能只呈现一个最终结果。必须展示你对模型关键假设的思考“违约概率准确”假设在结果分析部分必须讨论如果预测概率存在系统性偏差如整体低估了10%的风险你的优化策略会如何失效预期的坏账率会超标多少这体现了你对模型局限性的认识。参数敏感性分析如前所述系统地改变风险上限R_max如从1.5%到3%步长0.1%绘制“风险-利润”前沿曲线。这张图极具说服力它能直观展示为了降低一点风险需要牺牲多少利润。这往往是评委眼中的加分项。与基准策略的对比必须设立一个合理的基准Baseline例如现有策略模拟题目中可能给出的旧评分卡策略。贪婪策略按期望利润从高到低选择直到违反约束。随机策略。 清晰地用表格对比这些策略在利润、风险、通过率等核心指标上的表现突出你优化模型的价值。6.3 求解效率优化与代码实现细节当数据量增大时求解可能变慢。以下技巧可以提升效率设置初始可行解许多求解器如Gurobi允许你提供一个初始的可行整数解例如用贪婪算法得到的解。这能提供一个良好的下界加速剪枝过程。在PuLP中你可以在求解前通过设置变量的initialValue属性来提供初始解。调整求解器参数不要只使用默认参数。对于MILP可以尝试调整MIPGap设置一个可接受的最优间隙如0.01%让求解器在找到足够好的解后提前停止。TimeLimit设置合理的时间限制避免在复杂实例上无谓等待。Heuristics加大启发式搜索的力度以更快地找到可行解。利用问题特性简化模型仔细审视你的约束。有时可以通过预处理消除一些变量。例如如果某个客户的期望利润为负且风险很高那么在任何合理的目标和约束下他都不可能被选中。你可以预先将他的决策变量固定为0从而减少问题规模。最后将你的代码整理得清晰、模块化并添加详尽的注释。将模型构建、求解、结果分析和可视化分成不同的函数或模块。这不仅方便你自己调试也能让评委看到你良好的工程习惯。记住一个优雅、高效的求解方案和创新的模型本身同样重要。
返回列表