如何将约束包含在Scipy NNLS函数解决方案中,使其总和为1

nev*_*int 7 python numpy scipy

我有以下代码来解决非负最小二乘法.使用scipy.nnls.

import numpy as np
from scipy.optimize import nnls 

A = np.array([[60, 90, 120], 
              [30, 120, 90]])

b = np.array([67.5, 60])

x, rnorm = nnls(A,b)

print x
#[ 0.          0.17857143  0.42857143]
# Now need to have this array sum to 1.
Run Code Online (Sandbox Code Playgroud)

我想要做的是对x解决方案应用约束,使其总和为1.我该怎么做?

Ed *_*ith 13

我不认为你可以nnls直接使用它调用的Fortran代码不允许额外的约束.但是,方程总和为1的约束可以作为第三个等式引入,因此您的示例系统具有以下形式,

60 x1 + 90  x2 + 120 x3 = 67.5
30 x1 + 120 x2 +  90 x3 = 60
   x1 +     x2 +     x3 = 1
Run Code Online (Sandbox Code Playgroud)

由于这是现在的一组线性方程,确切的溶液可以从获得x=np.dot(np.linalg.inv(A),b)使得x=[0.6875, 0.3750, -0.0625].这需要x3消极.因此,当x对这个问题有积极意义时,没有确切的解决方案.

对于x约束为正的近似解,可以使用,

import numpy as np
from scipy.optimize import nnls 

#Define minimisation function
def fn(x, A, b):
    return np.sum(A*x,1) - b

#Define problem
A = np.array([[60., 90., 120.], 
              [30., 120., 90.],
              [1.,  1.,   1. ]])

b = np.array([67.5, 60., 1.])

x, rnorm = nnls(A,b)

print(x,x.sum(),fn(x,A,b))
Run Code Online (Sandbox Code Playgroud)

给出x=[0.60003332, 0.34998889, 0.]了一个x.sum()=0.95.

我想如果你想要一个更通用的解决方案,包括求和约束,你需要在下面的形式中使用带有显式约束/边界的最小化,

import numpy as np
from scipy.optimize import minimize 
from scipy.optimize import nnls 

#Define problem
A = np.array([[60, 90, 120], 
              [30, 120, 90]])

b = np.array([67.5, 60])

#Use nnls to get initial guess
x0, rnorm = nnls(A,b)

#Define minimisation function
def fn(x, A, b):
    return np.linalg.norm(A.dot(x) - b)

#Define constraints and bounds
cons = {'type': 'eq', 'fun': lambda x:  np.sum(x)-1}
bounds = [[0., None],[0., None],[0., None]]

#Call minimisation subject to these values
minout = minimize(fn, x0, args=(A, b), method='SLSQP',bounds=bounds,constraints=cons)
x = minout.x

print(x,x.sum(),fn(x,A,b))
Run Code Online (Sandbox Code Playgroud)

给出x=[0.674999366, 0.325000634, 0.]x.sum()=1.从最小化开始,总和是正确的,但价值x并不完全正确np.dot(A,x)=[ 69.75001902, 59.25005706].