{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":92399,"databundleVersionId":11038207,"sourceType":"competition"}],"dockerImageVersionId":31012,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## **💡Reference Notebook - Fernandosr85 - Dashcam Collision Prediction Project 🚗**\n#### **https://www.kaggle.com/code/fernandosr85/dashcam-collision-prediction-project**","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\n\nmax_files = 10 \n\ncount = 0\n\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n        count += 1\n        if count >= max_files:\n            break\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":"2025-05-03T01:02:44.095810Z","iopub.execute_input":"2025-05-03T01:02:44.096143Z","iopub.status.idle":"2025-05-03T01:02:51.145332Z","shell.execute_reply.started":"2025-05-03T01:02:44.096108Z","shell.execute_reply":"2025-05-03T01:02:51.144355Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport gc\nimport time\nimport warnings\nfrom multiprocessing import Pool\n\nimport cv2\nimport numpy as np\nimport pandas as pd\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.model_selection import KFold\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision\nimport torchvision.models\n\nwarnings.filterwarnings(\"ignore\")\n\n# Check GPU availability and set device\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\nprint(f\"Using device: {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T01:02:51.147044Z","iopub.execute_input":"2025-05-03T01:02:51.147483Z","iopub.status.idle":"2025-05-03T01:03:01.436466Z","shell.execute_reply.started":"2025-05-03T01:02:51.147460Z","shell.execute_reply":"2025-05-03T01:03:01.435653Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Suppress unnecessary formatting warnings\nimport warnings\nwarnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n\n# Paths to the CSV files\ntrain_csv_path = '/kaggle/input/nexar-collision-prediction/train.csv'\ntest_csv_path = '/kaggle/input/nexar-collision-prediction/test.csv'\nsubmission_csv_path = '/kaggle/input/nexar-collision-prediction/sample_submission.csv'\n\n# Paths to the video directories\ntrain_video_dir = '/kaggle/input/nexar-collision-prediction/train'\ntest_video_dir = '/kaggle/input/nexar-collision-prediction/test'\n\n# Load the CSV files\ntrain_df = pd.read_csv(train_csv_path)\ntest_df = pd.read_csv(test_csv_path)\nsubmission_df = pd.read_csv(submission_csv_path)\n\n# (추가) id 컬럼을 문자열(str)로 변환해서 .0 문제 없애기\ntrain_df['id'] = train_df['id'].astype(str)\n\n# Display the first few rows of the DataFrames\nprint(\"Train.csv:\")\nprint(train_df.head())\n\nprint(\"\\nTest.csv:\")\nprint(test_df.head())\n\nprint(\"\\nSample Submission:\")\nprint(submission_df.head())\n\n# Optional: handle NaN values if needed, filling with zero or another value\ntrain_df['time_of_event'] = train_df['time_of_event'].fillna(0)\ntrain_df['time_of_alert'] = train_df['time_of_alert'].fillna(0)\n\n# (추가) Check the video directory paths\nprint(\"\\nVideo Directory Paths:\")\nprint(f\"Train videos are located at: {train_video_dir}\")\nprint(f\"Test videos are located at: {test_video_dir}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T01:03:01.437463Z","iopub.execute_input":"2025-05-03T01:03:01.438470Z","iopub.status.idle":"2025-05-03T01:03:01.487223Z","shell.execute_reply.started":"2025-05-03T01:03:01.438443Z","shell.execute_reply":"2025-05-03T01:03:01.486282Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **Data Preprocessing and Feature Extraction** ","metadata":{}},{"cell_type":"code","source":"# 일반적으로 충돌이 발생하는 마지막 부분에 초점을 맞춰 비디오에서 주요 프레임을 추출\n# 지수 분포를 사용하여 마지막에 가까운 프레임에 더 많은 가중치를 부여\n\ndef extract_keyframes(video_path, num_frames=12, target_size=(160, 160)):\n    \"\"\"\n    Extracts key frames from the video, focusing on the final part where collisions typically occur.\n    Uses exponential distribution to give more weight to frames closer to the end.\n    \"\"\"\n    cap = cv2.VideoCapture(video_path) # 동영상을 불러오기 위해 OpenCV의 videoCapture 객체 생성 \n\n    # 파일이 제대로 열리지 않았을 경우 대비한 예외 처리\n    if not cap.isOpened():\n        print(f\"Could not open the video: {video_path}\")\n        return np.zeros((num_frames, target_size[0], target_size[1], 3), dtype=np.uint8)\n\n    # 총 프레임 수와 초당 프레임 수(FPS)를 가져오기 \n    frames = []\n    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n    fps = cap.get(cv2.CAP_PROP_FPS)\n    \n    if total_frames <= 0:\n        print(f\"Video without frames: {video_path}\")\n        cap.release()\n        return np.zeros((num_frames, target_size[0], target_size[1], 3), dtype=np.uint8)\n    \n    # 영상 길이(초 단위) 계산\n    duration = total_frames / fps if fps > 0 else 0\n    \n    # 짧은 영상 (10초 미만): 균등한 간격으로 프레임 추출\n    if duration < 10:\n        frame_indices = np.linspace(0, total_frames - 1, num_frames, dtype=int)\n\n    # 긴 영상 (10초 이상): 후반부에 더 집중해서 추출\n    else:\n        # 마지막 3초 동안 프레임의 80% 집중(중요 영역)\n        end_frames = int(num_frames * 0.8)\n        start_frames = num_frames - end_frames\n        \n        # 지난 3초 동안의 시작 인덱스를 계산\n        last_seconds = 3\n        last_frame_count = min(int(fps * last_seconds), total_frames - 1)\n        start_idx = max(0, total_frames - last_frame_count)\n        \n        # 마지막 프레임에 더 많은 가중치를 부여하는 지수 분포\n        # 이렇게 하면 마지막에 더 밀집된 인덱스가 생성된다 (\"프레임을 뽑는 간격\"자체를 조절 → 끝부분에 더 많이 몰리게 만드는 방식)\n        end_indices = np.array([\n            start_idx + int((total_frames - start_idx - 1) * (i/end_frames)**2) \n            for i in range(1, end_frames + 1)\n        ])\n        \n        # context에 맞게 균일하게 배포된 초기 프레임 (초반부에서 균등하게 추출한 프레임들)\n        # context란? 사고 직전에 어떤 상황이 펼쳐졌는지에 대한 흐름, 배경, 맥락 \n        begin_indices = np.linspace(0, start_idx - 1, start_frames, dtype=int) if start_idx > 0 else np.zeros(start_frames, dtype=int)\n        \n        # 인덱스 결합\n        frame_indices = np.concatenate([begin_indices, end_indices])\n    \n    # 선택한 프레임 추출 \n    for idx in frame_indices:\n        cap.set(cv2.CAP_PROP_POS_FRAMES, idx)\n        ret, frame = cap.read()\n        if ret:\n            # Use higher resolution and better interpolation\n            frame = cv2.resize(frame, target_size, interpolation=cv2.INTER_LANCZOS4)\n            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n            frames.append(frame)\n        else:\n            frames.append(np.zeros((target_size[0], target_size[1], 3), dtype=np.uint8))\n    \n    cap.release()\n    return np.array(frames, dtype=np.uint8)\n\n# 먼저, 전역 범위에서 변환 클래스를 정의 \n# 입력된 영상 프레임을 일정 확률로 좌우 반전시켜서, 데이터 다양성을 늘리는 역할\nclass RandomHorizontalFlip(object):\n    def __init__(self, p=0.5):\n        self.p = p\n        \n    def __call__(self, frames):\n        if np.random.random() < self.p:\n            return frames[:, :, ::-1, :].copy()  # horizontally flip each frame\n        return frames\n\n# 영상 프레임의 밝기와 대비를 무작위로 조정해, 다양한 조명 환경을 시뮬레이션하는 증강 클래스\nclass ColorJitter(object):\n    def __init__(self, brightness=0, contrast=0):\n        self.brightness = brightness\n        self.contrast = contrast\n        \n    def __call__(self, frames):\n        # Apply brightness jitter\n        if self.brightness > 0:\n            brightness_factor = np.random.uniform(max(0, 1-self.brightness), 1+self.brightness)\n            frames = frames * brightness_factor\n            frames = np.clip(frames, 0, 255)\n        \n        # Apply contrast jitter\n        if self.contrast > 0:\n            contrast_factor = np.random.uniform(max(0, 1-self.contrast), 1+self.contrast)\n            frames = (frames - 128) * contrast_factor + 128\n            frames = np.clip(frames, 0, 255)\n            \n        return frames\n\n# 프레임에 흐릿한 안개 효과를 넣어, 시야가 나쁜 날씨 상황을 시뮬레이션하는 클래스\nclass AddFog(object):\n    def __call__(self, frames):\n        fog = np.random.uniform(0.7, 0.9, frames.shape).astype(np.float32)\n        return frames * 0.8 + fog * 50  # Adjusted for 0-255 scale\n\n# 프레임에 흰색 선형 노이즈(빗방울)를 추가해 비 오는 날씨를 시뮬레이션하는 클래스\nclass AddRain(object):\n    def __call__(self, frames):\n        h, w = frames.shape[1:3]\n        rain = np.random.uniform(0, 1, (len(frames), h, w, 1)).astype(np.float32)\n        rain = (rain > 0.97).astype(np.float32) * 200  # White rain drops\n        return np.clip(frames * 0.9 + rain, 0, 255)  # Darken a bit and add drops\n\n# 지정된 확률에 따라 어떤 변환을 적용할지 말지를 무작위로 결정하는 컨트롤러 클래스(랜덤성 부여)\nclass RandomApply(object):\n    def __init__(self, transform, p=0.5):\n        self.transform = transform\n        self.p = p\n        \n    def __call__(self, frames):\n        if np.random.random() < self.p:\n            return self.transform(frames)\n        return frames\n\n# 여러 개의 변환(Flip, Jitter, Fog 등)을 순서대로 적용하는 데이터 증강 파이프라인 클래스\nclass Compose(object):\n    def __init__(self, transforms):\n        self.transforms = transforms\n        \n    def __call__(self, frames):\n        for t in self.transforms:\n            frames = t(frames)\n        return frames\n\n# 영상 프레임 배열을 PyTorch 텐서로 바꾸고, 픽셀 값을 0~1 범위로 정규화하는 클래스\nclass ToTensor(object):\n    def __call__(self, frames):\n        # Convert from (T, H, W, C) to (T, C, H, W)\n        frames = frames.transpose(0, 3, 1, 2)\n        # Convert to tensor and normalize to [0, 1]\n        return torch.from_numpy(frames).float() / 255.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T01:03:01.488175Z","iopub.execute_input":"2025-05-03T01:03:01.488418Z","iopub.status.idle":"2025-05-03T01:03:01.508575Z","shell.execute_reply.started":"2025-05-03T01:03:01.488398Z","shell.execute_reply":"2025-05-03T01:03:01.507549Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 동영상에서 데이터 증강을 위한 변환을 반환\n\ndef get_video_transforms():\n    \"\"\"\n    Returns transformations for data augmentation in videos.\n    \"\"\"\n    return {\n        'train': Compose([\n            RandomHorizontalFlip(p=0.5),\n            ColorJitter(brightness=0.3, contrast=0.3),\n            RandomApply(AddFog(), p=0.15),\n            RandomApply(AddRain(), p=0.15),\n            RandomApply(RandomNoise(0.05), p=0.2), \n            RandomApply(RandomOcclusion(), p=0.1),\n            ToTensor()\n        ]),\n        'val': Compose([\n            ToTensor()  # Only tensor conversion for validation\n        ])\n    }\n\n# 비디오 프레임에서 무작위 가우시안(정규분포) 노이즈를 추가하여, 실제 촬영 환경에서 \n# 발생할 수 있는 잡음에 대해 모델이 더 강건해지도록 만드는 클래스\nclass RandomNoise(object):\n    \"\"\"\n    Applies random Gaussian noise to video frames for data augmentation.\n    \n    This transformation helps the model become more robust to noise\n    that may be present in real-world video data.\n    \n    Args:\n        std (float): Standard deviation of the Gaussian noise as a fraction\n                     of the pixel value range (default: 0.05)\n    \"\"\"\n    def __init__(self, std=0.05):\n        self.std = std\n        \n    def __call__(self, frames):\n        \"\"\"\n        Apply random noise to the input frames.\n        \n        Args:\n            frames (numpy.ndarray): Input video frames of shape (T, H, W, C)\n                                   where T is number of frames\n        \n        Returns:\n            numpy.ndarray: Noise-augmented frames, clipped to valid pixel range [0, 255]\n        \"\"\"\n        # 지정된 표준 편차를 가진 가우시안 노이즈 생성\n        noise = np.random.normal(0, self.std * 255, frames.shape).astype(np.float32)\n        \n        # 유효한 픽셀 범위에 노이즈 및 클립 추가하기\n        # 영상은 정수형 데이터여야 하므로 형 변환 (astype)\n        return np.clip(frames + noise, 0, 255).astype(np.uint8)\n\n# 영상 프레임에 검은색 사각형을 무작위로 덮어 씌워, 일부 정보가 가려졌을 때도 모델이 견딜 수 있도록 훈련시키는 클래스\nclass RandomOcclusion(object):\n    \"\"\"\n    Simulates occlusion in video frames by adding black rectangles.\n    \n    This transformation helps the model learn to handle partial occlusions\n    that may occur in real-world scenarios when objects block the camera view.\n    \"\"\"\n    def __call__(self, frames):\n        \"\"\"\n        Apply random occlusion to the input frames.\n        \n        Args:\n            frames (numpy.ndarray): Input video frames of shape (T, H, W, C)\n                                   where T is number of frames\n        \n        Returns:\n            numpy.ndarray: Frames with random occlusion applied\n        \"\"\"\n        # 프레임 하나의 세로(h), 가로(w) 길이 가져오기\n        h, w = frames.shape[1:3]\n        \n        # 전체 프레임 크기의 10%~25% 사이 크기의 가림 영역 크기 설정\n        occl_h = np.random.randint(int(h * 0.1), int(h * 0.25))\n        occl_w = np.random.randint(int(w * 0.1), int(w * 0.25))\n        \n        # 이 가림 영역이 들어갈 무작위 위치 좌표 설정 \n        occl_x = np.random.randint(0, w - occl_w)\n        occl_y = np.random.randint(0, h - occl_h)\n        \n        # 원본 프레임을 수정하지 않도록 복사본 만들기\n        frames_copy = frames.copy()\n        \n        # 픽셀을 0(검정색)으로 설정하여 모든 프레임에 occlusion 적용\n        for i in range(len(frames)):\n            frames_copy[i, occl_y:occl_y+occl_h, occl_x:occl_x+occl_w, :] = 0\n            \n        return frames_copy","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T01:03:01.510780Z","iopub.execute_input":"2025-05-03T01:03:01.511079Z","iopub.status.idle":"2025-05-03T01:03:01.537718Z","shell.execute_reply.started":"2025-05-03T01:03:01.511055Z","shell.execute_reply":"2025-05-03T01:03:01.536422Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 비디오 프레임 간 움직임(모션)을 추적하는 'optical_flow'를 계산해, 객체나 배경의 이동 방향과 속도를 벡터 형태로 반환하는 함수\n# 두 연속된 이미지(또는 프레임) 사이에서, 각 픽셀이 어떻게 이동했는지를 벡터로 표현하는 기술 -> optical_flow\n# Farneback 방식만 사용\n# \"모든 픽셀의 방향 + 속도 정보를 다 남김\"\ndef compute_optical_flow_sequence(frames, skip_frames=1):\n    \"\"\"\n    Calculates per-frame optical flow magnitudes as a sequence.\n    \n    Args:\n        frames (numpy.ndarray): (T, H, W, C)\n        \n    Returns:\n        numpy.ndarray: (T, 1) array of flow magnitudes (first frame is 0)\n    \"\"\"\n    T = len(frames)\n    if T < 2:\n        return np.zeros((T, 1), dtype=np.float32)\n    \n    magnitudes = [0.0]  # 첫 프레임은 optical flow가 없으니 0으로 채움\n\n    prev_gray = cv2.cvtColor(frames[0], cv2.COLOR_RGB2GRAY)\n    \n    for i in range(1, T, skip_frames):\n        curr_gray = cv2.cvtColor(frames[i], cv2.COLOR_RGB2GRAY)\n        try:\n            flow = cv2.calcOpticalFlowFarneback(\n                prev_gray, curr_gray,\n                None, 0.5, 3, 15, 3, 5, 1.2, 0\n            )\n            flow_magnitude = np.linalg.norm(flow, axis=-1).mean()  # (H, W) → scalar mean\n            magnitudes.append(flow_magnitude)\n        except Exception as e:\n            print(f\"Error calculating optical flow: {str(e)}\")\n            magnitudes.append(0.0)\n        \n        prev_gray = curr_gray\n\n    # 길이가 부족하면 padding\n    while len(magnitudes) < T:\n        magnitudes.append(0.0)\n    \n    return np.array(magnitudes, dtype=np.float32).reshape(T, 1)  # (T, 1)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T01:03:01.538644Z","iopub.execute_input":"2025-05-03T01:03:01.538907Z","iopub.status.idle":"2025-05-03T01:03:01.563065Z","shell.execute_reply.started":"2025-05-03T01:03:01.538887Z","shell.execute_reply":"2025-05-03T01:03:01.562009Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings('ignore')\n\n# Deep Learning\nimport tensorflow as tf\nfrom tensorflow.keras.models import Model, Sequential\nfrom tensorflow.keras.layers import LSTM, Dense, Dropout, Bidirectional\nfrom tensorflow.keras.applications import InceptionV3\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import EarlyStopping, LearningRateScheduler\nfrom tensorflow.keras.applications.inception_v3 import preprocess_input\nfrom tensorflow.keras.applications import EfficientNetB0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T01:03:01.564764Z","iopub.execute_input":"2025-05-03T01:03:01.565374Z","iopub.status.idle":"2025-05-03T01:03:18.647783Z","shell.execute_reply.started":"2025-05-03T01:03:01.565349Z","shell.execute_reply":"2025-05-03T01:03:18.646720Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Positive이면 alert_event 사이만 뽑고, Negative이면 마지막 3초 구간을 기준으로 추출**\n**그리고 num_frames만큼 균등하게 뽑고 CNN + Optical Flow 둘 다 계산** ","metadata":{}},{"cell_type":"code","source":"# InceptionV3 모델로 특성 추출\nbase_model = InceptionV3(weights='imagenet', include_top=False, pooling='avg')\ncnn_feature_dim = base_model.output_shape[-1]\n\ndef get_hybrid_feature_sequence(video_path, num_frames=12):\n    \"\"\"\n    Extract per-frame hybrid features (CNN + Optical flow) as a sequence.\n    \n    Args:\n        video_path (str): Path to video file.\n        num_frames (int): Number of frames to extract.\n    \n    Returns:\n        np.ndarray: (T, 1281) array of per-frame features.\n    \"\"\"\n    # 1. 프레임 추출\n    frames = extract_keyframes(video_path, num_frames=num_frames, target_size=(160,160))\n    \n    if len(frames) == 0:\n        print(f\"Skipping {video_path}: no frames\")\n        return np.zeros((num_frames, 1281), dtype=np.float32)\n\n    # 2. CNN feature per frame (Inception expects (N, H, W, C))\n    spatial_features = base_model.predict(\n        preprocess_input(frames.astype('float32')),\n        batch_size=32,\n        verbose=0\n    )  # shape: (T, 1280)\n\n    # 3. Optical flow sequence\n    flow_magnitudes = compute_optical_flow_sequence(frames)  # shape: (T, 1)\n\n    # 4. Concatenate per frame\n    hybrid_features = np.concatenate([spatial_features, flow_magnitudes], axis=1)  # (T, 1281)\n\n    return hybrid_features\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T01:03:18.648903Z","iopub.execute_input":"2025-05-03T01:03:18.649648Z","iopub.status.idle":"2025-05-03T01:03:21.342238Z","shell.execute_reply.started":"2025-05-03T01:03:18.649621Z","shell.execute_reply":"2025-05-03T01:03:21.340944Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_hybrid_feature_sequence_from_frames(frames):\n    \"\"\"\n    Extract per-frame hybrid features (CNN + Optical flow) from pre-loaded frames.\n    \n    Args:\n        frames (torch.Tensor): (T, 3, 160, 160) tensor (after transform).\n    \n    Returns:\n        np.ndarray: (T, 1281) array of per-frame features.\n    \"\"\"\n    if len(frames) == 0:\n        print(\"Warning: empty frames input\")\n        return np.zeros((1, 1281), dtype=np.float32)\n\n    # 1️⃣ PyTorch tensor → numpy (T, 160, 160, 3), [0, 255] scale\n    frames_np = frames.permute(0, 2, 3, 1).numpy() * 255.0  # [0,1] → [0,255]\n    frames_np = frames_np.astype(np.uint8)\n\n    # 2️⃣ CNN Features per frame\n    spatial_features = base_model.predict(\n        preprocess_input(frames_np.astype('float32')),\n        batch_size=32,\n        verbose=0\n    )  # shape: (T, 1280)\n\n    # 3️⃣ Optical Flow per frame\n    flow_magnitudes = compute_optical_flow_sequence(frames_np)  # shape: (T, 1)\n\n    # 4️⃣ Concatenate → (T, 1281)\n    hybrid_features = np.concatenate([spatial_features, flow_magnitudes], axis=1)\n\n    return hybrid_features\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T01:03:21.343343Z","iopub.execute_input":"2025-05-03T01:03:21.343698Z","iopub.status.idle":"2025-05-03T01:03:21.350667Z","shell.execute_reply.started":"2025-05-03T01:03:21.343667Z","shell.execute_reply":"2025-05-03T01:03:21.349685Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compute_optical_flow_sequence(frames, skip_frames=1):\n    \"\"\"\n    Computes per-frame optical flow magnitudes.\n    \n    Args:\n        frames (np.ndarray): (T, H, W, 3) numpy array of frames.\n    \n    Returns:\n        np.ndarray: (T, 1) array of per-frame optical flow magnitudes.\n    \"\"\"\n    T = len(frames)\n    if T < 2:\n        return np.zeros((T, 1), dtype=np.float32)\n\n    magnitudes = []\n\n    prev_gray = cv2.cvtColor(frames[0], cv2.COLOR_RGB2GRAY)\n\n    for i in range(1, T, skip_frames):\n        curr_gray = cv2.cvtColor(frames[i], cv2.COLOR_RGB2GRAY)\n        try:\n            flow = cv2.calcOpticalFlowFarneback(prev_gray, curr_gray,\n                                                None, 0.5, 3, 15, 3, 5, 1.2, 0)\n            # magnitude = sqrt(u^2 + v^2)\n            mag = np.linalg.norm(flow, axis=-1)  # shape (H, W)\n            avg_mag = np.mean(mag)  # scalar\n            magnitudes.append(avg_mag)\n        except Exception as e:\n            print(f\"Error calculating flow at frame {i}: {str(e)}\")\n            magnitudes.append(0.0)\n\n        prev_gray = curr_gray\n\n    # 마지막 길이 맞춤 (T, 1)\n    if len(magnitudes) < T:\n        magnitudes.append(0.0)  # 마지막 프레임은 flow가 없음\n\n    magnitudes = np.array(magnitudes, dtype=np.float32).reshape(-1, 1)  # (T, 1)\n\n    return magnitudes\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T01:03:21.352079Z","iopub.execute_input":"2025-05-03T01:03:21.352382Z","iopub.status.idle":"2025-05-03T01:03:21.382860Z","shell.execute_reply.started":"2025-05-03T01:03:21.352361Z","shell.execute_reply":"2025-05-03T01:03:21.381861Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 아직도 최종적으로 Transformer에 넣은 (T, 1281) 시퀀스는 만들어지지 않음\n# 1. 프레임별 CNN Feature 추출 (InceptionV3) 추출\n# 2. optical flow sequence (compute_optical_flow_sequence) 추출\n# 3. 두 결과물 concat\n# 4. 이걸 Transformer의 input 시퀀스로 사용\n\n# 전체 처리 함수 (이제 둘을 결합하는 함수 생성)\n# CNN + optical flow 붙여서 (T, 1281) 만들어주는 함수 \n\ndef prepare_transformer_input(video_path, num_frames=12, target_size=(160, 160)):\n    \"\"\"\n    Prepares (T, 1281) input sequence combining CNN features + optical flow\n    for a given video.\n    \"\"\"\n    # === Step 1: Extract frames ===\n    frames = extract_keyframes(video_path, num_frames=num_frames, target_size=target_size)\n    if frames.shape[0] == 0:\n        print(f\"Skipping video {video_path} (no frames)\")\n        return None  # or np.zeros((num_frames, 1281)) as fallback\n\n    # === Step 2: Extract CNN (spatial) features per frame ===\n    frames_float = preprocess_input(frames.astype('float32'))  # preprocess for InceptionV3\n    spatial_features = base_model.predict(frames_float, batch_size=32, verbose=0)  # (T, 1280)\n\n    # === Step 3: Compute optical flow sequence ===\n    optical_flow_sequence = compute_optical_flow_sequence(frames)  # (T, 1)\n\n    # === Step 4: Combine both ===\n    combined_features = np.concatenate([spatial_features, optical_flow_sequence], axis=1)  # (T, 1281)\n\n    return combined_features","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T01:03:21.383975Z","iopub.execute_input":"2025-05-03T01:03:21.384279Z","iopub.status.idle":"2025-05-03T01:03:21.408957Z","shell.execute_reply.started":"2025-05-03T01:03:21.384258Z","shell.execute_reply":"2025-05-03T01:03:21.407905Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 중간 저장 포함 코드\nimport numpy as np\nimport os\nfrom tqdm import tqdm\n\n# 1️⃣ transform 준비\ntransforms = get_video_transforms()\ntrain_transform = transforms['train']\n\n# 2️⃣ 저장할 폴더 설정\noutput_dir = '/kaggle/working/'\nos.makedirs(output_dir, exist_ok=True)\n\n# 3️⃣ feature 저장용 리스트\nall_sequences = []\n\n# 4️⃣ 중간 저장 주기\nsave_every = 50\n\n# 5️⃣ 반복\nfor idx, row in tqdm(train_df.iterrows(), total=len(train_df)):\n    video_id = row['id']\n    video_path = os.path.join(train_video_dir, f\"{int(video_id):05d}.mp4\")\n\n    sequence = prepare_transformer_input(video_path, num_frames=12)\n\n    if sequence is None:\n        print(f\"Skipping video {video_id} (no valid sequence)\")\n        continue\n\n    all_sequences.append(sequence)\n\n    # 🔥 N개마다 중간 저장\n    if (idx + 1) % save_every == 0:\n        partial_path = os.path.join(output_dir, f'all_sequences_partial_{idx+1}.npy')\n        np.save(partial_path, np.array(all_sequences))\n        print(f\"Saved {idx + 1} sequences → {partial_path}\")\n\n# 6️⃣ 최종 저장\nfinal_path = os.path.join(output_dir, 'all_sequences_final.npy')\nnp.save(final_path, np.array(all_sequences))\nprint(f\"\\nFinal saved → {final_path}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T01:03:21.410127Z","iopub.execute_input":"2025-05-03T01:03:21.410561Z","iopub.status.idle":"2025-05-03T03:05:52.108374Z","shell.execute_reply.started":"2025-05-03T01:03:21.410526Z","shell.execute_reply":"2025-05-03T03:05:52.105906Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\n\n# 경로 설정\noutput_dir = '/kaggle/working/'\nfinal_file = os.path.join(output_dir, 'all_sequences_final.npy')\ncombined_save_path = os.path.join(output_dir, 'all_sequences_combined.npy')\n\n# ✅ 최종 저장된 파일만 불러오기\nfinal_array = np.load(final_file)\nprint(f\"Final array shape: {final_array.shape}\")\n\n# ✅ combined 파일로 따로 저장 (만약 필요할 때 대비)\nnp.save(combined_save_path, final_array)\nprint(f\"Combined array saved to: {combined_save_path}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T03:52:09.200101Z","iopub.execute_input":"2025-05-03T03:52:09.200946Z","iopub.status.idle":"2025-05-03T03:52:10.854743Z","shell.execute_reply.started":"2025-05-03T03:52:09.200919Z","shell.execute_reply":"2025-05-03T03:52:10.853746Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# (1) combined feature 불러오기\nimport numpy as np\nall_sequences = np.load('/kaggle/working/all_sequences_combined.npy')\nprint(all_sequences.shape)  # → (n_videos, 12, 2049) 같은 출력 확인\n\n# (2) train.csv 다시 로드\nimport pandas as pd\ntrain_df = pd.read_csv('/kaggle/input/nexar-collision-prediction/train.csv')\n\n# (3) label 추출\nlabels = train_df['target'].values  # shape (n_videos,)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T03:52:21.872410Z","iopub.execute_input":"2025-05-03T03:52:21.872763Z","iopub.status.idle":"2025-05-03T03:52:22.041222Z","shell.execute_reply.started":"2025-05-03T03:52:21.872736Z","shell.execute_reply":"2025-05-03T03:52:22.040215Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport torch\nfrom torch.utils.data import Dataset, DataLoader, random_split\n\n# 1️⃣ 저장된 feature 불러오기\nall_sequences = np.load('all_sequences_combined.npy')\nprint(f\"Loaded all_sequences shape: {all_sequences.shape}\")  # (1500, 12, 2049)\n\n# 2️⃣ train_df에서 label 불러오기 (주의: feature 개수에 맞게 잘라주기!)\nlabels = train_df['target'].values[:all_sequences.shape[0]]  # shape (1500,)\nprint(f\"Labels shape: {labels.shape}\")\n\n# 3️⃣ Dataset 클래스 정의\nclass VideoSequenceDataset(Dataset):\n    def __init__(self, sequences, labels):\n        \"\"\"\n        Args:\n            sequences (numpy.ndarray): shape (n_samples, T, feature_dim)\n            labels (numpy.ndarray or list): shape (n_samples,)\n        \"\"\"\n        self.sequences = torch.tensor(sequences, dtype=torch.float32)\n        self.labels = torch.tensor(labels, dtype=torch.float32)\n\n    def __len__(self):\n        return len(self.sequences)\n\n    def __getitem__(self, idx):\n        x = self.sequences[idx]  # (T, feature_dim)\n        y = self.labels[idx]     # scalar or class\n        return x, y\n\n# 4️⃣ Dataset 객체 생성\n# Dataset 객체 생성\ndataset = VideoSequenceDataset(all_sequences, labels)\n\n# 정확한 길이 체크\nprint(f\"Dataset length: {len(dataset)}\")  # 꼭 찍어봐!\n\n# Train/Val split\ntrain_size = int(0.8 * len(dataset))  # 80% split → 1200 if 1500 total\nval_size = len(dataset) - train_size\n\nprint(f\"Train size: {train_size}, Val size: {val_size}\")\n\ntrain_dataset, val_dataset = random_split(dataset, [train_size, val_size])\n\n# DataLoader\ntrain_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=16, shuffle=False)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T03:52:25.047974Z","iopub.execute_input":"2025-05-03T03:52:25.048314Z","iopub.status.idle":"2025-05-03T03:52:25.476091Z","shell.execute_reply.started":"2025-05-03T03:52:25.048291Z","shell.execute_reply":"2025-05-03T03:52:25.475224Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # DataLoader 생성\n# from torch.utils.data import DataLoader, random_split\n\n# dataset = VideoSequenceDataset(all_sequences, labels)\n\n# # Train/Val 분할\n# train_size = int(0.8 * len(dataset))\n# val_size = len(dataset) - train_size\n# train_dataset, val_dataset = random_split(dataset, [train_size, val_size])\n\n# train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)\n# val_loader = DataLoader(val_dataset, batch_size=16, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T03:28:12.492098Z","iopub.execute_input":"2025-05-03T03:28:12.492404Z","iopub.status.idle":"2025-05-03T03:28:14.215179Z","shell.execute_reply.started":"2025-05-03T03:28:12.492384Z","shell.execute_reply":"2025-05-03T03:28:14.214270Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Temporal Transformer 모델 설계\nimport torch\nimport torch.nn as nn\n\nclass TemporalTransformerModel(nn.Module):\n    def __init__(self, input_dim=1281, embed_dim=256, num_heads=4, num_layers=2, dropout=0.1):\n        super(TemporalTransformerModel, self).__init__()\n\n        # 1. Input → embedding layer\n        self.input_proj = nn.Linear(input_dim, embed_dim)\n\n        # 2. Transformer Encoder\n        encoder_layer = nn.TransformerEncoderLayer(d_model=embed_dim, nhead=num_heads, dropout=dropout, batch_first=True)\n        self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)\n\n        # 3. Classification head (binary classification)\n        self.classifier = nn.Sequential(\n            nn.Linear(embed_dim, 128),\n            nn.ReLU(),\n            nn.Dropout(dropout),\n            nn.Linear(128, 1),\n            nn.Sigmoid()  # Binary output (0~1)\n        )\n\n    def forward(self, x):\n        \"\"\"\n        x: (batch_size, T, input_dim)\n        \"\"\"\n        # Step 1: Project input features\n        x = self.input_proj(x)  # → (batch_size, T, embed_dim)\n\n        # Step 2: Apply Transformer Encoder\n        x = self.transformer_encoder(x)  # → (batch_size, T, embed_dim)\n\n        # Step 3: Aggregate (mean pooling over time)\n        x = x.mean(dim=1)  # → (batch_size, embed_dim)\n\n        # Step 4: Final classification\n        out = self.classifier(x)  # → (batch_size, 1)\n\n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T03:57:06.568263Z","iopub.execute_input":"2025-05-03T03:57:06.568664Z","iopub.status.idle":"2025-05-03T03:57:06.577080Z","shell.execute_reply.started":"2025-05-03T03:57:06.568643Z","shell.execute_reply":"2025-05-03T03:57:06.576176Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Spatial Transformer 모델 설계\n\nimport torch\nimport torch.nn as nn\n\nclass SpatialTransformer(nn.Module):\n    def __init__(self, input_dim=1280, hidden_dim=512, num_heads=8, num_layers=2, dropout=0.1):\n        super(SpatialTransformer, self).__init__()\n\n        encoder_layer = nn.TransformerEncoderLayer(\n            d_model=input_dim, nhead=num_heads, dim_feedforward=hidden_dim, dropout=dropout, batch_first=True\n        )\n        self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)\n\n        # 마지막 summary를 위한 pooling 또는 projection\n        self.output_layer = nn.Linear(input_dim, input_dim)\n\n    def forward(self, x):\n        \"\"\"\n        Args:\n            x: shape (batch_size, T, input_dim) → per-frame spatial features\n        \n        Returns:\n            out: shape (batch_size, input_dim) → aggregated spatial feature\n        \"\"\"\n        # transformer expects (batch_size, T, input_dim)\n        x_transformed = self.transformer(x)  # (batch_size, T, input_dim)\n\n        # Pooling over time (mean pooling)\n        x_pooled = x_transformed.mean(dim=1)  # (batch_size, input_dim)\n\n        out = self.output_layer(x_pooled)  # (batch_size, input_dim)\n        return out\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T03:57:06.933366Z","iopub.execute_input":"2025-05-03T03:57:06.934068Z","iopub.status.idle":"2025-05-03T03:57:06.941618Z","shell.execute_reply.started":"2025-05-03T03:57:06.934038Z","shell.execute_reply":"2025-05-03T03:57:06.940311Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CollisionPredictionModel(nn.Module):\n    def __init__(self, temporal_input_dim=2049, spatial_input_dim=1280, embed_dim=256, dropout=0.1):\n        super(CollisionPredictionModel, self).__init__()\n\n        self.temporal_transformer = TemporalTransformerModel(\n            input_dim=temporal_input_dim, embed_dim=embed_dim\n        )\n        self.spatial_transformer = SpatialTransformer(\n            input_dim=spatial_input_dim\n        )\n\n        fused_dim = embed_dim + spatial_input_dim  # Temporal + Spatial 출력 연결\n\n        self.classifier = nn.Sequential(\n            nn.Linear(fused_dim, 256),\n            nn.ReLU(),\n            nn.Dropout(dropout),\n            nn.Linear(256, 1),\n            nn.Sigmoid()  # Binary classification\n        )\n\n    def forward(self, temporal_input, spatial_input):\n        \"\"\"\n        temporal_input: (batch, T, 2049)\n        spatial_input: (batch, T, 1280)\n        \"\"\"\n        temporal_out = self.temporal_transformer(temporal_input)  # (batch, embed_dim)\n        spatial_out = self.spatial_transformer(spatial_input)      # (batch, spatial_input_dim)\n\n        # Fusion: concatenate\n        fused = torch.cat([temporal_out, spatial_out], dim=1)  # (batch, fused_dim)\n\n        out = self.classifier(fused)  # (batch, 1)\n\n        return out\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T03:57:07.229164Z","iopub.execute_input":"2025-05-03T03:57:07.229487Z","iopub.status.idle":"2025-05-03T03:57:07.237218Z","shell.execute_reply.started":"2025-05-03T03:57:07.229464Z","shell.execute_reply":"2025-05-03T03:57:07.236086Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\nclass TemporalTransformerModel(nn.Module):\n    def __init__(self, input_dim, embed_dim=256, num_heads=4, num_layers=2, dropout=0.1):\n        super(TemporalTransformerModel, self).__init__()\n\n        self.input_proj = nn.Linear(input_dim, embed_dim)\n        encoder_layer = nn.TransformerEncoderLayer(\n            d_model=embed_dim, nhead=num_heads, dropout=dropout, batch_first=True\n        )\n        self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)\n\n    def forward(self, x):\n        x = self.input_proj(x)            # (batch, T, embed_dim)\n        x = self.transformer_encoder(x)   # (batch, T, embed_dim)\n        x = x.mean(dim=1)                 # (batch, embed_dim)\n        return x\n\n\nclass SpatialTransformer(nn.Module):\n    def __init__(self, input_dim=1280, hidden_dim=512, num_heads=8, num_layers=2, dropout=0.1):\n        super(SpatialTransformer, self).__init__()\n\n        encoder_layer = nn.TransformerEncoderLayer(\n            d_model=input_dim, nhead=num_heads, dim_feedforward=hidden_dim, dropout=dropout, batch_first=True\n        )\n        self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)\n        self.output_layer = nn.Linear(input_dim, input_dim)\n\n    def forward(self, x):\n        x_transformed = self.transformer(x)  # (batch, T, input_dim)\n        x_pooled = x_transformed.mean(dim=1) # (batch, input_dim)\n        out = self.output_layer(x_pooled)    # (batch, input_dim)\n        return out\n\n\nclass CombinedModel(nn.Module):\n    def __init__(self, temporal_input_dim=2049, spatial_input_dim=1280,\n                 temporal_embed_dim=256, combined_dim=256, dropout=0.1):\n        super(CombinedModel, self).__init__()\n\n        self.temporal_transformer = TemporalTransformerModel(\n            input_dim=temporal_input_dim, embed_dim=temporal_embed_dim\n        )\n        self.spatial_transformer = SpatialTransformer(\n            input_dim=spatial_input_dim\n        )\n\n        # temporal (256) + spatial (1280) = 1536\n        self.classifier = nn.Sequential(\n            nn.Linear(1536, combined_dim),\n            nn.ReLU(),\n            nn.Dropout(dropout),\n            nn.Linear(combined_dim, 1),\n            nn.Sigmoid()\n        )\n\n    def forward(self, temporal_input, spatial_input):\n        temporal_out = self.temporal_transformer(temporal_input)  # (batch, 256)\n        spatial_out = self.spatial_transformer(spatial_input)     # (batch, 1280)\n\n        combined = torch.cat([temporal_out, spatial_out], dim=1) # (batch, 1536)\n        out = self.classifier(combined)                          # (batch, 1)\n\n        return out\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T03:57:07.503706Z","iopub.execute_input":"2025-05-03T03:57:07.504143Z","iopub.status.idle":"2025-05-03T03:57:07.516327Z","shell.execute_reply.started":"2025-05-03T03:57:07.504117Z","shell.execute_reply":"2025-05-03T03:57:07.515217Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\n\n# 모델 준비\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel = CombinedModel(\n    temporal_input_dim=2049, spatial_input_dim=1280,\n    temporal_embed_dim=256, combined_dim=256\n).to(device)\n\n# 손실 함수 및 옵티마이저\ncriterion = nn.BCELoss()\noptimizer = optim.Adam(model.parameters(), lr=1e-4)\n\nnum_epochs = 20\n\nfor epoch in range(num_epochs):\n    model.train()\n    running_loss = 0.0\n\n    for inputs, labels in train_loader:\n        inputs = inputs.to(device)  # (batch, T, 2049)\n        labels = labels.to(device).unsqueeze(1)  # (batch, 1)\n\n        # temporal_input = spatial(1280) + flow(1) + 추가 optical flow들 → (2049)\n        temporal_input = inputs[:, :, :2049]\n\n        # spatial_input = spatial part only → (1280)\n        spatial_input = inputs[:, :, :1280]\n\n        # forward\n        outputs = model(temporal_input, spatial_input)\n        loss = criterion(outputs, labels)\n\n        # backward\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n\n    avg_loss = running_loss / len(train_loader)\n    print(f\"Epoch [{epoch + 1}/{num_epochs}], Loss: {avg_loss:.4f}\")\n\n    # === Validation ===\n    model.eval()\n    correct = 0\n    total = 0\n\n    with torch.no_grad():\n        for inputs, labels in val_loader:\n            inputs = inputs.to(device)\n            labels = labels.to(device).unsqueeze(1)\n    \n            temporal_input = inputs[:, :, :2049]\n            spatial_input = inputs[:, :, :1280]\n    \n            outputs = model(temporal_input, spatial_input)\n            predicted = (outputs > 0.5).float()\n    \n            total += labels.size(0)\n            correct += (predicted == labels).sum().item()\n\n\n    val_acc = correct / total\n    print(f\"Validation Accuracy: {val_acc:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T04:14:27.461116Z","iopub.execute_input":"2025-05-03T04:14:27.461517Z","iopub.status.idle":"2025-05-03T04:22:07.684023Z","shell.execute_reply.started":"2025-05-03T04:14:27.461485Z","shell.execute_reply":"2025-05-03T04:22:07.683104Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 모델 저장 \ntorch.save(model.state_dict(), 'best_model.pth')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T04:24:32.924170Z","iopub.execute_input":"2025-05-03T04:24:32.924544Z","iopub.status.idle":"2025-05-03T04:24:33.244194Z","shell.execute_reply.started":"2025-05-03T04:24:32.924518Z","shell.execute_reply":"2025-05-03T04:24:33.243127Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 같은 모델 아키텍처 준비 (모델 불러오기)\nmodel = CombinedModel(\n    temporal_input_dim=2049,  # 주의: 학습할 때와 동일해야 한다\n    spatial_input_dim=1280,\n    temporal_embed_dim=256,\n    combined_dim=256\n)\nmodel.load_state_dict(torch.load('best_model.pth'))\nmodel.eval()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T04:24:45.675008Z","iopub.execute_input":"2025-05-03T04:24:45.675337Z","iopub.status.idle":"2025-05-03T04:24:45.888760Z","shell.execute_reply.started":"2025-05-03T04:24:45.675318Z","shell.execute_reply":"2025-05-03T04:24:45.887920Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_sequences = []\nfor _, row in tqdm(test_df.iterrows(), total=len(test_df), desc=\"Processing Test Videos\"):\n    video_path = f\"{test_video_dir}/{int(float(row['id'])):05d}.mp4\"\n    sequence = prepare_transformer_input(video_path, num_frames=12)\n    if sequence is not None:\n        test_sequences.append(sequence)\n\ntest_sequences = np.array(test_sequences)  # shape: (n_test, 12, 2049)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T04:27:42.391590Z","iopub.execute_input":"2025-05-03T04:27:42.392019Z","iopub.status.idle":"2025-05-03T06:11:16.427081Z","shell.execute_reply.started":"2025-05-03T04:27:42.391992Z","shell.execute_reply":"2025-05-03T06:11:16.424457Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 테스트용 Dataset, DataLoader\n\nimport torch\nfrom torch.utils.data import DataLoader\n\n# 테스트용: dummy labels (예측용이라 실제 label은 필요 없음)\ndummy_labels = np.zeros(len(test_sequences))\n\n# Dataset\ntest_dataset = VideoSequenceDataset(test_sequences, dummy_labels)\n\n# DataLoader\ntest_loader = DataLoader(test_dataset, batch_size=16, shuffle=False)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T06:19:14.015363Z","iopub.execute_input":"2025-05-03T06:19:14.016215Z","iopub.status.idle":"2025-05-03T06:19:14.134950Z","shell.execute_reply.started":"2025-05-03T06:19:14.016185Z","shell.execute_reply":"2025-05-03T06:19:14.134102Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 테스트 데이터 예측 실행\n\nmodel.eval()\nall_predictions = []\n\nwith torch.no_grad():\n    for inputs, _ in test_loader:\n        inputs = inputs.to(device)\n        temporal_input = inputs[:, :, :2049]\n        spatial_input = inputs[:, :, :1280]\n        outputs = model(temporal_input, spatial_input)\n        all_predictions.extend(outputs.cpu().numpy().flatten())\n\nall_predictions = np.array(all_predictions)\nprint(f\"Predictions shape: {all_predictions.shape}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T06:19:28.784003Z","iopub.execute_input":"2025-05-03T06:19:28.784387Z","iopub.status.idle":"2025-05-03T06:19:35.471031Z","shell.execute_reply.started":"2025-05-03T06:19:28.784354Z","shell.execute_reply":"2025-05-03T06:19:35.470133Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Kaggle 제출용 CSV\nsubmission = pd.DataFrame({\n    'id': test_df['id'],\n    'score': all_predictions\n})\n\nsubmission.to_csv('submission.csv', index=False)\nprint(\"Saved submission.csv!\")\n\n# 요약 확인\nprint(submission.describe())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T06:19:46.583166Z","iopub.execute_input":"2025-05-03T06:19:46.583562Z","iopub.status.idle":"2025-05-03T06:19:46.670955Z","shell.execute_reply.started":"2025-05-03T06:19:46.583537Z","shell.execute_reply":"2025-05-03T06:19:46.669579Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}