ash12321 commited on
Commit
fba5b26
·
verified ·
1 Parent(s): f4289c1

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +233 -0
app.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ V15 - Self-Learning Deepfake Detector with Web Search
3
+ """
4
+
5
+ import os
6
+ import json
7
+ import time
8
+ import gradio as gr
9
+ import torch
10
+ import torch.nn as nn
11
+ import torch.optim as optim
12
+ from torch.utils.data import Dataset, DataLoader
13
+ import timm
14
+ from torchvision import transforms
15
+ from PIL import Image
16
+ from safetensors.torch import load_file, save_file
17
+ from huggingface_hub import hf_hub_download
18
+ import numpy as np
19
+ import hashlib
20
+ from datetime import datetime
21
+ import requests
22
+ from io import BytesIO
23
+
24
+ SERPAPI_KEY = os.environ.get("SERPAPI_KEY", "")
25
+ SERPER_KEY = os.environ.get("SERPER_KEY", "")
26
+
27
+ CONFIG = {
28
+ 'model_repo': 'ash12321/deepfake-detector-v15',
29
+ 'data_dir': './data',
30
+ 'feedback_file': './data/feedback.json',
31
+ 'images_dir': './data/images',
32
+ 'checkpoint': './data/v15_model.safetensors',
33
+ 'retrain_threshold': 50,
34
+ 'learning_rate': 5e-6,
35
+ 'batch_size': 8,
36
+ 'epochs': 3,
37
+ }
38
+
39
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
40
+ os.makedirs(CONFIG['data_dir'], exist_ok=True)
41
+ os.makedirs(CONFIG['images_dir'], exist_ok=True)
42
+
43
+ def upload_image_temp(img):
44
+ try:
45
+ buf = BytesIO()
46
+ img.save(buf, format='JPEG', quality=85)
47
+ buf.seek(0)
48
+ r = requests.post('https://litterbox.catbox.moe/resources/internals/api.php',
49
+ files={'reqtype': (None, 'fileupload'), 'time': (None, '1h'),
50
+ 'fileToUpload': ('img.jpg', buf, 'image/jpeg')}, timeout=30)
51
+ if r.status_code == 200 and r.text.startswith('http'):
52
+ return r.text.strip()
53
+ except:
54
+ pass
55
+ return None
56
+
57
+ def serpapi_search(img):
58
+ if not SERPAPI_KEY:
59
+ return {'indicators': 0}
60
+ url = upload_image_temp(img)
61
+ if not url:
62
+ return {'indicators': 0}
63
+ try:
64
+ r = requests.get("https://serpapi.com/search.json",
65
+ params={"engine": "google_reverse_image", "image_url": url, "api_key": SERPAPI_KEY}, timeout=20)
66
+ if r.status_code == 200:
67
+ text = json.dumps(r.json()).lower()
68
+ count = sum(text.count(k) for k in ['deepfake', 'fake', 'ai generated', 'synthetic'])
69
+ return {'indicators': count, 'source': 'serpapi'}
70
+ except:
71
+ pass
72
+ return {'indicators': 0}
73
+
74
+ def serper_search():
75
+ if not SERPER_KEY:
76
+ return {'indicators': 0}
77
+ try:
78
+ r = requests.post("https://google.serper.dev/search",
79
+ headers={'X-API-KEY': SERPER_KEY, 'Content-Type': 'application/json'},
80
+ data=json.dumps({"q": "deepfake AI generated face", "num": 10}), timeout=15)
81
+ if r.status_code == 200:
82
+ count = sum(1 for x in r.json().get('organic', [])
83
+ if any(k in (x.get('title', '') + x.get('snippet', '')).lower()
84
+ for k in ['deepfake', 'fake', 'ai generated']))
85
+ return {'indicators': count, 'source': 'serper'}
86
+ except:
87
+ pass
88
+ return {'indicators': 0}
89
+
90
+ def web_search(img):
91
+ total = 0
92
+ serp = serpapi_search(img)
93
+ total += serp.get('indicators', 0)
94
+ serp2 = serper_search()
95
+ total += serp2.get('indicators', 0)
96
+ return {'total': total}
97
+
98
+ class DeepfakeDetectorV15(nn.Module):
99
+ def __init__(self):
100
+ super().__init__()
101
+ self.backbone = timm.create_model('swin_large_patch4_window7_224', pretrained=False, num_classes=0)
102
+ d = 1536
103
+ self.adapter = nn.Sequential(nn.Linear(d, 512), nn.LayerNorm(512), nn.ReLU(), nn.Dropout(0.1), nn.Linear(512, d))
104
+ self.classifier = nn.Sequential(
105
+ nn.Linear(d, 512), nn.BatchNorm1d(512), nn.GELU(), nn.Dropout(0.3),
106
+ nn.Linear(512, 128), nn.BatchNorm1d(128), nn.GELU(), nn.Dropout(0.15), nn.Linear(128, 1))
107
+
108
+ def forward(self, x):
109
+ f = self.backbone(x)
110
+ return self.classifier(f + 0.1 * self.adapter(f)).squeeze(-1)
111
+
112
+ print("Loading model...")
113
+ model = DeepfakeDetectorV15()
114
+ try:
115
+ if os.path.exists(CONFIG['checkpoint']):
116
+ model.load_state_dict(load_file(CONFIG['checkpoint']))
117
+ else:
118
+ path = hf_hub_download(repo_id=CONFIG['model_repo'], filename="model.safetensors")
119
+ model.load_state_dict(load_file(path), strict=False)
120
+ except:
121
+ path = hf_hub_download(repo_id="ash12321/deepfake-detector-v14", filename="model_3.safetensors")
122
+ model.load_state_dict(load_file(path), strict=False)
123
+
124
+ model = model.to(device).eval()
125
+ print(f"Model ready on {device}")
126
+
127
+ def load_feedback():
128
+ if os.path.exists(CONFIG['feedback_file']):
129
+ with open(CONFIG['feedback_file']) as f:
130
+ return json.load(f)
131
+ return []
132
+
133
+ def save_feedback(data):
134
+ with open(CONFIG['feedback_file'], 'w') as f:
135
+ json.dump(data, f)
136
+
137
+ feedback_data = load_feedback()
138
+ transform = transforms.Compose([transforms.Resize((224, 224)), transforms.ToTensor(),
139
+ transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
140
+ last = {'img': None, 'prob': 0.5, 'web': {}}
141
+
142
+ class FBDataset(Dataset):
143
+ def __init__(self, data):
144
+ self.data = [d for d in data if os.path.exists(d.get('path', ''))]
145
+ def __len__(self):
146
+ return len(self.data)
147
+ def __getitem__(self, i):
148
+ d = self.data[i]
149
+ img = transform(Image.open(d['path']).convert('RGB'))
150
+ return img, torch.tensor(d['label'], dtype=torch.float32)
151
+
152
+ def train_model():
153
+ global model, feedback_data
154
+ samples = [d for d in feedback_data if not d.get('trained')]
155
+ if len(samples) < 5:
156
+ return f"Need at least 5 samples (have {len(samples)})"
157
+ loader = DataLoader(FBDataset(samples), batch_size=CONFIG['batch_size'], shuffle=True)
158
+ for n, p in model.named_parameters():
159
+ p.requires_grad = 'backbone' not in n
160
+ opt = optim.AdamW([p for p in model.parameters() if p.requires_grad], lr=CONFIG['learning_rate'])
161
+ model.train()
162
+ for ep in range(CONFIG['epochs']):
163
+ for imgs, labels in loader:
164
+ imgs, labels = imgs.to(device), labels.to(device)
165
+ opt.zero_grad()
166
+ nn.BCEWithLogitsLoss()(model(imgs), labels).backward()
167
+ opt.step()
168
+ model.eval()
169
+ save_file(model.state_dict(), CONFIG['checkpoint'])
170
+ for d in samples:
171
+ d['trained'] = True
172
+ save_feedback(feedback_data)
173
+ return f"Trained on {len(samples)} samples!"
174
+
175
+ def analyze(image, use_web):
176
+ global last
177
+ if image is None:
178
+ return "Upload an image!", "", ""
179
+ img = Image.fromarray(image) if isinstance(image, np.ndarray) else image
180
+ img = img.convert('RGB')
181
+ last['img'] = img
182
+ inp = transform(img).unsqueeze(0).to(device)
183
+ model.eval()
184
+ with torch.no_grad():
185
+ prob = torch.sigmoid(model(inp)).item()
186
+ web_text = "Web search disabled"
187
+ if use_web:
188
+ web = web_search(img)
189
+ last['web'] = web
190
+ if web['total'] > 0:
191
+ prob = min(prob + web['total'] * 0.03, 0.99)
192
+ web_text = f"Found {web['total']} deepfake indicators online!"
193
+ else:
194
+ web_text = "No deepfake indicators found online"
195
+ last['prob'] = prob
196
+ result = f"## {'🚨 FAKE' if prob > 0.5 else '✅ REAL'}\n**Confidence**: {(prob if prob > 0.5 else 1-prob):.1%}"
197
+ pending = sum(1 for d in feedback_data if not d.get('trained'))
198
+ stats = f"Feedback: {len(feedback_data)} total | {pending} pending"
199
+ return result, web_text, stats
200
+
201
+ def submit(label):
202
+ global feedback_data, last
203
+ if last['img'] is None:
204
+ return "Analyze an image first!"
205
+ h = hashlib.md5(str(time.time()).encode()).hexdigest()[:12]
206
+ path = os.path.join(CONFIG['images_dir'], f"{h}.jpg")
207
+ last['img'].save(path)
208
+ feedback_data.append({'path': path, 'label': 1 if label == "Fake" else 0, 'prob': last['prob'], 'trained': False})
209
+ save_feedback(feedback_data)
210
+ pending = sum(1 for d in feedback_data if not d.get('trained'))
211
+ return f"Saved! ({pending}/{CONFIG['retrain_threshold']})"
212
+
213
+ with gr.Blocks(title="V15 Deepfake Detector") as app:
214
+ gr.Markdown("# 🧠 V15 Self-Learning Deepfake Detector")
215
+ with gr.Row():
216
+ with gr.Column():
217
+ img = gr.Image(type="pil", label="Upload Image")
218
+ web_cb = gr.Checkbox(label="Enable Web Search", value=True)
219
+ btn1 = gr.Button("Analyze", variant="primary")
220
+ gr.Markdown("---")
221
+ radio = gr.Radio(["Real", "Fake"], label="Correct label:")
222
+ btn2 = gr.Button("Submit Feedback")
223
+ btn3 = gr.Button("Train Model")
224
+ with gr.Column():
225
+ out1 = gr.Markdown()
226
+ out2 = gr.Markdown()
227
+ out3 = gr.Markdown()
228
+ out4 = gr.Markdown()
229
+ btn1.click(analyze, [img, web_cb], [out1, out2, out3])
230
+ btn2.click(submit, radio, out4)
231
+ btn3.click(train_model, outputs=out4)
232
+
233
+ app.queue().launch()