-
Notifications
You must be signed in to change notification settings - Fork 174
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
Fix NNLS weight solvers #1027
Fix NNLS weight solvers #1027
Conversation
8fab651
to
43b1879
Compare
This looks good to me, and fixes the original problem. I also confirmed that if you use the nnls with weights=True, you get the same result as if you used weights=False (if the encoders are all positive). However, oddly, if we use normal encoders and weights=True, the results are worse than I'd expect. For example: model = nengo.Network()
with model:
stim = nengo.Node(lambda t: np.sin(t*np.pi*2))
a = nengo.Ensemble(50, 1)
b = nengo.Ensemble(50, 1)#, encoders=nengo.dists.Choice([[1]]))
nengo.Connection(stim, a)
nengo.Connection(a, b, solver=nengo.solvers.Nnls(weights=True))
p = nengo.Probe(b, synapse=0.03)
p_stim = nengo.Probe(stim, synapse=0.03)
sim = nengo.Simulator(model)
sim.run(2)
pylab.plot(sim.trange(), sim.data[p_stim])
pylab.plot(sim.trange(), sim.data[p])
pylab.show() produces this: It looks to me like it could have gotten a better fit by just multiplying all the weights by ~1.4. Why didn't it find that solution? |
Ah, that makes total sense. For about half the neurons, it's finding a pretty perfect set of weights. For the other half, it's supposed to find weights that give a negative number for the input current, but it can't, so it just gets that as small as possible. But that screws up the decode, giving a smaller value than it should (since those neurons that are getting too much input are screwing up the decode, and will always be pushing the decoded value towards zero). When I did the "just multiply by 1.4" thing, I was actually making the input current wrong for all the neurons, but in a way that things kinda cancelled as far as the decode was concerned. But in this scenario the weights that are best at getting the right input current to each neuron are not the weights that are best at getting the right decoded value out of the population. It'd be nice to toss this observation into the NNLS documentation, along with your suggestion for the intercepts. But that's a separate PR from this one. Now that I (think I) understand what's happening here, this PR looks great to me. |
There's still a few things I'm working on adding, so I can throw that bit of documentation in as well. |
fc19682
to
8373d0c
Compare
Ok @tcstewar, two more little commits you can look at. There was some strange bug in the Also, one thing worth mentioning is that |
Y, m, n, _, matrix_in = format_system(A, Y) | ||
Y = self.mul_encoders(Y, E, copy=True) | ||
d = Y.shape[1] | ||
Y[Y < 0] = 0 # makes the final RMS error more reasonable |
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.
I'm not sure we should be doing this. The RMS error is what it is -- this is returning the RMS error where any negative target value is treated as if its target value was 0, so it's not really the RMS error any more. Having this happen silently seems a bit odd to me...
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.
Err, never mind -- I misinterpreted what was happening here (I thought it was just about giving a more reasonable RMS report out of the solver, rather than something that affects the actual decoders)
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.
Oh no, you're right, this is bad. I liked it because it helped to compare different solvers when I was just playing around, but you're right that it misrepresents things. It could also be problematic if we ever have neurons that can have negative activities.
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.
Oh that's so strange. It does make a difference in the Gram system (if you do it before multiplying Y by the transposed activities), but not in the regular system. I have no idea why that is.
Looks good to me. I think doing the extra documentation in a notebook makes the most sense, as there are a lot of possibilities of different ways of making use of this solver, and it's not something that people have done much of yet. |
Ok, I redid those last two commits so they don't affect the RMSE. We just clip Y to be non-negative in the Gram system, because this helps for some reason. |
Cool. That looks great to me. Thanks! (and thanks for adding the TODO about figuring out what's happening there in the future... ) :) |
Yeah, I mean it kind of makes sense. Making Y non-negative doesn't change the original system, because the solution can never achieve those negative values, so it just does what it can to minimize the error on the positive ones and keeps the negatives zero. The value that minimizes In the Gram system, if we don't clip Y, then when we multiply by A transpose, some of the positives and negatives might cancel out, and we end up with a different system than if we clip Y first. So it makes sense that things change, and the whole Gram system/normal equation idea is based on things being linear (i.e. linear least squares), and when we have the non-negative constraint that's no longer the case. |
Added changelog entries. I think this is ready for merge. |
I'll look at this shortly as it's the last thing for 2.1.1! |
3723ebd
to
5ac2e95
Compare
5ac2e95
to
3e8e1f6
Compare
We found a minor issue which Eric fixed; history is clean now so I'll merge this tomorrow morning unless there are objections! |
3e8e1f6
to
b25505a
Compare
Also fix up regularization to be more accurate.
`ravel` is more efficient as it does not make copies.
- Fixes a bug where Nnls was returning weights of the wrong dimensionality when `weights=True` (fixes #1019). - Also fixes some solvers to be safer and faster in flattening the weights they return.
This was not working properly because we were not clipping `Y` values to be non-negative before forming the Gram system. Now, `Nnls(weights=True)` and `NnlsL2(weights=True, reg=0)` give almost identical results.
b25505a
to
bdcb2c0
Compare
Fixes #1019, as well as some other small issues.