2023MathorCup A题赛后思路代码分享(一等奖)

最近忙建模比赛去了,所以其他事情有些搁置
下面会分享 A 题前两问的思路,代码和最终结果
第三问的模型我自己不太满意,便不分享出来了
最终结果出来啦,现在的思路方向是正确的(一等奖),可以安心参考

文章目录

  • Q1: 单信用评分卡求最大收入
    • 符号定义
    • 建模
    • QUBO形式
    • 结果
    • 代码
      • 1. 导入需要用到的库
      • 2. 数据处理
      • 3. 定义函数处理后面的结果
      • 4. 模型参数设置和求解
      • 5. 转化为单一变量后的代码
  • Q2:前三张信用评分卡求解最大收入
    • 建模
    • QUBO的形式--三次转二次
    • 结果
    • 代码
      • 1. 导入需要的库以及数据处理
      • 2. 定义函数处理结果
      • 3. 求解

Q1: 单信用评分卡求最大收入

符号定义

  • C:总贷款资金
  • R:利息收入率
  • P:惩罚系数(约束条件的)
  • x i x_i xi:二进制变量,表示是否选择第 i 张信用评分卡
  • y i , j y_{i,j} yi,j:表示是否选择第 i 张信用评分卡的第 j 个阈值
  • t i , j t_{i,j} ti,j:表示第 i 张信用评分卡的第 j 个阈值对应的通过率
  • h i , j h_{i,j} hi,j :表示第 i 张信用评分卡的第 j 个阈值对应的坏账率

建模

  1. 定义决策变量:用一个二进制变量 x i x_i xi 表示是否选择第 i 张信用评分卡,如果选择则 x i = 1 x_i = 1 xi=1,否则 x i = 0 x_i = 0 xi=0。我们还可以用一个二进制变量 y i , j y_{i,j} yi,j 表示是否选择第 i 张信用评分卡的第 j 个阈值,如果选择则 y i , j = 1 y_{i,j} = 1 yi,j=1,否则 y i , j = 0 y_{i,j} = 0 yi,j=0。这样我们就有 100 + 1000 = 1100 个决策变量。
  2. 定义目标函数:我们的目标是最大化银行的最终收入,根据题目给出的公式,我们可以写出目标函数为: max ⁡ M = ∑ i = 1 100 ∑ j = 1 10 ( x i ∗ y i , j ∗ ( C ∗ R ∗ t i , j ∗ ( 1 − h i , j ) − C ∗ t i , j ∗ h i , j ) ) \max M = \sum_{i=1}^{100} \sum_{j=1}^{10} (x_i * y_{i,j} * (C * R * t_{i,j} * (1 - h_{i,j}) - C * t_{i,j} * h_{i,j})) maxM=i=1100j=110(xiyi,j(CRti,j(1hi,j)Cti,jhi,j)) 其中 t i , j t_{i,j} ti,j h i , j h_{i,j} hi,j 分别表示第 i 张信用评分卡的第 j 个阈值对应的通过率和坏账率,贷款资金和利息收入率是已知的常数。
  3. 定义约束条件:需要满足以下约束条件:
    (1) 只能选择一张信用评分卡,即 ∑ i = 1 100 x i = 1 \sum_{i=1}^{100} x_i = 1 i=1100xi=1
    (2) 每张信用评分卡只能选择一个阈值,即对于任意 i,有 ∑ j = 1 10 y i , j = x i \sum_{j=1}^{10} y_{i,j} = x_i j=110yi,j=xi
    (3) 所有的决策变量都是二进制的,即对于任意 i 和 j,有 x i ∈ 0 , 1 x_i \in {0, 1} xi0,1 y i , j ∈ 0 , 1 y_{i,j} \in {0, 1} yi,j0,1

忽略常数项 C:

max ⁡ M = ∑ i = 1 100 ∑ j = 1 10 ( x i ∗ y i , j ∗ ( R ∗ t i , j ∗ ( 1 − h i , j ) − t i , j ∗ h i , j ) ) − P ∗ ( ∑ i = 1 100 x i − 1 ) 2 − P ∗ ∑ i = 1 100 ( ∑ j = 1 10 y i , j − x i ) 2 \max M = \sum_{i=1}^{100} \sum_{j=1}^{10} (x_i * y_{i,j} * (R * t_{i,j} * (1 - h_{i,j}) - t_{i,j} * h_{i,j})) - P * (\sum_{i=1}^{100} x_i - 1)^2 - P * \sum_{i=1}^{100} (\sum_{j=1}^{10} y_{i,j} - x_i)^2 maxM=i=1100j=110(xiyi,j(Rti,j(1hi,j)ti,jhi,j))P(i=1100xi1)2Pi=1100(j=110yi,jxi)2

