Hi,
I made 3 trainning for world "Reflets" in French.
- for the v1 and v2 I use only a female voice and get 0.2 with my voice 0.9 with sample
- for the v3 I mix 3 male and female voice and got 0 confidence
Here is my Notebook :
Nanowakeword_Reflets_v3.ipynb
And the model seems ok ?
The logs
_ _ __ __ _ __ __ _
| \ | | \ \ / / | | \ \ / / | |
| \| | __ _ _ __ __\ \ /\ / /_ _| | ____\ \ /\ / /__ _ __ __| |
| . ` |/ _` | '_ \ / _ \ \/ \/ / _` | |/ / _ \ \/ \/ / _ \| '__/ _` |
| |\ | (_| | | | | (_) \ /\ / (_| | < __/\ /\ / (_) | | | (_| |
|_| \_|\__,_|_| |_|\___/ \/ \/ \__,_|_|\_\___| \/ \/ \___/|_| \__,_|
STEP 1: Verifying and Preprocessing Data Directories
====================================================
Processing Noise: 100%|██████████| 1365/1365 [00:26<00:00, 51.44it/s]
Processing Rir: 100%|██████████| 1667/1667 [00:14<00:00, 112.07it/s]
WARNING:root:Directory not found, skipping preprocessing: nanowakeword_data/negative_speech
WARNING:root:Directory not found, skipping preprocessing: nanowakeword_data/positive_wakeword
INFO: Data verification and preprocessing complete.
INFO: Determining hardware-specific configurations...
STEP 2: Activating Synthetic Data Generation Engine
===================================================
INFO: Processing 10 custom negative phrases.
INFO: Generating 25 copies for EACH custom phrase.
INFO: Total custom samples prepared: 250
INFO: Target negative samples: 6000. Current custom samples: 250.
INFO: Generating 5750 auto-adversarial phrases to fill the gap.
WARNING:root:Downloading phonemizer model from https://github.com/arcosoph/phonemize/releases/download/v0.2.0/phonemize_m1.pt...
WARNING:root:The word 'reflets' was not found in the pronunciation dictionary! Using the Phonemize library to predict the phonemes.
WARNING:root:Phones for 'reflets': [R][IY][F][L][AH][T][S]
INFO: Initiating data generation pipeline for phrase: 'reflets'
INFO: Executing task 'Positive_Train': 3000 clips -> 'nanowakeword_data/positive_wakeword'
Loading voices: 100%|██████████| 3/3 [00:04<00:00, 1.54s/it]
Generating Audio: 100%|██████████| 3000/3000 [09:13<00:00, 5.42it/s]
INFO: Executing task 'Adversarial_Train': 6000 clips -> 'nanowakeword_data/negative_speech'
Generating Audio: 100%|██████████| 6000/6000 [23:06<00:00, 4.33it/s]
INFO: Synthetic data generation process finished successfully.
STEP 2.5: Activating Intelligent Configuration Engine
===================================================
Analyzing dataset characteristics...
Analyzing positive_wakeword files: 100%|██████████| 3000/3000 [00:13<00:00, 230.13it/s]
Analyzing negative_speech files: 100%|██████████| 6000/6000 [00:32<00:00, 182.49it/s]
Analyzing Noise files: 100%|██████████| 1365/1365 [00:23<00:00, 58.82it/s]Analysis complete!
Dataset Statistics
┌───────────────┬────────────────────────────────────────────────┐
│ Parameter │ Value │
├───────────────┼────────────────────────────────────────────────┤
│ H_pos │ 0.6620500868055553 │
│ H_neg │ 1.6486525520833364 │
│ H_noise_paths │ {'nanowakeword_data/Noise': 5.067988993055563} │
│ H_noise │ 5.067988993055563 │
│ A_noise │ 0.05487615084446175 │
│ N_rir │ 1667 │
└───────────────┴────────────────────────────────────────────────┘
INFO: Project assets will be saved in: /content/trained_models/reflets_v3
INFO: Autotuning optimal clip duration...
INFO: Optimal clip duration autotuned to: 32000 samples (2.00 seconds).
STEP 3: Computing Acoustic Features from Audio Sources
======================================================
[nanowakeword] Required model 'melspectrogram.onnx' not found. Downloading...
melspectrogram.onnx: 100%|██████████| 1.09M/1.09M [00:00<00:00, 34.4MiB/s]
[nanowakeword] Download of 'melspectrogram.onnx' complete.
[nanowakeword] Required model 'embedding_model.onnx' not found. Downloading...
embedding_model.onnx: 100%|██████████| 1.33M/1.33M [00:00<00:00, 36.9MiB/s]
[nanowakeword] Download of 'embedding_model.onnx' complete.
Computing features: 100%|██████████| 234/234 [58:27<00:00, 14.99s/it]
Computing features: 100%|██████████| 468/468 [1:57:38<00:00, 15.08s/it]
Computing features: 100%|██████████| 53/53 [14:26<00:00, 16.35s/it]
INFO: Initializing Model and Training Components...
INFO: Using optimizer: ADAMW
INFO: Setting up learning rate scheduler: ONECYCLE
STEP 4: Starting Training Process
=================================
INFO: Using model architecture: 🤍 RNN
INFO: Debug mode ON. Logs will be saved to:
/content/trained_models/reflets_v3/2_training_artifacts/training_debug/training_debug.log
INFO: Checkpointing is ENABLED. A checkpoint will be saved every 1000 steps.
INFO: Training for 30000 steps. Model checkpointing and early stopping will activate after 4500 warm-up steps.
Effective Training Configuration
┌───────────────────────────────────────────────────────────────┬────────────────────┐
│ Parameter │ Value │
├───────────────────────────────────────────────────────────────┼────────────────────┤
│ WARMUP_STEPS │ 4500 │
│ activation_function │ relu │
│ audio_processing.autotune_length.duration_buffer_ms │ 750 │
│ audio_processing.autotune_length.enabled │ True │
│ audio_processing.autotune_length.min_allowable_length │ 32000 │
│ audio_processing.autotune_length.num_samples_to_inspect │ 50 │
│ audio_processing.autotune_length.snap_to_min_tolerance │ 4000 │
│ augmentation_batch_size │ 128 │
│ augmentation_rounds │ 10 │
│ augmentation_settings.BackgroundNoise │ 0.8 │
│ augmentation_settings.BandStopFilter │ 0.2 │
│ augmentation_settings.ColoredNoise │ 0.4 │
│ augmentation_settings.Distortion │ 0.25 │
│ augmentation_settings.Gain │ 1.0 │
│ augmentation_settings.ParametricEQ │ 0.25 │
│ augmentation_settings.PitchShift │ 0.5 │
│ augmentation_settings.RIR │ 0.6 │
│ background_paths_duplication_rate │ [1] │
│ batch_composition.batch_size │ 128 │
│ batch_composition.source_distribution.negative_speech │ 40 │
│ batch_composition.source_distribution.positive │ 30 │
│ batch_composition.source_distribution.pure_noise │ 30 │
│ checkpoint_averaging_top_k │ 5 │
│ checkpointing.enabled │ True │
│ checkpointing.interval_steps │ 1000 │
│ checkpointing.limit │ 5 │
│ debug_mode │ True │
│ dropout_prob │ 0.5 │
│ ema_alpha │ 0.01 │
│ embedding_dim │ 96 │
│ feature_gen_cpu_ratio │ 0.8 │
│ layer_size │ 384 │
│ learning_rate_max │ 0.001 │
│ lr_scheduler_type │ onecycle │
│ main_delta │ 0.0001 │
│ max_snr_in_db │ 13.477561925166414 │
│ min_snr_in_db │ -7.217950459866868 │
│ model_name │ reflets_v3 │
│ model_type │ rnn │
│ momentum │ 0.9 │
│ n_blocks │ 6 │
│ optimizer_type │ adamw │
│ overwrite │ True │
│ show_training_summary │ True │
│ steps │ 30000 │
│ total_length │ 32000 │
│ train_model │ True │
│ training_refinement.hard_negative_mining.bank_size │ 500 │
│ training_refinement.hard_negative_mining.enable │ True │
│ training_refinement.hard_negative_mining.focus_on_confusing │ True │
│ training_refinement.hard_negative_mining.min_bank_size │ 50 │
│ training_refinement.hard_negative_mining.mining_threshold │ 0.3 │
│ training_refinement.hard_negative_mining.replay_batch_size │ 24 │
│ training_refinement.loss_settings.false_accept_penalty │ 20.0 │
│ training_refinement.loss_settings.focal_gamma │ 2.5 │
│ training_refinement.loss_settings.miss_penalty │ 5.0 │
│ training_refinement.loss_settings.temperature │ 2.0 │
│ training_refinement.loss_weights.bce │ 0.3 │
│ training_refinement.loss_weights.confidence_reg │ 10.0 │
│ training_refinement.loss_weights.focal │ 0.2 │
│ training_refinement.loss_weights.margin │ 100.0 │
│ training_refinement.loss_weights.negative_push │ 30.0 │
│ training_refinement.loss_weights.replay │ 20.0 │
│ training_refinement.loss_weights.triplet │ 0.5 │
│ training_refinement.margin_settings.confidence_penalty_weight │ 50.0 │
│ training_refinement.margin_settings.exponential_scale │ 4.0 │
│ training_refinement.margin_settings.safety_threshold │ 0.05 │
│ training_refinement.optimization.adaptive_clipping │ True │
│ training_refinement.optimization.aggressive_clip_factor │ 0.5 │
│ training_refinement.optimization.max_grad_norm │ 1.5 │
│ training_refinement.regularization.confidence_regularization │ 0.1 │
│ training_refinement.regularization.enable_spectral │ True │
│ training_refinement.regularization.spectral_weight │ 0.01 │
│ training_refinement.triplet_settings.base_margin │ 0.5 │
│ training_refinement.triplet_settings.curriculum_steps │ 15000 │
│ training_refinement.triplet_settings.hard_mining_ratio │ 0.6 │
│ training_refinement.triplet_settings.negative_push_margin │ 0.8 │
│ transform_clips │ True │
│ triplet_loss_margin │ 0.2 │
│ weight_decay │ 0.01 │
└───────────────────────────────────────────────────────────────┴────────────────────┘
Training: 35%|███▍ | 10385/30000 [56:54<1:47:30, 3.04it/s]
INFO: Early stopping triggered at step 10385. No improvement in stable loss for 4500 steps.
INFO: Training finished. Merging best checkpoints to create final model...
INFO: Averaging the top 5 most stable models found during training...
INFO: Calculating performance metrics for the final averaged model...
========================================
TRAINING COMPLETE - FINAL REPORT
========================================
INFO: NOTE: These metrics are indicators of model health, not real-world performance.
Average Stable Loss : 31.2011
Weight Diversity (Std Dev): 0.7673
Avg. Positive Confidence (Logit): -6.783
Avg. Negative Confidence (Logit): -22.139
Generating training performance graph...
Performance graph saved to: /content/trained_models/reflets_v3/2_training_artifacts/graphs/training_performance_graph.png
INFO: Reconstructing model into a sequential format for export.
INFO: Saving inference-ready ONNX model to '/content/trained_models/reflets_v3/3_model/reflets_v3.onnx'
INFO: Using ONNX opset version: 17
INFO: ONNX model saved successfully.
CONGRATULATIONS! (✿◕‿◕✿)
Your custom wake word model has been successfully trained!
The JS Code seems to works even if the confidence has variation from 0.2 to 0.35 for model v2 but it feels very very low.
try {
// Process audio in chunks and return highest score
let maxScore = 0;
const numFrames = Math.floor(audioFloat32.length / FRAME_SIZE);
// Initialize mel buffer for this check
let melBuffer = [];
// Use persistent embedding buffer (initialize only if empty)
if (persistentEmbeddingBuffer.length === 0) {
for (let i = 0; i < EMBEDDING_HISTORY_SIZE; i++) {
persistentEmbeddingBuffer.push(new Float32Array(96).fill(0));
}
}
for (let frameIdx = 0; frameIdx < numFrames; frameIdx++) {
const chunk = audioFloat32.subarray(frameIdx * FRAME_SIZE, (frameIdx + 1) * FRAME_SIZE);
// Stage 1: Audio → Melspectrogram
const melTensor = new ort.Tensor('float32', chunk, [1, FRAME_SIZE]);
const melResults = await melModel.run({ [melModel.inputNames[0]]: melTensor });
let melData = melResults[melModel.outputNames[0]].data;
// Apply transformation (critical for correct detection)
for (let j = 0; j < melData.length; j++) {
melData[j] = (melData[j] / 10.0) + 2.0;
}
// Add 5 mel frames (each chunk produces 5 frames)
for (let j = 0; j < 5; j++) {
melBuffer.push(new Float32Array(melData.subarray(j * 32, (j + 1) * 32)));
}
// Stage 2: Melspectrogram History → Embedding
while (melBuffer.length >= MEL_WINDOW_SIZE) {
const windowFrames = melBuffer.slice(0, MEL_WINDOW_SIZE);
const flattenedMel = new Float32Array(MEL_WINDOW_SIZE * 32);
for (let k = 0; k < MEL_WINDOW_SIZE; k++) {
flattenedMel.set(windowFrames[k], k * 32);
}
const embeddingTensor = new ort.Tensor('float32', flattenedMel, [1, MEL_WINDOW_SIZE, 32, 1]);
const embeddingResults = await embeddingModel.run({ [embeddingModel.inputNames[0]]: embeddingTensor });
const embedding = embeddingResults[embeddingModel.outputNames[0]].data;
// Stage 3: Embedding History → Wake Word Score
// Update persistent buffer (slide window)
persistentEmbeddingBuffer.shift();
persistentEmbeddingBuffer.push(new Float32Array(embedding));
const flattenedEmbeddings = new Float32Array(EMBEDDING_HISTORY_SIZE * 96);
for (let k = 0; k < EMBEDDING_HISTORY_SIZE; k++) {
flattenedEmbeddings.set(persistentEmbeddingBuffer[k], k * 96);
}
const wakeWordTensor = new ort.Tensor('float32', flattenedEmbeddings, [1, EMBEDDING_HISTORY_SIZE, 96]);
const wakeWordResults = await wakeWordModel.run({ [wakeWordModel.inputNames[0]]: wakeWordTensor });
const score = wakeWordResults[wakeWordModel.outputNames[0]].data[0];
if (score > maxScore) {
maxScore = score;
}
// Slide window by 8 frames
melBuffer.splice(0, 8);
}
}
const detected = maxScore > $.CLIENT.CONFIG.WAKEWORD_THRESHOLD;
console.log(`Wake word check: score=${maxScore.toFixed(4)}, detected=${detected}`);
// DEBUG: Log model info on first real check
if (maxScore > 0.01 && !window._wakeword_debug_logged) {
console.log('🔍 WAKEWORD DEBUG INFO:');
console.log(' Model inputs:', wakeWordModel.inputNames);
console.log(' Model outputs:', wakeWordModel.outputNames);
console.log(' Input shape expected: [1, 16, 96]');
console.log(' Frames processed:', numFrames);
console.log(' Max score location: frame', maxScore);
window._wakeword_debug_logged = true;
}
return { detected, score: maxScore, reason: 'checked' };
} catch (err) {
console.error('Wake word check failed:', err);
return { detected: false, score: 0, reason: 'error' };
}
};
Hi,
I made 3 trainning for world "Reflets" in French.
Here is my Notebook :
Nanowakeword_Reflets_v3.ipynb
And the model seems ok ?
The logs
The JS Code seems to works even if the confidence has variation from 0.2 to 0.35 for model v2 but it feels very very low.