--- license: mit tags: [deepfake-detection, image-classification, forensics, tensorflow, keras] library_name: keras pipeline_tag: image-classification --- # OpenForensics Deepfake Detector (v2) A multi-backbone CNN ensemble that classifies face crops as **Real** or **Fake**. Backbones: resnet50, vgg16, efficientnetv2b0. Their pooled embeddings are concatenated and read by a shared classifier head. ## Output A single sigmoid: **P(Real)**. Fake is `1 - p`. Decision threshold **0.362** and temperature **0.876** were fitted on a held-out validation split (target_recall criterion) and are carried in `serving.json`. ## Test metrics | Metric | Value | |---|---| | Accuracy | 0.9480 | | ROC-AUC | 0.9899 | | PR-AUC | 0.9900 | | Real images called fake | 25 (2.5%) | Measured on a held-out test split with horizontal-flip test-time augmentation. The split is content-hash deduplicated against train and validation, so no image appears in more than one split. ## Input Resize to 224x224, scale to `[0, 1]`, shape `(N, 224, 224, 3)` float32. Per-backbone normalisation happens **inside** the model — do not apply `preprocess_input` yourself. ```python from huggingface_hub import snapshot_download import tensorflow as tf, numpy as np, json from PIL import Image path = snapshot_download("adarshcod30/openforensics-ensemble") model = tf.keras.models.load_model(f"{path}/model.keras", compile=False) card = json.load(open(f"{path}/serving.json")) img = Image.open("face.jpg").convert("RGB").resize((224, 224)) x = np.asarray(img, dtype="float32")[None] / 255.0 p = float(model.predict(x)[0, 0]) print("Real" if p >= card["decision"]["threshold"] else "Fake", p) ``` Loading needs the `PreprocessLayer` custom layer from [the repo](https://github.com/adarshcod30/OpenForensics), or pass it via `custom_objects`. ## Training data The face-cropped OpenForensics distribution (190,334 images at 256x256). Training used corruption-matched augmentation — desaturation, colour cast, noise, speckle, blur, JPEG artefacts, pixelation, brightness shift and occlusion — because the test split is measurably more degraded than train. ## Robustness Accuracy with a single degradation family applied to the whole test set, one at a time. | Degradation | Accuracy | ROC-AUC | vs clean | |---|---|---|---| | clean | 0.9405 | 0.9899 | — | | desaturate | 0.9255 | 0.9874 | -0.0150 | | colour_cast | 0.9230 | 0.9873 | -0.0175 | | gaussian_noise | 0.9105 | 0.9827 | -0.0300 | | speckle | 0.8745 | 0.9786 | -0.0660 | | blur | 0.9025 | 0.9798 | -0.0380 | | jpeg_artifact | 0.9260 | 0.9854 | -0.0145 | | pixelate | 0.8725 | 0.9623 | -0.0680 | | brightness_shift | 0.9210 | 0.9865 | -0.0195 | | occlusion | 0.9300 | 0.9856 | -0.0105 | ## Limitations - Trained on **face crops**. Behaviour on full scenes or non-face images is undefined. - A score near the threshold is not evidence. Treat the margin as part of the output. - Performance degrades on manipulation methods absent from OpenForensics. - Research and educational use. Not a forensic authority. - **Validation tracks test closely.** Recall on genuine images at threshold 0.5 is 0.986 on validation and 0.961 on test — a gap of 0.025. The 10th percentile of scores on genuine images is 0.977 and 0.830 respectively, so the operating point fitted on validation transfers. This is a property of the corruption-matched augmentation, not of the benchmark. ## Citation > Trung-Nghia Le, Huy H. Nguyen, Junichi Yamagishi, Isao Echizen, > "OpenForensics: Large-Scale Challenging Dataset For Multi-Face Forgery > Detection And Segmentation In-The-Wild", ICCV 2021.