还可以进行一些简化:

因为仅选择一张卡,所以目标函数中的 x i x_i xi是可以省去的,也就是说只使用 y i , j y_{i,j} yi,j来表示是否选择第i张卡的第j个阈值,此时决策变量从1100变为了1000。如果 y i , j = 1 y_{i,j}=1 yi,j=1,那么表示选择了第i张卡的第j个阈值,现在我们可以将约束条件整合成 ∑ i = 1 100 ∑ j = 1 10 y i , j = 1 \sum_{i=1}^{100}\sum_{j=1}^{10} y_{i,j} = 1 i=1100j=110yi,j=1 就行

max ⁡ M = ∑ i = 1 100 ∑ j = 1 10 ( y i , j ∗ ( R ∗ t i , j ∗ ( 1 − h i , j ) − t i , j ∗ h i , j ) ) − P ∗ ( ∑ i = 1 100 ∑ j = 1 10 y i , j − 1 ) 2 \max M = \sum_{i=1}^{100} \sum_{j=1}^{10} (y_{i,j} * (R * t_{i,j} * (1 - h_{i,j}) - t_{i,j} * h_{i,j})) - P * (\sum_{i=1}^{100}\sum_{j=1}^{10} y_{i,j} - 1)^2 maxM=i=1100j=110(yi,j(Rti,j(1hi,j)ti,jhi,j))P(i=1100j=110yi,j1)2

按以下规则拼接 y i , j y_{i,j} yi,j z k z_k zk,以卡号的顺序排列:

k = 10 ( i − 1 ) + j k = 10(i-1)+j k=10(i1)+j

则有 max ⁡ M = ∑ i = 1 100 ∑ j = 1 10 ( z 10 ( i − 1 ) + j ∗ ( R ∗ t i , j ∗ ( 1 − h i , j ) − t i , j ∗ h i , j ) ) − P ∗ ( ∑ i = 1 100 ∑ j = 1 10 z 10 ( i − 1 ) + j − 1 ) 2 \max M = \sum_{i=1}^{100} \sum_{j=1}^{10} (z_{10(i-1)+j} * (R * t_{i,j} * (1 - h_{i,j}) - t_{i,j} * h_{i,j})) - P * (\sum_{i=1}^{100}\sum_{j=1}^{10} z_{10(i-1)+j} - 1)^2 maxM=i=1100j=110(z10(i1)+j(Rti,j(1hi,j)ti,jhi,j))P(i=1100j=110z10(i1)+j1)2

将i,j也表示为k的形式:

j = ( k + 1 ) m o d 10 , i = ( k − 1 ) / / 10 + 1 j = (k+1) mod 10, i = (k-1)//10+1 j=(k+1)mod10,i=(k1)//10+1

