这是David Eisenstat's solution 的工作代码。
要理解实现,我认为先进行数学运算会有所帮助。
提醒一下,有n 列表,每个列表都有m 选项。 (在问题底部的激励示例中,每个列表代表一种解决问题的方法,并且给您m-1 秒来解决问题。每个列表都使得list[index] 给出解决问题的机会如果该方法运行index 秒,则使用该方法。)
我们将列表存储在一个名为d(在代码中命名为data)的矩阵中,其中矩阵中的每一行都是一个列表。 (因此,每一列代表一个索引,或者,如果遵循激励示例,则代表一个时间量。)
假设我们为列表i 选择索引j*,硬币正面朝上的概率计算为
我们希望将其最大化。
(为了解释这个等式背后的统计数据,我们计算 1 减去硬币不正面朝上的概率。硬币不正面朝上的概率是每次抛硬币不正面朝上的概率正面朝上。单次翻转不正面朝上的概率只是 1 减去正面朝上的概率。它正面朝上的概率就是我们选择的数字,d[i][j*]。因此,所有翻转落在尾巴上的总概率只是每个翻转落在尾巴上的概率的乘积。然后硬币落在正面的概率只是 1 减去所有翻转落在尾巴上的概率。)
正如大卫所指出的,这与最小化相同:
这与最小化相同:
相当于:
那么,既然这是线性求和,我们可以把它变成一个整数程序。
我们将最小化:
这让计算机通过允许它创建一个由 1 和 0 组成的 n 和 m 矩阵来选择索引,称为 x,其中 1 选择特定索引。然后我们将定义规则,这样它就不会挑选出无效的索引集。
第一条规则是你必须为每个列表挑选一个索引:
第二条规则是你必须遵守所选择的索引总和必须等于或小于 m 的约束:
就是这样!然后我们可以告诉计算机根据这些规则最小化这个总和。它会输出一个x 矩阵,每行有一个1,告诉我们它为该行的列表选择了哪个索引。
在代码中(使用激励示例),实现如下:
'''
Requirements:
cvxopt==1.2.6
cvxpy==1.1.10
ecos==2.0.7.post1
numpy==1.20.1
osqp==0.6.2.post0
qdldl==0.1.5.post0
scipy==1.6.1
scs==2.1.2
'''
import math
import cvxpy as cp
import numpy as np
# number of methods
n = 3
# if you have 10 seconds, there are 11 options for each method (0 seconds, 1 second, ..., 10 seconds)
m = 11
# method A has 30% chance of working if run for at least 3 seconds
# equivalent to [0, 0, 0, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3]
A_list = [0, 0, 0] + [0.3] * (m - 3)
# method B has 30% chance of working if run for at least 3 seconds
# equivalent to [0, 0, 0, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3]
B_list = [0, 0, 0] + [0.3] * (m - 3)
# method C has 40% chance of working if run for 4 seconds, 30% otherwise
# equivalent to [0.3, 0.3, 0.3, 0.3, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4]
C_list = [0.3, 0.3, 0.3, 0.3] + [0.4] * (m - 4)
data = [A_list, B_list, C_list]
# do the logarithm
log_data = []
for row in data:
log_row = []
for col in row:
# deal with domain exception
if col == 1:
new_col = float('-inf')
else:
new_col = math.log(1 - col)
log_row.append(new_col)
log_data.append(log_row)
log_data = np.array(log_data)
x = cp.Variable((n, m), boolean=True)
objective = cp.Minimize(cp.sum(cp.multiply(log_data, x)))
# the current solver doesn't work with equalities, so each equality must be split into two inequalities.
# see https://github.com/cvxgrp/cvxpy/issues/1112
one_choice_per_method_constraint = [cp.sum(x[i]) <= 1 for i in range(n)] + [cp.sum(x[i]) >= 1 for i in range(n)]
# constrain the solution to not use more time than is allowed
# note that the time allowed is (m - 1), not m, because time is 1-indexed and the lists are 0-indexed
js = np.tile(np.array(list(range(m))), (n, 1))
time_constraint = [cp.sum(cp.multiply(js, x)) <= m - 1, cp.sum(cp.multiply(js, x)) >= m - 1]
constraints = one_choice_per_method_constraint + time_constraint
prob = cp.Problem(objective, constraints)
result = prob.solve()
def compute_probability(data, choices):
# compute 1 - ((1 - p1) * (1 - p2) * ...)
return 1 - np.prod(np.add(1, -np.multiply(data, choices)))
print("Choices:")
print(x.value)
'''
Choices:
[[0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]]
'''
print("Chance of success:")
print(compute_probability(data, x.value))
'''
Chance of success:
0.7060000000000001
'''
我们有了它!计算机已正确确定运行方法 A 3 秒、方法 B 运行 3 秒、方法 C 运行 4 秒是最佳的。 (请记住,x 矩阵是 0 索引的,而时间是 1 索引的。)
谢谢大卫的建议!