""" ML-Based Voice Detector using Pre-trained Models Uses Wav2Vec2 embeddings + classifier for high-accuracy deepfake detection """ import os import warnings import numpy as np from typing import Dict, Any, Tuple, List, Optional warnings.filterwarnings("ignore") class Wav2Vec2Classifier: """Simple classifier on top of Wav2Vec2 embeddings.""" def __init__(self, hidden_size: int = 768, num_classes: int = 2): import torch # Store the module as instance variable to avoid re-importing self.torch = torch self.classifier = torch.nn.Sequential( torch.nn.Linear(hidden_size, 256), torch.nn.ReLU(), torch.nn.Dropout(0.3), torch.nn.Linear(256, 64), torch.nn.ReLU(), torch.nn.Dropout(0.2), torch.nn.Linear(64, num_classes) ) def forward(self, x): return self.classifier(x) def to(self, device): self.classifier = self.classifier.to(device) return self def eval(self): self.classifier.eval() return self def modules(self): return self.classifier.modules() class MLVoiceDetector: """ ML-based voice detector using Wav2Vec2 embeddings. Combines pre-trained features with a trained classifier. """ def __init__(self, device: str = None): """ Initialize the ML voice detector. Args: device: Device to use ('cuda' or 'cpu'). Auto-detected if None. """ if device: self.device = device else: try: import torch self.device = "cuda" if torch.cuda.is_available() else "cpu" except ImportError: self.device = "cpu" self.processor = None self.wav2vec_model = None self.classifier = None self.is_loaded = False self.trained_model = None self.feature_mean = None self.feature_std = None def load_model(self): """Load the Wav2Vec2 model and classifier.""" if self.is_loaded: return from transformers import Wav2Vec2Processor, Wav2Vec2Model print("Loading Wav2Vec2 model...", flush=True) try: self.processor = Wav2Vec2Processor.from_pretrained( "facebook/wav2vec2-base", cache_dir="/tmp/hf_cache" ) self.wav2vec_model = Wav2Vec2Model.from_pretrained( "facebook/wav2vec2-base", cache_dir="/tmp/hf_cache" ) self.wav2vec_model.to(self.device) self.wav2vec_model.eval() self.classifier = Wav2Vec2Classifier() self._initialize_classifier_weights() self.classifier.to(self.device) self.classifier.eval() model_path = os.path.join(os.path.dirname(__file__), "trained_model.joblib") if os.path.exists(model_path): self.load_trained_model(model_path) self.is_loaded = True print(f"✓ Model loaded on {self.device}", flush=True) except Exception as e: print(f"❌ Model load failed: {e}", flush=True) self.is_loaded = False def _initialize_classifier_weights(self): import torch for module in self.classifier.modules(): if isinstance(module, torch.nn.Linear): torch.nn.init.xavier_uniform_(module.weight, gain=0.1) if module.bias is not None: torch.nn.init.zeros_(module.bias) def extract_wav2vec_features( self, audio: np.ndarray, sr: int = 16000 ) -> Optional[np.ndarray]: import torch if not self.is_loaded: self.load_model() if not self.is_loaded: return None try: inputs = self.processor( audio, sampling_rate=sr, return_tensors="pt", padding=True ) input_values = inputs.input_values.to(self.device) with torch.no_grad(): outputs = self.wav2vec_model(input_values) embeddings = outputs.last_hidden_state.mean(dim=1) return embeddings.cpu().numpy()[0] except Exception as e: print(f"Feature extraction failed: {e}", flush=True) return None def compute_embedding_statistics(self, embeddings: np.ndarray) -> Dict[str, float]: stats = { "embedding_mean": float(np.mean(embeddings)), "embedding_std": float(np.std(embeddings)), "embedding_max": float(np.max(embeddings)), "embedding_min": float(np.min(embeddings)), "embedding_range": float(np.ptp(embeddings)), "embedding_entropy": self._entropy(embeddings), } return stats def _entropy(self, x: np.ndarray, bins: int = 50) -> float: hist, _ = np.histogram(x, bins=bins, density=True) hist = hist[hist > 0] if len(hist) == 0: return 0.0 hist /= hist.sum() return float(-np.sum(hist * np.log2(hist + 1e-9))) def load_trained_model(self, path: str): try: import joblib data = joblib.load(path) self.trained_model = data["model"] print("✓ Trained model loaded", flush=True) except Exception as e: print(f"Trained model load failed: {e}", flush=True) def detect(self, audio: np.ndarray, sr: int = 16000) -> Dict[str, Any]: embeddings = self.extract_wav2vec_features(audio, sr) if embeddings is None: return { "classification": "UNKNOWN", "confidenceScore": 0.5, "explanation": "Feature extraction failed", "method": "fallback" } stats = self.compute_embedding_statistics(embeddings) ai_score = 0.5 if stats["embedding_std"] < 0.35: ai_score += 0.2 if stats["embedding_entropy"] < 3.2: ai_score += 0.2 ai_score = max(0.0, min(1.0, ai_score)) if ai_score > 0.5: return { "classification": "AI_GENERATED", "confidenceScore": round(ai_score, 2), "explanation": "Synthetic voice patterns detected", "method": "wav2vec2" } return { "classification": "HUMAN", "confidenceScore": round(1 - ai_score, 2), "explanation": "Natural human voice patterns detected", "method": "wav2vec2" } # 🔁 Lazy singleton (HF-safe) _ml_detector = None def get_ml_detector() -> MLVoiceDetector: global _ml_detector if _ml_detector is None: _ml_detector = MLVoiceDetector() return _ml_detector