{"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":8661913,"sourceType":"datasetVersion","datasetId":5189844},{"sourceId":8664205,"sourceType":"datasetVersion","datasetId":5191601},{"sourceId":11473701,"sourceType":"datasetVersion","datasetId":7190748}],"dockerImageVersionId":29845,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport json\nimport cv2\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm import tqdm\n\nclass DeepFakeDetector3D(nn.Module):\n    def __init__(self, input_channels=4, max_frames=32):\n        super(DeepFakeDetector3D, self).__init__()\n        \n        # Input channels = 3 (RGB) + 1 (flow) = 4\n        self.conv1 = nn.Conv3d(input_channels, 16, kernel_size=3, stride=1, padding=1)\n        self.pool1 = nn.MaxPool3d(kernel_size=(1, 2, 2), stride=(1, 2, 2))  # Only pool spatially\n        self.conv2 = nn.Conv3d(16, 32, kernel_size=3, stride=1, padding=1)\n        self.pool2 = nn.MaxPool3d(kernel_size=(1, 2, 2), stride=(1, 2, 2))  # Only pool spatially\n        self.relu = nn.ReLU()\n        self.dropout = nn.Dropout(0.5)\n        \n        # Calculate the size after convolutions and pooling\n        # Starting with (B, 4, T, 128, 128)\n        # After pool1: (B, 16, T, 64, 64)\n        # After pool2: (B, 32, T, 32, 32)\n        frames_used = max_frames - 1  # Since flow has T-1 elements\n        fc_input_size = 32 * frames_used * 32 * 32\n        \n        self.fc1 = nn.Linear(fc_input_size, 512)\n        self.fc2 = nn.Linear(512, 1)\n        \n        # Loss components\n        self.flow_loss_weight = 0.1\n        self.bce_loss = nn.BCEWithLogitsLoss()\n\n    def forward(self, x):\n        # x shape: (B, C, T, H, W)\n        x = self.relu(self.conv1(x))\n        x = self.pool1(x)\n        x = self.relu(self.conv2(x))\n        x = self.pool2(x)\n        \n        # Print shape before flattening for debugging\n        # print(f\"Shape before flattening: {x.shape}\")\n        \n        # Flatten\n        x = x.view(x.size(0), -1)\n        x = self.relu(self.fc1(x))\n        x = self.dropout(x)\n        x = self.fc2(x)\n        return x\n\n    def compute_loss(self, outputs, targets, flow_tensor):\n        # Classification loss\n        cls_loss = self.bce_loss(outputs.squeeze(), targets)\n        \n        # Temporal consistency loss\n        if flow_tensor.size(2) > 1:  # Only if we have multiple frames\n            temp_loss = torch.mean(torch.abs(flow_tensor[:, :, 1:] - flow_tensor[:, :, :-1]))\n            return cls_loss + self.flow_loss_weight * temp_loss\n        return cls_loss\n\ndef compute_optical_flow(prev_frame, next_frame):\n    prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)\n    next_gray = cv2.cvtColor(next_frame, cv2.COLOR_BGR2GRAY)\n    flow = cv2.calcOpticalFlowFarneback(\n        prev_gray, next_gray, None,\n        0.5, 3, 15, 3, 5, 1.2, 0\n    )\n    magnitude, _ = cv2.cartToPolar(flow[..., 0], flow[..., 1])\n    return magnitude\n\ndef process_video(video_path, resize=(128, 128), max_frames=32):\n    cap = cv2.VideoCapture(video_path)\n    if not cap.isOpened():\n        print(f\"Error: Failed to open video file {video_path}\")\n        return np.zeros((1, *resize, 3)), np.zeros((1, *resize))\n\n    frames = []\n    flow_magnitudes = []\n\n    ret, prev_frame = cap.read()\n    if not ret:\n        cap.release()\n        print(f\"Warning: No frames were read from {video_path}\")\n        return np.zeros((1, *resize, 3)), np.zeros((1, *resize))\n\n    if resize:\n        prev_frame = cv2.resize(prev_frame, resize)\n\n    frames.append(prev_frame)\n    frame_count = 1\n\n    while True:\n        ret, frame = cap.read()\n        if not ret or (max_frames and frame_count >= max_frames):\n            break\n\n        if resize:\n            frame = cv2.resize(frame, resize)\n\n        magnitude = compute_optical_flow(prev_frame, frame)\n        flow_magnitudes.append(magnitude)\n        frames.append(frame)\n\n        prev_frame = frame\n        frame_count += 1\n\n    cap.release()\n    \n    # Ensure we have at least one frame\n    if len(frames) == 0:\n        return np.zeros((1, *resize, 3)), np.zeros((1, *resize))\n    \n    # Ensure we have at least max_frames (pad if necessary)\n    if max_frames and len(frames) < max_frames:\n        pad_frames = max_frames - len(frames)\n        # Duplicate the last frame\n        for _ in range(pad_frames):\n            frames.append(frames[-1].copy())\n        # Duplicate the last flow magnitude\n        if len(flow_magnitudes) > 0:\n            for _ in range(pad_frames-1):  # One less for flow\n                flow_magnitudes.append(flow_magnitudes[-1].copy())\n        else:\n            # If no flow magnitudes, create zero arrays\n            flow_magnitudes = [np.zeros(resize) for _ in range(max_frames-1)]\n    \n    # Trim to max_frames if needed\n    if max_frames:\n        frames = frames[:max_frames]\n        flow_magnitudes = flow_magnitudes[:max_frames-1]  # One less for flow\n    \n    return np.array(frames), np.array(flow_magnitudes)\n\nclass DeepFakeDataset(Dataset):\n    def __init__(self, video_dir, video_files, labels, max_frames=32):\n        self.video_dir = video_dir\n        self.video_files = video_files\n        self.labels = labels\n        self.max_frames = max_frames\n    \n    def __len__(self):\n        return len(self.video_files)\n    \n    def __getitem__(self, idx):\n        try:\n            video_filename = self.video_files[idx]\n            video_path = os.path.join(self.video_dir, video_filename)\n            label = self.labels[idx]\n        \n            # Load video and flow data\n            frames, flow_magnitudes = process_video(video_path, max_frames=self.max_frames)\n            \n            # Handle the case when frames or flow_magnitudes are empty\n            if len(frames) == 0:\n                # Return dummy tensors with proper dimensions\n                rgb_tensor = torch.zeros((3, self.max_frames, 128, 128))\n                flow_tensor = torch.zeros((1, self.max_frames-1, 128, 128))\n                return rgb_tensor, flow_tensor, torch.tensor(label, dtype=torch.float32)\n            \n            # Convert to tensors\n            # RGB channels: (T, H, W, C) → (C, T, H, W)\n            rgb_tensor = torch.tensor(frames).permute(3, 0, 1, 2).float() / 255.0\n            \n            # Flow magnitude: (T-1, H, W) → (1, T-1, H, W)\n            flow_tensor = torch.tensor(flow_magnitudes).unsqueeze(0).float()\n            \n            # Ensure flow_tensor matches expected size\n            if flow_tensor.shape[1] < self.max_frames - 1:\n                # Pad with zeros if needed\n                padding = self.max_frames - 1 - flow_tensor.shape[1]\n                if padding > 0:\n                    padding_tensor = torch.zeros((1, padding, 128, 128))\n                    flow_tensor = torch.cat([flow_tensor, padding_tensor], dim=1)\n            \n            # Ensure RGB tensor has correct temporal dimension\n            if rgb_tensor.shape[1] < self.max_frames:\n                padding = self.max_frames - rgb_tensor.shape[1]\n                if padding > 0:\n                    # Duplicate the last frame\n                    last_frame = rgb_tensor[:, -1:, :, :]\n                    padding_tensor = last_frame.repeat(1, padding, 1, 1)\n                    rgb_tensor = torch.cat([rgb_tensor, padding_tensor], dim=1)\n            \n            # Trim tensors if they exceed max_frames\n            rgb_tensor = rgb_tensor[:, :self.max_frames]\n            flow_tensor = flow_tensor[:, :self.max_frames-1]\n            \n            return rgb_tensor, flow_tensor, torch.tensor(label, dtype=torch.float32)\n            \n        except Exception as e:\n            print(f\"Error processing video {self.video_files[idx]}: {e}\")\n            # Return dummy tensors\n            rgb_tensor = torch.zeros((3, self.max_frames, 128, 128))\n            flow_tensor = torch.zeros((1, self.max_frames-1, 128, 128))\n            return rgb_tensor, flow_tensor, torch.tensor(label, dtype=torch.float32)\n\ndef train_epoch(model, dataloader, optimizer, device=\"cpu\"):\n    model.train()\n    running_loss = 0.0\n    correct = 0\n    total = 0\n    \n    for i, (frames, flow, labels) in enumerate(tqdm(dataloader)):\n        try:\n            # Move data to device\n            frames = frames.to(device)\n            flow = flow.to(device)\n            labels = labels.to(device)\n            \n            # Get the actual number of frames (could be less than max_frames)\n            # Use the first frame's RGB + flow magnitude for each time step\n            # Frames: (B, 3, T, H, W), Flow: (B, 1, T-1, H, W)\n            \n            # Prepare combined input\n            rgb_part = frames[:, :, :-1]  # Use all but the last frame\n            combined_input = torch.cat([rgb_part, flow], dim=1)  # (B, 4, T-1, H, W)\n            \n            # Debug dimensions\n            # print(f\"Combined input shape: {combined_input.shape}\")\n            \n            # Forward pass\n            optimizer.zero_grad()\n            outputs = model(combined_input)\n            \n            # Compute loss\n            loss = model.compute_loss(outputs, labels, flow)\n            \n            # Backward and optimize\n            loss.backward()\n            optimizer.step()\n            \n            running_loss += loss.item()\n            \n            # Calculate accuracy\n            predicted = torch.sigmoid(outputs.squeeze()) > 0.5\n            total += labels.size(0)\n            correct += (predicted == labels).sum().item()\n            \n        except Exception as e:\n            print(f\"Error in training step: {e}\")\n            continue\n    \n    if total == 0:  # Avoid division by zero\n        return running_loss, 0\n        \n    accuracy = 100 * correct / total\n    return running_loss / len(dataloader), accuracy\n\n# Main code to run\ndef main():\n    # Path to the dataset\n    video_dir = \"/kaggle/input/deepfake-detection-challenge/train_sample_videos/\"\n    metadata_path = \"/kaggle/input/deepfake-detection-challenge/train_sample_videos/metadata.json\"\n\n    # Load metadata\n    with open(metadata_path, 'r') as f:\n        metadata = json.load(f)\n\n    # Create a list of video files with their labels\n    video_files = []\n    for key in metadata.keys():\n        video_files.append(key)\n        \n    label_map = {'REAL': 0, 'FAKE': 1}\n    labels = [label_map[metadata[f]['label']] for f in video_files]\n    \n    # Print some information about the dataset\n    print(f\"Total videos: {len(video_files)}\")\n    print(f\"First 5 videos: {video_files[:5]}\")\n    print(f\"First 5 labels: {labels[:5]}\")\n    \n    # Use a smaller subset for testing if needed\n    max_videos = 400  # Limit number of videos for testing\n    video_files = video_files[:max_videos]\n    labels = labels[:max_videos]\n    \n    # Define max frames for consistent dimensionality\n    max_frames = 32\n    \n    # Create dataset and dataloader\n    dataset = DeepFakeDataset(video_dir, video_files, labels, max_frames=max_frames)\n    dataloader = DataLoader(dataset, batch_size=4, shuffle=True, num_workers=0)\n    \n    # Set device\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Using device: {device}\")\n    \n    # Initialize model and optimizer\n    model = DeepFakeDetector3D(input_channels=4, max_frames=max_frames).to(device)\n    optimizer = optim.Adam(model.parameters(), lr=0.001)\n    \n    # Train for a few epochs\n    num_epochs = 200\n    for epoch in range(num_epochs):\n        loss, accuracy = train_epoch(model, dataloader, optimizer, device)\n        print(f\"Epoch {epoch+1}/{num_epochs}, Loss: {loss:.4f}, Accuracy: {accuracy:.2f}%\")\n    \n    # Save the model\n    torch.save(model.state_dict(), \"deepfake_detector.pth\")\n    print(\"Training complete. Model saved.\")\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T17:56:54.344390Z","iopub.execute_input":"2025-04-28T17:56:54.344661Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}