Spaces:
Runtime error
Runtime error
| # src/models/childfluency.py | |
| """ | |
| ChildFluency-Net β Full Model | |
| Combines WavLM acoustic encoder + LPE linguistic encoder | |
| into a unified architecture with dual SLD/OD classification heads. | |
| Architecture: | |
| [WavLM β 256-dim] + [LPE β 32-dim] | |
| β concat [288-dim] | |
| Shared MLP [64-dim] | |
| β | |
| [SLD Head] [OD Head] | |
| β | |
| Mean pool across windows β SLD rate % (clinical output) | |
| Clinical output: | |
| SLD rate > 3% β refer for specialist assessment | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| from typing import Dict | |
| from wavlm_encoder import WavLMEncoder | |
| from lpe_module import LPEModule | |
| class ChildFluencyNet(nn.Module): | |
| def __init__( | |
| self, | |
| wavlm_name : str = "microsoft/wavlm-large", | |
| acoustic_dim : int = 256, | |
| lpe_dim : int = 32, | |
| hidden_dim : int = 128, | |
| dropout : float = 0.3 | |
| ): | |
| super().__init__() | |
| # Branch A β acoustic | |
| self.acoustic_encoder = WavLMEncoder( | |
| model_name = wavlm_name, | |
| output_dim = acoustic_dim | |
| ) | |
| # Branch B β linguistic position | |
| self.lpe_module = LPEModule( | |
| input_dim = 6, | |
| output_dim = lpe_dim | |
| ) | |
| fusion_dim = acoustic_dim + lpe_dim # 256 + 32 = 288 | |
| # Shared MLP after fusion | |
| self.shared_mlp = nn.Sequential( | |
| nn.Linear(fusion_dim, hidden_dim), | |
| nn.GELU(), | |
| nn.Dropout(dropout), | |
| nn.Linear(hidden_dim, hidden_dim // 2) # β 64-dim | |
| ) | |
| # Head 1 β SLD (Stuttering-Like Disfluencies) | |
| # Detects: blocks, prolongations, sound/syllable repetitions | |
| # These are PATHOLOGICAL β what clinicians diagnose | |
| self.sld_head = nn.Linear(hidden_dim // 2, 1) | |
| # Head 2 β OD (Other Disfluencies) | |
| # Detects: word repetitions, interjections (um, uh) | |
| # These are NORMAL β present in all speakers | |
| self.od_head = nn.Linear(hidden_dim // 2, 1) | |
| def forward( | |
| self, | |
| audio : torch.Tensor, # [batch, 64000] | |
| lpe_features : torch.Tensor # [batch, 6] | |
| ) -> Dict[str, torch.Tensor]: | |
| """ | |
| Args: | |
| audio : raw waveform, 4 seconds at 16kHz | |
| lpe_features : 6 linguistic position features per window | |
| Returns dict with: | |
| sld_logit : [batch] raw score for SLD detection | |
| od_logit : [batch] raw score for OD detection | |
| embedding : [batch, 64] shared representation | |
| """ | |
| # Branch A | |
| acoustic_emb = self.acoustic_encoder(audio) # [batch, 256] | |
| # Branch B | |
| lpe_emb = self.lpe_module(lpe_features) # [batch, 32] | |
| # Fuse | |
| fused = torch.cat([acoustic_emb, lpe_emb], dim=-1) # [batch, 288] | |
| shared = self.shared_mlp(fused) # [batch, 64] | |
| return { | |
| "sld_logit" : self.sld_head(shared).squeeze(-1), # [batch] | |
| "od_logit" : self.od_head(shared).squeeze(-1), # [batch] | |
| "embedding" : shared # [batch, 64] | |
| } | |
| def predict_recording(self, window_outputs: list) -> Dict: | |
| """ | |
| Aggregates window-level predictions into recording-level diagnosis. | |
| Called during inference on a full recording. | |
| Args: | |
| window_outputs : list of dicts from forward(), one per window | |
| Returns: | |
| sld_rate : float SLD rate % (clinical diagnostic metric) | |
| has_stutter : bool True if SLD rate > 3% | |
| sld_timeline: list per-window SLD probability (for visualization) | |
| """ | |
| sld_probs = torch.sigmoid( | |
| torch.stack([o["sld_logit"] for o in window_outputs]) | |
| ) | |
| od_probs = torch.sigmoid( | |
| torch.stack([o["od_logit"] for o in window_outputs]) | |
| ) | |
| sld_rate = (sld_probs > 0.5).float().mean().item() * 100.0 | |
| return { | |
| "sld_rate" : round(sld_rate, 2), | |
| "has_stutter" : sld_rate > 3.0, | |
| "mean_sld_prob": round(sld_probs.mean().item(), 4), | |
| "mean_od_prob" : round(od_probs.mean().item(), 4), | |
| "sld_timeline" : sld_probs.cpu().tolist() | |
| } | |
| def count_params(self): | |
| trainable = sum(p.numel() for p in self.parameters() if p.requires_grad) | |
| total = sum(p.numel() for p in self.parameters()) | |
| print(f"ChildFluencyNet") | |
| print(f" Trainable : {trainable:,}") | |
| print(f" Total : {total:,}") | |
| print(f" Frozen : {total - trainable:,}") |