t k = t k / / 10 , ( k + 1 ) m o d 10 t_k = t_{k//10,(k+1)mod10} tk=tk//10,(k+1)mod10

max ⁡ M = ∑ k = 1 1000 ( z k ∗ ( R ∗ t k ∗ ( 1 − h k ) − t k ∗ h k ) ) − P ∗ ( ∑ k = 1 1000 z k − 1 ) 2 \max M = \sum_{k=1}^{1000} (z_k * (R * t_k * (1 - h_k) - t_k * h_k)) - P * (\sum_{k=1}^{1000} z_k - 1)^2 maxM=k=11000(zk(Rtk(1hk)tkhk))P(k=11000zk1)2

QUBO形式

为了将目标函数化为 QUBO 形式,需要将所有的一次项和常数项都转化为二次项。可以利用以下的等式来实现这一目的:

  1. z k = z k 2 z_k = z_k^2 zk=zk2,因为 z k z_k zk 是二值变量,所以它的平方等于它本身。
  2. ( ∑ k = 1 1000 z k − 1 ) 2 (\sum_{k=1}^{1000} z_k - 1)^2 (k=11000zk1)2 = ( ∑ k = 1 1000 z k ) 2 − 2 ∑ k = 1 1000 z k + 1 (\sum_{k=1}^{1000} z_k)^2 - 2 \sum_{k=1}^{1000} z_k + 1 (k=11000zk)22k=11000zk+1

将目标函数化为 :

max ⁡ M = ∑ k = 1 1000 z k 2 ( R ∗ t k ∗ ( 1 − h k ) − t k ∗ h k + 2 P ) − P ∗ ( ( ∑ k = 1 1000 z k ) 2 + 1 ) \max M = \sum_{k=1}^{1000} z_k^2(R * t_k * (1 - h_k) - t_k * h_k + 2P)- P*((\sum_{k=1}^{1000} z_k)^2 + 1) maxM=k=11000zk2(Rtk(1hk)tkhk+2P)P((k=11000zk)2+1).

最后将其转化为最小化,忽略常数项P:

min ⁡ M = − ∑ k = 1 1000 z k 2 ( R ∗ t k ∗ ( 1 − h k ) − t k ∗ h k + 2 P ) + P ∗ ( ( ∑ k = 1 1000 z k ) 2 + 1 ) \min M = -\sum_{k=1}^{1000} z_k^2(R * t_k * (1 - h_k) - t_k * h_k + 2P)+ P*((\sum_{k=1}^{1000} z_k)^2 + 1) minM=k=11000zk2(Rtk(1hk)tkhk+2P)+P((k=11000zk)2+1).

此时可以用QUBO矩阵Q表示,有:

min ⁡ M = z T Q z \min M = z^T Q z minM=zTQz

其中 Q Q Q 的元素为:

Q k , l = { − ( R ∗ t k ∗ ( 1 − h k ) − t k ∗ h k ) ) − P k = l   P k ≠ l Q_{k,l} = \begin{cases} - (R * t_k * (1 - h_k) - t_k * h_k)) - P & k = l \\\ P & k \ne l \end{cases} Qk,l={(Rtk(1hk)tkhk))P Pk=lk=l

结果

最终的结果是选择第 49 张卡的第 1 个阈值,对应的收入为 61172.0

代码

这一部分的求解有很多种方式,比如:你可以使用 D-wave 提供的 API 在量子计算机上真正的进行求解,也可以使用OpenJij库的函数 SQASampler() 模拟量子退火求解。

1. 导入需要用到的库

# 数据处理
import numpy as np
import pandas as pd

# 导入 PyQUBO 库
from pyqubo import Array, Binary, Constraint

# 模型求解
from dwave.system import LeapHybridSampler
from openjij import SQASampler

2. 数据处理

# 读取信用卡数据
data = pd.read_csv("附件1:data_100.csv", header=0)
t = data.iloc[:, ::2].values.T   # 通过率
h = data.iloc[:, 1::2].values.T  # 坏账率
print(t.shape, h.shape)          # 行对应阈值,列对应卡

3. 定义函数处理后面的结果

import re

def ret_result(solution):
    '''
    返回索引和结果
    '''
    i,j = 0,0
    pattern_idx = re.compile(r"[0-9]+")
    for key,value in solution.items():
        if value == 1:
            print(key, value)
            idx = pattern_idx.findall(key)
            if len(idx)==1:
                i = int(idx[0])
                print(i)

            else:
                i, j = int(idx[0]), int(idx[1])
                print(i, j)

    result = C * R * t[i, j] * (1 - h[i, j]) - C * t[i, j] * h[i, j]
    return i, j, result

4. 模型参数设置和求解

# 定义常数和变量
C = 1000000 # 总贷款资金
R = 0.08    # 利率
n = 100     # 信用评分卡数量
P = C       # 惩罚系数

# 定义目标函数
H = 0
for i in range(100):
    for j in range(10):
        H -= x[i] * y[i, j] * (C * R * t[i, j] * (1 - h[i, j]) - C * t[i, j] * h[i, j])

# 定义约束条件
C1 = P * (sum(x) - 1)**2
C2 = P * sum((sum(y[i]) - x[i])**2 for i in range(100))

# 将目标函数和约束条件相加
model = (H + C1 + C2).compile()

# 利用to_qubo()得到矩阵Q
Q, _ = model.to_qubo()

# 求解 QUBO 模型
sampler = LeapHybridSampler(num_reads=3)        # 使用 LeapHybridSampler
response = sampler.sample_qubo(Q) # 采样 QUBO
solution = response.first.sample     # 获取最优解
i, j, result = ret_result(solution)
print(f"第{i+1}张卡选择第{j+1}个阈值的收入是: {result}")

