{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"vscode":{"interpreter":{"hash":"f7241b2af102f7e024509099765066b36197b195077f7bfac6e5bc041ba17c8c"}},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12888675,"sourceType":"datasetVersion","datasetId":6093998},{"sourceId":12898879,"sourceType":"datasetVersion","datasetId":7957319},{"sourceId":12909581,"sourceType":"datasetVersion","datasetId":8168498},{"sourceId":12917259,"sourceType":"datasetVersion","datasetId":7983312},{"sourceId":12917291,"sourceType":"datasetVersion","datasetId":7983278},{"sourceId":12917332,"sourceType":"datasetVersion","datasetId":8173162},{"sourceId":12917342,"sourceType":"datasetVersion","datasetId":8172563},{"sourceId":12920136,"sourceType":"datasetVersion","datasetId":6202949},{"sourceId":12929851,"sourceType":"datasetVersion","datasetId":8178148},{"sourceId":12932896,"sourceType":"datasetVersion","datasetId":8183908},{"sourceId":12938086,"sourceType":"datasetVersion","datasetId":8183815},{"sourceId":12939006,"sourceType":"datasetVersion","datasetId":7957299},{"sourceId":259267077,"sourceType":"kernelVersion"},{"sourceId":259543862,"sourceType":"kernelVersion"}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"EVAL = False\nDEBUG = False\n\nimport os\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    EVAL = False\n    DEBUG = False\n\nIMU_ONLY = False\n\nUSE_GEOM_AVG = False\nFULLFIT = True\n\n\nORIENTATION_PP = False\nLIM = 2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:17:23.115800Z","iopub.execute_input":"2025-09-02T19:17:23.116007Z","iopub.status.idle":"2025-09-02T19:17:23.122753Z","shell.execute_reply.started":"2025-09-02T19:17:23.115990Z","shell.execute_reply":"2025-09-02T19:17:23.122047Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Imports","metadata":{}},{"cell_type":"code","source":"import os\nimport gc\nimport sys\nimport glob\nimport json\nimport torch\nimport shutil\nimport warnings\nimport numpy as np\nimport pandas as pd\nimport torch.nn.functional as F\nimport matplotlib.pyplot as plt\n\nfrom tqdm import tqdm\nfrom scipy.special import softmax\nfrom collections import defaultdict\n\nwarnings.simplefilter(action=\"ignore\", category=UserWarning)\npd.set_option('display.max_columns', 100)\npd.set_option(\"display.precision\", 5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:17:23.123451Z","iopub.execute_input":"2025-09-02T19:17:23.123671Z","iopub.status.idle":"2025-09-02T19:17:27.182803Z","shell.execute_reply.started":"2025-09-02T19:17:23.123650Z","shell.execute_reply":"2025-09-02T19:17:27.182226Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sys.path.append(\"/kaggle/input/cmi-behavior-code/src/\")\n\nfrom params import *\nfrom util.logger import Config\nfrom util.metrics import hierarchical_f1\nfrom util.torch import load_model_weights\nfrom model_zoo.models import define_model\nfrom inference.predict import predict as predict_","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:17:27.184072Z","iopub.execute_input":"2025-09-02T19:17:27.184443Z","iopub.status.idle":"2025-09-02T19:17:51.728071Z","shell.execute_reply.started":"2025-09-02T19:17:27.184423Z","shell.execute_reply":"2025-09-02T19:17:51.727291Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Theo","metadata":{}},{"cell_type":"markdown","source":"### Utils","metadata":{}},{"cell_type":"code","source":"from inference.utils import process, handle_imu_nans, gravity_only_acc\nfrom data.processing import flip_acc_rot\n\n\nclass TheoPredictor:\n    def __init__(self, exp_folder, folds, imu_only=False, device=\"cuda\", use_fp16=True, orientation=False):\n        self.exp_folder = exp_folder\n        self.folds = folds\n        self.device = device\n        self.use_fp16 = use_fp16\n        self.imu_only = imu_only\n        self.orientation = orientation\n\n        config = Config(json.load(open(exp_folder + \"config.json\", \"r\")))\n\n        self.max_len = config.max_len\n        self.resize = config.resize if hasattr(config, \"resize\") else False\n        self.fix_quat = config.fix_quat\n\n        self.models = []\n        for fold in folds:\n            weights = exp_folder + f\"{config.name}_{fold}.pt\"\n    \n            model = define_model(\n                config.name,\n                config.model_args,\n                num_classes=config.num_classes,\n                verbose=True,\n                pretrained=False,\n                config_dir=CFG_FOLDER,\n                cnn_2d=config.cnn_2d if hasattr(config, \"cnn_2d\") else False,\n            )\n            model = model.to(device).eval()\n    \n            model = load_model_weights(model, weights, verbose=True)\n            self.models.append(model)        \n\n    def preprocess(self, sequence, demographics):\n        fts = {\n            \"imu\": sequence[IMU_FEATURES].values.astype(np.float32),\n        }    \n        fts[\"imu\"] = handle_imu_nans(fts[\"imu\"])\n    \n        if not self.imu_only:\n            fts[\"thm\"] = sequence[THM_FEATURES].values.astype(np.float32)\n            fts[\"tof\"] = sequence[TOF_FEATURES].fillna(FILL_VALUES['tof']).values.astype(np.uint8)\n    \n        handedness = demographics.handedness.values[0]\n        if handedness == 0:\n            try:\n                fts[\"imu\"][:, 3:] = flip_acc_rot(fts[\"imu\"][:, 3:].copy())\n                fts[\"imu\"][:, 0] *= -1\n            except ValueError:\n                # print(\"ValueError\")\n                if self.fix_quat:\n                    fts[\"imu\"][:, [0, 4, 5]] *= -1\n                else:\n                    fts[\"imu\"][:, [0, 3, 6]] *= -1\n    \n            if \"thm\" in fts:\n                fts[\"thm\"] = fts[\"thm\"][:, [0, 1, 4, 3, 2]]\n    \n            if \"tof\" in fts:\n                tof = fts[\"tof\"].reshape(-1, 5, 8, 8)\n                tof = tof[:, [0, 1, 4, 3, 2]]\n                tof[:, [0, 1, 3]] = tof[:, [0, 1, 3], :, ::-1]\n                tof[:, [2, 4]] = tof[:, [2, 4], ::-1]\n                tof = tof.reshape(-1, 320)\n                fts[\"tof\"] = tof\n    \n        fts = process(\n            fts,\n            self.max_len,\n            demographics,\n            resize=self.resize,\n            fix_quat=self.fix_quat,\n            device=self.device\n        )\n        return fts\n\n    def infer(self, fts, activation=\"softmax\"):\n        all_preds = []\n        for model in self.models:\n            with torch.inference_mode(), torch.amp.autocast(\"cuda\", enabled=self.use_fp16):\n                if self.orientation:\n                    pred = model(fts)[2]\n                else:\n                    pred = model(fts)[0]\n            pred = pred[0].cpu()\n            if activation == \"softmax\":\n                pred = pred.softmax(0)\n            pred = pred.numpy().astype(np.float64)\n            all_preds.append(pred)\n        return all_preds","metadata":{"trusted":true,"_kg_hide-input":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-09-02T19:17:51.728885Z","iopub.execute_input":"2025-09-02T19:17:51.729501Z","iopub.status.idle":"2025-09-02T19:17:51.899200Z","shell.execute_reply.started":"2025-09-02T19:17:51.729470Z","shell.execute_reply":"2025-09-02T19:17:51.898429Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Params","metadata":{}},{"cell_type":"code","source":"FOLDS = [i for i in range(5)]\nif EVAL:\n    FULLFIT = False\n    FOLDS = [0] \nif DEBUG:\n    FULLFIT = False\n    FOLDS = [3]\nFOLDS","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:17:51.900016Z","iopub.execute_input":"2025-09-02T19:17:51.900489Z","iopub.status.idle":"2025-09-02T19:17:51.906811Z","shell.execute_reply.started":"2025-09-02T19:17:51.900470Z","shell.execute_reply":"2025-09-02T19:17:51.906089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_PATH = \"/kaggle/input/cmi-detect-behavior-with-sensor-data/\"\nDATASET = \"/kaggle/input/cmi-weights-1/\"\nCFG_FOLDER = \"/kaggle/input/cmi-behavior-code/src/configs/\"\n\nUSE_FP16 = True\nNUM_WORKERS = 8\n\nDEVICE = \"cuda:1\" if torch.cuda.is_available() else \"cpu\"\nAVG_FCT = np.prod if USE_GEOM_AVG else np.mean","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:17:51.907556Z","iopub.execute_input":"2025-09-02T19:17:51.907837Z","iopub.status.idle":"2025-09-02T19:17:51.917938Z","shell.execute_reply.started":"2025-09-02T19:17:51.907821Z","shell.execute_reply":"2025-09-02T19:17:51.917156Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EXP_FOLDERS_IMU = [\n    # (\"/kaggle/input/cmi-weights-2/2025-08-28_47/\", [f'fullfit_{i}' for i in range(3)] if FULLFIT else FOLDS),  # deberta-rnn      - 0.8321   ->3 FF\n    (\"/kaggle/input/cmi-weights-2/2025-08-28_19/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # maxvit-base 224  - 0.8331\n    (\"/kaggle/input/cmi-weights-2/2025-08-28_38/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # convnext 224     - 0.8299\n    (\"/kaggle/input/cmi-weights-2/2025-08-28_41/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # maxvit-base 384  - 0.8319\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:17:51.918733Z","iopub.execute_input":"2025-09-02T19:17:51.919016Z","iopub.status.idle":"2025-09-02T19:17:51.929408Z","shell.execute_reply.started":"2025-09-02T19:17:51.918993Z","shell.execute_reply":"2025-09-02T19:17:51.928622Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"models_imu = []\nfor exp_folder, folds in EXP_FOLDERS_IMU:\n    models_imu.append(\n        TheoPredictor(\n            exp_folder,\n            folds,\n            imu_only=True,\n            device=DEVICE,\n            use_fp16=USE_FP16\n        )\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:17:51.930185Z","iopub.execute_input":"2025-09-02T19:17:51.930443Z","iopub.status.idle":"2025-09-02T19:18:08.272072Z","shell.execute_reply.started":"2025-09-02T19:17:51.930426Z","shell.execute_reply":"2025-09-02T19:18:08.271315Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# EXP_FOLDERS = [\n#     (\"/kaggle/input/cmi-weights-2/2025-08-29_0/\",  [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # deberta          - 0.8845\n#     (\"/kaggle/input/cmi-weights-1/2025-08-29_44/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # deberta-rnn      - 0.8805\n#     # (\"/kaggle/input/cmi-weights-1/2025-08-29_45/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # modernbert       - 0.8806\n#     (\"/kaggle/input/cmi-weights-1/2025-08-29_47/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # Squeezeformer    - 0.8830\n\n#     (\"/kaggle/input/cmi-weights-2/2025-08-29_23/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # maxvit-base 224  - 0.8832\n#     (\"/kaggle/input/cmi-weights-2/2025-08-29_24/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # convnext 224     - 0.8790\n#     (\"/kaggle/input/cmi-weights-2/2025-08-29_25/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # maxvit-base 384  - 0.8827\n# ]\n\nEXP_FOLDERS = [\n    (\"/kaggle/input/cmi-weights-2/2025-08-29_0/\",  [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # deberta             - 0.8845\n    (\"/kaggle/input/cmi-weights-2/2025-08-29_25/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # maxvit-base 384     - 0.8827\n    (\"/kaggle/input/cmi-weights-3/2025-08-30_44/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # maxvit-base 224     - 0.8848\n    (\"/kaggle/input/cmi-weights-3/2025-08-31_7/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),   # convnext224 len120  - 0.8890\n    (\"/kaggle/input/cmi-weights-3/2025-08-31_10/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # convnext224 len110  - 0.8863\n]\n\n\nif IMU_ONLY: \n    EXP_FOLDERS = []","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:18:08.274449Z","iopub.execute_input":"2025-09-02T19:18:08.274666Z","iopub.status.idle":"2025-09-02T19:18:08.280112Z","shell.execute_reply.started":"2025-09-02T19:18:08.274650Z","shell.execute_reply":"2025-09-02T19:18:08.279432Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"models = []\nfor exp_folder, folds in EXP_FOLDERS:\n    models.append(\n        TheoPredictor(\n            exp_folder,\n            folds,\n            imu_only=False,\n            device=DEVICE,\n            use_fp16=USE_FP16\n        )\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:18:08.280899Z","iopub.execute_input":"2025-09-02T19:18:08.281411Z","iopub.status.idle":"2025-09-02T19:18:29.455664Z","shell.execute_reply.started":"2025-09-02T19:18:08.281387Z","shell.execute_reply":"2025-09-02T19:18:29.455048Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Orientation","metadata":{}},{"cell_type":"code","source":"ORIENTATION_PREDICTOR_IMU = [\n    (\"/kaggle/input/cmi-weights-3/2025-08-28_19/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS), # maxvit-base 224  - 0.8331\n    (\"/kaggle/input/cmi-weights-3/2025-09-01_6/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # squeezeformer\n]\n\nif not ORIENTATION_PP:\n    ORIENTATION_PREDICTOR_IMU = []\n\nmodels_orient_imu = []\nfor exp_folder, folds in ORIENTATION_PREDICTOR_IMU:\n    models_orient_imu.append(\n        TheoPredictor(\n            exp_folder,\n            folds,\n            imu_only=True,\n            device=DEVICE,\n            use_fp16=USE_FP16,\n            orientation=True,\n        )\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:18:29.456415Z","iopub.execute_input":"2025-09-02T19:18:29.456678Z","iopub.status.idle":"2025-09-02T19:18:35.993214Z","shell.execute_reply.started":"2025-09-02T19:18:29.456661Z","shell.execute_reply":"2025-09-02T19:18:35.992550Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ORIENTATION_PREDICTOR = [\n    (\"/kaggle/input/cmi-weights-3/2025-08-31_7/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # convnext\n    (\"/kaggle/input/cmi-weights-3/2025-09-01_2/\", [f'fullfit_{i}' for i in range(1)] if FULLFIT else FOLDS),  # deberta\n]\n\n\nif not ORIENTATION_PP:\n    ORIENTATION_PREDICTOR = []\n\nmodels_orient = []\nfor exp_folder, folds in ORIENTATION_PREDICTOR:\n    models_orient.append(\n        TheoPredictor(\n            exp_folder,\n            folds,\n            imu_only=False,\n            device=DEVICE,\n            use_fp16=USE_FP16,\n            orientation=True,\n        )\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:18:35.993937Z","iopub.execute_input":"2025-09-02T19:18:35.994222Z","iopub.status.idle":"2025-09-02T19:18:38.317512Z","shell.execute_reply.started":"2025-09-02T19:18:35.994193Z","shell.execute_reply":"2025-09-02T19:18:38.316892Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if EVAL:\n    ORIENTATION_PREDICTOR = ORIENTATION_PREDICTOR[-1:]\n    ORIENTATION_PREDICTOR_IMU = ORIENTATION_PREDICTOR_IMU[-1:]\n    models = models[:1]\n    models_imu = models_imu[:1]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:18:38.318225Z","iopub.execute_input":"2025-09-02T19:18:38.318414Z","iopub.status.idle":"2025-09-02T19:18:38.322380Z","shell.execute_reply.started":"2025-09-02T19:18:38.318399Z","shell.execute_reply":"2025-09-02T19:18:38.321658Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Yu4u","metadata":{}},{"cell_type":"code","source":"import sys\nsys.path.append(\"/kaggle/input/cmi3-src/kaggle-cmi3-main\")\n\nfrom src.submission_util import Classifier as yu4u_Classifier\nfrom src.util import gestures as yu4u_gestures","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:18:38.323009Z","iopub.execute_input":"2025-09-02T19:18:38.323289Z","iopub.status.idle":"2025-09-02T19:18:39.060521Z","shell.execute_reply.started":"2025-09-02T19:18:38.323273Z","shell.execute_reply":"2025-09-02T19:18:39.059894Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"yu4u_order_swap = [yu4u_gestures.index(c) for c in CLASSES]\nyu4u_order_swap","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:18:39.061295Z","iopub.execute_input":"2025-09-02T19:18:39.061501Z","iopub.status.idle":"2025-09-02T19:18:39.066855Z","shell.execute_reply.started":"2025-09-02T19:18:39.061484Z","shell.execute_reply":"2025-09-02T19:18:39.066066Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"yu4u_clf = yu4u_Classifier(\n    imu_model_paths=[\n        \"/kaggle/input/cmi3-models/exp310\",\n        \"/kaggle/input/cmi3-models/exp311\",\n        \"/kaggle/input/cmi3-models/exp312\",\n    ],\n    imu_bert_model_paths=[\n        \"/kaggle/input/cmi3-models/exp321\",\n        \"/kaggle/input/cmi3-models/exp323\",\n        \"/kaggle/input/cmi3-models/exp324\",\n    ],\n    tof_model_paths=[\n        \"/kaggle/input/cmi3-models/exp304\",\n        \"/kaggle/input/cmi3-models/exp305\",\n        \"/kaggle/input/cmi3-models/exp306\",\n    ],\n    use_softmax=True\n)","metadata":{"trusted":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2025-09-02T19:18:39.067654Z","iopub.execute_input":"2025-09-02T19:18:39.067917Z","iopub.status.idle":"2025-09-02T19:18:57.347237Z","shell.execute_reply.started":"2025-09-02T19:18:39.067903Z","shell.execute_reply":"2025-09-02T19:18:57.346593Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## ln","metadata":{}},{"cell_type":"code","source":"from ln_infer import Classifier as ln_Classifier","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:18:57.347899Z","iopub.execute_input":"2025-09-02T19:18:57.348126Z","iopub.status.idle":"2025-09-02T19:18:57.519911Z","shell.execute_reply.started":"2025-09-02T19:18:57.348087Z","shell.execute_reply":"2025-09-02T19:18:57.519031Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"target_gestures = ['Above ear - pull hair','Cheek - pinch skin','Eyebrow - pull hair','Eyelash - pull hair','Forehead - pull hairline','Forehead - scratch','Neck - pinch skin','Neck - scratch']\nnon_target_gestures = ['Write name on leg','Wave hello','Glasses on/off','Text on phone','Write name in air','Feel around in tray and pull out an object','Scratch knee/leg skin','Pull air toward your face','Drink from bottle/cup','Pinch knee/leg skin']\nln_gestures = sorted(target_gestures) + sorted(non_target_gestures)\n\nln_order_swap = [ln_gestures.index(c) for c in CLASSES]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-02T19:18:57.520875Z","iopub.execute_input":"2025-09-02T19:18:57.521129Z","iopub.status.idle":"2025-09-02T19:18:57.525530Z","shell.execute_reply.started":"2025-09-02T19:18:57.521082Z","shell.execute_reply":"2025-09-02T19:18:57.524751Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ln_clf = ln_Classifier()","metadata":{"trusted":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2025-09-02T19:18:57.526437Z","iopub.execute_input":"2025-09-02T19:18:57.526669Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Minerppdy","metadata":{}},{"cell_type":"code","source":"sys.path.append(os.path.join('/kaggle/input/minerppdy-src/ppdy/'))\n\nfrom util_.inference import Classifier as MinerppdyClassifier","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if torch.cuda.is_available() and torch.cuda.device_count() >= 2:\n    # Set device to cuda:1 (second GPU)\n    device = torch.device('cuda:1')\n    print(f\"Found 2 GPUs. Using GPU 1: {torch.cuda.get_device_name(1)}\")\nelif torch.cuda.is_available() and torch.cuda.device_count() == 1:\n    device = torch.device('cuda:0')\n    print(f\"Found 1 GPUs. Using GPU 0: {torch.cuda.get_device_name(0)}\")\nelse:\n    device = torch.device('cpu')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"minerppdy_gestures = [\n    \"Above ear - pull hair\",\n    \"Cheek - pinch skin\",\n    \"Eyebrow - pull hair\",\n    \"Eyelash - pull hair\",\n    \"Forehead - pull hairline\",\n    \"Forehead - scratch\",\n    \"Neck - pinch skin\",\n    \"Neck - scratch\",\n\n    \"Drink from bottle/cup\",\n    \"Feel around in tray and pull out an object\",\n    \"Glasses on/off\",\n    \"Pinch knee/leg skin\",\n    \"Pull air toward your face\",\n    \"Scratch knee/leg skin\",\n    \"Text on phone\",\n    \"Wave hello\",\n    \"Write name in air\",\n    \"Write name on leg\"\n]\n\n\nminerppdy_order_swap = [minerppdy_gestures.index(c) for c in CLASSES]\nminerppdy_order_swap","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"imu_model_dir = [\n    '/kaggle/input/cmi-minerppdy-model/only_imu_model_0831_1/seed_1/',\n    '/kaggle/input/cmi-minerppdy-model/only_imu_model_0831_1/seed_2/',\n    '/kaggle/input/cmi-minerppdy-model/only_imu_model_0831_1/seed_3/',\n]\n    \nall_model_dir = [\n    '/kaggle/input/cmi-minerppdy-model/all_sensor_model_0831_1/seed_1/',\n    '/kaggle/input/cmi-minerppdy-model/all_sensor_model_0831_1/seed_2/',\n    '/kaggle/input/cmi-minerppdy-model/all_sensor_model_0831_1/seed_3/',\n]\n# load_model\n\nmodel_dir = imu_model_dir + all_model_dir\nminerppdy_cls = MinerppdyClassifier(\n    model_dir,\n    device,\n    seq_len=128,\n    imu_weight=0.,\n    all_weight=1.\n)\n\nprint('imu_model_num', len(minerppdy_cls.imu_only_models))\nprint('all_model_num', len(minerppdy_cls.all_sensor_models))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inference","metadata":{}},{"cell_type":"code","source":"from typing import List\nfrom scipy.optimize import linear_sum_assignment\n\ndef batch_assign_last_optimal(\n    P: np.ndarray,          # shape: (N, C)  scores per sample (rows) and class (cols), larger is better\n    classes: List[str],     # length C\n    limit_per_class: int,   # uniform cap L for all classes\n) -> str:\n    \"\"\"\n    Batch-optimized assignment:\n      - Maximize total score under per-class cap L (uniform across classes).\n      - Return the class name assigned to the last sample (row N-1).\n\n    Precondition: L * C >= N (total capacity >= number of samples).\n    Raises ValueError if the capacity condition is violated.\n    \"\"\"\n    P = np.asarray(P, dtype=float)\n    N, C = P.shape\n    if len(classes) != C:\n        raise ValueError(\"Length of 'classes' must match the number of columns in P.\")\n    if limit_per_class < 0:\n        raise ValueError(\"'limit_per_class' must be >= 0.\")\n\n    total_slots = limit_per_class * C\n    if total_slots < N:\n        N = total_slots\n        P = P[-N:]\n        \n    # if total_slots < N:\n    #     raise ValueError(f\"Insufficient total capacity (L*C={total_slots} < N={N}). Increase L or reduce N.\")\n\n    # Expand each class into L identical slots\n    slot_to_cls_idx = []\n    for cls_idx in range(C):\n        slot_to_cls_idx.extend([cls_idx] * limit_per_class)\n    S = len(slot_to_cls_idx)  # total number of slots\n\n    # Build cost matrix (smaller is better) as negative scores to convert max-sum to min-cost\n    cost = np.empty((N, S), dtype=float)\n    for s, cls_idx in enumerate(slot_to_cls_idx):\n        cost[:, s] = -P[:, cls_idx]\n\n    # Hungarian method (supports rectangular matrices with columns >= rows)\n    row_ind, col_ind = linear_sum_assignment(cost)\n\n    # Retrieve the assigned slot for the last sample (row N-1) and map it back to the original class\n    assigned_slot = {r: c for r, c in zip(row_ind, col_ind)}[N - 1]\n    assigned_cls = classes[slot_to_cls_idx[assigned_slot]]\n    return assigned_cls","metadata":{"trusted":true,"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"AVG_FCT_BLEND = np.mean\n\nPREDS_BUFFER = defaultdict(list)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import polars as pl\nimport pandas as pd\nfrom scipy.special import softmax\n\n\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    if not isinstance(sequence, pd.core.frame.DataFrame):\n        sequence = sequence.to_pandas()\n    if not isinstance(demographics, pd.core.frame.DataFrame):\n        demographics = demographics.to_pandas()\n\n    global PREDS_BUFFER\n\n    # IMU only ?\n    imu_only = (\n        np.all(sequence[THM_FEATURES].isna().values) or\n        np.max(sequence[TOF_FEATURES].fillna(-1)) <= 0\n    )\n    if IMU_ONLY:\n        imu_only = True\n\n    missing_imu = np.mean(sequence[IMU_FEATURES].isna().values) > 0.2\n\n    if DEBUG:\n        seq = sequence['sequence_id'].values[0]\n        idx = df_val[\"sequence_id\"].values.tolist().index(seq)\n        print(f'\\n-> Sequence {seq}  - GT : {df_val.gesture.values[idx]} ({\"imu_only\" if imu_only else \"all\"})\\n')\n        \n    ###################################\n    #            minerppdy            #\n    ###################################\n\n    if not EVAL:\n        # if not imu_only:  # don't use otherwise\n        logits_minerppdy, gesture_minerppdy = minerppdy_cls.predict(sequence.copy(), demographics.copy())\n        pred_minerppdy = softmax(logits_minerppdy.flatten())\n        pred_minerppdy = pred_minerppdy[minerppdy_order_swap]\n        \n        if DEBUG:\n            print(f\"\\n  - Minerppdy preds :\\n{np.round(pred_minerppdy, 2).tolist()}\\n-> {CLASSES[np.argmax(pred_minerppdy)]}\") \n    \n    ###################################\n    #              yu4u               #\n    ###################################\n\n    if not EVAL:\n        pred_yu4u_imu = yu4u_clf.predict2(sequence.copy(), demographics.copy(), imu_only=True)\n        pred_yu4u_imu = pred_yu4u_imu[yu4u_order_swap].astype(float)\n    \n        if not imu_only:\n            pred_yu4u = yu4u_clf.predict2(sequence.copy(), demographics.copy(), imu_only=False) \n            pred_yu4u = pred_yu4u[yu4u_order_swap].astype(float)\n    \n        if DEBUG:\n            print(f\"\\n  - Yu4u preds imu :\\n{np.round(pred_yu4u_imu, 2).tolist()}\\n-> {CLASSES[np.argmax(pred_yu4u_imu)]}\") \n            if not imu_only:\n                print(f\"  - Yu4u preds :\\n{np.round(pred_yu4u, 2).tolist()}\\n-> {CLASSES[np.argmax(pred_yu4u)]}\") \n\n    ###################################\n    #               ln                #\n    ###################################\n\n    if not EVAL:\n        if \"gesture\" in sequence.columns:\n            sequence.drop(\"gesture\", axis=1, inplace=True)\n    \n        pred_ln_imu = ln_clf.predict(sequence.copy(), demographics.copy(), key=\"imu\")\n        pred_ln_imu = pred_ln_imu[ln_order_swap].astype(float)\n    \n        if not imu_only:\n            pred_ln = ln_clf.predict(sequence.copy(), demographics.copy(), key=\"all\")\n            pred_ln = pred_ln[ln_order_swap].astype(float)\n        \n        if DEBUG:\n            print(f\"\\n  - ln preds imu :\\n{np.round(pred_ln_imu, 4).tolist()}\\n-> {CLASSES[np.argmax(pred_ln_imu)]}\")\n            if not imu_only:\n                print(f\"  - ln preds :\\n{np.round(pred_ln, 4).tolist()}\\n-> {CLASSES[np.argmax(pred_ln)]}\")\n\n    ###################################\n    #              Theo               #\n    ###################################\n\n    # IMU only\n    all_preds = []\n    for model in models_imu:\n        fts = model.preprocess(sequence.copy(), demographics.copy())\n        pred = model.infer(fts, activation=\"softmax\")\n        all_preds += pred\n    pred_theo_imu = AVG_FCT(all_preds, 0).astype(float)\n\n    # All\n    if not imu_only:\n        # Inference\n        all_preds = []\n        for model in models:\n            fts = model.preprocess(sequence.copy(), demographics.copy())\n            pred = model.infer(fts, activation=\"softmax\")\n            all_preds += pred\n        pred_theo = AVG_FCT(all_preds, 0).astype(float)\n\n    if DEBUG:\n        print(f\"\\n  - Theo preds imu :\\n{np.round(pred_theo_imu, 2).tolist()}\\n-> {CLASSES[np.argmax(pred_theo_imu)]}\") \n        if not imu_only:\n            print(f\"  - Theo preds :\\n{np.round(pred_theo, 2).tolist()}\\n-> {CLASSES[np.argmax(pred_theo)]}\") \n\n    ###################################\n    #           Final blend           #\n    ###################################\n\n    if not EVAL:\n        if imu_only:\n            blended_preds = (\n                pred_theo_imu * 0.35 + \n                pred_yu4u_imu * 0.35 + \n                pred_ln_imu * 0.25 + \n                pred_minerppdy * 0.05\n            )\n        else:\n            if missing_imu:  # \n                blended_preds = (\n                    pred_theo * 1/3 + \n                    pred_ln * 1/3 + \n                    pred_yu4u * 1/6 + \n                    pred_minerppdy * 1/6\n                )\n            else:\n                blended_preds_imu = (pred_yu4u_imu + pred_theo_imu + pred_ln_imu) / 3\n                \n                blended_preds = (\n                    pred_theo * 0.21 + \n                    pred_ln * 0.21 + \n                    pred_yu4u * 0.13 + \n                    pred_minerppdy * 0.2 +\n                    blended_preds_imu * 0.25\n                )\n    else:\n        blended_preds = pred_theo_imu if imu_only else pred_theo\n\n    ###################################\n    #          Post-Process           #\n    ###################################\n    if ORIENTATION_PP:\n        models_to_use_orient = models_orient_imu if imu_only else models_orient\n        \n        try:\n            all_orients = []\n            for model in models_to_use_orient:\n                fts = model.preprocess(sequence.copy(), demographics.copy())\n                pred = model.infer(fts, activation=\"softmax\")\n                all_orients += pred\n            all_orients = np.mean(all_orients, 0)\n            orient = ORIENTATIONS[np.argmax(all_orients)]\n            # print(orient)\n    \n            subject = \"-\".join(map(str, demographics.values[0, 1:].tolist()))\n    \n            if LIM == 2:  # 2 preds per subj/orient pair\n                key = f'{subject}_{orient}'\n            else:  # One pred per subj/orient/imu_ony triplet\n                key = f'{subject}_{orient}_{imu_only_2}'\n\n            PREDS_BUFFER[key].append(blended_preds)\n\n            probs = np.stack(PREDS_BUFFER[key])\n\n            y = batch_assign_last_optimal(probs.copy(), limit_per_class=2, classes=CLASSES)\n\n            if DEBUG:\n                print(f\"\\n  - Blend preds :\\n{np.round(blended_preds, 2).tolist()}\\n\\t-> {y} (PP - {orient})\") \n        except Exception:\n            if EVAL:\n                print('PP ERROR')\n            y = CLASSES[np.argmax(blended_preds)]\n            if DEBUG:\n                print(f\"\\n  - Blend preds :\\n{np.round(blended_preds, 2).tolist()}\\n\\t-> {y} (Failed PP)\") \n\n        # if DEBUG:\n        #     # print(CONFS[f'{subject}_{orient}'], subject, orient)\n        #     print(sum(sum(COUNTS[k].values()) for k in COUNTS))\n    else:\n        y = CLASSES[np.argmax(blended_preds)]\n\n        if DEBUG:\n            print(f\"\\n  - Blend preds :\\n{np.round(blended_preds, 2).tolist()}\\n\\t-> {y}\") \n\n    # if DEBUG:\n    #     handedness = demographics.handedness.values[0]\n    #     preds_used = preds_imu if imu_only else preds_\n    #     seq = sequence['sequence_id'].values[0]\n    #     delta = ((all_preds - preds_used[idx]) ** 2).max()\n    #     if delta > 0.001:\n    #         print(f'❌ Different predictions for seq {seq}  ({\"IMU Only   \" if imu_only else \"IMU+THM+TOF\"} - {\"Right\" if handedness else \"Left\"}-Handed)')\n    #         print(f\"Preds: {np.round(all_preds, 3)} ({y})\")\n    #         print(f\"Ref  : {np.round(preds_used[idx], 3)} ({CLASSES[np.argmax(preds_used[idx])]})\")\n    #     else:\n    #         print(f'✅️ Matched predictions for seq {seq}  ({\"IMU Only   \" if imu_only else \"IMU+THM+TOF\"} - {\"Right\" if handedness else \"Left\"}-Handed)')\n    \n    return y","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Check","metadata":{}},{"cell_type":"code","source":"if EVAL:\n    data_paths = (DATASET + 'val_0.csv', DATA_PATH + \"train_demographics.csv\")\n    # data_paths = (DATA_PATH + 'train.csv', DATA_PATH + \"train_demographics.csv\")  # MORE DATA\nelif DEBUG:\n    data_paths = (DATASET + 'debug_3.csv', DATA_PATH + \"train_demographics.csv\")\nelse:\n    data_paths = (DATA_PATH + 'test.csv', DATA_PATH + \"test_demographics.csv\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if EVAL or DEBUG:\n    df_val = pd.read_csv(EXP_FOLDERS_IMU[0][0] + f'df_val_{FOLDS[0]}.csv')\n    preds_imu = AVG_FCT([\n        np.load(e + f'pred_val_{FOLDS[0]}.npy') for e, _ in EXP_FOLDERS_IMU\n    ], 0)\n    df_val['pred_val_imu'] = [CLASSES[c] for c in preds_imu.argmax(-1)]\n\n    if not IMU_ONLY:\n        preds_ = AVG_FCT([\n            np.load(e + f'pred_val_{FOLDS[0]}.npy') for e, _ in EXP_FOLDERS\n        ], 0)\n        df_val['pred_val'] = [CLASSES[c] for c in preds_.argmax(-1)]\n    else:\n        df_val['pred_val'] = df_val['pred_val_imu']","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if EVAL or DEBUG :\n    data = pd.read_csv(data_paths[0])\n    demo = pd.read_csv(data_paths[1])\n\n    preds = []\n    for seq, dfg in tqdm(data.groupby('sequence_id'), disable=DEBUG):\n        demo_seq = demo[demo['subject'] == dfg['subject'].values[0]]\n        y = predict(dfg, demo_seq)\n        preds.append({'sequence_id': seq, 'gesture': y})\n    #     break\n    \n    sub_ = pd.DataFrame(preds)\n    sub_ = sub_.merge(df_val[[\"sequence_id\", \"pred_val\", \"pred_val_imu\", \"gesture\"]].rename(columns={\"gesture\": \"gt\"}))\n\nif EVAL:\n    val_f1 = hierarchical_f1(sub_['gt'], sub_['pred_val'])\n    val_f1_imu = hierarchical_f1(sub_['gt'], sub_['pred_val_imu'])\n    inf_f1 = hierarchical_f1(sub_['gt'], sub_['gesture'])\n    print(f\" -> Val F1    :  {val_f1:.4f}\")\n    print(f\" -> Val F1 IMU:  {val_f1_imu:.4f}\")\n    print(f\" -> Inf F1    :  {inf_f1:.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Main","metadata":{}},{"cell_type":"code","source":"import kaggle_evaluation.cmi_inference_server\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    if EVAL and not DEBUG:\n        data_paths = (DATASET + 'val_0.csv', DATA_PATH + \"train_demographics.csv\")\n    elif DEBUG:\n        data_paths = (DATASET + 'debug_3.csv', DATA_PATH + \"train_demographics.csv\")\n    else:\n        data_paths = (DATA_PATH + 'test.csv', DATA_PATH + \"test_demographics.csv\")\n\n    print(f\"Infering using data: {data_paths} ...\\n\")\n    inference_server.run_local_gateway(data_paths=data_paths)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sub = pd.read_parquet('submission.parquet')\nif EVAL or DEBUG:\n    sub = sub.merge(df_val[[\"sequence_id\", \"pred_val\", \"gesture\"]].rename(columns={\"gesture\": \"gt\"}), how=\"left\")\nsub.head(10)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Done !","metadata":{}}]}