LouisBrusset/CITRIS-joint-embedding-Voice2Traj

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

See the code

README

CITRIS-joint-embedding-Voice2Traj

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.

Table of Contents

  1. Pipeline Overview
  2. Installation
  3. Data Setup
  4. Repository Structure
  5. Training Stages
  6. Visualization & Notebooks
  7. Public API & Models
  8. Testing
  9. PDF Reports
  10. Sources and References

Pipeline Overview

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/.


Installation

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):

  • Set HF_TOKEN in the environment, or
  • Place a huggingface_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/.


Data Setup

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.

ConfigKey path variablesExpected content
config_voice.pyDATA_ROOT, OUTPUT_DIRRaw audio per tower, H5 output dir
config_contrastive.pyVOICE_EMB_DIR, TRAJ_EMB_DIRH5 embedding files
config_bijective.pyVOICE_EMB_DIR, TRAJ_EMB_DIRSame H5 files
config_nsp.pyinherits from aboveSame H5 files
config_next_step.pyDATA_PATHTrajectory 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"

Repository Structure

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

Training Stages

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).

1. Voice Feature Extraction

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.

ParameterDefaultDescription
VOICE_MODEL"whisper""whisper" or "wav2vec2"
SAMPLE_RATE16_000Audio sample rate (Hz)
MAX_DURATION30Max audio clip length (seconds)
BATCH_SIZE8Clips processed per batch
TIME_MEAN_POOLTrueAverage over time dimension → fixed-size vector
SAVE_INTERMEDIATETrueSave partial results during long runs

Output dimensions:

  • Whisper large-v3: 1280
  • Wav2Vec2 XLS-R: 1024

Output files: {tower}_embeddings_{model}_{pool_tag}.h5 in OUTPUT_DIR.

2. Trajectory Feature Extraction

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).

3. Contrastive Learning

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.

ParameterDefaultDescription
VOICE_MODEL"whisper"Source voice model
VOICE_DIM1280Input dim for voice projector
TRAJ_DIM1792Input dim for trajectory projector
JOINT_DIM2048Shared embedding space dimension
HIDDEN_DIM4096Hidden layer width in both FFNNs
DROPOUT0.2Dropout rate in projectors
TEMPERATURE_INIT0.07Initial InfoNCE temperature (learnable)
BATCH_SIZE512Training batch size
NUM_EPOCHS300Maximum training epochs
LR1e-4Adam learning rate
WEIGHT_DECAY1e-4L2 regularization
GRAD_CLIP1.0Gradient clipping norm
VAL_FRACTION0.1Fraction of data held out for validation
PATIENCE15Early stopping patience (epochs)
LR_PATIENCE7ReduceLROnPlateau patience
LR_FACTOR0.5LR reduction factor
LR_MIN1e-6Minimum learning rate
PLOT_EVERY10Epoch interval for saving plots
SEED42Random seed

Checkpoints → checkpoints/contrastive/. A sweep variant (sweep_training.py) runs a grid over projector architectures.

4. Bijective Mapping (RealNVP)

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:

ParameterDefaultDescription
N_UNIFORM_SYNTHETIC20_000Uniform samples in bounding box
N_NOISE_SYNTHETIC20_000Real samples + Gaussian noise
NOISE_STD_FACTOR0.5Noise std relative to embedding std
FFNN_BATCH_SIZE2048Batch size when projecting synthetic data through the contrastive FFNNs

RealNVP architecture:

ParameterDefaultDescription
N_BLOCKS8Number of affine coupling blocks
HIDDEN_DIMS[256, 256]MLP width inside each conditioner
USE_ACTNORMTrueActivation normalization between blocks
USE_TANHTrueTanh saturation on scale output
JOINT_DIM2048Input/output dimension of the flow
VOICE_PADDED_DIM768Voice space padded to this size
TRAJ_PADDED_DIM256Trajectory space padded to this size

Loss weights:

ParameterDefaultDescription
LAMBDA_FORWARD0.5Forward reconstruction weight (joint → modality → joint)
LAMBDA_BACKWARD0.5Backward reconstruction weight
LAMBDA_CROSS0.1Cross-modal alignment: voice joint ↔ traj joint

Training:

ParameterDefaultDescription
NUM_EPOCHS200Maximum epochs
BATCH_SIZE256Training batch size
LR1e-4Adam learning rate
WEIGHT_DECAY1e-4L2 regularization
GRAD_CLIP1.0Gradient clipping norm
PATIENCE20Early stopping patience
LR_PATIENCE7ReduceLROnPlateau patience
VIZ_EVERY10Plot interval (UMAP + scatter, 2000 samples)

Checkpoints → checkpoints/bijective/.

5. Next Step Prediction

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:

ParameterDefaultDescription
N_CONTEXT10Number of context timesteps fed to the encoder
N_FUTURE4Number of future steps to predict
SEQ_LEN14Total sequence length (context + future)
N_FEATURES6Features per step: lon, lat, alt, vel, track, vrate
D_MODEL128MAE encoder hidden dimension

StepPredictor MLP: (B, 4, 128) → flatten → (B, 512) → [512, 256] → (B, 4, 6)