5. 转化为单一变量后的代码

z = [Binary(f"z_{k}") for k in range(n*10+100)]

# 定义目标函数
H = 0
for i in range(100):
    for j in range(10):
        H -= z[i] * z[10 * i + j + 100] * (C * R * t[i, j] * (1 - h[i, j]) - C * t[i, j] * h[i, j])

# 定义约束条件
C1 = P * (sum(z[:100]) - 1)**2
C2 = P * sum((sum(z[i * 10 + 100: i*10+110]) - z[i])**2 for i in range(100))

# 将目标函数和约束条件相加
model = (H + C1 + C2).compile()

# 利用to_qubo()得到矩阵Q
Q, _ = model.to_qubo()

# 求解 QUBO 模型
sampler = LeapHybridSampler()        # 使用 LeapHybridSampler
response = sampler.sample_qubo(Q)    # 采样 QUBO
solution = response.first.sample     # 获取最优解

# 打印对应的索引
pattern_idx = re.compile(r"[0-9]+")
for key,value in solution.items():
    if value == 1:
        print(key, value)
        idx = int(pattern_idx.findall(key)[0])
        print(idx)

Q2:前三张信用评分卡求解最大收入

建模

因为已经给定了三张信用评分卡,只需要选择每张信用评分卡的阈值。

可以用三个二值向量 y 1 , i y_{1,i} y1,i, y 2 , j y_{2,j} y2,j, y 3 , k y_{3,k} y3,k 来表示三张信用评分卡的阈值,每个变量有 10 种取值,分别对应 10 个阈值。

目标函数是最大化银行收入减去坏账损失,约束条件是每个变量只能取一个值。

  1. 定义决策变量: y i , j y_{i,j} yi,j 为一个二值变量,表示是否选择第 i 张信用评分卡的第 j 个阈值,其中 i = 1, 2, 3,j = 1, 2, …, 10。

  2. 定义目标函数:我们的目标是最大化银行的最终收入,根据题目给出的公式,我们可以写出目标函数为:
    max ⁡ M = ∑ i = 1 10 ∑ j = 1 10 ∑ k = 1 10 ( y 1 , i ∗ y 2 , j ∗ y 3 , k ∗ t 1 , i ∗ t 2 , j ∗ t 3 , k ) ∗ ( C ∗ R ∗ ( 1 − 1 3 ( h 1 , i + h 2 , j + h 3 , k ) ) − C ∗ 1 3 ( h 1 , i + h 2 , j + h 3 , k ) ) \max M = \sum_{i=1}^{10} \sum_{j=1}^{10}\sum_{k=1}^{10} (y_{1,i}*y_{2,j}*y_{3,k} * t_{1,i}*t_{2,j}*t_{3,k}) \\ * (C * R * (1 - \frac{1}{3} (h_{1,i}+h_{2,j}+h_{3,k})) - C * \frac{1}{3} (h_{1,i}+h_{2,j}+h_{3,k})) maxM=i=110j=110k=110(y1,iy2,jy3,kt1,it2,jt3,k)(CR(131(h1,i+h2,j+h3,k))C31(h1,i+h2,j+h3,k))
    因为该题规定了选择前三张卡,所以我们可以简化总通过率和总坏账率的表达形式为:
    T i , j , k = t 1 , i ∗ t 2 , j ∗ t 3 , k T_{i,j,k} = t_{1,i}*t_{2,j}*t_{3,k} Ti,j,k=t1,it2,jt3,k
    H i , j , k = 1 3 ( h 1 , i + h 2 , j + h 3 , k ) H_{i,j,k} = \frac{1}{3} (h_{1,i}+h_{2,j}+h_{3,k}) Hi,j,k=31(h1,i+h2,j+h3,k)
    忽略常数项做简化,有:
    max ⁡ M = ∑ i = 1 10 ∑ j = 1 10 ∑ k = 1 10 ( y 1 , i ∗ y 2 , j ∗ y 3 , k ) ∗ ( T i , j , k ∗ ( R ∗ ( 1 − H i , j , k ) − H i , j , k ) ) \max M = \sum_{i=1}^{10} \sum_{j=1}^{10}\sum_{k=1}^{10} (y_{1,i}*y_{2,j}*y_{3,k} ) * (T_{i,j,k} * (R * (1 - H_{i,j,k}) -H_{i,j,k})) maxM=i=110j=110k=110(y1,iy2,jy3,k)(Ti,j,k(R(1Hi,j,k)Hi,j,k))

  3. 定义约束条件:每张信用评分卡只能选择一个阈值,即: ∑ j = 1 10 y i , j = 1 \sum_{j=1}^{10} y_{i,j} = 1 j=110yi,j=1,其中 i = 1, 2, 3。

