adarshcod30 commited on
Commit
534b24f
·
verified ·
1 Parent(s): ca25b15

v1: two-backbone ensemble (ResNet50+VGG16), calibrated, TTA-evaluated

Browse files
Files changed (6) hide show
  1. .gitattributes +1 -0
  2. README.md +79 -0
  3. config.json +41 -0
  4. evaluation_report.json +305 -0
  5. model.keras +3 -0
  6. serving.json +44 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ model.keras filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ tags: [deepfake-detection, image-classification, forensics, tensorflow, keras]
4
+ library_name: keras
5
+ pipeline_tag: image-classification
6
+ ---
7
+
8
+ # OpenForensics Deepfake Detector (v1)
9
+
10
+ A multi-backbone CNN ensemble that classifies face crops as **Real** or
11
+ **Fake**. Backbones: resnet50, vgg16. Their pooled embeddings are
12
+ concatenated and read by a shared classifier head.
13
+
14
+ ## Output
15
+
16
+ A single sigmoid: **P(Real)**. Fake is `1 - p`.
17
+
18
+ The published threshold is **0.500** — a neutral default, **not** a fitted operating point. A temperature of **1.099** was fitted on validation and is applied.
19
+
20
+ Thresholds fitted on the validation split do not transfer to the test split for this dataset (see Limitations). Pick your own operating point from `threshold_sweep` in the evaluation report, on data resembling your deployment.
21
+
22
+ ## Test metrics
23
+
24
+ | Metric | Value |
25
+ |---|---|
26
+ | Accuracy | 0.8860 |
27
+ | ROC-AUC | 0.9421 |
28
+ | PR-AUC | 0.9527 |
29
+ | Real images called fake | 175 (17.5%) |
30
+
31
+ Measured on a held-out test split with horizontal-flip test-time
32
+ augmentation. The split is content-hash deduplicated against train and
33
+ validation, so no image appears in more than one split.
34
+
35
+ ## Input
36
+
37
+ Resize to 224x224, scale to `[0, 1]`, shape `(N, 224, 224, 3)` float32.
38
+ Per-backbone normalisation happens **inside** the model — do not apply
39
+ `preprocess_input` yourself.
40
+
41
+ ```python
42
+ from huggingface_hub import snapshot_download
43
+ import tensorflow as tf, numpy as np, json
44
+ from PIL import Image
45
+
46
+ path = snapshot_download("adarshcod30/openforensics-ensemble")
47
+ model = tf.keras.models.load_model(f"{path}/model.keras", compile=False)
48
+ card = json.load(open(f"{path}/serving.json"))
49
+
50
+ img = Image.open("face.jpg").convert("RGB").resize((224, 224))
51
+ x = np.asarray(img, dtype="float32")[None] / 255.0
52
+ p = float(model.predict(x)[0, 0])
53
+ print("Real" if p >= card["decision"]["threshold"] else "Fake", p)
54
+ ```
55
+
56
+ Loading needs the `PreprocessLayer` custom layer from
57
+ [the repo](https://github.com/adarshcod30/OpenForensics), or pass it via
58
+ `custom_objects`.
59
+
60
+ ## Training data
61
+
62
+ The face-cropped OpenForensics distribution (190,334 images at 256x256).
63
+ Training used light augmentation only: horizontal flip, small brightness and contrast jitter. No corruption-matched augmentation.
64
+
65
+ ## Limitations
66
+
67
+ - Trained on **face crops**. Behaviour on full scenes or non-face images is
68
+ undefined.
69
+ - A score near the threshold is not evidence. Treat the margin as part of
70
+ the output.
71
+ - Performance degrades on manipulation methods absent from OpenForensics.
72
+ - Research and educational use. Not a forensic authority.
73
+ - **Validation does not predict test performance on this dataset.** Recall on genuine images at threshold 0.5 is 0.984 on validation but 0.825 on test. The 10th percentile of scores on genuine images is 0.975 on validation and 0.138 on test — a subset of test images is confidently misread rather than the whole distribution shifting. The test split carries degradations (desaturation, noise, blocking, blur) that train and validation do not. Expect calibration to drift on degraded inputs.
74
+
75
+ ## Citation
76
+
77
+ > Trung-Nghia Le, Huy H. Nguyen, Junichi Yamagishi, Isao Echizen,
78
+ > "OpenForensics: Large-Scale Challenging Dataset For Multi-Face Forgery
79
+ > Detection And Segmentation In-The-Wild", ICCV 2021.
config.json ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "v1",
3
+ "out_dir": "/Users/adarsh/Desktop/Projects/OpenForensics/runs",
4
+ "data": {
5
+ "base_dir": "/Users/adarsh/Desktop/Projects/OpenForensics/Dataset",
6
+ "train_per_class": 10000,
7
+ "val_per_class": 3000,
8
+ "test_per_class": 1000,
9
+ "batch_size": 32,
10
+ "seed": 12345,
11
+ "img_size": [
12
+ 224,
13
+ 224
14
+ ],
15
+ "corruption_prob": 0.0,
16
+ "max_corruptions": 0
17
+ },
18
+ "model": {
19
+ "backbones": [
20
+ "resnet50",
21
+ "vgg16"
22
+ ],
23
+ "head_units": 256,
24
+ "dropout_branch": 0.4,
25
+ "dropout_merge": 0.4,
26
+ "dropout_final": 0.3
27
+ },
28
+ "train": {
29
+ "epochs": 20,
30
+ "lr": 0.0002,
31
+ "finetune_epochs": 10,
32
+ "finetune_lr": 1e-05,
33
+ "unfreeze_last": 50,
34
+ "freeze_batchnorm": true,
35
+ "monitor": "val_auc",
36
+ "monitor_mode": "max",
37
+ "early_stop_patience": 7,
38
+ "reduce_lr_patience": 3
39
+ },
40
+ "provenance": "Legacy run. Weights predate this package and were trained by the original two-stage pipeline; this config records the evaluation settings, not a reproducible training recipe."
41
+ }
evaluation_report.json ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "/Users/adarsh/Desktop/Projects/OpenForensics/runs/exp1_finetune/best_model_finetuned.keras",
3
+ "threshold_sweep": [
4
+ {
5
+ "threshold": 0.05,
6
+ "accuracy": 0.853,
7
+ "real_called_fake": 77,
8
+ "fake_called_real": 217
9
+ },
10
+ {
11
+ "threshold": 0.1,
12
+ "accuracy": 0.8795,
13
+ "real_called_fake": 87,
14
+ "fake_called_real": 154
15
+ },
16
+ {
17
+ "threshold": 0.15,
18
+ "accuracy": 0.8885,
19
+ "real_called_fake": 103,
20
+ "fake_called_real": 120
21
+ },
22
+ {
23
+ "threshold": 0.2,
24
+ "accuracy": 0.8915,
25
+ "real_called_fake": 113,
26
+ "fake_called_real": 104
27
+ },
28
+ {
29
+ "threshold": 0.25,
30
+ "accuracy": 0.8925,
31
+ "real_called_fake": 124,
32
+ "fake_called_real": 91
33
+ },
34
+ {
35
+ "threshold": 0.3,
36
+ "accuracy": 0.894,
37
+ "real_called_fake": 132,
38
+ "fake_called_real": 80
39
+ },
40
+ {
41
+ "threshold": 0.35,
42
+ "accuracy": 0.895,
43
+ "real_called_fake": 139,
44
+ "fake_called_real": 71
45
+ },
46
+ {
47
+ "threshold": 0.4,
48
+ "accuracy": 0.8945,
49
+ "real_called_fake": 151,
50
+ "fake_called_real": 60
51
+ },
52
+ {
53
+ "threshold": 0.45,
54
+ "accuracy": 0.8905,
55
+ "real_called_fake": 164,
56
+ "fake_called_real": 55
57
+ },
58
+ {
59
+ "threshold": 0.5,
60
+ "accuracy": 0.886,
61
+ "real_called_fake": 175,
62
+ "fake_called_real": 53
63
+ },
64
+ {
65
+ "threshold": 0.55,
66
+ "accuracy": 0.8825,
67
+ "real_called_fake": 188,
68
+ "fake_called_real": 47
69
+ },
70
+ {
71
+ "threshold": 0.6,
72
+ "accuracy": 0.88,
73
+ "real_called_fake": 198,
74
+ "fake_called_real": 42
75
+ },
76
+ {
77
+ "threshold": 0.65,
78
+ "accuracy": 0.8755,
79
+ "real_called_fake": 210,
80
+ "fake_called_real": 39
81
+ },
82
+ {
83
+ "threshold": 0.7,
84
+ "accuracy": 0.8735,
85
+ "real_called_fake": 219,
86
+ "fake_called_real": 34
87
+ },
88
+ {
89
+ "threshold": 0.75,
90
+ "accuracy": 0.8675,
91
+ "real_called_fake": 231,
92
+ "fake_called_real": 34
93
+ },
94
+ {
95
+ "threshold": 0.8,
96
+ "accuracy": 0.8635,
97
+ "real_called_fake": 243,
98
+ "fake_called_real": 30
99
+ },
100
+ {
101
+ "threshold": 0.85,
102
+ "accuracy": 0.8515,
103
+ "real_called_fake": 273,
104
+ "fake_called_real": 24
105
+ },
106
+ {
107
+ "threshold": 0.9,
108
+ "accuracy": 0.836,
109
+ "real_called_fake": 309,
110
+ "fake_called_real": 19
111
+ },
112
+ {
113
+ "threshold": 0.95,
114
+ "accuracy": 0.807,
115
+ "real_called_fake": 377,
116
+ "fake_called_real": 9
117
+ }
118
+ ],
119
+ "manifest_digest": "599c942765ed9553",
120
+ "tta": true,
121
+ "calibration": {
122
+ "temperature": 1.0986092864209605,
123
+ "val_ece_raw": 0.005462571293115611,
124
+ "val_ece_calibrated": 0.002969743043184234
125
+ },
126
+ "operating_point": {
127
+ "criterion": "fixed",
128
+ "threshold": 0.5,
129
+ "rationale": "Thresholds fitted on validation do not transfer to this test split. At 0.5 recall on Real is 0.984 on validation but 0.825 on test; at 0.90 the gap widens to 0.951 vs 0.691. The 10th percentile of scores on genuine images is 0.975 on validation and 0.138 on test, so a subset of test images is confidently misread rather than the whole distribution shifting. 0.5 is published as a neutral default and the full threshold sweep ships alongside so callers can pick their own operating point on data resembling their deployment."
130
+ },
131
+ "test_at_0.5": {
132
+ "threshold": 0.5,
133
+ "accuracy": 0.886,
134
+ "roc_auc": 0.94214,
135
+ "pr_auc": 0.9527261743419954,
136
+ "confusion_matrix": [
137
+ [
138
+ 947,
139
+ 53
140
+ ],
141
+ [
142
+ 175,
143
+ 825
144
+ ]
145
+ ],
146
+ "real_called_fake": 175,
147
+ "fake_called_real": 53,
148
+ "false_accusation_rate": 0.175,
149
+ "ece": 0.05536929062008858,
150
+ "n": 2000,
151
+ "fake_recall": 0.947,
152
+ "real_recall": 0.825
153
+ },
154
+ "test_at_threshold": {
155
+ "threshold": 0.5,
156
+ "accuracy": 0.886,
157
+ "roc_auc": 0.94214,
158
+ "pr_auc": 0.9527261743419954,
159
+ "confusion_matrix": [
160
+ [
161
+ 947,
162
+ 53
163
+ ],
164
+ [
165
+ 175,
166
+ 825
167
+ ]
168
+ ],
169
+ "real_called_fake": 175,
170
+ "fake_called_real": 53,
171
+ "false_accusation_rate": 0.175,
172
+ "ece": 0.05536929062008858,
173
+ "n": 2000,
174
+ "fake_recall": 0.947,
175
+ "real_recall": 0.825
176
+ },
177
+ "risk_coverage": [
178
+ {
179
+ "coverage": 1.0,
180
+ "n": 2000,
181
+ "accuracy": 0.886,
182
+ "min_confidence": 0.5004317760467529
183
+ },
184
+ {
185
+ "coverage": 0.953,
186
+ "n": 1905,
187
+ "accuracy": 0.90498687664042,
188
+ "min_confidence": 0.6455079317092896
189
+ },
190
+ {
191
+ "coverage": 0.905,
192
+ "n": 1810,
193
+ "accuracy": 0.9187845303867404,
194
+ "min_confidence": 0.7839853763580322
195
+ },
196
+ {
197
+ "coverage": 0.858,
198
+ "n": 1715,
199
+ "accuracy": 0.9276967930029154,
200
+ "min_confidence": 0.8622756600379944
201
+ },
202
+ {
203
+ "coverage": 0.811,
204
+ "n": 1621,
205
+ "accuracy": 0.9358420727945712,
206
+ "min_confidence": 0.9077473878860474
207
+ },
208
+ {
209
+ "coverage": 0.763,
210
+ "n": 1526,
211
+ "accuracy": 0.9403669724770642,
212
+ "min_confidence": 0.9426376223564148
213
+ },
214
+ {
215
+ "coverage": 0.716,
216
+ "n": 1431,
217
+ "accuracy": 0.944095038434661,
218
+ "min_confidence": 0.9612756371498108
219
+ },
220
+ {
221
+ "coverage": 0.668,
222
+ "n": 1336,
223
+ "accuracy": 0.9505988023952096,
224
+ "min_confidence": 0.9726122617721558
225
+ },
226
+ {
227
+ "coverage": 0.621,
228
+ "n": 1242,
229
+ "accuracy": 0.9565217391304348,
230
+ "min_confidence": 0.9810938835144043
231
+ },
232
+ {
233
+ "coverage": 0.574,
234
+ "n": 1147,
235
+ "accuracy": 0.95640802092415,
236
+ "min_confidence": 0.9872699975967407
237
+ },
238
+ {
239
+ "coverage": 0.526,
240
+ "n": 1052,
241
+ "accuracy": 0.9562737642585551,
242
+ "min_confidence": 0.9904727339744568
243
+ },
244
+ {
245
+ "coverage": 0.479,
246
+ "n": 957,
247
+ "accuracy": 0.955067920585162,
248
+ "min_confidence": 0.9927610158920288
249
+ },
250
+ {
251
+ "coverage": 0.432,
252
+ "n": 863,
253
+ "accuracy": 0.9559675550405562,
254
+ "min_confidence": 0.9945045709609985
255
+ },
256
+ {
257
+ "coverage": 0.384,
258
+ "n": 768,
259
+ "accuracy": 0.9557291666666666,
260
+ "min_confidence": 0.9956018924713135
261
+ },
262
+ {
263
+ "coverage": 0.337,
264
+ "n": 673,
265
+ "accuracy": 0.9554234769687965,
266
+ "min_confidence": 0.996526300907135
267
+ },
268
+ {
269
+ "coverage": 0.289,
270
+ "n": 578,
271
+ "accuracy": 0.9532871972318339,
272
+ "min_confidence": 0.9974053502082825
273
+ },
274
+ {
275
+ "coverage": 0.242,
276
+ "n": 484,
277
+ "accuracy": 0.9504132231404959,
278
+ "min_confidence": 0.9980881810188293
279
+ },
280
+ {
281
+ "coverage": 0.195,
282
+ "n": 389,
283
+ "accuracy": 0.9408740359897172,
284
+ "min_confidence": 0.9986152648925781
285
+ },
286
+ {
287
+ "coverage": 0.147,
288
+ "n": 294,
289
+ "accuracy": 0.9421768707482994,
290
+ "min_confidence": 0.9990310668945312
291
+ },
292
+ {
293
+ "coverage": 0.1,
294
+ "n": 200,
295
+ "accuracy": 0.945,
296
+ "min_confidence": 0.9993615746498108
297
+ }
298
+ ],
299
+ "distribution_shift": {
300
+ "recall_real_val_at_0.5": 0.9837,
301
+ "recall_real_test_at_0.5": 0.825,
302
+ "p10_real_scores_val": 0.9748,
303
+ "p10_real_scores_test": 0.1375
304
+ }
305
+ }
model.keras ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1bd92b1c334b738ae0e4dfb92dde7a5b77d08b86680ea02ab6d2b2c05272459e
3
+ size 157119017
serving.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "weights": "model.keras",
3
+ "input": {
4
+ "size": [
5
+ 224,
6
+ 224
7
+ ],
8
+ "channels": 3,
9
+ "dtype": "float32",
10
+ "range": [
11
+ 0,
12
+ 1
13
+ ],
14
+ "note": "Resize to size, divide by 255. Per-backbone normalisation happens inside the model."
15
+ },
16
+ "output": {
17
+ "name": "probability_real",
18
+ "range": [
19
+ 0,
20
+ 1
21
+ ],
22
+ "note": "P(image is Real). Fake is 1 - p."
23
+ },
24
+ "params": 39092929,
25
+ "decision": {
26
+ "threshold": 0.5,
27
+ "temperature": 1.0986092864209605,
28
+ "criterion": "fixed",
29
+ "calibrated": true
30
+ },
31
+ "tta": {
32
+ "recommended": true,
33
+ "transform": "horizontal_flip",
34
+ "note": "Average P(Real) over the image and its mirror.",
35
+ "used_in_eval": true
36
+ },
37
+ "test_metrics": {
38
+ "accuracy": 0.886,
39
+ "roc_auc": 0.94214,
40
+ "pr_auc": 0.9527261743419954,
41
+ "real_called_fake": 175,
42
+ "false_accusation_rate": 0.175
43
+ }
44
+ }