-
-
Notifications
You must be signed in to change notification settings - Fork 11.1k
ENH: Add support for lstsq on stacks of matrices #15777
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
b5d0e0d
to
22d955c
Compare
Parameters | ||
---------- | ||
a : (M, N) array_like | ||
a : (..., M, N) array_like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, the current docstring here is very old and could use improvement. Just array_like on one line, then a description of the shapes in the explanatory part and how they are treated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This style of docstring with the shape in the argument name is fairly prevalent throughout this module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, could use improvement throughout. Not saying you should undertake the task in this PR.
numpy/linalg/linalg.py
Outdated
Least-squares solution. If `b` is two-dimensional, | ||
the solutions are in the `K` columns of `x`. | ||
residuals : {(1,), (K,), (0,)} ndarray | ||
residuals : {(), (..., K,)} ndarray | ||
Sums of residuals; squared Euclidean 2-norm for each column in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never much liked this, it isn't that useful. There have been a number of requests for returning the covariance matrix of the fitted coefficients.
I'm going to suggest a new function here. I don't feel strongly about broadcasting either way, but there have been requests for the covariance matrix of the fitted parameters and I often end up computing that myself as it is more informative and allows for generating error bars on curve fits and such. Unfortunately, I can't see if DGELSD provides sufficient information to do that, the documentation simply says that the input design matrix is destroyed. DGELSS looks like an option but I can't find it in our fallback routines. I suppose we could implement it for ourselves. Multiple return options would be required as computing the covariance could take significant time. |
That sounds like an orthogonal suggestion here - the only point of this change is to enable broadcasting, since Unfortunately, the meaning of |
It seemed to me the main change is that resids previously had (in some sense) a |
It's a little worse than that. The old behavior was to return any of:
The new behavior is
So there are two incompatible changes here, not just one. |
I suppose I was guessing that the majority of use cases is likely currently either in the So the only option would be keeping the |
Yes, I thought it might be more useful in the long run :) Computing the sum of squared residuals also has a noticeable impact on performance, which has also been the subject of complaints. But back to the case at hand with some explanation for other reviewers. The problem with the stacked case it that the dimensions of the returned residual array must be determined by M, N, and K and cannot depend on runtime results like matrix rank. DEGLS will always compute residuals if Unfortunately, we fixed the function rather than the documentation in #9986, so there is some question as to whether we should retain the NaN array return when the matrix rank is < N regardless. The upshot is that we need to make the change to deal with stacked arrays. It is enough of a corner case that I think we can do that, having zero/NaN arrays should deal with the case of downstream users being caught without being aware that something has changed. |
In short pseudo code
And an array always returned for the residuals. |
@IvanYashchuk since you've just spent a bunch of time dealing with this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The residuals change looks good to me. It's great to see the lstsq work on the stack of matrices.
I only have concerns about the behavior when the b
array is a stack of vectors, numpy.linalg.solve
works for this case, while numpy.linalg.lstsq
doesn't, which is weird since they would compute the same result for the well-determined square case.
@@ -2180,11 +2180,14 @@ def lstsq(a, b, rcond="warn"): | |||
Euclidean 2-norm :math:`||b - ax||`. If there are multiple minimizing | |||
solutions, the one with the smallest 2-norm :math:`||x||` is returned. | |||
|
|||
.. versionchanged:: 1.19 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be 1.21.0
?
"Coefficient" matrix. | ||
b : {(M,), (M, K)} array_like | ||
b : {(M,), (..., M, K)} array_like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
numpy.linalg.solve
has the following description for b
: {(…, M,), (…, M, K)}, array_like
. solve
works for the (…, M,)
case, while lstsq
from this PR raises LinAlgError: Incompatible dimensions
.
Should the behavior of this function follow the solve
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's probably a good idea, I hadn't realized that we can make that change without affecting compatibility. We'd replace the current is_1d
logic with something like
Lines 384 to 389 in 29561ed
# We use the b = (..., M,) logic, only if the number of extra dimensions | |
# match exactly | |
if b.ndim == a.ndim - 1: | |
gufunc = _umath_linalg.solve1 | |
else: | |
gufunc = _umath_linalg.solve |
Close #8720, at the cost of behavior changes in the
resids
return value. Marking as draft since I am publishing it primarily to facilitate discussion at that issue.