将约束条件加入目标函数中,并引入一个惩罚项系数 P,得到:

max ⁡ M = ∑ i = 1 10 ∑ j = 1 10 ∑ k = 1 10 ( y 1 , i ∗ y 2 , j ∗ y 3 , k ) ∗ ( T i , j , k ∗ ( R ∗ ( 1 − H i , j , k ) − H i , j , k ) ) − P ∗ ∑ i = 1 3 ( ∑ j = 1 10 y i , j − 1 ) 2 \max M = \sum_{i=1}^{10} \sum_{j=1}^{10}\sum_{k=1}^{10} (y_{1,i}*y_{2,j}*y_{3,k} ) * (T_{i,j,k} * (R * (1 - H_{i,j,k}) -H_{i,j,k}))- P * \sum_{i=1}^{3} (\sum_{j=1}^{10} y_{i,j} - 1)^2 maxM=i=110j=110k=110(y1,iy2,jy3,k)(Ti,j,k(R(1Hi,j,k)Hi,j,k))Pi=13(j=110yi,j1)2

QUBO的形式–三次转二次

这个目标函数是三次的,我们需要将其转为二次,这里设 y 2 , j ∗ y 3 , k = z j , k y_{2,j}*y_{3,k} = z_{j,k} y2,jy3,k=zj,k,这一操作对应的约束条件为:

(1) z j , k ≤ y 2 , j z_{j,k} \leq y_{2,j} zj,ky2,j

(2) z j , k ≤ y 3 , k z_{j,k} \leq y_{3,k} zj,ky3,k

(3) z j , k ≥ y 2 , j + y 3 , k − 1 → y 2 , j + y 3 , k + ( − z j , k ) ≤ 1 z_{j,k} \geq y_{2,j} + y_{3,k} - 1 \rightarrow y_{2,j} + y_{3,k} + (-z_{j,k}) \le 1 zj,ky2,j+y3,k1y2,j+y3,k+(zj,k)1

这些约束条件可以保证当且仅当 y 2 , j = 1 y_{2,j} = 1 y2,j=1 y 3 , k = 1 y_{3,k} = 1 y3,k=1 时, z j , k = 1 z_{j,k} = 1 zj,k=1

根据参考文献(A Tutorial on Formulating and Using QUBO Models)中的
2023MathorCup A题赛后思路代码分享(一等奖)_第1张图片

可以得到对应的惩罚项:

(1) P ∗ ( z j , k − y 2 , j ∗ z j , k ) P*(z_{j,k} - y_{2,j} * z_{j,k}) P(zj,ky2,jzj,k)

(2) P ∗ ( z j , k − y 3 , k ∗ z j , k ) P*(z_{j,k} - y_{3,k} * z_{j,k}) P(zj,ky3,kzj,k)

(3) P ∗ ( y 2 , j y 3 , k − y 2 , j ∗ z j , k − y 3 , k ∗ z j , k ) P*(y_{2,j}y_{3,k}-y_{2,j} * z_{j,k}-y_{3,k} * z_{j,k}) P(y2,jy3,ky2,jzj,ky3,kzj,k) = P ∗ ( z j , k − y 2 , j ∗ z j , k − y 3 , k ∗ z j , k ) P*(z_{j,k}-y_{2,j} * z_{j,k}-y_{3,k} * z_{j,k}) P(zj,ky2,jzj,ky3,kzj,k)

汇总即有:

P ∗ ( 3 z j , k + y 2 , j y 3 , k − 2 y 3 , k z j , k − 2 y 2 , j z j , k ) P*(3z_{j,k} + y_{2,j}y_{3,k}-2y_{3,k}z_{j,k}-2y_{2,j}z_{j,k}) P(3zj,k+y2,jy3,k2y3,kzj,k2y2,jzj,k)

将惩罚项加入目标函数:

