-
Notifications
You must be signed in to change notification settings - Fork 14
Description
Background
Currently, NeMoS handles convolutions by pre-computing a full design matrix X from basis convolutions, then fitting via loglike(inv_link(X @ w)). This has two efficiency issues:
- Memory: For long time series, the design matrix
Xcan be very large - Multiple epoch handling: When fitting discontinuous trials of varying lengths, we currently loop over epochs and convolve one at a time, which is inefficient when convolution happens inside the optimization loop
Proposed solution: Generalized linear operators
Instead of hard-coding the dot product, generalize the GLM objective to:
loglike(inv_link(linear_operator(x, w)))Where linear_operator can flexibly combine different operations (convolution, dot product, etc.) while maintaining linearity.
Example: For a mixed design with one convolution feature and other regular features:
# Conceptually:
L(x, w) = convolve(x[:, slice_x_1], w[slice_w_1]) + x[:, slice_x_2] @ w[slice_w_2]
# Where:
# - len(slice_w_1) determines convolution kernel size (unrelated to len(slice_x_1))
# - len(slice_x_2) must equal len(slice_w_2) for dot product compatibilityExample 2: Convolution with basis
# B is a basis function kernel of shape (window_size, num_bases)
# Instead of convolving each basis separately and forming design matrix:
# X = convolve(x, B[:, 0]) | convolve(x, B[:, 1]) | ... (large matrix)
# We compute: convolve(x, B @ w) directly
lin_operator = lambda x, w: convolve(x, B @ w)
L(x, w) = lin_operator(x[:, slice_x_1], w[slice_w_1]) + x[:, slice_x_2] @ w[slice_w_2]Advantages:
- Memory: O(n log n) convolution without forming large design matrix
- Speed: FFT-based convolution is faster than matrix multiplication
- Flexibility: Can mix convolution with other linear operations
Challenge: Multiple epochs with varying lengths
People fit discontinuous time series where each epoch represents a trial. Trials may vary in duration. Currently using pynapple to chunk time series and convolving one trial at a time in a loop - fine for one-time convolution, but inefficient inside optimization.
Proposed strategy (discussed with Sarah):
- Group epochs by similar length
- Pad within groups to enable vectorized convolution
- Loop over small number of groups instead of all epochs
Open questions:
- How many groups is optimal?
- What's a good criterion for "similar enough" length? (e.g., within 10%? fixed bin sizes?)
- Trade-off between padding overhead vs. number of groups
Design considerations
Interface challenge: How do users specify custom linear operators intuitively?
Compatibility: Need to maintain current flexibility where:
- GLM is agnostic about design matrix
- Basis objects handle their own transformations
- Users can provide arbitrary designs
Software engineering needs:
- Refactor GLM internals to support pluggable linear operators
- Implement efficient multi-epoch batching/grouping
- Design user-friendly API for specifying operators