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
[MRG] ENH: Optional positivity constraints on the dictionary and sparse code #6374
Conversation
In sparse_encode, you cannot ignore the positive argument for algorithms that do not support it. You need either to support it (for threshold it's just a question of putting the negative values to zero) or to raise an error. In addition, you need a test for all the features that you have added, here testing positive support for all algorithms. |
c33c2f2
to
dec16c1
Compare
Added positivity support for the threshold case and added a Added tests that verify the positivity constraint is met for the sparse code in a variety of cases. Also, verifies that the Additionally renamed the It would be nice to extend this to the dictionary, as well. This is something that Marial, et al. do in SPAMS. I know this will come down to some sort of thresholding on the dictionary, but am a little unclear as to where. If you have any thoughts, please share. |
c80e19e
to
fc931aa
Compare
So, I have added something that I think will correctly constrain the dictionary to positive values. It does appear to work. However, it may not be the best way or could have errors so feedback is definitely welcome here. Still not sure if there isn't something missing here. |
Thus far, using this function on more complex datasets seems to yield the right results. |
@@ -304,7 +321,7 @@ def sparse_encode(X, dictionary, gram=None, cov=None, algorithm='lasso_lars', | |||
|
|||
|
|||
def _update_dict(dictionary, Y, code, verbose=False, return_r2=False, | |||
random_state=None): | |||
random_state=None, dict_positive=False): |
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.
positive=False
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 had thought about this, but was concerned it might be unclear. If you would prefer it, I can make the change.
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 has been changed.
We need an example showing the interest of enforcing positivity to the dictionary / code. It would be great to compare to NMF estimators. I guess modifying faces decomposition examples is the easiest way, though it would be interesting to have a different usecase. |
So, I am using this in image processing of calcium image data from the mouse brain primarily (various regions). While there are many cases of using NMF in this area (and we tried ourselves), we found dictionary learning with positivity constraints was more performant. Even on artificial data (used for testing), I will get the wrong answer without this positivity constraint on the dictionary. The reason I added this is it was a feature provided in SPAMS by Marial, et al.. However, this package has gone without a release for a year and a half. I have tried patching it to fix various bugs that have crept up (NumPy support, better linking to the BLAS, etc.), but this solution is untenable. Especially as I am now looking to have Python 3 support and a new version of NumPy is on the horizon. It seems the original authors are likely exploring new interesting problems, which makes sense. Unfortunately, this is a problem from the maintenance side. I suppose one could argue about licensing too, but this seems secondary to having a working dependency stack. |
fc931aa
to
ae02712
Compare
I am sure there are some usecases where it performs better than NMF. However there is no point in providing a new functionality to the end-user without advertising it by an example. Do you have any idea ? I'll look into that as well |
Ok, sure, I'll try to come up with an example. It might not be today, but I will at least try tomorrow or this weekend. |
a1c1f53
to
7594fbf
Compare
For an example, take a look at this ( http://nbviewer.jupyter.org/gist/jakirkham/c5622e41843e6dafbcb7 ). I modified an example that I found in the scikit-learn docs and imposed the positivity constraints in different combinations. Thoughts? |
Sorry for our slow replies @jakirkham. I think we don't have maintainers that use these methods a lot unfortunately, and we are swamped with many PRs. We need to figure something out though :-/ |
Ping @vene |
(Caveat: I don't really have any experience with practical application of dictionary learning whatsoever...) Maybe just show the last slice of the example (positive dictionary + code) since it looks like the components are a bit sparser than NMF. Another idea is to check if it works as a topic model for text data, comparably to how NMF does. |
7594fbf
to
704d419
Compare
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.
LGTM but disclaimer: I am not familiar with the SPAMS implementation. Maybe @arthurmensch is more knowledgeable.
It would be great to update the faces decomposition example with a new entry with a positivity constraint on the dictionary components.
Whether to enforce positivity when finding the code. | ||
|
||
fit_positive : bool | ||
Whether to enforce positivity when finding the dictionary |
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.
Why not name this code_positive
and dict_positive
instead?
n_components = 8 | ||
|
||
dico = MiniBatchDictionaryLearning( | ||
n_components, transform_algorithm='lasso_lars', random_state=0, |
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.
If the test is not too long to run, it would be great to also check with other values of transform_algorithm
.
dict_positive=True, | ||
code_positive=True) | ||
assert_true((dictionary >= 0).all()) | ||
assert_true((code >= 0).all()) |
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.
And maybe also test for other combinations such as: (dict_positive=True
and dict_positive=False
) and (dict_positive=False
and dict_positive=True
).
`
401c65f
to
52d41af
Compare
Thanks for your feedback, @ogrisel. Have pushed some changes that hopefully address your comments and have squashed the history a bit. Tests appear to be passing. 🎉 For the most part this is just relying on whatever algorithm exposed positivity constraint is available and exposing that to the user. With the exception of the thresholding case where it is custom and orthogonal matching pursuit where we just error out (as it is unsupported), which is just following @GaelVaroquaux's advice earlier in the thread. So would hope knowledge of SPAMS is not required, but it would be nice to have someone with that expertise take a look, if they have time. As to an example (though we did look at this case in person), have the faces with the positivity constraint in a notebook. This shows a few different results depending on these constraints. Would be happy to add them if they would be useful. Could also play with different data like the digits set as you suggested or something else that we deem appropriate. Though would suggest either selecting one or two of these to add to the existing gallery or breaking them out on another doc page to avoid having too much info for users to ingest at one time. Thoughts on any/all of this would be welcome. 😄 |
@jakirkham yes! Please add the MBDL case with positive code and dict to the existing example for the faces decomposition. Maybe using the red-blue color map with white centered at zero for all those face plots would better highlight the differences between the decompositions. |
Maybe breaking out the positivity constraints on a simpler dataset like digits in a standalone example is a good idea too. I let you experiment and judge which is best. |
Also, I am wondering if it would not be better to rename "dict_positive" to "positive_dict" and "code_positive" to "positive_code". You are the native English speaker though :) |
Provides an option for dictionary learning to positively constrain the dictionary and the sparse code. This is useful in applications of dictionary learning where the data is know to be positive (e.g. images), but the sparsity constraint that dictionary learning has is better suited for factorizing the data in contrast to other positively constrained factorization techniques like NMF, which may not be similarly sparse.
52d41af
to
fd13a29
Compare
Ensure that when the positivity constraint is applied that the dictionary and code end up having only positive values in the respective results depending on whether dictionary and/or code are positively constrained.
fd13a29
to
076d049
Compare
Shows the various positivity constraints on dictionary learning and what the results of these look like using a Red to Blue color map. These are included in the examples and also in the docs below dictionary learning. All of these use the Olivetti faces as a training set.
Thanks for the suggestions, @ogrisel. Agree that The red/blue color map with faces is a good idea. Have included some code for this in Please let me know what you think. :) |
Any other thoughts? |
Thank you very much @jakirkham! That is a great contribution. |
I merged a bit too quickly: I forgot to ask for an entry in whats_new.rst and we also need the |
Great! This is a fantastic addition. Thanks a lot!
|
Of course. Thanks for the help reviewing. |
Allows for a positivity constraint to be set during dictionary learning. This should be very similar to one provided for by Marial, et al. in SPAMS. However, I may need guidance in making sure this is working correctly in all cases.
Todo: