rijal028 commited on
Commit
d5ed908
·
verified ·
1 Parent(s): f448cd3

Initial release: Unified Dual-Expert ViT balanced AI detector

Browse files
README.md ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language:
3
+ - en
4
+ - id
5
+ license: apache-2.0
6
+ tags:
7
+ - vision
8
+ - image-classification
9
+ - deepfake-detection
10
+ - ai-generated
11
+ - defactify
12
+ - dual-expert
13
+ pipeline_tag: image-classification
14
+ ---
15
+
16
+ # Dual-Expert Vision Transformer (Defactify Balanced)
17
+
18
+ Arsitektur ensemble spesialis untuk memecahkan classifier bias pada deteksi gambar AI modern.
19
+ - **Pakar Real (Guardian):** Mencegah false positive pada foto asli (Weight [3.0, 1.0]).
20
+ - **Pakar AI (Hunter):** Menangkap artefak generator difusi modern (Weight [1.0, 3.0]).
21
+
22
+ ## Benchmark Evaluasi (500 Sampel Defactify Test)
23
+ - Balanced Accuracy: 79.00%
24
+ - Macro F1: 79.00%
25
+ - Recall Real: 80.00%
26
+ - Recall AI: 78.00%
27
+ - Latency: ~6.57 ms (Concurrent CUDA Streams)
config.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "dual_expert_vision_transformer",
3
+ "base_architecture": "google/vit-base-patch16-224",
4
+ "num_classes": 2,
5
+ "id2label": {
6
+ "0": "Real Camera",
7
+ "1": "AI-Generated"
8
+ },
9
+ "label2id": {
10
+ "Real Camera": 0,
11
+ "AI-Generated": 1
12
+ },
13
+ "calibration": {
14
+ "method": "Linear Weighted Consensus",
15
+ "w_hunter": 0.6,
16
+ "w_guardian": 0.4,
17
+ "threshold": 0.46
18
+ },
19
+ "metrics_benchmark": {
20
+ "dataset": "Defactify Balanced (500 Test)",
21
+ "accuracy": 0.79,
22
+ "macro_f1": 0.79,
23
+ "recall_real": 0.8,
24
+ "recall_ai": 0.78,
25
+ "latency_ms": 6.57
26
+ }
27
+ }
guardian/config.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "ViTForImageClassification"
4
+ ],
5
+ "attention_probs_dropout_prob": 0.0,
6
+ "dtype": "float32",
7
+ "encoder_stride": 16,
8
+ "hidden_act": "gelu",
9
+ "hidden_dropout_prob": 0.0,
10
+ "hidden_size": 768,
11
+ "image_size": 224,
12
+ "initializer_range": 0.02,
13
+ "intermediate_size": 3072,
14
+ "layer_norm_eps": 1e-12,
15
+ "model_type": "vit",
16
+ "num_attention_heads": 12,
17
+ "num_channels": 3,
18
+ "num_hidden_layers": 12,
19
+ "patch_size": 16,
20
+ "pooler_act": "tanh",
21
+ "pooler_output_size": 768,
22
+ "qkv_bias": true,
23
+ "transformers_version": "5.0.0"
24
+ }
guardian/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:697950f2f90f29120db62f55dbfce8ef801b28adff7ea6e05b5528886b5a5945
3
+ size 343223968
hunter/config.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "ViTForImageClassification"
4
+ ],
5
+ "attention_probs_dropout_prob": 0.0,
6
+ "dtype": "float32",
7
+ "encoder_stride": 16,
8
+ "hidden_act": "gelu",
9
+ "hidden_dropout_prob": 0.0,
10
+ "hidden_size": 768,
11
+ "image_size": 224,
12
+ "initializer_range": 0.02,
13
+ "intermediate_size": 3072,
14
+ "layer_norm_eps": 1e-12,
15
+ "model_type": "vit",
16
+ "num_attention_heads": 12,
17
+ "num_channels": 3,
18
+ "num_hidden_layers": 12,
19
+ "patch_size": 16,
20
+ "pooler_act": "tanh",
21
+ "pooler_output_size": 768,
22
+ "qkv_bias": true,
23
+ "transformers_version": "5.0.0"
24
+ }
hunter/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e25d011f8ce70594c1a6577fdbbb735592c671008293e816d5e909b86dcbdba4
3
+ size 343223968
pipeline.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import torch
4
+ import torch.nn as nn
5
+ from transformers import AutoImageProcessor, AutoModelForImageClassification
6
+ from PIL import Image
7
+
8
+ class DualExpertDetector(nn.Module):
9
+ def __init__(self, model_dir: str):
10
+ super().__init__()
11
+ self.processor = AutoImageProcessor.from_pretrained(model_dir)
12
+ self.guardian = AutoModelForImageClassification.from_pretrained(os.path.join(model_dir, 'guardian'))
13
+ self.hunter = AutoModelForImageClassification.from_pretrained(os.path.join(model_dir, 'hunter'))
14
+ with open(os.path.join(model_dir, 'config.json'), 'r') as f:
15
+ cfg = json.load(f)['calibration']
16
+ self.w_hunter = cfg['w_hunter']
17
+ self.w_guardian = cfg['w_guardian']
18
+ self.threshold = cfg['threshold']
19
+
20
+ def predict(self, image: Image.Image, device: str = 'cuda'):
21
+ self.to(device)
22
+ self.eval()
23
+ inputs = self.processor(images=image.convert('RGB'), return_tensors='pt').to(device)
24
+ with torch.no_grad():
25
+ p_g = torch.softmax(self.guardian(**inputs).logits, dim=-1)[0, 1].item()
26
+ p_h = torch.softmax(self.hunter(**inputs).logits, dim=-1)[0, 1].item()
27
+ combined = (self.w_hunter * p_h) + (self.w_guardian * p_g)
28
+ is_ai = combined >= self.threshold
29
+ return {
30
+ 'verdict': 'AI-Generated' if is_ai else 'Real Camera',
31
+ 'is_ai': bool(is_ai),
32
+ 'confidence_ai': round(combined * 100, 2),
33
+ 'confidence_real': round((1.0 - combined) * 100, 2),
34
+ 'raw_scores': {'hunter_ai': round(p_h * 100, 2), 'guardian_ai': round(p_g * 100, 2)}
35
+ }
preprocessor_config.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "do_convert_rgb": null,
3
+ "do_normalize": true,
4
+ "do_rescale": true,
5
+ "do_resize": true,
6
+ "image_mean": [
7
+ 0.5,
8
+ 0.5,
9
+ 0.5
10
+ ],
11
+ "image_processor_type": "ViTImageProcessor",
12
+ "image_std": [
13
+ 0.5,
14
+ 0.5,
15
+ 0.5
16
+ ],
17
+ "resample": 2,
18
+ "rescale_factor": 0.00392156862745098,
19
+ "size": {
20
+ "height": 224,
21
+ "width": 224
22
+ }
23
+ }