Hamidreza-Hashemp commited on
Commit
cd3346a
1 Parent(s): 59a4d93

Upload 65 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. Dockerfile +54 -0
  3. LICENSE +21 -0
  4. README.md +171 -12
  5. app.py +62 -0
  6. configs/b5.json +28 -0
  7. configs/b7.json +29 -0
  8. download_weights.sh +9 -0
  9. images/augmentations.jpg +0 -0
  10. images/loss_plot.png +0 -0
  11. kernel_utils.py +361 -0
  12. libs/shape_predictor_68_face_landmarks.dat +3 -0
  13. logs/.gitkeep +0 -0
  14. logs/classifier_tf_efficientnet_b7_ns_1/events.out.tfevents.1671630947.Green.15284.0 +3 -0
  15. logs/classifier_tf_efficientnet_b7_ns_1/events.out.tfevents.1671631032.Green.22668.0 +3 -0
  16. logs/classifier_tf_efficientnet_b7_ns_1/events.out.tfevents.1671715958.Green.34292.0 +3 -0
  17. plot_loss.py +45 -0
  18. predict_folder.py +47 -0
  19. predict_submission.sh +13 -0
  20. predictions_1.json +1 -0
  21. preprocess_data.sh +15 -0
  22. preprocessing/__init__.py +1 -0
  23. preprocessing/__pycache__/face_detector.cpython-39.pyc +0 -0
  24. preprocessing/__pycache__/utils.cpython-39.pyc +0 -0
  25. preprocessing/compress_videos.py +45 -0
  26. preprocessing/detect_original_faces.py +63 -0
  27. preprocessing/extract_crops.py +88 -0
  28. preprocessing/extract_images.py +42 -0
  29. preprocessing/face_detector.py +72 -0
  30. preprocessing/face_encodings.py +55 -0
  31. preprocessing/generate_diffs.py +75 -0
  32. preprocessing/generate_folds.py +117 -0
  33. preprocessing/generate_landmarks.py +76 -0
  34. preprocessing/utils.py +51 -0
  35. train.sh +18 -0
  36. training/__init__.py +0 -0
  37. training/__pycache__/__init__.cpython-39.pyc +0 -0
  38. training/__pycache__/losses.cpython-39.pyc +0 -0
  39. training/datasets/__init__.py +0 -0
  40. training/datasets/__pycache__/__init__.cpython-39.pyc +0 -0
  41. training/datasets/__pycache__/classifier_dataset.cpython-39.pyc +0 -0
  42. training/datasets/__pycache__/validation_set.cpython-39.pyc +0 -0
  43. training/datasets/classifier_dataset.py +379 -0
  44. training/datasets/validation_set.py +60 -0
  45. training/losses.py +28 -0
  46. training/pipelines/__init__.py +0 -0
  47. training/pipelines/logs/classifier_tf_efficientnet_b7_ns_1/events.out.tfevents.1671716506.Green.33248.0 +3 -0
  48. training/pipelines/train_classifier.py +365 -0
  49. training/tools/__init__.py +0 -0
  50. training/tools/__pycache__/__init__.cpython-39.pyc +0 -0
.gitattributes CHANGED
@@ -32,3 +32,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
32
  *.zip filter=lfs diff=lfs merge=lfs -text
33
  *.zst filter=lfs diff=lfs merge=lfs -text
34
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
32
  *.zip filter=lfs diff=lfs merge=lfs -text
33
  *.zst filter=lfs diff=lfs merge=lfs -text
34
  *tfevents* filter=lfs diff=lfs merge=lfs -text
