Python中具有特定系数约束的多元线性回归

13

我目前正在对数据集运行多元线性回归。起初,我没有意识到需要对权重进行约束;实际上,我需要具有特定的正负权重。

更准确地说,我正在进行评分系统,这就是为什么我的某些变量应该对得分产生正面或负面影响的原因。然而,当运行我的模型时,结果并不符合我的预期,我的“正面”变量中有一些获得了负系数,反之亦然。

例如,假设我的模型为:

y = W0*x0 + W1*x1 + W2*x2 

当x2是一个“正”变量时,我希望对W2设置一个正的约束条件!

我已经寻找了很多关于这个问题的资料,但我没有找到关于特定权重/系数的约束条件,所有我发现的都是设置所有系数为正或将它们加和为一。

我正在使用ScikitLearn软件包在Python上工作。以下是我获得最佳模型的方法:

def ridge(Xtrain, Xtest, Ytrain, Ytest, position):
    param_grid={'alpha':[0.01 , 0.1, 1, 10, 50, 100, 1000]}
    gs = grid_search.GridSearchCV(Ridge(), param_grid=param_grid, n_jobs=-1, cv=3)
    gs.fit(Xtrain, Ytrain)
    hatytrain = gs.predict(Xtrain)
    hatytest = gs.predict(Xtest)

你有没有任何想法,如何对特定变量的系数进行约束?可能定义每个约束会很繁琐,但我不知道有其他方法怎么做。


为什么你需要使用scikit-learn呢?这只是一个函数拟合问题,不是吗?我敢打赌有更好的软件包可以完成这个任务,其中可以轻松指定要拟合参数的约束条件。 - mzoll
您可能考虑实现“砖墙”约束条件,即如果在正在拟合的函数内部违反了约束条件,则返回一个非常大的值-因此是一个非常大的误差。这种方法有些粗糙,但作为实际问题,它很容易编码并易于测试。 - James Phillips
2个回答

14

Scikit-learn不允许对系数施加这样的限制。

但是,如果您实现自己的估算器,则可以对系数强制任何约束并使用坐标下降法优化损失。在无约束情况下,坐标下降在合理的迭代次数内产生与OLS相同的结果。

我写了一个类,对LinearRegression系数施加了上限和下限。如果需要,您可以扩展它以使用Ridge或Lasso惩罚:

from sklearn.linear_model.base import LinearModel
from sklearn.base import RegressorMixin
from sklearn.utils import check_X_y
import numpy as np

class ConstrainedLinearRegression(LinearModel, RegressorMixin):

    def __init__(self, fit_intercept=True, normalize=False, copy_X=True, nonnegative=False, tol=1e-15):
        self.fit_intercept = fit_intercept
        self.normalize = normalize
        self.copy_X = copy_X
        self.nonnegative = nonnegative
        self.tol = tol

    def fit(self, X, y, min_coef=None, max_coef=None):
        X, y = check_X_y(X, y, accept_sparse=['csr', 'csc', 'coo'], y_numeric=True, multi_output=False)
        X, y, X_offset, y_offset, X_scale = self._preprocess_data(
            X, y, fit_intercept=self.fit_intercept, normalize=self.normalize, copy=self.copy_X)
        self.min_coef_ = min_coef if min_coef is not None else np.repeat(-np.inf, X.shape[1])
        self.max_coef_ = max_coef if max_coef is not None else np.repeat(np.inf, X.shape[1])
        if self.nonnegative:
            self.min_coef_ = np.clip(self.min_coef_, 0, None)

        beta = np.zeros(X.shape[1]).astype(float)
        prev_beta = beta + 1
        hessian = np.dot(X.transpose(), X)
        while not (np.abs(prev_beta - beta)<self.tol).all():
            prev_beta = beta.copy()
            for i in range(len(beta)):
                grad = np.dot(np.dot(X,beta) - y, X)
                beta[i] = np.minimum(self.max_coef_[i], 
                                     np.maximum(self.min_coef_[i], 
                                                beta[i]-grad[i] / hessian[i,i]))

        self.coef_ = beta
        self._set_intercept(X_offset, y_offset, X_scale)
        return self    

例如,您可以使用此类来使所有系数为非负数。

from sklearn.datasets import load_boston
from sklearn.linear_model import LinearRegression
X, y = load_boston(return_X_y=True)
model = ConstrainedLinearRegression(nonnegative=True)
model.fit(X, y)
print(model.intercept_)
print(model.coef_)

这将生成类似于以下输出:

-36.99292986145538
[0.         0.05286515 0.         4.12512386 0.         8.04017956
 0.         0.         0.         0.         0.         0.02273805
 0.        ]

您可以看到大多数系数都是零。一个普通的线性模型会使它们变成负数:

model = LinearRegression()
model.fit(X, y)
print(model.intercept_)
print(model.coef_)

它会返回给你

36.49110328036191
[-1.07170557e-01  4.63952195e-02  2.08602395e-02  2.68856140e+00
 -1.77957587e+01  3.80475246e+00  7.51061703e-04 -1.47575880e+00
  3.05655038e-01 -1.23293463e-02 -9.53463555e-01  9.39251272e-03
 -5.25466633e-01]

你也可以对任何系数施加任意限制 - 这就是你所要求的。例如,在这种设置中

model = ConstrainedLinearRegression()
min_coef = np.repeat(-np.inf, X.shape[1])
min_coef[0] = 0
min_coef[4] = -1
max_coef = np.repeat(4, X.shape[1])
max_coef[3] = 2
model.fit(X, y, max_coef=max_coef, min_coef=min_coef)
print(model.intercept_)
print(model.coef_)

你将会得到一个输出

24.060175576410515
[ 0.          0.04504673 -0.0354073   2.         -1.          4.
 -0.01343263 -1.17231216  0.2183103  -0.01375266 -0.7747823   0.01122374
 -0.56678676]

更新。这个解决方案可以适应对系数的线性组合的约束(例如它们的总和) - 在这种情况下,每个系数的独立约束将在每一步重新计算。这个 Github Gist 提供了一个例子。

更新 由于此问题的受欢迎程度,我创建了一个包,其中包含我的实现有约束的线性回归:https://github.com/avidale/constrained-linear-regression。 您可以使用 pip install constrained-linear-regression 安装它。欢迎提交拉取请求!


这是一个很棒的答案。感谢您分享。虽然有点晚了,但您是否也知道如何添加约束条件以限制所有系数的总和?例如对于股票组合,不仅系数不能为负数,而且它们的总和不能大于1。谢谢! - Angelo
如果您使用坐标下降法,则在每个步骤中只更新一个系数。因此,对系数之和的约束可以表示为对该特定系数的下限/上限。唯一的区别是,该约束的值将在每个步骤中重新计算。 - David Dale
1
当然,你需要提供一个可行的初始解决方案,而找到它可能是一个单独的问题。如果变得困难,你可能想要寻找专门用于约束优化的软件包(如cvxopt),而不是自己编写优化例程。 - David Dale
@DavidDale 我已经使用了scipy.optimize.differential_evolution遗传算法来寻找初始参数估计,并且取得了良好的成功。 - James Phillips
@DavidDale,我该如何在这段代码中添加套索/岭惩罚? - throwawaydisplayname
显示剩余3条评论

1
scikit-learn 的版本0.24.2中,您可以通过将参数positive=True传递给 LinearRegression 来强制算法使用正系数,通过将需要负系数的列乘以-1,您应该可以得到想要的结果。请注意保留HTML标签。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接