Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ff7c458
Update package versions and data file paths
davidOplatka Nov 12, 2025
2e9844e
Add baseline model (not fully trained)
davidOplatka Nov 12, 2025
6ed2237
Add model weights to .gitignore
davidOplatka Nov 12, 2025
68511ab
Merge pull request #1 from davidOplatka/David
davidOplatka Nov 13, 2025
20a05ca
Applied time masking in the training loop, added unit tests
gigichen880 Nov 15, 2025
ddf54a9
Merge pull request #2 from davidOplatka/Bree
davidOplatka Nov 16, 2025
5cbf591
Add Baseline Model Eval and Electrode Correlation
davidOplatka Nov 16, 2025
70508ab
LSTM model + Grad Clip + Speckle Noise
alejandresam Nov 16, 2025
e10ae39
Implement Feature Dropping
davidOplatka Nov 16, 2025
75f29a0
Add hyperparameter options for optimizer
davidOplatka Nov 16, 2025
4f46b80
Update model.py
yoyo-xyy Nov 17, 2025
b42d490
Add early stopping and SGF optimizer
davidOplatka Nov 18, 2025
dd4080a
TDS Conv blovks added
yoyo-xyy Nov 20, 2025
64aaf7f
New Time masking
alejandresam Nov 20, 2025
a2eeb1d
PCA Exploratory Analysis and Updated Learning Rate Schedulers
davidOplatka Nov 21, 2025
f688c1a
Implement PCA
davidOplatka Nov 21, 2025
65eedc5
Safe access of input features
davidOplatka Nov 21, 2025
55b47aa
Optimizer and PCA Bug Fixes
davidOplatka Nov 22, 2025
7a4e63e
Merge pull request #3 from davidOplatka/David
gigichen880 Nov 22, 2025
1f70052
Modify naming and change GPU set up
alejandresam Nov 22, 2025
d754dd3
setup left alone
alejandresam Nov 22, 2025
2b8c2c1
Merge branch 'master' into Samy
alejandresam Nov 24, 2025
9ef2925
Update conditional logic for model selection, add timewise for tds
yoyo-xyy Nov 24, 2025
2ed1c77
Merge pull request #7 from davidOplatka/Yoyo
davidOplatka Nov 24, 2025
26f7e30
Merge branch 'master' into Samy
davidOplatka Nov 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ share/python-wheels/
*.egg
MANIFEST

# Data Directory/Files
data/

# Model Weights Files
modelWeights

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
Expand Down Expand Up @@ -127,6 +133,7 @@ venv/
ENV/
env.bak/
venv.bak/
uv.*

# Spyder project settings
.spyderproject
Expand Down
Binary file added models/speechBaseline4/args
Binary file not shown.
Binary file added models/speechBaseline4/trainingStats
Binary file not shown.
678 changes: 678 additions & 0 deletions notebooks/electrodeCorrelation.ipynb

Large diffs are not rendered by default.

53 changes: 36 additions & 17 deletions notebooks/formatCompetitionData.ipynb

Large diffs are not rendered by default.

120 changes: 120 additions & 0 deletions notebooks/modelEvaluation.ipynb

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
torch
edit_distance
numpy
pytest
hydra-core
37 changes: 28 additions & 9 deletions scripts/train_model.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,47 @@

#import sys
#import pathlib

# Ensure the project's `src/` directory is on sys.path so package imports work
# when running this script directly (without installing the package).
#sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / 'src'))

modelName = 'speechBaseline4'

args = {}
args['outputDir'] = '/oak/stanford/groups/henderj/stfan/logs/speech_logs/' + modelName
args['datasetPath'] = '/oak/stanford/groups/henderj/fwillett/speech/ptDecoder_ctc'
args['outputDir'] = '../models/' + modelName
args['datasetPath'] = '../data/ptDecoder_ctc'
args['seqLen'] = 150
args['maxTimeSeriesLen'] = 1200
args['batchSize'] = 64
args['lrStart'] = 0.02
args['batchSize'] = 128
args['optimizer'] = 'ADAM'
args['warmupSteps'] = 500
args['decayType'] = 'linear'
args['lrStart'] = 0.05
args['lrEnd'] = 0.02
args['nUnits'] = 1024
args['optimizerEps'] = 0.1
args['SGDMomentum'] = 0
args['l2_decay'] = 1e-5
args['nUnits'] = 256
args['nBatch'] = 10000 #3000
args['patience'] = 10000 # Reduce to implement early stopping
args['nLayers'] = 5
args['seed'] = 0
args['nClasses'] = 40
args['nInputFeatures'] = 256
args['dropout'] = 0.4
args['nInputFeatures'] = 256 # Number of electrodes * 2
args['nThresholdCrossings'] = 128
args['nSpikeBandPowers'] = 128
args['dropout'] = 0.2
args['whiteNoiseSD'] = 0.8
args['constantOffsetSD'] = 0.2
args['gaussianSmoothWidth'] = 2.0
args['strideLen'] = 4
args['kernelLen'] = 32
args['bidirectional'] = True
args['l2_decay'] = 1e-5
args['bidirectional'] = False

# --- Time masking hyperparameters ---
args['timeMaskNum'] = 20 # N: number of masks per trial
args['timeMaskMaxFrac'] = 0.075 # M: max mask length as fraction of trial length

from neural_decoder.neural_decoder_trainer import trainModel