35
+ libs/shape_predictor_68_face_landmarks.dat filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ARG PYTORCH="1.10.0"
2
+ ARG CUDA="11.3"
3
+ ARG CUDNN="8"
4
+
5
+ FROM pytorch/pytorch:${PYTORCH}-cuda${CUDA}-cudnn${CUDNN}-devel
6
+
7
+ ENV TORCH_NVCC_FLAGS="-Xfatbin -compress-all"
8
+ ENV CMAKE_PREFIX_PATH="$(dirname $(which conda))/../"
9
+
10
+ # Setting noninteractive build, setting up tzdata and configuring timezones
11
+ ENV DEBIAN_FRONTEND=noninteractive
12
+ ENV TZ=Europe/Berlin
13
+ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
14
+
15
+ RUN apt-get update && apt-get install -y libglib2.0-0 libsm6 libxrender-dev libxext6 nano mc glances vim git \
16
+ && apt-get clean \
17
+ && rm -rf /var/lib/apt/lists/*
18
+
19
+ # Install cython
20
+ RUN conda install cython -y && conda clean --all
21
+
22
+ # Installing APEX
23
+ RUN pip install -U pip
24
+ RUN git clone https://github.com/NVIDIA/apex
25
+ RUN sed -i 's/check_cuda_torch_binary_vs_bare_metal(torch.utils.cpp_extension.CUDA_HOME)/pass/g' apex/setup.py
26
+ RUN pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" ./apex
27
+ RUN apt-get update -y
28
+ RUN apt-get install build-essential cmake -y
29
+ RUN apt-get install libopenblas-dev liblapack-dev -y
30
+ RUN apt-get install libx11-dev libgtk-3-dev -y
31
+ RUN pip install dlib
32
+ RUN pip install facenet-pytorch
33
+ RUN pip install albumentations==1.0.0 timm==0.4.12 pytorch_toolbelt tensorboardx
34
+ RUN pip install cython jupyter jupyterlab ipykernel matplotlib tqdm pandas
35
+
36
+ # download pretraned Imagenet models
37
+ RUN apt install wget
38
+ RUN wget https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b7_ns-1dbc32de.pth -P /root/.cache/torch/hub/checkpoints/
39
+ RUN wget https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b5_ns-6f26d0cf.pth -P /root/.cache/torch/hub/checkpoints/
40
+
41
+ # Setting the working directory
42
+ WORKDIR /workspace
43
+
44
+ # Copying the required codebase
45
+ COPY . /workspace
46
+
47
+ RUN chmod 777 preprocess_data.sh
48
+ RUN chmod 777 train.sh
49
+ RUN chmod 777 predict_submission.sh
50
+
51
+ ENV PYTHONPATH=.
52
+
53
+ CMD ["/bin/bash"]
54
+
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Selim Seferbekov
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.
README.md CHANGED
@@ -1,12 +1,171 @@
1
- ---
2
- title: DeepFakeClassifier
3
- emoji:
4
- colorFrom: gray
5
- colorTo: red
6
- sdk: gradio
7
- sdk_version: 3.15.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## DeepFake Detection (DFDC) Solution by @selimsef
2
+
3
+ ## Challenge details:
4
+
5
+ [Kaggle Challenge Page](https://www.kaggle.com/c/deepfake-detection-challenge)
6
+
7
+
8
+ ### Fake detection articles
9
+ - [The Deepfake Detection Challenge (DFDC) Preview Dataset](https://arxiv.org/abs/1910.08854)
10
+ - [Deep Fake Image Detection Based on Pairwise Learning](https://www.mdpi.com/2076-3417/10/1/370)
11
+ - [DeeperForensics-1.0: A Large-Scale Dataset for Real-World Face Forgery Detection](https://arxiv.org/abs/2001.03024)
12
+ - [DeepFakes and Beyond: A Survey of Face Manipulation and Fake Detection](https://arxiv.org/abs/2001.00179)
13
+ - [Real or Fake? Spoofing State-Of-The-Art Face Synthesis Detection Systems](https://arxiv.org/abs/1911.05351)
14
+ - [CNN-generated images are surprisingly easy to spot... for now](https://arxiv.org/abs/1912.11035)
15
+ - [FakeSpotter: A Simple yet Robust Baseline for Spotting AI-Synthesized Fake Faces](https://arxiv.org/abs/1909.06122)
16
+ - [FakeLocator: Robust Localization of GAN-Based Face Manipulations via Semantic Segmentation Networks with Bells and Whistles](https://arxiv.org/abs/2001.09598)
17
+ - [Media Forensics and DeepFakes: an overview](https://arxiv.org/abs/2001.06564)
18
+ - [Face X-ray for More General Face Forgery Detection](https://arxiv.org/abs/1912.13458)
19
+
20
+ ## Solution description
21
+ In general solution is based on frame-by-frame classification approach. Other complex things did not work so well on public leaderboard.
22
+
23
+ #### Face-Detector
24
+ MTCNN detector is chosen due to kernel time limits. It would be better to use S3FD detector as more precise and robust, but opensource Pytorch implementations don't have a license.
25
+
26
+ Input size for face detector was calculated for each video depending on video resolution.
27
+
28
+ - 2x scale for videos with less than 300 pixels wider side
29
+ - no rescale for videos with wider side between 300 and 1000
30
+ - 0.5x scale for videos with wider side > 1000 pixels
31
+ - 0.33x scale for videos with wider side > 1900 pixels
32
+
33
+ ### Input size
34
+ As soon as I discovered that EfficientNets significantly outperform other encoders I used only them in my solution.
35
+ As I started with B4 I decided to use "native" size for that network (380x380).
36
+ Due to memory costraints I did not increase input size even for B7 encoder.
37
+
38
+ ### Margin
39
+ When I generated crops for training I added 30% of face crop size from each side and used only this setting during the competition.
40
+ See [extract_crops.py](preprocessing/extract_crops.py) for the details
41
+
42
+ ### Encoders
43
+ The winning encoder is current state-of-the-art model (EfficientNet B7) pretrained with ImageNet and noisy student [Self-training with Noisy Student improves ImageNet classification
44
+ ](https://arxiv.org/abs/1911.04252)
45
+
46
+ ### Averaging predictions
47
+ I used 32 frames for each video.
48
+ For each model output instead of simple averaging I used the following heuristic which worked quite well on public leaderbord (0.25 -> 0.22 solo B5).
49
+ ```python
50
+ import numpy as np
51
+
52
+ def confident_strategy(pred, t=0.8):
53
+ pred = np.array(pred)
54
+ sz = len(pred)
55
+ fakes = np.count_nonzero(pred > t)
56
+ # 11 frames are detected as fakes with high probability
57
+ if fakes > sz // 2.5 and fakes > 11:
58
+ return np.mean(pred[pred > t])
59
+ elif np.count_nonzero(pred < 0.2) > 0.9 * sz:
60
+ return np.mean(pred[pred < 0.2])
61
+ else:
62
+ return np.mean(pred)
63
+ ```
64
+
65
+ ### Augmentations
66
+
67
+ I used heavy augmentations by default.
68
+ [Albumentations](https://github.com/albumentations-team/albumentations) library supports most of the augmentations out of the box. Only needed to add IsotropicResize augmentation.
69
+ ```
70
+
71
+ def create_train_transforms(size=300):
72
+ return Compose([
73
+ ImageCompression(quality_lower=60, quality_upper=100, p=0.5),
74
+ GaussNoise(p=0.1),
75
+ GaussianBlur(blur_limit=3, p=0.05),
76
+ HorizontalFlip(),
77
+ OneOf([
78
+ IsotropicResize(max_side=size, interpolation_down=cv2.INTER_AREA, interpolation_up=cv2.INTER_CUBIC),
79
+ IsotropicResize(max_side=size, interpolation_down=cv2.INTER_AREA, interpolation_up=cv2.INTER_LINEAR),
80
+ IsotropicResize(max_side=size, interpolation_down=cv2.INTER_LINEAR, interpolation_up=cv2.INTER_LINEAR),
81
+ ], p=1),
82
+ PadIfNeeded(min_height=size, min_width=size, border_mode=cv2.BORDER_CONSTANT),
83
+ OneOf([RandomBrightnessContrast(), FancyPCA(), HueSaturationValue()], p=0.7),
84
+ ToGray(p=0.2),
85
+ ShiftScaleRotate(shift_limit=0.1, scale_limit=0.2, rotate_limit=10, border_mode=cv2.BORDER_CONSTANT, p=0.5),
86
+ ]
87
+ )
88
+ ```
89
+ In addition to these augmentations I wanted to achieve better generalization with
90
+ - Cutout like augmentations (dropping artefacts and parts of face)
91
+ - Dropout part of the image, inspired by [GridMask](https://arxiv.org/abs/2001.04086) and [Severstal Winning Solution](https://www.kaggle.com/c/severstal-steel-defect-detection/discussion/114254)
92
+
93
+ ![augmentations](images/augmentations.jpg "Dropout augmentations")
94
+
95
+ ## Building docker image
96
+ All libraries and enviroment is already configured with Dockerfile. It requires docker engine https://docs.docker.com/engine/install/ubuntu/ and nvidia docker in your system https://github.com/NVIDIA/nvidia-docker.
97
+
98
+ To build a docker image run `docker build -t df .`
99
+
100
+ ## Running docker
101
+ `docker run --runtime=nvidia --ipc=host --rm --volume <DATA_ROOT>:/dataset -it df`
102
+
103
+ ## Data preparation
104
+
105
+ Once DFDC dataset is downloaded all the scripts expect to have `dfdc_train_xxx` folders under data root directory.
106
+
107
+ Preprocessing is done in a single script **`preprocess_data.sh`** which requires dataset directory as first argument.
108
+ It will execute the steps below:
109
+
110
+ ##### 1. Find face bboxes
111
+ To extract face bboxes I used facenet library, basically only MTCNN.
112
+ `python preprocessing/detect_original_faces.py --root-dir DATA_ROOT`
113
+ This script will detect faces in real videos and store them as jsons in DATA_ROOT/bboxes directory
114
+
115
+ ##### 2. Extract crops from videos
116
+ To extract image crops I used bboxes saved before. It will use bounding boxes from original videos for face videos as well.
117
+ `python preprocessing/extract_crops.py --root-dir DATA_ROOT --crops-dir crops`
118
+ This script will extract face crops from videos and save them in DATA_ROOT/crops directory
119
+
120
+ ##### 3. Generate landmarks
121
+ From the saved crops it is quite fast to process crops with MTCNN and extract landmarks
122
+ `python preprocessing/generate_landmarks.py --root-dir DATA_ROOT`
123
+ This script will extract landmarks and save them in DATA_ROOT/landmarks directory
124
+
125
+ ##### 4. Generate diff SSIM masks
126
+ `python preprocessing/generate_diffs.py --root-dir DATA_ROOT`
127
+ This script will extract SSIM difference masks between real and fake images and save them in DATA_ROOT/diffs directory
128
+
129
+ ##### 5. Generate folds
130
+ `python preprocessing/generate_folds.py --root-dir DATA_ROOT --out folds.csv`
131
+ By default it will use 16 splits to have 0-2 folders as a holdout set. Though only 400 videos can be used for validation as well.
132
+
133
+
134
+ ## Training
135
+
136
+ Training 5 B7 models with different seeds is done in **`train.sh`** script.
137
+
138
+ During training checkpoints are saved for every epoch.
139
+
140
+ ## Hardware requirements
141
+ Mostly trained on devbox configuration with 4xTitan V, thanks to Nvidia and DSB2018 competition where I got these gpus https://www.kaggle.com/c/data-science-bowl-2018/
142
+
143
+ Overall training requires 4 GPUs with 12gb+ memory.
144
+ Batch size needs to be adjusted for standard 1080Ti or 2080Ti graphic cards.
145
+
146
+ As I computed fake loss and real loss separately inside each batch, results might be better with larger batch size, for example on V100 gpus.
147
+ Even though SyncBN is used larger batch on each GPU will lead to less noise as DFDC dataset has some fakes where face detector failed and face crops are not really fakes.
148
+
149
+ ## Plotting losses to select checkpoints
150
+
151
+ `python plot_loss.py --log-file logs/<log file>`
152
+
153
+ ![loss plot](images/loss_plot.png "Weighted loss")
154
+
155
+ ## Inference
156
+
157
+
158
+ Kernel is reproduced with `predict_folder.py` script.
159
+
160
+
161
+ ## Pretrained models
162
+ `download_weights.sh` script will download trained models to `weights/` folder. They should be downloaded before building a docker image.
163
+
164
+ Ensemble inference is already preconfigured with `predict_submission.sh` bash script. It expects a directory with videos as first argument and an output csv file as second argument.
165
+
166
+ For example `./predict_submission.sh /mnt/datasets/deepfake/test_videos submission.csv`
167
+
168
+
169
+
170
+
171
+
app.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ import re
4
+ import time
5
+ import cv2
6
+
7
+ import torch
8
+ import pandas as pd
9
+ from kernel_utils import VideoReader, FaceExtractor, confident_strategy, predict_on_video_set
10
+ from training.zoo.classifiers import DeepFakeClassifier
11
+ import gradio as gr
12
+
13
+
14
+
15
+ def deepfakeclassifier(potential_test_video, option):
16
+ if option == 'Original':
17
+ weights_dir = "./weights"
18
+ models_dir = ["Original_DeepFakeClassifier_tf_efficientnet_b7_ns"]
19
+ else:
20
+ weights_dir = "./weights"
21
+ models_dir = ["Custom_classifier_DeepFakeClassifier_tf_efficientnet_b7_ns"]
22
+
23
+ parts = potential_test_video.split("\\")
24
+ test_videos = [parts[-1]]
25
+ parts[0] += "\\"
26
+ test_dir = parts[:-1]
27
+ test_dir = os.path.join(*test_dir)
28
+
29
+
30
+ models = []
31
+ model_paths = [os.path.join(weights_dir, model) for model in models_dir]
32
+ for path in model_paths:
33
+ model = DeepFakeClassifier(encoder="tf_efficientnet_b7_ns").to('cuda')
34
+ print("loading state dict {}".format(path))
35
+ checkpoint = torch.load(path, map_location="cpu")
36
+ state_dict = checkpoint.get("state_dict", checkpoint)
37
+ model.load_state_dict({re.sub("^module.", "", k): v for k, v in state_dict.items()}, strict=True)
38
+ model.eval()
39
+ del checkpoint
40
+ models.append(model.half())
41
+
42
+ frames_per_video = 32
43
+ video_reader = VideoReader()
44
+ video_read_fn = lambda x: video_reader.read_frames(x, num_frames=frames_per_video)
45
+ face_extractor = FaceExtractor(video_read_fn)
46
+ input_size = 380
47
+ strategy = confident_strategy
48
+ stime = time.time()
49
+
50
+ print("Predicting {} videos".format(len(test_videos)))
51
+ predictions = predict_on_video_set(face_extractor=face_extractor, input_size=input_size, models=models,
52
+ strategy=strategy, frames_per_video=frames_per_video, videos=test_videos,
53
+ num_workers=6, test_dir=test_dir)
54
+
55
+ print("Elapsed:", time.time() - stime)
56
+ return "This video is FAKE with {} probability!".format(predictions[0])
57
+
58
+ demo = gr.Interface(fn=deepfakeclassifier, inputs=[gr.Video(),
59
+ gr.Radio(["Original", "Custom"])] ,outputs="text", description="Original option uses the trained weights of the winning idea. Custom is my trained \
60
+ network. Original optional performs better as it uses much more data for training!")
61
+
62
+ demo.launch()
configs/b5.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "network": "DeepFakeClassifier",
3
+ "encoder": "tf_efficientnet_b5_ns",
4
+ "batches_per_epoch": 2500,
5
+ "size": 380,
6
+ "fp16": true,
7
+ "optimizer": {
8
+ "batch_size": 20,
9
+ "type": "SGD",
10
+ "momentum": 0.9,
11
+ "weight_decay": 1e-4,
12
+ "learning_rate": 0.01,
13
+ "nesterov": true,
14
+ "schedule": {
15
+ "type": "poly",
16
+ "mode": "step",
17
+ "epochs": 30,
18
+ "params": {"max_iter": 75100}
19
+ }
20
+ },
21
+ "normalize": {
22
+ "mean": [0.485, 0.456, 0.406],
23
+ "std": [0.229, 0.224, 0.225]
24
+ },
25
+ "losses": {
26
+ "BinaryCrossentropy": 1
27
+ }
28
+ }
configs/b7.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "network": "DeepFakeClassifier",
3
+ "encoder": "tf_efficientnet_b7_ns",
4
+ "batches_per_epoch": 10000,
5
+ "size": 380,
6
+ "fp16": true,
7
+ "optimizer": {
8
+ "batch_size": 2,
9
+ "type": "SGD",
10
+ "momentum": 0.9,
11
+ "weight_decay": 1e-4,
12
+ "learning_rate": 0.01,
13
+ "nesterov": true,
14
+ "schedule": {
15
+ "type": "poly",
16
+ "mode": "step",
17
+ "epochs": 15,
18
+ "params": {"max_iter": 100500}
19
+ }
20
+ },
21
+ "normalize": {
22
+ "mean": [0.485, 0.456, 0.406],
23
+ "std": [0.229, 0.224, 0.225]
24
+ },
25
+ "losses": {
26
+ "BinaryCrossentropy": 1
27
+ }
28
+ }
29
+
download_weights.sh ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ tag=0.0.1
2
+
3
+ wget -O weights/final_111_DeepFakeClassifier_tf_efficientnet_b7_ns_0_36 https://github.com/selimsef/dfdc_deepfake_challenge/releases/download/$tag/final_111_DeepFakeClassifier_tf_efficientnet_b7_ns_0_36
4
+ wget -O weights/final_555_DeepFakeClassifier_tf_efficientnet_b7_ns_0_19 https://github.com/selimsef/dfdc_deepfake_challenge/releases/download/$tag/final_555_DeepFakeClassifier_tf_efficientnet_b7_ns_0_19
5
+ wget -O weights/final_777_DeepFakeClassifier_tf_efficientnet_b7_ns_0_29 https://github.com/selimsef/dfdc_deepfake_challenge/releases/download/$tag/final_777_DeepFakeClassifier_tf_efficientnet_b7_ns_0_29
6
+ wget -O weights/final_777_DeepFakeClassifier_tf_efficientnet_b7_ns_0_31 https://github.com/selimsef/dfdc_deepfake_challenge/releases/download/$tag/final_777_DeepFakeClassifier_tf_efficientnet_b7_ns_0_31
7
+ wget -O weights/final_888_DeepFakeClassifier_tf_efficientnet_b7_ns_0_37 https://github.com/selimsef/dfdc_deepfake_challenge/releases/download/$tag/final_888_DeepFakeClassifier_tf_efficientnet_b7_ns_0_37
8
+ wget -O weights/final_888_DeepFakeClassifier_tf_efficientnet_b7_ns_0_40 https://github.com/selimsef/dfdc_deepfake_challenge/releases/download/$tag/final_888_DeepFakeClassifier_tf_efficientnet_b7_ns_0_40
9
+ wget -O weights/final_999_DeepFakeClassifier_tf_efficientnet_b7_ns_0_23 https://github.com/selimsef/dfdc_deepfake_challenge/releases/download/$tag/final_999_DeepFakeClassifier_tf_efficientnet_b7_ns_0_23
images/augmentations.jpg ADDED
images/loss_plot.png ADDED
kernel_utils.py ADDED
@@ -0,0 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import cv2
4
+ import numpy as np
5
+ import torch
6
+ from PIL import Image
7
+ from albumentations.augmentations.functional import image_compression
8
+ from facenet_pytorch.models.mtcnn import MTCNN
9
+ from concurrent.futures import ThreadPoolExecutor
10
+
11
+ from torchvision.transforms import Normalize
12
+
13
+ mean = [0.485, 0.456, 0.406]
14
+ std = [0.229, 0.224, 0.225]
15
+ normalize_transform = Normalize(mean, std)
16
+
17
+
18
+ class VideoReader:
19
+ """Helper class for reading one or more frames from a video file."""
20
+
21
+ def __init__(self, verbose=True, insets=(0, 0)):
22
+ """Creates a new VideoReader.
23
+
24
+ Arguments:
25
+ verbose: whether to print warnings and error messages
26
+ insets: amount to inset the image by, as a percentage of
27
+ (width, height). This lets you "zoom in" to an image
28
+ to remove unimportant content around the borders.
29
+ Useful for face detection, which may not work if the
30
+ faces are too small.
31
+ """
32
+ self.verbose = verbose
33
+ self.insets = insets
34
+
35
+ def read_frames(self, path, num_frames, jitter=0, seed=None):
36
+ """Reads frames that are always evenly spaced throughout the video.
37
+
38
+ Arguments:
39
+ path: the video file
40
+ num_frames: how many frames to read, -1 means the entire video
41
+ (warning: this will take up a lot of memory!)
42
+ jitter: if not 0, adds small random offsets to the frame indices;
43
+ this is useful so we don't always land on even or odd frames
44
+ seed: random seed for jittering; if you set this to a fixed value,
45
+ you probably want to set it only on the first video
46
+ """
47
+ assert num_frames > 0
48
+
49
+ capture = cv2.VideoCapture(path)
50
+ frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
51
+ if frame_count <= 0: return None
52
+
53
+ frame_idxs = np.linspace(0, frame_count - 1, num_frames, endpoint=True, dtype=np.int)
54
+ if jitter > 0:
55
+ np.random.seed(seed)
56
+ jitter_offsets = np.random.randint(-jitter, jitter, len(frame_idxs))
57
+ frame_idxs = np.clip(frame_idxs + jitter_offsets, 0, frame_count - 1)
58
+
59
+ result = self._read_frames_at_indices(path, capture, frame_idxs)
60
+ capture.release()
61
+ return result
62
+
63
+ def read_random_frames(self, path, num_frames, seed=None):
64
+ """Picks the frame indices at random.
65
+
66
+ Arguments:
67
+ path: the video file
68
+ num_frames: how many frames to read, -1 means the entire video
69
+ (warning: this will take up a lot of memory!)
70
+ """
71
+ assert num_frames > 0
72
+ np.random.seed(seed)
73
+
74
+ capture = cv2.VideoCapture(path)
75
+ frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
76
+ if frame_count <= 0: return None
77
+
78
+ frame_idxs = sorted(np.random.choice(np.arange(0, frame_count), num_frames))
79
+ result = self._read_frames_at_indices(path, capture, frame_idxs)
80
+
81
+ capture.release()
82
+ return result
83
+
84
+ def read_frames_at_indices(self, path, frame_idxs):
85
+ """Reads frames from a video and puts them into a NumPy array.
86
+
87
+ Arguments:
88
+ path: the video file
89
+ frame_idxs: a list of frame indices. Important: should be
90
+ sorted from low-to-high! If an index appears multiple
91
+ times, the frame is still read only once.
92
+
93
+ Returns:
94
+ - a NumPy array of shape (num_frames, height, width, 3)
95
+ - a list of the frame indices that were read
96
+
97
+ Reading stops if loading a frame fails, in which case the first
98
+ dimension returned may actually be less than num_frames.
99
+
100
+ Returns None if an exception is thrown for any reason, or if no
101
+ frames were read.
102
+ """
103
+ assert len(frame_idxs) > 0
104
+ capture = cv2.VideoCapture(path)
105
+ result = self._read_frames_at_indices(path, capture, frame_idxs)
106
+ capture.release()
107
+ return result
108
+
109
+ def _read_frames_at_indices(self, path, capture, frame_idxs):
110
+ try:
111
+ frames = []
112
+ idxs_read = []
113
+ for frame_idx in range(frame_idxs[0], frame_idxs[-1] + 1):
114
+ # Get the next frame, but don't decode if we're not using it.
115
+ ret = capture.grab()
116
+ if not ret:
117
+ if self.verbose:
118
+ print("Error grabbing frame %d from movie %s" % (frame_idx, path))
119
+ break
120
+
121
+ # Need to look at this frame?
122
+ current = len(idxs_read)
123
+ if frame_idx == frame_idxs[current]:
124
+ ret, frame = capture.retrieve()
125
+ if not ret or frame is None:
126
+ if self.verbose:
127
+ print("Error retrieving frame %d from movie %s" % (frame_idx, path))
128
+ break
129
+
130
+ frame = self._postprocess_frame(frame)
131
+ frames.append(frame)
132
+ idxs_read.append(frame_idx)
133
+
134
+ if len(frames) > 0:
135
+ return np.stack(frames), idxs_read
136
+ if self.verbose:
137
+ print("No frames read from movie %s" % path)
138
+ return None
139
+ except:
140
+ if self.verbose:
141
+ print("Exception while reading movie %s" % path)
142
+ return None
143
+
144
+ def read_middle_frame(self, path):
145
+ """Reads the frame from the middle of the video."""
146
+ capture = cv2.VideoCapture(path)
147
+ frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
148
+ result = self._read_frame_at_index(path, capture, frame_count // 2)
149
+ capture.release()
150
+ return result
151
+
152
+ def read_frame_at_index(self, path, frame_idx):
153
+ """Reads a single frame from a video.
154
+
155
+ If you just want to read a single frame from the video, this is more
156
+ efficient than scanning through the video to find the frame. However,
157
+ for reading multiple frames it's not efficient.
158
+
159
+ My guess is that a "streaming" approach is more efficient than a
160
+ "random access" approach because, unless you happen to grab a keyframe,
161
+ the decoder still needs to read all the previous frames in order to
162
+ reconstruct the one you're asking for.
163
+
164
+ Returns a NumPy array of shape (1, H, W, 3) and the index of the frame,
165
+ or None if reading failed.
166
+ """
167
+ capture = cv2.VideoCapture(path)
168
+ result = self._read_frame_at_index(path, capture, frame_idx)
169
+ capture.release()
170
+ return result
171
+
172
+ def _read_frame_at_index(self, path, capture, frame_idx):
173
+ capture.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
174
+ ret, frame = capture.read()
175
+ if not ret or frame is None:
176
+ if self.verbose:
177
+ print("Error retrieving frame %d from movie %s" % (frame_idx, path))
178
+ return None
179
+ else:
180
+ frame = self._postprocess_frame(frame)
181
+ return np.expand_dims(frame, axis=0), [frame_idx]
182
+
183
+ def _postprocess_frame(self, frame):
184
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
185
+
186
+ if self.insets[0] > 0:
187
+ W = frame.shape[1]
188
+ p = int(W * self.insets[0])
189
+ frame = frame[:, p:-p, :]
190
+
191
+ if self.insets[1] > 0:
192
+ H = frame.shape[1]
193
+ q = int(H * self.insets[1])
194
+ frame = frame[q:-q, :, :]
195
+
196
+ return frame
197
+
198
+
199
+ class FaceExtractor:
200
+ def __init__(self, video_read_fn):
201
+ self.video_read_fn = video_read_fn
202
+ self.detector = MTCNN(margin=0, thresholds=[0.7, 0.8, 0.8], device='cuda')
203
+
204
+ def process_videos(self, input_dir, filenames, video_idxs):
205
+ videos_read = []
206
+ frames_read = []
207
+ frames = []
208
+ results = []
209
+ for video_idx in video_idxs:
210
+ # Read the full-size frames from this video.
211
+ filename = filenames[video_idx]
212
+ video_path = os.path.join(input_dir, filename)
213
+ result = self.video_read_fn(video_path)
214
+ # Error? Then skip this video.
215
+ if result is None: continue
216
+
217
+ videos_read.append(video_idx)
218
+
219
+ # Keep track of the original frames (need them later).
220
+ my_frames, my_idxs = result
221
+
222
+ frames.append(my_frames)
223
+ frames_read.append(my_idxs)
224
+ for i, frame in enumerate(my_frames):
225
+ h, w = frame.shape[:2]
226
+ img = Image.fromarray(frame.astype(np.uint8))
227
+ img = img.resize(size=[s // 2 for s in img.size])
228
+
229
+ batch_boxes, probs = self.detector.detect(img, landmarks=False)
230
+
231
+ faces = []
232
+ scores = []
233
+ if batch_boxes is None:
234
+ continue
235
+ for bbox, score in zip(batch_boxes, probs):
236
+ if bbox is not None:
237
+ xmin, ymin, xmax, ymax = [int(b * 2) for b in bbox]
238
+ w = xmax - xmin
239
+ h = ymax - ymin
240
+ p_h = h // 3
241
+ p_w = w // 3
242
+ crop = frame[max(ymin - p_h, 0):ymax + p_h, max(xmin - p_w, 0):xmax + p_w]
243
+ faces.append(crop)
244
+ scores.append(score)
245
+
246
+ frame_dict = {"video_idx": video_idx,
247
+ "frame_idx": my_idxs[i],
248
+ "frame_w": w,
249
+ "frame_h": h,
250
+ "faces": faces,
251
+ "scores": scores}
252
+ results.append(frame_dict)
253
+
254
+ return results
255
+
256
+ def process_video(self, video_path):
257
+ """Convenience method for doing face extraction on a single video."""
258
+ input_dir = os.path.dirname(video_path)
259
+ filenames = [os.path.basename(video_path)]
260
+ return self.process_videos(input_dir, filenames, [0])
261
+
262
+
263
+
264
+ def confident_strategy(pred, t=0.8):
265
+ pred = np.array(pred)
266
+ sz = len(pred)
267
+ fakes = np.count_nonzero(pred > t)
268
+ # 11 frames are detected as fakes with high probability
269
+ if fakes > sz // 2.5 and fakes > 11:
270
+ return np.mean(pred[pred > t])
271
+ elif np.count_nonzero(pred < 0.2) > 0.9 * sz:
272
+ return np.mean(pred[pred < 0.2])
273
+ else:
274
+ return np.mean(pred)
275
+
276
+ strategy = confident_strategy
277
+
278
+
279
+ def put_to_center(img, input_size):
280
+ img = img[:input_size, :input_size]
281
+ image = np.zeros((input_size, input_size, 3), dtype=np.uint8)
282
+ start_w = (input_size - img.shape[1]) // 2
283
+ start_h = (input_size - img.shape[0]) // 2
284
+ image[start_h:start_h + img.shape[0], start_w: start_w + img.shape[1], :] = img
285
+ return image
286
+
287
+
288
+ def isotropically_resize_image(img, size, interpolation_down=cv2.INTER_AREA, interpolation_up=cv2.INTER_CUBIC):
289
+ h, w = img.shape[:2]
290
+ if max(w, h) == size:
291
+ return img
292
+ if w > h:
293
+ scale = size / w
294
+ h = h * scale
295
+ w = size
296
+ else:
297
+ scale = size / h
298
+ w = w * scale
299
+ h = size
300
+ interpolation = interpolation_up if scale > 1 else interpolation_down
301
+ resized = cv2.resize(img, (int(w), int(h)), interpolation=interpolation)
302
+ return resized
303
+
304
+
305
+ def predict_on_video(face_extractor, video_path, batch_size, input_size, models, strategy=np.mean,
306
+ apply_compression=False):
307
+ batch_size *= 4
308
+ try:
309
+ faces = face_extractor.process_video(video_path)
310
+ if len(faces) > 0:
311
+ x = np.zeros((batch_size, input_size, input_size, 3), dtype=np.uint8)
312
+ n = 0
313
+ for frame_data in faces:
314
+ for face in frame_data["faces"]:
315
+ resized_face = isotropically_resize_image(face, input_size)
316
+ resized_face = put_to_center(resized_face, input_size)
317
+ if apply_compression:
318
+ resized_face = image_compression(resized_face, quality=90, image_type=".jpg")
319
+ if n + 1 < batch_size:
320
+ x[n] = resized_face
321
+ n += 1
322
+ else:
323
+ pass
324
+ if n > 0:
325
+ x = torch.tensor(x, device="cuda").float()
326
+ # Preprocess the images.
327
+ x = x.permute((0, 3, 1, 2))
328
+ # x = x.to('cpu')
329
+ for i in range(len(x)):
330
+ x[i] = normalize_transform(x[i] / 255.)
331
+ # Make a prediction, then take the average.
332
+ with torch.no_grad():
333
+ preds = []
334
+ for model in models:
335
+ # with torch.cuda.amp.autocast():
336
+ y_pred = model(x[:n].float())
337
+ y_pred = torch.sigmoid(y_pred.squeeze())
338
+ bpred = y_pred[:n].cpu().numpy()
339
+ preds.append(strategy(bpred))
340
+ return np.mean(preds)
341
+ except Exception as e:
342
+ print("Prediction error on video %s: %s" % (video_path, str(e)))
343
+
344
+ return 0.5
345
+
346
+
347
+ def predict_on_video_set(face_extractor, videos, input_size, num_workers, test_dir, frames_per_video, models,
348
+ strategy=np.mean,
349
+ apply_compression=False):
350
+ def process_file(i):
351
+ filename = videos[i]
352
+ y_pred = predict_on_video(face_extractor=face_extractor, video_path=os.path.join(test_dir, filename),
353
+ input_size=input_size,
354
+ batch_size=frames_per_video,
355
+ models=models, strategy=strategy, apply_compression=apply_compression)
356
+ return y_pred
357
+
358
+ with ThreadPoolExecutor(max_workers=num_workers) as ex:
359
+ predictions = ex.map(process_file, range(len(videos)))
360
+ return list(predictions)
361
+
libs/shape_predictor_68_face_landmarks.dat ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fbdc2cb80eb9aa7a758672cbfdda32ba6300efe9b6e6c7a299ff7e736b11b92f
3
+ size 99693937
logs/.gitkeep ADDED
File without changes
logs/classifier_tf_efficientnet_b7_ns_1/events.out.tfevents.1671630947.Green.15284.0 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e450ee9880145e3f8ec94a5f7c344e8a24b4c57184d033fa8622a56987a778ba
3
+ size 40
logs/classifier_tf_efficientnet_b7_ns_1/events.out.tfevents.1671631032.Green.22668.0 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b497c6e7dd9bd7d92ab941a7cb3d4778c07b1c91c5cc24d8174a1176b0f25d10
3
+ size 1154
logs/classifier_tf_efficientnet_b7_ns_1/events.out.tfevents.1671715958.Green.34292.0 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:95ce902041c80cb054cd04d4c98d9eeecaa51ef6957f8c4dd62a3eba482c1b46
3
+ size 40
plot_loss.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+
3
+ import numpy as np
4
+
5
+ from matplotlib import pyplot as plt
6
+
7
+
8
+ def parse_args():
9
+ parser = argparse.ArgumentParser(
10
+ description="Plot losses from log")
11
+ parser.add_argument("--log-file", help="path to log file", required=True)
12
+ parser.add_argument("--fake-weight", help="weight for fake loss", default=1.4, type=float)
13
+ args = parser.parse_args()
14
+ return args
15
+
16
+
17
+ def main():
18
+ args = parse_args()
19
+
20
+ with open(args.log_file, "r") as f:
21
+ lines = f.readlines()
22
+ real_losses = []
23
+ fake_losses = []
24
+ for line in lines:
25
+ line = line.strip()
26
+ if line.startswith("fake_loss"):
27
+ fake_losses.append(float(line.split(" ")[-1]))
28
+ elif line.startswith("real_loss"):
29
+ real_losses.append(float(line.split(" ")[-1]))
30
+ real_losses = np.array(real_losses)
31
+ fake_losses = np.array(fake_losses)
32
+ loss = (fake_losses * args.fake_weight + real_losses)/2
33
+ plt.title("Weighted loss ({}*fake_loss + real_loss)/2)".format(args.fake_weight))
34
+ best_loss_idx = np.argsort(loss)[:5]
35
+ # ignore early epochs loss is quite noisy and there could be spikes
36
+ best_loss_idx = best_loss_idx[best_loss_idx > 16]
37
+ plt.scatter(best_loss_idx, loss[best_loss_idx], c="red")
38
+ for idx in best_loss_idx:
39
+ plt.annotate(str(idx), (idx, loss[idx]))
40
+ plt.plot(loss)
41
+ plt.show()
42
+
43
+
44
+ if __name__ == '__main__':
45
+ main()
predict_folder.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ import re
4
+ import time
5
+
6
+ import torch
7
+ import pandas as pd
8
+ from kernel_utils import VideoReader, FaceExtractor, confident_strategy, predict_on_video_set
9
+ from training.zoo.classifiers import DeepFakeClassifier
10
+
11
+ if __name__ == '__main__':
12
+ parser = argparse.ArgumentParser("Predict test videos")
13
+ arg = parser.add_argument
14
+ arg('--weights-dir', type=str, default="weights", help="path to directory with checkpoints")
15
+ arg('--models', nargs='+', required=True, help="checkpoint files")
16
+ arg('--test-dir', type=str, required=True, help="path to directory with videos")
17
+ arg('--output', type=str, required=False, help="path to output csv", default="submission.csv")
18
+ args = parser.parse_args()
19
+
20
+ models = []
21
+ model_paths = [os.path.join(args.weights_dir, model) for model in args.models]
22
+ for path in model_paths:
23
+ model = DeepFakeClassifier(encoder="tf_efficientnet_b7_ns").to("cuda")
24
+ print("loading state dict {}".format(path))
25
+ checkpoint = torch.load(path, map_location="cpu")
26
+ state_dict = checkpoint.get("state_dict", checkpoint)
27
+ model.load_state_dict({re.sub("^module.", "", k): v for k, v in state_dict.items()}, strict=True)
28
+ model.eval()
29
+ del checkpoint
30
+ models.append(model.half())
31
+
32
+ frames_per_video = 32
33
+ video_reader = VideoReader()
34
+ video_read_fn = lambda x: video_reader.read_frames(x, num_frames=frames_per_video)
35
+ face_extractor = FaceExtractor(video_read_fn)
36
+ input_size = 380
37
+ strategy = confident_strategy
38
+ stime = time.time()
39
+
40
+ test_videos = sorted([x for x in os.listdir(args.test_dir) if x[-4:] == ".mp4"])
41
+ print("Predicting {} videos".format(len(test_videos)))
42
+ predictions = predict_on_video_set(face_extractor=face_extractor, input_size=input_size, models=models,
43
+ strategy=strategy, frames_per_video=frames_per_video, videos=test_videos,
44
+ num_workers=6, test_dir=args.test_dir)
45
+ submission_df = pd.DataFrame({"filename": test_videos, "label": predictions})
46
+ submission_df.to_csv(args.output, index=False)
47
+ print("Elapsed:", time.time() - stime)
predict_submission.sh ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ TEST_DIR=$1
2
+ CSV=$2
3
+
4
+ python predict_folder.py \
5
+ --test-dir $TEST_DIR \
6
+ --output $CSV \
7
+ --models final_111_DeepFakeClassifier_tf_efficientnet_b7_ns_0_36 \
8
+ final_555_DeepFakeClassifier_tf_efficientnet_b7_ns_0_19 \
9
+ final_777_DeepFakeClassifier_tf_efficientnet_b7_ns_0_29 \
10
+ final_777_DeepFakeClassifier_tf_efficientnet_b7_ns_0_31 \
11
+ final_888_DeepFakeClassifier_tf_efficientnet_b7_ns_0_37 \
12
+ final_888_DeepFakeClassifier_tf_efficientnet_b7_ns_0_40 \
13
+ final_999_DeepFakeClassifier_tf_efficientnet_b7_ns_0_23
predictions_1.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"probs": {"avmjormvsx": [[0.98095703125], [0.984375], [0.97900390625], [0.97021484375], [0.98291015625], [0.98193359375], [0.97998046875], [0.97998046875], [0.9833984375], [0.984375], [0.97802734375], [0.95947265625], [0.97802734375], [0.98046875], [0.974609375]], "curpwogllm": [[0.97998046875], [0.93115234375], [0.9736328125], [0.9521484375], [0.93017578125], [0.97119140625], [0.9716796875], [0.9736328125], [0.97998046875], [0.97705078125], [0.93017578125], [0.95751953125], [0.97509765625], [0.95458984375], [0.98291015625]], "aorjvbyxhw": [[0.9814453125], [0.98388671875], [0.98388671875], [0.9853515625], [0.9833984375], [0.98388671875], [0.98046875], [0.98486328125], [0.98046875], [0.9794921875], [0.97021484375], [0.982421875], [0.98193359375], [0.984375], [0.982421875]], "cprhtltsjp": [[0.9814453125], [0.984375], [0.984375], [0.9775390625], [0.9833984375], [0.97900390625], [0.9833984375], [0.984375], [0.9775390625], [0.9775390625], [0.9833984375], [0.9833984375], [0.978515625], [0.98291015625], [0.9853515625]], "avnqydkqjj": [[0.9814453125], [0.98583984375], [0.98583984375], [0.9794921875], [0.9794921875], [0.9814453125], [0.97119140625], [0.96240234375], [0.97900390625], [0.98583984375], [0.98046875], [0.98193359375], [0.9814453125], [0.98486328125], [0.98291015625]], "bulkxhhknf": [[0.9794921875], [0.9765625], [0.97802734375], [0.9716796875], [0.98193359375], [0.9833984375], [0.98095703125], [0.970703125], [0.98095703125], [0.9794921875], [0.9833984375], [0.97900390625], [0.9814453125], [0.98193359375], [0.982421875]], "ekcrtigpab": [[0.98876953125], [0.94384765625], [0.9853515625], [0.9775390625], [0.9794921875], [0.97900390625], [0.96142578125], [0.98291015625], [0.98486328125], [0.98388671875], [0.97998046875], [0.7119140625], [0.98583984375], [0.984375], [0.9794921875]], "dhkwmjxwrn": [[0.9794921875], [0.98486328125], [0.638671875], [0.98193359375], [0.97216796875], [0.98486328125], [0.9833984375], [0.9814453125], [0.98046875], [0.982421875], [0.97998046875], [0.98095703125], [0.966796875], [0.9814453125], [0.98486328125], [0.97802734375], [0.97900390625], [0.9208984375], [0.98193359375], [0.97119140625], [0.98291015625], [0.9755859375], [0.9716796875], [0.97509765625], [0.91650390625], [0.98046875]], "apatcsqejh": [[0.97412109375], [0.9794921875], [0.955078125], [0.982421875], [0.98046875], [0.982421875], [0.98193359375], [0.982421875], [0.9814453125], [0.98193359375], [0.98388671875], [0.9375], [0.982421875], [0.9755859375], [0.98095703125], [0.98388671875], [0.97216796875], [0.9697265625], [0.9814453125], [0.98291015625], [0.98388671875], [0.97509765625], [0.98291015625], [0.9814453125], [0.98193359375], [0.982421875], [0.97802734375], [0.982421875], [0.9814453125], [0.98193359375]], "dzyuwjkjui": [[0.978515625], [0.96826171875], [0.9814453125], [0.978515625], [0.98046875], [0.97607421875], [0.97998046875], [0.98291015625], [0.9775390625], [0.97900390625], [0.9775390625], [0.982421875], [0.982421875], [0.98046875], [0.98046875]], "emaalmsonj": [[0.98095703125], [0.9599609375], [0.98046875], [0.98291015625], [0.97802734375], [0.9775390625], [0.97998046875], [0.9794921875], [0.97998046875], [0.9775390625], [0.97705078125], [0.9794921875], [0.97998046875], [0.9736328125], [0.98046875]], "bgwmmujlmc": [[0.98291015625], [0.982421875], [0.9736328125], [0.97998046875], [0.984375], [0.9814453125], [0.92822265625], [0.98388671875], [0.97314453125], [0.978515625], [0.98681640625], [0.98486328125], [0.98388671875], [0.98486328125], [0.97314453125]], "eckvhdusax": [[0.98388671875], [0.98388671875], [0.98046875], [0.96142578125], [0.9794921875], [0.98291015625], [0.98291015625], [0.9833984375], [0.98193359375], [0.98388671875], [0.98486328125], [0.984375], [0.98486328125], [0.98583984375], [0.982421875]], "ajwpjhrbcv": [[0.9765625], [0.96875], [0.9765625], [0.9599609375], [0.9755859375], [0.97607421875], [0.9658203125], [0.95703125], [0.958984375], [0.97705078125], [0.93505859375], [0.943359375], [0.96142578125], [0.73583984375], [0.95556640625]], "efwfxwwlbw": [[0.97998046875], [0.9609375], [0.96875], [0.97900390625], [0.98486328125], [0.9794921875], [0.94580078125], [0.9853515625], [0.966796875], [0.982421875], [0.93896484375], [0.8740234375], [0.98046875], [0.9462890625], [0.970703125]], "emfbhytfhc": [[0.984375], [0.9833984375], [0.982421875], [0.9853515625], [0.9833984375], [0.982421875], [0.98193359375], [0.98193359375], [0.9833984375], [0.98291015625], [0.98486328125], [0.982421875], [0.98291015625], [0.98388671875], [0.98193359375]], "bnjcdrfuov": [[0.984375], [0.98388671875], [0.98193359375], [0.98583984375], [0.984375], [0.98193359375], [0.98486328125], [0.98583984375], [0.984375], [0.98583984375], [0.98583984375], [0.9833984375], [0.986328125], [0.9833984375], [0.98486328125]], "drcyabprvt": [[0.98583984375], [0.98388671875], [0.9873046875], [0.98779296875], [0.986328125], [0.98486328125], [0.98388671875], [0.9853515625], [0.9853515625], [0.9853515625], [0.9853515625], [0.98388671875], [0.9853515625], [0.98583984375], [0.98486328125]], "cdphtzqrvp": [[0.984375], [0.98486328125], [0.982421875], [0.984375], [0.984375], [0.98291015625], [0.9833984375], [0.98193359375], [0.98291015625], [0.9833984375], [0.98583984375], [0.9833984375], [0.98388671875], [0.986328125], [0.9833984375]], "ellavthztb": [[0.982421875], [0.98193359375], [0.98681640625], [0.9833984375], [0.98486328125], [0.98193359375], [0.9853515625], [0.98291015625], [0.9853515625], [0.982421875], [0.986328125], [0.984375], [0.984375], [0.98486328125], [0.98291015625]], "dbtbbhakdv": [[0.9765625], [0.98193359375], [0.96630859375], [0.98388671875], [0.98193359375], [0.978515625], [0.96728515625], [0.978515625], [0.97607421875], [0.9755859375], [0.98095703125], [0.982421875], [0.98193359375], [0.9765625], [0.97802734375]], "btohlidmru": [[0.98583984375], [0.984375], [0.98486328125], [0.984375], [0.98388671875], [0.986328125], [0.98583984375], [0.98681640625], [0.98681640625], [0.98779296875], [0.9873046875], [0.98486328125], [0.98681640625], [0.98681640625], [0.98583984375]], "bzythlfnhq": [[0.98193359375], [0.978515625], [0.97021484375], [0.97900390625], [0.97998046875], [0.96142578125], [0.95849609375], [0.97705078125], [0.96240234375], [0.98193359375], [0.96435546875], [0.982421875], [0.978515625], [0.97607421875], [0.93359375]], "etmcruaihe": [[0.96435546875], [0.96435546875], [0.97998046875], [0.98193359375], [0.9814453125], [0.98095703125], [0.95703125], [0.962890625], [0.98291015625], [0.97119140625], [0.97509765625], [0.982421875], [0.982421875], [0.984375], [0.9814453125]], "bsqgziaylx": [[0.98095703125], [0.982421875], [0.98291015625], [0.97998046875], [0.982421875], [0.97607421875], [0.982421875], [0.92431640625], [0.98388671875], [0.98095703125], [0.9814453125], [0.982421875], [0.98193359375], [0.982421875], [0.9814453125], [0.97900390625], [0.98291015625], [0.982421875], [0.98046875], [0.8251953125], [0.986328125], [0.9755859375], [0.9755859375], [0.98193359375], [0.98095703125], [0.98193359375], [0.98291015625], [0.9814453125], [0.9814453125], [0.9794921875]], "bpxckdzddv": [[0.9658203125], [0.95556640625], [0.9853515625], [0.82080078125], [0.9833984375], [0.98681640625], [0.98193359375], [0.94091796875], [0.97265625], [0.98193359375], [0.974609375], [0.97998046875], [0.9794921875], [0.708984375], [0.96923828125]], "dakiztgtnw": [[0.98291015625], [0.9794921875], [0.984375], [0.9716796875], [0.98291015625], [0.984375], [0.97802734375], [0.984375], [0.98291015625], [0.984375], [0.98388671875], [0.97998046875], [0.98193359375], [0.97802734375], [0.98388671875]], "dgxrqjdomn": [[0.9658203125], [0.9482421875], [0.982421875], [0.98291015625], [0.98486328125], [0.970703125], [0.97412109375], [0.98291015625], [0.96142578125], [0.98681640625], [0.98095703125], [0.98291015625], [0.97705078125], [0.97705078125], [0.98486328125]], "aytzyidmgs": [[0.98388671875], [0.98046875], [0.9814453125], [0.98046875], [0.974609375], [0.9814453125], [0.97412109375], [0.98095703125], [0.98046875], [0.98193359375], [0.98095703125], [0.9814453125], [0.97705078125], [0.98095703125], [0.9794921875]], "eepezmygaq": [[0.98291015625], [0.98388671875], [0.98046875], [0.984375], [0.986328125], [0.98583984375], [0.98486328125], [0.98388671875], [0.98046875], [0.98681640625], [0.98681640625], [0.984375], [0.98583984375], [0.9833984375], [0.98583984375]], "bofqajtwve": [[0.98193359375], [0.96240234375], [0.98486328125], [0.98291015625], [0.986328125], [0.982421875], [0.982421875], [0.9814453125], [0.98291015625], [0.9814453125], [0.982421875], [0.98193359375], [0.984375], [0.984375], [0.982421875]], "abarnvbtwb": [[0.984375], [0.98388671875], [0.97998046875], [0.984375], [0.96484375], [0.98046875], [0.984375], [0.9853515625], [0.98388671875], [0.9677734375], [0.984375], [0.98583984375], [0.96142578125], [0.98583984375], [0.98388671875]], "btmsngnqhv": [[0.98486328125], [0.98291015625], [0.98486328125], [0.984375], [0.9853515625], [0.9814453125], [0.98486328125], [0.986328125], [0.98486328125], [0.98486328125], [0.98388671875], [0.984375], [0.984375], [0.98291015625], [0.98388671875]], "ehfiekigla": [[0.984375], [0.97998046875], [0.97607421875], [0.9833984375], [0.98095703125], [0.98291015625], [0.982421875], [0.982421875], [0.982421875], [0.984375], [0.982421875], [0.9775390625], [0.98388671875], [0.98193359375], [0.98193359375]], "awnwkrqibf": [[0.98388671875], [0.9814453125], [0.982421875], [0.98388671875], [0.9833984375], [0.98193359375], [0.98291015625], [0.984375], [0.98193359375], [0.9833984375], [0.98388671875], [0.98486328125], [0.9833984375], [0.9853515625], [0.98583984375]], "eqvuznuwsa": [[0.9814453125], [0.9853515625], [0.84912109375], [0.98193359375], [0.98681640625], [0.9814453125], [0.98486328125], [0.98095703125], [0.984375], [0.9814453125], [0.9853515625], [0.98291015625], [0.98193359375], [0.9853515625], [0.98681640625], [0.97900390625]], "cizlkenljw": [[0.9853515625], [0.97998046875], [0.982421875], [0.98388671875], [0.92626953125], [0.98486328125], [0.97998046875], [0.9736328125], [0.97900390625], [0.97216796875], [0.98095703125], [0.986328125], [0.982421875], [0.9853515625], [0.984375]], "ahbweevwpv": [[0.93603515625], [0.89892578125], [0.98486328125], [0.96728515625], [0.98388671875], [0.98095703125], [0.97412109375], [0.95068359375], [0.75732421875], [0.85107421875], [0.94921875], [0.377197265625], [0.97216796875], [0.90771484375], [0.98193359375]], "ehccixxzoe": [[0.978515625], [0.9833984375], [0.9765625], [0.9853515625], [0.98193359375], [0.9814453125], [0.9755859375], [0.9755859375], [0.98193359375], [0.984375], [0.98486328125], [0.98046875], [0.9814453125], [0.984375], [0.97900390625], [0.98388671875], [0.9814453125], [0.98046875], [0.9814453125], [0.97607421875], [0.9765625], [0.951171875], [0.9658203125], [0.98486328125], [0.9609375], [0.8681640625]], "duycddgtrl": [[0.96875], [0.97412109375], [0.939453125], [0.97607421875], [0.88623046875], [0.97509765625], [0.98486328125], [0.95654296875], [0.7041015625], [0.96923828125], [0.96435546875], [0.974609375], [0.96240234375], [0.9736328125], [0.97705078125]], "eqjscdagiv": [[0.9833984375], [0.984375], [0.98291015625], [0.9775390625], [0.974609375], [0.98095703125], [0.97705078125], [0.9814453125], [0.9775390625], [0.98291015625], [0.96142578125], [0.9814453125], [0.951171875], [0.97412109375], [0.98193359375]], "bwipwzzxxu": [[0.9580078125], [0.97900390625], [0.97802734375], [0.98193359375], [0.93115234375], [0.97119140625], [0.97509765625], [0.97900390625], [0.97021484375], [0.974609375], [0.98193359375], [0.9775390625], [0.9736328125], [0.90478515625], [0.91943359375]], "asmpfjfzif": [[0.98046875], [0.97705078125], [0.982421875], [0.98291015625], [0.98193359375], [0.96875], [0.974609375], [0.97998046875], [0.98095703125], [0.98095703125], [0.97998046875], [0.97900390625], [0.98095703125], [0.9814453125], [0.97021484375]], "ekhacizpah": [[0.98779296875], [0.97802734375], [0.9794921875], [0.97998046875], [0.9736328125], [0.962890625], [0.9501953125], [0.97119140625], [0.98388671875], [0.97900390625], [0.978515625], [0.9775390625], [0.96630859375], [0.95556640625], [0.97705078125]], "btjlfpzbdu": [[0.986328125], [0.98681640625], [0.984375], [0.9853515625], [0.9873046875], [0.98779296875], [0.98583984375], [0.98681640625], [0.98681640625], [0.986328125], [0.98486328125], [0.98291015625], [0.984375], [0.98779296875], [0.9853515625]], "brwrlczjvi": [[0.9833984375], [0.95654296875], [0.97900390625], [0.982421875], [0.98095703125], [0.9794921875], [0.97705078125], [0.9677734375], [0.9814453125], [0.98193359375], [0.98095703125], [0.98046875], [0.9833984375], [0.9580078125], [0.98193359375], [0.98291015625], [0.984375], [0.97021484375], [0.9775390625], [0.94384765625], [0.98291015625], [0.94384765625], [0.982421875], [0.9814453125], [0.97998046875], [0.9814453125], [0.984375], [0.974609375], [0.9794921875], [0.97314453125]], "bxzakyopjf": [[0.9833984375], [0.9833984375], [0.9814453125], [0.984375], [0.98193359375], [0.97998046875], [0.9833984375], [0.9853515625], [0.98046875], [0.984375], [0.98388671875], [0.98046875], [0.98486328125], [0.98388671875], [0.9833984375]], "cppdvdejkc": [[0.94091796875], [0.9560546875], [0.92529296875], [0.96728515625], [0.98388671875], [0.98193359375], [0.95458984375], [0.9736328125], [0.953125], [0.96826171875], [0.96826171875], [0.98291015625], [0.982421875], [0.9716796875], [0.9794921875]], "edyncaijwx": [[0.98291015625], [0.98046875], [0.98193359375], [0.927734375], [0.97119140625], [0.97998046875], [0.9755859375], [0.97998046875], [0.98095703125], [0.96533203125], [0.97412109375], [0.98193359375], [0.984375], [0.982421875], [0.97021484375], [0.97998046875], [0.982421875], [0.9765625], [0.982421875], [0.97998046875], [0.9794921875], [0.98046875], [0.98095703125], [0.98095703125], [0.98193359375], [0.98193359375], [0.984375], [0.98388671875], [0.98388671875], [0.9794921875]], "bffwsjxghk": [[0.98046875], [0.98046875], [0.98291015625], [0.9814453125], [0.98388671875], [0.98193359375], [0.98291015625], [0.982421875], [0.9833984375], [0.9814453125], [0.98046875], [0.97998046875], [0.9814453125], [0.97998046875], [0.9794921875]], "cxrfacemmq": [[0.96337890625], [0.97802734375], [0.982421875], [0.98583984375], [0.98046875], [0.982421875], [0.98095703125], [0.98291015625], [0.9765625], [0.9814453125], [0.9814453125], [0.9775390625], [0.97509765625], [0.978515625], [0.97802734375]], "dbzpcjntve": [[0.98828125], [0.986328125], [0.98828125], [0.98583984375], [0.98779296875], [0.9873046875], [0.98779296875], [0.98681640625], [0.98876953125], [0.98583984375], [0.98486328125], [0.98486328125], [0.984375], [0.98974609375], [0.98388671875]], "agqphdxmwt": [[0.9814453125], [0.9814453125], [0.98291015625], [0.98046875], [0.97802734375], [0.98193359375], [0.9814453125], [0.9765625], [0.98046875], [0.982421875], [0.98193359375], [0.982421875], [0.982421875], [0.98046875], [0.98046875]], "dtocdfbwca": [[0.98583984375], [0.982421875], [0.98583984375], [0.9853515625], [0.982421875], [0.98583984375], [0.9853515625], [0.98291015625], [0.98486328125], [0.986328125], [0.9794921875], [0.98583984375], [0.98583984375], [0.98486328125], [0.98388671875]], "bdnaqemxmr": [[0.982421875], [0.98291015625], [0.97998046875], [0.984375], [0.98486328125], [0.98388671875], [0.98095703125], [0.9814453125], [0.98046875], [0.9775390625], [0.98583984375], [0.9814453125], [0.97705078125], [0.98779296875], [0.98193359375]], "crezycjqyk": [[0.97900390625], [0.98193359375], [0.98291015625], [0.98486328125], [0.9833984375], [0.984375], [0.98388671875], [0.9833984375], [0.98388671875], [0.98388671875], [0.98388671875], [0.98388671875], [0.9833984375], [0.98291015625], [0.9833984375]], "ebchwmwayp": [[0.9794921875], [0.95166015625], [0.97998046875], [0.98583984375], [0.97119140625], [0.98095703125], [0.98291015625], [0.97998046875], [0.98095703125], [0.9833984375], [0.96240234375], [0.96875], [0.9794921875], [0.982421875], [0.96826171875], [0.97802734375], [0.984375], [0.98193359375], [0.98388671875], [0.984375], [0.9697265625], [0.966796875], [0.98193359375], [0.98095703125], [0.9814453125], [0.96337890625]], "caifxvsozs": [[0.98046875], [0.97998046875], [0.98291015625], [0.9775390625], [0.97900390625], [0.98291015625], [0.9794921875], [0.986328125], [0.98095703125], [0.96435546875], [0.98193359375], [0.97607421875], [0.98095703125], [0.96923828125], [0.98486328125], [0.98046875], [0.9775390625], [0.982421875], [0.98193359375], [0.9833984375], [0.984375], [0.98193359375], [0.97900390625], [0.978515625], [0.9853515625], [0.97509765625], [0.9755859375], [0.978515625], [0.98291015625], [0.98193359375]], "blzydqdfem": [[0.9853515625], [0.984375], [0.98388671875], [0.98486328125], [0.9716796875], [0.984375], [0.9833984375], [0.98291015625], [0.98291015625], [0.978515625], [0.98486328125], [0.984375], [0.9755859375], [0.98486328125], [0.982421875]], "bbhtdfuqxq": [[0.98193359375], [0.9794921875], [0.9853515625], [0.98388671875], [0.98681640625], [0.9833984375], [0.98388671875], [0.98388671875], [0.97998046875], [0.9833984375], [0.9853515625], [0.9853515625], [0.984375], [0.982421875], [0.9873046875]], "dlpoieqvfb": [[0.966796875], [0.98046875], [0.98046875], [0.9794921875], [0.97998046875], [0.98193359375], [0.9755859375], [0.9736328125], [0.982421875], [0.98095703125], [0.982421875], [0.97900390625], [0.97119140625], [0.978515625], [0.986328125]], "cknyxaqouy": [[0.982421875], [0.98193359375], [0.98388671875], [0.98291015625], [0.9833984375], [0.9833984375], [0.98291015625], [0.98291015625], [0.98193359375], [0.98291015625], [0.9833984375], [0.9833984375], [0.982421875], [0.982421875], [0.98095703125]], "egghxjjmfg": [[0.98291015625], [0.9833984375], [0.9814453125], [0.98388671875], [0.9814453125], [0.9873046875], [0.97998046875], [0.9853515625], [0.98388671875], [0.97900390625], [0.98193359375], [0.9853515625], [0.9765625], [0.97998046875], [0.98291015625]], "cksanfsjhc": [[0.98681640625], [0.986328125], [0.98486328125], [0.9853515625], [0.98486328125], [0.98779296875], [0.984375], [0.98486328125], [0.9853515625], [0.986328125], [0.9873046875], [0.98583984375], [0.986328125], [0.986328125], [0.986328125]], "etohcvnzbj": [[0.96484375], [0.970703125], [0.8544921875], [0.9482421875], [0.87158203125], [0.62939453125], [0.97802734375], [0.97314453125], [0.9609375], [0.970703125], [0.966796875], [0.9560546875], [0.79638671875], [0.92919921875], [0.71240234375]], "bqqpbzjgup": [[0.9814453125], [0.98388671875], [0.98388671875], [0.98291015625], [0.98291015625], [0.9814453125], [0.98193359375], [0.98291015625], [0.98583984375], [0.98193359375], [0.98193359375], [0.982421875], [0.9833984375], [0.98193359375], [0.982421875], [0.9833984375]], "czfunozvwp": [[0.98681640625], [0.98388671875], [0.982421875], [0.98291015625], [0.9833984375], [0.98486328125], [0.96826171875], [0.982421875], [0.9853515625], [0.98486328125], [0.98095703125], [0.98681640625], [0.98291015625], [0.97705078125], [0.98486328125], [0.98291015625], [0.98388671875], [0.98193359375], [0.98291015625], [0.98486328125], [0.98193359375], [0.98486328125], [0.98291015625], [0.984375], [0.98291015625], [0.97900390625], [0.9833984375], [0.986328125], [0.982421875], [0.9833984375]], "dsdoseflas": [[0.97998046875], [0.98046875], [0.98193359375], [0.98095703125], [0.9814453125], [0.95751953125], [0.97705078125], [0.966796875], [0.97998046875], [0.9794921875], [0.97900390625], [0.9833984375], [0.9814453125], [0.98095703125], [0.98095703125]], "bvzjkezkms": [[0.9873046875], [0.98486328125], [0.98583984375], [0.9814453125], [0.9853515625], [0.98193359375], [0.97998046875], [0.9873046875], [0.98046875], [0.9736328125], [0.9833984375], [0.98583984375], [0.98486328125], [0.9892578125], [0.986328125]], "dbnygxtwek": [[0.97705078125], [0.98095703125], [0.96875], [0.9677734375], [0.96875], [0.94921875], [0.9794921875], [0.9765625], [0.97998046875], [0.9482421875], [0.94140625], [0.9794921875], [0.96923828125], [0.9814453125], [0.97900390625]], "dnyvfblxpm": [[0.97705078125], [0.98583984375], [0.984375], [0.98486328125], [0.98388671875], [0.982421875], [0.9833984375], [0.98388671875], [0.98583984375], [0.9853515625], [0.984375], [0.9833984375], [0.98095703125], [0.9853515625], [0.986328125]], "coadfnerlk": [[0.97265625], [0.98291015625], [0.982421875], [0.9814453125], [0.96337890625], [0.98291015625], [0.98291015625], [0.98291015625], [0.98291015625], [0.982421875], [0.98095703125], [0.9853515625], [0.9443359375], [0.98291015625], [0.98388671875]], "afoovlsmtx": [[0.97412109375], [0.98193359375], [0.93896484375], [0.97509765625], [0.9814453125], [0.96826171875], [0.9677734375], [0.97802734375], [0.9794921875], [0.98193359375], [0.97509765625], [0.97607421875], [0.9521484375], [0.982421875], [0.9794921875]], "dhevettufk": [[0.984375], [0.98388671875], [0.98095703125], [0.98193359375], [0.98095703125], [0.98193359375], [0.98388671875], [0.98486328125], [0.978515625], [0.98291015625], [0.98193359375], [0.98193359375], [0.97607421875], [0.98193359375], [0.9814453125]], "chtapglbcj": [[0.98291015625], [0.982421875], [0.98291015625], [0.98486328125], [0.98291015625], [0.9853515625], [0.9775390625], [0.9833984375], [0.97314453125], [0.9755859375], [0.97998046875], [0.98388671875], [0.97900390625], [0.98095703125], [0.97705078125]], "cpjxareypw": [[0.98193359375], [0.9814453125], [0.98193359375], [0.9853515625], [0.9853515625], [0.9658203125], [0.984375], [0.98193359375], [0.98193359375], [0.97265625], [0.98291015625], [0.97998046875], [0.98486328125], [0.97509765625], [0.9853515625]], "bourlmzsio": [[0.98095703125], [0.98388671875], [0.98486328125], [0.9814453125], [0.97998046875], [0.98486328125], [0.98291015625], [0.98291015625], [0.98486328125], [0.98291015625], [0.98291015625], [0.97900390625], [0.97900390625], [0.97998046875], [0.98291015625]], "cmbzllswnl": [[0.98388671875], [0.986328125], [0.9775390625], [0.85595703125], [0.984375], [0.984375], [0.9833984375], [0.9833984375], [0.98291015625], [0.96923828125], [0.98583984375], [0.9755859375], [0.9853515625], [0.9853515625], [0.9814453125], [0.9833984375]], "azsmewqghg": [[0.982421875], [0.9794921875], [0.98291015625], [0.9833984375], [0.982421875], [0.98486328125], [0.98193359375], [0.98095703125], [0.98095703125], [0.98095703125], [0.9833984375], [0.98095703125], [0.9814453125], [0.98193359375], [0.9814453125], [0.98291015625]], "drtbksnpol": [[0.98095703125], [0.97998046875], [0.97998046875], [0.97998046875], [0.9814453125], [0.974609375], [0.9794921875], [0.9814453125], [0.94921875], [0.97998046875], [0.97900390625], [0.98046875], [0.978515625], [0.98046875], [0.9775390625]], "ehtdtkmmli": [[0.98681640625], [0.98486328125], [0.98193359375], [0.984375], [0.984375], [0.9833984375], [0.97998046875], [0.982421875], [0.98388671875], [0.9853515625], [0.98486328125], [0.984375], [0.98291015625], [0.982421875], [0.9765625]], "atkdltyyen": [[0.9833984375], [0.978515625], [0.97607421875], [0.984375], [0.98095703125], [0.9794921875], [0.98193359375], [0.97802734375], [0.98291015625], [0.98388671875], [0.982421875], [0.98291015625], [0.98291015625], [0.98291015625], [0.9833984375]], "dzvyfiarrq": [[0.9833984375], [0.98291015625], [0.982421875], [0.9853515625], [0.98388671875], [0.9873046875], [0.984375], [0.97607421875], [0.9814453125], [0.98486328125], [0.9814453125], [0.984375], [0.98388671875], [0.9814453125], [0.984375]], "dkuayagnmc": [[0.9794921875], [0.97607421875], [0.978515625], [0.97802734375], [0.97412109375], [0.98193359375], [0.9794921875], [0.9619140625], [0.98095703125], [0.98095703125], [0.9814453125], [0.97607421875], [0.970703125], [0.9765625], [0.982421875]], "adylbeequz": [[0.94189453125], [0.9716796875], [0.94921875], [0.978515625], [0.970703125], [0.96923828125], [0.97900390625], [0.97607421875], [0.96875], [0.98046875], [0.873046875], [0.97705078125], [0.98291015625], [0.966796875], [0.97412109375]], "djxdyjopjd": [[0.97412109375], [0.98388671875], [0.98291015625], [0.97998046875], [0.982421875], [0.9775390625], [0.96142578125], [0.98291015625], [0.9736328125], [0.9833984375], [0.98046875], [0.98291015625], [0.98095703125], [0.9794921875], [0.97412109375], [0.97998046875]], "bejhvclboh": [[0.96875], [0.96875], [0.95263671875], [0.9814453125], [0.986328125], [0.9677734375], [0.9833984375], [0.9765625], [0.97265625], [0.94384765625], [0.98046875], [0.97802734375], [0.9775390625], [0.98388671875], [0.96630859375], [0.98193359375]], "dfbpceeaox": [[0.9853515625], [0.98388671875], [0.984375], [0.9814453125], [0.9833984375], [0.98291015625], [0.9873046875], [0.98291015625], [0.98291015625], [0.982421875], [0.98046875], [0.98095703125], [0.9833984375], [0.98291015625], [0.984375]], "ehdkmxgtxh": [[0.98046875], [0.98486328125], [0.984375], [0.9765625], [0.97509765625], [0.978515625], [0.98291015625], [0.92041015625], [0.98046875], [0.9677734375], [0.984375], [0.98193359375], [0.984375], [0.98388671875], [0.95361328125], [0.9775390625]], "cyxlcuyznd": [[0.97265625], [0.98095703125], [0.97119140625], [0.98193359375], [0.984375], [0.984375], [0.9775390625], [0.9794921875], [0.9697265625], [0.986328125], [0.97607421875], [0.9794921875], [0.9814453125], [0.97314453125], [0.9853515625]], "acqfdwsrhi": [[0.95556640625], [0.982421875], [0.98095703125], [0.97802734375], [0.97119140625], [0.98291015625], [0.98388671875], [0.9521484375], [0.97998046875], [0.986328125], [0.97265625], [0.98193359375], [0.95166015625], [0.9736328125], [0.9833984375]], "alaijyygdv": [[0.986328125], [0.982421875], [0.9775390625], [0.98388671875], [0.974609375], [0.98095703125], [0.9833984375], [0.97998046875], [0.9814453125], [0.9853515625], [0.9814453125], [0.984375], [0.97900390625], [0.98583984375], [0.9794921875]], "ccfoszqabv": [[0.97705078125], [0.97900390625], [0.982421875], [0.9755859375], [0.98388671875], [0.96142578125], [0.9814453125], [0.96533203125], [0.9619140625], [0.9345703125], [0.8916015625], [0.96435546875], [0.9765625], [0.98291015625], [0.95654296875]]}, "targets": {"avmjormvsx": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "curpwogllm": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "aorjvbyxhw": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "cprhtltsjp": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "avnqydkqjj": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "bulkxhhknf": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "ekcrtigpab": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "dhkwmjxwrn": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "apatcsqejh": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "dzyuwjkjui": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "emaalmsonj": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "bgwmmujlmc": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "eckvhdusax": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "ajwpjhrbcv": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "efwfxwwlbw": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "emfbhytfhc": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "bnjcdrfuov": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "drcyabprvt": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "cdphtzqrvp": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "ellavthztb": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "dbtbbhakdv": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "btohlidmru": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "bzythlfnhq": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "etmcruaihe": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "bsqgziaylx": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "bpxckdzddv": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "dakiztgtnw": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "dgxrqjdomn": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "aytzyidmgs": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "eepezmygaq": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "bofqajtwve": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "abarnvbtwb": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "btmsngnqhv": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "ehfiekigla": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "awnwkrqibf": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "eqvuznuwsa": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "cizlkenljw": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "ahbweevwpv": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "ehccixxzoe": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "duycddgtrl": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "eqjscdagiv": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "bwipwzzxxu": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "asmpfjfzif": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "ekhacizpah": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "btjlfpzbdu": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "brwrlczjvi": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "bxzakyopjf": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "cppdvdejkc": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "edyncaijwx": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "bffwsjxghk": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "cxrfacemmq": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "dbzpcjntve": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "agqphdxmwt": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "dtocdfbwca": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "bdnaqemxmr": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "crezycjqyk": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "ebchwmwayp": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "caifxvsozs": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "blzydqdfem": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "bbhtdfuqxq": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "dlpoieqvfb": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "cknyxaqouy": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "egghxjjmfg": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "cksanfsjhc": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "etohcvnzbj": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "bqqpbzjgup": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "czfunozvwp": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "dsdoseflas": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "bvzjkezkms": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "dbnygxtwek": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "dnyvfblxpm": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "coadfnerlk": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "afoovlsmtx": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "dhevettufk": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "chtapglbcj": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "cpjxareypw": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "bourlmzsio": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "cmbzllswnl": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "azsmewqghg": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "drtbksnpol": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "ehtdtkmmli": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "atkdltyyen": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "dzvyfiarrq": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "dkuayagnmc": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "adylbeequz": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "djxdyjopjd": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "bejhvclboh": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "dfbpceeaox": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "ehdkmxgtxh": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "cyxlcuyznd": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]], "acqfdwsrhi": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "alaijyygdv": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]], "ccfoszqabv": [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]]}}
preprocess_data.sh ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ DATA_ROOT=$1
2
+ echo "Extracting bounding boxes from original videos"
3
+ PYTHONPATH=. python preprocessing/detect_original_faces.py --root-dir $DATA_ROOT
4
+
5
+ echo "Extracting crops as pngs"
6
+ PYTHONPATH=. python preprocessing/extract_crops.py --root-dir $DATA_ROOT --crops-dir crops
7
+
8
+ echo "Extracting landmarks"
9
+ PYTHONPATH=. python preprocessing/generate_landmarks.py --root-dir $DATA_ROOT
10
+
11
+ echo "Extracting SSIM masks"
12
+ PYTHONPATH=. python preprocessing/generate_diffs.py --root-dir $DATA_ROOT
13
+
14
+ echo "Generate folds"
15
+ PYTHONPATH=. python preprocessing/generate_folds.py --root-dir $DATA_ROOT --out folds.csv
preprocessing/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .face_detector import *
preprocessing/__pycache__/face_detector.cpython-39.pyc ADDED
Binary file (3.46 kB). View file
 
preprocessing/__pycache__/utils.cpython-39.pyc ADDED
Binary file (1.64 kB). View file
 
preprocessing/compress_videos.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ import random
4
+ import subprocess
5
+
6
+ os.environ["MKL_NUM_THREADS"] = "1"
7
+ os.environ["NUMEXPR_NUM_THREADS"] = "1"
8
+ os.environ["OMP_NUM_THREADS"] = "1"
9
+ from functools import partial
10
+ from glob import glob
11
+ from multiprocessing.pool import Pool
12
+ from os import cpu_count
13
+
14
+ import cv2
15
+
16
+ cv2.ocl.setUseOpenCL(False)
17
+ cv2.setNumThreads(0)
18
+ from tqdm import tqdm
19
+
20
+
21
+ def compress_video(video, root_dir):
22
+ parent_dir = video.split("/")[-2]
23
+ out_dir = os.path.join(root_dir, "compressed", parent_dir)
24
+ os.makedirs(out_dir, exist_ok=True)
25
+ video_name = video.split("/")[-1]
26
+ out_path = os.path.join(out_dir, video_name)
27
+ lvl = random.choice([23, 28, 32])
28
+ command = "ffmpeg -i {} -c:v libx264 -crf {} -threads 1 {}".format(video, lvl, out_path)
29
+ try:
30
+ subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT)
31
+ except Exception as e:
32
+ print("Could not process vide", str(e))
33
+
34
+
35
+ if __name__ == '__main__':
36
+ parser = argparse.ArgumentParser(
37
+ description="Extracts jpegs from video")
38
+ parser.add_argument("--root-dir", help="root directory", default="/mnt/sota/datasets/deepfake")
39
+
40
+ args = parser.parse_args()
41
+ videos = [video_path for video_path in glob(os.path.join(args.root_dir, "*/*.mp4"))]
42
+ with Pool(processes=cpu_count() - 2) as p:
43
+ with tqdm(total=len(videos)) as pbar:
44
+ for v in p.imap_unordered(partial(compress_video, root_dir=args.root_dir), videos):
45
+ pbar.update()
preprocessing/detect_original_faces.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import json
3
+ import os
4
+ from os import cpu_count
5
+ from typing import Type
6
+ import dill
7
+ from torch.utils.data.dataloader import DataLoader
8
+ from tqdm import tqdm
9
+
10
+ # import face_detector, VideoDataset
11
+ import face_detector
12
+ from face_detector import VideoDataset
13
+ from face_detector import VideoFaceDetector
14
+ from utils import get_original_video_paths
15
+
16
+
17
+ def parse_args():
18
+ parser = argparse.ArgumentParser(
19
+ description="Process a original videos with face detector")
20
+ parser.add_argument("--root-dir", help="root directory")
21
+ parser.add_argument("--detector-type", help="type of the detector", default="FacenetDetector",
22
+ choices=["FacenetDetector"])
23
+
24
+ args = parser.parse_args()
25
+ return args
26
+
27
+ def collate_fn(batch) -> tuple:
28
+ return tuple(zip(*batch))
29
+
30
+ def process_videos(videos, root_dir, detector_cls: Type[VideoFaceDetector]):
31
+ detector = face_detector.__dict__[detector_cls](device="cuda:0")
32
+ dataset = VideoDataset(videos)
33
+ # loader = DataLoader(dataset, shuffle=False, num_workers=cpu_count() - 2, batch_size=1, collate_fn=lambda x: x)
34
+ # loader = DataLoader(dataset, shuffle=False, num_workers=1, batch_size=1, collate_fn=lambda x: x)
35
+ # loader = DataLoader(dataset, shuffle=False, num_workers=1, batch_size=1)
36
+ loader = DataLoader(dataset, shuffle=False, num_workers=4, batch_size=1, collate_fn=collate_fn)
37
+ for item in tqdm(loader):
38
+ result = {}
39
+ # video, indices, frames = item[0]
40
+ video, indices, frames = item
41
+ video = video[0]
42
+ indices = indices[0]
43
+ frames = frames[0]
44
+ batches = [frames[i:i + detector._batch_size] for i in range(0, len(frames), detector._batch_size)]
45
+ for j, frames in enumerate(batches):
46
+ result.update({int(j * detector._batch_size) + i : b for i, b in zip(indices, detector._detect_faces(frames))})
47
+ id = os.path.splitext(os.path.basename(video))[0]
48
+ out_dir = os.path.join(root_dir, "boxes")
49
+ os.makedirs(out_dir, exist_ok=True)
50
+ with open(os.path.join(out_dir, "{}.json".format(id)), "w") as f:
51
+ json.dump(result, f)
52
+
53
+
54
+
55
+
56
+ def main():
57
+ args = parse_args()
58
+ originals = get_original_video_paths(args.root_dir)
59
+ process_videos(originals, args.root_dir, args.detector_type)
60
+ z = 2
61
+
62
+ if __name__ == "__main__":
63
+ main()
preprocessing/extract_crops.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import json
3
+ import os
4
+ from os import cpu_count
5
+ from pathlib import Path
6
+
7
+ os.environ["MKL_NUM_THREADS"] = "1"
8
+ os.environ["NUMEXPR_NUM_THREADS"] = "1"
9
+ os.environ["OMP_NUM_THREADS"] = "1"
10
+ from functools import partial
11
+ from glob import glob
12
+ from multiprocessing.pool import Pool
13
+
14
+ import cv2
15
+
16
+ cv2.ocl.setUseOpenCL(False)
17
+ cv2.setNumThreads(0)
18
+ from tqdm import tqdm
19
+
20
+
21
+ def extract_video(param, root_dir, crops_dir):
22
+ video, bboxes_path = param
23
+ with open(bboxes_path, "r") as bbox_f:
24
+ bboxes_dict = json.load(bbox_f)
25
+
26
+ capture = cv2.VideoCapture(video)
27
+ frames_num = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
28
+
29
+ for i in range(frames_num):
30
+ capture.grab()
31
+ if i % 10 != 0:
32
+ continue
33
+ success, frame = capture.retrieve()
34
+ if not success or str(i) not in bboxes_dict:
35
+ continue
36
+ id = os.path.splitext(os.path.basename(video))[0]
37
+ crops = []
38
+ bboxes = bboxes_dict[str(i)]
39
+ if bboxes is None:
40
+ continue
41
+ for bbox in bboxes:
42
+ xmin, ymin, xmax, ymax = [int(b * 2) for b in bbox]
43
+ w = xmax - xmin
44
+ h = ymax - ymin
45
+ p_h = h // 3
46
+ p_w = w // 3
47
+ crop = frame[max(ymin - p_h, 0):ymax + p_h, max(xmin - p_w, 0):xmax + p_w]
48
+ h, w = crop.shape[:2]
49
+ crops.append(crop)
50
+ img_dir = os.path.join(root_dir, crops_dir, id)
51
+ os.makedirs(img_dir, exist_ok=True)
52
+ for j, crop in enumerate(crops):
53
+ cv2.imwrite(os.path.join(img_dir, "{}_{}.png".format(i, j)), crop)
54
+
55
+
56
+ def get_video_paths(root_dir):
57
+ paths = []
58
+ for json_path in glob(os.path.join(root_dir, "*/metadata.json")):
59
+ dir = Path(json_path).parent
60
+ with open(json_path, "r") as f:
61
+ metadata = json.load(f)
62
+ for k, v in metadata.items():
63
+ original = v.get("original", None)
64
+ if not original:
65
+ original = k
66
+ bboxes_path = os.path.join(root_dir, "boxes", original[:-4] + ".json")
67
+ if not os.path.exists(bboxes_path):
68
+ continue
69
+ paths.append((os.path.join(dir, k), bboxes_path))
70
+
71
+ return paths
72
+
73
+
74
+ if __name__ == '__main__':
75
+ parser = argparse.ArgumentParser(
76
+ description="Extracts crops from video")
77
+ parser.add_argument("--root-dir", help="root directory")
78
+ parser.add_argument("--crops-dir", help="crops directory")
79
+
80
+ args = parser.parse_args()
81
+ os.makedirs(os.path.join(args.root_dir, args.crops_dir), exist_ok=True)
82
+ params = get_video_paths(args.root_dir)
83
+ with Pool(processes=cpu_count()) as p:
84
+ with tqdm(total=len(params)) as pbar:
85
+ for v in p.imap_unordered(partial(extract_video, root_dir=args.root_dir, crops_dir=args.crops_dir), params):
86
+ pbar.update()
87
+ z = 2
88
+
preprocessing/extract_images.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ os.environ["MKL_NUM_THREADS"] = "1"
4
+ os.environ["NUMEXPR_NUM_THREADS"] = "1"
5
+ os.environ["OMP_NUM_THREADS"] = "1"
6
+ from functools import partial
7
+ from glob import glob
8
+ from multiprocessing.pool import Pool
9
+ from os import cpu_count
10
+
11
+ import cv2
12
+ cv2.ocl.setUseOpenCL(False)
13
+ cv2.setNumThreads(0)
14
+ from tqdm import tqdm
15
+
16
+
17
+ def extract_video(video, root_dir):
18
+ capture = cv2.VideoCapture(video)
19
+ frames_num = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
20
+
21
+ for i in range(frames_num):
22
+ capture.grab()
23
+ success, frame = capture.retrieve()
24
+ if not success:
25
+ continue
26
+ id = os.path.splitext(os.path.basename(video))[0]
27
+ cv2.imwrite(os.path.join(root_dir, "jpegs", "{}_{}.jpg".format(id, i)), frame, [cv2.IMWRITE_JPEG_QUALITY, 100])
28
+
29
+
30
+
31
+ if __name__ == '__main__':
32
+ parser = argparse.ArgumentParser(
33
+ description="Extracts jpegs from video")
34
+ parser.add_argument("--root-dir", help="root directory")
35
+
36
+ args = parser.parse_args()
37
+ os.makedirs(os.path.join(args.root_dir, "jpegs"), exist_ok=True)
38
+ videos = [video_path for video_path in glob(os.path.join(args.root_dir, "*/*.mp4"))]
39
+ with Pool(processes=cpu_count() - 2) as p:
40
+ with tqdm(total=len(videos)) as pbar:
41
+ for v in p.imap_unordered(partial(extract_video, root_dir=args.root_dir), videos):
42
+ pbar.update()
preprocessing/face_detector.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ os.environ["MKL_NUM_THREADS"] = "1"
3
+ os.environ["NUMEXPR_NUM_THREADS"] = "1"
4
+ os.environ["OMP_NUM_THREADS"] = "1"
5
+
6
+ from abc import ABC, abstractmethod
7
+ from collections import OrderedDict
8
+ from typing import List
9
+
10
+ import cv2
11
+ cv2.ocl.setUseOpenCL(False)
12
+ cv2.setNumThreads(0)
13
+
14
+ from PIL import Image
15
+ from facenet_pytorch.models.mtcnn import MTCNN
16
+ from torch.utils.data import Dataset
17
+
18
+
19
+ class VideoFaceDetector(ABC):
20
+
21
+ def __init__(self, **kwargs) -> None:
22
+ super().__init__()
23
+
24
+ @property
25
+ @abstractmethod
26
+ def _batch_size(self) -> int:
27
+ pass
28
+
29
+ @abstractmethod
30
+ def _detect_faces(self, frames) -> List:
31
+ pass
32
+
33
+
34
+ class FacenetDetector(VideoFaceDetector):
35
+
36
+ def __init__(self, device="cuda:0") -> None:
37
+ super().__init__()
38
+ self.detector = MTCNN(margin=0,thresholds=[0.85, 0.95, 0.95], device=device)
39
+
40
+ def _detect_faces(self, frames) -> List:
41
+ batch_boxes, *_ = self.detector.detect(frames, landmarks=False)
42
+ return [b.tolist() if b is not None else None for b in batch_boxes]
43
+
44
+ @property
45
+ def _batch_size(self):
46
+ return 32
47
+
48
+
49
+ class VideoDataset(Dataset):
50
+
51
+ def __init__(self, videos) -> None:
52
+ super().__init__()
53
+ self.videos = videos
54
+
55
+ def __getitem__(self, index: int):
56
+ video = self.videos[index]
57
+ capture = cv2.VideoCapture(video)
58
+ frames_num = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
59
+ frames = OrderedDict()
60
+ for i in range(frames_num):
61
+ capture.grab()
62
+ success, frame = capture.retrieve()
63
+ if not success:
64
+ continue
65
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
66
+ frame = Image.fromarray(frame)
67
+ frame = frame.resize(size=[s // 2 for s in frame.size])
68
+ frames[i] = frame
69
+ return video, list(frames.keys()), list(frames.values())
70
+
71
+ def __len__(self) -> int:
72
+ return len(self.videos)
preprocessing/face_encodings.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ from functools import partial
4
+ from multiprocessing.pool import Pool
5
+
6
+ from tqdm import tqdm
7
+
8
+ from preprocessing.utils import get_original_video_paths
9
+
10
+ os.environ["MKL_NUM_THREADS"] = "1"
11
+ os.environ["NUMEXPR_NUM_THREADS"] = "1"
12
+ os.environ["OMP_NUM_THREADS"] = "1"
13
+
14
+ import random
15
+
16
+ import face_recognition
17
+ import numpy as np
18
+
19
+
20
+ def write_face_encodings(video, root_dir):
21
+ video_id, *_ = os.path.splitext(video)
22
+ crops_dir = os.path.join(root_dir, "crops", video_id)
23
+ if not os.path.exists(crops_dir):
24
+ return
25
+ crop_files = [f for f in os.listdir(crops_dir) if f.endswith("jpg")]
26
+ if crop_files:
27
+ crop_files = random.sample(crop_files, min(10, len(crop_files)))
28
+ encodings = []
29
+ for crop_file in crop_files:
30
+ img = face_recognition.load_image_file(os.path.join(crops_dir, crop_file))
31
+ encoding = face_recognition.face_encodings(img, num_jitters=10)
32
+ if encoding:
33
+ encodings.append(encoding[0])
34
+ np.save(os.path.join(crops_dir, "encodings"), encodings)
35
+
36
+
37
+ def parse_args():
38
+ parser = argparse.ArgumentParser(
39
+ description="Extract 10 crops encodings for each video")
40
+ parser.add_argument("--root-dir", help="root directory", default="/home/selim/datasets/deepfake")
41
+ args = parser.parse_args()
42
+ return args
43
+
44
+
45
+ def main():
46
+ args = parse_args()
47
+ originals = get_original_video_paths(args.root_dir, basename=True)
48
+ with Pool(processes=os.cpu_count() - 4) as p:
49
+ with tqdm(total=len(originals)) as pbar:
50
+ for v in p.imap_unordered(partial(write_face_encodings, root_dir=args.root_dir), originals):
51
+ pbar.update()
52
+
53
+
54
+ if __name__ == '__main__':
55
+ main()
preprocessing/generate_diffs.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+
4
+ os.environ["MKL_NUM_THREADS"] = "1"
5
+ os.environ["NUMEXPR_NUM_THREADS"] = "1"
6
+ os.environ["OMP_NUM_THREADS"] = "1"
7
+ # from skimage.measure import compare_ssim
8
+ from skimage.metrics import structural_similarity as ssim
9
+
10
+ from functools import partial
11
+ from multiprocessing.pool import Pool
12
+
13
+ from tqdm import tqdm
14
+
15
+ from utils import get_original_with_fakes
16
+
17
+ import cv2
18
+
19
+ cv2.ocl.setUseOpenCL(False)
20
+ cv2.setNumThreads(0)
21
+
22
+ import numpy as np
23
+
24
+ cache = {}
25
+
26
+
27
+ def save_diffs(pair, root_dir):
28
+ ori_id, fake_id = pair
29
+ ori_dir = os.path.join(root_dir, "crops", ori_id)
30
+ fake_dir = os.path.join(root_dir, "crops", fake_id)
31
+ diff_dir = os.path.join(root_dir, "diffs", fake_id)
32
+ os.makedirs(diff_dir, exist_ok=True)
33
+ for frame in range(320):
34
+ if frame % 10 != 0:
35
+ continue
36
+ for actor in range(2):
37
+ image_id = "{}_{}.png".format(frame, actor)
38
+ diff_image_id = "{}_{}_diff.png".format(frame, actor)
39
+ ori_path = os.path.join(ori_dir, image_id)
40
+ fake_path = os.path.join(fake_dir, image_id)
41
+ diff_path = os.path.join(diff_dir, diff_image_id)
42
+ if os.path.exists(ori_path) and os.path.exists(fake_path):
43
+ img1 = cv2.imread(ori_path, cv2.IMREAD_COLOR)
44
+ img2 = cv2.imread(fake_path, cv2.IMREAD_COLOR)
45
+ try:
46
+ d, a = ssim(img1, img2, multichannel=True, full=True)
47
+ a = 1 - a
48
+ diff = (a * 255).astype(np.uint8)
49
+ diff = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
50
+ cv2.imwrite(diff_path, diff)
51
+ except:
52
+ pass
53
+
54
+ def parse_args():
55
+ parser = argparse.ArgumentParser(
56
+ description="Extract image diffs")
57
+ parser.add_argument("--root-dir", help="root directory", default="/mnt/sota/datasets/deepfake")
58
+ args = parser.parse_args()
59
+ return args
60
+
61
+
62
+ def main():
63
+ args = parse_args()
64
+ pairs = get_original_with_fakes(args.root_dir)
65
+ os.makedirs(os.path.join(args.root_dir, "diffs"), exist_ok=True)
66
+ with Pool(processes=os.cpu_count() - 2) as p:
67
+ with tqdm(total=len(pairs)) as pbar:
68
+ func = partial(save_diffs, root_dir=args.root_dir)
69
+ for v in p.imap_unordered(func, pairs):
70
+ pbar.update()
71
+
72
+
73
+ if __name__ == '__main__':
74
+ main()
75
+ z=2
preprocessing/generate_folds.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import json
3
+ import os
4
+ import random
5
+ from functools import partial
6
+ from multiprocessing.pool import Pool
7
+ from pathlib import Path
8
+
9
+ os.environ["MKL_NUM_THREADS"] = "1"
10
+ os.environ["NUMEXPR_NUM_THREADS"] = "1"
11
+ os.environ["OMP_NUM_THREADS"] = "1"
12
+ import pandas as pd
13
+
14
+ from tqdm import tqdm
15
+
16
+ from utils import get_original_with_fakes
17
+
18
+ import cv2
19
+
20
+ cv2.ocl.setUseOpenCL(False)
21
+ cv2.setNumThreads(0)
22
+
23
+
24
+ def get_paths(vid, label, root_dir):
25
+ ori_vid, fake_vid = vid
26
+ ori_dir = os.path.join(root_dir, "crops", ori_vid)
27
+ fake_dir = os.path.join(root_dir, "crops", fake_vid)
28
+ data = []
29
+ for frame in range(320):
30
+ if frame % 10 != 0:
31
+ continue
32
+ for actor in range(2):
33
+ image_id = "{}_{}.png".format(frame, actor)
34
+ ori_img_path = os.path.join(ori_dir, image_id)
35
+ fake_img_path = os.path.join(fake_dir, image_id)
36
+ img_path = ori_img_path if label == 0 else fake_img_path
37
+ try:
38
+ # img = cv2.imread(img_path)[..., ::-1]
39
+ if os.path.exists(img_path):
40
+ data.append([img_path, label, ori_vid])
41
+ except:
42
+ pass
43
+ return data
44
+
45
+
46
+ def parse_args():
47
+ parser = argparse.ArgumentParser(
48
+ description="Generate Folds")
49
+ parser.add_argument("--root-dir", help="root directory", default="/mnt/sota/datasets/deepfake")
50
+ parser.add_argument("--out", type=str, default="folds02.csv", help="CSV file to save")
51
+ parser.add_argument("--seed", type=int, default=777, help="Seed to split, default 777")
52
+ parser.add_argument("--n_splits", type=int, default=2, help="Num folds, default 10")
53
+ args = parser.parse_args()
54
+
55
+ return args
56
+
57
+
58
+ def main():
59
+ args = parse_args()
60
+ ori_fakes = get_original_with_fakes(args.root_dir)
61
+ # sz = 50 // args.n_splits
62
+ sz = 2 // args.n_splits
63
+ folds = []
64
+ for fold in range(args.n_splits):
65
+ # folds.append(list(range(sz * fold, sz * fold + sz if fold < args.n_splits - 1 else 50)))
66
+ folds.append(list(range(sz * fold, sz * fold + sz if fold < args.n_splits - 1 else 2)))
67
+ print(folds)
68
+ video_fold = {}
69
+ for d in os.listdir(args.root_dir):
70
+ if "dfdc" in d:
71
+ part = int(d.split("_")[-1])
72
+ for f in os.listdir(os.path.join(args.root_dir, d)):
73
+ if "metadata.json" in f:
74
+ with open(os.path.join(args.root_dir, d, "metadata.json")) as metadata_json:
75
+ metadata = json.load(metadata_json)
76
+
77
+ for k, v in metadata.items():
78
+ fold = None
79
+ for i, fold_dirs in enumerate(folds):
80
+ if part in fold_dirs:
81
+ fold = i
82
+ break
83
+ assert fold is not None
84
+ video_id = k[:-4]
85
+ video_fold[video_id] = fold
86
+ for fold in range(len(folds)):
87
+ holdoutset = {k for k, v in video_fold.items() if v == fold}
88
+ trainset = {k for k, v in video_fold.items() if v != fold}
89
+ assert holdoutset.isdisjoint(trainset), "Folds have leaks"
90
+ data = []
91
+ ori_ori = set([(ori, ori) for ori, fake in ori_fakes])
92
+ with Pool(processes=os.cpu_count()) as p:
93
+ with tqdm(total=len(ori_ori)) as pbar:
94
+ func = partial(get_paths, label=0, root_dir=args.root_dir)
95
+ for v in p.imap_unordered(func, ori_ori):
96
+ pbar.update()
97
+ data.extend(v)
98
+ with tqdm(total=len(ori_fakes)) as pbar:
99
+ func = partial(get_paths, label=1, root_dir=args.root_dir)
100
+ for v in p.imap_unordered(func, ori_fakes):
101
+ pbar.update()
102
+ data.extend(v)
103
+ fold_data = []
104
+ for img_path, label, ori_vid in data:
105
+ path = Path(img_path)
106
+ video = path.parent.name
107
+ file = path.name
108
+ assert video_fold[video] == video_fold[ori_vid], "original video and fake have leak {} {}".format(ori_vid,
109
+ video)
110
+ fold_data.append([video, file, label, ori_vid, int(file.split("_")[0]), video_fold[video]])
111
+ random.shuffle(fold_data)
112
+ pd.DataFrame(fold_data, columns=["video", "file", "label", "original", "frame", "fold"]).to_csv(args.out, index=False)
113
+
114
+
115
+ if __name__ == '__main__':
116
+ main()
117
+ z = 2
preprocessing/generate_landmarks.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ from functools import partial
4
+ from multiprocessing.pool import Pool
5
+
6
+
7
+
8
+ os.environ["MKL_NUM_THREADS"] = "1"
9
+ os.environ["NUMEXPR_NUM_THREADS"] = "1"
10
+ os.environ["OMP_NUM_THREADS"] = "1"
11
+
12
+ from tqdm import tqdm
13
+
14
+
15
+ import cv2
16
+
17
+ cv2.ocl.setUseOpenCL(False)
18
+ cv2.setNumThreads(0)
19
+ from utils import get_original_video_paths
20
+
21
+ from PIL import Image
22
+ from facenet_pytorch.models.mtcnn import MTCNN
23
+ import numpy as np
24
+
25
+ detector = MTCNN(margin=0, thresholds=[0.65, 0.75, 0.75], device="cpu")
26
+
27
+
28
+ def save_landmarks(ori_id, root_dir):
29
+ ori_id = ori_id[:-4]
30
+ ori_dir = os.path.join(root_dir, "crops", ori_id)
31
+ landmark_dir = os.path.join(root_dir, "landmarks", ori_id)
32
+ os.makedirs(landmark_dir, exist_ok=True)
33
+ for frame in range(320):
34
+ if frame % 10 != 0:
35
+ continue
36
+ for actor in range(2):
37
+ image_id = "{}_{}.png".format(frame, actor)
38
+ landmarks_id = "{}_{}".format(frame, actor)
39
+ ori_path = os.path.join(ori_dir, image_id)
40
+ landmark_path = os.path.join(landmark_dir, landmarks_id)
41
+
42
+ if os.path.exists(ori_path):
43
+ try:
44
+ image_ori = cv2.imread(ori_path, cv2.IMREAD_COLOR)[...,::-1]
45
+ frame_img = Image.fromarray(image_ori)
46
+ batch_boxes, conf, landmarks = detector.detect(frame_img, landmarks=True)
47
+ if landmarks is not None:
48
+ landmarks = np.around(landmarks[0]).astype(np.int16)
49
+ np.save(landmark_path, landmarks)
50
+ except Exception as e:
51
+ print(e)
52
+ pass
53
+
54
+
55
+ def parse_args():
56
+ parser = argparse.ArgumentParser(
57
+ description="Extract image landmarks")
58
+ parser.add_argument("--root-dir", help="root directory", default="/mnt/sota/datasets/deepfake")
59
+ args = parser.parse_args()
60
+ return args
61
+
62
+
63
+ def main():
64
+ args = parse_args()
65
+ ids = get_original_video_paths(args.root_dir, basename=True)
66
+ os.makedirs(os.path.join(args.root_dir, "landmarks"), exist_ok=True)
67
+ with Pool(processes=os.cpu_count()-8) as p:
68
+ with tqdm(total=len(ids)) as pbar:
69
+ func = partial(save_landmarks, root_dir=args.root_dir)
70
+ for v in p.imap_unordered(func, ids):
71
+ pbar.update()
72
+
73
+
74
+ if __name__ == '__main__':
75
+ main()
76
+ Z = 2
preprocessing/utils.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from glob import glob
4
+ from pathlib import Path
5
+
6
+
7
+ def get_original_video_paths(root_dir, basename=False):
8
+ originals = set()
9
+ originals_v = set()
10
+ for json_path in glob(os.path.join(root_dir, "*/metadata.json")):
11
+ dir = Path(json_path).parent
12
+ with open(json_path, "r") as f:
13
+ metadata = json.load(f)
14
+ for k, v in metadata.items():
15
+ original = v.get("original", None)
16
+ if v["label"] == "REAL":
17
+ original = k
18
+ originals_v.add(original)
19
+ originals.add(os.path.join(dir, original))
20
+ originals = list(originals)
21
+ originals_v = list(originals_v)
22
+ print(len(originals))
23
+ return originals_v if basename else originals
24
+
25
+
26
+ def get_original_with_fakes(root_dir):
27
+ pairs = []
28
+ for json_path in glob(os.path.join(root_dir, "*/metadata.json")):
29
+ with open(json_path, "r") as f:
30
+ metadata = json.load(f)
31
+ for k, v in metadata.items():
32
+ original = v.get("original", None)
33
+ if v["label"] == "FAKE":
34
+ pairs.append((original[:-4], k[:-4] ))
35
+
36
+ return pairs
37
+
38
+
39
+ def get_originals_and_fakes(root_dir):
40
+ originals = []
41
+ fakes = []
42
+ for json_path in glob(os.path.join(root_dir, "*/metadata.json")):
43
+ with open(json_path, "r") as f:
44
+ metadata = json.load(f)
45
+ for k, v in metadata.items():
46
+ if v["label"] == "FAKE":
47
+ fakes.append(k[:-4])
48
+ else:
49
+ originals.append(k[:-4])
50
+
51
+ return originals, fakes
train.sh ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ ROOT_DIR=$1
4
+ NUM_GPUS=$2
5
+ python -u -m torch.distributed.launch --nproc_per_node=$NUM_GPUS --master_port 9901 training/pipelines/train_classifier.py \
6
+ --distributed --config configs/b7.json --freeze-epochs 0 --test_every 1 --opt-level O1 --label-smoothing 0.01 --folds-csv folds.csv --fold 0 --seed 111 --data-dir $ROOT_DIR --prefix b7_111_ > logs/b7_111
7
+
8
+ python -u -m torch.distributed.launch --nproc_per_node=$NUM_GPUS --master_port 9901 training/pipelines/train_classifier.py \
9
+ --distributed --config configs/b7.json --freeze-epochs 0 --test_every 1 --opt-level O1 --label-smoothing 0.01 --folds-csv folds.csv --fold 0 --seed 555 --data-dir $ROOT_DIR --prefix b7_555_ > logs/b7_555
10
+
11
+ python -u -m torch.distributed.launch --nproc_per_node=$NUM_GPUS --master_port 9901 training/pipelines/train_classifier.py \
12
+ --distributed --config configs/b7.json --freeze-epochs 0 --test_every 1 --opt-level O1 --label-smoothing 0.01 --folds-csv folds.csv --fold 0 --seed 777 --data-dir $ROOT_DIR --prefix b7_777_ > logs/b7_777
13
+
14
+ python -u -m torch.distributed.launch --nproc_per_node=$NUM_GPUS --master_port 9901 training/pipelines/train_classifier.py \
15
+ --distributed --config configs/b7.json --freeze-epochs 0 --test_every 1 --opt-level O1 --label-smoothing 0.01 --folds-csv folds.csv --fold 0 --seed 888 --data-dir $ROOT_DIR --prefix b7_888_ > logs/b7_888
16
+
17
+ python -u -m torch.distributed.launch --nproc_per_node=$NUM_GPUS --master_port 9901 training/pipelines/train_classifier.py \
18
+ --distributed --config configs/b7.json --freeze-epochs 0 --test_every 1 --opt-level O1 --label-smoothing 0.01 --folds-csv folds.csv --fold 0 --seed 999 --data-dir $ROOT_DIR --prefix b7_999_ > logs/b7_999
training/__init__.py ADDED
File without changes
training/__pycache__/__init__.cpython-39.pyc ADDED
Binary file (164 Bytes). View file
 
training/__pycache__/losses.cpython-39.pyc ADDED
Binary file (1.55 kB). View file
 
training/datasets/__init__.py ADDED
File without changes
training/datasets/__pycache__/__init__.cpython-39.pyc ADDED
Binary file (173 Bytes). View file
 
training/datasets/__pycache__/classifier_dataset.cpython-39.pyc ADDED
Binary file (11 kB). View file
 
training/datasets/__pycache__/validation_set.cpython-39.pyc ADDED
Binary file (5 kB). View file
 
training/datasets/classifier_dataset.py ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import os
3
+ import random
4
+ import sys
5
+ import traceback
6
+
7
+ import cv2
8
+ import numpy as np
9
+ import pandas as pd
10
+ import skimage.draw
11
+ from albumentations import ImageCompression, OneOf, GaussianBlur, Blur
12
+ from albumentations.augmentations.functional import image_compression
13
+ from albumentations.augmentations.geometric.functional import rot90
14
+ from albumentations.pytorch.functional import img_to_tensor
15
+ from scipy.ndimage import binary_erosion, binary_dilation
16
+ from skimage import measure
17
+ from torch.utils.data import Dataset
18
+ import dlib
19
+
20
+ from training.datasets.validation_set import PUBLIC_SET
21
+
22
+
23
+ def prepare_bit_masks(mask):
24
+ h, w = mask.shape
25
+ mid_w = w // 2
26
+ mid_h = w // 2
27
+ masks = []
28
+ ones = np.ones_like(mask)
29
+ ones[:mid_h] = 0
30
+ masks.append(ones)
31
+ ones = np.ones_like(mask)
32
+ ones[mid_h:] = 0
33
+ masks.append(ones)
34
+ ones = np.ones_like(mask)
35
+ ones[:, :mid_w] = 0
36
+ masks.append(ones)
37
+ ones = np.ones_like(mask)
38
+ ones[:, mid_w:] = 0
39
+ masks.append(ones)
40
+ ones = np.ones_like(mask)
41
+ ones[:mid_h, :mid_w] = 0
42
+ ones[mid_h:, mid_w:] = 0
43
+ masks.append(ones)
44
+ ones = np.ones_like(mask)
45
+ ones[:mid_h, mid_w:] = 0
46
+ ones[mid_h:, :mid_w] = 0
47
+ masks.append(ones)
48
+ return masks
49
+
50
+ sys.path.insert(1, 'D:\\University And Papers\\VESSL\\dfdc_deepfake_challenge')
51
+ detector = dlib.get_frontal_face_detector()
52
+ predictor = dlib.shape_predictor('D:\\University And Papers\\VESSL\\dfdc_deepfake_challenge/libs/shape_predictor_68_face_landmarks.dat')
53
+
54
+
55
+ def blackout_convex_hull(img):
56
+ try:
57
+ rect = detector(img)[0]
58
+ sp = predictor(img, rect)
59
+ landmarks = np.array([[p.x, p.y] for p in sp.parts()])
60
+ outline = landmarks[[*range(17), *range(26, 16, -1)]]
61
+ Y, X = skimage.draw.polygon(outline[:, 1], outline[:, 0])
62
+ cropped_img = np.zeros(img.shape[:2], dtype=np.uint8)
63
+ cropped_img[Y, X] = 1
64
+ # if random.random() > 0.5:
65
+ # img[cropped_img == 0] = 0
66
+ # #leave only face
67
+ # return img
68
+
69
+ y, x = measure.centroid(cropped_img)
70
+ y = int(y)
71
+ x = int(x)
72
+ first = random.random() > 0.5
73
+ if random.random() > 0.5:
74
+ if first:
75
+ cropped_img[:y, :] = 0
76
+ else:
77
+ cropped_img[y:, :] = 0
78
+ else:
79
+ if first:
80
+ cropped_img[:, :x] = 0
81
+ else:
82
+ cropped_img[:, x:] = 0
83
+
84
+ img[cropped_img > 0] = 0
85
+ except Exception as e:
86
+ pass
87
+
88
+
89
+ def dist(p1, p2):
90
+ return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
91
+
92
+
93
+ def remove_eyes(image, landmarks):
94
+ image = image.copy()
95
+ (x1, y1), (x2, y2) = landmarks[:2]
96
+ mask = np.zeros_like(image[..., 0])
97
+ line = cv2.line(mask, (x1, y1), (x2, y2), color=(1), thickness=2)
98
+ w = dist((x1, y1), (x2, y2))
99
+ dilation = int(w // 4)
100
+ line = binary_dilation(line, iterations=dilation)
101
+ image[line, :] = 0
102
+ return image
103
+
104
+
105
+ def remove_nose(image, landmarks):
106
+ image = image.copy()
107
+ (x1, y1), (x2, y2) = landmarks[:2]
108
+ x3, y3 = landmarks[2]
109
+ mask = np.zeros_like(image[..., 0])
110
+ x4 = int((x1 + x2) / 2)
111
+ y4 = int((y1 + y2) / 2)
112
+ line = cv2.line(mask, (x3, y3), (x4, y4), color=(1), thickness=2)
113
+ w = dist((x1, y1), (x2, y2))
114
+ dilation = int(w // 4)
115
+ line = binary_dilation(line, iterations=dilation)
116
+ image[line, :] = 0
117
+ return image
118
+
119
+
120
+ def remove_mouth(image, landmarks):
121
+ image = image.copy()
122
+ (x1, y1), (x2, y2) = landmarks[-2:]
123
+ mask = np.zeros_like(image[..., 0])
124
+ line = cv2.line(mask, (x1, y1), (x2, y2), color=(1), thickness=2)
125
+ w = dist((x1, y1), (x2, y2))
126
+ dilation = int(w // 3)
127
+ line = binary_dilation(line, iterations=dilation)
128
+ image[line, :] = 0
129
+ return image
130
+
131
+
132
+ def remove_landmark(image, landmarks):
133
+ if random.random() > 0.5:
134
+ image = remove_eyes(image, landmarks)
135
+ elif random.random() > 0.5:
136
+ image = remove_mouth(image, landmarks)
137
+ elif random.random() > 0.5:
138
+ image = remove_nose(image, landmarks)
139
+ return image
140
+
141
+
142
+ def change_padding(image, part=5):
143
+ h, w = image.shape[:2]
144
+ # original padding was done with 1/3 from each side, too much
145
+ pad_h = int(((3 / 5) * h) / part)
146
+ pad_w = int(((3 / 5) * w) / part)
147
+ image = image[h // 5 - pad_h:-h // 5 + pad_h, w // 5 - pad_w:-w // 5 + pad_w]
148
+ return image
149
+
150
+
151
+ def blackout_random(image, mask, label):
152
+ binary_mask = mask > 0.4 * 255
153
+ h, w = binary_mask.shape[:2]
154
+
155
+ tries = 50
156
+ current_try = 1
157
+ while current_try < tries:
158
+ first = random.random() < 0.5
159
+ if random.random() < 0.5:
160
+ pivot = random.randint(h // 2 - h // 5, h // 2 + h // 5)
161
+ bitmap_msk = np.ones_like(binary_mask)
162
+ if first:
163
+ bitmap_msk[:pivot, :] = 0
164
+ else:
165
+ bitmap_msk[pivot:, :] = 0
166
+ else:
167
+ pivot = random.randint(w // 2 - w // 5, w // 2 + w // 5)
168
+ bitmap_msk = np.ones_like(binary_mask)
169
+ if first:
170
+ bitmap_msk[:, :pivot] = 0
171
+ else:
172
+ bitmap_msk[:, pivot:] = 0
173
+
174
+ if label < 0.5 and np.count_nonzero(image * np.expand_dims(bitmap_msk, axis=-1)) / 3 > (h * w) / 5 \
175
+ or np.count_nonzero(binary_mask * bitmap_msk) > 40:
176
+ mask *= bitmap_msk
177
+ image *= np.expand_dims(bitmap_msk, axis=-1)
178
+ break
179
+ current_try += 1
180
+ return image
181
+
182
+
183
+ def blend_original(img):
184
+ img = img.copy()
185
+ h, w = img.shape[:2]
186
+ rect = detector(img)
187
+ if len(rect) == 0:
188
+ return img
189
+ else:
190
+ rect = rect[0]
191
+ sp = predictor(img, rect)
192
+ landmarks = np.array([[p.x, p.y] for p in sp.parts()])
193
+ outline = landmarks[[*range(17), *range(26, 16, -1)]]
194
+ Y, X = skimage.draw.polygon(outline[:, 1], outline[:, 0])
195
+ raw_mask = np.zeros(img.shape[:2], dtype=np.uint8)
196
+ raw_mask[Y, X] = 1
197
+ face = img * np.expand_dims(raw_mask, -1)
198
+
199
+ # add warping
200
+ h1 = random.randint(h - h // 2, h + h // 2)
201
+ w1 = random.randint(w - w // 2, w + w // 2)
202
+ while abs(h1 - h) < h // 3 and abs(w1 - w) < w // 3:
203
+ h1 = random.randint(h - h // 2, h + h // 2)
204
+ w1 = random.randint(w - w // 2, w + w // 2)
205
+ face = cv2.resize(face, (w1, h1), interpolation=random.choice([cv2.INTER_LINEAR, cv2.INTER_AREA, cv2.INTER_CUBIC]))
206
+ face = cv2.resize(face, (w, h), interpolation=random.choice([cv2.INTER_LINEAR, cv2.INTER_AREA, cv2.INTER_CUBIC]))
207
+
208
+ raw_mask = binary_erosion(raw_mask, iterations=random.randint(4, 10))
209
+ img[raw_mask, :] = face[raw_mask, :]
210
+ if random.random() < 0.2:
211
+ img = OneOf([GaussianBlur(), Blur()], p=0.5)(image=img)["image"]
212
+ # image compression
213
+ if random.random() < 0.5:
214
+ img = ImageCompression(quality_lower=40, quality_upper=95)(image=img)["image"]
215
+ return img
216
+
217
+
218
+ class DeepFakeClassifierDataset(Dataset):
219
+
220
+ def __init__(self,
221
+ data_path="/mnt/sota/datasets/deepfake",
222
+ fold=0,
223
+ label_smoothing=0.01,
224
+ padding_part=3,
225
+ hardcore=True,
226
+ crops_dir="crops",
227
+ folds_csv="folds.csv",
228
+ normalize={"mean": [0.485, 0.456, 0.406],
229
+ "std": [0.229, 0.224, 0.225]},
230
+ rotation=False,
231
+ mode="train",
232
+ reduce_val=True,
233
+ oversample_real=True,
234
+ transforms=None
235
+ ):
236
+ super().__init__()
237
+ self.data_root = data_path
238
+ self.fold = fold
239
+ self.folds_csv = folds_csv
240
+ self.mode = mode
241
+ self.rotation = rotation
242
+ self.padding_part = padding_part
243
+ self.hardcore = hardcore
244
+ self.crops_dir = crops_dir
245
+ self.label_smoothing = label_smoothing
246
+ self.normalize = normalize
247
+ self.transforms = transforms
248
+ self.df = pd.read_csv(os.path.join(data_path, folds_csv))
249
+ self.oversample_real = oversample_real
250
+ self.reduce_val = reduce_val
251
+
252
+ def __getitem__(self, index: int):
253
+
254
+ while True:
255
+ video, img_file, label, ori_video, frame, fold = self.data[index]
256
+ try:
257
+ if self.mode == "train":
258
+ label = np.clip(label, self.label_smoothing, 1 - self.label_smoothing)
259
+ img_path = os.path.join(self.data_root, self.crops_dir, video, img_file)
260
+ image = cv2.imread(img_path, cv2.IMREAD_COLOR)
261
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
262
+ mask = np.zeros(image.shape[:2], dtype=np.uint8)
263
+ diff_path = os.path.join(self.data_root, "diffs", video, img_file[:-4] + "_diff.png")
264
+ try:
265
+ msk = cv2.imread(diff_path, cv2.IMREAD_GRAYSCALE)
266
+ if msk is not None:
267
+ mask = msk
268
+ except:
269
+ print("not found mask", diff_path)
270
+ pass
271
+ if self.mode == "train" and self.hardcore and not self.rotation:
272
+ landmark_path = os.path.join(self.data_root, "landmarks", ori_video, img_file[:-4] + ".npy")
273
+ if os.path.exists(landmark_path) and random.random() < 0.7:
274
+ landmarks = np.load(landmark_path)
275
+ image = remove_landmark(image, landmarks)
276
+ elif random.random() < 0.2:
277
+ blackout_convex_hull(image)
278
+ elif random.random() < 0.1:
279
+ binary_mask = mask > 0.4 * 255
280
+ masks = prepare_bit_masks((binary_mask * 1).astype(np.uint8))
281
+ tries = 6
282
+ current_try = 1
283
+ while current_try < tries:
284
+ bitmap_msk = random.choice(masks)
285
+ if label < 0.5 or np.count_nonzero(mask * bitmap_msk) > 20:
286
+ mask *= bitmap_msk
287
+ image *= np.expand_dims(bitmap_msk, axis=-1)
288
+ break
289
+ current_try += 1
290
+ if self.mode == "train" and self.padding_part > 3:
291
+ image = change_padding(image, self.padding_part)
292
+ valid_label = np.count_nonzero(mask[mask > 20]) > 32 or label < 0.5
293
+ valid_label = 1 if valid_label else 0
294
+ rotation = 0
295
+ if self.transforms:
296
+ data = self.transforms(image=image, mask=mask)
297
+ image = data["image"]
298
+ mask = data["mask"]
299
+ if self.mode == "train" and self.hardcore and self.rotation:
300
+ # landmark_path = os.path.join(self.data_root, "landmarks", ori_video, img_file[:-4] + ".npy")
301
+ dropout = 0.8 if label > 0.5 else 0.6
302
+ if self.rotation:
303
+ dropout *= 0.7
304
+ elif random.random() < dropout:
305
+ blackout_random(image, mask, label)
306
+
307
+ #
308
+ # os.makedirs("../images", exist_ok=True)
309
+ # cv2.imwrite(os.path.join("../images", video+ "_" + str(1 if label > 0.5 else 0) + "_"+img_file), image[...,::-1])
310
+
311
+ if self.mode == "train" and self.rotation:
312
+ rotation = random.randint(0, 3)
313
+ image = rot90(image, rotation)
314
+
315
+ image = img_to_tensor(image, self.normalize)
316
+ return {"image": image, "labels": np.array((label,)), "img_name": os.path.join(video, img_file),
317
+ "valid": valid_label, "rotations": rotation}
318
+ except Exception as e:
319
+ traceback.print_exc(file=sys.stdout)
320
+ print("Broken image", os.path.join(self.data_root, self.crops_dir, video, img_file))
321
+ index = random.randint(0, len(self.data) - 1)
322
+
323
+ def random_blackout_landmark(self, image, mask, landmarks):
324
+ x, y = random.choice(landmarks)
325
+ first = random.random() > 0.5
326
+ # crop half face either vertically or horizontally
327
+ if random.random() > 0.5:
328
+ # width
329
+ if first:
330
+ image[:, :x] = 0
331
+ mask[:, :x] = 0
332
+ else:
333
+ image[:, x:] = 0
334
+ mask[:, x:] = 0
335
+ else:
336
+ # height
337
+ if first:
338
+ image[:y, :] = 0
339
+ mask[:y, :] = 0
340
+ else:
341
+ image[y:, :] = 0
342
+ mask[y:, :] = 0
343
+
344
+ def reset(self, epoch, seed):
345
+ self.data = self._prepare_data(epoch, seed)
346
+
347
+ def __len__(self) -> int:
348
+ return len(self.data)
349
+
350
+ def _prepare_data(self, epoch, seed):
351
+ df = self.df
352
+ if self.mode == "train":
353
+ rows = df[df["fold"] != self.fold]
354
+ else:
355
+ rows = df[df["fold"] == self.fold]
356
+ seed = (epoch + 1) * seed
357
+ if self.oversample_real:
358
+ rows = self._oversample(rows, seed)
359
+ if self.mode == "val" and self.reduce_val:
360
+ # every 2nd frame, to speed up validation
361
+ rows = rows[rows["frame"] % 20 == 0]
362
+ # another option is to use public validation set
363
+ #rows = rows[rows["video"].isin(PUBLIC_SET)]
364
+
365
+ print(
366
+ "real {} fakes {} mode {}".format(len(rows[rows["label"] == 0]), len(rows[rows["label"] == 1]), self.mode))
367
+ data = rows.values
368
+
369
+ np.random.seed(seed)
370
+ np.random.shuffle(data)
371
+ return data
372
+
373
+ def _oversample(self, rows: pd.DataFrame, seed):
374
+ real = rows[rows["label"] == 0]
375
+ fakes = rows[rows["label"] == 1]
376
+ num_real = real["video"].count()
377
+ if self.mode == "train":
378
+ fakes = fakes.sample(n=num_real, replace=False, random_state=seed)
379
+ return pd.concat([real, fakes])
training/datasets/validation_set.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ PUBLIC_SET = {'tjuihawuqm', 'prwsfljdjo', 'scrbqgpvzz', 'ziipxxchai', 'uubgqnvfdl', 'wclvkepakb', 'xjvxtuakyd',
4
+ 'qlvsqdroqo', 'bcbqxhziqz', 'yzuestxcbq', 'hxwtsaydal', 'kqlvggiqee', 'vtunvalyji', 'mohiqoogpb',
5
+ 'siebfpwuhu', 'cekwtyxdoo', 'hszwwswewp', 'orekjthsef', 'huvlwkxoxm', 'fmhiujydwo', 'lhvjzhjxdp',
6
+ 'ibxfxggtqh', 'bofrwgeyjo', 'rmufsuogzn', 'zbgssotnjm', 'dpevefkefv', 'sufvvwmbha', 'ncoeewrdlo',
7
+ 'qhsehzgxqj', 'yxadevzohx', 'aomqqjipcp', 'pcyswtgick', 'wfzjxzhdkj', 'rcjfxxhcal', 'lnjkpdviqb',
8
+ 'xmkwsnuzyq', 'ouaowjmigq', 'bkuzquigyt', 'vwxednhlwz', 'mszblrdprw', 'blnmxntbey', 'gccnvdoknm',
9
+ 'mkzaekkvej', 'hclsparpth', 'eryjktdexi', 'hfsvqabzfq', 'acazlolrpz', 'yoyhmxtrys', 'rerpivllud',
10
+ 'elackxuccp', 'zgbhzkditd', 'vjljdfopjg', 'famlupsgqm', 'nymodlmxni', 'qcbkztamqc', 'qclpbcbgeq',
11
+ 'lpkgabskbw', 'mnowxangqx', 'czfqlbcfpa', 'qyyhuvqmyf', 'toinozytsp', 'ztyvglkcsf', 'nplviymzlg',
12
+ 'opvqdabdap', 'uxuvkrjhws', 'mxahsihabr', 'cqxxumarvp', 'ptbfnkajyi', 'njzshtfmcw', 'dcqodpzomd',
13
+ 'ajiyrjfyzp', 'ywauoonmlr', 'gochxzemmq', 'lpgxwdgnio', 'hnfwagcxdf', 'gfcycflhbo', 'gunamloolc',
14
+ 'yhjlnisfel', 'srfefmyjvt', 'evysmtpnrf', 'aktnlyqpah', 'gpsxfxrjrr', 'zfobicuigx', 'mnzabbkpmt',
15
+ 'rfjuhbnlro', 'zuwwbbusgl', 'csnkohqxdv', 'bzvzpwrabw', 'yietrwuncf', 'wynotylpnm', 'ekboxwrwuv',
16
+ 'rcecrgeotc', 'rklawjhbpv', 'ilqwcbprqa', 'jsysgmycsx', 'sqixhnilfm', 'wnlubukrki', 'nikynwcvuh',
17
+ 'sjkfxrlxxs', 'btdxnajogv', 'wjhpisoeaj', 'dyjklprkoc', 'qlqhjcshpk', 'jyfvaequfg', 'dozjwhnedd',
18
+ 'owaogcehvc', 'oyqgwjdwaj', 'vvfszaosiv', 'kmcdjxmnoa', 'jiswxuqzyz', 'ddtbarpcgo', 'wqysrieiqu',
19
+ 'xcruhaccxc', 'honxqdilvv', 'nxgzmgzkfv', 'cxsvvnxpyz', 'demuhxssgl', 'hzoiotcykp', 'fwykevubzy',
20
+ 'tejfudfgpq', 'kvmpmhdxly', 'oojxonbgow', 'vurjckblge', 'oysopgovhu', 'khpipxnsvx', 'pqthmvwonf',
21
+ 'fddmkqjwsh', 'pcoxcmtroa', 'cnxccbjlct', 'ggzjfrirjh', 'jquevmhdvc', 'ecumyiowzs', 'esmqxszybs',
22
+ 'mllzkpgatp', 'ryxaqpfubf', 'hbufmvbium', 'vdtsbqidjb', 'sjwywglgym', 'qxyrtwozyw', 'upmgtackuf',
23
+ 'ucthmsajay', 'zgjosltkie', 'snlyjbnpgw', 'nswtvttxre', 'iznnzjvaxc', 'jhczqfefgw', 'htzbnroagi',
24
+ 'pdswwyyntw', 'uvrzaczrbx', 'vbcgoyxsvn', 'hzssdinxec', 'novarhxpbj', 'vizerpsvbz', 'jawgcggquk',
25
+ 'iorbtaarte', 'yarpxfqejd', 'vhbbwdflyh', 'rrrfjhugvb', 'fneqiqpqvs', 'jytrvwlewz', 'bfjsthfhbd',
26
+ 'rxdoimqble', 'ekelfsnqof', 'uqvxjfpwdo', 'cjkctqqakb', 'tynfsthodx', 'yllztsrwjw', 'bktkwbcawi',
27
+ 'wcqvzujamg', 'bcvheslzrq', 'aqrsylrzgi', 'sktpeppbkc', 'mkmgcxaztt', 'etdliwticv', 'hqzwudvhih',
28
+ 'swsaoktwgi', 'temjefwaas', 'papagllumt', 'xrtvqhdibb', 'oelqpetgwj', 'ggdpclfcgk', 'imdmhwkkni',
29
+ 'lebzjtusnr', 'xhtppuyqdr', 'nxzgekegsp', 'waucvvmtkq', 'rnfcjxynfa', 'adohdulfwb', 'tjywwgftmv',
30
+ 'fjrueenjyp', 'oaguiggjyv', 'ytopzxrswu', 'yxvmusxvcz', 'rukyxomwcx', 'qdqdsaiitt', 'mxlipjhmqk',
31
+ 'voawxrmqyl', 'kezwvsxxzj', 'oocincvedt', 'qooxnxqqjb', 'mwwploizlj', 'yaxgpxhavq', 'uhakqelqri',
32
+ 'bvpeerislp', 'bkcyglmfci', 'jyoxdvxpza', 'gkutjglghz', 'knxltsvzyu', 'ybbrkacebd', 'apvzjkvnwn',
33
+ 'ahjnxtiamx', 'hsbljbsgxr', 'fnxgqcvlsd', 'xphdfgmfmz', 'scbdenmaed', 'ywxpquomgt', 'yljecirelf',
34
+ 'wcvsqnplsk', 'vmxfwxgdei', 'icbsahlivv', 'yhylappzid', 'irqzdokcws', 'petmyhjclt', 'rmlzgerevr',
35
+ 'qarqtkvgby', 'nkhzxomani', 'viteugozpv', 'qhkzlnzruj', 'eisofhptvk', 'gqnaxievjx', 'heiyoojifp',
36
+ 'zcxcmneefk', 'wvgviwnwob', 'gcdtglsoqj', 'yqhouqakbx', 'fopjiyxiqd', 'hierggamuo', 'ypbtpunjvm',
37
+ 'sjinmmbipg', 'kmqkiihrmj', 'wmoqzxddkb', 'lnhkjhyhvw', 'wixbuuzygv', 'fsdrwikhge', 'sfsayjgzrh',
38
+ 'pqdeutauqc', 'frqfsucgao', 'pdufsewrec', 'bfdopzvxbi', 'shnsajrsow', 'rvvpazsffd', 'pxcfrszlgi',
39
+ 'itfsvvmslp', 'ayipraspbn', 'prhmixykhr', 'doniqevxeg', 'dvtpwatuja', 'jiavqbrkyk', 'ipkpxvwroe',
40
+ 'syxobtuucp', 'syuxttuyhm', 'nwvsbmyndn', 'eqslzbqfea', 'ytddugrwph', 'vokrpfjpeb', 'bdshuoldwx',
41
+ 'fmvvmcbdrw', 'bnuwxhfahw', 'gbnzicjyhz', 'txnmkabufs', 'gfdjzwnpyp', 'hweshqpfwe', 'dxgnpnowgk',
42
+ 'xugmhbetrw', 'rktrpsdlci', 'nthpnwylxo', 'ihglzxzroo', 'ocgdbrgmtq', 'ruhtnngrqv', 'xljemofssi',
43
+ 'zxacihctqp', 'ghnpsltzyn', 'lbigytrrtr', 'ndikguxzek', 'mdfndlljvt', 'lyoslorecs', 'oefukgnvel',
44
+ 'zmxeiipnqb', 'cosghhimnd', 'alrtntfxtd', 'eywdmustbb', 'ooafcxxfrs', 'fqgypsunzr', 'hevcclcklc',
45
+ 'uhrqlmlclw', 'ipvwtgdlre', 'wcssbghcpc', 'didzujjhtg', 'fjxovgmwnm', 'dmmvuaikkv', 'hitfycdavv',
46
+ 'zyufpqvpyu', 'coujjnypba', 'temeqbmzxu', 'apedduehoy', 'iksxzpqxzi', 'kwfdyqofzw', 'aassnaulhq',
47
+ 'eyguqfmgzh', 'yiykshcbaz', 'sngjsueuhs', 'okgelildpc', 'ztyuiqrhdk', 'tvhjcfnqtg', 'gfgcwxkbjd',
48
+ 'lbfqksftuo', 'kowiwvrjht', 'dkuqbduxev', 'mwnibuujwz', 'sodvtfqbpf', 'hsbwhlolsn', 'qsjiypnjwi',
49
+ 'blszgmxkvu', 'ystdtnetgj', 'rfwxcinshk', 'vnlzxqwthl', 'ljouzjaqqe', 'gahgyuwzbu', 'xxzefxwyku',
50
+ 'xitgdpzbxv', 'sylnrepacf', 'igpvrfjdzc', 'nxnmkytwze', 'psesikjaxx', 'dvwpvqdflx', 'bjyaxvggle',
51
+ 'dpmgoiwhuf', 'wadvzjhwtw', 'kcjvhgvhpt', 'eppyqpgewp', 'tyjpjpglgx', 'cekarydqba', 'dvkdfhrpph',
52
+ 'cnpanmywno', 'ljauauuyka', 'hicjuubiau', 'cqhwesrciw', 'dnmowthjcj', 'lujvyveojc', 'wndursivcx',
53
+ 'espkiocpxq', 'jsbpkpxwew', 'dsnxgrfdmd', 'hyjqolupxn', 'xdezcezszc', 'axfhbpkdlc', 'qqnlrngaft',
54
+ 'coqwgzpbhx', 'ncmpqwmnzb', 'sznkemeqro', 'omphqltjdd', 'uoccaiathd', 'jzmzdispyo', 'pxjkzvqomp',
55
+ 'udxqbhgvvx', 'dzkyxbbqkr', 'dtozwcapoa', 'qswlzfgcgj', 'tgawasvbbr', 'lmdyicksrv', 'fzvpbrzssi',
56
+ 'dxfdovivlw', 'zzmgnglanj', 'vssmlqoiti', 'vajkicalux', 'ekvwecwltj', 'ylxwcwhjjd', 'keioymnobc',
57
+ 'usqqvxcjmg', 'phjvutxpoi', 'nycmyuzpml', 'bwdmzwhdnw', 'fxuxxtryjn', 'orixbcfvdz', 'hefisnapds',
58
+ 'fpevfidstw', 'halvwiltfs', 'dzojiwfvba', 'ojsxxkalat', 'esjdyghhog', 'ptbnewtvon', 'hcanfkwivl',
59
+ 'yronlutbgm', 'llplvmcvbl', 'yxirnfyijn', 'nwvloufjty', 'rtpbawlmxr', 'aayfryxljh', 'zfrrixsimm',
60
+ 'txmnoyiyte'}
training/losses.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any
2
+
3
+ from pytorch_toolbelt.losses import BinaryFocalLoss
4
+ from torch import nn
5
+ from torch.nn.modules.loss import BCEWithLogitsLoss
6
+
7
+
8
+ class WeightedLosses(nn.Module):
9
+ def __init__(self, losses, weights):
10
+ super().__init__()
11
+ self.losses = losses
12
+ self.weights = weights
13
+
14
+ def forward(self, *input: Any, **kwargs: Any):
15
+ cum_loss = 0
16
+ for loss, w in zip(self.losses, self.weights):
17
+ cum_loss += w * loss.forward(*input, **kwargs)
18
+ return cum_loss
19
+
20
+
21
+ class BinaryCrossentropy(BCEWithLogitsLoss):
22
+ pass
23
+
24
+
25
+ class FocalLoss(BinaryFocalLoss):
26
+ def __init__(self, alpha=None, gamma=3, ignore_index=None, reduction="mean", normalized=False,
27
+ reduced_threshold=None):
28
+ super().__init__(alpha, gamma, ignore_index, reduction, normalized, reduced_threshold)
training/pipelines/__init__.py ADDED
File without changes
training/pipelines/logs/classifier_tf_efficientnet_b7_ns_1/events.out.tfevents.1671716506.Green.33248.0 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d08877d01d980696dce4abdd58b57e37f18910c0ad8aa2cfe140a6baad2a1c35
3
+ size 1020
training/pipelines/train_classifier.py ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import json
3
+ import os
4
+ import sys
5
+ from collections import defaultdict
6
+
7
+ from sklearn.metrics import log_loss
8
+ from torch import topk
9
+ sys.path.insert(1, 'D:\\University And Papers\\VESSL\\dfdc_deepfake_challenge')
10
+ sys.path.insert(2, 'D:\\University And Papers\\VESSL\\dfdc_deepfake_challenge\\apex\\apex')
11
+ from training import losses
12
+ from training.datasets.classifier_dataset import DeepFakeClassifierDataset
13
+ from training.losses import WeightedLosses
14
+ from training.tools.config import load_config
15
+ from training.tools.utils import create_optimizer, AverageMeter
16
+ from training.transforms.albu import IsotropicResize
17
+ from training.zoo import classifiers
18
+
19
+ os.environ["MKL_NUM_THREADS"] = "1"
20
+ os.environ["NUMEXPR_NUM_THREADS"] = "1"
21
+ os.environ["OMP_NUM_THREADS"] = "1"
22
+
23
+ import cv2
24
+
25
+ cv2.ocl.setUseOpenCL(False)
26
+ cv2.setNumThreads(0)
27
+ import numpy as np
28
+ from albumentations import Compose, RandomBrightnessContrast, \
29
+ HorizontalFlip, FancyPCA, HueSaturationValue, OneOf, ToGray, \
30
+ ShiftScaleRotate, ImageCompression, PadIfNeeded, GaussNoise, GaussianBlur
31
+
32
+ from apex.parallel import DistributedDataParallel, convert_syncbn_model
33
+ # from tensorboardX import SummaryWriter
34
+ from torch.utils.tensorboard import SummaryWriter
35
+
36
+ from apex import amp
37
+
38
+ import torch
39
+ from torch.backends import cudnn
40
+ from torch.nn import DataParallel
41
+ from torch.utils.data import DataLoader
42
+ from tqdm import tqdm
43
+ import torch.distributed as dist
44
+
45
+ torch.backends.cudnn.benchmark = True
46
+
47
+
48
+ def create_train_transforms(size=300):
49
+ return Compose([
50
+ ImageCompression(quality_lower=60, quality_upper=100, p=0.5),
51
+ GaussNoise(p=0.1),
52
+ GaussianBlur(blur_limit=3, p=0.05),
53
+ HorizontalFlip(),
54
+ OneOf([
55
+ IsotropicResize(max_side=size, interpolation_down=cv2.INTER_AREA, interpolation_up=cv2.INTER_CUBIC),
56
+ IsotropicResize(max_side=size, interpolation_down=cv2.INTER_AREA, interpolation_up=cv2.INTER_LINEAR),
57
+ IsotropicResize(max_side=size, interpolation_down=cv2.INTER_LINEAR, interpolation_up=cv2.INTER_LINEAR),
58
+ ], p=1),
59
+ PadIfNeeded(min_height=size, min_width=size, border_mode=cv2.BORDER_CONSTANT),
60
+ OneOf([RandomBrightnessContrast(), FancyPCA(), HueSaturationValue()], p=0.7),
61
+ ToGray(p=0.2),
62
+ ShiftScaleRotate(shift_limit=0.1, scale_limit=0.2, rotate_limit=10, border_mode=cv2.BORDER_CONSTANT, p=0.5),
63
+ ]
64
+ )
65
+
66
+
67
+ def create_val_transforms(size=300):
68
+ return Compose([
69
+ IsotropicResize(max_side=size, interpolation_down=cv2.INTER_AREA, interpolation_up=cv2.INTER_CUBIC),
70
+ PadIfNeeded(min_height=size, min_width=size, border_mode=cv2.BORDER_CONSTANT),
71
+ ])
72
+
73
+
74
+ def main():
75
+ parser = argparse.ArgumentParser("PyTorch Xview Pipeline")
76
+ arg = parser.add_argument
77
+ arg('--config', metavar='CONFIG_FILE', help='path to configuration file')
78
+ arg('--workers', type=int, default=6, help='number of cpu threads to use')
79
+ arg('--gpu', type=str, default='0', help='List of GPUs for parallel training, e.g. 0,1,2,3')
80
+ arg('--output-dir', type=str, default='C:/Users/Green/Desktop/VESSL/dfdc_deepfake_challenge/data_root/weights/')
81
+ arg('--resume', type=str, default='')
82
+ arg('--fold', type=int, default=1)
83
+ arg('--prefix', type=str, default='classifier_')
84
+ arg('--data-dir', type=str, default="C:/Users/Green/Desktop/VESSL/dfdc_deepfake_challenge/data_root")
85
+ arg('--folds-csv', type=str, default='folds02.csv')
86
+ arg('--crops-dir', type=str, default='crops')
87
+ arg('--label-smoothing', type=float, default=0.01)
88
+ arg('--logdir', type=str, default='logs')
89
+ arg('--zero-score', action='store_true', default=False)
90
+ arg('--from-zero', action='store_true', default=False)
91
+ arg('--distributed', action='store_true', default=False)
92
+ arg('--freeze-epochs', type=int, default=0)
93
+ arg("--local_rank", default=0, type=int)
94
+ arg("--seed", default=777, type=int)
95
+ arg("--padding-part", default=3, type=int)
96
+ arg("--opt-level", default='O1', type=str)
97
+ arg("--test_every", type=int, default=1)
98
+ arg("--no-oversample", action="store_true")
99
+ arg("--no-hardcore", action="store_true")
100
+ arg("--only-changed-frames", action="store_true")
101
+
102
+ args = parser.parse_args()
103
+ os.makedirs(args.output_dir, exist_ok=True)
104
+ if args.distributed:
105
+ torch.cuda.set_device(args.local_rank)
106
+ torch.distributed.init_process_group(backend='nccl', init_method='env://')
107
+ else:
108
+ os.environ['CUDA_DEVICE_ORDER'] = 'PCI_BUS_ID'
109
+ os.environ["CUDA_VISIBLE_DEVICES"] = args.gpu
110
+
111
+ cudnn.benchmark = True
112
+
113
+ conf = load_config(args.config)
114
+ model = classifiers.__dict__[conf['network']](encoder=conf['encoder'])
115
+
116
+ model = model.cuda()
117
+ if args.distributed:
118
+ model = convert_syncbn_model(model)
119
+ ohem = conf.get("ohem_samples", None)
120
+ reduction = "mean"
121
+ if ohem:
122
+ reduction = "none"
123
+ loss_fn = []
124
+ weights = []
125
+ for loss_name, weight in conf["losses"].items():
126
+ loss_fn.append(losses.__dict__[loss_name](reduction=reduction).cuda())
127
+ weights.append(weight)
128
+ loss = WeightedLosses(loss_fn, weights)
129
+ loss_functions = {"classifier_loss": loss}
130
+ optimizer, scheduler = create_optimizer(conf['optimizer'], model)
131
+ bce_best = 100
132
+ start_epoch = 0
133
+ batch_size = conf['optimizer']['batch_size']
134
+
135
+ data_train = DeepFakeClassifierDataset(mode="train",
136
+ oversample_real=not args.no_oversample,
137
+ fold=args.fold,
138
+ padding_part=args.padding_part,
139
+ hardcore=not args.no_hardcore,
140
+ crops_dir=args.crops_dir,
141
+ data_path=args.data_dir,
142
+ label_smoothing=args.label_smoothing,
143
+ folds_csv=args.folds_csv,
144
+ transforms=create_train_transforms(conf["size"]),
145
+ normalize=conf.get("normalize", None))
146
+ data_val = DeepFakeClassifierDataset(mode="val",
147
+ fold=args.fold,
148
+ padding_part=args.padding_part,
149
+ crops_dir=args.crops_dir,
150
+ data_path=args.data_dir,
151
+ folds_csv=args.folds_csv,
152
+ transforms=create_val_transforms(conf["size"]),
153
+ normalize=conf.get("normalize", None))
154
+ val_data_loader = DataLoader(data_val, batch_size=batch_size * 2, num_workers=args.workers, shuffle=False,
155
+ pin_memory=False)
156
+ os.makedirs(args.logdir, exist_ok=True)
157
+ summary_writer = SummaryWriter(args.logdir + '/' + conf.get("prefix", args.prefix) + conf['encoder'] + "_" + str(args.fold))
158
+ if args.resume:
159
+ if os.path.isfile(args.resume):
160
+ print("=> loading checkpoint '{}'".format(args.resume))
161
+ checkpoint = torch.load(args.resume, map_location='cpu')
162
+ state_dict = checkpoint['state_dict']
163
+ state_dict = {k[7:]: w for k, w in state_dict.items()}
164
+ model.load_state_dict(state_dict, strict=False)
165
+ if not args.from_zero:
166
+ start_epoch = checkpoint['epoch']
167
+ if not args.zero_score:
168
+ bce_best = checkpoint.get('bce_best', 0)
169
+ print("=> loaded checkpoint '{}' (epoch {}, bce_best {})"
170
+ .format(args.resume, checkpoint['epoch'], checkpoint['bce_best']))
171
+ else:
172
+ print("=> no checkpoint found at '{}'".format(args.resume))
173
+ if args.from_zero:
174
+ start_epoch = 0
175
+ current_epoch = start_epoch
176
+
177
+ if conf['fp16']:
178
+ model, optimizer = amp.initialize(model, optimizer,
179
+ opt_level=args.opt_level,
180
+ loss_scale='dynamic')
181
+
182
+ snapshot_name = "{}{}_{}_{}".format(conf.get("prefix", args.prefix), conf['network'], conf['encoder'], args.fold)
183
+
184
+ if args.distributed:
185
+ model = DistributedDataParallel(model, delay_allreduce=True)
186
+ else:
187
+ model = DataParallel(model).cuda()
188
+ data_val.reset(1, args.seed)
189
+ max_epochs = conf['optimizer']['schedule']['epochs']
190
+ for epoch in range(start_epoch, max_epochs):
191
+ data_train.reset(epoch, args.seed)
192
+ train_sampler = None
193
+ if args.distributed:
194
+ train_sampler = torch.utils.data.distributed.DistributedSampler(data_train)
195
+ train_sampler.set_epoch(epoch)
196
+ if epoch < args.freeze_epochs:
197
+ print("Freezing encoder!!!")
198
+ model.module.encoder.eval()
199
+ for p in model.module.encoder.parameters():
200
+ p.requires_grad = False
201
+ else:
202
+ model.module.encoder.train()
203
+ for p in model.module.encoder.parameters():
204
+ p.requires_grad = True
205
+
206
+ train_data_loader = DataLoader(data_train, batch_size=batch_size, num_workers=args.workers,
207
+ shuffle=train_sampler is None, sampler=train_sampler, pin_memory=False,
208
+ drop_last=True)
209
+
210
+ train_epoch(current_epoch, loss_functions, model, optimizer, scheduler, train_data_loader, summary_writer, conf,
211
+ args.local_rank, args.only_changed_frames)
212
+ model = model.eval()
213
+
214
+ if args.local_rank == 0:
215
+ torch.save({
216
+ 'epoch': current_epoch + 1,
217
+ 'state_dict': model.state_dict(),
218
+ 'bce_best': bce_best,
219
+ }, args.output_dir + '/' + snapshot_name + "_last")
220
+ torch.save({
221
+ 'epoch': current_epoch + 1,
222
+ 'state_dict': model.state_dict(),
223
+ 'bce_best': bce_best,
224
+ }, args.output_dir + snapshot_name + "_{}".format(current_epoch))
225
+ if (epoch + 1) % args.test_every == 0:
226
+ bce_best = evaluate_val(args, val_data_loader, bce_best, model,
227
+ snapshot_name=snapshot_name,
228
+ current_epoch=current_epoch,
229
+ summary_writer=summary_writer)
230
+ current_epoch += 1
231
+
232
+
233
+ def evaluate_val(args, data_val, bce_best, model, snapshot_name, current_epoch, summary_writer):
234
+ print("Test phase")
235
+ model = model.eval()
236
+
237
+ bce, probs, targets = validate(model, data_loader=data_val)
238
+ if args.local_rank == 0:
239
+ summary_writer.add_scalar('val/bce', float(bce), global_step=current_epoch)
240
+ if bce < bce_best:
241
+ print("Epoch {} improved from {} to {}".format(current_epoch, bce_best, bce))
242
+ if args.output_dir is not None:
243
+ torch.save({
244
+ 'epoch': current_epoch + 1,
245
+ 'state_dict': model.state_dict(),
246
+ 'bce_best': bce,
247
+ }, args.output_dir + snapshot_name + "_best_dice")
248
+ bce_best = bce
249
+ with open("predictions_{}.json".format(args.fold), "w") as f:
250
+ json.dump({"probs": probs, "targets": targets}, f)
251
+ torch.save({
252
+ 'epoch': current_epoch + 1,
253
+ 'state_dict': model.state_dict(),
254
+ 'bce_best': bce_best,
255
+ }, args.output_dir + snapshot_name + "_last")
256
+ print("Epoch: {} bce: {}, bce_best: {}".format(current_epoch, bce, bce_best))
257
+ return bce_best
258
+
259
+
260
+ def validate(net, data_loader, prefix=""):
261
+ probs = defaultdict(list)
262
+ targets = defaultdict(list)
263
+
264
+ with torch.no_grad():
265
+ for sample in tqdm(data_loader):
266
+ imgs = sample["image"].cuda()
267
+ img_names = sample["img_name"]
268
+ labels = sample["labels"].cuda().float()
269
+ out = net(imgs)
270
+ labels = labels.cpu().numpy()
271
+ preds = torch.sigmoid(out).cpu().numpy()
272
+ for i in range(out.shape[0]):
273
+ video, img_id = img_names[i].split("\\")
274
+ probs[video].append(preds[i].tolist())
275
+ targets[video].append(labels[i].tolist())
276
+ data_x = []
277
+ data_y = []
278
+ for vid, score in probs.items():
279
+ score = np.array(score)
280
+ lbl = targets[vid]
281
+
282
+ score = np.mean(score)
283
+ lbl = np.mean(lbl)
284
+ data_x.append(score)
285
+ data_y.append(lbl)
286
+ y = np.array(data_y)
287
+ x = np.array(data_x)
288
+ fake_idx = y > 0.1
289
+ real_idx = y < 0.1
290
+ fake_loss = log_loss(y[fake_idx], x[fake_idx], labels=[0, 1])
291
+ real_loss = log_loss(y[real_idx], x[real_idx], labels=[0, 1])
292
+ print("{}fake_loss".format(prefix), fake_loss)
293
+ print("{}real_loss".format(prefix), real_loss)
294
+
295
+ return (fake_loss + real_loss) / 2, probs, targets
296
+
297
+
298
+ def train_epoch(current_epoch, loss_functions, model, optimizer, scheduler, train_data_loader, summary_writer, conf,
299
+ local_rank, only_valid):
300
+ losses = AverageMeter()
301
+ fake_losses = AverageMeter()
302
+ real_losses = AverageMeter()
303
+ max_iters = conf["batches_per_epoch"]
304
+ print("training epoch {}".format(current_epoch))
305
+ model.train()
306
+ pbar = tqdm(enumerate(train_data_loader), total=max_iters, desc="Epoch {}".format(current_epoch), ncols=0)
307
+ if conf["optimizer"]["schedule"]["mode"] == "epoch":
308
+ scheduler.step(current_epoch)
309
+ for i, sample in pbar:
310
+ imgs = sample["image"].cuda()
311
+ labels = sample["labels"].cuda().float()
312
+ out_labels = model(imgs)
313
+ if only_valid:
314
+ valid_idx = sample["valid"].cuda().float() > 0
315
+ out_labels = out_labels[valid_idx]
316
+ labels = labels[valid_idx]
317
+ if labels.size(0) == 0:
318
+ continue
319
+
320
+ fake_loss = 0
321
+ real_loss = 0
322
+ fake_idx = labels > 0.5
323
+ real_idx = labels <= 0.5
324
+
325
+ ohem = conf.get("ohem_samples", None)
326
+ if torch.sum(fake_idx * 1) > 0:
327
+ fake_loss = loss_functions["classifier_loss"](out_labels[fake_idx], labels[fake_idx])
328
+ if torch.sum(real_idx * 1) > 0:
329
+ real_loss = loss_functions["classifier_loss"](out_labels[real_idx], labels[real_idx])
330
+ if ohem:
331
+ fake_loss = topk(fake_loss, k=min(ohem, fake_loss.size(0)), sorted=False)[0].mean()
332
+ real_loss = topk(real_loss, k=min(ohem, real_loss.size(0)), sorted=False)[0].mean()
333
+
334
+ loss = (fake_loss + real_loss) / 2
335
+ losses.update(loss.item(), imgs.size(0))
336
+ fake_losses.update(0 if fake_loss == 0 else fake_loss.item(), imgs.size(0))
337
+ real_losses.update(0 if real_loss == 0 else real_loss.item(), imgs.size(0))
338
+
339
+ optimizer.zero_grad()
340
+ pbar.set_postfix({"lr": float(scheduler.get_lr()[-1]), "epoch": current_epoch, "loss": losses.avg,
341
+ "fake_loss": fake_losses.avg, "real_loss": real_losses.avg})
342
+
343
+ if conf['fp16']:
344
+ with amp.scale_loss(loss, optimizer) as scaled_loss:
345
+ scaled_loss.backward()
346
+ else:
347
+ loss.backward()
348
+ torch.nn.utils.clip_grad_norm_(amp.master_params(optimizer), 1)
349
+ optimizer.step()
350
+ torch.cuda.synchronize()
351
+ if conf["optimizer"]["schedule"]["mode"] in ("step", "poly"):
352
+ scheduler.step(i + current_epoch * max_iters)
353
+ if i == max_iters - 1:
354
+ break
355
+ pbar.close()
356
+ if local_rank == 0:
357
+ for idx, param_group in enumerate(optimizer.param_groups):
358
+ lr = param_group['lr']
359
+ summary_writer.add_scalar('group{}/lr'.format(idx), float(lr), global_step=current_epoch)
360
+ summary_writer.add_scalar('train/loss', float(losses.avg), global_step=current_epoch)
361
+
362
+
363
+ if __name__ == '__main__':
364
+ main()
365
+ z = 2
training/tools/__init__.py ADDED
File without changes
training/tools/__pycache__/__init__.cpython-39.pyc ADDED
Binary file (172 Bytes). View file