有了给定的信息,没有什么比某种形式的二进制搜索更好的了。
编辑:请参阅此答案末尾的编辑/备注以获得更好的解决方案(尽管没有严格的理论解释)!
这可以使用 scipy 的minimize_scalar 来实现。使用method: golden很重要!
方法 Golden 使用黄金分割搜索技术。它使用二分法的模拟来减小括号内的间隔。
问题在于没有任何实值答案。只有 yes/no 不允许形成任何类型的梯度信息或代理模型。
我假设:
- 我们正在寻找黑盒返回 1 的最小值
- 黑盒是确定性的
想法: 构建一些包装函数,该函数在返回 1 的最小值处具有最小值。
由于 x 应该在 [0,1] 中,试图最小化 x,我们可以将包装函数表示为:x + 1 - black_box(x)。每个答案为 0 的解决方案都 >= 每个答案为 1 的解决方案(可能在边界处需要一些保护措施;例如,x + (1 - eps) - black_box(x) eps 非常小!;可能需要在选择 xtol 时牢记)。
代码:
from scipy import optimize
SECRET_VAL = 0.7
def black_box(x):
if x > SECRET_VAL:
return 1.
else:
return 0.
def wrapper(x):
return x + 1 - black_box(x)
res = optimize.minimize_scalar(wrapper, bracket=(0,1), method='golden')
print(res)
输出:
fun: 0.7000000042155881
nfev: 44
nit: 39
success: True
x: 0.7000000042155881
或者secret_val=0.04:
fun: 0.04000000033008555
nfev: 50
nit: 45
success: True
x: 0.040000000330085564
或者如果你知道你需要什么样的准确度(原始秘密 0.7):
res = optimize.minimize_scalar(wrapper, bracket=(0,1), method='golden',
options={'xtol': 1e-2})
输出:
fun: 0.7000733152965655
nfev: 16 !!!!!
nit: 11
success: True
x: 0.7000733152965655
备注:
在这里编写一个基于二进制搜索的定制解决方案可能会更好(不是 100% 肯定)。但是考虑到缺少单峰性等假设,需要小心。
编辑:
好的……我终于把这个minimization-problem转化为求根问题,可以更高效地解决!
警告:
很明显,wrapper 永远不会返回 0.0 的值(找不到确切的根)!
但是二分法是关于zero crossing within the new intervalwiki。
所以在这里它找到了两个点a, b,其中函数的符号正在发生变化,并将其解释为一个根(考虑到一些容忍度!)。
与前一种方法相比,这种分析不如前一种方法那么严格(给出的分析不多,但在 scipy 的文档中使用纯最小化方法更容易做到)。
def wrapper_bisect(x):
return 1 - 2*black_box(x)
res = optimize.bisect(wrapper_bisect, 0, 1, xtol=1e-2, full_output=True)
print(res)
输出:
(0.6953125, converged: True
flag: 'converged'
function_calls: 9
iterations: 7
root: 0.6953125)
考虑到上述假设(并且只有这些假设),这应该是理论上的最优算法(我们将函数评估的数量从 16 个减少到 9 个;优化目标更差,但在界限)!
最后一次测试:
secret: 0.9813; xtol: 1e-4:
金色:
fun: 0.9813254238281632
nfev: 25
nit: 20
success: True
x: 0.9813254238291631
二等分:
(0.98126220703125, converged: True
flag: 'converged'
function_calls: 16
iterations: 14
root: 0.98126220703125)