max ⁡ M = ∑ i = 1 10 ∑ j = 1 10 ∑ k = 1 10 ( y 1 , i ∗ z j , k ) ∗ ( T i , j , k ∗ ( R ∗ ( 1 − H i , j , k ) − H i , j , k ) ) − P ∗ ∑ i = 1 3 ( ∑ j = 1 10 y i , j − 1 ) 2 − P ∗ ∑ j = 1 10 ∑ k = 1 10 ( 3 z j , k + y 2 , j y 3 , k − 2 y 3 , k z j , k − 2 y 2 , j z j , k ) \max M = \sum_{i=1}^{10} \sum_{j=1}^{10}\sum_{k=1}^{10} (y_{1,i}*z_{j,k} ) * (T_{i,j,k} * (R * (1 - H_{i,j,k}) -H_{i,j,k}))\\- P * \sum_{i=1}^{3} (\sum_{j=1}^{10} y_{i,j} - 1)^2-P*\sum_{j=1}^{10}\sum_{k=1}^{10}(3z_{j,k} + y_{2,j}y_{3,k}-2y_{3,k}z_{j,k}-2y_{2,j}z_{j,k}) maxM=i=110j=110k=110(y1,izj,k)(Ti,j,k(R(1Hi,j,k)Hi,j,k))Pi=13(j=110yi,j1)2Pj=110k=110(3zj,k+y2,jy3,k2y3,kzj,k2y2,jzj,k)

打断一下,下面的拼接只是为了写出 QUBO 矩阵,其实能弄明白第一题的怎么写就行,不需要太花费时间,这里只是一个简单的二次型矩阵表达,但因为拼接后看起来会很复杂。

现在,我们需要将 y 1 , i , y 2 , j , y 3 , k y_{1,i},y_{2,j},y_{3,k} y1,i,y2,j,y3,k z j , k z_{j,k} zj,k合并成一个向量 L m L_{m} Lm,拼接规则如下:

