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.
Files changed (50) hide show
  1. deepclip-0.1.0/LICENSE +21 -0
  2. deepclip-0.1.0/PKG-INFO +36 -0
  3. deepclip-0.1.0/README.md +7 -0
  4. deepclip-0.1.0/deepclip/__init__.py +31 -0
  5. deepclip-0.1.0/deepclip/config/deepclip_b0.yaml +65 -0
  6. deepclip-0.1.0/deepclip/config/deepclip_b5.yaml +65 -0
  7. deepclip-0.1.0/deepclip/data/__init__.py +9 -0
  8. deepclip-0.1.0/deepclip/data/collate.py +104 -0
  9. deepclip-0.1.0/deepclip/data/dataset.py +67 -0
  10. deepclip-0.1.0/deepclip/data/handle_imbalance.py +44 -0
  11. deepclip-0.1.0/deepclip/data/split_data.py +83 -0
  12. deepclip-0.1.0/deepclip/data/text/__init__.py +16 -0
  13. deepclip-0.1.0/deepclip/data/text/bpe_simple_vocab_16e6.txt.gz +0 -0
  14. deepclip-0.1.0/deepclip/data/text/template.py +133 -0
  15. deepclip-0.1.0/deepclip/data/text/tokenizer.py +146 -0
  16. deepclip-0.1.0/deepclip/data/transforms.py +76 -0
  17. deepclip-0.1.0/deepclip/layers/__init__.py +13 -0
  18. deepclip-0.1.0/deepclip/layers/attention.py +153 -0
  19. deepclip-0.1.0/deepclip/layers/clip_text.py +61 -0
  20. deepclip-0.1.0/deepclip/layers/drop.py +35 -0
  21. deepclip-0.1.0/deepclip/layers/featextractor.py +56 -0
  22. deepclip-0.1.0/deepclip/layers/global_query.py +65 -0
  23. deepclip-0.1.0/deepclip/layers/mlp.py +41 -0
  24. deepclip-0.1.0/deepclip/layers/patch.py +58 -0
  25. deepclip-0.1.0/deepclip/layers/reducesize.py +45 -0
  26. deepclip-0.1.0/deepclip/layers/se.py +28 -0
  27. deepclip-0.1.0/deepclip/layers/transformer.py +266 -0
  28. deepclip-0.1.0/deepclip/layers/weight_init.py +30 -0
  29. deepclip-0.1.0/deepclip/layers/window.py +22 -0
  30. deepclip-0.1.0/deepclip/losses.py +16 -0
  31. deepclip-0.1.0/deepclip/models/__init__.py +8 -0
  32. deepclip-0.1.0/deepclip/models/deepclip.py +199 -0
  33. deepclip-0.1.0/deepclip/models/image_encoder.py +198 -0
  34. deepclip-0.1.0/deepclip/models/text_encoder.py +125 -0
  35. deepclip-0.1.0/deepclip/optimizer/__init__.py +1 -0
  36. deepclip-0.1.0/deepclip/optimizer/build_optimizer.py +29 -0
  37. deepclip-0.1.0/deepclip/scheduler/__init__.py +1 -0
  38. deepclip-0.1.0/deepclip/scheduler/fetch_scheduler.py +58 -0
  39. deepclip-0.1.0/deepclip/utils/__init__.py +4 -0
  40. deepclip-0.1.0/deepclip/utils/build_metrics.py +62 -0
  41. deepclip-0.1.0/deepclip/utils/early_stopping.py +24 -0
  42. deepclip-0.1.0/deepclip/utils/meter.py +26 -0
  43. deepclip-0.1.0/deepclip/utils/seed.py +23 -0
  44. deepclip-0.1.0/deepclip.egg-info/PKG-INFO +36 -0
  45. deepclip-0.1.0/deepclip.egg-info/SOURCES.txt +48 -0
  46. deepclip-0.1.0/deepclip.egg-info/dependency_links.txt +1 -0
  47. deepclip-0.1.0/deepclip.egg-info/requires.txt +9 -0
  48. deepclip-0.1.0/deepclip.egg-info/top_level.txt +1 -0
  49. deepclip-0.1.0/pyproject.toml +50 -0
  50. 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.
@@ -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.
@@ -0,0 +1,7 @@
1
+ # DeepClip
2
+ CLIP for deepfakes
3
+
4
+ ## Dataset
5
+
6
+ [`preprocess/`](preprocess/README.md) builds the frame-level FaceForensics++ dataset and its forgery annotation
7
+ (face crops, 68 facial landmarks, forgery masks and forgery features) used to train DeepClip.
@@ -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