Expand Down
12 changes: 7 additions & 5 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ python_requires = >=3.9
# For more information, check out https://semver.org/.
install_requires =
importlib-metadata; python_version<"3.8"
torch==1.13.1
# torch==1.13.1
hydra-core==1.3.2
hydra-submitit-launcher==1.1.5
hydra-optuna-sweeper==1.2.0
numpy==1.25.0
scipy==1.11.1
numba==0.58.1
numpy==1.26.4
# scipy==1.11.1
scipy==1.16.0
# numba==0.58.1
numba==0.62.0
scikit-learn==1.3.2
g2p_en==2.1.0
edit_distance==1.0.6
Expand All @@ -56,4 +58,4 @@ install_requires =
where = src
exclude =
tests
examples
examples
12 changes: 12 additions & 0 deletions src/neural_decoder/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""neural_decoder package initializer.

This file makes `src/neural_decoder` a regular Python package so relative
and absolute imports work reliably when running scripts from the repository.
"""

__all__ = [
"augmentations",
"dataset",
"model",
"neural_decoder_trainer",
]
24 changes: 24 additions & 0 deletions src/neural_decoder/augmentations.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@ def forward(self, x):
noise = torch.randn(1, C) * self.std
return x + noise

class SpeckleNoise(nn.Module):
"""
Speckled masking / coordinated dropout over neural inputs.

This applies elementwise dropout on an input tensor of shape [B, T, C]
(batch, time, channels) with probability p. During training, each element
is zeroed with probability p and the remaining elements are scaled by
1 / (1 - p). At eval time, the input is returned unchanged.
"""
def __init__(self, p: float = 0.3):
super().__init__()
self.p = p

def forward(self, x: torch.Tensor) -> torch.Tensor:
# x can be [B, T, C] or any shape; we treat all elements independently.
if (not self.training) or self.p <= 0.0:
return x
# mask: 1 with prob (1-p), 0 with prob p
mask = (torch.rand_like(x) > self.p).to(x.dtype)
# Inverted dropout scaling so expectation is preserved
x = x * mask / (1.0 - self.p)
return x


class GaussianSmoothing(nn.Module):
"""
Apply gaussian smoothing on a
Expand Down
3 changes: 3 additions & 0 deletions src/neural_decoder/conf/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ datasetPath: /oak/stanford/groups/henderj/stfan/data/ptDecoder_ctc

seed: 0
batchSize: 64
rnn_type: "lstm"
lrStart: 0.02
lrEnd: 0.02
l2_decay: 1e-5
Expand All @@ -25,6 +26,8 @@ whiteNoiseSD: 0.8
constantOffsetSD: 0.2
gaussianSmoothWidth: 2.0

max_grad_norm: 5.0
speckle_prob: 0.3
nUnits: 1024
nLayers: 5
nInputFeatures: 256
Expand Down
42 changes: 34 additions & 8 deletions src/neural_decoder/dataset.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import numpy as np
import torch
from torch.utils.data import Dataset


class SpeechDataset(Dataset):
def __init__(self, data, transform=None):
def __init__(self, data, transform=None, num_threshold_crossings=128, num_spike_band_powers=128):
self.data = data
self.transform = transform
self.n_days = len(data)
Expand All @@ -14,13 +15,38 @@ def __init__(self, data, transform=None):
self.neural_time_bins = []
self.phone_seq_lens = []
self.days = []
for day in range(self.n_days):
for trial in range(len(data[day]["sentenceDat"])):
self.neural_feats.append(data[day]["sentenceDat"][trial])
self.phone_seqs.append(data[day]["phonemes"][trial])
self.neural_time_bins.append(data[day]["sentenceDat"][trial].shape[0])
self.phone_seq_lens.append(data[day]["phoneLens"][trial])
self.days.append(day)

if (num_threshold_crossings == 128) & (num_spike_band_powers == 128):
for day in range(self.n_days):
for trial in range(len(data[day]["sentenceDat"])):
self.neural_feats.append(data[day]["sentenceDat"][trial])
self.phone_seqs.append(data[day]["phonemes"][trial])
self.neural_time_bins.append(data[day]["sentenceDat"][trial].shape[0])
self.phone_seq_lens.append(data[day]["phoneLens"][trial])
self.days.append(day)
else:
pcs_tc = np.load("../data/threshold_crossing_principal_components.npy")
pcs_sbp = np.load("../data/spike_band_power_principal_components.npy")

tc_mean = np.load("../data/threshold_crossings_mean.npy")
sbp_mean = np.load("../data/spike_band_power_mean.npy")

for day in range(self.n_days):
for trial in range(len(data[day]["sentenceDat"])):
trial_data = data[day]["sentenceDat"][trial]
sample_tcs = trial_data.T[:128]
sample_sbp = trial_data.T[128:]

tc_new = (pcs_tc[:, :num_threshold_crossings].T @ (sample_tcs - tc_mean)).T
sbp_new = (pcs_sbp[:, :num_spike_band_powers].T @ (sample_sbp - sbp_mean)).T

trial_data_new = np.concatenate([tc_new, sbp_new], axis=1)

self.neural_feats.append(trial_data_new)
self.phone_seqs.append(data[day]["phonemes"][trial])
self.neural_time_bins.append(data[day]["sentenceDat"][trial].shape[0])
self.phone_seq_lens.append(data[day]["phoneLens"][trial])
self.days.append(day)

def __len__(self):
return self.n_trials
Expand Down
Loading