Spaces:
Sleeping
Sleeping
Commit ·
f07936b
1
Parent(s): b1df80d
Upgrade to specialized deepfake detection model and optimize audio processing
Browse files- app/infer.py +79 -92
- test_user_b64.py +33 -0
- verify_inference_logic.py +38 -0
- verify_model.py +33 -0
app/infer.py
CHANGED
|
@@ -4,7 +4,7 @@ import os
|
|
| 4 |
import numpy as np
|
| 5 |
import librosa
|
| 6 |
import time
|
| 7 |
-
from transformers import
|
| 8 |
from dotenv import load_dotenv
|
| 9 |
|
| 10 |
load_dotenv()
|
|
@@ -12,112 +12,97 @@ load_dotenv()
|
|
| 12 |
class VoiceClassifier:
|
| 13 |
def __init__(self):
|
| 14 |
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 15 |
-
print(f"Loading
|
| 16 |
|
| 17 |
-
# Load
|
| 18 |
-
self.
|
| 19 |
-
self.encoder.to(self.device)
|
| 20 |
-
self.encoder.eval()
|
| 21 |
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
nn.init.constant_(self.classifier.bias, -1.0)
|
| 34 |
-
nn.init.normal_(self.classifier.weight, mean=0.0, std=0.01)
|
| 35 |
-
|
| 36 |
-
print("Model loaded successfully.")
|
| 37 |
-
|
| 38 |
-
def extract_features(self, waveform: torch.Tensor):
|
| 39 |
-
"""
|
| 40 |
-
waveform: [1, T] Tensor at 16kHz
|
| 41 |
-
Returns: feature_vector [1, 769]
|
| 42 |
-
"""
|
| 43 |
-
waveform = waveform.to(self.device)
|
| 44 |
-
|
| 45 |
-
t0 = time.time()
|
| 46 |
-
# 1. Wav2Vec2 Embedding
|
| 47 |
-
with torch.no_grad():
|
| 48 |
-
outputs = self.encoder(waveform)
|
| 49 |
-
# last_hidden_state: [1, Sequence, 768]
|
| 50 |
-
hidden_states = outputs.last_hidden_state
|
| 51 |
-
# Mean Pooling -> [1, 768]
|
| 52 |
-
embedding = torch.mean(hidden_states, dim=1)
|
| 53 |
-
|
| 54 |
-
t1 = time.time()
|
| 55 |
-
print(f"DEBUG: Wav2Vec2 Embedding took {t1 - t0:.3f}s")
|
| 56 |
-
|
| 57 |
-
# 2. Pitch Variance
|
| 58 |
-
# Move to CPU for numpy/librosa ops
|
| 59 |
-
wav_np = waveform.squeeze().cpu().numpy()
|
| 60 |
-
|
| 61 |
-
# Use librosa for pitch tracking (fast approximation)
|
| 62 |
-
# fmin/fmax for human speech range
|
| 63 |
-
f0, voiced_flag, voiced_probs = librosa.pyin(
|
| 64 |
-
wav_np,
|
| 65 |
-
fmin=librosa.note_to_hz('C2'),
|
| 66 |
-
fmax=librosa.note_to_hz('C7'),
|
| 67 |
-
sr=16000,
|
| 68 |
-
frame_length=2048
|
| 69 |
-
)
|
| 70 |
-
|
| 71 |
-
# Filter NaNs
|
| 72 |
-
f0 = f0[~np.isnan(f0)]
|
| 73 |
-
|
| 74 |
-
if len(f0) > 0:
|
| 75 |
-
pitch_std = np.std(f0)
|
| 76 |
-
# Normalize? Let's just keep raw for now, or log scale
|
| 77 |
-
pitch_var = pitch_std
|
| 78 |
-
else:
|
| 79 |
-
pitch_var = 0.0
|
| 80 |
-
|
| 81 |
-
t2 = time.time()
|
| 82 |
-
print(f"DEBUG: Pitch Detection (librosa.pyin) took {t2 - t1:.3f}s")
|
| 83 |
-
|
| 84 |
-
# Combine
|
| 85 |
-
pitch_feature = torch.tensor([[pitch_var]], device=self.device, dtype=torch.float32)
|
| 86 |
-
|
| 87 |
-
# Concatenate [1, 768] + [1, 1] -> [1, 769]
|
| 88 |
-
features = torch.cat((embedding, pitch_feature), dim=1)
|
| 89 |
-
return features, pitch_var
|
| 90 |
|
| 91 |
def predict(self, waveform: torch.Tensor):
|
| 92 |
-
if self.
|
| 93 |
return {"error": "Model not loaded"}
|
| 94 |
|
| 95 |
try:
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
with torch.no_grad():
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
prediction = "AI_GENERATED"
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
confidence = 0.7 + (0.29 * (1.0 - (pitch_var / THRESHOLD_PITCH)))
|
| 111 |
-
explanation = "Unnatural pitch consistency and robotic speech patterns detected."
|
| 112 |
else:
|
| 113 |
prediction = "HUMAN"
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
-
# Override prob_ai for consistency in response
|
| 119 |
-
prob_ai = 0.9 if prediction == "AI_GENERATED" else 0.1
|
| 120 |
-
|
| 121 |
return {
|
| 122 |
"prediction": prediction,
|
| 123 |
"probability_ai": float(f"{prob_ai:.4f}"),
|
|
@@ -130,4 +115,6 @@ class VoiceClassifier:
|
|
| 130 |
|
| 131 |
except Exception as e:
|
| 132 |
print(f"Prediction Error: {e}")
|
|
|
|
|
|
|
| 133 |
return {"error": str(e)}
|
|
|
|
| 4 |
import numpy as np
|
| 5 |
import librosa
|
| 6 |
import time
|
| 7 |
+
from transformers import AutoModelForAudioClassification, Wav2Vec2FeatureExtractor
|
| 8 |
from dotenv import load_dotenv
|
| 9 |
|
| 10 |
load_dotenv()
|
|
|
|
| 12 |
class VoiceClassifier:
|
| 13 |
def __init__(self):
|
| 14 |
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 15 |
+
print(f"Loading Deepfake Detection model on {self.device}...")
|
| 16 |
|
| 17 |
+
# Load Fine-Tuned Deepfake Detection Model
|
| 18 |
+
self.model_name = "mo-thecreator/Deepfake-audio-detection"
|
|
|
|
|
|
|
| 19 |
|
| 20 |
+
try:
|
| 21 |
+
self.feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(self.model_name)
|
| 22 |
+
self.model = AutoModelForAudioClassification.from_pretrained(self.model_name)
|
| 23 |
+
self.model.to(self.device)
|
| 24 |
+
self.model.eval()
|
| 25 |
+
print(f"Model {self.model_name} loaded successfully.")
|
| 26 |
+
# Labels: {0: 'fake', 1: 'real'} usually for this model
|
| 27 |
+
print(f"Labels: {self.model.config.id2label}")
|
| 28 |
+
except Exception as e:
|
| 29 |
+
print(f"Error loading model: {e}")
|
| 30 |
+
self.model = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
def predict(self, waveform: torch.Tensor):
|
| 33 |
+
if self.model is None:
|
| 34 |
return {"error": "Model not loaded"}
|
| 35 |
|
| 36 |
try:
|
| 37 |
+
# 1. Preprocess Audio
|
| 38 |
+
# Waveform is [1, T] Tensor. Convert to numpy [T]
|
| 39 |
+
wav_np = waveform.squeeze().cpu().numpy()
|
| 40 |
+
|
| 41 |
+
# Ensure we send it as a list or numpy array to the extractor
|
| 42 |
+
inputs = self.feature_extractor(
|
| 43 |
+
wav_np,
|
| 44 |
+
sampling_rate=16000,
|
| 45 |
+
return_tensors="pt",
|
| 46 |
+
padding=True
|
| 47 |
+
)
|
| 48 |
|
| 49 |
+
inputs = {k: v.to(self.device) for k, v in inputs.items()}
|
| 50 |
+
|
| 51 |
+
t0 = time.time()
|
| 52 |
+
|
| 53 |
+
# 2. Model Inference
|
| 54 |
with torch.no_grad():
|
| 55 |
+
outputs = self.model(**inputs)
|
| 56 |
+
logits = outputs.logits
|
| 57 |
+
probs = torch.softmax(logits, dim=-1)
|
| 58 |
+
|
| 59 |
+
# Logic for this specific model:
|
| 60 |
+
# Label 0: 'fake' (AI)
|
| 61 |
+
# Label 1: 'real' (Human)
|
| 62 |
+
prob_fake = probs[0][0].item()
|
| 63 |
+
prob_real = probs[0][1].item()
|
| 64 |
|
| 65 |
+
t1 = time.time()
|
| 66 |
+
print(f"DEBUG: Inference took {t1 - t0:.3f}s. probs: {probs}")
|
| 67 |
+
|
| 68 |
+
# 3. Pitch Analysis (for explanation)
|
| 69 |
+
# Use librosa for pitch tracking (fast approximation)
|
| 70 |
+
f0, voiced_flag, voiced_probs = librosa.pyin(
|
| 71 |
+
wav_np,
|
| 72 |
+
fmin=librosa.note_to_hz('C2'),
|
| 73 |
+
fmax=librosa.note_to_hz('C7'),
|
| 74 |
+
sr=16000,
|
| 75 |
+
frame_length=2048
|
| 76 |
+
)
|
| 77 |
+
f0 = f0[~np.isnan(f0)]
|
| 78 |
+
pitch_var = np.std(f0) if len(f0) > 0 else 0.0
|
| 79 |
|
| 80 |
+
t2 = time.time()
|
| 81 |
+
print(f"DEBUG: Pitch Detection took {t2 - t1:.3f}s. Variance: {pitch_var}")
|
| 82 |
+
|
| 83 |
+
# 4. Final Classification Logic
|
| 84 |
+
# Deepfake model is the authority
|
| 85 |
+
if prob_fake > prob_real:
|
| 86 |
prediction = "AI_GENERATED"
|
| 87 |
+
confidence = prob_fake
|
| 88 |
+
prob_ai = prob_fake
|
|
|
|
|
|
|
| 89 |
else:
|
| 90 |
prediction = "HUMAN"
|
| 91 |
+
confidence = prob_real
|
| 92 |
+
prob_ai = prob_fake
|
| 93 |
+
|
| 94 |
+
# Construct Explanation
|
| 95 |
+
if prediction == "AI_GENERATED":
|
| 96 |
+
if pitch_var < 20.0:
|
| 97 |
+
explanation = f"Deepfake model reported {confidence*100:.1f}% confidence. Detected unnatural pitch consistency (Variance: {pitch_var:.1f})."
|
| 98 |
+
else:
|
| 99 |
+
explanation = f"Deepfake model reported {confidence*100:.1f}% confidence. Detected digital artifacts characteristic of AI synthesis."
|
| 100 |
+
else:
|
| 101 |
+
if pitch_var > 20.0:
|
| 102 |
+
explanation = f"Deepfake model reported {confidence*100:.1f}% confidence. Natural prosody and high pitch variance detected."
|
| 103 |
+
else:
|
| 104 |
+
explanation = f"Deepfake model reported {confidence*100:.1f}% confidence. Audio classified as human despite low pitch variance."
|
| 105 |
|
|
|
|
|
|
|
|
|
|
| 106 |
return {
|
| 107 |
"prediction": prediction,
|
| 108 |
"probability_ai": float(f"{prob_ai:.4f}"),
|
|
|
|
| 115 |
|
| 116 |
except Exception as e:
|
| 117 |
print(f"Prediction Error: {e}")
|
| 118 |
+
import traceback
|
| 119 |
+
traceback.print_exc()
|
| 120 |
return {"error": str(e)}
|
test_user_b64.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import requests
|
| 3 |
+
import json
|
| 4 |
+
|
| 5 |
+
def test_provided_b64():
|
| 6 |
+
# Use the first part of the string provided by the user (it was truncated in the prompt view but I'll use the one I have)
|
| 7 |
+
# Actually, I'll just use a small script that reads it from a file if I could,
|
| 8 |
+
# but I'll just paste the one I have from the prompt if it fits.
|
| 9 |
+
# If the string is too long, I'll just use the first 100kb of the user request.
|
| 10 |
+
|
| 11 |
+
b64_str = "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"
|
| 12 |
+
url = "http://127.0.0.1:7860/api/voice-detection"
|
| 13 |
+
headers = {
|
| 14 |
+
"x-api-key": "test-key-123",
|
| 15 |
+
"Content-Type": "application/json"
|
| 16 |
+
}
|
| 17 |
+
payload = {
|
| 18 |
+
"language": "English",
|
| 19 |
+
"audioFormat": "mp3",
|
| 20 |
+
"audioBase64": b64_str
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
print(f"📡 POSTing specific test case to {url}...")
|
| 24 |
+
try:
|
| 25 |
+
response = requests.post(url, headers=headers, json=payload)
|
| 26 |
+
print(f"📥 Status Code: {response.status_code}")
|
| 27 |
+
print("📄 Response JSON:")
|
| 28 |
+
print(response.json())
|
| 29 |
+
except Exception as e:
|
| 30 |
+
print(f"❌ ERROR: {e}")
|
| 31 |
+
|
| 32 |
+
if __name__ == "__main__":
|
| 33 |
+
test_provided_b64()
|
verify_inference_logic.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
import numpy as np
|
| 5 |
+
import io
|
| 6 |
+
from pydub import AudioSegment
|
| 7 |
+
|
| 8 |
+
# Ensure app is in path
|
| 9 |
+
sys.path.append(os.getcwd())
|
| 10 |
+
|
| 11 |
+
from app.infer import VoiceClassifier
|
| 12 |
+
from app.audio import process_audio
|
| 13 |
+
|
| 14 |
+
def verify():
|
| 15 |
+
classifier = VoiceClassifier()
|
| 16 |
+
|
| 17 |
+
# Generate a valid sine wave MP3 in memory
|
| 18 |
+
sr = 44100
|
| 19 |
+
t = np.linspace(0, 1, sr, endpoint=False)
|
| 20 |
+
x = 0.5 * np.sin(2 * np.pi * 440 * t)
|
| 21 |
+
x_int = (x * 32767).astype(np.int16)
|
| 22 |
+
audio = AudioSegment(x_int.tobytes(), frame_rate=sr, sample_width=2, channels=1)
|
| 23 |
+
|
| 24 |
+
mp3_io = io.BytesIO()
|
| 25 |
+
audio.export(mp3_io, format="mp3")
|
| 26 |
+
mp3_bytes = mp3_io.getvalue()
|
| 27 |
+
|
| 28 |
+
print(f"Generated test MP3 bytes: {len(mp3_bytes)} bytes")
|
| 29 |
+
|
| 30 |
+
# Process audio raw bytes
|
| 31 |
+
waveform = process_audio(mp3_bytes)
|
| 32 |
+
|
| 33 |
+
# Predict
|
| 34 |
+
result = classifier.predict(waveform)
|
| 35 |
+
print("Result:", result)
|
| 36 |
+
|
| 37 |
+
if __name__ == "__main__":
|
| 38 |
+
verify()
|
verify_model.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import torch
|
| 3 |
+
from transformers import AutoModelForAudioClassification, Wav2Vec2FeatureExtractor
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
def verify_model():
|
| 7 |
+
model_name = "mo-thecreator/Deepfake-audio-detection"
|
| 8 |
+
print(f"Loading {model_name}...")
|
| 9 |
+
try:
|
| 10 |
+
feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(model_name)
|
| 11 |
+
model = AutoModelForAudioClassification.from_pretrained(model_name)
|
| 12 |
+
print("Model loaded successfully!")
|
| 13 |
+
|
| 14 |
+
print("Labels:", model.config.id2label)
|
| 15 |
+
|
| 16 |
+
# Create dummy audio (1 second of silence/noise)
|
| 17 |
+
# 16000 Hz
|
| 18 |
+
dummy_audio = np.random.uniform(-1, 1, 16000)
|
| 19 |
+
|
| 20 |
+
inputs = feature_extractor(dummy_audio, sampling_rate=16000, return_tensors="pt")
|
| 21 |
+
|
| 22 |
+
with torch.no_grad():
|
| 23 |
+
logits = model(**inputs).logits
|
| 24 |
+
|
| 25 |
+
print("Logits:", logits)
|
| 26 |
+
predicted_class_id = torch.argmax(logits, dim=-1).item()
|
| 27 |
+
print("Predicted Label:", model.config.id2label[predicted_class_id])
|
| 28 |
+
|
| 29 |
+
except Exception as e:
|
| 30 |
+
print(f"Failed to load/run model: {e}")
|
| 31 |
+
|
| 32 |
+
if __name__ == "__main__":
|
| 33 |
+
verify_model()
|