| name | neural-manifold-dynamics-learning |
| description | Neural Manifold Learning Dynamics methodology for analyzing population activity in high-dimensional neural state spaces. Extracts low-dimensional structure from neural recordings to understand computation and behavior. Combines dimensionality reduction with dynamical systems analysis for neural population decoding. Activation: neural manifold, latent dynamics, population activity, dimensionality reduction, neural state space, behavior decoding, jPCA, dPCA, GPFA. |
| tags | ["neural-manifold","population-dynamics","dimensionality-reduction","jPCA","dPCA","GPFA","latent-dynamics","neural-decoding"] |
Neural Manifold Learning Dynamics
Overview
Neural manifold learning dynamics is a framework for understanding how populations of neurons coordinate their activity to generate behavior. The key insight is that despite having thousands or millions of neurons, neural activity often occupies a low-dimensional manifold within the high-dimensional neural state space.
Core Concepts
Neural Manifold Hypothesis
High-Dimensional Neural Space (N neurons)
↓
Low-Dimensional Manifold (d << N dimensions)
↓
Behavioral Output
Key Properties:
- Neural activity is constrained to a low-dimensional surface
- Manifold structure reflects computational strategies
- Dynamics on the manifold generate behavior
- Stable across trials and conditions
Dimensionality Reduction Methods
import numpy as np
from sklearn.decomposition import PCA
def extract_neural_manifold(spike_trains, method='PCA', n_components=10):
"""
Extract low-dimensional neural manifold from spike data.
Args:
spike_trains: (n_trials, n_neurons, n_timepoints) array
method: 'PCA', 'jPCA', 'dPCA', 'GPFA'
n_components: Dimensionality of manifold
Returns:
manifold_projection: Low-dimensional trajectory
components: Manifold basis vectors
explained_variance: Variance explained
"""
n_trials, n_neurons, n_timepoints = spike_trains.shape
data = spike_trains.reshape(-1, n_neurons)
if method == 'PCA':
model = PCA(n_components=n_components)
projection = model.fit_transform(data)
components = model.components_
variance = model.explained_variance_ratio_
elif method == 'jPCA':
projection, components, variance = jpca_analysis(spike_trains, n_components)
elif method == 'dPCA':
projection, components, variance = dpca_analysis(spike_trains, labels, n_components)
elif method == 'GPFA':
projection, components, variance = gpfa_analysis(spike_trains, n_components)
projection = projection.reshape(n_trials, n_timepoints, n_components)
return projection, components, variance
jPCA: Rotational Dynamics
JPCA (jPCA) discovers rotational dynamics in neural population activity, particularly relevant for motor cortex.
import numpy as np
from scipy.linalg import schur
def jpca_analysis(spike_trains, n_components=6):
"""
jPCA for extracting rotational dynamics.
Based on: Churchland et al. (2012) Neural population dynamics...
Args:
spike_trains: (n_conditions, n_neurons, n_timepoints)
n_components: Must be even (pairs of components)
Returns:
jPC_trajectories: Projected neural trajectories
jPCs: jPCA components
eigenvalues: Complex eigenvalues (rotation frequencies)
"""
n_conditions, n_neurons, n_timepoints = spike_trains.shape
mean_activity = spike_trains.mean(axis=0)
centered = mean_activity - mean_activity.mean(axis=1, keepdims=True)
pca = PCA(n_components=n_components)
pca_proj = pca.fit_transform(centered.T).T
dX = np.diff(pca_proj, axis=1)
X = pca_proj[:, :-1]
M = fit_skew_symmetric(X, dX)
T, Z = schur(M)
jPCs = pca.components_.T @ Z
jPC_trajectories = []
i (n_conditions):
traj = spike_trains[i].T @ jPCs
jPC_trajectories.append(traj)
eigenvalues = np.linalg.eigvals(M)
np.array(jPC_trajectories), jPCs, eigenvalues
():
n = X.shape[]
scipy.optimize minimize
():
M = m_flat.reshape(n, n)
M_skew = (M - M.T) /
pred = M_skew @ X
np.((dX - pred) ** )
result = minimize(objective, np.zeros(n * n), method=)
M_optimal = result.x.reshape(n, n)
M_skew = (M_optimal - M_optimal.T) /
M_skew
dPCA: Demixed Principal Component Analysis
Separates neural activity into components dependent on different task parameters.
def dpca_analysis(spike_trains, labels, n_components_per_marginal=3):
"""
Demixed PCA for separating task parameter contributions.
Args:
spike_trains: (n_stimuli, n_decisions, n_timepoints, n_neurons)
labels: Dictionary mapping parameter names to axis indices
n_components_per_marginal: Components per parameter
Returns:
dPCs: Demixed principal components
marginal_variances: Variance per parameter
"""
from dPCA import dPCA
dpca = dPCA(labels.keys(), n_components_per_marginal, regularizer='auto')
dpca.fit(spike_trains)
transformed = dpca.transform(spike_trains)
results = {}
for marginal in labels.keys():
results[marginal] = {
'components': dpca.P[marginal],
'variance': dpca.explained_variance_ratio_[marginal],
'trajectory': transformed[marginal]
}
return results
GPFA: Gaussian Process Factor Analysis
For extracting smooth latent trajectories from noisy spike data.
class GPFA:
"""
Gaussian Process Factor Analysis for neural population data.
Combines factor analysis (linear dimensionality reduction)
with Gaussian process priors (temporal smoothing).
"""
def __init__(self, n_latent_dimensions=3, em_max_iters=100):
self.n_latent = n_latent_dimensions
self.em_max_iters = em_max_iters
self.C = None
self.R = None
self.tau = None
def fit(self, spike_trains, bin_width=20):
"""
Fit GPFA model using EM algorithm.
Args:
spike_trains: List of (n_neurons, n_timepoints) arrays
bin_width: Bin width in ms
"""
Y = np.concatenate([y.T for y in spike_trains], axis=0)
n_time_total, n_neurons = Y.shape
pca = PCA(n_components=self.n_latent)
X_init = pca.fit_transform(Y)
self.C = pca.components_.T
self.R = np.eye(n_neurons) * 0.1
self.tau = np.ones(self.n_latent) * 100
iteration (.em_max_iters):
X, log_likelihood = ._e_step(spike_trains)
._m_step(X, spike_trains)
()
():
X_all = []
total_ll =
Y spike_trains:
n_time = Y.shape[]
K = ._build_gp_covariance(n_time)
X_trial, ll = ._kalman_smoother(Y, K)
X_all.append(X_trial)
total_ll += ll
X_all, total_ll
():
times = np.arange(n_time)
K = np.zeros((n_time, n_time))
i (n_time):
j (n_time):
dt = (times[i] - times[j])
K[i, j] = np.exp(-dt / .tau[])
K
():
n_time, n_latent = (Y), .n_latent
X_smooth = np.random.randn(n_time, n_latent)
log_likelihood =
X_smooth, log_likelihood
():
():
X_all, _ = ._e_step(spike_trains)
X_all
Manifold Dynamics Analysis
Trajectory Analysis
def analyze_manifold_trajectory(projection, time_points):
"""
Analyze dynamics on neural manifold.
Args:
projection: (n_timepoints, n_dimensions) trajectory
time_points: Time vector
Returns:
dynamics_metrics: Dictionary of dynamical properties
"""
velocity = np.gradient(projection, axis=0) / np.gradient(time_points)[:, None]
speed = np.linalg.norm(velocity, axis=1)
acceleration = np.gradient(velocity, axis=0) / np.gradient(time_points)[:, None]
curvature = np.linalg.norm(np.cross(velocity[:-1], acceleration[:-1]), axis=1) / \
(speed[:-1] ** 3 + 1e-10)
tangling = compute_tangling(projection, velocity)
return {
'velocity': velocity,
'speed': speed,
'acceleration': acceleration,
'curvature': curvature,
'tangling': tangling
}
def compute_tangling(trajectory, velocity):
"""
Compute trajectory tangling metric.
Tangling measures how often different neural states produce
the same future trajectory. Low tangling is good for decoding.
Based on: Russo et al. (2018) Motor cortex embeds muscle-like commands
"""
n_time = len(trajectory)
tangling_values = []
for t in range(n_time):
distances = np.linalg.norm(trajectory - trajectory[t], axis=)
nearby = np.where(distances < np.percentile(distances, ))[]
(nearby) > :
v_current = velocity[t]
v_nearby = velocity[nearby]
similarities = np.dot(v_nearby, v_current) / \
(np.linalg.norm(v_nearby, axis=) * np.linalg.norm(v_current) + )
tangling = - np.mean(similarities)
tangling_values.append(tangling)
np.mean(tangling_values)
Applications
1. Motor Cortex Decoding
def decode_movement_from_manifold(spike_trains, behavior, decoder_type='velocity'):
"""
Decode movement kinematics from neural manifold.
Args:
spike_trains: Neural activity (n_trials, n_neurons, n_time)
behavior: Movement data (n_trials, n_dims, n_time)
decoder_type: 'velocity', 'position', or 'force'
Returns:
decoder: Trained decoder model
predictions: Decoded behavior
accuracy: Decoding performance
"""
manifold, components, _ = extract_neural_manifold(
spike_trains, method='jPCA', n_components=6
)
X = manifold.reshape(-1, manifold.shape[-1])
y = behavior.reshape(-1, behavior.shape[-1])
from sklearn.linear_model import Ridge
decoder = Ridge(alpha=1.0)
decoder.fit(X, y)
predictions = decoder.predict(X)
accuracy = compute_decoding_accuracy(predictions, y, decoder_type)
return decoder, predictions, accuracy
2. Preparatory Activity Analysis
def analyze_preparatory_manifold(spike_trains, cue_onset, movement_onset):
"""
Analyze preparatory activity in neural manifold.
Key question: How is movement prepared before execution?
"""
prep_spikes = []
for trial in spike_trains:
cue_idx = cue_onset
move_idx = movement_onset
prep_activity = trial[:, cue_idx:move_idx]
prep_spikes.append(prep_activity)
prep_manifold, _, _ = extract_neural_manifold(
np.array(prep_spikes), method='dPCA', n_components=4
)
prep_movement_correlation = compute_prep_movement_correlation(
prep_manifold, behavior
)
return prep_manifold, prep_movement_correlation
3. Learning-Related Manifold Changes
def track_manifold_learning(spike_trains_early, spike_trains_late, behavior):
"""
Track how neural manifold changes with learning.
"""
man_early, comp_early, _ = extract_neural_manifold(spike_trains_early)
man_late, comp_late, _ = extract_neural_manifold(spike_trains_late)
dim_early = estimate_manifold_dimensionality(spike_trains_early)
dim_late = estimate_manifold_dimensionality(spike_trains_late)
align_early = compute_behavior_alignment(comp_early, behavior)
align_late = compute_behavior_alignment(comp_late, behavior)
consistency_early = compute_trajectory_consistency(man_early)
consistency_late = compute_trajectory_consistency(man_late)
return {
'dimensionality_change': dim_late - dim_early,
'behavior_alignment_change': align_late - align_early,
'consistency_change': consistency_late - consistency_early
}
Visualization
def plot_neural_manifold(projection, conditions=None, colors=None,
method='3d_trajectory'):
"""
Visualize neural manifold.
Args:
projection: (n_timepoints, n_dimensions) or list of trajectories
conditions: Labels for different conditions
colors: Color for each condition
method: '3d_trajectory', 'heat_map', 'state_space'
"""
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(12, 4))
if method == '3d_trajectory':
ax = fig.add_subplot(111, projection='3d')
if isinstance(projection, list):
for i, traj in enumerate(projection):
color = colors[i] if colors else None
label = conditions[i] if conditions else None
ax.plot(traj[:, 0], traj[:, 1], traj[:, 2],
color=color, label=label, alpha=0.6)
else:
ax.plot(projection[:, 0], projection[:, 1], projection[:, 2])
ax.set_xlabel('jPC1')
ax.set_ylabel('jPC2')
ax.set_zlabel('jPC3')
elif method == 'heat_map':
ax = fig.add_subplot()
scatter = ax.scatter(projection[:, ], projection[:, ],
c=conditions, cmap=, alpha=)
plt.colorbar(scatter, label=)
plt.legend()
plt.tight_layout()
fig
References
- Churchland, M. M., et al. (2012). Neural population dynamics during reaching. Nature, 487(7405), 51-56.
- Cunningham, J. P., & Yu, B. M. (2014). Dimensionality reduction for large-scale neural recordings. Nature Neuroscience, 17(11), 1500-1509.
- Gallego, J. A., et al. (2017). Neural manifolds for the control of movement. Neuron, 94(5), 978-984.
- Kaufman, M. T., et al. (2014). The role of premotor cortex in reaching movements. Neuron, 84(2), 468-482.
- Russo, A. A., et al. (2018). Motor cortex embeds muscle-like commands in an untangled population response. Neuron, 97(4), 953-966.
- Yu, B. M., et al. (2009). Gaussian-process factor analysis for low-dimensional single-trial analysis of neural population activity. Journal of Neurophysiology, 102(1), 614-635.
Activation Keywords
- neural manifold
- population dynamics
- latent dynamics
- jPCA rotation
- dPCA demixing
- GPFA trajectory
- neural state space
- dimensionality reduction neuroscience
- neural decoding manifold
- behavior encoding