-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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: Refactor Xdawn #3425
MRG: Refactor Xdawn #3425
Conversation
|
||
# Get prototype events | ||
if events is not None: | ||
evokeds, toeplitzs = _least_square_evoked(epochs, events, tmin, sfreq) |
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.
Btw this (that is, _least_square_evoked
) is where an encoder model backbone would fit in.
toeplitz
is an encoder's X, evokeds
are the coefs of the fitted model/the estimated rERPs.
(As @alexandrebarachant has noted a few times, originally, XDawn is applied to continuous data, and this internally re-creates the continuous data.)
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.
Good, I was actually unclear what toeplitz was :/
Can you suggest a snippet to incorporate the encoding model here in a subsequent PR?
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.
With the current code, it would be something like
from mne.stats.regression import _prepare_rerp_preds, _make_evokeds
from mne.preprocessing.xdawn import _construct_signal_from_epochs
from sklearn.linear_model import Ridge
# function would have to be modified to return events)
raw_, events_ = _construct_signal_from_epochs(
events, epochs.info["sfreq"], tmin, tmax, epochs.get_data())
X, conds, cond_length, tmin_s, tmax_s = _prepare_rerp_preds(
raw_.n_times, raw_.info["sfreq"], events_, event_id, tmin, tmax)
coefs = Ridge(alpha=0).fit(X, raw_._data).coefs_
evokeds = _make_evokeds(coefs, conds, cond_length, tmin_s, tmax_s, raw_.info)
Hm. Doesn't look that hard actually.
I think I'll try finally doing this (1 year after I opened the issue) once this one is merged.
To build the evokeds, you need some information about the delays/estimated response functions per condition to extract the data from coefs. So if you use a different mechanism to build X/toeplitzs, that will have to be taken care of.
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.
_prepare_rerp_preds would have to be modified to optionally return the unstacked X, because XDawn uses the stacked X in _least_square_evoked, but XDawn also needs them unstacked later. Alternatively, I think X can be memory efficiently reconstructed on the fly, not sure.
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.
Okay, I think this would work:
from mne.stats.regression import _prepare_rerp_preds, _make_evokeds
from mne.preprocessing.xdawn import _construct_signal_from_epochs
from sklearn.linear_model import Ridge
from scipy import sparse
raw_ = _construct_signal_from_epochs(
events, epochs.info["sfreq"], tmin, tmax, epochs.get_data())
X, conds, cond_length, tmin_s, tmax_s = _prepare_rerp_preds(
raw_.n_times, raw_.info["sfreq"], mne.find_events(raw_),
event_id, tmin, tmax, stack=False)
coefs = Ridge(alpha=0).fit(sparse.hstack(X), raw_._data).coefs_
evokeds = _make_evokeds(coefs, conds, cond_length, tmin_s, tmax_s, raw_.info)
toeplitzs = [t_.toarray() for t_ in X]
Would be a 2 line change to _make_evokeds, and allow us to remove about 100 lines of code from XDawn and around 20 lines of code from linear_regression_raw tests.
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.
Would be a 2 line change to _make_evokeds, and allow us to remove about 100 lines of code from XDawn and around 20 lines of code from linear_regression_raw tests.
Nice, shall I try it in this PR, or does it require some other code?
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.
Let me do it. I've been postponing this for much too long.
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.
Can you open an Issue so that we don't forget, I won't handle this in this PR
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.
There already is one: #2332
Current coverage is 86.73% (diff: 95.41%)@@ master #3425 diff @@
==========================================
Files 339 337 -2
Lines 58637 58592 -45
Methods 0 0
Messages 0 0
Branches 8928 8920 -8
==========================================
- Hits 50856 50821 -35
+ Misses 5092 5085 -7
+ Partials 2689 2686 -3
|
@Eric89GXL I'm not sure I did the deprecation correctly could you check? @alexandrebarachant I incorporated the overlap correction here. Do you think it should be added direclty to pyRiemann? @kaichogami @dengemann @jona-sassenhagen Could you give it a review API wise? |
here is a snippet to test the new API:
|
# Plot in sensor space | ||
X_denoised = xd.inverse_transform(Xt) | ||
epochs_denoised = epochs.copy() | ||
epochs_denoised._data[test] = X_denoised |
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 prefer if you use EpochsArray rather than hacking private attributes.
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.
yes. I'm actually wondering whether I shouldn't just plot the data, since we're not using any particular topography, might as well keep it simple, WDYT?
I think that the decorator can work directly on a class like that. But just
try instantiating the class, that will tell you if it's working.
|
return evokeds | ||
n_components : int (default 2) | ||
The number of components to decompose the signals. | ||
reg : float | str | None (default None) |
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.
Brackets around None
.
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.
what do you mean @kaichogami , the rest of the doc doesn't seem to have bracket?
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.
It does, but @dengemann suggested it should be written in normal text and I changed it accordingly there. Or am I missing something here?
I think its a good idea to keep one |
|
@@ -77,6 +78,7 @@ def test_xdawn_fit(): | |||
assert_raises(ValueError, xd.fit, epochs) | |||
# fit with baseline correction and ovverlapp correction should throw an | |||
# error | |||
# XXX This is a buggy test, the epochs here don't overlap |
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.
FWIW the EEGLAB example data has overlapping events.
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.
Thx. We have to simulate however, else the tests are slower, no?
Overall, we could indeed keep a single Specifically,
The latter points needs not creating a new class, but I believe that the former does. Overall, I think we have multiple non-exclusive options
|
xd.fit(epochs) | ||
|
||
# Denoise epochs | ||
# Denoise epochs. Note that it outputs a numpy array |
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.
woops
I tried to successively implement three different API, and came to the conclusion to stick to the 3rd one:
Additionally, I:
However, I did not completely refactored the Xdawn class. The
So, ATM, I have not deprecated Xdawn as I suspect @agramfort wants a high level class, but I can already say that I don't think I won't be the one supporting such high level beast in the future. |
I'm only waiting for reviews |
@@ -76,5 +75,5 @@ | |||
# Denoise epochs | |||
epochs_denoised = xd.apply(epochs) | |||
|
|||
# Plot image epoch after xdawn | |||
# Plot |
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 a more descriptive one?
Can you set to MRG then? To ask a slightly different, question do you expect this to be merged today? Waiting for reviews and API comments could mean several more iterations, and more days of waiting, especially since current holiday time might make the review lag long. Based on the length of your last comment, I'm assuming this will be the case, no? In the meantime we should try to get |
# with this parameters, the overlapp correction must be False | ||
assert_equal(xd.correct_overlap, False) | ||
# no overlapp correction should give averaged evoked | ||
# With this parameters, the overlap correction must be 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.
these parameters
ok, I'll try to give a quick fix to the example in a separate PR. |
# Contruct raw signal | ||
raw = _construct_signal_from_epochs(epochs_data, events, sfreq, tmin) | ||
|
||
# Compute average evoked |
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 that's the correct term considering the overlap correction. Maybe more something like "Compute the independent evoked responses per condition, correcting for overlap".
(No evokeds are ever averaged, and no averaging is actually done.)
@Eric89GXL it's a linalg error in the xdawn. I don't manage to replicate it locally. Let's see whether setting the random seed fixes it. EDIT: no, that didn't work :/ |
If not, I recently added the buggy_mkl test decorator
|
@Eric89GXL I'm stuck. I installed python 2.6 with scipy 11.0, numpy 1.7 etc, and I can't reproduce the linalg error. Any chance you could have a look? |
It is CPU architecture dependent use the decorator
|
@kingjr debug and use mkl debug decorator for the tests |
Come on, that s not really helpful. I'm already using the mkl decorator. See L88 of the test. |
Sorry was offline, I will have time to review today and tomorrow.
|
Your error is not the SVD error that I saw before, so the MKL decorator will not help. It is a different linear algebra error: https://travis-ci.org/mne-tools/mne-python/jobs/147028330#L2833 It looks like it might be suggesting that your (covariance?) matrix is not positive definite. Are there zero eigenvalues? If so, it's not positive definite but positive semidefinite. I googled the error and first result was something very similar: scikit-learn/scikit-learn#2640 I wonder if you should use some other linear algebra function like |
Without looking at the code, I'd assume that it's happening on element 60 because it's EEG data with 60 channels and an avg ref applied, i.e. of rank 59? That's one way this could happen. |
I can relook into it, but I'm surprised it only crashes in one of the python job |
Slight numerical differences can crop up only sometimes. In this case they probably cause one of the eigenvalues to go to like -1e-16 or actual 0 instead of (in most cases) +1e-16. Or maybe |
@Eric89GXL ok thanks. I've tried to use the same package versions as the failing job, without success. Let's see whether the explicit non reference makes it. |
Looks like it passed other than the unrelated flake8 package error |
@kingjr ping us when CIs are happy |
@agramfort IIUC Cis are happy, it's a non related flake8 bug that made it crash. |
I fixed flake last night, just restarted the build |
FIX: docs FIX: error overlap Update xdawn example FIX: inverse transform dimensionality FIX: remove old decoding files FIX: EpochsArray in example, back to not cv address comments docs precise example doc ENH: XdawnTransformer accepts Epochs ENH: allow XdawnTransformer epochs Back to XdawnTransformer not accepting epochs ENH: add tests transform, inv_transform size FIX: extra Xdawn attributes mess
@agramfort confirmed CIs are happy |
thx @kingjr |
* FIX: original tests * REFACTOR: original xdawn * REFACTOR: XdawnTransformer FIX: docs FIX: error overlap Update xdawn example FIX: inverse transform dimensionality FIX: remove old decoding files FIX: EpochsArray in example, back to not cv address comments docs precise example doc ENH: XdawnTransformer accepts Epochs ENH: allow XdawnTransformer epochs Back to XdawnTransformer not accepting epochs ENH: add tests transform, inv_transform size FIX: extra Xdawn attributes mess * FIX: examples with XDawn instead of XdawnTransformer * FIX: address comments * FIX: explicit no ref in EEG
mne.preprocessing.xdawn
Xdawn.exclude
), add and document necessary ones (Xdawn.overlap_
,evokeds_
,.event_id_
)_xdawn_fit
private function for more explicit args.XdawnTransformer
devoid of any fancy objects (Evoked / dictionary etc), and only uses numpycc @kaichogami @alexandrebarachant @dengemann