{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":16880,"databundleVersionId":858837,"sourceType":"competition"},{"sourceId":1003630,"sourceType":"datasetVersion","datasetId":442595}],"dockerImageVersionId":29845,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Data Preparation\n\n### Configure hyper-parameters","metadata":{}},{"cell_type":"code","source":"TRAIN_DIR = '/kaggle/input/deepfake-detection-challenge/train_sample_videos/'\n\nBATCH_SIZE = 1\nSCALE = 0.25\nN_FRAMES = None # The number of frames extracted from each video, 'None' means get all available frames","metadata":{"execution":{"iopub.status.busy":"2024-09-26T04:35:56.960276Z","iopub.execute_input":"2024-09-26T04:35:56.960572Z","iopub.status.idle":"2024-09-26T04:35:56.966364Z","shell.execute_reply.started":"2024-09-26T04:35:56.960522Z","shell.execute_reply":"2024-09-26T04:35:56.965268Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Install dependencies","metadata":{}},{"cell_type":"code","source":"# Install facenet-pytorch\n!pip install /kaggle/input/facenet-pytorch-vggface2/facenet_pytorch-2.0.0-py3-none-any.whl\n\nfrom facenet_pytorch.models.inception_resnet_v1 import get_torch_home\ntorch_home = get_torch_home()\n\n# Copy model checkpoints to torch cache so they are loaded automatically by the package\n!mkdir -p $torch_home/checkpoints/\n!cp /kaggle/input/facenet-pytorch-vggface2/20180402-114759-vggface2-logits.pth $torch_home/checkpoints/vggface2_DG3kwML46X.pt\n!cp /kaggle/input/facenet-pytorch-vggface2/20180402-114759-vggface2-features.pth $torch_home/checkpoints/vggface2_G5aNV2VSMn.pt","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2024-09-26T04:37:41.163323Z","iopub.execute_input":"2024-09-26T04:37:41.163711Z","iopub.status.idle":"2024-09-26T04:38:15.248812Z","shell.execute_reply.started":"2024-09-26T04:37:41.163652Z","shell.execute_reply":"2024-09-26T04:38:15.247703Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Import libraries","metadata":{}},{"cell_type":"code","source":"import os\nimport glob\nimport json\nimport torch\nimport cv2\nfrom PIL import Image\nimport numpy as np\nimport pandas as pd\nfrom tqdm.notebook import tqdm\nfrom facenet_pytorch import MTCNN, InceptionResnetV1\n\ndevice = 'cuda:0' if torch.cuda.is_available() else 'cpu'\nprint(f'Running on device: {device}')","metadata":{"execution":{"iopub.status.busy":"2024-09-26T04:38:47.570066Z","iopub.execute_input":"2024-09-26T04:38:47.570373Z","iopub.status.idle":"2024-09-26T04:38:47.576639Z","shell.execute_reply.started":"2024-09-26T04:38:47.570330Z","shell.execute_reply":"2024-09-26T04:38:47.575728Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Define useful classes","metadata":{}},{"cell_type":"code","source":"# Source: https://www.kaggle.com/timesler/facial-recognition-model-in-pytorch\nclass DetectionPipeline:\n    \"\"\"Pipeline class for detecting faces in the frames of a video file.\"\"\"\n    \n    def __init__(self, detector, n_frames=None, batch_size=60, resize=None):\n        \"\"\"Constructor for DetectionPipeline class.\n        \n        Keyword Arguments:\n            n_frames {int} -- Total number of frames to load. These will be evenly spaced\n                throughout the video. If not specified (i.e., None), all frames will be loaded.\n                (default: {None})\n            batch_size {int} -- Batch size to use with MTCNN face detector. (default: {32})\n            resize {float} -- Fraction by which to resize frames from original prior to face\n                detection. A value less than 1 results in downsampling and a value greater than\n                1 result in upsampling. (default: {None})\n        \"\"\"\n        self.detector = detector\n        self.n_frames = n_frames\n        self.batch_size = batch_size\n        self.resize = resize\n    \n    def __call__(self, filename):\n        \"\"\"Load frames from an MP4 video and detect faces.\n\n        Arguments:\n            filename {str} -- Path to video.\n        \"\"\"\n        # Create video reader and find length\n        v_cap = cv2.VideoCapture(filename)\n        v_len = int(v_cap.get(cv2.CAP_PROP_FRAME_COUNT))\n\n        # Pick 'n_frames' evenly spaced frames to sample\n        if self.n_frames is None:\n            sample = np.arange(0, v_len)\n        else:\n            sample = np.linspace(0, v_len - 1, self.n_frames).astype(int)\n\n        # Loop through frames\n        faces = []\n        frames = []\n        for j in range(v_len):\n            success = v_cap.grab()\n            if j in sample:\n                # Load frame\n                success, frame = v_cap.retrieve()\n                if not success:\n                    continue\n                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n                frame = Image.fromarray(frame)\n                \n                # Resize frame to desired size\n                if self.resize is not None:\n                    frame = frame.resize([int(d * self.resize) for d in frame.size])\n                frames.append(frame)\n\n                # When batch is full, detect faces and reset frame list\n                if len(frames) % self.batch_size == 0 or j == sample[-1]:\n                    faces.extend(self.detector(frames))\n                    frames = []\n\n        v_cap.release()\n\n        return faces","metadata":{"execution":{"iopub.status.busy":"2024-09-26T04:39:06.866064Z","iopub.execute_input":"2024-09-26T04:39:06.866568Z","iopub.status.idle":"2024-09-26T04:39:06.881641Z","shell.execute_reply.started":"2024-09-26T04:39:06.866509Z","shell.execute_reply":"2024-09-26T04:39:06.880574Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Define helper-functions","metadata":{}},{"cell_type":"code","source":"# Source: https://www.kaggle.com/timesler/facial-recognition-model-in-pytorch\ndef process_faces(faces, feature_extractor):\n    # Filter out frames without faces\n    faces = [f for f in faces if f is not None]\n    if len(faces) == 0:\n        return None\n    faces = torch.cat(faces).to(device)\n\n    # Generate facial feature vectors using a pretrained model\n    embeddings = feature_extractor(faces)\n\n    # Calculate centroid for video and distance of each face's feature vector from centroid\n    centroid = embeddings.mean(dim=0)\n    x = (embeddings - centroid).norm(dim=1).cpu().numpy()\n    \n    return x","metadata":{"execution":{"iopub.status.busy":"2024-09-26T04:39:24.456446Z","iopub.execute_input":"2024-09-26T04:39:24.456770Z","iopub.status.idle":"2024-09-26T04:39:24.463336Z","shell.execute_reply.started":"2024-09-26T04:39:24.456724Z","shell.execute_reply":"2024-09-26T04:39:24.462477Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Start data-preparation process","metadata":{}},{"cell_type":"code","source":"# Load face detector\nface_detector = MTCNN(margin=14, keep_all=True, factor=0.5, device=device).eval()\n\n# Load facial recognition model\nfeature_extractor = InceptionResnetV1(pretrained='vggface2', device=device).eval()\n\n# Define face detection pipeline\ndetection_pipeline = DetectionPipeline(detector=face_detector, n_frames=N_FRAMES, batch_size=BATCH_SIZE, resize=SCALE)","metadata":{"execution":{"iopub.status.busy":"2024-09-26T04:39:40.540135Z","iopub.execute_input":"2024-09-26T04:39:40.540438Z","iopub.status.idle":"2024-09-26T04:39:45.744934Z","shell.execute_reply.started":"2024-09-26T04:39:40.540393Z","shell.execute_reply":"2024-09-26T04:39:45.743903Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Get the paths of all train videos\nall_train_videos = glob.glob(os.path.join(TRAIN_DIR, '*.mp4'))\n\n# Get path of metadata.json\nmetadata_path = TRAIN_DIR + 'metadata.json'\n\n# Get metadata\nwith open(metadata_path, 'r') as f:\n    metadata = json.load(f)","metadata":{"execution":{"iopub.status.busy":"2024-09-26T04:39:54.145277Z","iopub.execute_input":"2024-09-26T04:39:54.145574Z","iopub.status.idle":"2024-09-26T04:39:54.154033Z","shell.execute_reply.started":"2024-09-26T04:39:54.145529Z","shell.execute_reply":"2024-09-26T04:39:54.153247Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = pd.DataFrame(columns=['filename', 'distance', 'label'])\n\nwith torch.no_grad():\n    for path in tqdm(all_train_videos):\n        file_name = path.split('/')[-1]\n\n        # Detect all faces occur in the video\n        faces = detection_pipeline(path)\n        \n        # Calculate the distances of all faces' feature vectors to the centroid\n        distances = process_faces(faces, feature_extractor)\n        if distances is None:\n            continue\n\n        for distance in distances:\n            row = [\n                file_name,\n                distance,\n                1 if metadata[file_name]['label'] == 'FAKE' else 0\n            ]\n\n            # Append a new row at the end of the data frame\n            df.loc[len(df)] = row","metadata":{"execution":{"iopub.status.busy":"2024-09-26T04:40:23.215659Z","iopub.execute_input":"2024-09-26T04:40:23.215962Z","iopub.status.idle":"2024-09-26T05:48:51.186181Z","shell.execute_reply.started":"2024-09-26T04:40:23.215919Z","shell.execute_reply":"2024-09-26T05:48:51.185215Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Save prepared train data","metadata":{}},{"cell_type":"code","source":"df.to_csv('train.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training","metadata":{}},{"cell_type":"markdown","source":"### Configure hyper-parameters","metadata":{}},{"cell_type":"code","source":"TRAIN_PATH = '/kaggle/input/deepfake-detection-data-preparation/train.csv'\nSAVE_PATH = '/kaggle/working/model.pth'\n\nTEST_SIZE = 0.3\nRANDOM_STATE = 128\nEPOCHS = 200\nBATCH_SIZE = 64\nLR = 1e-4","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Import libraries","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom tqdm.notebook import tqdm\n\ndevice = 'cuda:0' if torch.cuda.is_available() else 'cpu'\nprint(f'Running on device: {device}')","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Define useful classes","metadata":{}},{"cell_type":"code","source":"class LogisticRegression(nn.Module):\n    def __init__(self, D_in=1, D_out=1):\n        super(LogisticRegression, self).__init__()\n        self.linear = nn.Linear(D_in, D_out)\n        \n    def forward(self, x):\n        y_pred = self.linear(x)\n\n        return y_pred\n    \n    def predict(self, x):\n        result = self.forward(x)\n\n        return torch.sigmoid(result)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Define helper-functions","metadata":{}},{"cell_type":"code","source":"def shuffle_data(X, y):\n    assert len(X) == len(y)\n    \n    p = np.random.permutation(len(X))\n    \n    return X[p], y[p]","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Get data","metadata":{}},{"cell_type":"code","source":"train_df = pd.read_csv(TRAIN_PATH)\ntrain_df.head()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"label_count = train_df.groupby('label').count()['filename']\nprint(label_count)\n\n# Use pos_weight value to overcome imbalanced dataset.\n# https://pytorch.org/docs/stable/nn.html#torch.nn.BCEWithLogitsLoss\npos_weight = torch.ones([1]) * label_count[0]/label_count[1]\nprint('pos_weight:', pos_weight)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X = train_df['distance'].to_numpy()\ny = train_df['label'].to_numpy()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=TEST_SIZE, random_state=RANDOM_STATE, stratify=y)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train = torch.tensor(X_train).to(device).unsqueeze(dim=1).float()\nX_val = torch.tensor(X_val).to(device).unsqueeze(dim=1).float()\ny_train = torch.tensor(y_train).to(device).unsqueeze(dim=1).float()\ny_val = torch.tensor(y_val).to(device).unsqueeze(dim=1).float()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Train the classifier","metadata":{}},{"cell_type":"code","source":"classifier = LogisticRegression()\ncriterion = nn.BCEWithLogitsLoss(reduction='mean', pos_weight=pos_weight) # Improve stability\noptimizer = optim.Adam(classifier.parameters(), lr=LR)\n\nn_batches = np.ceil(len(X_train) / BATCH_SIZE).astype(int)\nlosses = np.zeros(EPOCHS)\nval_losses = np.zeros(EPOCHS)\nbest_val_loss = 1e7\n\nfor e in tqdm(range(EPOCHS)):\n    batch_losses = np.zeros(n_batches)\n    pbar = tqdm(range(n_batches))\n    pbar.desc = f'Epoch {e+1}'\n    classifier.train()\n    \n    # Shuffle training data\n    X_train, y_train = shuffle_data(X_train, y_train)\n\n    for i in pbar:\n        # Get batch.\n        X_batch = X_train[i*BATCH_SIZE:min(len(X_train), (i+1)*BATCH_SIZE)]\n        y_batch = y_train[i*BATCH_SIZE:min(len(y_train), (i+1)*BATCH_SIZE)]\n\n        # Make prediction.\n        y_pred = classifier(X_batch)\n\n        # Compute loss.\n        loss = criterion(y_pred, y_batch)\n        batch_losses[i] = loss\n\n        # Zero gradients, perform a backward pass, and update the weights.\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n    \n    # Compute batch loss (average)\n    losses[e] = batch_losses.mean()\n    \n    # Compute val loss\n    classifier.eval()\n    y_val_pred = classifier(X_val)\n    val_losses[e] = criterion(y_val_pred, y_val)\n    \n    # Save model based on the best (lowest) val loss.\n    if val_losses[e] < best_val_loss:\n        print('Found a better checkpoint!')\n        torch.save(classifier.state_dict(), SAVE_PATH)\n        best_val_loss = val_losses[e]\n        \n    \n    # Display some information in progress-bar.\n    pbar.set_postfix({\n        'loss': losses[e],\n        'val_loss': val_losses[e]\n    })","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig = plt.figure(figsize=(16, 8))\nax = fig.add_axes([0, 0, 1, 1])\n\nax.plot(np.arange(EPOCHS), losses)\nax.plot(np.arange(EPOCHS), val_losses)\nax.set_xlabel('epoch', fontsize='xx-large')\nax.set_ylabel('log loss', fontsize='xx-large')\nax.legend(\n    ['loss', 'val loss'],\n    loc='upper right',\n    fontsize='xx-large',\n    shadow=True\n)\nplt.show()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"without_weight_criterion = nn.BCELoss(reduction='mean')\n\nclassifier.eval()\nwith torch.no_grad():\n    y_val_pred = classifier.predict(X_val)\n    val_loss = without_weight_criterion(y_val_pred, y_val)\n\nprint('val loss:', val_loss.detach().numpy())","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.hist(y_val_pred.squeeze(dim=-1).detach())\nplt.plot()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Inference","metadata":{}},{"cell_type":"markdown","source":"### Configure hyper-parameters","metadata":{}},{"cell_type":"code","source":"TEST_DIR = '/kaggle/input/deepfake-detection-challenge/test_videos/'\nMODEL_PATH = '/kaggle/input/deepfake-detection-logistic-regression/model.pth'\n\nBATCH_SIZE = 60\nSCALE = 0.25\nN_FRAMES = None # 'None' means using all available frames\nDEFAULT_PROB = 0.5","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Install dependencies","metadata":{}},{"cell_type":"code","source":"# Install facenet-pytorch\n!pip install /kaggle/input/facenet-pytorch-vggface2/facenet_pytorch-2.0.0-py3-none-any.whl\n\nfrom facenet_pytorch.models.inception_resnet_v1 import get_torch_home\ntorch_home = get_torch_home()\n\n# Copy model checkpoints to torch cache so they are loaded automatically by the package\n!mkdir -p $torch_home/checkpoints/\n!cp /kaggle/input/facenet-pytorch-vggface2/20180402-114759-vggface2-logits.pth $torch_home/checkpoints/vggface2_DG3kwML46X.pt\n!cp /kaggle/input/facenet-pytorch-vggface2/20180402-114759-vggface2-features.pth $torch_home/checkpoints/vggface2_G5aNV2VSMn.pt","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Import libraries","metadata":{}},{"cell_type":"code","source":"import os\nimport glob\nimport json\nimport torch\nimport torch.nn as nn\nimport cv2\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport numpy as np\nimport pandas as pd\nfrom tqdm.notebook import tqdm\nfrom facenet_pytorch import MTCNN, InceptionResnetV1, extract_face\n\nif torch.cuda.is_available():\n    device = 'cuda:0'\n    torch.set_default_tensor_type('torch.cuda.FloatTensor')\nelse:\n    device = 'cpu'\nprint(f'Running on device: {device}')","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Define useful classes","metadata":{}},{"cell_type":"code","source":"# Source: https://www.kaggle.com/timesler/facial-recognition-model-in-pytorch\nclass DetectionPipeline:\n    \"\"\"Pipeline class for detecting faces in the frames of a video file.\"\"\"\n    \n    def __init__(self, detector, n_frames=None, batch_size=60, resize=None):\n        \"\"\"Constructor for DetectionPipeline class.\n        \n        Keyword Arguments:\n            n_frames {int} -- Total number of frames to load. These will be evenly spaced\n                throughout the video. If not specified (i.e., None), all frames will be loaded.\n                (default: {None})\n            batch_size {int} -- Batch size to use with MTCNN face detector. (default: {32})\n            resize {float} -- Fraction by which to resize frames from original prior to face\n                detection. A value less than 1 results in downsampling and a value greater than\n                1 result in upsampling. (default: {None})\n        \"\"\"\n        self.detector = detector\n        self.n_frames = n_frames\n        self.batch_size = batch_size\n        self.resize = resize\n    \n    def __call__(self, filename):\n        \"\"\"Load frames from an MP4 video and detect faces.\n\n        Arguments:\n            filename {str} -- Path to video.\n        \"\"\"\n        # Create video reader and find length\n        v_cap = cv2.VideoCapture(filename)\n        v_len = int(v_cap.get(cv2.CAP_PROP_FRAME_COUNT))\n\n        # Pick 'n_frames' evenly spaced frames to sample\n        if self.n_frames is None:\n            sample = np.arange(0, v_len)\n        else:\n            sample = np.linspace(0, v_len - 1, self.n_frames).astype(int)\n\n        # Loop through frames\n        faces = []\n        frames = []\n        for j in range(v_len):\n            success = v_cap.grab()\n            if j in sample:\n                # Load frame\n                success, frame = v_cap.retrieve()\n                if not success:\n                    continue\n                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n                frame = Image.fromarray(frame)\n                \n                # Resize frame to desired size\n                if self.resize is not None:\n                    frame = frame.resize([int(d * self.resize) for d in frame.size])\n                frames.append(frame)\n\n                # When batch is full, detect faces and reset frame list\n                if len(frames) % self.batch_size == 0 or j == sample[-1]:\n                    faces.extend(self.detector(frames))\n                    frames = []\n\n        v_cap.release()\n\n        return faces\n    \n\nclass LogisticRegression(nn.Module):\n    def __init__(self, D_in=1, D_out=1):\n        super(LogisticRegression, self).__init__()\n        self.linear = nn.Linear(D_in, D_out)\n        \n    def forward(self, x):\n        y_pred = self.linear(x)\n        y_pred = torch.sigmoid(y_pred)\n        \n        return y_pred","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Define helper-functions","metadata":{}},{"cell_type":"code","source":"# Source: https://www.kaggle.com/timesler/facial-recognition-model-in-pytorch\ndef process_faces(faces, feature_extractor):\n    # Filter out frames without faces\n    faces = [f for f in faces if f is not None]\n    if len(faces) == 0:\n        return None\n    faces = torch.cat(faces).to(device)\n\n    # Generate facial feature vectors using a pretrained model\n    embeddings = feature_extractor(faces)\n\n    # Calculate centroid for video and distance of each face's feature vector from centroid\n    centroid = embeddings.mean(dim=0)\n    x = (embeddings - centroid).norm(dim=1).cpu().numpy()\n    \n    return x","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Start inference process","metadata":{}},{"cell_type":"code","source":"# Load model.\nclassifier = LogisticRegression()\nclassifier.load_state_dict(torch.load(MODEL_PATH))\nclassifier.eval()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Get all test videos.\nall_test_videos = glob.glob(os.path.join(TEST_DIR, '*.mp4'))","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load face detector.\nface_detector = MTCNN(margin=14, keep_all=True, factor=0.5, device=device).eval()\n\n# Load facial recognition model.\nfeature_extractor = InceptionResnetV1(pretrained='vggface2', device=device).eval()\n\n# Define face detection pipeline.\ndetection_pipeline = DetectionPipeline(detector=face_detector, n_frames=N_FRAMES, batch_size=BATCH_SIZE, resize=SCALE)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_test = []\n\nwith torch.no_grad():\n    for path in tqdm(all_test_videos):\n        try:\n            # Detect all faces occur in the video.\n            faces = detection_pipeline(path)\n\n            # Calculate the distances of all faces' feature vectors to the centroid.\n            distances = process_faces(faces, feature_extractor)\n            X_test.append(distances)\n        except:\n            X_test.append(None)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission = []\n\nwith torch.no_grad():\n    for path, distances in zip(all_test_videos, X_test):\n        file_name = os.path.basename(path)\n\n        if distances is not None:\n            distances = torch.tensor(distances).unsqueeze(dim=1).float().to(device)\n            y_pred = classifier(distances)\n            y_pred = float(y_pred.mean().cpu().numpy())\n        else:\n            y_pred = DEFAULT_PROB\n\n        submission.append([file_name, y_pred])","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission = pd.DataFrame(submission, columns=['filename', 'label'])\nsubmission.sort_values('filename').to_csv('submission.csv', index=False)\n\nplt.hist(submission.label, 20)\nplt.show()","metadata":{},"execution_count":null,"outputs":[]}]}