Training:

ParameterDefaultDescription
NUM_EPOCHS150Maximum epochs
BATCH_SIZE256Training batch size
LR1e-3Adam learning rate
WEIGHT_DECAY1e-4L2 regularization
GRAD_CLIP1.0Gradient clipping norm
PATIENCE20Early stopping patience
LR_PATIENCE7ReduceLROnPlateau patience
PLOT_EVERY10Plot interval (6 trajectory examples)

Checkpoints → checkpoints/next_step/. The contrastive and bijective checkpoints are loaded for evaluation only (not trained here).

6. Next Sentence Prediction

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_PROJPipeline
Truevoice → FFNN projector → voice_realnvp.joint_to_emb() → kNN search
Falsevoice → traj_realnvp.emb_to_joint() → voice_realnvp.joint_to_emb() → kNN search

kNN parameters:

ParameterDefaultDescription
DISTANCE_METRIC"cosine""cosine" or "l2"
TOP_K1Number 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.

7. Ablation Study

Config: src/joint_embedding_voice2traj/training/ablation_study/config_ablation.py

Compares two end-to-end architectures:

Arch 1Arch 2
StructureRealNVP + contrastive lossSingle RealNVP + reconstruction
LossInfoNCE (temperature=0.07)Cycle-consistency reconstruction
Epochs300200
Batch size512256

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


Visualization & Notebooks

UMAP projections

Config: src/joint_embedding_voice2traj/features_projection/config_proj.py

ParameterDefaultDescription
VOICE_MODEL"whisper"Which embeddings to project
N_NEIGHBORS15UMAP neighbors parameter
MIN_DIST0.1UMAP minimum distance
METRIC"cosine"Distance metric

Notebooks

NotebookPurpose
visualization_proj_voice_features.ipynbUMAP of raw voice embeddings colored by tower
visualization_proj_traj_features.ipynbUMAP of raw trajectory embeddings
visualization_proj_joint_space.ipynbUMAP of the learned joint embedding space
from_traj_to_sentence.ipynbEnd-to-end demo: trajectory → retrieved voice segment

Public API & Models

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

Quick example: contrastive training step

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)

Quick example: bijective round-trip

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)

Quick example: ATC transcription

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

Testing

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.
  • Coverage HTML artifacts are written to htmlcov/.
  • See src/tests/TESTING.md for detailed guidance per suite.

PDF Reports

Generated LaTeX reports in pdf_latex_reports/:

ReportContent
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

Sources and References

Research context

xLSTM

RealNVP and normalizing flows

Contrastive learning

ATC speech model

Tooling

LouisBrusset/CITRIS-joint-embedding-Voice2Traj

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

See the code

README

CITRIS-joint-embedding-Voice2Traj

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.

Table of Contents

  1. Pipeline Overview
  2. Installation
  3. Data Setup
  4. Repository Structure
  5. Training Stages
  6. Visualization & Notebooks
  7. Public API & Models
  8. Testing
  9. PDF Reports
  10. Sources and References

Pipeline Overview

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/.


Installation

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):

  • Set HF_TOKEN in the environment, or
  • Place a huggingface_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/.


Data Setup

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.

ConfigKey path variablesExpected content
config_voice.pyDATA_ROOT, OUTPUT_DIRRaw audio per tower, H5 output dir
config_contrastive.pyVOICE_EMB_DIR, TRAJ_EMB_DIRH5 embedding files
config_bijective.pyVOICE_EMB_DIR, TRAJ_EMB_DIRSame H5 files
config_nsp.pyinherits from aboveSame H5 files
config_next_step.pyDATA_PATHTrajectory 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"

Repository Structure

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

Training Stages

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).

1. Voice Feature Extraction

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.

ParameterDefaultDescription
VOICE_MODEL"whisper""whisper" or "wav2vec2"
SAMPLE_RATE16_000Audio sample rate (Hz)
MAX_DURATION30Max audio clip length (seconds)
BATCH_SIZE8Clips processed per batch
TIME_MEAN_POOLTrueAverage over time dimension → fixed-size vector
SAVE_INTERMEDIATETrueSave partial results during long runs

Output dimensions:

  • Whisper large-v3: 1280
  • Wav2Vec2 XLS-R: 1024

Output files: {tower}_embeddings_{model}_{pool_tag}.h5 in OUTPUT_DIR.

2. Trajectory Feature Extraction

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).

3. Contrastive Learning

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.

ParameterDefaultDescription
VOICE_MODEL"whisper"Source voice model
VOICE_DIM1280Input dim for voice projector
TRAJ_DIM1792Input dim for trajectory projector
JOINT_DIM2048Shared embedding space dimension
HIDDEN_DIM4096Hidden layer width in both FFNNs
DROPOUT0.2Dropout rate in projectors
TEMPERATURE_INIT0.07Initial InfoNCE temperature (learnable)
BATCH_SIZE512Training batch size
NUM_EPOCHS300Maximum training epochs
LR1e-4Adam learning rate
WEIGHT_DECAY1e-4L2 regularization
GRAD_CLIP1.0Gradient clipping norm
VAL_FRACTION0.1Fraction of data held out for validation
PATIENCE15Early stopping patience (epochs)
LR_PATIENCE7ReduceLROnPlateau patience
LR_FACTOR0.5LR reduction factor
LR_MIN1e-6Minimum learning rate
PLOT_EVERY10Epoch interval for saving plots
SEED42Random seed

