{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.11"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12285448,"sourceType":"datasetVersion","datasetId":7715218}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":20.184915,"end_time":"2025-06-21T19:01:48.289795","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-06-21T19:01:28.104880","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# CMI - LSTM Classifier Starter Inference\n\nThis is the submission notebook from the following starter training notebook: [CMI - LSTM Classifier Starter](https://www.kaggle.com/code/morodertobias/cmi-lstm-classifier-starter/).\n\n**All comments welcome!**\n\n## Change Log\n- V4: Use updated notebook V2.\n- V6: Use updated notebook V3.\n- V8: Use updated notebook V4.","metadata":{"papermill":{"duration":0.004663,"end_time":"2025-06-21T19:01:32.987977","exception":false,"start_time":"2025-06-21T19:01:32.983314","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"## Import & Settings","metadata":{"papermill":{"duration":0.003421,"end_time":"2025-06-21T19:01:32.995342","exception":false,"start_time":"2025-06-21T19:01:32.991921","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import os\nimport pathlib\nimport numpy as np\nimport pandas as pd\nimport polars as pl\nimport matplotlib.pyplot as plt\nfrom tqdm.auto import tqdm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport kaggle_evaluation.cmi_inference_server\nimport joblib","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:58:11.701652Z","iopub.execute_input":"2025-06-25T20:58:11.701909Z","iopub.status.idle":"2025-06-25T20:58:18.348052Z","shell.execute_reply.started":"2025-06-25T20:58:11.701882Z","shell.execute_reply":"2025-06-25T20:58:18.347472Z"},"papermill":{"duration":8.325273,"end_time":"2025-06-21T19:01:41.324371","exception":false,"start_time":"2025-06-21T19:01:32.999098","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BASE_DIR = pathlib.Path(\"/kaggle/input/cmi-detect-behavior-with-sensor-data\")\nMODEL_DIR = pathlib.Path(\"/kaggle/input/csi-lstm-classifier-starter-dataset\")\nDEVICE = torch.device(\"cuda\") \n\n# data\nID_COL = \"sequence_id\"\nSEQ_COL = \"sequence_counter\"\nFEATURES = [\n    \"acc_x\",\n    \"acc_y\",\n    \"acc_z\",\n    \"rot_w\",\n    \"rot_x\",\n    \"rot_y\",\n    \"rot_z\",\n]\nCLASSES = [\n    \"Above ear - pull hair\",\n    \"Cheek - pinch skin\",\n    \"Drink from bottle/cup\",\n    \"Eyebrow - pull hair\",\n    \"Eyelash - pull hair\",\n    \"Feel around in tray and pull out an object\",\n    \"Forehead - pull hairline\",\n    \"Forehead - scratch\",\n    \"Glasses on/off\",\n    \"Neck - pinch skin\",\n    \"Neck - scratch\",\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# model\nMAX_LENGTH = 40\nHIDDEN_SIZE = 256\nN_LAYERS = 3\nN_CLF_LAYERS = 2\nBIDIRECTIONAL = True\nDROP = 0.15\nNUM_CLASSES = len(CLASSES)","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:58:59.205614Z","iopub.execute_input":"2025-06-25T20:58:59.205887Z","iopub.status.idle":"2025-06-25T20:58:59.211326Z","shell.execute_reply.started":"2025-06-25T20:58:59.205868Z","shell.execute_reply":"2025-06-25T20:58:59.210649Z"},"papermill":{"duration":0.014086,"end_time":"2025-06-21T19:01:41.342286","exception":false,"start_time":"2025-06-21T19:01:41.328200","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"os.listdir(MODEL_DIR)","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:58:59.987430Z","iopub.execute_input":"2025-06-25T20:58:59.988075Z","iopub.status.idle":"2025-06-25T20:59:00.046586Z","shell.execute_reply.started":"2025-06-25T20:58:59.988050Z","shell.execute_reply":"2025-06-25T20:59:00.046049Z"},"papermill":{"duration":0.017916,"end_time":"2025-06-21T19:01:41.363954","exception":false,"start_time":"2025-06-21T19:01:41.346038","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Prepare","metadata":{"papermill":{"duration":0.003498,"end_time":"2025-06-21T19:01:41.371470","exception":false,"start_time":"2025-06-21T19:01:41.367972","status":"completed"},"tags":[]}},{"cell_type":"code","source":"transformer = joblib.load(MODEL_DIR / \"preprocessing.joblib\")\ntransformer","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:59:03.561980Z","iopub.execute_input":"2025-06-25T20:59:03.562571Z","iopub.status.idle":"2025-06-25T20:59:04.481064Z","shell.execute_reply.started":"2025-06-25T20:59:03.562546Z","shell.execute_reply":"2025-06-25T20:59:04.480282Z"},"papermill":{"duration":1.897422,"end_time":"2025-06-21T19:01:43.272445","exception":false,"start_time":"2025-06-21T19:01:41.375023","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class LSTMClassifier(nn.Module):\n\n    def __init__(self):\n        super().__init__()\n        self.drop = nn.Dropout(DROP)\n        self.lstm = nn.LSTM(\n            input_size=len(FEATURES),\n            hidden_size=HIDDEN_SIZE,\n            num_layers=N_LAYERS,\n            bidirectional=BIDIRECTIONAL,\n            dropout=DROP if N_LAYERS > 1 else 0.0,\n            batch_first=False,\n        )\n        output_size = 2 * HIDDEN_SIZE if BIDIRECTIONAL else HIDDEN_SIZE\n        clf_layers = []\n        for i in range(N_CLF_LAYERS - 1):\n            clf_layers.append(nn.Linear(output_size, output_size))\n            clf_layers.append(nn.ReLU())\n        clf_layers.append(nn.Linear(output_size, NUM_CLASSES))\n        self.clf = nn.Sequential(*clf_layers)\n\n    def forward(self, x, mask):\n        x, _ = self.lstm(x)\n        x = torch.mean(x * mask.unsqueeze(-1), dim=0)\n        x = self.drop(x)\n        return self.clf(x)\n\n\nmodel = LSTMClassifier()\nmodel.eval()\nmodel.to(DEVICE)","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:59:04.482316Z","iopub.execute_input":"2025-06-25T20:59:04.482668Z","iopub.status.idle":"2025-06-25T20:59:04.870290Z","shell.execute_reply.started":"2025-06-25T20:59:04.482649Z","shell.execute_reply":"2025-06-25T20:59:04.869672Z"},"papermill":{"duration":0.051318,"end_time":"2025-06-21T19:01:43.328103","exception":false,"start_time":"2025-06-21T19:01:43.276785","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(torch.load(MODEL_DIR / \"model_best_score.pt\", weights_only=True))","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:59:06.081711Z","iopub.execute_input":"2025-06-25T20:59:06.082293Z","iopub.status.idle":"2025-06-25T20:59:06.508292Z","shell.execute_reply.started":"2025-06-25T20:59:06.082270Z","shell.execute_reply":"2025-06-25T20:59:06.507670Z"},"papermill":{"duration":0.029664,"end_time":"2025-06-21T19:01:43.362550","exception":false,"start_time":"2025-06-21T19:01:43.332886","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Direct test","metadata":{"papermill":{"duration":0.004039,"end_time":"2025-06-21T19:01:43.370963","exception":false,"start_time":"2025-06-21T19:01:43.366924","status":"completed"},"tags":[]}},{"cell_type":"code","source":"train = pl.read_csv( BASE_DIR / \"train.csv\", n_rows=1000)\nsequence = train.filter( pl.col(ID_COL) == \"SEQ_000092\")\nsequence","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:59:09.861502Z","iopub.execute_input":"2025-06-25T20:59:09.861780Z","iopub.status.idle":"2025-06-25T20:59:10.995041Z","shell.execute_reply.started":"2025-06-25T20:59:09.861759Z","shell.execute_reply":"2025-06-25T20:59:10.994220Z"},"papermill":{"duration":0.311706,"end_time":"2025-06-21T19:01:43.686648","exception":false,"start_time":"2025-06-21T19:01:43.374942","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = sequence.to_pandas()\ndf = df.sort_values(by=SEQ_COL)","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:59:12.507724Z","iopub.execute_input":"2025-06-25T20:59:12.508663Z","iopub.status.idle":"2025-06-25T20:59:12.562002Z","shell.execute_reply.started":"2025-06-25T20:59:12.508629Z","shell.execute_reply":"2025-06-25T20:59:12.561306Z"},"papermill":{"duration":0.070684,"end_time":"2025-06-21T19:01:43.762065","exception":false,"start_time":"2025-06-21T19:01:43.691381","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = transformer.transform(df[FEATURES])\ndf = df[-MAX_LENGTH:]\nx = torch.tensor(df[FEATURES].values.astype(np.float32))\nx = x.unsqueeze(dim=1)\nx.shape","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:59:15.945044Z","iopub.execute_input":"2025-06-25T20:59:15.945291Z","iopub.status.idle":"2025-06-25T20:59:15.973132Z","shell.execute_reply.started":"2025-06-25T20:59:15.945275Z","shell.execute_reply":"2025-06-25T20:59:15.972297Z"},"papermill":{"duration":0.04637,"end_time":"2025-06-21T19:01:43.813282","exception":false,"start_time":"2025-06-21T19:01:43.766912","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mask = torch.ones_like(x[..., -1])\nmask.shape","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:59:17.498898Z","iopub.execute_input":"2025-06-25T20:59:17.499461Z","iopub.status.idle":"2025-06-25T20:59:17.508437Z","shell.execute_reply.started":"2025-06-25T20:59:17.499439Z","shell.execute_reply":"2025-06-25T20:59:17.507863Z"},"papermill":{"duration":0.022532,"end_time":"2025-06-21T19:01:43.840460","exception":false,"start_time":"2025-06-21T19:01:43.817928","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with torch.no_grad():\n    x = x.to(DEVICE)\n    mask = mask.to(DEVICE)\n    logits = model(x, mask).cpu()\nlogits","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:59:19.545293Z","iopub.execute_input":"2025-06-25T20:59:19.545833Z","iopub.status.idle":"2025-06-25T20:59:19.847702Z","shell.execute_reply.started":"2025-06-25T20:59:19.545808Z","shell.execute_reply":"2025-06-25T20:59:19.846974Z"},"papermill":{"duration":0.149952,"end_time":"2025-06-21T19:01:43.995096","exception":false,"start_time":"2025-06-21T19:01:43.845144","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"CLASSES[int(torch.argmax(logits).item())]","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:59:20.548408Z","iopub.execute_input":"2025-06-25T20:59:20.548684Z","iopub.status.idle":"2025-06-25T20:59:20.555073Z","shell.execute_reply.started":"2025-06-25T20:59:20.548663Z","shell.execute_reply":"2025-06-25T20:59:20.554446Z"},"papermill":{"duration":0.01491,"end_time":"2025-06-21T19:01:44.014868","exception":false,"start_time":"2025-06-21T19:01:43.999958","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Wrap it into predict function","metadata":{"papermill":{"duration":0.004466,"end_time":"2025-06-21T19:01:44.024333","exception":false,"start_time":"2025-06-21T19:01:44.019867","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    df = sequence.to_pandas()\n    df = df.sort_values(SEQ_COL)\n    df = transformer.transform(df)\n    df = df[-MAX_LENGTH:]\n    x = torch.tensor(df[FEATURES].values.astype(np.float32))\n    x = x.unsqueeze(dim=1)\n    mask = torch.ones_like(x[..., -1])\n    model.eval()\n    with torch.no_grad():\n        x = x.to(DEVICE)\n        mask = mask.to(DEVICE)\n        logits = model(x, mask).cpu()\n    pred = CLASSES[int(torch.argmax(logits).item())]\n    print(pred)\n    return pred","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:59:34.964007Z","iopub.execute_input":"2025-06-25T20:59:34.964722Z","iopub.status.idle":"2025-06-25T20:59:34.970126Z","shell.execute_reply.started":"2025-06-25T20:59:34.964698Z","shell.execute_reply":"2025-06-25T20:59:34.969260Z"},"papermill":{"duration":0.011803,"end_time":"2025-06-21T19:01:44.040680","exception":false,"start_time":"2025-06-21T19:01:44.028877","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Run ","metadata":{"papermill":{"duration":0.004527,"end_time":"2025-06-21T19:01:44.049974","exception":false,"start_time":"2025-06-21T19:01:44.045447","status":"completed"},"tags":[]}},{"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=(BASE_DIR / \"test.csv\", BASE_DIR / \"test_demographics.csv\")\n    )","metadata":{"execution":{"iopub.status.busy":"2025-06-25T20:59:42.162251Z","iopub.execute_input":"2025-06-25T20:59:42.162975Z","iopub.status.idle":"2025-06-25T20:59:43.248623Z","shell.execute_reply.started":"2025-06-25T20:59:42.162948Z","shell.execute_reply":"2025-06-25T20:59:43.248058Z"},"papermill":{"duration":1.172872,"end_time":"2025-06-21T19:01:45.227499","exception":false,"start_time":"2025-06-21T19:01:44.054627","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}