{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":16880,"databundleVersionId":858837}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 🕵️‍♂️ Deepfake Video Detection Pipeline\n\n## Project Overview\nThe goal of this notebook is to build an end-to-end deep learning pipeline capable of analyzing a video file and classifying it as either **Real** or **AI-Generated (Fake)**. \n\nDeepfakes manipulate visual and sometimes temporal data. To detect these anomalies, this pipeline will process raw video data, extract individual frames, and leverage deep learning architectures to identify subtle inconsistencies that human eyes might miss.","metadata":{}},{"cell_type":"markdown","source":"## Step 1: Environment Setup & Dependencies","metadata":{}},{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:33:40.156334Z","iopub.execute_input":"2026-05-05T17:33:40.156743Z","iopub.status.idle":"2026-05-05T17:33:43.171145Z","shell.execute_reply.started":"2026-05-05T17:33:40.156716Z","shell.execute_reply":"2026-05-05T17:33:43.170304Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nos.environ[\"CUDA_VISIBLE_DEVICES\"] = \"-1\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:33:49.857037Z","iopub.execute_input":"2026-05-05T17:33:49.857737Z","iopub.status.idle":"2026-05-05T17:33:49.861673Z","shell.execute_reply.started":"2026-05-05T17:33:49.857706Z","shell.execute_reply":"2026-05-05T17:33:49.860783Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -q git+https://github.com/tensorflow/docs","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:33:51.755990Z","iopub.execute_input":"2026-05-05T17:33:51.756680Z","iopub.status.idle":"2026-05-05T17:34:06.946870Z","shell.execute_reply.started":"2026-05-05T17:33:51.756649Z","shell.execute_reply":"2026-05-05T17:34:06.945832Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Standard Libraries ---\nfrom base64 import b64encode\nfrom pathlib import Path\n\n# --- Third-Party Utilities ---\nimport cv2\nimport imageio\nimport numpy as np\nimport pandas as pd\nfrom imutils import paths\nfrom sklearn.model_selection import train_test_split\n\n# --- Deep Learning Frameworks ---\nimport tensorflow as tf\nfrom tensorflow import keras\n\n# --- Visualization & Notebook Output ---\nimport matplotlib.pyplot as plt\nfrom IPython.display import HTML\nfrom tensorflow_docs.vis import embed","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:34:14.354853Z","iopub.execute_input":"2026-05-05T17:34:14.355307Z","iopub.status.idle":"2026-05-05T17:34:41.637033Z","shell.execute_reply.started":"2026-05-05T17:34:14.355273Z","shell.execute_reply":"2026-05-05T17:34:41.636426Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 2: Data Directory Setup & Verification","metadata":{}},{"cell_type":"code","source":"DATA_FOLDER = '/kaggle/input/competitions/deepfake-detection-challenge'\nTRAIN_SAMPLE_FOLDER = '/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos'\nTEST_FOLDER = '/kaggle/input/competitions/deepfake-detection-challenge/test_videos'\n\nprint(f\"Train samples: {len(os.listdir(os.path.join(DATA_FOLDER, TRAIN_SAMPLE_FOLDER)))}\")\nprint(f\"Test samples: {len(os.listdir(os.path.join(DATA_FOLDER, TEST_FOLDER)))}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:34:45.121235Z","iopub.execute_input":"2026-05-05T17:34:45.121844Z","iopub.status.idle":"2026-05-05T17:34:45.129504Z","shell.execute_reply.started":"2026-05-05T17:34:45.121816Z","shell.execute_reply":"2026-05-05T17:34:45.128854Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_sample_metadata = pd.read_json('/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos/metadata.json').T\ntrain_sample_metadata.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:34:47.788827Z","iopub.execute_input":"2026-05-05T17:34:47.789667Z","iopub.status.idle":"2026-05-05T17:34:47.887559Z","shell.execute_reply.started":"2026-05-05T17:34:47.789635Z","shell.execute_reply":"2026-05-05T17:34:47.886820Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_sample_metadata['label'].value_counts().plot(\n    kind='bar', \n    figsize=(15, 5), \n    title='Distribution of labels in the Training Set'\n)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:34:50.404383Z","iopub.execute_input":"2026-05-05T17:34:50.405073Z","iopub.status.idle":"2026-05-05T17:34:50.662030Z","shell.execute_reply.started":"2026-05-05T17:34:50.405042Z","shell.execute_reply":"2026-05-05T17:34:50.661405Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_sample_metadata['label'].value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:34:53.964527Z","iopub.execute_input":"2026-05-05T17:34:53.965125Z","iopub.status.idle":"2026-05-05T17:34:53.971408Z","shell.execute_reply.started":"2026-05-05T17:34:53.965062Z","shell.execute_reply":"2026-05-05T17:34:53.970562Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_sample_metadata.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:34:55.149196Z","iopub.execute_input":"2026-05-05T17:34:55.149756Z","iopub.status.idle":"2026-05-05T17:34:55.154428Z","shell.execute_reply.started":"2026-05-05T17:34:55.149728Z","shell.execute_reply":"2026-05-05T17:34:55.153874Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fake_train_sample_video = list(train_sample_metadata.loc[train_sample_metadata.label=='FAKE'].sample(5).index)\nfake_train_sample_video","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:34:56.464322Z","iopub.execute_input":"2026-05-05T17:34:56.465023Z","iopub.status.idle":"2026-05-05T17:34:56.474494Z","shell.execute_reply.started":"2026-05-05T17:34:56.464992Z","shell.execute_reply":"2026-05-05T17:34:56.473869Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 3: Video Frame Extraction & Visualization\n\nTo verify our data and visually inspect the content of the video files, we define a helper function, `display_image_from_video`. This function allows us to \"peek\" into a video by extracting and rendering its first frame.","metadata":{}},{"cell_type":"code","source":"def display_image_from_video(video_path):\n    '''\n    input: video_path - path for video\n    process:\n    1. perform a video capture from the video\n    2. read the image\n    3. display the image\n    '''\n\n    capture_image = cv2.VideoCapture(video_path)\n    ret, frame = capture_image.read()\n    fig = plt.figure(figsize=(10, 10))\n    ax = fig.add_subplot(111)\n    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n    ax.imshow(frame)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:34:59.716991Z","iopub.execute_input":"2026-05-05T17:34:59.717749Z","iopub.status.idle":"2026-05-05T17:34:59.722122Z","shell.execute_reply.started":"2026-05-05T17:34:59.717719Z","shell.execute_reply":"2026-05-05T17:34:59.721338Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for video_file in fake_train_sample_video:\n    display_image_from_video(os.path.join(DATA_FOLDER, TRAIN_SAMPLE_FOLDER, video_file))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:00.972305Z","iopub.execute_input":"2026-05-05T17:35:00.972820Z","iopub.status.idle":"2026-05-05T17:35:03.302599Z","shell.execute_reply.started":"2026-05-05T17:35:00.972790Z","shell.execute_reply":"2026-05-05T17:35:03.301834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"real_train_sample_video = list(train_sample_metadata.loc[train_sample_metadata.label=='REAL'].sample(5).index)\nreal_train_sample_video","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:09.468620Z","iopub.execute_input":"2026-05-05T17:35:09.469286Z","iopub.status.idle":"2026-05-05T17:35:09.475628Z","shell.execute_reply.started":"2026-05-05T17:35:09.469254Z","shell.execute_reply":"2026-05-05T17:35:09.474833Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for video_file in real_train_sample_video:\n    display_image_from_video(os.path.join(DATA_FOLDER, TRAIN_SAMPLE_FOLDER, video_file))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:11.197428Z","iopub.execute_input":"2026-05-05T17:35:11.197989Z","iopub.status.idle":"2026-05-05T17:35:13.544451Z","shell.execute_reply.started":"2026-05-05T17:35:11.197958Z","shell.execute_reply":"2026-05-05T17:35:13.543532Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_sample_metadata['original'].value_counts().head(5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:15.989552Z","iopub.execute_input":"2026-05-05T17:35:15.990316Z","iopub.status.idle":"2026-05-05T17:35:15.996903Z","shell.execute_reply.started":"2026-05-05T17:35:15.990282Z","shell.execute_reply":"2026-05-05T17:35:15.996174Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 4: Batch Video Comparison (Grid View)\n\nTo identify patterns across multiple videos—such as consistent artifacts in FAKE videos or lighting variations in REAL videos—we use the `display_image_from_video_list` function. This utility automates the extraction process for a collection of files and organizes them into a structured grid.","metadata":{}},{"cell_type":"code","source":"def display_image_from_video_list(video_path_list, video_folder=TRAIN_SAMPLE_FOLDER):\n    '''\n    input: video_path_list - path for video\n    process:\n    0. for each video in the video path list\n        1. perform a video capture from the video\n        2. read the image\n        3. display the image\n    '''\n    plt.figure()\n    fig, ax = plt.subplots(2, 3, figsize=(16, 8))\n    # Only show images extracted from the first 6 videos\n    for i, video_file in enumerate(video_path_list[0:6]):\n        video_path = os.path.join(DATA_FOLDER, video_folder, video_file)\n        capture_image = cv2.VideoCapture(video_path)\n        ret, frame = capture_image.read()\n        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n        ax[i//3, i%3].imshow(frame)\n        ax[i//3, i%3].set_title(f\"Video: {video_file}\")\n        ax[i//3, i%3].axis('on')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:20.704822Z","iopub.execute_input":"2026-05-05T17:35:20.705593Z","iopub.status.idle":"2026-05-05T17:35:20.710864Z","shell.execute_reply.started":"2026-05-05T17:35:20.705562Z","shell.execute_reply":"2026-05-05T17:35:20.710069Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"same_original_fake_train_sample_video = list(train_sample_metadata.loc[train_sample_metadata.original=='atvmxvwyns.mp4'].index)\ndisplay_image_from_video_list(same_original_fake_train_sample_video)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:22.804479Z","iopub.execute_input":"2026-05-05T17:35:22.805127Z","iopub.status.idle":"2026-05-05T17:35:24.729893Z","shell.execute_reply.started":"2026-05-05T17:35:22.805063Z","shell.execute_reply":"2026-05-05T17:35:24.729068Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_videos = pd.DataFrame(list(os.listdir(os.path.join(DATA_FOLDER, TEST_FOLDER))), columns=['video'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:27.061935Z","iopub.execute_input":"2026-05-05T17:35:27.062707Z","iopub.status.idle":"2026-05-05T17:35:27.067420Z","shell.execute_reply.started":"2026-05-05T17:35:27.062676Z","shell.execute_reply":"2026-05-05T17:35:27.066690Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_videos.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:29.672447Z","iopub.execute_input":"2026-05-05T17:35:29.673058Z","iopub.status.idle":"2026-05-05T17:35:29.679647Z","shell.execute_reply.started":"2026-05-05T17:35:29.673031Z","shell.execute_reply":"2026-05-05T17:35:29.678993Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"display_image_from_video(os.path.join(DATA_FOLDER, TEST_FOLDER, test_videos.iloc[2].video))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:31.699648Z","iopub.execute_input":"2026-05-05T17:35:31.700370Z","iopub.status.idle":"2026-05-05T17:35:32.184570Z","shell.execute_reply.started":"2026-05-05T17:35:31.700341Z","shell.execute_reply":"2026-05-05T17:35:32.183724Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fake_videos = list(train_sample_metadata.loc[train_sample_metadata.label=='FAKE'].index)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:34.139870Z","iopub.execute_input":"2026-05-05T17:35:34.140237Z","iopub.status.idle":"2026-05-05T17:35:34.145041Z","shell.execute_reply.started":"2026-05-05T17:35:34.140207Z","shell.execute_reply":"2026-05-05T17:35:34.144392Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 5: Interactive In-Notebook Video Playback\n\nTo perform a deeper qualitative analysis of the dataset, we implement `play_video`. This function allows for the direct embedding of `.mp4` files into the notebook, enabling us to scrub through the timeline and inspect temporal inconsistencies (like flickering or audio-visual lag) that static frames might miss.","metadata":{}},{"cell_type":"code","source":"def play_video(video_file, subset=TRAIN_SAMPLE_FOLDER):\n    '''\n    Display video\n    param: video_file - the name of the video file to display\n    param: subset - the folder where the video file is located (can be TRAIN_SAMPLE_FOLDER or TEST_Folder)\n    '''\n    video_url = open(os.path.join(DATA_FOLDER, subset, video_file), 'rb').read()\n    data_url = \"data:video/mp4;base64,\" + b64encode(video_url).decode()\n    return HTML(\"\"\"<video width=500 controls><source src=\"%s\" type=\"video/mp4\"></video>\"\"\" % data_url)\n\nplay_video(fake_videos[10])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:37.070158Z","iopub.execute_input":"2026-05-05T17:35:37.070898Z","iopub.status.idle":"2026-05-05T17:35:37.456891Z","shell.execute_reply.started":"2026-05-05T17:35:37.070867Z","shell.execute_reply":"2026-05-05T17:35:37.455561Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_SIZE = 380\nBATCH_SIZE = 32\nEPOCHS = 10\n\nMAX_SEQ_LENGTH = 20\nNUM_FEATURES = 1792","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:45.270217Z","iopub.execute_input":"2026-05-05T17:35:45.270916Z","iopub.status.idle":"2026-05-05T17:35:45.274710Z","shell.execute_reply.started":"2026-05-05T17:35:45.270886Z","shell.execute_reply":"2026-05-05T17:35:45.273934Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 6: Video Preprocessing & Frame Loading\n\nTo prepare the video data for the deep learning model, we need to standardize the spatial and temporal dimensions. These functions handle the transformation of raw video files into structured NumPy arrays.","metadata":{}},{"cell_type":"code","source":"def crop_center_sqaure(frame):\n    y, x= frame.shape[0:2]\n    min_dim = min(y, x)\n    start_x = (x // 2) - (min_dim // 2)\n    start_y = (y // 2) - (min_dim // 2)\n    return frame[start_y : start_y + min_dim, start_x : start_x + min_dim]\n\ndef load_video(path, max_frames=0, resize=(IMG_SIZE, IMG_SIZE)):\n    cap = cv2.VideoCapture(path)\n    frames = []\n    try:\n        while True:\n            ret, frame = cap.read()\n            if not ret:\n                break\n            frame = crop_center_sqaure(frame)\n            frame = cv2.resize(frame, resize)\n            frame = frame[:, :, [2, 1, 0]]\n            frames.append(frame)\n\n            if len(frames) == max_frames:\n                break\n    finally:\n        cap.release()\n    return np.array(frames)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:48.435287Z","iopub.execute_input":"2026-05-05T17:35:48.435935Z","iopub.status.idle":"2026-05-05T17:35:48.442327Z","shell.execute_reply.started":"2026-05-05T17:35:48.435903Z","shell.execute_reply":"2026-05-05T17:35:48.441454Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 7: Feature Extraction via Transfer Learning (InceptionV3)\n\nTo translate raw pixel data into meaningful high-level features, we utilize a pre-trained **InceptionV3** architecture. This allows the system to leverage complex visual patterns already learned from the ImageNet dataset, focusing on feature representation rather than basic edge detection.","metadata":{}},{"cell_type":"code","source":"def build_feature_extractor():\n    feature_extractor = keras.applications.EfficientNetB4(\n        weights='imagenet',\n        include_top=False,\n        pooling='avg',\n        input_shape=(IMG_SIZE, IMG_SIZE, 3),\n    )\n    \n    inputs = keras.Input((IMG_SIZE, IMG_SIZE, 3))\n\n    outputs = feature_extractor(inputs)\n    \n    return keras.Model(inputs, outputs, name=\"feature_extractor\")\n\nfeature_extractor = build_feature_extractor()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:35:53.290845Z","iopub.execute_input":"2026-05-05T17:35:53.291697Z","iopub.status.idle":"2026-05-05T17:35:56.250756Z","shell.execute_reply.started":"2026-05-05T17:35:53.291666Z","shell.execute_reply":"2026-05-05T17:35:56.249856Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 8: Multi-Step Video Processing and Feature Encoding\nThis function, prepare_all_videos, serves as the final data pipeline that bridges the gap between raw video files and the recurrent neural network. It orchestrates the extraction of spatial features and generates the temporal metadata required for training.","metadata":{}},{"cell_type":"code","source":"def prepare_all_videos(df, root_dir):\n    num_samples = len(df)\n    video_paths = list(df.index)\n    labels = df['label'].values\n    labels = np.array(labels=='FAKE').astype(int)\n\n    # 'frame_masks' and 'frame_features' are what we will feed to our sequence model.\n    # 'frame_masks' will contain a bunch of booleans denoting if a timestep is masked with padding or not.\n\n    frame_masks = np.zeros(shape=(num_samples, MAX_SEQ_LENGTH), dtype='bool')\n    frame_features = np.zeros(\n        shape=(num_samples, MAX_SEQ_LENGTH, NUM_FEATURES), dtype='float32'\n    )\n\n    for idx, path in enumerate(video_paths):\n        frames = load_video(os.path.join(root_dir, path))\n        frames = frames[None, ...]\n\n        temp_fram_mask = np.zeros(shape=(1, MAX_SEQ_LENGTH,), dtype='bool')\n        temp_fram_features = np.zeros(\n            shape=(1, MAX_SEQ_LENGTH, NUM_FEATURES), dtype='float32'\n        )\n        \n        for i, batch in enumerate(frames):\n            video_length = batch.shape[0]\n            length = min(MAX_SEQ_LENGTH, video_length)\n            for j in range(length):\n                temp_fram_features[i, j, :] = feature_extractor.predict(\n                    batch[None, j, :]\n                )\n            temp_fram_mask[i, :length] = 1\n            \n        frame_features[idx,] = temp_fram_features.squeeze()\n        frame_masks[idx,] = temp_fram_mask.squeeze()\n        \n    return (frame_features, frame_masks), labels\n    \n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:36:05.232888Z","iopub.execute_input":"2026-05-05T17:36:05.233515Z","iopub.status.idle":"2026-05-05T17:36:05.240517Z","shell.execute_reply.started":"2026-05-05T17:36:05.233482Z","shell.execute_reply":"2026-05-05T17:36:05.239772Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"Train_set, Test_set = train_test_split(train_sample_metadata, test_size=0.2, random_state=42, stratify=train_sample_metadata['label'])\n\nprint(Train_set.shape, Test_set.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:36:07.305796Z","iopub.execute_input":"2026-05-05T17:36:07.306569Z","iopub.status.idle":"2026-05-05T17:36:07.314412Z","shell.execute_reply.started":"2026-05-05T17:36:07.306539Z","shell.execute_reply":"2026-05-05T17:36:07.313782Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data, train_labels = prepare_all_videos(Train_set, TRAIN_SAMPLE_FOLDER)\ntest_data, test_labels = prepare_all_videos(Test_set, TRAIN_SAMPLE_FOLDER)\n\nprint(f'Frame features in the train set: {train_data[0].shape}')\nprint(f'Frame masks in train set: {train_data[1].shape}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T17:36:08.592372Z","iopub.execute_input":"2026-05-05T17:36:08.593169Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"np.save('train_features.npy', train_data[0])\nnp.save('train_labels.npy', train_labels)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 9: Recurrent Neural Network (RNN) Architecture and Training\nThis section defines the deep learning architecture designed to analyze the temporal sequences of the video features. By using a stacked Gated Recurrent Unit (GRU) approach, the model learns to identify motion-based inconsistencies typical of deepfake generation.","metadata":{}},{"cell_type":"code","source":"frame_features_input = keras.Input((MAX_SEQ_LENGTH, NUM_FEATURES))\nmask_input = keras.Input((MAX_SEQ_LENGTH,), dtype='bool')\nx = keras.layers.GRU(64, return_sequences=True)(frame_features_input, mask=mask_input)\n\nx = keras.layers.BatchNormalization()(x)\n\nx = keras.layers.GRU(32, return_sequences=True)(x)\n\nx = keras.layers.BatchNormalization()(x)\n\nx = keras.layers.GRU(16)(x)\n\nx = keras.layers.Dropout(0.5)(x)\n\nx= keras.layers.Dense(32, activation='relu')(x)\n\nx = keras.layers.Dropout(0.5)(x)\n\noutput = keras.layers.Dense(1, activation='sigmoid')(x)\n\nmodel = keras.Model([frame_features_input, mask_input], output)\n\noptimizer = keras.optimizers.Adam(learning_rate=1e-4)\n\nmodel.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'])\nmodel.summary()\n\nearly_stopping = keras.callbacks.EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)\n\ncheckpoint = keras.callbacks.ModelCheckpoint('best_model.weights.h5', save_weights_only=True, save_best_only=True)\n\nclass_weights = {\n    0: 2.60,  \n    1: 0.62   \n}\n\nhistory = model.fit(\n    [train_data[0], train_data[1]],\n    train_labels,\n    validation_data = ([test_data[0], test_data[1]], test_labels),\n    callbacks = [checkpoint, early_stopping],\n    epochs = EPOCHS,\n    batch_size = 16,\n    class_weight = class_weights\n)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 10: Model Performance Visualization and Evaluation\nThis section visualizes the training history to assess the model's learning trajectory and diagnostic health. By plotting the accuracy and loss curves, we can determine if the model is effectively generalizing or if it has begun to overfit the training data.","metadata":{}},{"cell_type":"code","source":"accuracy = history.history['accuracy']\nval_accuracy = history.history['val_accuracy']\nloss = history.history['loss']\nval_loss = history.history['val_loss']\nepochs = range(1, len(accuracy) + 1)\n\nplt.figure(figsize=(14, 5))\n\nplt.subplot(1, 2, 1)\nplt.plot(epochs, accuracy, 'bo-', label='Training Accuracy')\nplt.plot(epochs, val_accuracy, 'ro-', label='Validation Accuracy')\nplt.title('Training and Validation Accuracies')\nplt.xlabel('Epochs')\nplt.ylabel('Accuracy')\nplt.legend()\n\nplt.subplot(1, 2, 2)\nplt.plot(epochs, loss, 'bo-', label='Training Loss')\nplt.plot(epochs, val_loss, 'ro-', label='Validation Loss')\nplt.title('Training and Validation Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.legend()\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 11: Inference Pipeline and Test Set Evaluation\nThis final section implements the end-to-end inference logic required to classify unseen videos from the test set. It encapsulates the full transformation from raw video file to a final prediction label.","metadata":{}},{"cell_type":"code","source":"def prepare_single_video(frames):\n    frames = frames[None, ...]\n    frame_mask = np.zeros(shape=(1, MAX_SEQ_LENGTH,), dtype='bool')\n    frame_features = np.zeros(shape=(1, MAX_SEQ_LENGTH, NUM_FEATURES), dtype='float32')\n\n    for i, batch in enumerate(frames):\n        video_length = batch.shape[0]\n        length = min(MAX_SEQ_LENGTH, video_length)\n        for j in range(length):\n            frame_features[i, j, :] = feature_extractor.predict(batch[None, j, :])\n        frame_mask[i, :length] = 1\n\n    return frame_features, frame_mask\n\ndef sequence_prediction(path):\n    frames= load_video(os.path.join(DATA_FOLDER, TEST_FOLDER, path))\n    frame_features, frame_mask = prepare_single_video(frames)\n    return model.predict([frame_features, frame_mask])[0]\n\ndef to_gif(images):\n    converted_images= images.astype(np.uint8)\n    imageio.mimsave('animation.gif', converted_images, fps=10)\n    return embed.embed_file('animation.gif')\n\ntest_video = np.random.choice(test_videos['video'].values.tolist())\nprint(f'Test video path: {test_video}')\n\nif(sequence_prediction(test_video) >= 0.5):\n    print(f'The predicted class of the video is Fake')\nelse:\n    print(f'The predicted class of the video is Real')\n\nplay_video(test_video, TEST_FOLDER)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.save('deepfake_video_model.h5')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# %% [markdown]\n# ## Step 12: Advanced Model Evaluation (Confusion Matrix & ROC)\n\n# %%\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import confusion_matrix, classification_report, roc_curve, auc\n\n# 1. Setup variables\nTHRESHOLD = 0.5  # Defining the cutoff for REAL vs FAKE\n\nprint(\"Loading saved model for evaluation...\")\nloaded_model = keras.models.load_model('deepfake_video_model.h5')\n\nprint(\"Generating predictions on the test set...\")\n# test_data[0] is frame_features, test_data[1] is frame_masks\ny_pred_probs = loaded_model.predict([test_data[0], test_data[1]])\n\n# Convert raw probabilities to strict 0 or 1 classes using the threshold\ny_pred_classes = (y_pred_probs >= THRESHOLD).astype(int)\n\n\n# --- 2. Confusion Matrix ---\nplt.figure(figsize=(8, 6))\ncm = confusion_matrix(test_labels, y_pred_classes)\n\n# Use Seaborn to generate a professional heatmap\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n            xticklabels=['REAL (0)', 'FAKE (1)'], \n            yticklabels=['REAL (0)', 'FAKE (1)'])\n\nplt.title('Confusion Matrix: Deepfake Detection', fontsize=16)\nplt.xlabel('Predicted Label', fontsize=12)\nplt.ylabel('True Label', fontsize=12)\nplt.show()\n\n# Print the text-based classification report for precision, recall, and f1-score\nprint(\"\\nClassification Report:\\n\")\nprint(classification_report(test_labels, y_pred_classes, target_names=['REAL', 'FAKE']))\n\n\n# --- 3. ROC Curve & AUC ---\n# Calculate the False Positive Rate (fpr) and True Positive Rate (tpr)\nfpr, tpr, thresholds = roc_curve(test_labels, y_pred_probs)\nroc_auc = auc(fpr, tpr)\n\nplt.figure(figsize=(8, 6))\nplt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.3f})')\nplt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--', label='Random Guess')\n\nplt.xlim([0.0, 1.0])\nplt.ylim([0.0, 1.05])\nplt.xlabel('False Positive Rate (FPR)', fontsize=12)\nplt.ylabel('True Positive Rate (TPR)', fontsize=12)\nplt.title('Receiver Operating Characteristic (ROC) Curve', fontsize=16)\nplt.legend(loc=\"lower right\", fontsize=12)\nplt.grid(alpha=0.3)\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}