Checkpoints → checkpoints/contrastive/. A sweep variant (sweep_training.py) runs a grid over projector architectures.

4. Bijective Mapping (RealNVP)

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:

ParameterDefaultDescription
N_UNIFORM_SYNTHETIC20_000Uniform samples in bounding box
N_NOISE_SYNTHETIC20_000Real samples + Gaussian noise
NOISE_STD_FACTOR0.5Noise std relative to embedding std
FFNN_BATCH_SIZE2048Batch size when projecting synthetic data through the contrastive FFNNs

RealNVP architecture:

ParameterDefaultDescription
N_BLOCKS8Number of affine coupling blocks
HIDDEN_DIMS[256, 256]MLP width inside each conditioner
USE_ACTNORMTrueActivation normalization between blocks
USE_TANHTrueTanh saturation on scale output
JOINT_DIM2048Input/output dimension of the flow
VOICE_PADDED_DIM768Voice space padded to this size
TRAJ_PADDED_DIM256Trajectory space padded to this size

Loss weights:

ParameterDefaultDescription
LAMBDA_FORWARD0.5Forward reconstruction weight (joint → modality → joint)
LAMBDA_BACKWARD0.5Backward reconstruction weight
LAMBDA_CROSS0.1Cross-modal alignment: voice joint ↔ traj joint

Training:

ParameterDefaultDescription
NUM_EPOCHS200Maximum epochs
BATCH_SIZE256Training batch size
LR1e-4Adam learning rate
WEIGHT_DECAY1e-4L2 regularization
GRAD_CLIP1.0Gradient clipping norm
PATIENCE20Early stopping patience
LR_PATIENCE7ReduceLROnPlateau patience
VIZ_EVERY10Plot interval (UMAP + scatter, 2000 samples)

Checkpoints → checkpoints/bijective/.

5. Next Step Prediction

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:

ParameterDefaultDescription
N_CONTEXT10Number of context timesteps fed to the encoder
N_FUTURE4Number of future steps to predict
SEQ_LEN14Total sequence length (context + future)
N_FEATURES6Features per step: lon, lat, alt, vel, track, vrate
D_MODEL128MAE encoder hidden dimension

StepPredictor MLP: (B, 4, 128) → flatten → (B, 512) → [512, 256] → (B, 4, 6)

Training:

ParameterDefaultDescription
NUM_EPOCHS150Maximum epochs
BATCH_SIZE256Training batch size
LR1e-3Adam learning rate
WEIGHT_DECAY1e-4L2 regularization
GRAD_CLIP1.0Gradient clipping norm
PATIENCE20Early stopping patience
LR_PATIENCE7ReduceLROnPlateau patience
PLOT_EVERY10Plot interval (6 trajectory examples)

Checkpoints → checkpoints/next_step/. The contrastive and bijective checkpoints are loaded for evaluation only (not trained here).

6. Next Sentence Prediction

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_PROJPipeline
Truevoice → FFNN projector → voice_realnvp.joint_to_emb() → kNN search
Falsevoice → traj_realnvp.emb_to_joint() → voice_realnvp.joint_to_emb() → kNN search

kNN parameters:

ParameterDefaultDescription
DISTANCE_METRIC"cosine""cosine" or "l2"
TOP_K1Number 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.

7. Ablation Study

Config: src/joint_embedding_voice2traj/training/ablation_study/config_ablation.py

Compares two end-to-end architectures:

Arch 1Arch 2
StructureRealNVP + contrastive lossSingle RealNVP + reconstruction
LossInfoNCE (temperature=0.07)Cycle-consistency reconstruction
Epochs300200
Batch size512256

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


Visualization & Notebooks

UMAP projections

Config: src/joint_embedding_voice2traj/features_projection/config_proj.py

ParameterDefaultDescription
VOICE_MODEL"whisper"Which embeddings to project
N_NEIGHBORS15UMAP neighbors parameter
MIN_DIST0.1UMAP minimum distance
METRIC"cosine"Distance metric

Notebooks

NotebookPurpose
visualization_proj_voice_features.ipynbUMAP of raw voice embeddings colored by tower
visualization_proj_traj_features.ipynbUMAP of raw trajectory embeddings
visualization_proj_joint_space.ipynbUMAP of the learned joint embedding space
from_traj_to_sentence.ipynbEnd-to-end demo: trajectory → retrieved voice segment

Public API & Models

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

Quick example: contrastive training step

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)

Quick example: bijective round-trip

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)

Quick example: ATC transcription

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

Testing

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.
  • Coverage HTML artifacts are written to htmlcov/.
  • See src/tests/TESTING.md for detailed guidance per suite.

PDF Reports

Generated LaTeX reports in pdf_latex_reports/:

ReportContent
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

Sources and References

Research context

xLSTM

RealNVP and normalizing flows

Contrastive learning

ATC speech model

Tooling