Spaces:
Configuration error
Configuration error
Commit
Β·
b5b2f19
1
Parent(s):
5ff9f70
first commit
Browse files- .gitignore +3 -0
- Evaluation/visualize_tsne.py +24 -0
- README.md +1 -11
- app.py +67 -0
- features/embeddings.npy +3 -0
- features/labels.npy +3 -0
- model/random_forest.pkl +3 -0
- model/random_forest_aug.pkl +3 -0
- model/random_forest_tuned_aug.pkl +3 -0
- render.yaml +7 -0
- requirements.txt +0 -0
- scripts/augment_data.py +44 -0
- scripts/ensemble_model.py +44 -0
- scripts/extract_audio_features.py +60 -0
- scripts/extract_features.py +114 -0
- scripts/predict.py +39 -0
- scripts/predict_audio.py +45 -0
- scripts/predict_video.py +91 -0
- scripts/random_forest.py +34 -0
- scripts/stacking_model.py +47 -0
- scripts/svm.py +34 -0
- scripts/xgboost.py +34 -0
- space.yaml +3 -0
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.venv
|
| 2 |
+
pyodide
|
| 3 |
+
venv/
|
Evaluation/visualize_tsne.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import matplotlib.pyplot as plt
|
| 2 |
+
from sklearn.manifold import TSNE
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
# Load your embeddings (X) and labels (y)
|
| 6 |
+
X = np.load("../features/embeddings.npy") # FaceNet embeddings
|
| 7 |
+
y = np.load("../features/labels.npy") # Labels (real, fake, AI-generated)
|
| 8 |
+
|
| 9 |
+
# Ensure that the number of samples is greater than the perplexity value
|
| 10 |
+
n_samples = X.shape[0]
|
| 11 |
+
perplexity_value = min(30, n_samples - 1) # Set perplexity less than number of samples
|
| 12 |
+
|
| 13 |
+
# Apply t-SNE to reduce dimensionality to 2D
|
| 14 |
+
tsne = TSNE(n_components=2, random_state=42, perplexity=perplexity_value)
|
| 15 |
+
X_tsne = tsne.fit_transform(X)
|
| 16 |
+
|
| 17 |
+
# Plot the results
|
| 18 |
+
plt.figure(figsize=(8, 6))
|
| 19 |
+
plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='viridis', s=50, alpha=0.7)
|
| 20 |
+
plt.title("t-SNE Visualization of FaceNet Embeddings")
|
| 21 |
+
plt.colorbar(label='Class')
|
| 22 |
+
|
| 23 |
+
# Show the plot in a window
|
| 24 |
+
plt.show()
|
README.md
CHANGED
|
@@ -1,11 +1 @@
|
|
| 1 |
-
|
| 2 |
-
title: DeepscanAPI
|
| 3 |
-
emoji: π
|
| 4 |
-
colorFrom: red
|
| 5 |
-
colorTo: green
|
| 6 |
-
sdk: docker
|
| 7 |
-
pinned: false
|
| 8 |
-
license: mit
|
| 9 |
-
---
|
| 10 |
-
|
| 11 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
+
Please create venv and install the requirementss.txt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
import numpy as np
|
| 4 |
+
import joblib
|
| 5 |
+
from PIL import Image
|
| 6 |
+
from flask import Flask, request, jsonify
|
| 7 |
+
from transformers import CLIPProcessor, CLIPModel
|
| 8 |
+
from io import BytesIO
|
| 9 |
+
from flask_cors import CORS
|
| 10 |
+
import base64
|
| 11 |
+
import io
|
| 12 |
+
|
| 13 |
+
# Flask app initialization
|
| 14 |
+
app = Flask(__name__)
|
| 15 |
+
CORS(app)
|
| 16 |
+
|
| 17 |
+
# Load models once at the start
|
| 18 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 19 |
+
print(f"[INFO] Using device: {device}")
|
| 20 |
+
|
| 21 |
+
# Load the CLIP model and processor
|
| 22 |
+
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to(device)
|
| 23 |
+
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
|
| 24 |
+
|
| 25 |
+
# Load the ensemble classifier model
|
| 26 |
+
ensemble_clf = joblib.load("model/random_forest_tuned_aug.pkl")
|
| 27 |
+
|
| 28 |
+
# Label mapping
|
| 29 |
+
label_map = {0: "real", 1: "deepfake", 2: "ai_gen"}
|
| 30 |
+
|
| 31 |
+
def extract_features(image):
|
| 32 |
+
image = image.resize((224, 224)) # Resize to the required input size (224x224)
|
| 33 |
+
inputs = processor(images=image, return_tensors="pt").to(device)
|
| 34 |
+
|
| 35 |
+
with torch.no_grad():
|
| 36 |
+
# Extract image features using CLIP
|
| 37 |
+
outputs = model.get_image_features(**inputs)
|
| 38 |
+
|
| 39 |
+
emb = outputs.cpu().numpy().squeeze()
|
| 40 |
+
return emb
|
| 41 |
+
|
| 42 |
+
@app.route("/predict", methods=["POST"])
|
| 43 |
+
def predict():
|
| 44 |
+
# Get the uploaded image
|
| 45 |
+
data = request.json
|
| 46 |
+
if 'image' not in data:
|
| 47 |
+
return jsonify({"error": "No image provided"}), 400
|
| 48 |
+
|
| 49 |
+
image_data = base64.b64decode(data['image'])
|
| 50 |
+
image = Image.open(io.BytesIO(image_data)).convert("RGB")
|
| 51 |
+
|
| 52 |
+
# Extract features and predict
|
| 53 |
+
features = extract_features(image)
|
| 54 |
+
probs = ensemble_clf.predict_proba([features])[0]
|
| 55 |
+
top_idx = np.argmax(probs)
|
| 56 |
+
|
| 57 |
+
# Prepare response
|
| 58 |
+
response = {
|
| 59 |
+
"prediction": label_map[top_idx],
|
| 60 |
+
"probabilities": probs.tolist()
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
return jsonify(response)
|
| 64 |
+
|
| 65 |
+
if __name__ == "__main__":
|
| 66 |
+
# Run Flask app
|
| 67 |
+
app.run(debug=True)
|
features/embeddings.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7021bc09baa1ad36ee9dbd765f3fc7332a3a7a9465b9c3369c2d20970d55ca1b
|
| 3 |
+
size 18560
|
features/labels.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:04df4e7bc4f3e3683b77f8deea9fd98ea9c1ca22959c6d40b442d192f3e6092c
|
| 3 |
+
size 164
|
model/random_forest.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d5da12754b9328bcee20fd05b4edb7c3252601f381f9accac2859e5305a416c8
|
| 3 |
+
size 85009
|
model/random_forest_aug.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:43e8f80fbe0c4e0c5a3e7c46105048c603bf65c8f9177a813fb1c03acfcf1e11
|
| 3 |
+
size 58425313
|
model/random_forest_tuned_aug.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:941c23b7a9dcaf45f37b9b978deb9b1aad27112a7db3da69ff26208a1c45bc2c
|
| 3 |
+
size 73887089
|
render.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
- type: web
|
| 3 |
+
name: deepfake-detector
|
| 4 |
+
env: python
|
| 5 |
+
plan: free
|
| 6 |
+
buildCommand: ""
|
| 7 |
+
startCommand: gunicorn app:app
|
requirements.txt
ADDED
|
Binary file (1.79 kB). View file
|
|
|
scripts/augment_data.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import cv2
|
| 3 |
+
import numpy as np
|
| 4 |
+
import albumentations as A
|
| 5 |
+
from glob import glob
|
| 6 |
+
|
| 7 |
+
# Define the augmentation pipeline
|
| 8 |
+
AUG = A.Compose([
|
| 9 |
+
A.HorizontalFlip(p=0.5),
|
| 10 |
+
A.RandomBrightnessContrast(p=0.5),
|
| 11 |
+
A.GaussianBlur(blur_limit=3, p=0.3),
|
| 12 |
+
A.Rotate(limit=15, p=0.3),
|
| 13 |
+
A.RandomResizedCrop(160, 160, scale=(0.9, 1.0), p=0.3),
|
| 14 |
+
A.ElasticTransform(alpha=1.0, sigma=50, alpha_affine=50, p=0.3), # Elastic transformation
|
| 15 |
+
A.CoarseDropout(max_holes=1, max_height=8, max_width=8, p=0.3), # Random Erasing
|
| 16 |
+
A.PerspectiveTransform(scale=(0.01, 0.1), p=0.5) # Random perspective shift
|
| 17 |
+
])
|
| 18 |
+
|
| 19 |
+
# Directory with input images
|
| 20 |
+
INPUT_DIR = "data"
|
| 21 |
+
CATEGORIES = ["real", "deepfake", "ai_gen"]
|
| 22 |
+
valid_extensions = ['.jpg', '.jpeg', '.png']
|
| 23 |
+
|
| 24 |
+
for cat in CATEGORIES:
|
| 25 |
+
os.makedirs(os.path.join(INPUT_DIR, cat, 'augmented'), exist_ok=True) # Create an 'augmented' folder inside each category
|
| 26 |
+
files = glob(f"{INPUT_DIR}/{cat}/*")
|
| 27 |
+
|
| 28 |
+
for i, file in enumerate(files):
|
| 29 |
+
# Skip non-image files
|
| 30 |
+
if not any(file.lower().endswith(ext) for ext in valid_extensions):
|
| 31 |
+
continue
|
| 32 |
+
|
| 33 |
+
img = cv2.imread(file)
|
| 34 |
+
if img is None:
|
| 35 |
+
continue
|
| 36 |
+
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
| 37 |
+
|
| 38 |
+
# Generate 3 augmented images
|
| 39 |
+
for j in range(3):
|
| 40 |
+
aug = AUG(image=img)["image"]
|
| 41 |
+
save_path = os.path.join(INPUT_DIR, cat, 'augmented', f"aug_{i}_{j}.jpg")
|
| 42 |
+
cv2.imwrite(save_path, cv2.cvtColor(aug, cv2.COLOR_RGB2BGR))
|
| 43 |
+
|
| 44 |
+
print("β
Augmentation complete. You can now re-run feature extraction and model training.")
|
scripts/ensemble_model.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
from sklearn.ensemble import VotingClassifier
|
| 4 |
+
from sklearn.metrics import classification_report
|
| 5 |
+
from sklearn.ensemble import RandomForestClassifier
|
| 6 |
+
from sklearn.svm import SVC
|
| 7 |
+
from xgboost import XGBClassifier
|
| 8 |
+
import joblib
|
| 9 |
+
from sklearn.model_selection import train_test_split
|
| 10 |
+
|
| 11 |
+
# Load pre-extracted features and labels
|
| 12 |
+
print("π¦ Loading pre-extracted features and labels...")
|
| 13 |
+
|
| 14 |
+
# Load the features (X) and labels (y)
|
| 15 |
+
X = np.load("features/embeddings.npy")
|
| 16 |
+
y = np.load("features/labels.npy")
|
| 17 |
+
|
| 18 |
+
print(f"β
Loaded {len(X)} samples with {X.shape[1]} features each.")
|
| 19 |
+
|
| 20 |
+
# Split into training and testing sets
|
| 21 |
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
| 22 |
+
|
| 23 |
+
# Initialize individual classifiers
|
| 24 |
+
rf = RandomForestClassifier(n_estimators=100, random_state=42)
|
| 25 |
+
svm = SVC(probability=True, kernel='linear') # Using probability=True for soft voting
|
| 26 |
+
xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss')
|
| 27 |
+
|
| 28 |
+
# Create the Voting Classifier ensemble
|
| 29 |
+
ensemble_clf = VotingClassifier(estimators=[('rf', rf), ('svm', svm), ('xgb', xgb)], voting='soft')
|
| 30 |
+
|
| 31 |
+
# Train the ensemble model
|
| 32 |
+
print("π§ Training the ensemble classifier...")
|
| 33 |
+
ensemble_clf.fit(X_train, y_train)
|
| 34 |
+
|
| 35 |
+
# Evaluate the ensemble model
|
| 36 |
+
print("\nπ Evaluation Report:")
|
| 37 |
+
y_pred = ensemble_clf.predict(X_test)
|
| 38 |
+
print(classification_report(y_test, y_pred, target_names=["real", "deepfake", "ai_gen"]))
|
| 39 |
+
|
| 40 |
+
# Save the trained ensemble model
|
| 41 |
+
os.makedirs("model", exist_ok=True)
|
| 42 |
+
joblib.dump(ensemble_clf, "model/ensemble_model.pkl")
|
| 43 |
+
|
| 44 |
+
print("\nβ
Ensemble model trained and saved to model/ensemble_model.pkl")
|
scripts/extract_audio_features.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import librosa
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
DATA_DIR = "data"
|
| 6 |
+
CATEGORIES = ["real_audio", "fake_audio"]
|
| 7 |
+
OUTPUT_DIR = "features_audio"
|
| 8 |
+
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 9 |
+
|
| 10 |
+
X, y = [], []
|
| 11 |
+
|
| 12 |
+
# Data augmentation techniques
|
| 13 |
+
def augment_audio(audio, sr):
|
| 14 |
+
# Example: Shift pitch randomly within a range
|
| 15 |
+
pitch_shift = np.random.randint(-5, 5) # Random pitch shift between -5 and 5 semitones
|
| 16 |
+
audio = librosa.effects.pitch_shift(audio, sr, n_steps=pitch_shift)
|
| 17 |
+
|
| 18 |
+
# Example: Time stretch randomly between 0.8x and 1.2x speed
|
| 19 |
+
rate = np.random.uniform(0.8, 1.2)
|
| 20 |
+
audio = librosa.effects.time_stretch(audio, rate)
|
| 21 |
+
|
| 22 |
+
# Example: Add random noise
|
| 23 |
+
noise_factor = np.random.uniform(0.001, 0.005) # Random noise factor
|
| 24 |
+
noise = np.random.randn(len(audio)) * noise_factor
|
| 25 |
+
audio = audio + noise
|
| 26 |
+
|
| 27 |
+
return audio
|
| 28 |
+
|
| 29 |
+
def extract_mfcc(path):
|
| 30 |
+
try:
|
| 31 |
+
audio, sr = librosa.load(path, sr=16000) # Load the audio with 16kHz sampling rate
|
| 32 |
+
|
| 33 |
+
# Apply audio augmentation
|
| 34 |
+
audio = augment_audio(audio, sr)
|
| 35 |
+
|
| 36 |
+
# Extract MFCCs from the augmented audio
|
| 37 |
+
mfcc = librosa.feature.mfcc(y=audio, sr=sr, n_mfcc=13)
|
| 38 |
+
mfcc_mean = np.mean(mfcc.T, axis=0) # Average MFCC features
|
| 39 |
+
return mfcc_mean
|
| 40 |
+
except Exception as e:
|
| 41 |
+
print(f"[ERROR] Failed to process {path}: {e}")
|
| 42 |
+
return None
|
| 43 |
+
|
| 44 |
+
# Loop through each category and process audio files
|
| 45 |
+
for label, cat in enumerate(CATEGORIES):
|
| 46 |
+
folder = os.path.join(DATA_DIR, cat)
|
| 47 |
+
for fname in os.listdir(folder):
|
| 48 |
+
if not fname.endswith(".wav"): # Only process .wav files
|
| 49 |
+
continue
|
| 50 |
+
fpath = os.path.join(folder, fname)
|
| 51 |
+
features = extract_mfcc(fpath) # Extract MFCC features
|
| 52 |
+
if features is not None:
|
| 53 |
+
X.append(features)
|
| 54 |
+
y.append(label)
|
| 55 |
+
|
| 56 |
+
# Save the extracted features and labels
|
| 57 |
+
np.save(os.path.join(OUTPUT_DIR, "embeddings.npy"), np.array(X))
|
| 58 |
+
np.save(os.path.join(OUTPUT_DIR, "labels.npy"), np.array(y))
|
| 59 |
+
|
| 60 |
+
print(f"β
Extracted MFCC features for {len(X)} audio samples.")
|
scripts/extract_features.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
import numpy as np
|
| 4 |
+
from PIL import Image
|
| 5 |
+
from tqdm import tqdm
|
| 6 |
+
from facenet_pytorch import InceptionResnetV1, MTCNN
|
| 7 |
+
from transformers import CLIPProcessor, CLIPModel
|
| 8 |
+
import albumentations as A
|
| 9 |
+
import cv2
|
| 10 |
+
|
| 11 |
+
# Set device
|
| 12 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 13 |
+
print(f"[INFO] Using device: {device}")
|
| 14 |
+
|
| 15 |
+
# Initialize models
|
| 16 |
+
mtcnn = MTCNN(image_size=160, device=device)
|
| 17 |
+
facenet = InceptionResnetV1(pretrained='vggface2').eval().to(device)
|
| 18 |
+
|
| 19 |
+
# Load CLIP model and processor
|
| 20 |
+
clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to(device)
|
| 21 |
+
clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
|
| 22 |
+
|
| 23 |
+
# Input data folders
|
| 24 |
+
DATA_DIR = "data"
|
| 25 |
+
CATEGORIES = ["real", "deepfake", "ai_gen"]
|
| 26 |
+
|
| 27 |
+
# Output path
|
| 28 |
+
os.makedirs("features", exist_ok=True)
|
| 29 |
+
|
| 30 |
+
# Data augmentation pipeline
|
| 31 |
+
augment = A.Compose([
|
| 32 |
+
A.RandomBrightnessContrast(p=0.2),
|
| 33 |
+
A.HorizontalFlip(p=0.5),
|
| 34 |
+
A.Rotate(limit=10, p=0.3),
|
| 35 |
+
A.MotionBlur(p=0.2),
|
| 36 |
+
A.Resize(160, 160), # For MTCNN size requirement
|
| 37 |
+
])
|
| 38 |
+
|
| 39 |
+
def extract_facenet_features(img_path):
|
| 40 |
+
image = Image.open(img_path).convert("RGB")
|
| 41 |
+
|
| 42 |
+
# Resize image before passing it to MTCNN
|
| 43 |
+
img_np = np.array(image)
|
| 44 |
+
img_resized = cv2.resize(img_np, (160, 160)) # Resize image to 160x160
|
| 45 |
+
|
| 46 |
+
# Apply augmentation
|
| 47 |
+
augmented = augment(image=img_resized)["image"]
|
| 48 |
+
img_aug = Image.fromarray(augmented)
|
| 49 |
+
|
| 50 |
+
# Face detection using MTCNN
|
| 51 |
+
face = mtcnn(img_aug)
|
| 52 |
+
if face is None:
|
| 53 |
+
print(f"[WARN] No face detected in {img_path}")
|
| 54 |
+
return None
|
| 55 |
+
face = face.unsqueeze(0).to(device)
|
| 56 |
+
|
| 57 |
+
# Feature extraction using FaceNet
|
| 58 |
+
with torch.no_grad():
|
| 59 |
+
face_emb = facenet(face)
|
| 60 |
+
|
| 61 |
+
return face_emb.squeeze().cpu().numpy()
|
| 62 |
+
|
| 63 |
+
def extract_clip_features(img_path):
|
| 64 |
+
image = Image.open(img_path).convert("RGB")
|
| 65 |
+
|
| 66 |
+
# Apply the same augmentation to the image before passing to CLIP
|
| 67 |
+
img_np = np.array(image)
|
| 68 |
+
augmented = augment(image=img_np)["image"]
|
| 69 |
+
img_aug = Image.fromarray(augmented)
|
| 70 |
+
|
| 71 |
+
# Extract features using CLIP
|
| 72 |
+
inputs = clip_processor(images=img_aug, return_tensors="pt").to(device)
|
| 73 |
+
with torch.no_grad():
|
| 74 |
+
clip_outputs = clip_model.get_image_features(**inputs)
|
| 75 |
+
|
| 76 |
+
return clip_outputs.cpu().numpy().squeeze()
|
| 77 |
+
|
| 78 |
+
def extract_combined_features(img_path):
|
| 79 |
+
# Extract features from both FaceNet and CLIP
|
| 80 |
+
facenet_features = extract_facenet_features(img_path)
|
| 81 |
+
clip_features = extract_clip_features(img_path)
|
| 82 |
+
|
| 83 |
+
if facenet_features is None:
|
| 84 |
+
return None
|
| 85 |
+
|
| 86 |
+
# Combine (concatenate) the features from FaceNet and CLIP
|
| 87 |
+
combined_features = np.concatenate((facenet_features, clip_features))
|
| 88 |
+
return combined_features
|
| 89 |
+
|
| 90 |
+
def extract_all_features():
|
| 91 |
+
X, y = [], []
|
| 92 |
+
for label, category in enumerate(CATEGORIES):
|
| 93 |
+
folder = os.path.join(DATA_DIR, category)
|
| 94 |
+
if not os.path.isdir(folder):
|
| 95 |
+
print(f"[WARN] Missing folder: {folder}")
|
| 96 |
+
continue
|
| 97 |
+
|
| 98 |
+
print(f"\nπ§ Extracting from: {category} ({folder})")
|
| 99 |
+
for fname in tqdm(os.listdir(folder)):
|
| 100 |
+
if not fname.lower().endswith((".jpg", ".jpeg", ".png")):
|
| 101 |
+
continue
|
| 102 |
+
path = os.path.join(folder, fname)
|
| 103 |
+
combined_features = extract_combined_features(path)
|
| 104 |
+
if combined_features is not None:
|
| 105 |
+
X.append(combined_features)
|
| 106 |
+
y.append(label)
|
| 107 |
+
|
| 108 |
+
# Save the extracted features
|
| 109 |
+
np.save("../features/embeddings.npy", np.array(X))
|
| 110 |
+
np.save("../features/labels.npy", np.array(y))
|
| 111 |
+
print(f"\nβ
Done: Saved {len(X)} embeddings.")
|
| 112 |
+
|
| 113 |
+
if __name__ == "__main__":
|
| 114 |
+
extract_all_features()
|
scripts/predict.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import torch
|
| 3 |
+
import numpy as np
|
| 4 |
+
import joblib
|
| 5 |
+
from PIL import Image
|
| 6 |
+
from transformers import CLIPProcessor, CLIPModel
|
| 7 |
+
from io import BytesIO
|
| 8 |
+
|
| 9 |
+
# Load models
|
| 10 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 11 |
+
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to(device)
|
| 12 |
+
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
|
| 13 |
+
ensemble_clf = joblib.load("models/random_forest_aug.pkl")
|
| 14 |
+
|
| 15 |
+
label_map = {0: "real", 1: "deepfake", 2: "ai_gen"}
|
| 16 |
+
|
| 17 |
+
def extract_features(image):
|
| 18 |
+
image = image.resize((224, 224)) # Resize image
|
| 19 |
+
inputs = processor(images=image, return_tensors="pt").to(device)
|
| 20 |
+
with torch.no_grad():
|
| 21 |
+
outputs = model.get_image_features(**inputs)
|
| 22 |
+
emb = outputs.cpu().numpy().squeeze()
|
| 23 |
+
return emb
|
| 24 |
+
|
| 25 |
+
def predict(image_path):
|
| 26 |
+
image = Image.open(image_path).convert("RGB")
|
| 27 |
+
features = extract_features(image)
|
| 28 |
+
probs = ensemble_clf.predict_proba([features])[0]
|
| 29 |
+
top_idx = np.argmax(probs)
|
| 30 |
+
print(f"Prediction: {label_map[top_idx]}")
|
| 31 |
+
print(f"Probabilities: {probs}")
|
| 32 |
+
|
| 33 |
+
if __name__ == "__main__":
|
| 34 |
+
if len(sys.argv) != 2:
|
| 35 |
+
print("Usage: python predict.py <image_path>")
|
| 36 |
+
sys.exit(1)
|
| 37 |
+
predict(sys.argv[1])
|
| 38 |
+
|
| 39 |
+
|
scripts/predict_audio.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import librosa
|
| 3 |
+
import numpy as np
|
| 4 |
+
import joblib
|
| 5 |
+
|
| 6 |
+
def extract_mfcc(path):
|
| 7 |
+
try:
|
| 8 |
+
audio, sr = librosa.load(path, sr=16000) # Load the audio with 16kHz sample rate
|
| 9 |
+
mfcc = librosa.feature.mfcc(y=audio, sr=sr, n_mfcc=13) # Extract MFCC features
|
| 10 |
+
return np.mean(mfcc.T, axis=0) # Return the mean of MFCCs across time
|
| 11 |
+
except Exception as e:
|
| 12 |
+
print(f"[ERROR] Failed to process {path}: {e}")
|
| 13 |
+
return None
|
| 14 |
+
|
| 15 |
+
def predict_audio(path):
|
| 16 |
+
# Extract features from the given audio path
|
| 17 |
+
features = extract_mfcc(path)
|
| 18 |
+
if features is None:
|
| 19 |
+
return # Exit if feature extraction fails
|
| 20 |
+
|
| 21 |
+
# Load pre-trained Random Forest model for audio prediction
|
| 22 |
+
model = joblib.load("model/audio_rf.pkl")
|
| 23 |
+
|
| 24 |
+
# Reshape the features to match the expected input shape (1, -1)
|
| 25 |
+
features = features.reshape(1, -1)
|
| 26 |
+
|
| 27 |
+
# Make prediction
|
| 28 |
+
pred = model.predict(features)[0]
|
| 29 |
+
|
| 30 |
+
# Map the prediction to class labels (real: 0, fake: 1)
|
| 31 |
+
label = "real" if pred == 0 else "fake"
|
| 32 |
+
|
| 33 |
+
# Print the prediction
|
| 34 |
+
print(f"π§ Prediction: {label}")
|
| 35 |
+
|
| 36 |
+
if __name__ == "__main__":
|
| 37 |
+
if len(sys.argv) != 2:
|
| 38 |
+
print("Usage: python scripts/predict_audio.py <audio_path>")
|
| 39 |
+
sys.exit(1)
|
| 40 |
+
|
| 41 |
+
# Get the audio file path from command line argument
|
| 42 |
+
path = sys.argv[1]
|
| 43 |
+
|
| 44 |
+
# Predict the audio label
|
| 45 |
+
predict_audio(path)
|
scripts/predict_video.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import torch
|
| 3 |
+
import numpy as np
|
| 4 |
+
from PIL import Image
|
| 5 |
+
import joblib
|
| 6 |
+
from facenet_pytorch import MTCNN, InceptionResnetV1
|
| 7 |
+
|
| 8 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 9 |
+
print(f"[INFO] Using device: {device}")
|
| 10 |
+
|
| 11 |
+
# Load models
|
| 12 |
+
mtcnn = MTCNN(image_size=160, device=device)
|
| 13 |
+
facenet = InceptionResnetV1(pretrained='vggface2').eval().to(device)
|
| 14 |
+
clf = joblib.load("model/ensemble_model.pkl") # Example classifier model
|
| 15 |
+
label_map = {0: "real", 1: "deepfake", 2: "ai_gen"}
|
| 16 |
+
|
| 17 |
+
def extract_faces_from_video(video_path, time_interval_sec=10):
|
| 18 |
+
cap = cv2.VideoCapture(video_path)
|
| 19 |
+
embeddings = []
|
| 20 |
+
|
| 21 |
+
# Get the total number of frames in the video and the FPS
|
| 22 |
+
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 23 |
+
fps = cap.get(cv2.CAP_PROP_FPS())
|
| 24 |
+
video_duration = total_frames / fps # Video duration in seconds
|
| 25 |
+
print(f"[INFO] Video duration: {video_duration} seconds, FPS: {fps}")
|
| 26 |
+
|
| 27 |
+
# Calculate the frame skip based on the desired time interval
|
| 28 |
+
frame_skip = int(fps * time_interval_sec) # Process frames every 'time_interval_sec' seconds
|
| 29 |
+
print(f"[INFO] Processing every {time_interval_sec} seconds. Skipping {frame_skip} frames.")
|
| 30 |
+
|
| 31 |
+
frame_idx = 0
|
| 32 |
+
while True:
|
| 33 |
+
ret, frame = cap.read()
|
| 34 |
+
if not ret:
|
| 35 |
+
break
|
| 36 |
+
|
| 37 |
+
# Process frames based on the calculated frame skip
|
| 38 |
+
if frame_idx % frame_skip == 0:
|
| 39 |
+
image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) # Convert to RGB
|
| 40 |
+
combined_features = extract_combined_features(image) # Assuming extract_combined_features() is defined
|
| 41 |
+
|
| 42 |
+
if combined_features is not None:
|
| 43 |
+
embeddings.append(combined_features)
|
| 44 |
+
|
| 45 |
+
frame_idx += 1
|
| 46 |
+
|
| 47 |
+
cap.release()
|
| 48 |
+
return embeddings
|
| 49 |
+
|
| 50 |
+
def extract_combined_features(image):
|
| 51 |
+
# Example: Combine features from FaceNet and CLIP (code for this is assumed to be defined already)
|
| 52 |
+
facenet_features = extract_facenet_features(image)
|
| 53 |
+
clip_features = extract_clip_features(image)
|
| 54 |
+
|
| 55 |
+
if facenet_features is None:
|
| 56 |
+
return None
|
| 57 |
+
|
| 58 |
+
# Combine (concatenate) the features from FaceNet and CLIP
|
| 59 |
+
combined_features = np.concatenate((facenet_features, clip_features))
|
| 60 |
+
return combined_features
|
| 61 |
+
|
| 62 |
+
def extract_facenet_features(image):
|
| 63 |
+
# Example function for FaceNet feature extraction
|
| 64 |
+
pass
|
| 65 |
+
|
| 66 |
+
def extract_clip_features(image):
|
| 67 |
+
# Example function for CLIP feature extraction
|
| 68 |
+
pass
|
| 69 |
+
|
| 70 |
+
def predict_video(video_path):
|
| 71 |
+
embeddings = extract_faces_from_video(video_path, time_interval_sec=10)
|
| 72 |
+
|
| 73 |
+
if not embeddings:
|
| 74 |
+
print("[WARN] No faces found in video.")
|
| 75 |
+
return
|
| 76 |
+
|
| 77 |
+
# Predict using the classifier
|
| 78 |
+
preds = clf.predict(embeddings)
|
| 79 |
+
|
| 80 |
+
# Majority voting for final prediction
|
| 81 |
+
final_pred = np.bincount(preds).argmax() # Most frequent label
|
| 82 |
+
print(f"\nπ§ Final Video Prediction: {label_map[final_pred]} ({len(preds)} frame(s) used)")
|
| 83 |
+
|
| 84 |
+
if __name__ == "__main__":
|
| 85 |
+
import sys
|
| 86 |
+
if len(sys.argv) != 2:
|
| 87 |
+
print("Usage: python scripts/predict_video.py <video_path>")
|
| 88 |
+
sys.exit(1)
|
| 89 |
+
|
| 90 |
+
# Run the video prediction function
|
| 91 |
+
predict_video(sys.argv[1])
|
scripts/random_forest.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
from sklearn.ensemble import RandomForestClassifier
|
| 4 |
+
from sklearn.metrics import classification_report
|
| 5 |
+
import joblib
|
| 6 |
+
|
| 7 |
+
# Load pre-extracted features and labels
|
| 8 |
+
print("π¦ Loading pre-extracted features and labels...")
|
| 9 |
+
|
| 10 |
+
# Load the features (X) and labels (y)
|
| 11 |
+
X = np.load("features/embeddings.npy")
|
| 12 |
+
y = np.load("features/labels.npy")
|
| 13 |
+
|
| 14 |
+
print(f"β
Loaded {len(X)} samples with {X.shape[1]} features each.")
|
| 15 |
+
|
| 16 |
+
# Split into training and testing sets
|
| 17 |
+
from sklearn.model_selection import train_test_split
|
| 18 |
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
| 19 |
+
|
| 20 |
+
# Initialize and train RandomForestClassifier
|
| 21 |
+
print("π§ Training RandomForestClassifier...")
|
| 22 |
+
rf = RandomForestClassifier(n_estimators=100, random_state=42)
|
| 23 |
+
rf.fit(X_train, y_train)
|
| 24 |
+
|
| 25 |
+
# Evaluate the model
|
| 26 |
+
print("\nπ Evaluation Report:")
|
| 27 |
+
y_pred = rf.predict(X_test)
|
| 28 |
+
print(classification_report(y_test, y_pred, target_names=["real", "deepfake", "ai_gen"]))
|
| 29 |
+
|
| 30 |
+
# Save the trained model
|
| 31 |
+
os.makedirs("model", exist_ok=True)
|
| 32 |
+
joblib.dump(rf, "model/random_forest.pkl")
|
| 33 |
+
|
| 34 |
+
print("\nβ
Model trained and saved to model/random_forest.pkl")
|
scripts/stacking_model.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
from sklearn.ensemble import RandomForestClassifier, StackingClassifier
|
| 4 |
+
from sklearn.svm import SVC
|
| 5 |
+
from xgboost import XGBClassifier
|
| 6 |
+
from sklearn.linear_model import LogisticRegression
|
| 7 |
+
from sklearn.metrics import classification_report
|
| 8 |
+
from sklearn.model_selection import train_test_split
|
| 9 |
+
import joblib
|
| 10 |
+
|
| 11 |
+
# Load the pre-extracted features and labels
|
| 12 |
+
print("π¦ Loading pre-extracted features and labels...")
|
| 13 |
+
|
| 14 |
+
# Load the features (X) and labels (y)
|
| 15 |
+
X = np.load("features/embeddings.npy")
|
| 16 |
+
y = np.load("features/labels.npy")
|
| 17 |
+
|
| 18 |
+
print(f"β
Loaded {len(X)} samples with {X.shape[1]} features each.")
|
| 19 |
+
|
| 20 |
+
# Split into training and testing sets
|
| 21 |
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
| 22 |
+
|
| 23 |
+
# Initialize the base models
|
| 24 |
+
rf = RandomForestClassifier(n_estimators=100, random_state=42)
|
| 25 |
+
svm = SVC(probability=True, kernel='linear') # SVM with probability for soft voting
|
| 26 |
+
xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss')
|
| 27 |
+
|
| 28 |
+
# Create the meta-model (Logistic Regression)
|
| 29 |
+
meta_model = LogisticRegression()
|
| 30 |
+
|
| 31 |
+
# Create the Stacking Classifier
|
| 32 |
+
stacking_model = StackingClassifier(estimators=[('rf', rf), ('svm', svm), ('xgb', xgb)], final_estimator=meta_model)
|
| 33 |
+
|
| 34 |
+
# Train the stacking model
|
| 35 |
+
print("π§ Training the stacking classifier...")
|
| 36 |
+
stacking_model.fit(X_train, y_train)
|
| 37 |
+
|
| 38 |
+
# Evaluate the model
|
| 39 |
+
print("\nπ Evaluation Report:")
|
| 40 |
+
y_pred = stacking_model.predict(X_test)
|
| 41 |
+
print(classification_report(y_test, y_pred, target_names=["real", "deepfake", "ai_gen"]))
|
| 42 |
+
|
| 43 |
+
# Save the trained stacking model
|
| 44 |
+
os.makedirs("model", exist_ok=True)
|
| 45 |
+
joblib.dump(stacking_model, "model/stacking_model.pkl")
|
| 46 |
+
|
| 47 |
+
print("\nβ
Stacking model trained and saved to model/stacking_model.pkl")
|
scripts/svm.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
from sklearn.svm import SVC
|
| 4 |
+
from sklearn.metrics import classification_report
|
| 5 |
+
import joblib
|
| 6 |
+
|
| 7 |
+
# Load pre-extracted features and labels
|
| 8 |
+
print("π¦ Loading pre-extracted features and labels...")
|
| 9 |
+
|
| 10 |
+
# Load the features (X) and labels (y)
|
| 11 |
+
X = np.load("features/embeddings.npy")
|
| 12 |
+
y = np.load("features/labels.npy")
|
| 13 |
+
|
| 14 |
+
print(f"β
Loaded {len(X)} samples with {X.shape[1]} features each.")
|
| 15 |
+
|
| 16 |
+
# Split into training and testing sets
|
| 17 |
+
from sklearn.model_selection import train_test_split
|
| 18 |
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
| 19 |
+
|
| 20 |
+
# Initialize and train SVM Classifier
|
| 21 |
+
print("π§ Training SVM Classifier...")
|
| 22 |
+
svm = SVC(probability=True, kernel='linear') # Using probability=True for soft voting
|
| 23 |
+
svm.fit(X_train, y_train)
|
| 24 |
+
|
| 25 |
+
# Evaluate the model
|
| 26 |
+
print("\nπ Evaluation Report:")
|
| 27 |
+
y_pred = svm.predict(X_test)
|
| 28 |
+
print(classification_report(y_test, y_pred, target_names=["real", "deepfake", "ai_gen"]))
|
| 29 |
+
|
| 30 |
+
# Save the trained model
|
| 31 |
+
os.makedirs("model", exist_ok=True)
|
| 32 |
+
joblib.dump(svm, "model/svm.pkl")
|
| 33 |
+
|
| 34 |
+
print("\nβ
Model trained and saved to model/svm.pkl")
|
scripts/xgboost.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
from xgboost import XGBClassifier
|
| 4 |
+
from sklearn.metrics import classification_report
|
| 5 |
+
import joblib
|
| 6 |
+
|
| 7 |
+
# Load pre-extracted features and labels
|
| 8 |
+
print("π¦ Loading pre-extracted features and labels...")
|
| 9 |
+
|
| 10 |
+
# Load the features (X) and labels (y)
|
| 11 |
+
X = np.load("features/embeddings.npy")
|
| 12 |
+
y = np.load("features/labels.npy")
|
| 13 |
+
|
| 14 |
+
print(f"β
Loaded {len(X)} samples with {X.shape[1]} features each.")
|
| 15 |
+
|
| 16 |
+
# Split into training and testing sets
|
| 17 |
+
from sklearn.model_selection import train_test_split
|
| 18 |
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
| 19 |
+
|
| 20 |
+
# Initialize and train XGBoost Classifier
|
| 21 |
+
print("π§ Training XGBoost Classifier...")
|
| 22 |
+
xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss')
|
| 23 |
+
xgb.fit(X_train, y_train)
|
| 24 |
+
|
| 25 |
+
# Evaluate the model
|
| 26 |
+
print("\nπ Evaluation Report:")
|
| 27 |
+
y_pred = xgb.predict(X_test)
|
| 28 |
+
print(classification_report(y_test, y_pred, target_names=["real", "deepfake", "ai_gen"]))
|
| 29 |
+
|
| 30 |
+
# Save the trained model
|
| 31 |
+
os.makedirs("model", exist_ok=True)
|
| 32 |
+
joblib.dump(xgb, "model/xgboost.pkl")
|
| 33 |
+
|
| 34 |
+
print("\nβ
Model trained and saved to model/xgboost.pkl")
|
space.yaml
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# space.yaml
|
| 2 |
+
sdk: docker
|
| 3 |
+
app_port: 7860
|