Spaces:
Sleeping
Sleeping
| import time | |
| import torch | |
| import numpy as np | |
| from dataclasses import dataclass | |
| class DetectionResult: | |
| human_probability: float | |
| ai_probability: float | |
| prediction: str | |
| confidence: float | |
| inference_time_ms: float | |
| class EnsembleDetector: | |
| """ | |
| Runs inference on multiple deepfake detection models and aggregates the results. | |
| Specifically uses a 'Max-Ensemble' strategy: The final AI probability is the | |
| maximum AI probability across all models. This ensures robustness against both | |
| standard synthetic voices and SOTA deepfakes (like ElevenLabs). | |
| """ | |
| def __init__(self, gary_model, gary_fe, bisher_model, bisher_fe, sample_rate: int = 16000): | |
| self.gary_model = gary_model | |
| self.gary_fe = gary_fe | |
| self.bisher_model = bisher_model | |
| self.bisher_fe = bisher_fe | |
| self.target_sample_rate = sample_rate | |
| def _get_probs(self, model, fe, waveform) -> tuple[float, float]: | |
| """Returns (human_prob, ai_prob) for a single model.""" | |
| inputs = fe( | |
| waveform, | |
| sampling_rate=self.target_sample_rate, | |
| return_tensors="pt", | |
| padding=True | |
| ) | |
| with torch.no_grad(): | |
| logits = model(**inputs).logits | |
| probabilities = torch.nn.functional.softmax(logits, dim=-1) | |
| id2label = model.config.id2label | |
| fake_idx = 1 | |
| for idx, label in id2label.items(): | |
| l_lower = label.lower() | |
| if "fake" in l_lower or "spoof" in l_lower or "ai" in l_lower: | |
| fake_idx = idx | |
| break | |
| human_idx = 1 if fake_idx == 0 else 0 | |
| probs = probabilities[0].tolist() | |
| return probs[human_idx], probs[fake_idx] | |
| def analyze(self, waveform: np.ndarray, sample_rate: int = 16000) -> DetectionResult: | |
| if sample_rate != self.target_sample_rate: | |
| raise ValueError(f"Detector requires sample rate of {self.target_sample_rate}Hz. Got {sample_rate}Hz.") | |
| start_time = time.time() | |
| # Cap waveform to 10 seconds to prevent OOM and quadratic attention lag | |
| max_samples = 10 * self.target_sample_rate | |
| if len(waveform) > max_samples: | |
| waveform = waveform[:max_samples] | |
| try: | |
| # 1. Run Garystafford model | |
| g_hp, g_ap = self._get_probs(self.gary_model, self.gary_fe, waveform) | |
| # 2. Run Bisher model | |
| b_hp, b_ap = self._get_probs(self.bisher_model, self.bisher_fe, waveform) | |
| # 3. Aggregate (Max-Ensemble for AI probability) | |
| final_ai_prob = max(g_ap, b_ap) | |
| final_human_prob = 1.0 - final_ai_prob | |
| is_human = final_human_prob > final_ai_prob | |
| prediction = "LIKELY HUMAN" if is_human else "LIKELY AI GENERATED" | |
| confidence = final_human_prob if is_human else final_ai_prob | |
| inference_time_ms = (time.time() - start_time) * 1000 | |
| return DetectionResult( | |
| human_probability=final_human_prob, | |
| ai_probability=final_ai_prob, | |
| prediction=prediction, | |
| confidence=confidence, | |
| inference_time_ms=inference_time_ms | |
| ) | |
| except Exception as e: | |
| raise RuntimeError(f"Deepfake ensemble detection failed during inference: {str(e)}") | |
| def _get_batch_probs(self, model, fe, chunks, batch_size=10) -> list[tuple[float, float]]: | |
| """Returns list of (human_prob, ai_prob) for a batch of chunks.""" | |
| all_probs = [] | |
| # Determine the fake index dynamically once | |
| id2label = model.config.id2label | |
| fake_idx = 1 | |
| for idx, label in id2label.items(): | |
| l_lower = label.lower() | |
| if "fake" in l_lower or "spoof" in l_lower or "ai" in l_lower: | |
| fake_idx = idx | |
| break | |
| human_idx = 1 if fake_idx == 0 else 0 | |
| # Process in smaller batches to prevent OOM | |
| for i in range(0, len(chunks), batch_size): | |
| batch = chunks[i:i+batch_size] | |
| inputs = fe( | |
| batch, | |
| sampling_rate=self.target_sample_rate, | |
| return_tensors="pt", | |
| padding=True | |
| ) | |
| with torch.no_grad(): | |
| logits = model(**inputs).logits | |
| probabilities = torch.nn.functional.softmax(logits, dim=-1) | |
| probs_list = probabilities.tolist() | |
| all_probs.extend([(p[human_idx], p[fake_idx]) for p in probs_list]) | |
| return all_probs | |
| def analyze_timeline(self, waveform: np.ndarray, sample_rate: int = 16000) -> list: | |
| if sample_rate != self.target_sample_rate: | |
| raise ValueError(f"Detector requires sample rate of {self.target_sample_rate}Hz. Got {sample_rate}Hz.") | |
| window_size = sample_rate # 1 second windows | |
| segments = [] | |
| num_windows = len(waveform) // window_size | |
| chunks = [] | |
| for i in range(num_windows): | |
| start_sample = i * window_size | |
| end_sample = start_sample + window_size | |
| chunks.append(waveform[start_sample:end_sample]) | |
| has_remainder = False | |
| if len(waveform) % window_size > int(window_size * 0.1): | |
| start_sample = num_windows * window_size | |
| chunks.append(waveform[start_sample:]) | |
| has_remainder = True | |
| if not chunks: | |
| return [] | |
| try: | |
| g_probs = self._get_batch_probs(self.gary_model, self.gary_fe, chunks) | |
| b_probs = self._get_batch_probs(self.bisher_model, self.bisher_fe, chunks) | |
| for i in range(len(chunks)): | |
| g_hp, g_ap = g_probs[i] | |
| b_hp, b_ap = b_probs[i] | |
| final_ap = max(g_ap, b_ap) | |
| final_hp = 1.0 - final_ap | |
| if final_hp > 0.70: | |
| lbl = "Human-like" | |
| elif final_ap > 0.70: | |
| lbl = "Suspicious" | |
| else: | |
| lbl = "Neutral" | |
| start_sec = float(i) | |
| end_sec = float(i + 1) | |
| if has_remainder and i == len(chunks) - 1: | |
| end_sec = float(len(waveform) / sample_rate) | |
| segments.append({ | |
| "start": start_sec, | |
| "end": end_sec, | |
| "label": lbl, | |
| "human_probability": float(final_hp), | |
| "ai_probability": float(final_ap) | |
| }) | |
| except Exception as e: | |
| for i in range(len(chunks)): | |
| start_sec = float(i) | |
| end_sec = float(i + 1) if not (has_remainder and i == len(chunks) - 1) else float(len(waveform) / sample_rate) | |
| segments.append({ | |
| "start": start_sec, | |
| "end": end_sec, | |
| "label": "Neutral", | |
| "human_probability": 0.5, | |
| "ai_probability": 0.5 | |
| }) | |
| return segments | |