
1. 从传统优化到量子计算信用评分卡组合问题的范式转移在金融风控领域信用评分卡模型是评估个人或企业信用风险的核心工具。银行或金融机构通常不会只依赖单一评分卡而是会构建一个评分卡组合通过加权或规则集成的方式综合多个模型的预测结果以期达到更稳定、更精准的风险评估效果。这就引出了一个经典的优化问题如何从一组备选评分卡中挑选出一个最优的子集并确定其权重使得组合后的模型在某个业务指标如总利润、KS值、AUC等上表现最佳同时满足一些业务约束如通过率、坏账率控制这个问题在数学上可以归结为一个复杂的组合优化问题。传统的求解方法如穷举、整数规划、启发式算法遗传算法、模拟退火等在面对几十甚至上百个备选评分卡时往往会遇到“组合爆炸”的困境。计算时间随着问题规模呈指数级增长使得在有限时间内找到全局最优解变得异常困难很多时候我们只能接受一个“还不错”的局部最优解。近年来随着量子计算特别是量子退火和相干伊辛机等专用量子计算硬件的快速发展为解决这类组合优化问题提供了全新的思路。其核心是将我们的优化目标映射成一个物理系统如伊辛模型的能量最低状态通过量子隧穿等效应有望更高效地跳出局部最优寻找到全局最优或近似全局最优的解。2023年MathorCup的A题正是将这一前沿科技与金融风控的实际需求相结合要求我们探索量子计算机在信用评分卡组合优化中的应用。这不仅仅是套用一个新工具那么简单。它要求我们深入理解两个领域的“语言”一方面要能将金融风控的业务目标和约束精准地翻译成数学优化模型另一方面要能将这个数学模型进一步“编译”成量子计算硬件能理解的格式——通常是二次无约束二值优化模型。这个过程充满了挑战也充满了乐趣。接下来我将以一个从业者的视角详细拆解这道赛题的完整建模思路、QUBO模型构建技巧、以及关键的代码实现细节并分享我在模拟求解过程中的一些心得和避坑点。2. 问题拆解与数学建模将业务语言转化为优化方程面对一个实际问题第一步永远是清晰地定义它。我们假设手头有N个备选的信用评分卡模型每个模型对一批样本的预测结果如“好客户”概率或评分是已知的。我们的目标是选择一个包含K个评分卡的子集并为选中的每个评分卡分配一个权重最终通过加权集成的方式得到组合评分。2.1 定义决策变量与目标函数首先定义最核心的决策变量。我们引入两组变量选择变量 x_i这是一个二值变量0或1。x_i 1表示第 i 个评分卡被选中进入组合x_i 0则表示未被选中。这里 i 1, 2, ..., N。权重变量 w_i这是一个连续变量或离散化后的变量表示第 i 个评分卡在组合中的权重。通常我们要求权重非负且所有选中评分卡的权重之和为1归一化约束。一个关键的建模技巧是权重变量只对选中的评分卡有意义。因此我们可以将权重表示为w_i s_i * v_i其中v_i是一个非负的连续权重分量而s_i是一个与x_i相关的开关。一种简化且适用于QUBO建模的方式是直接对权重进行离散化编码。目标函数是我们优化的方向。在信用评分场景中常见的目标有最大化总利润需要根据评分卡预测结果、贷款利率、资金成本、违约损失等计算每笔贷款的预期利润求和。最大化KS值/AUC衡量模型区分好坏客户的能力。最小化风险如VaR在给定置信水平下控制最坏的损失。假设我们以“最大化总利润”为目标。那么对于每个客户样本组合评分卡的预测结果将是各选中评分卡预测结果的加权平均。根据该预测结果决定是否授信例如评分高于阈值T则通过进而计算出这笔业务的预期利润。将所有样本的利润相加就得到了总利润P_total。我们的目标是Maximize P_total。2.2 识别并量化约束条件没有约束的优化是不切实际的。业务约束通常包括组合大小约束选中的评分卡数量必须等于一个指定值 K。数学表达为sum_{i1}^{N} x_i K。这是最核心的组合约束。权重归一化约束所有选中评分卡的权重之和为1。即sum_{i1}^{N} w_i 1且w_i 0。这里需要注意w_i和x_i的关联当x_i0时必须强制w_i0。业务指标约束例如整体通过率不能低于某个值P_min整体坏账率不能高于某个值B_max。这些约束将组合的预测结果与最终的业务统计量联系起来是最复杂的一环因为它们是关于决策变量x_i和w_i的复杂函数。2.3 构建混合整数规划模型将上述目标与约束整合我们可以得到一个混合整数非线性规划模型Maximize: P_total(x, w) Subject to: (1) sum_{i1}^{N} x_i K, (2) sum_{i1}^{N} w_i 1, (3) w_i 0 for all i, (4) w_i M * x_i for all i, (这是一个“大M”约束确保如果x_i0则w_i必须为0) (5) PassRate(x, w) P_min, (6) BadRate(x, w) B_max.其中M是一个足够大的常数。这个模型已经清晰地描述了问题但它包含连续变量、整数变量、非线性目标和非线性约束直接求解非常困难。而量子退火等硬件擅长求解的是二次无约束二值优化问题。因此我们的下一个关键步骤就是进行模型转化与简化。3. 通往QUBO模型转化、线性化与惩罚函数技巧QUBO问题的标准形式是Minimize y x^T Q x其中x是二值决策变量向量元素为0或1Q是一个实对称矩阵。我们的任务是把MINLP模型“塞进”这个形式里。3.1 决策变量的编码策略首先我们需要用二值变量表示所有信息。选择变量 x_i本身已经是二值变量可以直接使用。权重变量 w_i这是最大的挑战。我们需要将其离散化。例如假设我们要求权重精确到小数点后两位且总和为1。我们可以将1“分割”成100个最小单位0.01。那么每个评分卡的权重可以表示为占有多少个这样的单位。引入二值变量z_{i, r}其中r 1, 2, ..., R例如R100。z_{i, r} 1表示第 i 个评分卡获得了第 r 个权重单位。 那么权重w_i可以近似表示为w_i (1 / R) * sum_{r1}^{R} z_{i, r}。 同时我们需要约束每个权重单位最多只能被分配一次sum_{i1}^{N} z_{i, r} 1for all r。并且只有当评分卡被选中时它才能获得权重单位sum_{r1}^{R} z_{i, r} R * x_i。通过这种“资源分配”式的编码我们将连续的权重变量转化为了大量的二值变量z_{i, r}。问题规模显著增大但结构变得适合QUBO。3.2 处理约束惩罚函数法QUBO是无约束的。如何处理原有的等式和不等式约束答案是惩罚函数法。我们将约束作为惩罚项加入目标函数中。如果约束被违反惩罚项会产生一个很大的正成本从而使目标函数值变差迫使优化器寻找满足约束的解。等式约束sum x_i K转化为惩罚项A * (sum x_i - K)^2。当选中数量恰好等于K时此项为0否则为正。A是一个足够大的正数惩罚系数。权重单位分配约束sum_i z_{i, r} 1类似地转化为B * sum_{r} (sum_i z_{i, r} - 1)^2。关联约束sum_r z_{i, r} R * x_i这是一个不等式约束。我们可以将其转化为等式约束引入松弛变量或者用一个惩罚项来抑制x_i0时z_{i, r}为1的情况C * sum_i [ (1 - x_i) * (sum_r z_{i, r}) ]。当x_i0时如果任何z_{i, r}1此项为正。业务约束如通过率这是最复杂的部分。PassRate(x, z)是一个关于所有x_i和z_{i, r}的函数。假设我们计算出的通过率为P约束为P P_min即P_min - P 0。我们可以引入一个松弛变量s 0将其转化为等式P s P_min然后对s进行二值编码最后将等式P s - P_min 0作为惩罚项加入目标函数。另一种更实用的近似方法是在目标函数中直接加入-D * (P - P_min)如果P P_min此项为负是奖励如果P P_min此项为正是惩罚。但这种方法需要谨慎调整系数D并可能无法严格保证约束。3.3 构建最终的QUBO矩阵假设我们最终的二值变量向量为y它由所有的x_i和z_{i, r}拼接而成。我们的原始目标是最大化总利润P_total。在QUBO中我们习惯最小化目标。因此我们将目标设为Minimize H -P_total Penalty。P_total本身是关于y的函数。由于y是二值的任何函数都可以展开成其变量的多项式形式。在我们的问题中P_total可以表示为y的线性项和二次项之和。具体推导需要根据利润计算的具体公式进行。例如对于一个样本组合评分是各模型评分的加权平均决策通过与否取决于该平均分是否超过阈值这本身就是一个包含逻辑判断的非线性函数。通常我们需要对其进行线性化近似例如通过引入辅助二值变量来表示“是否通过”这个决策。最终经过一系列展开、线性化和合并同类项我们可以将目标函数H写成标准的二次型H y^T Q y constant。这里的常数项不影响优化可以忽略。矩阵Q的非对角元素Q_{ij}表示变量y_i和y_j同时为1时的成本或收益对角元素Q_{ii}表示变量y_i为1时的成本。一个关键的实操心得惩罚系数A, B, C, D的选取至关重要。系数太小约束容易被违反系数太大可能会淹没原始目标-P_total导致找到的解虽然满足约束但利润很低。一个经验法则是让惩罚项的数量级与原始目标项的数量级相匹配并通过多次试验来调整。4. 代码实现从数据预处理到模拟退火求解由于真正的量子计算机访问受限我们通常使用经典模拟退火或量子退火模拟器来验证QUBO模型。下面以Python为例勾勒关键代码模块。4.1 数据准备与变量编码import numpy as np import pandas as pd from dimod import Binary, quicksum, SampleSet from neal import SimulatedAnnealingSampler # 假设有N个评分卡M个样本 N 20 M 10000 K 5 # 选择5个评分卡 R 20 # 将权重离散化为20个等级精度0.05 # 1. 生成模拟数据每个评分卡对每个样本的预测概率 scores np.random.rand(N, M) # 形状 (N, M) # 2. 生成样本的真实标签和利润参数 true_label np.random.randint(0, 2, M) # 0坏客户1好客户 loan_amount np.random.uniform(1000, 5000, M) interest_rate 0.08 loss_given_default 1.0 # 3. 定义二值变量 x [Binary(f‘x_{i}’) for i in range(N)] # 选择变量 z [[Binary(f‘z_{i}_{r}’) for r in range(R)] for i in range(N)] # 权重分配变量 # 将所有变量拉平到一个列表便于构建QUBO all_vars [] var_index_map {} idx 0 for i in range(N): var_index_map[f‘x_{i}’] idx all_vars.append((idx, f‘x_{i}’)) idx 1 for r in range(R): var_index_map[f‘z_{i}_{r}’] idx all_vars.append((idx, f‘z_{i}_{r}’)) idx 1 num_vars len(all_vars)4.2 构建QUBO目标函数这是最核心也是最繁琐的部分。我们需要计算利润的数学期望并将其表达为二值变量的函数。def calculate_profit_for_sample(sample_scores, selected_indices, weights, threshold0.5): 计算单个样本在给定选中评分卡和权重下的利润。这是一个简化示例。 combined_score 0 for idx, w in zip(selected_indices, weights): combined_score w * sample_scores[idx] if combined_score threshold: # 决定放款 if true_label_of_sample 1: # 好客户 profit loan_amount * interest_rate else: # 坏客户 profit -loan_amount * loss_given_default else: profit 0 # 拒绝无利润无损失 return profit # 由于上述函数包含条件判断直接写入QUBO困难我们需要线性化。 # 一个常见技巧引入辅助二值变量 d_j 表示是否对样本j放款。 # 那么利润 d_j * [ (好客户利润)* prob_good_j (坏客户利润)* prob_bad_j ] # 其中 prob_good_j 和 prob_bad_j 是基于组合评分估计的该样本为好/坏的概率。 # 这又引入了组合评分与概率的关系通常需要分段线性近似。 # 鉴于其复杂性在竞赛或初步验证中我们常采用一种简化策略 # 预先计算每个评分卡i对每个样本j的“基础利润” contribution[i, j]。 # 然后假设组合决策是简单的“投票”或“平均分”这允许我们将总利润近似为 # P_total ≈ sum_i sum_j (w_i * contribution[i, j] * x_i) 的某种形式。 # 注意这只是一个高度简化的示意真实模型要复杂得多。 # 假设我们有了一个线性化后的利润表达式 P_linear sum_i c_i * x_i sum_{i,r} d_{i,r} * z_{i,r} sum_{i,j,r,s} e_{i,j,r,s} * z_{i,r} * z_{j,s} # 其中系数c, d, e需要根据业务逻辑预先计算。 # 这里我们用随机系数示意构建QUBO Q np.zeros((num_vars, num_vars)) np.random.seed(42) # 添加利润目标项最小化负利润 for i in range(N): idx_x var_index_map[f‘x_{i}’] Q[idx_x, idx_x] np.random.randn() * 10 # 对角项模拟单个评分卡的贡献 for r in range(R): idx_z var_index_map[f‘z_{i}_{r}’] Q[idx_z, idx_z] np.random.randn() * 5 # 添加x_i和z_{i,r}的耦合项例如选中才有权重 Q[idx_x, idx_z] np.random.randn() * 20 Q[idx_z, idx_x] np.random.randn() * 20 # 添加约束惩罚项 A 1000 # 选择K个评分卡的惩罚系数 # 惩罚项: A * (sum x_i - K)^2 A*(sum x_i)^2 - 2A*K*(sum x_i) A*K^2 # 常数项A*K^2可忽略。二次项: A * sum_{i} sum_{j} x_i x_j (i!j) 以及 A * sum_i x_i (因为x_i^2 x_i) for i in range(N): idx_i var_index_map[f‘x_{i}’] Q[idx_i, idx_i] A * (1 - 2*K) # 来自 -2A*K * x_i 和 A * x_i^2 for j in range(i1, N): idx_j var_index_map[f‘x_{j}’] Q[idx_i, idx_j] 2 * A # 因为 (sum x_i)^2 展开后x_i*x_j的系数是2A Q[idx_j, idx_i] 2 * A # 添加权重单位唯一分配约束 sum_i z_{i,r} 1 的惩罚项 (系数B) B 800 for r in range(R): for i in range(N): idx_i var_index_map[f‘z_{i}_{r}’] Q[idx_i, idx_i] B * (1 - 2) # 来自 B*(sum z -1)^2 for j in range(i1, N): idx_j var_index_map[f‘z_{j}_{r}’] Q[idx_i, idx_j] 2 * B Q[idx_j, idx_i] 2 * B # 确保Q是对称的上三角复制到下三角 for i in range(num_vars): for j in range(i1, num_vars): Q[j, i] Q[i, j]4.3 使用模拟退火求解与结果解析# 使用SimulatedAnnealingSampler (模拟退火) 求解QUBO sampler SimulatedAnnealingSampler() # 将Q矩阵转换为upper-triangular dictionary格式dimod库常用 qubo_dict {} for i in range(num_vars): for j in range(i, num_vars): if Q[i, j] ! 0: qubo_dict[(all_vars[i][1], all_vars[j][1])] Q[i, j] sampleset sampler.sample_qubo(qubo_dict, num_reads1000, num_sweeps1000) # num_reads: 独立退火运行次数 # num_sweeps: 每次运行的迭代步数 # 获取能量最低的解即最优解 best_sample sampleset.first.sample best_energy sampleset.first.energy print(f“找到的最低能量目标函数值: {best_energy}”) print(“\n解析结果”) selected_cards [] weights np.zeros(N) for i in range(N): x_key f‘x_{i}’ if best_sample.get(x_key, 0) 1: selected_cards.append(i) total_units 0 for r in range(R): z_key f‘z_{i}_{r}’ if best_sample.get(z_key, 0) 1: total_units 1 weights[i] total_units / R # 计算权重 print(f“评分卡 {i} 被选中权重为 {weights[i]:.3f}”) print(f“\n选中的评分卡索引: {selected_cards}”) print(f“权重和: {weights.sum():.3f} (应接近1)”) print(f“选中数量: {len(selected_cards)} (目标为{K})”) # 验证约束满足情况 selected_count sum(1 for i in range(N) if best_sample.get(f‘x_{i}’, 0) 1) print(f“实际选中数量: {selected_count}, 约束满足: {selected_count K}”)4.4 模型验证与调优运行上述代码后你可能会发现结果并不完美选中数量可能不等于K权重和可能不严格为1。这是因为惩罚系数需要精细调优并且模拟退火作为一种启发式算法可能陷入局部最优。调优步骤检查约束违反程度如果约束被严重违反等比例增大对应的惩罚系数A,B。平衡目标与惩罚如果约束满足但目标利润很差可能是惩罚项过强。尝试略微降低惩罚系数或增加目标项-P_total的缩放因子。调整退火参数增加num_reads更多次独立尝试和num_sweeps每次更充分的搜索可以提高找到更好解的概率但会增加计算时间。多次运行取最优由于随机性对同一个QUBO问题多次运行模拟退火选择其中目标函数最优且约束满足最好的解。简化模型如果问题规模太大N或R太大导致变量过多求解会非常慢且效果差。考虑降低权重离散化精度减小R或者先用传统方法如贪心算法进行初筛减少N。5. 核心挑战、实用技巧与未来展望将量子计算思想应用于此类金融优化问题目前仍处于探索阶段。在实际操作和参赛过程中我总结了以下几个核心挑战和应对技巧挑战一问题规模与映射复杂度问题真实的评分卡数量N可能上百权重离散化粒度R也需要足够细以保证精度这会导致二值变量数量爆炸N * (R1)量级使得QUBO矩阵异常庞大超出当前模拟器或量子硬件的处理能力。技巧分层优化先使用传统快速筛选方法如基于单模型性能排序、相关性分析将N缩小到一个候选池如30个。粗粒度离散先使用较大的权重单位如0.1进行优化找到大致范围后再在局部进行细粒度优化。分解问题是否可以先将评分卡聚类在类内优选再进行类间组合挑战二非线性业务约束的精确表达问题通过率、坏账率等约束是决策变量x, w的复杂非线性函数精确线性化需要引入大量辅助变量进一步增大问题规模。技巧近似与松弛在初赛阶段可以采用近似约束。例如将“通过率P_min”转化为对组合评分均值的约束这可能是线性的。后验校验与迭代先在不考虑复杂业务约束的情况下优化得到一组解后计算其实际业务指标。如果满足约束则接受如果不满足则调整惩罚系数或修改目标函数例如将不满足的程度作为惩罚项加入重新优化。目标整合有时可以将业务约束转化为多目标的一部分使用加权和法将其与主目标利润结合。挑战三惩罚系数的“艺术”问题惩罚系数没有通用公式调参过程耗时且像一门“玄学”。技巧量级匹配初始设置时让惩罚项的数量级与目标项的数量级处于同一水平。可以先用一组解估算目标函数值和各约束违反的程度。自适应调整编写一个简单的循环求解 - 检查约束违反 - 按比例增大对应惩罚系数 - 再次求解直到约束满足或达到迭代上限。使用专业工具一些高级的QUBO/Ising模型求解库或平台提供了自动约束处理机制可以减轻手动调参的负担。挑战四从QUBO解到业务方案的解码与验证问题求解器返回的是0/1比特串我们需要将其准确解码回选择了哪些评分卡以及具体的权重值。技巧鲁棒解码由于求解可能得到近似解解码时需要容错。例如x_i的值可能是0.87而不是严格的1。可以设定一个阈值如0.5大于阈值则认为被选中。对于权重z_{i,r}可能需要检查每个权重单位r是否被唯一分配。后处理解码得到选中的评分卡和粗略权重后可以固定选中的评分卡在经典计算机上使用连续优化方法如梯度下降对权重进行微调以进一步提升目标并严格满足业务约束。这一步结合了量子和经典的优势。未来展望 虽然目前我们大多使用经典模拟器来验证想法但整个建模流程——从业务问题定义到混合整数规划模型再到QUBO转化——是与量子硬件对接的标准路径。随着量子退火机比特数增加和相干伊辛机性能提升未来直接处理上百变量的问题将成为可能。对于金融行业这意味着一类新的“量子优势”可能首先在复杂的资产组合优化、风险对冲策略搜索、高频交易模式识别等场景中体现。这道赛题的价值不仅在于让参赛者熟悉了QUBO建模这一工具更在于它提供了一种思考复杂优化问题的新范式如何将你的问题“量子化”。这个过程本身就是对问题结构一次极其深刻的再理解。即使没有量子计算机这种结构化、标准化的建模思想也能帮助我们设计出更好的经典启发式算法。