This repository is an implementation (training and inference) of an innovative DL architecture based on joint embedding that aims to link ATC communication to the corresponding trajectory.
Jupyter Notebook
3
19 commits
updated May 16, 2026
Python package for joint embedding of ATC voice communications and aircraft trajectories. The goal is to learn a shared representation space where a voice segment and its corresponding trajectory sit close together, enabling cross-modal retrieval (voice → next trajectory, trajectory → matching voice).
The project is part of the CITRIS aviation research context and is designed to be continued — each training stage is independent, checkpointed, and documented with a PDF report. See pdf_latex_reports/ for results so far.
Raw audio (.mp3) Raw trajectory (.nc / OpenSky)
│ │
▼ ▼
ATCTranscriptor MAE Encoder (external repo)
(Whisper or Wav2Vec2) CITRIS-modern-transformer-encoding
│ │
▼ ▼
Voice embeddings (.h5) Trajectory embeddings (.h5)
dim=1280 (Whisper) dim=1792 (14 × 128)
dim=1024 (Wav2Vec2)
│ │
└─────────────┬───────────────────────────┘
▼
Contrastive learning (InfoNCE)
FFNN projectors → joint space (dim=2048)
│
▼
Bijective mapping (RealNVP)
joint ↔ voice / joint ↔ traj
│
┌─────────┴─────────┐
▼ ▼
kNN lookup table Next-step predictor
(voice → traj) (partial traj → future)
Each stage saves checkpoints under checkpoints/ and results under results/.
git clone https://github.com/LouisBrusset/CITRIS-joint-embedding-Voice2Traj.git
cd CITRIS-joint-embedding-Voice2Traj
Create a Python 3.12 virtual environment (required for the CUDA torch wheels):
# With uv (recommended)
uv venv --python 3.12
.venv\Scripts\Activate.ps1
# Or plain venv
python -m venv .venv
.venv\Scripts\Activate.ps1
Install in editable mode:
uv pip install -e .
ffmpeg must be in PATH for audio processing (.mp3 input):
# Windows — with winget or choco
winget install ffmpeg
HuggingFace token (only needed if re-downloading models from the Hub):
HF_TOKEN in the environment, orhuggingface_token.json at the repo root or next to src/joint_embedding_voice2traj/model/wav2vect.py.Local copies of the pretrained models are already cached under models/.
The training scripts expect data on a separate drive. Before running any training, update the path constants at the top of the relevant config file.
| Config | Key path variables | Expected content |
|---|---|---|
config_voice.py | DATA_ROOT, OUTPUT_DIR | Raw audio per tower, H5 output dir |
config_contrastive.py | VOICE_EMB_DIR, TRAJ_EMB_DIR | H5 embedding files |
config_bijective.py | VOICE_EMB_DIR, TRAJ_EMB_DIR | Same H5 files |
config_nsp.py | inherits from above | Same H5 files |
config_next_step.py | DATA_PATH | Trajectory sequences |
The dataset contains recordings from 8 ATC towers:
knuq_gnd_twr koak_del_gnd_twr koak_norcal_app
ksfo_clnc_del ksfo_gnd_twr ksfo_norcal_app
ksjc_norcal_app ksjc_twr
Trajectory embeddings come from the MAE encoder trained in the companion repo CITRIS-modern-transformer-encoding. The config_next_step.py points to that checkpoint:
MAE_CHECKPOINT = "../CITRIS-modern-transformer-encoding/checkpoints/stage1_transformer_opensky_v0/best.pt"
CITRIS-joint-embedding-Voice2Traj/
├── checkpoints/ # Saved model weights (gitignored)
│ ├── ablation/
│ ├── bijective/
│ ├── contrastive/
│ ├── next_sentence_prediction/
│ └── next_step/
├── data/
│ └── step_1_trajectories/ # Raw trajectory data
├── dev/
│ └── test.ps1 # Windows test runner
├── models/ # Cached pretrained models
│ ├── wav2vec2-xls-r-300m-en-atc-uwb-atcc-and-atcosim/
│ ├── whisper-large-v3/
│ └── models--Systran--faster-whisper-large-v3/
├── notebooks/ # Jupyter notebooks
├── pdf_latex_reports/ # LaTeX-generated PDF reports per stage
├── results/ # Plots and metrics per training run
├── src/
│ ├── joint_embedding_voice2traj/
│ │ ├── __init__.py # Public API (lazy imports)
│ │ ├── model/ # Core model implementations
│ │ ├── ml_tools/ # Device selection, seeds, callbacks
│ │ ├── features_projection/ # UMAP visualization
│ │ └── training/ # One sub-package per training stage
│ │ ├── voice_features_inference/
│ │ ├── traj_features_inference/
│ │ ├── contrastive_learning/
│ │ ├── bijective_learning/
│ │ ├── next_step_prediction/
│ │ ├── next_sentence_prediction/
│ │ └── ablation_study/
│ └── tests/
├── Makefile
├── pyproject.toml
└── uv.lock
Each stage has a config_*.py file that centralizes all hyperparameters. Edit that file, then run the corresponding training.py (or run_all.py for the ablation).
Config: src/joint_embedding_voice2traj/training/voice_features_inference/config_voice.py
Extracts fixed-size voice embeddings from raw audio files and saves them to H5 files.
| Parameter | Default | Description |
|---|---|---|
VOICE_MODEL | "whisper" | "whisper" or "wav2vec2" |
SAMPLE_RATE | 16_000 | Audio sample rate (Hz) |
MAX_DURATION | 30 | Max audio clip length (seconds) |
BATCH_SIZE | 8 | Clips processed per batch |
TIME_MEAN_POOL | True | Average over time dimension → fixed-size vector |
SAVE_INTERMEDIATE | True | Save partial results during long runs |
Output dimensions:
Output files: {tower}_embeddings_{model}_{pool_tag}.h5 in OUTPUT_DIR.
Script: src/joint_embedding_voice2traj/training/traj_features_inference/retrieve_traj_features.py
Runs the MAE encoder (from CITRIS-modern-transformer-encoding) over trajectory sequences and saves the per-timestep hidden states. Output dimension: 14 timesteps × 128 d_model = 1792 (flattened).
Config: src/joint_embedding_voice2traj/training/contrastive_learning/config_contrastive.py
Trains two FFNN projectors (one for voice, one for trajectory) that push paired embeddings into a shared joint space using the InfoNCE (contrastive) loss.
| Parameter | Default | Description |
|---|---|---|
VOICE_MODEL | "whisper" | Source voice model |
VOICE_DIM | 1280 | Input dim for voice projector |
TRAJ_DIM | 1792 | Input dim for trajectory projector |
JOINT_DIM | 2048 | Shared embedding space dimension |
HIDDEN_DIM | 4096 | Hidden layer width in both FFNNs |
DROPOUT | 0.2 | Dropout rate in projectors |
TEMPERATURE_INIT | 0.07 | Initial InfoNCE temperature (learnable) |
BATCH_SIZE | 512 | Training batch size |
NUM_EPOCHS | 300 | Maximum training epochs |
LR | 1e-4 | Adam learning rate |
WEIGHT_DECAY | 1e-4 | L2 regularization |
GRAD_CLIP | 1.0 | Gradient clipping norm |
VAL_FRACTION | 0.1 | Fraction of data held out for validation |
PATIENCE | 15 | Early stopping patience (epochs) |
LR_PATIENCE | 7 | ReduceLROnPlateau patience |
LR_FACTOR | 0.5 | LR reduction factor |
LR_MIN | 1e-6 | Minimum learning rate |
PLOT_EVERY | 10 | Epoch interval for saving plots |
SEED | 42 | Random seed |
Checkpoints → checkpoints/contrastive/. A sweep variant (sweep_training.py) runs a grid over projector architectures.
Config: src/joint_embedding_voice2traj/training/bijective_learning/config_bijective.py
Trains a RealNVP normalizing flow that maps bidirectionally between the joint space and each modality space. This makes the joint embedding fully invertible: given a joint vector, you can reconstruct a voice or trajectory embedding.
The training set is augmented with synthetic data to improve coverage of the joint space:
| Parameter | Default | Description |
|---|---|---|
N_UNIFORM_SYNTHETIC | 20_000 | Uniform samples in bounding box |
N_NOISE_SYNTHETIC | 20_000 | Real samples + Gaussian noise |
NOISE_STD_FACTOR | 0.5 | Noise std relative to embedding std |
FFNN_BATCH_SIZE | 2048 | Batch size when projecting synthetic data through the contrastive FFNNs |
RealNVP architecture:
| Parameter | Default | Description |
|---|---|---|
N_BLOCKS | 8 | Number of affine coupling blocks |
HIDDEN_DIMS | [256, 256] | MLP width inside each conditioner |
USE_ACTNORM | True | Activation normalization between blocks |
USE_TANH | True | Tanh saturation on scale output |
JOINT_DIM | 2048 | Input/output dimension of the flow |
VOICE_PADDED_DIM | 768 | Voice space padded to this size |
TRAJ_PADDED_DIM | 256 | Trajectory space padded to this size |
Loss weights:
| Parameter | Default | Description |
|---|---|---|
LAMBDA_FORWARD | 0.5 | Forward reconstruction weight (joint → modality → joint) |
LAMBDA_BACKWARD | 0.5 | Backward reconstruction weight |
LAMBDA_CROSS | 0.1 | Cross-modal alignment: voice joint ↔ traj joint |
Training:
| Parameter | Default | Description |
|---|---|---|
NUM_EPOCHS | 200 | Maximum epochs |
BATCH_SIZE | 256 | Training batch size |
LR | 1e-4 | Adam learning rate |
WEIGHT_DECAY | 1e-4 | L2 regularization |
GRAD_CLIP | 1.0 | Gradient clipping norm |
PATIENCE | 20 | Early stopping patience |
LR_PATIENCE | 7 | ReduceLROnPlateau patience |
VIZ_EVERY | 10 | Plot interval (UMAP + scatter, 2000 samples) |
Checkpoints → checkpoints/bijective/.
Config: src/joint_embedding_voice2traj/training/next_step_prediction/config_next_step.py
Trains a lightweight MLP (StepPredictor) on top of the frozen MAE encoder. Given the encoder hidden states for the first 10 timesteps, it predicts the next 4 timesteps.
Sequence layout:
| Parameter | Default | Description |
|---|---|---|
N_CONTEXT | 10 | Number of context timesteps fed to the encoder |
N_FUTURE | 4 | Number of future steps to predict |
SEQ_LEN | 14 | Total sequence length (context + future) |
N_FEATURES | 6 | Features per step: lon, lat, alt, vel, track, vrate |
D_MODEL | 128 | MAE encoder hidden dimension |
StepPredictor MLP: (B, 4, 128) → flatten → (B, 512) → [512, 256] → (B, 4, 6)
Training:
| Parameter | Default | Description |
|---|---|---|
NUM_EPOCHS | 150 | Maximum epochs |
BATCH_SIZE | 256 | Training batch size |
LR | 1e-3 | Adam learning rate |
WEIGHT_DECAY | 1e-4 | L2 regularization |
GRAD_CLIP | 1.0 | Gradient clipping norm |
PATIENCE | 20 | Early stopping patience |
LR_PATIENCE | 7 | ReduceLROnPlateau patience |
PLOT_EVERY | 10 | Plot interval (6 trajectory examples) |
Checkpoints → checkpoints/next_step/. The contrastive and bijective checkpoints are loaded for evaluation only (not trained here).
Config: src/joint_embedding_voice2traj/training/next_sentence_prediction/config_nsp.py
Cross-modal retrieval: given a voice embedding, find the most similar trajectory in the lookup table.
Pipeline modes — controlled by USE_FFNN_PROJ:
USE_FFNN_PROJ | Pipeline |
|---|---|
True | voice → FFNN projector → voice_realnvp.joint_to_emb() → kNN search |
False | voice → traj_realnvp.emb_to_joint() → voice_realnvp.joint_to_emb() → kNN search |
kNN parameters:
| Parameter | Default | Description |
|---|---|---|
DISTANCE_METRIC | "cosine" | "cosine" or "l2" |
TOP_K | 1 | Number of retrieved trajectories |
The lookup table is precomputed from the training set and saved to checkpoints/next_sentence_prediction/lookup_table.npz. Run build_lookup_table.py once before using pipeline.py.
Config: src/joint_embedding_voice2traj/training/ablation_study/config_ablation.py
Compares two end-to-end architectures:
| Arch 1 | Arch 2 | |
|---|---|---|
| Structure | RealNVP + contrastive loss | Single RealNVP + reconstruction |
| Loss | InfoNCE (temperature=0.07) | Cycle-consistency reconstruction |
| Epochs | 300 | 200 |
| Batch size | 512 | 256 |
Both share the same RealNVP config: 8 blocks, hidden [256, 256], ActNorm=True, Tanh=True.
Run: python src/joint_embedding_voice2traj/training/ablation_study/run_all.py
Config: src/joint_embedding_voice2traj/features_projection/config_proj.py
| Parameter | Default | Description |
|---|---|---|
VOICE_MODEL | "whisper" | Which embeddings to project |
N_NEIGHBORS | 15 | UMAP neighbors parameter |
MIN_DIST | 0.1 | UMAP minimum distance |
METRIC | "cosine" | Distance metric |
| Notebook | Purpose |
|---|---|
| visualization_proj_voice_features.ipynb | UMAP of raw voice embeddings colored by tower |
| visualization_proj_traj_features.ipynb | UMAP of raw trajectory embeddings |
| visualization_proj_joint_space.ipynb | UMAP of the learned joint embedding space |
| from_traj_to_sentence.ipynb | End-to-end demo: trajectory → retrieved voice segment |
Once installed, all main classes are importable from the package root:
from joint_embedding_voice2traj import (
# Contrastive
FFNN, InfoNCELoss, PairDataset,
# Bijective
RealNVP1D, AffineCoupling1D, ActNorm1D, ConditionerMLP,
BijectiveReconstructionLoss, GaussianPriorLoss,
# ATC speech
ATCEncoder, ATCDecoder, ATCTranscriptor,
# Trajectory
LSTMPredictor, TimeSeriesDataset, pad_collate,
xLSTM, sLSTMCell, mLSTMCell, xLSTMBlock, TrajectoryMSELoss,
)
You can also import from submodules directly:
from joint_embedding_voice2traj.model.real_nvp import RealNVP1D
from joint_embedding_voice2traj.model.x_lstm import xLSTM
import torch
from joint_embedding_voice2traj import FFNN, InfoNCELoss
voice_proj = FFNN(input_dim=1280, hidden_dim=4096, output_dim=2048)
traj_proj = FFNN(input_dim=1792, hidden_dim=4096, output_dim=2048)
loss_fn = InfoNCELoss(temperature_init=0.07)
v = torch.randn(32, 1280) # batch of voice embeddings
t = torch.randn(32, 1792) # paired trajectory embeddings
z_v = voice_proj(v)
z_t = traj_proj(t)
loss = loss_fn(z_v, z_t)
import torch
from joint_embedding_voice2traj import RealNVP1D
flow = RealNVP1D(dim=2048, n_blocks=8, hidden_dims=[256, 256])
x = torch.randn(16, 2048)
z, log_det = flow.forward(x)
x_rec, _ = flow.inverse(z)
assert torch.allclose(x, x_rec, atol=1e-5)
from joint_embedding_voice2traj import ATCTranscriptor
transcriptor = ATCTranscriptor(use_lm=False, output_hidden_states=False)
result = transcriptor.forward("path/to/atc_audio.mp3", return_representation=True)
print(result["text"])
print(result["representation"].shape) # (1280,) with TIME_MEAN_POOL=True
Tests are organized under src/tests/ by subsystem.
Windows PowerShell:
.\dev\test.ps1 run-all
.\dev\test.ps1 run-real-nvp
.\dev\test.ps1 run-x-lstm
.\dev\test.ps1 run-lstm-normal
.\dev\test.ps1 run-ffnn-contrastive
.\dev\test.ps1 run-wav2vect
.\dev\test.ps1 clean-all
Unix / macOS:
make test-run-all
make test-run-real-nvp
make test-run-x-lstm
make test-run-lstm-normal
make test-run-ffnn-contrastive
make test-run-wav2vect
make test-clean-all
Direct pytest:
uv run pytest -v src/tests/
uv run pytest -v src/tests/test_real_nvp/
Notes:
test_wav2vect uses a real MP3 file and a pretrained model — it is heavier than the rest.htmlcov/.Generated LaTeX reports in pdf_latex_reports/:
| Report | Content |
|---|---|
| voice_latent_space/ | Whisper vs Wav2Vec2 embedding analysis, UMAP, per-tower statistics |
| inference/ | Voice feature extraction pipeline performance and embedding quality |
| contrastive/ | InfoNCE training curves, temperature evolution, retrieval metrics |
| bijective/ | RealNVP loss curves, cycle-consistency reconstruction error |
| next_step_prediction/ | StepPredictor accuracy, trajectory prediction visualizations |
| next_sentence_prediction/ | kNN lookup table retrieval performance |
This repository is an implementation (training and inference) of an innovative DL architecture based on joint embedding that aims to link ATC communication to the corresponding trajectory.
Jupyter Notebook
3
19 commits
updated May 16, 2026
Python package for joint embedding of ATC voice communications and aircraft trajectories. The goal is to learn a shared representation space where a voice segment and its corresponding trajectory sit close together, enabling cross-modal retrieval (voice → next trajectory, trajectory → matching voice).
The project is part of the CITRIS aviation research context and is designed to be continued — each training stage is independent, checkpointed, and documented with a PDF report. See pdf_latex_reports/ for results so far.
Raw audio (.mp3) Raw trajectory (.nc / OpenSky)
│ │
▼ ▼
ATCTranscriptor MAE Encoder (external repo)
(Whisper or Wav2Vec2) CITRIS-modern-transformer-encoding
│ │
▼ ▼
Voice embeddings (.h5) Trajectory embeddings (.h5)
dim=1280 (Whisper) dim=1792 (14 × 128)
dim=1024 (Wav2Vec2)
│ │
└─────────────┬───────────────────────────┘
▼
Contrastive learning (InfoNCE)
FFNN projectors → joint space (dim=2048)
│
▼
Bijective mapping (RealNVP)
joint ↔ voice / joint ↔ traj
│
┌─────────┴─────────┐
▼ ▼
kNN lookup table Next-step predictor
(voice → traj) (partial traj → future)
Each stage saves checkpoints under checkpoints/ and results under results/.
git clone https://github.com/LouisBrusset/CITRIS-joint-embedding-Voice2Traj.git
cd CITRIS-joint-embedding-Voice2Traj
Create a Python 3.12 virtual environment (required for the CUDA torch wheels):
# With uv (recommended)
uv venv --python 3.12
.venv\Scripts\Activate.ps1
# Or plain venv
python -m venv .venv
.venv\Scripts\Activate.ps1
Install in editable mode:
uv pip install -e .
ffmpeg must be in PATH for audio processing (.mp3 input):
# Windows — with winget or choco
winget install ffmpeg
HuggingFace token (only needed if re-downloading models from the Hub):
HF_TOKEN in the environment, orhuggingface_token.json at the repo root or next to src/joint_embedding_voice2traj/model/wav2vect.py.Local copies of the pretrained models are already cached under models/.
The training scripts expect data on a separate drive. Before running any training, update the path constants at the top of the relevant config file.
| Config | Key path variables | Expected content |
|---|---|---|
config_voice.py | DATA_ROOT, OUTPUT_DIR | Raw audio per tower, H5 output dir |
config_contrastive.py | VOICE_EMB_DIR, TRAJ_EMB_DIR | H5 embedding files |
config_bijective.py | VOICE_EMB_DIR, TRAJ_EMB_DIR | Same H5 files |
config_nsp.py | inherits from above | Same H5 files |
config_next_step.py | DATA_PATH | Trajectory sequences |
The dataset contains recordings from 8 ATC towers:
knuq_gnd_twr koak_del_gnd_twr koak_norcal_app
ksfo_clnc_del ksfo_gnd_twr ksfo_norcal_app
ksjc_norcal_app ksjc_twr
Trajectory embeddings come from the MAE encoder trained in the companion repo CITRIS-modern-transformer-encoding. The config_next_step.py points to that checkpoint:
MAE_CHECKPOINT = "../CITRIS-modern-transformer-encoding/checkpoints/stage1_transformer_opensky_v0/best.pt"
CITRIS-joint-embedding-Voice2Traj/
├── checkpoints/ # Saved model weights (gitignored)
│ ├── ablation/
│ ├── bijective/
│ ├── contrastive/
│ ├── next_sentence_prediction/
│ └── next_step/
├── data/
│ └── step_1_trajectories/ # Raw trajectory data
├── dev/
│ └── test.ps1 # Windows test runner
├── models/ # Cached pretrained models
│ ├── wav2vec2-xls-r-300m-en-atc-uwb-atcc-and-atcosim/
│ ├── whisper-large-v3/
│ └── models--Systran--faster-whisper-large-v3/
├── notebooks/ # Jupyter notebooks
├── pdf_latex_reports/ # LaTeX-generated PDF reports per stage
├── results/ # Plots and metrics per training run
├── src/
│ ├── joint_embedding_voice2traj/
│ │ ├── __init__.py # Public API (lazy imports)
│ │ ├── model/ # Core model implementations
│ │ ├── ml_tools/ # Device selection, seeds, callbacks
│ │ ├── features_projection/ # UMAP visualization
│ │ └── training/ # One sub-package per training stage
│ │ ├── voice_features_inference/
│ │ ├── traj_features_inference/
│ │ ├── contrastive_learning/
│ │ ├── bijective_learning/
│ │ ├── next_step_prediction/
│ │ ├── next_sentence_prediction/
│ │ └── ablation_study/
│ └── tests/
├── Makefile
├── pyproject.toml
└── uv.lock
Each stage has a config_*.py file that centralizes all hyperparameters. Edit that file, then run the corresponding training.py (or run_all.py for the ablation).
Config: src/joint_embedding_voice2traj/training/voice_features_inference/config_voice.py
Extracts fixed-size voice embeddings from raw audio files and saves them to H5 files.
| Parameter | Default | Description |
|---|---|---|
VOICE_MODEL | "whisper" | "whisper" or "wav2vec2" |
SAMPLE_RATE | 16_000 | Audio sample rate (Hz) |
MAX_DURATION | 30 | Max audio clip length (seconds) |
BATCH_SIZE | 8 | Clips processed per batch |
TIME_MEAN_POOL | True | Average over time dimension → fixed-size vector |
SAVE_INTERMEDIATE | True | Save partial results during long runs |
Output dimensions:
Output files: {tower}_embeddings_{model}_{pool_tag}.h5 in OUTPUT_DIR.
Script: src/joint_embedding_voice2traj/training/traj_features_inference/retrieve_traj_features.py
Runs the MAE encoder (from CITRIS-modern-transformer-encoding) over trajectory sequences and saves the per-timestep hidden states. Output dimension: 14 timesteps × 128 d_model = 1792 (flattened).
Config: src/joint_embedding_voice2traj/training/contrastive_learning/config_contrastive.py
Trains two FFNN projectors (one for voice, one for trajectory) that push paired embeddings into a shared joint space using the InfoNCE (contrastive) loss.
| Parameter | Default | Description |
|---|---|---|
VOICE_MODEL | "whisper" | Source voice model |
VOICE_DIM | 1280 | Input dim for voice projector |
TRAJ_DIM | 1792 | Input dim for trajectory projector |
JOINT_DIM | 2048 | Shared embedding space dimension |
HIDDEN_DIM | 4096 | Hidden layer width in both FFNNs |
DROPOUT | 0.2 | Dropout rate in projectors |
TEMPERATURE_INIT | 0.07 | Initial InfoNCE temperature (learnable) |
BATCH_SIZE | 512 | Training batch size |
NUM_EPOCHS | 300 | Maximum training epochs |
LR | 1e-4 | Adam learning rate |
WEIGHT_DECAY | 1e-4 | L2 regularization |
GRAD_CLIP | 1.0 | Gradient clipping norm |
VAL_FRACTION | 0.1 | Fraction of data held out for validation |
PATIENCE | 15 | Early stopping patience (epochs) |
LR_PATIENCE | 7 | ReduceLROnPlateau patience |
LR_FACTOR | 0.5 | LR reduction factor |
LR_MIN | 1e-6 | Minimum learning rate |
PLOT_EVERY | 10 | Epoch interval for saving plots |
SEED | 42 | Random seed |
Checkpoints → checkpoints/contrastive/. A sweep variant (sweep_training.py) runs a grid over projector architectures.
Config: src/joint_embedding_voice2traj/training/bijective_learning/config_bijective.py
Trains a RealNVP normalizing flow that maps bidirectionally between the joint space and each modality space. This makes the joint embedding fully invertible: given a joint vector, you can reconstruct a voice or trajectory embedding.
The training set is augmented with synthetic data to improve coverage of the joint space:
| Parameter | Default | Description |
|---|---|---|
N_UNIFORM_SYNTHETIC | 20_000 | Uniform samples in bounding box |
N_NOISE_SYNTHETIC | 20_000 | Real samples + Gaussian noise |
NOISE_STD_FACTOR | 0.5 | Noise std relative to embedding std |
FFNN_BATCH_SIZE | 2048 | Batch size when projecting synthetic data through the contrastive FFNNs |
RealNVP architecture:
| Parameter | Default | Description |
|---|---|---|
N_BLOCKS | 8 | Number of affine coupling blocks |
HIDDEN_DIMS | [256, 256] | MLP width inside each conditioner |
USE_ACTNORM | True | Activation normalization between blocks |
USE_TANH | True | Tanh saturation on scale output |
JOINT_DIM | 2048 | Input/output dimension of the flow |
VOICE_PADDED_DIM | 768 | Voice space padded to this size |
TRAJ_PADDED_DIM | 256 | Trajectory space padded to this size |
Loss weights:
| Parameter | Default | Description |
|---|---|---|
LAMBDA_FORWARD | 0.5 | Forward reconstruction weight (joint → modality → joint) |
LAMBDA_BACKWARD | 0.5 | Backward reconstruction weight |
LAMBDA_CROSS | 0.1 | Cross-modal alignment: voice joint ↔ traj joint |
Training:
| Parameter | Default | Description |
|---|---|---|
NUM_EPOCHS | 200 | Maximum epochs |
BATCH_SIZE | 256 | Training batch size |
LR | 1e-4 | Adam learning rate |
WEIGHT_DECAY | 1e-4 | L2 regularization |
GRAD_CLIP | 1.0 | Gradient clipping norm |
PATIENCE | 20 | Early stopping patience |
LR_PATIENCE | 7 | ReduceLROnPlateau patience |
VIZ_EVERY | 10 | Plot interval (UMAP + scatter, 2000 samples) |
Checkpoints → checkpoints/bijective/.
Config: src/joint_embedding_voice2traj/training/next_step_prediction/config_next_step.py
Trains a lightweight MLP (StepPredictor) on top of the frozen MAE encoder. Given the encoder hidden states for the first 10 timesteps, it predicts the next 4 timesteps.
Sequence layout:
| Parameter | Default | Description |
|---|---|---|
N_CONTEXT | 10 | Number of context timesteps fed to the encoder |
N_FUTURE | 4 | Number of future steps to predict |
SEQ_LEN | 14 | Total sequence length (context + future) |
N_FEATURES | 6 | Features per step: lon, lat, alt, vel, track, vrate |
D_MODEL | 128 | MAE encoder hidden dimension |
StepPredictor MLP: (B, 4, 128) → flatten → (B, 512) → [512, 256] → (B, 4, 6)
Training:
| Parameter | Default | Description |
|---|---|---|
NUM_EPOCHS | 150 | Maximum epochs |
BATCH_SIZE | 256 | Training batch size |
LR | 1e-3 | Adam learning rate |
WEIGHT_DECAY | 1e-4 | L2 regularization |
GRAD_CLIP | 1.0 | Gradient clipping norm |
PATIENCE | 20 | Early stopping patience |
LR_PATIENCE | 7 | ReduceLROnPlateau patience |
PLOT_EVERY | 10 | Plot interval (6 trajectory examples) |
Checkpoints → checkpoints/next_step/. The contrastive and bijective checkpoints are loaded for evaluation only (not trained here).
Config: src/joint_embedding_voice2traj/training/next_sentence_prediction/config_nsp.py
Cross-modal retrieval: given a voice embedding, find the most similar trajectory in the lookup table.
Pipeline modes — controlled by USE_FFNN_PROJ:
USE_FFNN_PROJ | Pipeline |
|---|---|
True | voice → FFNN projector → voice_realnvp.joint_to_emb() → kNN search |
False | voice → traj_realnvp.emb_to_joint() → voice_realnvp.joint_to_emb() → kNN search |
kNN parameters:
| Parameter | Default | Description |
|---|---|---|
DISTANCE_METRIC | "cosine" | "cosine" or "l2" |
TOP_K | 1 | Number of retrieved trajectories |
The lookup table is precomputed from the training set and saved to checkpoints/next_sentence_prediction/lookup_table.npz. Run build_lookup_table.py once before using pipeline.py.
Config: src/joint_embedding_voice2traj/training/ablation_study/config_ablation.py
Compares two end-to-end architectures:
| Arch 1 | Arch 2 | |
|---|---|---|
| Structure | RealNVP + contrastive loss | Single RealNVP + reconstruction |
| Loss | InfoNCE (temperature=0.07) | Cycle-consistency reconstruction |
| Epochs | 300 | 200 |
| Batch size | 512 | 256 |
Both share the same RealNVP config: 8 blocks, hidden [256, 256], ActNorm=True, Tanh=True.
Run: python src/joint_embedding_voice2traj/training/ablation_study/run_all.py
Config: src/joint_embedding_voice2traj/features_projection/config_proj.py
| Parameter | Default | Description |
|---|---|---|
VOICE_MODEL | "whisper" | Which embeddings to project |
N_NEIGHBORS | 15 | UMAP neighbors parameter |
MIN_DIST | 0.1 | UMAP minimum distance |
METRIC | "cosine" | Distance metric |
| Notebook | Purpose |
|---|---|
| visualization_proj_voice_features.ipynb | UMAP of raw voice embeddings colored by tower |
| visualization_proj_traj_features.ipynb | UMAP of raw trajectory embeddings |
| visualization_proj_joint_space.ipynb | UMAP of the learned joint embedding space |
| from_traj_to_sentence.ipynb | End-to-end demo: trajectory → retrieved voice segment |
Once installed, all main classes are importable from the package root:
from joint_embedding_voice2traj import (
# Contrastive
FFNN, InfoNCELoss, PairDataset,
# Bijective
RealNVP1D, AffineCoupling1D, ActNorm1D, ConditionerMLP,
BijectiveReconstructionLoss, GaussianPriorLoss,
# ATC speech
ATCEncoder, ATCDecoder, ATCTranscriptor,
# Trajectory
LSTMPredictor, TimeSeriesDataset, pad_collate,
xLSTM, sLSTMCell, mLSTMCell, xLSTMBlock, TrajectoryMSELoss,
)
You can also import from submodules directly:
from joint_embedding_voice2traj.model.real_nvp import RealNVP1D
from joint_embedding_voice2traj.model.x_lstm import xLSTM
import torch
from joint_embedding_voice2traj import FFNN, InfoNCELoss
voice_proj = FFNN(input_dim=1280, hidden_dim=4096, output_dim=2048)
traj_proj = FFNN(input_dim=1792, hidden_dim=4096, output_dim=2048)
loss_fn = InfoNCELoss(temperature_init=0.07)
v = torch.randn(32, 1280) # batch of voice embeddings
t = torch.randn(32, 1792) # paired trajectory embeddings
z_v = voice_proj(v)
z_t = traj_proj(t)
loss = loss_fn(z_v, z_t)
import torch
from joint_embedding_voice2traj import RealNVP1D
flow = RealNVP1D(dim=2048, n_blocks=8, hidden_dims=[256, 256])
x = torch.randn(16, 2048)
z, log_det = flow.forward(x)
x_rec, _ = flow.inverse(z)
assert torch.allclose(x, x_rec, atol=1e-5)
from joint_embedding_voice2traj import ATCTranscriptor
transcriptor = ATCTranscriptor(use_lm=False, output_hidden_states=False)
result = transcriptor.forward("path/to/atc_audio.mp3", return_representation=True)
print(result["text"])
print(result["representation"].shape) # (1280,) with TIME_MEAN_POOL=True
Tests are organized under src/tests/ by subsystem.
Windows PowerShell:
.\dev\test.ps1 run-all
.\dev\test.ps1 run-real-nvp
.\dev\test.ps1 run-x-lstm
.\dev\test.ps1 run-lstm-normal
.\dev\test.ps1 run-ffnn-contrastive
.\dev\test.ps1 run-wav2vect
.\dev\test.ps1 clean-all
Unix / macOS:
make test-run-all
make test-run-real-nvp
make test-run-x-lstm
make test-run-lstm-normal
make test-run-ffnn-contrastive
make test-run-wav2vect
make test-clean-all
Direct pytest:
uv run pytest -v src/tests/
uv run pytest -v src/tests/test_real_nvp/
Notes:
test_wav2vect uses a real MP3 file and a pretrained model — it is heavier than the rest.htmlcov/.Generated LaTeX reports in pdf_latex_reports/:
| Report | Content |
|---|---|
| voice_latent_space/ | Whisper vs Wav2Vec2 embedding analysis, UMAP, per-tower statistics |
| inference/ | Voice feature extraction pipeline performance and embedding quality |
| contrastive/ | InfoNCE training curves, temperature evolution, retrieval metrics |
| bijective/ | RealNVP loss curves, cycle-consistency reconstruction error |
| next_step_prediction/ | StepPredictor accuracy, trajectory prediction visualizations |
| next_sentence_prediction/ | kNN lookup table retrieval performance |