{"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":102335,"databundleVersionId":12518947},{"sourceType":"datasetVersion","sourceId":16472686,"datasetId":10555351,"databundleVersionId":17472995},{"sourceType":"datasetVersion","sourceId":16491409,"datasetId":10564561,"databundleVersionId":17493013}],"dockerImageVersionId":31329,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"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\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')\n\nimport sys, os\nfrom pathlib import Path\n\n\nMY_DATASET = Path(\"/kaggle/input/datasets/kyuhwahong/cmi-xmodal/kaggle_submission\")\nCOMP_DATA  = Path(\"/kaggle/input/competitions/cmi-detect-behavior-with-sensor-data\")\n\n\nsys.path.insert(0, str(MY_DATASET))\nprint(f\"sys.path[0] = {sys.path[0]}\")\n\nfor m in [\"model\", \"model_mb\", \"imu_utils\"]:\n    sys.modules.pop(m, None)\n\n\nimport numpy as np, json, torch\nimport polars as pl\nimport kaggle_evaluation.core.templates\nimport kaggle_evaluation.cmi_inference_server\nimport kaggle_evaluation.cmi_gateway\n\n\nfrom model import CMIBaseline\nfrom model_mb import CMIMultiBranch\nimport imu_utils\n\nprint(\"Imported\")\nprint(f\"torch: {torch.__version__}, CUDA: {torch.cuda.is_available()}\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-27T15:04:42.175218Z","iopub.execute_input":"2026-05-27T15:04:42.175507Z","iopub.status.idle":"2026-05-27T15:04:45.075643Z","shell.execute_reply.started":"2026-05-27T15:04:42.175463Z","shell.execute_reply":"2026-05-27T15:04:45.074956Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nCKPT_DIR = MY_DATASET\nimport glob\n\nstats = np.load(MY_DATASET / \"stats.npz\")\nimu_mean = stats[\"imu_mean\"];  imu_std = stats[\"imu_std\"]\nthm_mean = stats[\"thm_mean\"];  thm_std = stats[\"thm_std\"]\ntof_scale = float(stats[\"tof_scale\"])\n\nlabel_maps = json.loads((MY_DATASET / \"label_maps.json\").read_text())\nid2gesture = {v: k for k, v in label_maps[\"gesture\"].items()}\n\ntuned = np.load(MY_DATASET / \"ensemble_tuned.npz\")\nW_MB = float(tuned[\"w_mb\"])         # 0.6\nBIAS = torch.from_numpy(tuned[\"bias\"]).to(DEVICE).float()\nprint(f\"w_mb={W_MB},  bias range=[{BIAS.min():.2f}, {BIAS.max():.2f}]\")\n\nW_D = 0.70\nMAX_LEN = 128\nTOTAL_FEAT_V4 = 348\nIMU_DIM_MB_V4 = 17\nIMU_DIM_STUDENT = 17\nN_GESTURE = 18\n\ndef load_model(cls, kwargs, ckpt_path):\n    m = cls(**kwargs).to(DEVICE).eval()\n    sd = torch.load(ckpt_path, map_location=DEVICE, weights_only=False)[\"model_state\"]\n    m.load_state_dict(sd)\n    return m\n\nsb_models   = [load_model(CMIBaseline,    {\"in_feat\": TOTAL_FEAT_V4},      MY_DATASET / f\"v4_fold{f}.pt\")      for f in range(5)]\nmb_models   = [load_model(CMIMultiBranch, {\"imu_in\":  IMU_DIM_MB_V4},      MY_DATASET / f\"mb_v4_fold{f}.pt\")   for f in range(5)]\ndist_models = []\nfor tag in (\"distill\", \"distill_s2\"):\n    for f in range(5):\n        dist_models.append(load_model(CMIBaseline, {\"in_feat\": IMU_DIM_STUDENT}, MY_DATASET / f\"{tag}_fold{f}.pt\"))\nprint(f\"loaded {len(sb_models)} sb + {len(mb_models)} mb + {len(dist_models)} distill\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T15:04:45.078873Z","iopub.execute_input":"2026-05-27T15:04:45.079073Z","iopub.status.idle":"2026-05-27T15:04:47.855575Z","shell.execute_reply.started":"2026-05-27T15:04:45.079051Z","shell.execute_reply":"2026-05-27T15:04:47.854889Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMU_COLS = [\"acc_x\",\"acc_y\",\"acc_z\",\"rot_w\",\"rot_x\",\"rot_y\",\"rot_z\"]\nTHM_COLS = [f\"thm_{i}\" for i in range(1,6)]\nTOF_COLS = [f\"tof_{s}_v{p}\" for s in range(1,6) for p in range(64)]\n\ndef build_features(seq):\n    \"\"\"seq: polars DataFrame, returns (feat_348, mb_imu_17, mb_thm_10, mb_tof, mb_tof_mi, mask, imu_only)\"\"\"\n    df = seq.to_pandas()\n    imu = df[IMU_COLS].to_numpy(dtype=np.float32)\n    thm = df[THM_COLS].to_numpy(dtype=np.float32)\n    tof_raw = df[TOF_COLS].to_numpy(dtype=np.float32)\n    T = imu.shape[0]\n\n    imu_miss = np.isnan(imu[:, 3:7]).any(axis=1, keepdims=True).astype(np.float32)\n    thm_miss = np.isnan(thm).astype(np.float32)\n    tof_miss_pixel = np.logical_or(np.isnan(tof_raw), tof_raw < 0)\n    tof_miss_frac = tof_miss_pixel.mean(axis=1, keepdims=True).astype(np.float32)\n    imu_only = bool(np.isnan(thm).all()) and bool((np.isnan(tof_raw) | (tof_raw < 0)).all())\n\n    for c in range(7):\n        col = imu[:, c]; col[np.isnan(col)] = imu_mean[c]\n    for c in range(5):\n        col = thm[:, c]; col[np.isnan(col)] = thm_mean[c]\n    tof = np.where(tof_miss_pixel, 0.0, np.nan_to_num(tof_raw, nan=0.0))\n\n    acc, quat = imu[:, :3], imu[:, 3:7]\n    body = imu_utils.build_body_frame_features(acc, quat)\n    derived = np.concatenate([\n        body[\"linear_acc\"]/10.0,\n        (body[\"linear_acc_mag\"] - 4.0)/5.0,\n        body[\"jerk\"]/10.0,\n        body[\"pitch_roll\"],\n    ], axis=1).astype(np.float32)\n    imu_norm = ((imu - imu_mean) / imu_std).astype(np.float32)\n    thm_norm = ((thm - thm_mean) / thm_std).astype(np.float32)\n    tof_norm = tof / tof_scale  # (T, 320)\n\n    feat = np.concatenate([imu_norm, derived, imu_miss, thm_norm, thm_miss, tof_norm, tof_miss_frac], axis=1).astype(np.float32)\n    mb_imu = np.concatenate([imu_norm, derived, imu_miss], axis=1).astype(np.float32)\n    mb_thm = np.concatenate([thm_norm, thm_miss], axis=1).astype(np.float32)\n    mb_tof = tof_norm.reshape(T, 5, 8, 8).astype(np.float32)\n\n    if imu_only:\n        feat[:, 17:22] = 0; feat[:, 22:27] = 1.0    # thm + thm_miss\n        feat[:, 27:347] = 0; feat[:, 347:348] = 1.0 # tof + tof_miss_frac\n        mb_thm[:, :5] = 0; mb_thm[:, 5:] = 1.0\n        mb_tof[:] = 0; tof_miss_frac[:] = 1.0\n\n    if T > MAX_LEN:\n        lo = (T - MAX_LEN)//2\n        feat = feat[lo:lo+MAX_LEN]\n        mb_imu = mb_imu[lo:lo+MAX_LEN]; mb_thm = mb_thm[lo:lo+MAX_LEN]\n        mb_tof = mb_tof[lo:lo+MAX_LEN]; tof_miss_frac = tof_miss_frac[lo:lo+MAX_LEN]\n        mask = np.ones(MAX_LEN, dtype=np.float32)\n    else:\n        pad = MAX_LEN - T\n        feat = np.concatenate([feat, np.zeros((pad, feat.shape[1]), dtype=np.float32)])\n        mb_imu = np.concatenate([mb_imu, np.zeros((pad, 17), dtype=np.float32)])\n        mb_thm = np.concatenate([mb_thm, np.zeros((pad, 10), dtype=np.float32)])\n        mb_tof = np.concatenate([mb_tof, np.zeros((pad, 5, 8, 8), dtype=np.float32)])\n        tof_miss_frac = np.concatenate([tof_miss_frac, np.zeros((pad, 1), dtype=np.float32)])\n        mask = np.concatenate([np.ones(T, dtype=np.float32), np.zeros(pad, dtype=np.float32)])\n    return feat, mb_imu, mb_thm, mb_tof, tof_miss_frac, mask, imu_only","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T15:04:47.857362Z","iopub.execute_input":"2026-05-27T15:04:47.857577Z","iopub.status.idle":"2026-05-27T15:04:47.872658Z","shell.execute_reply.started":"2026-05-27T15:04:47.857555Z","shell.execute_reply":"2026-05-27T15:04:47.872083Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@torch.no_grad()\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    print(f\"[predict called] seq len={len(sequence)} subject={sequence['subject'][0]}\")\n    feat, mb_imu, mb_thm, mb_tof, mb_tof_mi, mask, imu_only = build_features(sequence)\n\n    feat_t   = torch.from_numpy(feat).unsqueeze(0).to(DEVICE)\n    mask_t   = torch.from_numpy(mask).unsqueeze(0).to(DEVICE)\n    mb_imu_t = torch.from_numpy(mb_imu).unsqueeze(0).to(DEVICE)\n    mb_thm_t = torch.from_numpy(mb_thm).unsqueeze(0).to(DEVICE)\n    mb_tof_t = torch.from_numpy(mb_tof).unsqueeze(0).to(DEVICE)\n    mb_tof_mi_t = torch.from_numpy(mb_tof_mi).unsqueeze(0).to(DEVICE)\n\n    sb_full = torch.zeros(1, N_GESTURE, device=DEVICE)\n    for m in sb_models:\n        logit, _ = m(feat_t, mask_t)\n        sb_full += torch.softmax(logit, 1)\n    sb_full /= len(sb_models)\n\n    mb_full = torch.zeros(1, N_GESTURE, device=DEVICE)\n    for m in mb_models:\n        logit, _ = m(mb_imu_t, mb_thm_t, mb_tof_t, mb_tof_mi_t, mask_t)\n        mb_full += torch.softmax(logit, 1)\n    mb_full /= len(mb_models)\n\n    if imu_only:\n        dist = torch.zeros(1, N_GESTURE, device=DEVICE)\n        imu_part = feat_t[:, :, :17]   # (B, T, 17)\n        for m in dist_models:\n            logit, _ = m(imu_part, mask_t)\n            dist += torch.softmax(logit, 1)\n        dist /= len(dist_models)\n        v4_imu = (1 - W_MB)*sb_full + W_MB*mb_full\n        probs = (1 - W_D)*v4_imu + W_D*dist\n    else:\n        probs = (1 - W_MB)*sb_full + W_MB*mb_full\n\n    log_probs = torch.log(probs.clamp_min(1e-9)) + BIAS.unsqueeze(0)\n    gid = int(log_probs.argmax(1).item())\n    print(f\"  → predicted: {id2gesture[gid]}\")\n    return id2gesture[gid]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T15:04:47.873549Z","iopub.execute_input":"2026-05-27T15:04:47.873738Z","iopub.status.idle":"2026-05-27T15:04:47.887751Z","shell.execute_reply.started":"2026-05-27T15:04:47.873719Z","shell.execute_reply":"2026-05-27T15:04:47.887109Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"inference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway(\n        data_paths=(\n            '/kaggle/input/competitions/cmi-detect-behavior-with-sensor-data/test.csv',\n            '/kaggle/input/competitions/cmi-detect-behavior-with-sensor-data/test_demographics.csv',\n        )\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T15:04:47.888723Z","iopub.execute_input":"2026-05-27T15:04:47.889031Z","iopub.status.idle":"2026-05-27T15:04:50.690394Z","shell.execute_reply.started":"2026-05-27T15:04:47.888987Z","shell.execute_reply":"2026-05-27T15:04:50.689672Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nfrom pathlib import Path\n\nsub_path = Path(\"/kaggle/working/submission.parquet\")\nsub = pd.read_parquet(sub_path)\nprint(f\"submission.parquet  shape={sub.shape}\")\nprint(sub)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T15:04:50.691491Z","iopub.execute_input":"2026-05-27T15:04:50.691855Z","iopub.status.idle":"2026-05-27T15:04:50.799324Z","shell.execute_reply.started":"2026-05-27T15:04:50.691777Z","shell.execute_reply":"2026-05-27T15:04:50.798417Z"}},"outputs":[],"execution_count":null}]}