{"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":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12081169,"sourceType":"datasetVersion","datasetId":7602951}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"%%capture\n# Set base directory (note: shell variable may won't work directly in `!pip install` in notebooks when in submission)\nBASE=\"/kaggle/input/pytorch-forecasting-env/wheels\"\n\n!pip install --no-index --no-deps \\\n  $BASE/lightning-2.5.1.post0-py3-none-any.whl \\\n  $BASE/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl \\\n  $BASE/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl \\\n  $BASE/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl \\\n  $BASE/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl \\\n  $BASE/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl \\\n  $BASE/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl \\\n  $BASE/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl \\\n  $BASE/packaging-24.2-py3-none-any.whl \\\n  $BASE/pytorch_forecasting-1.3.0-py3-none-any.whl \\\n  $BASE/pytorch_optimizer-3.6.0-py3-none-any.whl","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:34:40.580679Z","iopub.execute_input":"2025-06-13T13:34:40.580903Z","iopub.status.idle":"2025-06-13T13:35:38.739099Z","shell.execute_reply.started":"2025-06-13T13:34:40.580872Z","shell.execute_reply":"2025-06-13T13:35:38.738301Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"\n\n### **✅ This notebook demonstrates how a complex Transformer-based model can effectively tackle a classification task.**<br>\n### **📊 The model is trained only on IMU data, so the full dataset (100%) was not utilized during training.**<br>\n### **🚀 I aimed to submit this approach for the competition, but couldn’t proceed due to other priorities.**<br>\n### **🌱 I’d be excited to see someone build on this work and take this approach to victory!**<br>\n","metadata":{}},{"cell_type":"markdown","source":"# Libraries","metadata":{}},{"cell_type":"code","source":"# Standard libraries\nimport os\nimport pickle\nimport warnings\nfrom tqdm import tqdm\n\n# Scientific and data libraries\nimport numpy as np\nimport pandas as pd\nimport torch\n\n# Scikit-learn\nfrom sklearn.metrics import f1_score\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, confusion_matrix, ConfusionMatrixDisplay\n\n# Polars\nimport polars as pl\n\n# PyTorch Forecasting\nfrom pytorch_forecasting import Baseline, TemporalFusionTransformer, TimeSeriesDataSet\nfrom pytorch_forecasting.data import GroupNormalizer, NaNLabelEncoder\nfrom pytorch_forecasting.metrics import (\n    CrossEntropy,\n    MAE,\n    PoissonLoss,\n    QuantileLoss,\n    SMAPE,\n)\nfrom pytorch_forecasting.models.temporal_fusion_transformer.tuning import (\n    optimize_hyperparameters,\n)\n\n# PyTorch Lightning / Lightning\nimport lightning.pytorch as pyl\nfrom lightning.pytorch import Trainer\nfrom lightning.pytorch.callbacks import EarlyStopping, LearningRateMonitor, ModelCheckpoint\nfrom lightning.pytorch.loggers import CSVLogger\nfrom pytorch_lightning import Trainer as PLTrainer  # if needed separately\nfrom pytorch_lightning.callbacks import EarlyStopping as PLEarlyStopping\nfrom pytorch_lightning.callbacks import LearningRateMonitor as PLLearningRateMonitor\nfrom pytorch_lightning.callbacks import ModelCheckpoint as PLModelCheckpoint\nfrom pytorch_lightning.loggers import TensorBoardLogger\n\n# Ignore warnings\nwarnings.filterwarnings(\"ignore\")\ntorch.use_deterministic_algorithms(False)\n\nimport os\nimport sys\nfrom absl import logging\n\nos.environ[\"TF_CPP_MIN_LOG_LEVEL\"] = \"3\"\nos.environ[\"XLA_FLAGS\"] = \"--xla_gpu_cuda_data_dir=/dev/null\"\n\nlogging.set_verbosity(logging.ERROR)\n\n# Optionally suppress all stderr\n# sys.stderr = open(os.devnull, 'w')\n\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:38:44.370549Z","iopub.execute_input":"2025-06-13T13:38:44.371279Z","iopub.status.idle":"2025-06-13T13:38:44.377965Z","shell.execute_reply.started":"2025-06-13T13:38:44.371251Z","shell.execute_reply":"2025-06-13T13:38:44.377398Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# CONFIGURATION","metadata":{}},{"cell_type":"code","source":"# Detect if running in submission mode\nIS_KAGGLE_SUBMISSION = os.getenv(\"KAGGLE_KERNEL_RUN_TYPE\") == \"Batch\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:38:50.027923Z","iopub.execute_input":"2025-06-13T13:38:50.028212Z","iopub.status.idle":"2025-06-13T13:38:50.031015Z","shell.execute_reply.started":"2025-06-13T13:38:50.028189Z","shell.execute_reply":"2025-06-13T13:38:50.030458Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CFG:\n    # Set this globally or pass during instantiation if dynamic\n    IS_KAGGLE_SUBMISSION = os.getenv(\"KAGGLE_KERNEL_RUN_TYPE\") == \"Batch\"\n\n    # Mode and basic run controls\n    mode = 'train'\n    dry_run = 2 if not IS_KAGGLE_SUBMISSION else 0\n    skip_frame = 1\n    limit_frame = 70\n    n_class = 18\n    batch_size = 16\n\n    # Temporal Fusion Transformer (TFT) hyperparameters\n    tft_hparams = {\n        \"learning_rate\": 1e-2,\n        \"hidden_size\": 128,\n        \"lstm_layers\": 2,\n        \"attention_head_size\": 4,\n        \"dropout\": 0.1,\n        \"hidden_continuous_size\": 32,\n        \"reduce_on_plateau_patience\": 8,\n        \"optimizer\": \"ranger\",\n    }\n\n    trainer = {\n        \"max_epochs\": 2 if not IS_KAGGLE_SUBMISSION else 400,\n        \"gradient_clip_val\": 0.1\n    }\n\n    target = \"gesture\"\n    \n    # Static features (do not change over time for each sequence)\n    static_categoricals = [\"subject\", \"sex\", \"handedness\", \"adult_child\"]\n    static_reals = [\"age\", \"height_cm\", \"shoulder_to_wrist_cm\", \"elbow_to_wrist_cm\"]\n\n    # Known time-varying features (available ahead of prediction)\n    time_varying_known_reals = [\"sequence_counter\"]\n\n    # Unknown time-varying features (used for learning/prediction)\n    time_varying_unknown_reals = [\n        \"acc_x\", \"acc_y\", \"acc_z\",         # Accelerometer\n        \"rot_w\", \"rot_x\", \"rot_y\", \"rot_z\" # Rotation/quaternion data\n    ]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:38:50.103855Z","iopub.execute_input":"2025-06-13T13:38:50.104047Z","iopub.status.idle":"2025-06-13T13:38:50.108871Z","shell.execute_reply.started":"2025-06-13T13:38:50.104033Z","shell.execute_reply":"2025-06-13T13:38:50.108422Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Gathering ","metadata":{}},{"cell_type":"code","source":"train_df = pl.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv\")\nsubject_df = pl.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:38:50.268345Z","iopub.execute_input":"2025-06-13T13:38:50.268934Z","iopub.status.idle":"2025-06-13T13:38:54.278751Z","shell.execute_reply.started":"2025-06-13T13:38:50.268915Z","shell.execute_reply":"2025-06-13T13:38:54.278284Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Preprocessing","metadata":{}},{"cell_type":"markdown","source":"## Train Test Split\n\n","metadata":{}},{"cell_type":"code","source":"if CFG.mode == 'train':\n    subject_train, subject_test = train_test_split(subject_df, test_size=0.2, random_state=43, shuffle=False)\n    n = CFG.dry_run if CFG.dry_run > 0 else None\n    X_train = train_df.join(subject_train[:n], on='subject', how='inner')\n    X_test = train_df.join(subject_test[:n], on='subject', how='inner')\nelse:\n    X_train = train_df.join(subject_train, on='subject', how='inner')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:38:54.279739Z","iopub.execute_input":"2025-06-13T13:38:54.279954Z","iopub.status.idle":"2025-06-13T13:38:54.701666Z","shell.execute_reply.started":"2025-06-13T13:38:54.279937Z","shell.execute_reply":"2025-06-13T13:38:54.701021Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Preprocessing","metadata":{}},{"cell_type":"code","source":"def generate_filtered_joined_df(\n    df: pl.DataFrame,\n    group_col: str = \"sequence_id\",\n    counter_col: str = \"sequence_counter\",\n    skip_frame: int = 1,\n    limit_frame: int = 50\n) -> pl.DataFrame:\n\n    # 1. Compute max counter per group\n    max_counter_df = df.group_by(group_col).agg(\n        pl.col(counter_col).max().alias(\"max_counter\")\n    )\n\n    # 2. Generate countdown lists per max_counter\n    def pick_useful_counter(x):\n        step = skip_frame if skip_frame > 0 else 1\n        frames = list(range(x, 0, -step))[:limit_frame-1]\n        # Pad first step twice\n        frames.insert(0, frames[0])        \n        # Pad list if shorter than limit_frame\n        while len(frames) < limit_frame:\n            frames.append(frames[-1])\n        return frames\n\n    max_counter_df = max_counter_df.with_columns(\n        pl.col(\"max_counter\")\n        .map_elements(pick_useful_counter, return_dtype=pl.List(pl.Int64))\n        .alias(\"filtered_counters\")\n    )\n\n    # 3. Explode counters into rows for join\n    exploded_counters = max_counter_df.explode(\"filtered_counters\").select([\n        pl.col(group_col),\n        pl.col(\"filtered_counters\").alias(counter_col)\n    ])\n\n    # 4. Join original df with exploded counters on group and counter\n    joined_df = df.join(exploded_counters, on=[group_col, counter_col], how=\"inner\")\n\n    # 5. Sort and reset sequence_counter numbering from 1 per group\n    result_df = (\n        joined_df.sort([group_col, counter_col])\n                 .with_columns(\n                    (pl.arange(0, pl.len()).over(group_col) + 1).alias(counter_col)\n                 )\n    )\n\n    return result_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:38:54.702730Z","iopub.execute_input":"2025-06-13T13:38:54.703029Z","iopub.status.idle":"2025-06-13T13:38:54.715604Z","shell.execute_reply.started":"2025-06-13T13:38:54.703004Z","shell.execute_reply":"2025-06-13T13:38:54.715035Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_gesture_df(df):\n    categorical_cols = [CFG.target] + CFG.static_categoricals\n\n    df = df.bfill().ffill()\n    for col in categorical_cols:\n        if col not in df.columns:\n            df[col] = np.nan\n        df[col] = df[col].astype(str).astype('category')\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:38:54.717146Z","iopub.execute_input":"2025-06-13T13:38:54.717387Z","iopub.status.idle":"2025-06-13T13:38:54.731778Z","shell.execute_reply.started":"2025-06-13T13:38:54.717361Z","shell.execute_reply":"2025-06-13T13:38:54.731203Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Preprocess Pipeline","metadata":{}},{"cell_type":"code","source":"def data_pipeline(df, skip_frame, limit_frame, preprocess=True):\n\n    # Step 1: Filter and join frames\n    df = generate_filtered_joined_df(\n        df,\n        skip_frame=skip_frame,\n        limit_frame=limit_frame\n    ).to_pandas()\n\n    # Step 2: preprocessing\n    df = preprocess_gesture_df(df)\n\n    return df\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:38:54.732400Z","iopub.execute_input":"2025-06-13T13:38:54.732614Z","iopub.status.idle":"2025-06-13T13:38:54.746214Z","shell.execute_reply.started":"2025-06-13T13:38:54.732599Z","shell.execute_reply":"2025-06-13T13:38:54.745709Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Fetch config values\nskip_frame = CFG.skip_frame\nlimit_frame = CFG.limit_frame\n\nX_train_1 = data_pipeline(X_train, skip_frame, limit_frame, preprocess=True)\n\nif CFG.mode == 'train':\n    X_test_1 = data_pipeline(X_test, skip_frame, limit_frame, preprocess=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:38:54.746963Z","iopub.execute_input":"2025-06-13T13:38:54.747220Z","iopub.status.idle":"2025-06-13T13:38:55.177756Z","shell.execute_reply.started":"2025-06-13T13:38:54.747195Z","shell.execute_reply":"2025-06-13T13:38:55.177276Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training","metadata":{}},{"cell_type":"code","source":"categorical_encoders = {}\nfor col in CFG.static_categoricals:\n    categorical_encoders[col] = NaNLabelEncoder(add_nan=True)\n\ntraining = TimeSeriesDataSet(\n    X_train_1,\n    time_idx=\"sequence_counter\",               # Must be correctly incrementing time index\n    target=CFG.target,                          # Your classification label\n    group_ids=[\"sequence_id\"],                 # Grouping by sequence\n\n    # Encoder: full sequence before prediction\n    min_encoder_length=CFG.limit_frame-1,\n    max_encoder_length=CFG.limit_frame-1,\n\n    # Decoder: classification is one-step-ahead\n    min_prediction_length=1,\n    max_prediction_length=1,\n\n    # Feature configs\n    static_categoricals=CFG.static_categoricals,\n    static_reals=CFG.static_reals,\n    time_varying_known_reals=CFG.time_varying_known_reals,\n    time_varying_unknown_reals=CFG.time_varying_unknown_reals,\n\n    # Classification-specific normalization\n    target_normalizer=NaNLabelEncoder(add_nan=True),\n\n    # Encode group id\n    categorical_encoders=categorical_encoders,\n\n    # Optional: useful features\n    add_relative_time_idx=True,\n    add_target_scales=False,        # Only needed for regression\n    add_encoder_length=True,        # Helps model learn variable-lengths, if applicable\n\n    # Optimization\n    allow_missing_timesteps=True   # Safer for sequence classification\n)\n\n# create dataloaders for model\ntrain_dataloader = training.to_dataloader(\n    train=True, batch_size=CFG.batch_size, num_workers=3\n)\n\nif CFG.mode == 'train':\n    validation = TimeSeriesDataSet.from_dataset(\n        training, X_test_1, predict=True, stop_randomization=True\n    )\n    \n    val_dataloader = validation.to_dataloader(\n        train=False, batch_size=CFG.batch_size * 10, num_workers=3\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:38:55.178529Z","iopub.execute_input":"2025-06-13T13:38:55.178794Z","iopub.status.idle":"2025-06-13T13:38:55.501548Z","shell.execute_reply.started":"2025-06-13T13:38:55.178766Z","shell.execute_reply":"2025-06-13T13:38:55.501075Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tft = TemporalFusionTransformer.from_dataset(\n    training,\n    learning_rate=CFG.tft_hparams['learning_rate'],\n    hidden_size=CFG.tft_hparams['hidden_size'],\n    lstm_layers=CFG.tft_hparams['lstm_layers'],\n    attention_head_size=CFG.tft_hparams['attention_head_size'],\n    dropout=CFG.tft_hparams['dropout'],\n    hidden_continuous_size=CFG.tft_hparams['hidden_continuous_size'],\n    loss=CrossEntropy(),\n    output_size=CFG.n_class + 1,\n    optimizer=CFG.tft_hparams['optimizer'],\n    share_single_variable_networks=True,\n    causal_attention=False,\n    reduce_on_plateau_patience = CFG.tft_hparams['reduce_on_plateau_patience']\n)\n\n\n# Callbacks\nearly_stop_callback = EarlyStopping(\n    monitor=\"val_loss\", min_delta=1e-4, patience=16, verbose=True, mode=\"min\"\n)\n\ncheckpoint_callback = ModelCheckpoint(\n    monitor=\"val_loss\",\n    save_top_k=1,\n    every_n_epochs=1,\n    dirpath=\"checkpoints/\",\n    filename=\"tft-epoch-{epoch:02d}-val_loss-{val_loss:.4f}\",\n)\n\n# Logger\ncsv_logger = CSVLogger(\"logs/\", name=\"tft\")\n\n# Trainer\ntrainer = Trainer(\n    max_epochs=CFG.trainer['max_epochs'],\n    accelerator=\"auto\",              # uses GPU if available\n    devices=\"auto\",                  # picks number of devices automatically\n    enable_model_summary=True,\n    gradient_clip_val=CFG.trainer['gradient_clip_val'],\n    callbacks=[checkpoint_callback, early_stop_callback],\n    logger=csv_logger,              # pass actual logger object\n    log_every_n_steps=1,            # ensures logging after every step (helpful for debugging)\n    check_val_every_n_epoch=1,      # run validation every epoch\n    enable_checkpointing=True,      # make sure checkpointing is active\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:38:55.502241Z","iopub.execute_input":"2025-06-13T13:38:55.502427Z","iopub.status.idle":"2025-06-13T13:38:55.633213Z","shell.execute_reply.started":"2025-06-13T13:38:55.502412Z","shell.execute_reply":"2025-06-13T13:38:55.632822Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if CFG.mode == 'train':\n    trainer.fit(\n        tft,\n        train_dataloaders=train_dataloader,\n        val_dataloaders=val_dataloader,\n    )\nelse:\n    trainer.fit(\n        tft,\n        train_dataloaders=train_dataloader,\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:38:55.633819Z","iopub.execute_input":"2025-06-13T13:38:55.634017Z","iopub.status.idle":"2025-06-13T13:39:05.292865Z","shell.execute_reply.started":"2025-06-13T13:38:55.634001Z","shell.execute_reply":"2025-06-13T13:39:05.292010Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Storing Artifacts","metadata":{}},{"cell_type":"code","source":"# Store it in a file\nwith open('training_TimeSeriesDataset.pkl', 'wb') as f:\n    pickle.dump(training, f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:39:05.295871Z","iopub.execute_input":"2025-06-13T13:39:05.296288Z","iopub.status.idle":"2025-06-13T13:39:05.305571Z","shell.execute_reply.started":"2025-06-13T13:39:05.296257Z","shell.execute_reply":"2025-06-13T13:39:05.305074Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model Evaluation","metadata":{}},{"cell_type":"code","source":"class 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    ) -> 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\n        return 0.5 * f1_binary + 0.5 * f1_macro","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:39:05.306210Z","iopub.execute_input":"2025-06-13T13:39:05.306474Z","iopub.status.idle":"2025-06-13T13:39:05.368222Z","shell.execute_reply.started":"2025-06-13T13:39:05.306433Z","shell.execute_reply":"2025-06-13T13:39:05.367567Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"This code does point forecast as in completion it is asked so and it is indeed slow...","metadata":{}},{"cell_type":"code","source":"if CFG.mode == 'train':\n    \n    solution = X_test_1[['sequence_id','gesture']].drop_duplicates()\n    seq_to_gesture = X_test_1.drop_duplicates('sequence_id').set_index('sequence_id')['gesture'].to_dict()\n    reverse_map = {v: k for k, v in training.get_parameters()[\"target_normalizer\"].__dict__[\"classes_\"].items()}\n\n    results = []\n\n    # Wrap the iterator with tqdm for progress bar\n    for seq in list(seq_to_gesture.keys()):\n        pred = tft.predict(\n            validation.filter(lambda x: x.sequence_id == seq),\n            trainer_kwargs=dict(accelerator=\"auto\"),\n            mode=\"quantiles\"\n        )\n        \n        # Get predicted label index (assuming classification output)\n        label_idx = int(pred[0][0][1:].argmax() + 1)\n        \n        # Convert to gesture name\n        gesture = reverse_map[label_idx]\n        \n        # Store in results list\n        results.append({\"sequence_id\": seq, \"gesture\": gesture})\n    \n    # Convert to DataFrame\n    submission = pd.DataFrame(results)\n\n    solution.to_csv(\"y_true.csv\")\n    submission.to_csv(\"y_pred.csv\")\n\n    cm = CompetitionMetric()\n    print(cm.calculate_hierarchical_f1(solution, submission))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:39:05.369499Z","iopub.execute_input":"2025-06-13T13:39:05.370012Z","iopub.status.idle":"2025-06-13T13:39:39.068872Z","shell.execute_reply.started":"2025-06-13T13:39:05.369982Z","shell.execute_reply":"2025-06-13T13:39:39.068156Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_true = list(solution['gesture'])\ny_pred = list(submission['gesture'])\n\n# Accuracy\nacc = accuracy_score(y_true, y_pred)\nprint(f\"Accuracy: {acc:.4f}\")\n\n# Confusion Matrix (as array)\ncm = confusion_matrix(y_true, y_pred)\nprint(\"Confusion Matrix:\\n\", cm)\n\n# Optional: Visualize confusion matrix\ndisp = ConfusionMatrixDisplay.from_predictions(y_true, y_pred)\n# Rotate x-axis tick labels by 90 degrees\ndisp.ax_.set_xticklabels(disp.display_labels, rotation=90)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T13:39:39.069790Z","iopub.execute_input":"2025-06-13T13:39:39.070111Z","iopub.status.idle":"2025-06-13T13:39:39.859589Z","shell.execute_reply.started":"2025-06-13T13:39:39.070067Z","shell.execute_reply":"2025-06-13T13:39:39.858893Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}