deepclip 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deepclip-0.1.0/LICENSE +21 -0
- deepclip-0.1.0/PKG-INFO +36 -0
- deepclip-0.1.0/README.md +7 -0
- deepclip-0.1.0/deepclip/__init__.py +31 -0
- deepclip-0.1.0/deepclip/config/deepclip_b0.yaml +65 -0
- deepclip-0.1.0/deepclip/config/deepclip_b5.yaml +65 -0
- deepclip-0.1.0/deepclip/data/__init__.py +9 -0
- deepclip-0.1.0/deepclip/data/collate.py +104 -0
- deepclip-0.1.0/deepclip/data/dataset.py +67 -0
- deepclip-0.1.0/deepclip/data/handle_imbalance.py +44 -0
- deepclip-0.1.0/deepclip/data/split_data.py +83 -0
- deepclip-0.1.0/deepclip/data/text/__init__.py +16 -0
- deepclip-0.1.0/deepclip/data/text/bpe_simple_vocab_16e6.txt.gz +0 -0
- deepclip-0.1.0/deepclip/data/text/template.py +133 -0
- deepclip-0.1.0/deepclip/data/text/tokenizer.py +146 -0
- deepclip-0.1.0/deepclip/data/transforms.py +76 -0
- deepclip-0.1.0/deepclip/layers/__init__.py +13 -0
- deepclip-0.1.0/deepclip/layers/attention.py +153 -0
- deepclip-0.1.0/deepclip/layers/clip_text.py +61 -0
- deepclip-0.1.0/deepclip/layers/drop.py +35 -0
- deepclip-0.1.0/deepclip/layers/featextractor.py +56 -0
- deepclip-0.1.0/deepclip/layers/global_query.py +65 -0
- deepclip-0.1.0/deepclip/layers/mlp.py +41 -0
- deepclip-0.1.0/deepclip/layers/patch.py +58 -0
- deepclip-0.1.0/deepclip/layers/reducesize.py +45 -0
- deepclip-0.1.0/deepclip/layers/se.py +28 -0
- deepclip-0.1.0/deepclip/layers/transformer.py +266 -0
- deepclip-0.1.0/deepclip/layers/weight_init.py +30 -0
- deepclip-0.1.0/deepclip/layers/window.py +22 -0
- deepclip-0.1.0/deepclip/losses.py +16 -0
- deepclip-0.1.0/deepclip/models/__init__.py +8 -0
- deepclip-0.1.0/deepclip/models/deepclip.py +199 -0
- deepclip-0.1.0/deepclip/models/image_encoder.py +198 -0
- deepclip-0.1.0/deepclip/models/text_encoder.py +125 -0
- deepclip-0.1.0/deepclip/optimizer/__init__.py +1 -0
- deepclip-0.1.0/deepclip/optimizer/build_optimizer.py +29 -0
- deepclip-0.1.0/deepclip/scheduler/__init__.py +1 -0
- deepclip-0.1.0/deepclip/scheduler/fetch_scheduler.py +58 -0
- deepclip-0.1.0/deepclip/utils/__init__.py +4 -0
- deepclip-0.1.0/deepclip/utils/build_metrics.py +62 -0
- deepclip-0.1.0/deepclip/utils/early_stopping.py +24 -0
- deepclip-0.1.0/deepclip/utils/meter.py +26 -0
- deepclip-0.1.0/deepclip/utils/seed.py +23 -0
- deepclip-0.1.0/deepclip.egg-info/PKG-INFO +36 -0
- deepclip-0.1.0/deepclip.egg-info/SOURCES.txt +48 -0
- deepclip-0.1.0/deepclip.egg-info/dependency_links.txt +1 -0
- deepclip-0.1.0/deepclip.egg-info/requires.txt +9 -0
- deepclip-0.1.0/deepclip.egg-info/top_level.txt +1 -0
- deepclip-0.1.0/pyproject.toml +50 -0
- deepclip-0.1.0/setup.cfg +4 -0
deepclip-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 seoyunje
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
deepclip-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepclip
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLIP for deepfakes: face forgery detection aligned with CLIP text embeddings
|
|
5
|
+
Author-email: seoyunje <seoyunje2001@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/SEOYUNJE/DeepClip
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/SEOYUNJE/DeepClip/issues
|
|
9
|
+
Project-URL: Source Code, https://github.com/SEOYUNJE/DeepClip
|
|
10
|
+
Project-URL: Releases, https://github.com/SEOYUNJE/DeepClip/releases
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: torch>=2.4.0
|
|
20
|
+
Requires-Dist: timm>=1.0.0
|
|
21
|
+
Requires-Dist: torchmetrics>=1.4.0
|
|
22
|
+
Requires-Dist: albumentations>=2.0.8
|
|
23
|
+
Requires-Dist: opencv-python>=5.0.0.93
|
|
24
|
+
Requires-Dist: numpy<3.0.0,>=1.23.0
|
|
25
|
+
Requires-Dist: pandas>=2.2.3
|
|
26
|
+
Requires-Dist: scikit-learn>=1.6.1
|
|
27
|
+
Requires-Dist: regex>=2023.0.0
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# DeepClip
|
|
31
|
+
CLIP for deepfakes
|
|
32
|
+
|
|
33
|
+
## Dataset
|
|
34
|
+
|
|
35
|
+
[`preprocess/`](preprocess/README.md) builds the frame-level FaceForensics++ dataset and its forgery annotation
|
|
36
|
+
(face crops, 68 facial landmarks, forgery masks and forgery features) used to train DeepClip.
|
deepclip-0.1.0/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
import logging
|
|
3
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("deepclip") # pyproject.toml
|
|
7
|
+
except PackageNotFoundError: # Not installed (e.g. run from the repository)
|
|
8
|
+
__version__ = "0.0.0"
|
|
9
|
+
|
|
10
|
+
# ------- 로그 및 경고 제어 ---------
|
|
11
|
+
warnings.filterwarnings("ignore") # 일반 경고 숨기기
|
|
12
|
+
logging.getLogger("timm").setLevel(logging.ERROR) # timm 내부 로그 숨기기
|
|
13
|
+
logging.getLogger("torch").setLevel(logging.ERROR) # torch 관련 로그 숨기기
|
|
14
|
+
|
|
15
|
+
from .models import image_encoder, text_encoder, deepclip
|
|
16
|
+
from .models.image_encoder import DeepImageEncoder
|
|
17
|
+
from .models.text_encoder import DeepTextEncoder
|
|
18
|
+
from .models.deepclip import (
|
|
19
|
+
DeepClip,
|
|
20
|
+
deepclip_b0,
|
|
21
|
+
deepclip_b5,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"__version__",
|
|
26
|
+
"DeepImageEncoder",
|
|
27
|
+
"DeepTextEncoder",
|
|
28
|
+
"DeepClip",
|
|
29
|
+
"deepclip_b0",
|
|
30
|
+
"deepclip_b5",
|
|
31
|
+
]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
dataset: "FaceForensics++"
|
|
2
|
+
|
|
3
|
+
model:
|
|
4
|
+
model_name: "tf_efficientnet_b0.ns_jft_in1k"
|
|
5
|
+
img_size: [224,224]
|
|
6
|
+
|
|
7
|
+
# ===== Feature Extraction Blocks =====
|
|
8
|
+
l_block_idx: 1 # Low-level feature stage index
|
|
9
|
+
h_block_idx: 6 # High-level feature stage index
|
|
10
|
+
|
|
11
|
+
# ===== Transformer Dimensions =====
|
|
12
|
+
l_dim: 24 # Dimension for low-block transformer
|
|
13
|
+
h_dim: 256 # Dimension for high-block transformer
|
|
14
|
+
l_depths: [2,2,4,2] # Number of transformer layers (Low)
|
|
15
|
+
h_depths: [4] # Number of transformer layers (High)
|
|
16
|
+
|
|
17
|
+
# ===== Attention Settings =====
|
|
18
|
+
l_windows: [7,7,14,7] # Window Size for Attention (Low)
|
|
19
|
+
h_windows: [7] # Window Size for Attention (High)
|
|
20
|
+
l_heads: [1,2,4,8] # Multi Head Attention (Low)
|
|
21
|
+
h_heads: [4] # Multi Head Attention (High)
|
|
22
|
+
l_ratio: [4,4,4,4] # MLP expansion ratio (Low)
|
|
23
|
+
h_ratio: [4] # MLP expansion ratio (High)
|
|
24
|
+
|
|
25
|
+
# ===== Regularization (Dropout) =====
|
|
26
|
+
l_drop: 0 # Pos dropout, Mlp dropout, Proj dropout (Low)
|
|
27
|
+
h_drop: 0.05 # Pos dropout, Mlp dropout, Proj dropout (High)
|
|
28
|
+
l_attn_drop: 0.05 # Attention Dropout (Low)
|
|
29
|
+
h_attn_drop: 0 # Attention Dropout (High)
|
|
30
|
+
l_drop_path: 0.1 # Stochastic depth rate (Low)
|
|
31
|
+
h_drop_path: 0.05 # Stochastic depth rate (High)
|
|
32
|
+
|
|
33
|
+
# ===== Norm (GCViT + image projection) =====
|
|
34
|
+
image_norm: "rmsnorm" # rmsnorm or layernorm
|
|
35
|
+
|
|
36
|
+
# ===== Text Encoder (frozen) =====
|
|
37
|
+
clip_name: "ViT-B/16"
|
|
38
|
+
embed_dim: 512 # Text embedding dimension of clip_name (checked)
|
|
39
|
+
|
|
40
|
+
text:
|
|
41
|
+
type_sampling_alpha: 1.0 # Type weight = freq^-alpha
|
|
42
|
+
region_sampling_beta: 1.0 # Region weight = region_freq^-beta
|
|
43
|
+
|
|
44
|
+
loss:
|
|
45
|
+
real_text_weight: 1.0 # Weight of the REAL_TEXT column of the loss
|
|
46
|
+
fake_text_weight: 1.0 # Weight of the FAKE caption columns of the loss
|
|
47
|
+
|
|
48
|
+
train:
|
|
49
|
+
epochs: 10
|
|
50
|
+
use_amp: True # Mixed Precision Training
|
|
51
|
+
n_accumulate: 1 # Gradient Accumulation steps
|
|
52
|
+
optimizer: "AdamW"
|
|
53
|
+
scheduler: "CosineAnnealingLR"
|
|
54
|
+
|
|
55
|
+
# DataLoader
|
|
56
|
+
train_bs: 16
|
|
57
|
+
valid_bs: 32
|
|
58
|
+
|
|
59
|
+
# Optimizer (decay / no_decay groups, same lr)
|
|
60
|
+
lr: 2e-4
|
|
61
|
+
weight_decay: 1e-3
|
|
62
|
+
|
|
63
|
+
# Early Stopping (valid_loss)
|
|
64
|
+
patience: 3
|
|
65
|
+
min_delta: 0.001
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
dataset: "FaceForensics++"
|
|
2
|
+
|
|
3
|
+
model:
|
|
4
|
+
model_name: "tf_efficientnet_b5.ns_jft_in1k"
|
|
5
|
+
img_size: [384,384]
|
|
6
|
+
|
|
7
|
+
# ===== Feature Extraction Blocks =====
|
|
8
|
+
l_block_idx: 1 # Low-level feature stage index
|
|
9
|
+
h_block_idx: 6 # High-level feature stage index
|
|
10
|
+
|
|
11
|
+
# ===== Transformer Dimensions =====
|
|
12
|
+
l_dim: 48 # Dimension for low-block transformer
|
|
13
|
+
h_dim: 512 # Dimension for high-block transformer
|
|
14
|
+
l_depths: [2,2,6,2] # Number of transformer layers (Low)
|
|
15
|
+
h_depths: [6] # Number of transformer layers (High)
|
|
16
|
+
|
|
17
|
+
# ===== Attention Settings =====
|
|
18
|
+
l_windows: [12,12,24,12] # Window Size for Attention (Low)
|
|
19
|
+
h_windows: [12] # Window Size for Attention (High)
|
|
20
|
+
l_heads: [2,4,8,16] # Multi Head Attention (Low)
|
|
21
|
+
h_heads: [16] # Multi Head Attention (High)
|
|
22
|
+
l_ratio: [3,3,3,3] # MLP expansion ratio (Low)
|
|
23
|
+
h_ratio: [3] # MLP expansion ratio (High)
|
|
24
|
+
|
|
25
|
+
# ===== Regularization (Dropout) =====
|
|
26
|
+
l_drop: 0.0 # Pos dropout, Mlp dropout, Proj dropout (Low)
|
|
27
|
+
h_drop: 0.1 # Pos dropout, Mlp dropout, Proj dropout (High)
|
|
28
|
+
l_attn_drop: 0.1 # Attention Dropout (Low)
|
|
29
|
+
h_attn_drop: 0.0 # Attention Dropout (High)
|
|
30
|
+
l_drop_path: 0.15 # Stochastic depth rate (Low)
|
|
31
|
+
h_drop_path: 0.1 # Stochastic depth rate (High)
|
|
32
|
+
|
|
33
|
+
# ===== Norm (GCViT + image projection) =====
|
|
34
|
+
image_norm: "rmsnorm" # rmsnorm or layernorm
|
|
35
|
+
|
|
36
|
+
# ===== Text Encoder (frozen) =====
|
|
37
|
+
clip_name: "ViT-L/14"
|
|
38
|
+
embed_dim: 768 # Text embedding dimension of clip_name (checked)
|
|
39
|
+
|
|
40
|
+
text:
|
|
41
|
+
type_sampling_alpha: 1.0 # Type weight = freq^-alpha
|
|
42
|
+
region_sampling_beta: 1.0 # Region weight = region_freq^-beta
|
|
43
|
+
|
|
44
|
+
loss:
|
|
45
|
+
real_text_weight: 1.0 # Weight of the REAL_TEXT column of the loss
|
|
46
|
+
fake_text_weight: 1.0 # Weight of the FAKE caption columns of the loss
|
|
47
|
+
|
|
48
|
+
train:
|
|
49
|
+
epochs: 10
|
|
50
|
+
use_amp: True # Mixed Precision Training
|
|
51
|
+
n_accumulate: 1 # Gradient Accumulation steps
|
|
52
|
+
optimizer: "AdamW"
|
|
53
|
+
scheduler: "CosineAnnealingLR"
|
|
54
|
+
|
|
55
|
+
# DataLoader
|
|
56
|
+
train_bs: 16
|
|
57
|
+
valid_bs: 32
|
|
58
|
+
|
|
59
|
+
# Optimizer (decay / no_decay groups, same lr)
|
|
60
|
+
lr: 5e-5
|
|
61
|
+
weight_decay: 2e-4
|
|
62
|
+
|
|
63
|
+
# Early Stopping (valid_loss)
|
|
64
|
+
patience: 3
|
|
65
|
+
min_delta: 0.001
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .dataset import DeepClipDataset
|
|
2
|
+
from .collate import DeepClipCollate
|
|
3
|
+
from .transforms import (
|
|
4
|
+
get_train_transforms,
|
|
5
|
+
get_valid_transforms,
|
|
6
|
+
get_test_transforms,
|
|
7
|
+
)
|
|
8
|
+
from .handle_imbalance import class_imbalance_handle
|
|
9
|
+
from .split_data import split_data
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import torch
|
|
3
|
+
from torch.utils.data import get_worker_info
|
|
4
|
+
from deepclip.data.text import REAL_TEXT, parse_pair_label, sample_selection, render_caption
|
|
5
|
+
|
|
6
|
+
class DeepClipCollate:
|
|
7
|
+
"""
|
|
8
|
+
Makes the captions of a batch and the targets of the loss.
|
|
9
|
+
|
|
10
|
+
For every image: REAL -> REAL_TEXT, FAKE -> the caption of the pair chosen by sample_selection
|
|
11
|
+
(a new choice at every step). The B captions are reduced to the U unique captions (in order).
|
|
12
|
+
|
|
13
|
+
Returns a dict:
|
|
14
|
+
images (B,3,H,W)
|
|
15
|
+
labels (B,) 0: REAL, 1: FAKE
|
|
16
|
+
text_idx (U,) rows of text_table of the unique captions
|
|
17
|
+
Y (B,U) 1 if caption j is true for image i
|
|
18
|
+
(REAL_TEXT column: image i is REAL, FAKE column: the pair of caption j is on in image i)
|
|
19
|
+
W (B,U) real_text_weight for the REAL_TEXT column, fake_text_weight for the FAKE columns
|
|
20
|
+
pair_targets (B,20) pair_label of every image (all 0 for REAL), target of map and the AP
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
caption_to_idx: dict,
|
|
26
|
+
type_weights: np.ndarray,
|
|
27
|
+
region_weights: np.ndarray,
|
|
28
|
+
real_text_weight: float = 1.0,
|
|
29
|
+
fake_text_weight: float = 1.0,
|
|
30
|
+
seed: int | None = None,
|
|
31
|
+
):
|
|
32
|
+
"""
|
|
33
|
+
Args:
|
|
34
|
+
caption_to_idx: caption -> row of text_table (DeepClip.caption_to_idx).
|
|
35
|
+
type_weights: (4,5) weights of the types (type_weights of the train FAKE frames).
|
|
36
|
+
region_weights: (4,) weights of the regions (region_weights of the train FAKE frames).
|
|
37
|
+
real_text_weight, fake_text_weight: Weights of the REAL_TEXT column and the FAKE columns of the loss.
|
|
38
|
+
seed: Fixed seed for the same captions at every epoch (valid). None for new captions at every epoch (train).
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
self.caption_to_idx = caption_to_idx
|
|
42
|
+
self.type_weights = type_weights
|
|
43
|
+
self.region_weights = region_weights
|
|
44
|
+
self.real_text_weight = real_text_weight
|
|
45
|
+
self.fake_text_weight = fake_text_weight
|
|
46
|
+
self.seed = seed
|
|
47
|
+
self.reset()
|
|
48
|
+
|
|
49
|
+
def reset(self):
|
|
50
|
+
"""Restarts the random captions (with seed: the same captions as the previous epoch when num_workers=0)"""
|
|
51
|
+
self.rng = np.random.default_rng(self.seed if self.seed is not None else np.random.randint(2**31))
|
|
52
|
+
self._worker_id = None
|
|
53
|
+
|
|
54
|
+
def _update_rng(self):
|
|
55
|
+
# Every DataLoader worker gets a copy of this object, so the rng is set again in each worker
|
|
56
|
+
info = get_worker_info()
|
|
57
|
+
|
|
58
|
+
if info is not None and self._worker_id != info.id:
|
|
59
|
+
self._worker_id = info.id
|
|
60
|
+
self.rng = np.random.default_rng([self.seed, info.id] if self.seed is not None else info.seed)
|
|
61
|
+
|
|
62
|
+
def __call__(self, batch):
|
|
63
|
+
self._update_rng()
|
|
64
|
+
|
|
65
|
+
imgs, labels, pair_labels = zip(*batch)
|
|
66
|
+
images = torch.stack(imgs) # (B,3,H,W)
|
|
67
|
+
labels = torch.stack(labels).float() # (B,)
|
|
68
|
+
|
|
69
|
+
lab = np.stack([parse_pair_label(p) for p in pair_labels]) # (B,4,5)
|
|
70
|
+
is_fake = labels.numpy() == 1
|
|
71
|
+
|
|
72
|
+
# ====== Caption of every image ======
|
|
73
|
+
captions, selections = [], []
|
|
74
|
+
for i in range(len(batch)):
|
|
75
|
+
if is_fake[i]:
|
|
76
|
+
sel = sample_selection(lab[i], self.type_weights, self.region_weights, self.rng)
|
|
77
|
+
captions.append(render_caption(sel))
|
|
78
|
+
else:
|
|
79
|
+
sel = np.zeros_like(lab[i])
|
|
80
|
+
captions.append(REAL_TEXT)
|
|
81
|
+
selections.append(sel)
|
|
82
|
+
|
|
83
|
+
# ====== Unique captions (in order) ======
|
|
84
|
+
uniq = list(dict.fromkeys(captions))
|
|
85
|
+
first = [captions.index(c) for c in uniq]
|
|
86
|
+
text_idx = torch.tensor([self.caption_to_idx[c] for c in uniq], dtype=torch.long) # (U,)
|
|
87
|
+
|
|
88
|
+
# ====== Targets Y and weights W ======
|
|
89
|
+
is_real_col = np.array([c == REAL_TEXT for c in uniq]) # (U,)
|
|
90
|
+
sel_cols = np.stack([selections[i] for i in first]) # (U,4,5)
|
|
91
|
+
|
|
92
|
+
# the pair of caption j is on in image i
|
|
93
|
+
subset = ~(sel_cols[None] & ~lab[:, None]).any(axis=(2, 3)) # (B,U)
|
|
94
|
+
Y = np.where(is_real_col[None], ~is_fake[:, None], is_fake[:, None] & subset)
|
|
95
|
+
W = np.where(is_real_col, self.real_text_weight, self.fake_text_weight)
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
"images": images,
|
|
99
|
+
"labels": labels,
|
|
100
|
+
"text_idx": text_idx,
|
|
101
|
+
"Y": torch.from_numpy(Y).float(),
|
|
102
|
+
"W": torch.from_numpy(np.broadcast_to(W, Y.shape).copy()).float(),
|
|
103
|
+
"pair_targets": torch.from_numpy(lab.reshape(len(batch), -1)).long(),
|
|
104
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from typing import List
|
|
3
|
+
import cv2
|
|
4
|
+
import torch
|
|
5
|
+
from torch.utils.data import Dataset
|
|
6
|
+
import albumentations as A
|
|
7
|
+
|
|
8
|
+
class DeepClipDataset(Dataset):
|
|
9
|
+
"""
|
|
10
|
+
Dataset class for DeepClip, based on DeepFakeDataset of DeepGuard.
|
|
11
|
+
Reads image paths, labels and pair labels from a pandas DataFrame, loads images,
|
|
12
|
+
and applies specified transformations. The caption is made by DeepClipCollate.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
meta_df (pd.DataFrame): Dataframe containing image paths, labels and pair labels.
|
|
16
|
+
transforms (A.Compose): Albumentations transformation pipeline.
|
|
17
|
+
img_size (List[int]): [Height, Width] of the image.
|
|
18
|
+
img_col (str): Name of the column containing image file paths.
|
|
19
|
+
label_col (str): Name of the column containing labels (0: REAL, 1: FAKE).
|
|
20
|
+
pair_col (str): Name of the column containing pair labels (20 characters of 0 and 1).
|
|
21
|
+
return_label (bool): Whether to return (image, label, pair_label) or just image.
|
|
22
|
+
"""
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
meta_df: pd.DataFrame,
|
|
26
|
+
img_size: List[int],
|
|
27
|
+
transforms: A.Compose,
|
|
28
|
+
img_col: str = "img_path",
|
|
29
|
+
label_col: str = "label",
|
|
30
|
+
pair_col: str = "pair_label",
|
|
31
|
+
return_label: bool = True,
|
|
32
|
+
):
|
|
33
|
+
self.meta_df = meta_df
|
|
34
|
+
self.img_size = img_size
|
|
35
|
+
self.transforms = transforms(img_size)
|
|
36
|
+
self.img_col = img_col
|
|
37
|
+
self.label_col = label_col
|
|
38
|
+
self.pair_col = pair_col
|
|
39
|
+
self.return_label = return_label
|
|
40
|
+
|
|
41
|
+
def __len__(self):
|
|
42
|
+
return len(self.meta_df)
|
|
43
|
+
|
|
44
|
+
def _load_image(self, path: str):
|
|
45
|
+
img_bgr = cv2.imread(path)
|
|
46
|
+
if img_bgr is None:
|
|
47
|
+
raise FileNotFoundError(f"Cannot read image: {path}")
|
|
48
|
+
|
|
49
|
+
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
|
50
|
+
|
|
51
|
+
return img_rgb
|
|
52
|
+
|
|
53
|
+
def __getitem__(self, idx):
|
|
54
|
+
row = self.meta_df.iloc[idx]
|
|
55
|
+
img_path = str(row[self.img_col])
|
|
56
|
+
|
|
57
|
+
img = self._load_image(img_path)
|
|
58
|
+
img = self.transforms(image=img)['image']
|
|
59
|
+
|
|
60
|
+
if not self.return_label:
|
|
61
|
+
return img
|
|
62
|
+
else:
|
|
63
|
+
label = row[self.label_col]
|
|
64
|
+
return img, torch.tensor(label, dtype=torch.float32), str(row[self.pair_col])
|
|
65
|
+
|
|
66
|
+
def __str__(self):
|
|
67
|
+
return f"DeepClipDataset with {len(self.meta_df)} samples"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
def class_imbalance_handle(
|
|
5
|
+
real_df: pd.DataFrame,
|
|
6
|
+
fake_df: pd.DataFrame,
|
|
7
|
+
k: int,
|
|
8
|
+
seed: int = 2025):
|
|
9
|
+
"""
|
|
10
|
+
Constructs a balanced training dataset by combining real data with a specific slice of fake data.
|
|
11
|
+
|
|
12
|
+
This function implements a sliding-window approach to select synthetic (fake) samples
|
|
13
|
+
to match the count of real samples. It ensures a 1:1 class distribution for
|
|
14
|
+
iterative training or cross-validation scenarios.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
real_df (pd.DataFrame): The original/minority class dataframe.
|
|
18
|
+
fake_df (pd.DataFrame): The synthetic/majority class dataframe to sample from.
|
|
19
|
+
k (int): The fold index used to determine the starting position of the fake data slice.
|
|
20
|
+
seed (int, optional): Random seed for reproducibility during sampling and shuffling. Defaults to 2025.
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
n_real = len(real_df)
|
|
25
|
+
start_idx = k * n_real
|
|
26
|
+
end_idx = start_idx + n_real
|
|
27
|
+
|
|
28
|
+
# Handle wrap-around if the slice exceeds the number of fake samples
|
|
29
|
+
if end_idx > len(fake_df):
|
|
30
|
+
remaining = fake_df[start_idx:]
|
|
31
|
+
n_remaining = len(remaining)
|
|
32
|
+
|
|
33
|
+
# Sample additional fake data from the start to match the required slice
|
|
34
|
+
extra = fake_df[:start_idx].sample(n=n_real - n_remaining, replace=False, random_state=seed)
|
|
35
|
+
fake_slice = pd.concat([remaining, extra], axis=0).reset_index(drop=True)
|
|
36
|
+
|
|
37
|
+
else:
|
|
38
|
+
fake_slice = fake_df[start_idx:end_idx].reset_index(drop=True)
|
|
39
|
+
|
|
40
|
+
# Combine real and fake samples, then shuffle
|
|
41
|
+
train_df = pd.concat([real_df, fake_slice], ignore_index=True)
|
|
42
|
+
train_df = train_df.sample(frac=1, random_state=seed).reset_index(drop=True)
|
|
43
|
+
|
|
44
|
+
return train_df
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from sklearn.model_selection import train_test_split, GroupKFold
|
|
3
|
+
|
|
4
|
+
def split_data(
|
|
5
|
+
meta_df: pd.DataFrame,
|
|
6
|
+
seed: int = 2025,
|
|
7
|
+
label_col: str = 'label',
|
|
8
|
+
ori_vid: str = 'ori_vid',
|
|
9
|
+
dataset: str = 'kodf',
|
|
10
|
+
group_col: str = "pid",
|
|
11
|
+
test_size: float = 0.2,
|
|
12
|
+
balance_val: bool = True,
|
|
13
|
+
debug: bool = False,
|
|
14
|
+
debug_ratio: float = 0.1,
|
|
15
|
+
) :
|
|
16
|
+
"""Splits the dataset into training and validation sets based on original video IDs.
|
|
17
|
+
|
|
18
|
+
This function ensures that all fake videos derived from the same original video
|
|
19
|
+
stay within the same split (Train or Val) to prevent data leakage. It first
|
|
20
|
+
splits the real videos and then maps the corresponding fake videos.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
meta_df: The complete metadata DataFrame containing labels and video IDs.
|
|
24
|
+
seed: Random seed for reproducibility. Defaults to 2025.
|
|
25
|
+
label_col: Name of the column containing labels (0 for REAL, 1 for FAKE).
|
|
26
|
+
ori_vid: Name of the column identifying the original video source.
|
|
27
|
+
test_size: Proportion of the dataset to include in the validation split.
|
|
28
|
+
balance_val: If True, samples the fake validation set to match the
|
|
29
|
+
length of the real validation set.
|
|
30
|
+
debug: If True, downsamples the final datasets for quick testing.
|
|
31
|
+
debug_ratio: The ratio of data to keep when debug mode is enabled.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
tuple[pd.DataFrame, pd.DataFrame]: A tuple containing (train_df, valid_df).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Split real and fake(0: REAL, 1: FAKE)
|
|
38
|
+
|
|
39
|
+
assert meta_df[label_col].dtype in [int, float], "You need to label encoding {'FAKE': 1, 'REAL': 0}"
|
|
40
|
+
|
|
41
|
+
df_real = meta_df[meta_df[label_col] == 0].reset_index(drop=True)
|
|
42
|
+
df_fake = meta_df[meta_df[label_col] == 1].reset_index(drop=True)
|
|
43
|
+
|
|
44
|
+
# Split real Images
|
|
45
|
+
if dataset.lower() != "kodf":
|
|
46
|
+
real_train, real_val = train_test_split(df_real, test_size=test_size, random_state=seed, shuffle=True)
|
|
47
|
+
else:
|
|
48
|
+
df_real_shuffled = df_real.sample(frac=1, random_state=seed).reset_index(drop=True)
|
|
49
|
+
|
|
50
|
+
n_splits = int(1 / test_size)
|
|
51
|
+
gkf = GroupKFold(n_splits=n_splits)
|
|
52
|
+
|
|
53
|
+
splits = list(gkf.split(df_real_shuffled, groups=df_real_shuffled[group_col]))
|
|
54
|
+
train_idx, val_idx = splits[0]
|
|
55
|
+
|
|
56
|
+
real_train = df_real_shuffled.iloc[train_idx]
|
|
57
|
+
real_val = df_real_shuffled.iloc[val_idx]
|
|
58
|
+
|
|
59
|
+
# Match fake images to corresponding real train/val
|
|
60
|
+
fake_train = df_fake[df_fake[ori_vid].isin(real_train[ori_vid].unique())].reset_index(drop=True)
|
|
61
|
+
fake_val = df_fake[df_fake[ori_vid].isin(real_val[ori_vid].unique())].reset_index(drop=True)
|
|
62
|
+
|
|
63
|
+
# Sample fake_val to match real_val length
|
|
64
|
+
if balance_val:
|
|
65
|
+
fake_val = fake_val.sample(len(real_val), random_state=seed)
|
|
66
|
+
|
|
67
|
+
# Concatenate real + fake
|
|
68
|
+
train_df = pd.concat([real_train, fake_train], axis=0)
|
|
69
|
+
valid_df = pd.concat([real_val, fake_val], axis=0)
|
|
70
|
+
|
|
71
|
+
# Debug mode: downsample dataset
|
|
72
|
+
if debug:
|
|
73
|
+
n_train = max(1, int(len(train_df) * debug_ratio))
|
|
74
|
+
n_valid = max(1, int(len(valid_df) * debug_ratio))
|
|
75
|
+
train_df = train_df.sample(n=n_train, random_state=seed).reset_index(drop=True)
|
|
76
|
+
valid_df = valid_df.sample(n=n_valid, random_state=seed).reset_index(drop=True)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Shuffle final datasets
|
|
80
|
+
train_df = train_df.sample(frac=1, random_state=seed).reset_index(drop=True)
|
|
81
|
+
valid_df = valid_df.sample(frac=1, random_state=seed).reset_index(drop=True)
|
|
82
|
+
|
|
83
|
+
return train_df, valid_df
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .template import (
|
|
2
|
+
REGIONS,
|
|
3
|
+
TYPES,
|
|
4
|
+
PAIR_ORDER,
|
|
5
|
+
REAL_TEXT,
|
|
6
|
+
parse_pair_label,
|
|
7
|
+
pair_frequency,
|
|
8
|
+
region_frequency,
|
|
9
|
+
type_weights,
|
|
10
|
+
region_weights,
|
|
11
|
+
sample_selection,
|
|
12
|
+
render_caption,
|
|
13
|
+
all_captions,
|
|
14
|
+
single_pair_caption,
|
|
15
|
+
)
|
|
16
|
+
from .tokenizer import tokenize
|
|
Binary file
|