I am using a standard linear regression using scikit-learn in python. However, I would like to force the weights to be all non-negative for every feature. is there any way I can accomplish that? I was looking in the documentation but could not find a way to accomplish that.
What you are looking for, is the Non-negative least square regression. It is a simple optimization problem in quadratic programming where your constraint is that all the coefficients(a.k.a weights) should be positive.
But, looks like Scipy has implemented the same.
PS: I haven't tried the scipy version. I found it solely by googling around.
I use a workaround with Lasso on Scikit Learn (It is definitely not the best way to do things but it works well). Lasso has a parameter
positive which can be set to
True and force the coefficients to be positive. Further, setting the Regularization coefficient
alpha to lie close to 0 makes the Lasso mimic Linear Regression with no regularization. Here's the code:
from sklearn.linear_model import Lasso lin = Lasso(alpha=0.0001,precompute=True,max_iter=1000, positive=True, random_state=9999, selection='random') lin.fit(X,y)
As of version 0.24, scikit-learn
LinearRegression includes a parameter
positive, which does exactly that; from the docs:
positive : bool, default=False
When set to
True, forces the coefficients to be positive. This option is only supported for dense arrays.
New in version 0.24.
There are is a constrained least squares method scipy.optimize.lsq_linear. Another option is to use an optimizing solver for Python. Here is one of the options (Gekko) that I maintain that includes coefficient constraints.
# Constrained Multiple Linear Regression import numpy as np nd = 100 # number of data sets nc = 5 # number of inputs x = np.random.rand(nd,nc) y = np.random.rand(nd) from gekko import GEKKO m = GEKKO(remote=False); m.options.IMODE=2 c = m.Array(m.FV,nc+1) for ci in c: ci.STATUS=1 ci.LOWER=0 xd = m.Array(m.Param,nc) for i in range(nc): xd[i].value = x[:,i] yd = m.Param(y); yp = m.Var() s = m.sum([c[i]*xd[i] for i in range(nc)]) m.Equation(yp==s+c[-1]) m.Minimize((yd-yp)**2) m.solve(disp=True) a = [c[i].value for i in range(nc+1)] print('Solve time: ' + str(m.options.SOLVETIME)) print('Coefficients: ' + str(a))
It uses the nonlinear solver
IPOPT to solve the problem. It is a good option for problems that aren't too large because there is some waisted computational effort on calculating exact 1st and 2nd derivatives for possible nonlinear functions. It may be faster for larger problems with the
APOPT solver with
Here is an example of why you would want to do it (and approximately how).
I have 3 predictive models of housing prices: linear, gradient boosting, neural network.
I want to blend them into a weighted average and find the best weights.
I run linear regression, and I get a solution with weights like -3.1, 2.5, 1.5, and some intercept.
So what I do instead of using sklearn is:
blendlasso = LassoCV(alphas=np.logspace(-6, -3, 7), max_iter=100000, cv=5, fit_intercept=False, positive=True)
And I get positive weights that sum (very close) to 1. In my example, I want the alpha that works best out-of-sample so I use LassoCV with cross-validation.
The sklearn docs state that you shouldn't set alpha to 0 for numerical reasons, however, you can also use straight Lasso() and set the alpha parameter as low as you can get away with to get a reasonable answer.