{"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":102335,"databundleVersionId":12518947,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":12189294,"sourceType":"datasetVersion","datasetId":7677371}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"try:\n    import reservoir_computing\nexcept ModuleNotFoundError:\n    !pip install reservoir-computing","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T22:59:57.823628Z","iopub.execute_input":"2025-06-17T22:59:57.824662Z","iopub.status.idle":"2025-06-17T22:59:57.830165Z","shell.execute_reply.started":"2025-06-17T22:59:57.824629Z","shell.execute_reply":"2025-06-17T22:59:57.828923Z"}},"outputs":[],"execution_count":8},{"cell_type":"code","source":"try:\n    !cp /kaggle/input/mydata/prepared_data.pkl /kaggle/working/\nexcept:\n    print(\"Prepared data not found\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T22:51:24.812139Z","iopub.execute_input":"2025-06-17T22:51:24.812722Z","iopub.status.idle":"2025-06-17T22:51:26.399817Z","shell.execute_reply.started":"2025-06-17T22:51:24.812686Z","shell.execute_reply":"2025-06-17T22:51:26.398655Z"}},"outputs":[],"execution_count":2},{"cell_type":"code","source":"import os, json, joblib, numpy as np, pandas as pd\nfrom pathlib import Path\nimport warnings \n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom sklearn.linear_model import Ridge, RidgeClassifier\nfrom sklearn.svm import SVC\nimport polars as pl\n\nfrom sklearn.model_selection import StratifiedGroupKFold\nimport kaggle_evaluation.cmi_inference_server\n\nfrom reservoir_computing.modules import RC_model\nimport pickle\nimport numpy as np\n\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-17T22:51:26.402134Z","iopub.execute_input":"2025-06-17T22:51:26.402451Z","iopub.status.idle":"2025-06-17T22:51:28.663841Z","shell.execute_reply.started":"2025-06-17T22:51:26.402415Z","shell.execute_reply":"2025-06-17T22:51:28.662611Z"}},"outputs":[],"execution_count":3},{"cell_type":"code","source":"RAW_DIR = Path(\"/kaggle/input/cmi-detect-behavior-with-sensor-data\")\nPRETRAINED_DIR = Path(\"/kaggle/input/pretrained-model\")  # used when TRAIN=False\nEXPORT_DIR = Path(\"./\")                                    # artefacts will be saved here\nBATCH_SIZE = 64\nPAD_PERCENTILE = 95\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T22:51:28.664753Z","iopub.execute_input":"2025-06-17T22:51:28.665203Z","iopub.status.idle":"2025-06-17T22:51:28.671931Z","shell.execute_reply.started":"2025-06-17T22:51:28.665169Z","shell.execute_reply":"2025-06-17T22:51:28.670655Z"}},"outputs":[],"execution_count":4},{"cell_type":"code","source":"def to_categorical(y, num_classes=None, dtype='float32'):\n    \"\"\"\n    Converts a class vector (integers) to one-hot matrix.\n\n    Args:\n        y: array-like of shape (n_samples,) containing class indices 0..num_classes-1\n        num_classes: total number of classes. If None, inferred as y.max()+1.\n        dtype: data type of the output array.\n\n    Returns:\n        one_hot: numpy array of shape (n_samples, num_classes)\n    \"\"\"\n    y = np.array(y, dtype='int')\n    if num_classes is None:\n        num_classes = int(y.max()) + 1\n    one_hot = np.zeros((y.shape[0], num_classes), dtype=dtype)\n    one_hot[np.arange(y.shape[0]), y] = 1\n    return one_hot\n\ndef pad_sequences_2d(sequences, maxlen, dtype='float32',\n                     padding='post', truncating='post', value=0.0):\n    n_samples = len(sequences)\n    n_features = sequences[0].shape[1]\n    padded = np.full((n_samples, maxlen, n_features),\n                     fill_value=value, dtype=dtype)\n\n    for i, seq in enumerate(sequences):\n        length = seq.shape[0]\n        if length <= maxlen:\n            if padding == 'post':\n                padded[i, :length, :] = seq\n            else:  # 'pre'\n                padded[i, -length:, :] = seq\n        else:\n            if truncating == 'post':\n                truncated = seq[:maxlen]\n            else:  # 'pre'\n                truncated = seq[-maxlen:]\n            padded[i] = truncated\n\n    return padded\n\n\ndef wrap_with_one_hot_encoding(CLF, encode_size):\n    class OneHotEncodingWrapper(CLF):            \n        def predict(self, *args, **kwargs):\n            out = super().predict(*args, **kwargs).astype(int)\n            ohe = np.zeros((out.size, encode_size))\n            ohe[np.arange(out.size), out] = 1\n            return ohe\n    return OneHotEncodingWrapper","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T22:51:28.673222Z","iopub.execute_input":"2025-06-17T22:51:28.673519Z","iopub.status.idle":"2025-06-17T22:51:28.700696Z","shell.execute_reply.started":"2025-06-17T22:51:28.673498Z","shell.execute_reply":"2025-06-17T22:51:28.699627Z"}},"outputs":[],"execution_count":5},{"cell_type":"code","source":"\"\"\"\nHierarchical macro F1 metric for the CMI 2025 Challenge.\n\nThis script defines a single entry point `score(solution, submission, row_id_column_name)`\nthat the Kaggle metrics orchestrator will call.\nIt performs validation on submission IDs and computes a combined binary & multiclass F1 score.\n\"\"\"\n\nimport pandas as pd\nfrom sklearn.metrics import f1_score\n\n\nclass ParticipantVisibleError(Exception):\n    \"\"\"Errors raised here will be shown directly to the competitor.\"\"\"\n    pass\n\n\nclass CompetitionMetric:\n    \"\"\"Hierarchical macro F1 for the CMI 2025 challenge.\"\"\"\n    def __init__(self):\n        self.target_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        self.non_target_gestures = [\n            'Write name on leg',\n            'Wave hello',\n            'Glasses on/off',\n            'Text on phone',\n            'Write name in air',\n            'Feel around in tray and pull out an object',\n            'Scratch knee/leg skin',\n            'Pull air toward your face',\n            'Drink from bottle/cup',\n            'Pinch knee/leg skin'\n        ]\n        self.all_classes = self.target_gestures + self.non_target_gestures\n\n    def calculate_hierarchical_f1(\n        self,\n        sol: pd.DataFrame,\n        sub: pd.DataFrame,\n        verbose=False\n    ) -> float:\n\n        # Validate gestures\n        invalid_types = {i for i in sub['gesture'].unique() if i not in self.all_classes}\n        if invalid_types:\n            raise ParticipantVisibleError(\n                f\"Invalid gesture values in submission: {invalid_types}\"\n            )\n\n        # Compute binary F1 (Target vs Non-Target)\n        y_true_bin = sol['gesture'].isin(self.target_gestures).values\n        y_pred_bin = sub['gesture'].isin(self.target_gestures).values\n        f1_binary = f1_score(\n            y_true_bin,\n            y_pred_bin,\n            pos_label=True,\n            zero_division=0,\n            average='binary'\n        )\n\n        # Build multi-class labels for gestures\n        y_true_mc = sol['gesture'].apply(lambda x: x if x in self.target_gestures else 'non_target')\n        y_pred_mc = sub['gesture'].apply(lambda x: x if x in self.target_gestures else 'non_target')\n\n        # Compute macro F1 over all gesture classes\n        f1_macro = f1_score(\n            y_true_mc,\n            y_pred_mc,\n            average='macro',\n            zero_division=0\n        )\n        if verbose:\n            print(f\"f1_binary: {f1_binary}, f1_macro: {f1_macro}\")\n\n        return 0.5 * f1_binary + 0.5 * f1_macro\n\n\ndef score(\n    solution: pd.DataFrame,\n    submission: pd.DataFrame,\n    row_id_column_name: str\n) -> float:\n    \"\"\"\n    Compute hierarchical macro F1 for the CMI 2025 challenge.\n\n    Expected input:\n      - solution and submission as pandas.DataFrame\n      - Column 'sequence_id': unique identifier for each sequence\n      - 'gesture': one of the eight target gestures or \"Non-Target\"\n\n    This metric averages:\n    1. Binary F1 on SequenceType (Target vs Non-Target)\n    2. Macro F1 on gesture (mapping non-targets to \"Non-Target\")\n\n    Raises ParticipantVisibleError for invalid submissions,\n    including invalid SequenceType or gesture values.\n\n\n    Examples\n    --------\n    >>> import pandas as pd\n    >>> row_id_column_name = \"id\"\n    >>> solution = pd.DataFrame({'id': range(4), 'gesture': ['Eyebrow - pull hair']*4})\n    >>> submission = pd.DataFrame({'id': range(4), 'gesture': ['Forehead - pull hairline']*4})\n    >>> score(solution, submission, row_id_column_name=row_id_column_name)\n    0.5\n    >>> submission = pd.DataFrame({'id': range(4), 'gesture': ['Text on phone']*4})\n    >>> score(solution, submission, row_id_column_name=row_id_column_name)\n    0.0\n    >>> score(solution, solution, row_id_column_name=row_id_column_name)\n    1.0\n    \"\"\"\n    # Validate required columns\n    for col in (row_id_column_name, 'gesture'):\n        if col not in solution.columns:\n            raise ParticipantVisibleError(f\"Solution file missing required column: '{col}'\")\n        if col not in submission.columns:\n            raise ParticipantVisibleError(f\"Submission file missing required column: '{col}'\")\n\n    metric = CompetitionMetric()\n    return metric.calculate_hierarchical_f1(solution, submission)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T22:51:28.701829Z","iopub.execute_input":"2025-06-17T22:51:28.702163Z","iopub.status.idle":"2025-06-17T22:51:28.73113Z","shell.execute_reply.started":"2025-06-17T22:51:28.702133Z","shell.execute_reply":"2025-06-17T22:51:28.730028Z"}},"outputs":[],"execution_count":6},{"cell_type":"code","source":"TRAIN = False\ntry:\n    with open(\"prepared_data.pkl\", \"rb\") as f:\n        out = pickle.load(f)\n    \n    X_tr, X_val, y_tr, y_val, class_weight, le, scaler, final_feature_cols = out\nexcept FileNotFoundError:\n    TRAIN = True\nTRAIN","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T22:51:28.732011Z","iopub.execute_input":"2025-06-17T22:51:28.73234Z","iopub.status.idle":"2025-06-17T22:51:28.918463Z","shell.execute_reply.started":"2025-06-17T22:51:28.732307Z","shell.execute_reply":"2025-06-17T22:51:28.917664Z"}},"outputs":[{"execution_count":7,"output_type":"execute_result","data":{"text/plain":"False"},"metadata":{}}],"execution_count":7},{"cell_type":"code","source":"if TRAIN:\n    print(\"▶ TRAIN MODE – loading dataset …\")\n    df = pd.read_csv(RAW_DIR / \"train.csv\")\n\n    train_dem_df = pd.read_csv(RAW_DIR / \"train_demographics.csv\")\n    df_for_groups = pd.merge(df.copy(), train_dem_df, on='subject', how='left')\n\n    le = LabelEncoder()\n    df['gesture_int'] = le.fit_transform(df['gesture'])\n    np.save(EXPORT_DIR / \"gesture_classes.npy\", le.classes_)\n    gesture_classes = le.classes_\n\n    print(\"  Calculating engineered IMU features (magnitude, angle)...\")\n    df['acc_mag'] = np.sqrt(df['acc_x']**2 + df['acc_y']**2 + df['acc_z']**2)\n    df['rot_angle'] = 2 * np.arccos(df['rot_w'].clip(-1, 1))\n    \n    print(\"  Calculating engineered IMU derivatives (jerk, angular velocity)...\")\n    df['acc_mag_jerk'] = df.groupby('sequence_id')['acc_mag'].diff().fillna(0)\n    df['rot_angle_vel'] = df.groupby('sequence_id')['rot_angle'].diff().fillna(0)\n\n    meta_cols = {'gesture', 'gesture_int', 'sequence_type', 'behavior', 'orientation',\n                 'row_id', 'subject', 'phase', 'sequence_id', 'sequence_counter'}\n\n    imu_cols = [c for c in df.columns if c.startswith('acc_') and c not in ['acc_mag', 'acc_mag_jerk']]\n    imu_cols.extend([c for c in df.columns if c.startswith('rot_') and c not in ['rot_angle', 'rot_angle_vel']])\n    imu_cols.extend(['acc_mag', 'rot_angle', 'acc_mag_jerk', 'rot_angle_vel'])\n\n    thm_cols_original = [c for c in df.columns if c.startswith('thm_')]\n    \n    tof_aggregated_cols_template = []\n    for i in range(1, 6):\n        tof_aggregated_cols_template.extend([f'tof_{i}_mean', f'tof_{i}_std', f'tof_{i}_min', f'tof_{i}_max'])\n\n    final_feature_cols = imu_cols + thm_cols_original + tof_aggregated_cols_template\n    imu_dim_final = len(imu_cols)\n    tof_thm_aggregated_dim_final = len(thm_cols_original) + len(tof_aggregated_cols_template)\n    \n    print(f\"  IMU (incl. engineered & derivatives) {imu_dim_final} | THM + Aggregated TOF {tof_thm_aggregated_dim_final} | total {len(final_feature_cols)} features\")\n    np.save(EXPORT_DIR / \"feature_cols.npy\", np.array(final_feature_cols))\n\n    print(\"  Building sequences with aggregated TOF and preparing data for scaler...\")\n    seq_gp = df.groupby('sequence_id') \n    \n    all_steps_for_scaler_list = []\n    X_list_unscaled, y_list_int_for_stratify, lens = [], [], [] \n\n    for seq_id, seq_df_orig in seq_gp:\n        seq_df = seq_df_orig.copy()\n\n        for i in range(1, 6):\n            pixel_cols_tof = [f\"tof_{i}_v{p}\" for p in range(64)]\n            tof_sensor_data = seq_df[pixel_cols_tof].replace(-1, np.nan)\n            seq_df[f'tof_{i}_mean'] = tof_sensor_data.mean(axis=1)\n            seq_df[f'tof_{i}_std']  = tof_sensor_data.std(axis=1)\n            seq_df[f'tof_{i}_min']  = tof_sensor_data.min(axis=1)\n            seq_df[f'tof_{i}_max']  = tof_sensor_data.max(axis=1)\n        \n        mat_unscaled = seq_df[final_feature_cols].ffill().bfill().fillna(0).values.astype('float32')\n        \n        all_steps_for_scaler_list.append(mat_unscaled)\n        X_list_unscaled.append(mat_unscaled)\n        y_list_int_for_stratify.append(seq_df['gesture_int'].iloc[0])\n        lens.append(len(mat_unscaled))\n\n    print(\"  Fitting StandardScaler...\")\n    all_steps_concatenated = np.concatenate(all_steps_for_scaler_list, axis=0)\n    scaler = StandardScaler().fit(all_steps_concatenated)\n    joblib.dump(scaler, EXPORT_DIR / \"scaler.pkl\")\n    del all_steps_for_scaler_list, all_steps_concatenated\n\n    print(\"  Scaling and padding sequences...\")\n    X_scaled_list = [scaler.transform(x_seq) for x_seq in X_list_unscaled]\n    del X_list_unscaled\n\n    pad_len = int(np.percentile(lens, PAD_PERCENTILE))\n    np.save(EXPORT_DIR / \"sequence_maxlen.npy\", pad_len)\n    X = pad_sequences_2d(X_scaled_list, maxlen=pad_len, padding='post', truncating='post', dtype='float32')\n    del X_scaled_list\n\n    y_int_for_stratify = np.array(y_list_int_for_stratify)\n    y = to_categorical(y_int_for_stratify, num_classes=len(le.classes_))\n\n    print(\"  Splitting data and preparing for training...\")\n    X_tr, X_val, y_tr, y_val = train_test_split(X, y, test_size=0.1, random_state=82, stratify=y_int_for_stratify)\n    X_tr, X_val = np.array(X_tr), np.array(X_val)\n    \n    cw_vals = compute_class_weight('balanced', classes=np.arange(len(le.classes_)), y=y_int_for_stratify)\n    class_weight = dict(enumerate(cw_vals))\n\n    with open(\"prepared_data.pkl\", \"wb\") as f:\n        pickle.dump((X_tr, X_val, y_tr, y_val, class_weight, le, scaler, final_feature_cols), f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T23:00:01.213382Z","iopub.execute_input":"2025-06-17T23:00:01.213692Z","iopub.status.idle":"2025-06-17T23:00:01.232219Z","shell.execute_reply.started":"2025-06-17T23:00:01.21367Z","shell.execute_reply":"2025-06-17T23:00:01.23125Z"}},"outputs":[],"execution_count":9},{"cell_type":"code","source":"config = {}\n\n# Hyperarameters of the reservoir\nconfig['n_internal_units'] = 600        # size of the reservoir\nconfig['spectral_radius'] = 0.90        # largest eigenvalue of the reservoir\nconfig['leak'] = 0.9                    # amount of leakage in the reservoir state update (None or 1.0 --> no leakage)\nconfig['connectivity'] = 0.8            # percentage of nonzero connections in the reservoir\nconfig['input_scaling'] = 0.4           # scaling of the input weights\nconfig['noise_level'] = 0.01            # noise in the reservoir state update\nconfig['n_drop'] = 10                    # transient states to be dropped\nconfig['bidir'] = True                  # if True, use bidirectional reservoir\nconfig['circle'] = False                # use reservoir with circle topology\n\n# Dimensionality reduction hyperparameters\nconfig['dimred_method'] = None      # options: {None (no dimensionality reduction), 'pca', 'tenpca'}\nconfig['n_dim'] = 75                    # number of resulting dimensions after the dimensionality reduction procedure\n\n# Type of MTS representation\nconfig['mts_rep'] = 'mean'         # MTS representation:  {'last', 'mean', 'output', 'reservoir'}\nconfig['w_ridge_embedding'] = 1.00   \nconfig['readout_type'] = 'svm'          # readout used for classification\nconfig['svm_gamma'] = 5e-3              # bandwith of the RBF kernel\nconfig['svm_C'] = 10                  # regularization for SVM hyperplane","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T23:59:22.484525Z","iopub.execute_input":"2025-06-17T23:59:22.484878Z","iopub.status.idle":"2025-06-17T23:59:22.491006Z","shell.execute_reply.started":"2025-06-17T23:59:22.484854Z","shell.execute_reply":"2025-06-17T23:59:22.490058Z"}},"outputs":[],"execution_count":53},{"cell_type":"code","source":"config = {}\n\n# Hyperarameters of the reservoir\nconfig['n_internal_units'] = 600        # size of the reservoir\nconfig['spectral_radius'] = 0.90        # largest eigenvalue of the reservoir\nconfig['leak'] = 0.9                    # amount of leakage in the reservoir state update (None or 1.0 --> no leakage)\nconfig['connectivity'] = 0.8            # percentage of nonzero connections in the reservoir\nconfig['input_scaling'] = 0.4           # scaling of the input weights\nconfig['noise_level'] = 0.01            # noise in the reservoir state update\nconfig['n_drop'] = 10                    # transient states to be dropped\nconfig['bidir'] = True                  # if True, use bidirectional reservoir\nconfig['circle'] = False                # use reservoir with circle topology\n\n# Dimensionality reduction hyperparameters\nconfig['dimred_method'] = 'pca'      # options: {None (no dimensionality reduction), 'pca', 'tenpca'}\nconfig['n_dim'] = 150                    # number of resulting dimensions after the dimensionality reduction procedure\n\n# Type of MTS representation\nconfig['mts_rep'] = 'reservoir'         # MTS representation:  {'last', 'mean', 'output', 'reservoir'}\nconfig['w_ridge_embedding'] = 1.00   \nconfig['readout_type'] = 'svm'          # readout used for classification\nconfig['svm_gamma'] = 5e-3              # bandwith of the RBF kernel\nconfig['svm_C'] = 10                    # regularization for SVM hyperplane","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T00:56:06.717735Z","iopub.execute_input":"2025-06-18T00:56:06.718151Z","iopub.status.idle":"2025-06-18T00:56:06.726869Z","shell.execute_reply.started":"2025-06-18T00:56:06.718125Z","shell.execute_reply":"2025-06-18T00:56:06.725472Z"}},"outputs":[],"execution_count":64},{"cell_type":"code","source":"FULL = 1\nif FULL:\n    clf =  RC_model(**config)\n       \n    clf.fit(X_tr, y_tr) \n    \n    y_hat = clf.predict(X_val)\n    \n    true_val_int  = y_val.argmax(1)\n    \n    h_f1 = CompetitionMetric().calculate_hierarchical_f1(\n        pd.DataFrame({'gesture': le.classes_[true_val_int]}),\n        pd.DataFrame({'gesture': le.classes_[y_hat]}),\n        verbose = True\n    )\n    print(\"Hold‑out H‑F1 =\", round(h_f1, 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T00:56:06.92972Z","iopub.execute_input":"2025-06-18T00:56:06.930095Z","iopub.status.idle":"2025-06-18T01:09:40.453023Z","shell.execute_reply.started":"2025-06-18T00:56:06.930073Z","shell.execute_reply":"2025-06-18T01:09:40.451788Z"}},"outputs":[{"name":"stdout","text":"Training completed in 11.62 min\nf1_binary: 0.8206686930091185, f1_macro: 0.3364730416880831\nHold‑out H‑F1 = 0.5786\n","output_type":"stream"}],"execution_count":65},{"cell_type":"code","source":"y_tr_bin = [le.classes_[y_.argmax()] in CompetitionMetric().target_gestures for y_ in y_tr]\n\n\nBIN = 0\nif BIN:\n        \n    from sklearn.pipeline import Pipeline\n    from sklearn.preprocessing import StandardScaler\n    from sklearn.linear_model import LogisticRegression\n    \n    clf_bin =  RC_model(**config)\n    \n    #LR_cls = wrap_with_one_hot_encoding(LogisticRegression, 2)\n    #clf_bin.readout = Pipeline([\n    #    ('scaler', StandardScaler()),\n    #    ('lr',     LR_cls(class_weight=\"balanced\", max_iter=250, solver=\"newton-cholesky\"))\n    #])\n\n    # 2) fit on the raw reservoir states\n    clf_bin.fit(X_tr, y_tr_bin)\n\n    # 3) predict (pipeline will scale internally)\n    y_hat = clf_bin.predict(X_val)\n\n    # 4) compute F1 as before\n    true_val_int = y_val.argmax(1)\n    pred_labels  = 15 * (1 - y_hat)\n    h_f1 = CompetitionMetric().calculate_hierarchical_f1(\n        pd.DataFrame({'gesture': le.classes_[true_val_int]}),\n        pd.DataFrame({'gesture': le.classes_[pred_labels]}),\n        verbose=True\n    )\n    print(\"Hold-out H-F1 =\", round(h_f1, 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T23:24:23.137833Z","iopub.execute_input":"2025-06-17T23:24:23.138125Z","iopub.status.idle":"2025-06-17T23:24:23.156091Z","shell.execute_reply.started":"2025-06-17T23:24:23.138105Z","shell.execute_reply":"2025-06-17T23:24:23.15518Z"}},"outputs":[],"execution_count":25},{"cell_type":"code","source":"TARGET_ONLY = 1\nif TARGET_ONLY:\n    target_gesture_idxs = [0, 1, 3, 4, 6, 7, 9, 10]\n\n    clf_tgto =  RC_model(**config)\n   \n    clf_tgto.fit(X_tr[y_tr_bin], y_tr[y_tr_bin][:, target_gesture_idxs]) \n\n    mask = [True if le.classes_[y_] in CompetitionMetric().target_gestures else False for y_ in y_val.argmax(1)]\n    true_val_int  = y_val.argmax(1)\n\n    if FULL:\n        baseline_y_hat = clf.predict(X_val)\n        h_f1 = CompetitionMetric().calculate_hierarchical_f1(\n            pd.DataFrame({'gesture': le.classes_[true_val_int][mask]}),\n            pd.DataFrame({'gesture': le.classes_[baseline_y_hat][mask]}),\n            verbose = True\n        )\n        print(\"Hold‑out H‑F1 (BASELINE) =\", round(h_f1, 4))\n    y_hat = [target_gesture_idxs[y_] for y_ in clf_tgto.predict(X_val)]\n    \n    \n    h_f1 = CompetitionMetric().calculate_hierarchical_f1(\n        pd.DataFrame({'gesture': le.classes_[true_val_int][mask]}),\n        pd.DataFrame({'gesture': le.classes_[y_hat][mask]}),\n        verbose = True\n    )\n    print(\"Hold‑out H‑F1 =\", round(h_f1, 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T23:24:23.15727Z","iopub.execute_input":"2025-06-17T23:24:23.157546Z","iopub.status.idle":"2025-06-17T23:25:42.761292Z","shell.execute_reply.started":"2025-06-17T23:24:23.157527Z","shell.execute_reply":"2025-06-17T23:25:42.760349Z"}},"outputs":[{"name":"stdout","text":"Training completed in 0.88 min\nf1_binary: 0.9821073558648111, f1_macro: 0.4703280602972401\nHold‑out H‑F1 (BASELINE) = 0.7262\nf1_binary: 1.0, f1_macro: 0.5441045604303962\nHold‑out H‑F1 = 0.7721\n","output_type":"stream"}],"execution_count":26},{"cell_type":"code","source":"assert False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T03:41:40.246073Z","iopub.execute_input":"2025-06-17T03:41:40.246394Z","iopub.status.idle":"2025-06-17T03:41:40.400149Z","shell.execute_reply.started":"2025-06-17T03:41:40.246366Z","shell.execute_reply":"2025-06-17T03:41:40.398968Z"}},"outputs":[{"traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mAssertionError\u001b[0m                            Traceback (most recent call last)","\u001b[0;32m/tmp/ipykernel_366/2103537015.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m","\u001b[0;31mAssertionError\u001b[0m: "],"ename":"AssertionError","evalue":"","output_type":"error"}],"execution_count":13},{"cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    df_seq = sequence.to_pandas()\n\n    df_seq['acc_mag'] = np.sqrt(df_seq['acc_x']**2 + df_seq['acc_y']**2 + df_seq['acc_z']**2)\n    df_seq['rot_angle'] = 2 * np.arccos(df_seq['rot_w'].clip(-1, 1))\n    df_seq['acc_mag_jerk'] = df_seq['acc_mag'].diff().fillna(0)\n    df_seq['rot_angle_vel'] = df_seq['rot_angle'].diff().fillna(0)\n    \n    for i in range(1, 6): \n        pixel_cols_tof = [f\"tof_{i}_v{p}\" for p in range(64)]\n        tof_sensor_data = df_seq[pixel_cols_tof].replace(-1, np.nan)\n        \n        df_seq[f'tof_{i}_mean'] = tof_sensor_data.mean(axis=1)\n        df_seq[f'tof_{i}_std']  = tof_sensor_data.std(axis=1)\n        df_seq[f'tof_{i}_min']  = tof_sensor_data.min(axis=1)\n        df_seq[f'tof_{i}_max']  = tof_sensor_data.max(axis=1)\n        \n    df_seq_reordered = pd.DataFrame(columns=final_feature_cols)\n    for col in final_feature_cols:\n        if col in df_seq.columns:\n            df_seq_reordered[col] = df_seq[col]\n\n    mat_unscaled = df_seq_reordered.ffill().bfill().fillna(0).values.astype('float32')\n    \n    mat_scaled = scaler.transform(mat_unscaled)\n    \n    pad_input = pad_sequences_2d([mat_scaled], maxlen=pad_len, padding='post', truncating='post', dtype='float32')\n\n    idx = clf.predict(pad_input)\n    \n    try:\n        n = len(idx)\n        if n <= 1:\n            return str(gesture_classes[idx][0])\n    except:\n        pass\n    return str(gesture_classes[idx])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T03:41:40.400843Z","iopub.status.idle":"2025-06-17T03:41:40.401115Z","shell.execute_reply.started":"2025-06-17T03:41:40.400991Z","shell.execute_reply":"2025-06-17T03:41:40.401002Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"predict(pl.from_pandas(df.head()), None)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T03:41:40.402859Z","iopub.status.idle":"2025-06-17T03:41:40.403177Z","shell.execute_reply.started":"2025-06-17T03:41:40.403032Z","shell.execute_reply":"2025-06-17T03:41:40.403049Z"}},"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/cmi-detect-behavior-with-sensor-data/test.csv',\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv',\n        )\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T03:41:40.404305Z","iopub.status.idle":"2025-06-17T03:41:40.404612Z","shell.execute_reply.started":"2025-06-17T03:41:40.404477Z","shell.execute_reply":"2025-06-17T03:41:40.40449Z"}},"outputs":[],"execution_count":null}]}