L m = { y 1 , m 1 ≤ m ≤ 10 y 2 , m − 10 11 ≤ m ≤ 20 y 3 , m − 20 21 ≤ m ≤ 30 z ( m − 31 ) / / 10 + 1 , ( m − 31 ) % 10 + 1 31 ≤ m ≤ 130 L_{m} = \begin{cases} y_{1,m} & 1 \leq m \leq 10 \\y_{2,m-10} & 11 \leq m \leq 20 \\y_{3,m-20} &21 \leq m \leq 30 \\ z_{(m-31)//10+1,(m-31)\%10+1} & 31 \leq m \leq 130\end{cases} Lm= y1,my2,m10y3,m20z(m31)//10+1,(m31)%10+11m1011m2021m3031m130

此时,对应的约束条件转化为:

∑ j = 1 10 y i , j = 1 → ∑ j = 1 10 L ( i − 1 ) ∗ 10 + j = 1 \sum_{j=1}^{10} y_{i,j} = 1 \rightarrow \sum_{j=1}^{10} L_{(i-1)*10+j} = 1 j=110yi,j=1j=110L(i1)10+j=1

z j , k ≤ y 2 , j → L ( j − 1 ) ∗ 10 + k + 30 ≤ L j + 10 z_{j,k} \leq y_{2,j} \rightarrow L_{(j-1)*10+k+30} \leq L_{j+10} zj,ky2,jL(j1)10+k+30Lj+10

z j , k ≤ y 3 , k → L ( j − 1 ) ∗ 10 + k + 30 ≤ L k + 20 z_{j,k} \leq y_{3,k} \rightarrow L_{(j-1)*10+k+30} \leq L_{k+20} zj,ky3,kL(j1)10+k+30Lk+20

y 2 , j + y 3 , k + ( − z j , k ) ≤ 1 → L j + 10 + L k + 20 + ( − L ( j − 1 ) ∗ 10 + k + 30 ) ≤ 1 y_{2,j} + y_{3,k} + (-z_{j,k}) \le 1 \rightarrow L_{j+10} + L_{k+20} + (-L_{(j-1)*10+k+30}) \leq 1 y2,j+y3,k+(zj,k)1Lj+10+Lk+20+(L(j1)10+k+30)1

目标函数:

max ⁡ M = ∑ i = 1 10 ∑ j = 1 10 ∑ k = 1 10 ( L i ∗ L ( j − 1 ) ∗ 10 + k + 30 ) ∗ ( T i , j , k ∗ ( R ∗ ( 1 − H i , j , k ) − H i , j , k ) ) − P ∗ ∑ i = 1 3 ( ∑ j = 1 10 L ( i − 1 ) ∗ 10 + j − 1 ) 2 − P ∗ ∑ j = 1 10 ∑ k = 1 10 ( 3 L ( j − 1 ) ∗ 10 + k + 30 + L j + 10 L k + 20 − 2 L k + 20 L ( j − 1 ) ∗ 10 + k + 30 − 2 L j + 10 L ( j − 1 ) ∗ 10 + k + 30 ) \max M = \sum_{i=1}^{10} \sum_{j=1}^{10}\sum_{k=1}^{10} (L_i*L_{(j-1)*10+k+30} ) * (T_{i,j,k} * (R * (1 - H_{i,j,k}) -H_{i,j,k})) - P * \sum_{i=1}^{3} (\sum_{j=1}^{10} L_{(i-1)*10+j} - 1)^2-P*\sum_{j=1}^{10}\sum_{k=1}^{10}(3L_{(j-1)*10+k+30} + L_{j+10}L_{k+20}-2L_{k+20}L_{(j-1)*10+k+30}-2L_{j+10}L_{(j-1)*10+k+30}) maxM=i=110j=110k=110(LiL(j1)10+k+30)(Ti,j,k(R(1Hi,j,k)Hi,j,k))Pi=13(j=110L(i1)10+j1)2Pj=110k=110(3L(j1)10+k+30+Lj+10Lk+202Lk+20L(j1)10+k+302Lj+10L(j1)10+k+30)

因为 ( ∑ j = 1 10 L ( i − 1 ) ∗ 10 + j − 1 ) 2 = ( ∑ j = 1 10 L ( i − 1 ) ∗ 10 + j ) 2 − 2 ∑ j = 1 10 L ( i − 1 ) ∗ 10 + j + 1 (\sum_{j=1}^{10} L_{(i-1)*10+j} - 1)^2 = (\sum_{j=1}^{10} L_{(i-1)*10+j})^2 - 2 \sum_{j=1}^{10} L_{(i-1)*10+j} + 1 (j=110L(i1)10+j1)2=(j=110L(i1)10+j)22j=110L(i1)10+j+1

可以将目标函数转换为以下形式:

min ⁡ M = − ∑ i = 1 10 ∑ j = 1 10 ∑ k = 1 10 ( L i ∗ L ( j − 1 ) ∗ 10 + k + 30 ) ∗ ( T i , j , k ∗ ( R ∗ ( 1 − H i , j , k ) − H i , j , k ) ) + P ∗ ∑ i = 1 3 ( ( ∑ j = 1 10 L ( i − 1 ) ∗ 10 + j ) 2 − 2 ∑ j = 1 10 L ( i − 1 ) ∗ 10 + j + 1 ) + P ∗ ∑ j = 1 10 ∑ k = 1 10 ( 3 L ( j − 1 ) ∗ 10 + k + 30 + L j + 10 L k + 20 − 2 L k + 20 L ( j − 1 ) ∗ 10 + k + 30 − 2 L j + 10 L ( j − 1 ) ∗ 10 + k + 30 ) \min M = -\sum_{i=1}^{10} \sum_{j=1}^{10}\sum_{k=1}^{10} (L_i*L_{(j-1)*10+k+30} ) * (T_{i,j,k} * (R * (1 - H_{i,j,k}) -H_{i,j,k})) + P * \sum_{i=1}^{3} ((\sum_{j=1}^{10} L_{(i-1)*10+j})^2 - 2 \sum_{j=1}^{10} L_{(i-1)*10+j} + 1)+P*\sum_{j=1}^{10}\sum_{k=1}^{10}(3L_{(j-1)*10+k+30} + L_{j+10}L_{k+20}-2L_{k+20}L_{(j-1)*10+k+30}-2L_{j+10}L_{(j-1)*10+k+30}) minM=i=110j=110k=110(LiL(j1)10+k+30)(Ti,j,k(R(1Hi,j,k)Hi,j,k))+Pi=13((j=110L(i1)10+j)22j=110L(i1)10+j+1)+Pj=110k=110(3L(j1)10+k+30+Lj+10Lk+202Lk+20L(j1)10+k+302Lj+10L(j1)10+k+30)

然后,可以将上面的表达式转换为 QUBO 矩阵的形式。具体来说,可以定义一个 130 × 130 130 \times 130 130×130 的矩阵 Q Q Q,其中 Q Q Q 的元素为:
2023MathorCup A题赛后思路代码分享(一等奖)_第2张图片

结果

最终的结果是分别选择前三张卡的第 8,1,2 个阈值,最终的收入为27914.8。

代码

1. 导入需要的库以及数据处理

# 导入需要的库
import re

import numpy as np
import pandas as pd

from openjij import SQASampler
# from dwave.system import DWaveSampler, LeapHybridSampler, EmbeddingComposite
from pyqubo import Array, Binary


# 读取信用卡数据
data = pd.read_csv("附件1:data_100.csv", header=0)
t = data.iloc[:, ::2].values.T   # 通过率
h = data.iloc[:, 1::2].values.T  # 坏账率
print(t.shape, h.shape)          # 行对应卡,列对应阈值

2. 定义函数处理结果

def get_result(solution):
    '''
    返回结果并打印索引及结果
    '''
    # x, y 用于存储下标
    x, y = [], []
    i = 0
    pattern_idx = re.compile(r"[0-9]+")
    for key, value in solution.items():
        if len(key) < 10:
            if value == 1 and i < 3:
                print(key, value)
                x_idx, y_idx = pattern_idx.findall(key)
                print(f"第{int(x_idx)+1}张卡选择第{int(y_idx)+1}个阈值")
                x.append(int(x_idx))
                y.append(int(y_idx))
                i += 1
    # T, H 分别表示总通过率和总坏账率
    T = t[x[0], y[0]] * t[x[1], y[1]] * t[x[2], y[2]]
    H = 1 / 3 * (h[x[0], y[0]] + h[x[1], y[1]] + h[x[2], y[2]])
    result = C * T * (R * (1 - H) - H)
    print(f"result\n")
    return result

3. 求解

# 定义常数和变量
C = 1000000  # 总贷款资金
R = 0.08     # 利率
n = 100      # 信用评分卡数量
P = C        # 惩罚系数

# 定义目标函数
y = Array.create('y', shape=(3, 10), vartype='BINARY')
z = Array.create('z', shape=(10, 10), vartype='BINARY')

Q = 0
for i in range(10):
    for j in range(10):
        for k in range(10):
            T_ijk = t[0, i] * t[1, j] * t[2, k]
            H_ijk = (h[0, i] + h[1, j] + h[2, k]) / 3
            Q += y[0, i] * z[j, k] * T_ijk * (C * R * (1 - H_ijk) - C * H_ijk)

c1 = P * sum((sum(y[i]) - 1)**2 for i in range(3))
c2 = 0
for j in range(10):
    for k in range(10):
        c2 += P * (3 * z[j, k] - 2 * z[j, k] * y[1, j] -
                   2 * z[j, k] * y[2, k] + y[1, j] * y[2, k])

model = -Q + c1 + c2
model = model.compile()

qubo, offset = model.to_qubo()

best_result = 0
results = []
print("因为输出不稳定,所以写了个循环代码进行结果的验证,你可以修改SQASampler来得到更稳定的结果(每次运行大概需要一分半)\n")
for k in range(20):
    # sampler = LeapHybridSampler()  # 使用 LeapHybridSampler,如果没有api的话就用下面那行
    sampler = SQASampler(
        num_sweeps=3000,
        num_reads=5000)  # num_sweeps每个温度的扫描次数,num_reads指重复模拟退火算法的次数
    response = sampler.sample_qubo(qubo, )  # 采样 QUBO
    solution = response.first.sample
    
    print(f"第{k+1}次运行结果:")
    result = get_result(solution)
    results.append(result)

    if result > best_result:
        best_result = result
        best_solution = solution

print("最优解:")
get_result(best_solution)

# 单次运行结果(注释上面循环部分,再取消注释下面)
# sampler = SQASampler(num_sweeps=3000, num_reads=5000)  # num_sweeps每个温度的扫描次数,num_reads指重复模拟退火算法的次数
# response = sampler.sample_qubo(qubo, )  # 采样 QUBO
# solution = response.first.sample

# get_result(solution)

需要注意的是,代码中模拟量子退火的解是不稳定的,在我最近一次的试运行中,10 次中输出最优解的次数为 7 次。

你可能感兴趣的:(MathorCup,量子退火,QUBO,组合优化)