scipy 约束线性回归/二次规划python

5hcedyr0  于 2022-11-23  发布在  Python
关注(0)|答案(4)|浏览(402)

我有这样一个数据集:

import numpy as np

a = np.array([1.2, 2.3, 4.2])
b = np.array([1, 5, 6])
c = np.array([5.4, 6.2, 1.9])

m = np.vstack([a,b,c])
y = np.array([5.3, 0.9, 5.6])

想要拟合一个约束线性回归
y = b1a + b2b + b3*c
其中所有b总和为1且为正:b1+b2+b3=1
在R中的类似问题在这里详细说明:
https://stats.stackexchange.com/questions/21565/how-do-i-fit-a-constrained-regression-in-r-so-that-coefficients-total-1
如何在python中执行此操作?

oyxsuwqo

oyxsuwqo1#

EDIT:* 这两种方法非常通用,适用于中小型示例。要获得更有效的方法,请查看*chthonicdaemon的答案(使用自定义的预处理和scipy的optimize.nnls)。

使用脚本

代码

import numpy as np
from scipy.optimize import minimize

a = np.array([1.2, 2.3, 4.2])
b = np.array([1, 5, 6])
c = np.array([5.4, 6.2, 1.9])

m = np.vstack([a,b,c])
y = np.array([5.3, 0.9, 5.6])

def loss(x):
    return np.sum(np.square((np.dot(x, m) - y)))

cons = ({'type': 'eq',
         'fun' : lambda x: np.sum(x) - 1.0})

x0 = np.zeros(m.shape[0])
res = minimize(loss, x0, method='SLSQP', constraints=cons,
               bounds=[(0, np.inf) for i in range(m.shape[0])], options={'disp': True})

print(res.x)
print(np.dot(res.x, m.T))
print(np.sum(np.square(np.dot(res.x, m) - y)))

输出

Optimization terminated successfully.    (Exit mode 0)
        Current function value: 18.817792344
        Iterations: 5
        Function evaluations: 26
        Gradient evaluations: 5
[ 0.7760881  0.         0.2239119]
[ 1.87173571  2.11955951  4.61630834]
18.817792344

评估

  • 很明显,模型能力/模型复杂度不足以获得良好的性能(高损耗!)

使用由cvxpy建模的通用QP/SOCP优化

优点:

  • cvxpy证明问题是凸的
  • 保证凸优化问题的收敛性(上述情况也可能如此)
  • 一般情况下:精度更高
  • 一般而言:在数值不稳定性方面更稳健(求解器仅能求解SOCP;而不是像上面的SLSQP方法那样的非凸模型!)

代码

import numpy as np
from cvxpy import *

a = np.array([1.2, 2.3, 4.2])
b = np.array([1, 5, 6])
c = np.array([5.4, 6.2, 1.9])

m = np.vstack([a,b,c])
y = np.array([5.3, 0.9, 5.6])

X = Variable(m.shape[0])
constraints = [X >= 0, sum_entries(X) == 1.0]

product = m.T * diag(X)
diff = sum_entries(product, axis=1) - y
problem = Problem(Minimize(norm(diff)), constraints)
problem.solve(verbose=True)

print(problem.value)
print(X.value)

输出

ECOS 2.0.4 - (C) embotech GmbH, Zurich Switzerland, 2012-15. Web: www.embotech.com/ECOS

It     pcost       dcost      gap   pres   dres    k/t    mu     step   sigma     IR    |   BT
 0  +0.000e+00  -0.000e+00  +2e+01  5e-01  1e-01  1e+00  4e+00    ---    ---    1  1  - |  -  - 
 1  +2.451e+00  +2.539e+00  +4e+00  1e-01  2e-02  2e-01  8e-01  0.8419  4e-02   2  2  2 |  0  0
 2  +4.301e+00  +4.306e+00  +2e-01  5e-03  7e-04  1e-02  4e-02  0.9619  1e-02   2  2  2 |  0  0
 3  +4.333e+00  +4.334e+00  +2e-02  4e-04  6e-05  1e-03  4e-03  0.9326  2e-02   2  1  2 |  0  0
 4  +4.338e+00  +4.338e+00  +5e-04  1e-05  2e-06  4e-05  1e-04  0.9698  1e-04   2  1  1 |  0  0
 5  +4.338e+00  +4.338e+00  +3e-05  8e-07  1e-07  3e-06  7e-06  0.9402  7e-03   2  1  1 |  0  0
 6  +4.338e+00  +4.338e+00  +7e-07  2e-08  2e-09  6e-08  2e-07  0.9796  1e-03   2  1  1 |  0  0
 7  +4.338e+00  +4.338e+00  +1e-07  3e-09  4e-10  1e-08  3e-08  0.8458  2e-02   2  1  1 |  0  0
 8  +4.338e+00  +4.338e+00  +7e-09  2e-10  2e-11  9e-10  2e-09  0.9839  5e-02   1  1  1 |  0  0

