优化以最小化与SLSQP不兼容的不平等约束

时间:2019-07-08 10:43:18

标签: python scipy scipy-optimize

我正在尝试使用Scipy的Optimize运行一些曲线拟合。我不使用polyfit进行此操作,因为考虑到我具有的关系,我想确保曲线是单调的。因此,假设我在硫和温度之间具有以下关系:

sulphur = array([  71.,   82.,   50.,  113.,  153.,  177.,  394., 1239., 2070., 2662., 3516., 4000., 4954., 6314.])
temperature = array([ 70.,  90., 140., 165., 210., 235., 265., 330., 350., 390., 410., 435., 540., 580.])

我想找到一条曲线来拟合这种关系,该曲线是单调递增的。

我发现周围有一些代码,这就是我所拥有的:我计算多项式并将其用于目标函数,并且将一阶导数约束为每个点都为正。我还使用polyfit值作为x0来加快操作速度:

x = sul
y = temperature
initial = list(reversed(np.polyfit(sul, temperature, 3)))
Nfeval = 1

def polynomial(p, x):
    return p[0]+p[1]*x+p[2]*x**2+p[3]*x**3

def constraint_1st_der(p, x):
    return p[1]+2*p[2]*x+3*p[3]*x**2

def objective(p, x):
    return ((polynomial(p, x) - y)**2).sum()

def f(p):
    return objective(p, x)

def callback(p):
    global Nfeval
    print(Nfeval, p, constraint_1st_der(p, x))
    Nfeval += 1

cons = {'type' : 'ineq', 'fun' : lambda p : constraint_1st_der(p, x)}

res = optimize.minimize(f, x0=np.array(initial), method='SLSQP', constraints=cons, callback = callback)

但是,优化总是返回:

     fun: 4.0156824919527855e+23
     jac: array([0.00000000e+00, 0.00000000e+00, 7.02561542e+17, 3.62183986e+20])
 message: 'Inequality constraints incompatible'
    nfev: 6
     nit: 1
    njev: 1
  status: 4
 success: False
       x: array([ -111.35802358,  1508.06894349, -2969.11149743,  2223.26354865])

我尝试进行规范化(例如sul_norm = sul / max(sul)起作用),并且通过这样做,优化成功进行,但是我想避免这样做(我必须在某一点上反转函数,然后那么返回原始值时可能会变得混乱。另外,在我看来,这种关系是非常基本的,并且温度和硫之间的值在不同的范围内并不是那么极端。会是什么呢?谢谢!

2 个答案:

答案 0 :(得分:1)

您在这里遇到了各种限制问题:首先是求解器的选择,它极大地影响您可以执行的优化类型(约束,有界等)。 最重要的是,对于您的情况,您对参数感兴趣并设置了预定义的集(x, y),因此您应该以多维方式处理数据以进行与(x,y)相关的计算。但是,据我所知,您使用的约束定义适合于一维输出。因此,正如SO-question所建议的那样,使用渐变是一个好主意。

不幸的是,尽管对您的案例进行了测试,但是即使没有错误的代码运行,结果也无法令人信服。稍微调整一下代码后,我设法找到了一个不错的解决方法,但是我不确定那里是否是最好的方法。我的想法是使用Nelder-Mead Solver并使用等式约束来确保您的导数向量均为正。代码如下:

import numpy as np
from scipy import optimize
import matplotlib.pyplot as plt

np.set_printoptions(precision=3)
# init data
sulphur     = np.array([ 71.,  82.,  50., 113., 153., 177., 394., 1239., 2070., 2662., 3516., 4000., 4954., 6314.])
temperature = np.array([ 70.,  90., 140., 165., 210., 235., 265.,  330.,  350.,  390.,  410.,  435.,  540.,  580.])

# init x and y
x = sulphur
y = temperature

# compute initial guess
initial = list(reversed(np.polyfit(x, y, 3)))
Nfeval  = 1

# define functions
polynomial = lambda p, x: p[0] + p[1]*x +   p[2]*x**2 +   p[3]*x**3
derivative = lambda p, x:        p[1]   + 2*p[2]*x    + 3*p[3]*x**2 

def constraint(p):
    if (derivative(p, x) > 0).all() : return 0
    else                            : return -1

def callback(p):
    global Nfeval
    print("Evaluations nbr: %3s | p: %5s |cons: %3s" % (Nfeval,
                                                        p, 
                                                        constraint(p)))
    Nfeval += 1

func = lambda p: np.linalg.norm(y - polynomial(p, x))
cons = {'type' : 'eq', 'fun' : constraint}
res  = optimize.minimize(func, 
                         x0          = np.array(initial), 
                         method      ='Nelder-Mead', 
                         constraints = cons,
                         callback    = callback)
print('----------------------------------------------------------------------------------------')
print(res)

# plot results
f   = plt.figure(figsize=(10,4))
ax1 = f.add_subplot(131)
ax2 = f.add_subplot(132)
ax3 = f.add_subplot(133)

ax1.plot(x, y,'ro', label = 'data')
ax1.plot(x, polynomial(res.x,   x), label = 'fit using optimization', color="orange")
ax1.legend(loc=0) 
ax2.plot(x, derivative(res.x, x), label ='derivative')
ax2.legend(loc=0)
ax3.plot(x, y,'ro', label = 'data')
ax3.plot(x, polynomial(initial, x), label = 'fit using polyfit', color="green")
ax3.legend(loc=0)
plt.show()

输出:

.
.
.
Evaluations nbr:  95 | p: [ 1.400e+02  1.830e-01 -4.203e-05  3.882e-09] |cons:   0
Evaluations nbr:  96 | p: [ 1.400e+02  1.830e-01 -4.203e-05  3.882e-09] |cons:   0
Evaluations nbr:  97 | p: [ 1.400e+02  1.830e-01 -4.203e-05  3.882e-09] |cons:   0
Evaluations nbr:  98 | p: [ 1.400e+02  1.830e-01 -4.203e-05  3.882e-09] |cons:   0
----------------------------------------------------------------------------------------
 final_simplex: (array([[ 1.400e+02,  1.830e-01, -4.203e-05,  3.882e-09],
       [ 1.400e+02,  1.830e-01, -4.203e-05,  3.882e-09],
       [ 1.400e+02,  1.830e-01, -4.203e-05,  3.882e-09],
       [ 1.400e+02,  1.830e-01, -4.203e-05,  3.882e-09],
       [ 1.400e+02,  1.830e-01, -4.203e-05,  3.881e-09]]), array([159.565, 159.565, 159.565, 159.565, 159.565]))
           fun: 159.5654373399882
       message: 'Optimization terminated successfully.'
          nfev: 168
           nit: 99
        status: 0
       success: True
             x: array([ 1.400e+02,  1.830e-01, -4.203e-05,  3.882e-09])

图:

enter image description here

答案 1 :(得分:0)

问题出在边界上。对于极小的 floats ,某些数字的二进制表示不存在。在内部,优化器正在比较例如 99.9999999 -100 >0 并确定它们不相等(不满足边界)如果您的约束是 X-Y==.0 。达到 maxEval 后,它得出结论,没有满足约束的组合。为了避免这种情况,如果解决方案中没有问题,请将边界更改为 (0, 0.000001) 而不是 (0.,0.)