OPTIMAL (within feastol=1.7e-10, reltol=1.5e-09, abstol=6.5e-09).
Runtime: 0.000555 seconds.

4.337947939  # needs to be squared to be compared to scipy's output!
             #  as we are using l2-norm (outer sqrt) instead of sum-of-squares
             #  which is nicely converted to SOCP-form and easier to
             #  tackle by SOCP-based solvers like ECOS
             #  -> does not change the solution-vector x, only the obj-value
[[  7.76094262e-01]
 [  7.39698388e-10]
 [  2.23905737e-01]]
kpbpu008

kpbpu0082#

你可以通过一些数学运算和scipy.optimize.nnls得到一个很好的解决方案:
首先我们做一下数学计算:
如果
y = b1a + b2b + b3*c且b1 + b2 + b3 = 1,则b3 = 1 - b1 - b2。
如果我们进行替换和简化,我们最终得到
y - c = b1(a - c)+ b2(b - c)
现在,我们没有任何等式约束,nnls可以直接求解:

import scipy.optimize
A = np.vstack([a - c, b - c]).T
(b1, b2), norm = scipy.optimize.nnls(A, y - c)
b3 = 1 - b1 - b2

这恢复了使用cvxpy在另一个答案中获得的相同解。

b1 = 0.77608809648662802
b2 = 0.0
b3 = 0.22391190351337198
norm = 4.337947941595865

这种方法可以推广到任意维数的情况,如下所示:假设我们有一个矩阵B,它是由原始问题中的a、b、c按列排列而成的,任何额外的维数都将被添加到其中。
现在,我们可以做

A = B[:, :-1] - B[:, -1:]
bb, norm = scipy.optimize.nnls(A, y - B[:, -1])
bi = np.append(bb, 1 - sum(bb))
uidvcgyl

uidvcgyl3#

关于sascha的scipy实现的一点意见是:请注意,使用scipy minimize时,SLSQP的试错性质可能会使您得到一个“稍微”偏离的解决方案,除非您制定了一些其他规范,即最大迭代次数(maxiter)和最大容差(ftol),如此处的scipy文档中所详细说明的。
默认值为:最大值=100和FTOL= 1 E-06。
下面是一个使用矩阵表示法的示例:首先去掉约束和边界,为简单起见,还假设截距=0,在这种情况下,任何多元回归的系数(如第4页所述here)将(精确地)为:

def betas(y, x):
    # y and x are ndarrays--response & design matrixes
    return np.dot(np.linalg.inv(np.dot(x.T, x)), np.dot(x.T, y))

现在,假设最小二乘回归的目标是使残差平方和最小化,那么我们可以使用萨莎的损失函数(稍微重写了一下):

def resids(b, y, x):
    resid = y - np.dot(x, b)
    return np.dot(resid.T, resid)

给定实际的Y和X向量,你可以把上面第一个方程中的“真”beta值代入第二个方程,得到一个更好的“基准”。把这个基准与res的.fun属性(scipy最小化的结果)进行比较。即使是很小的变化也会对得到的系数产生有意义的变化。
所以长话短说,使用类似于

options={'maxiter' : 1000, 'ftol' : 1e-07}

在Sascha的代码里

t9aqgxwy

t9aqgxwy4#

你的问题是一个线性最小二乘问题,你可以使用qpsolvers中的solve_ls函数,直接用二次规划求解器来求解。

from qpsolvers import solve_ls

# Objective (|| R x - s ||^2): || [a b c] x - y ||^2
R = m.T
s = y

# Linear constraint (A * x == b): sum(x) = 1
A = np.ones((1, 3))
b = np.array([1.0])

# Box constraint (lb <= x): x >= 0
lb = np.zeros(3)

x = solve_ls(R, s, A=A, b=b, lb=lb, solver="quadprog")

在我的机器上,这段代码找到了解决方案x = array([0.7760881, 0.0, 0.2239119])。我已经将完整的代码上传到constrained_linear_regression.py,请随时尝试。

相关问题