{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"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"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:23:21.823009Z","iopub.execute_input":"2025-12-17T11:23:21.823464Z","iopub.status.idle":"2025-12-17T11:23:21.843814Z","shell.execute_reply.started":"2025-12-17T11:23:21.823431Z","shell.execute_reply":"2025-12-17T11:23:21.842828Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset\n\nclass RotationWindowDataset(Dataset):\n    def __init__(\n        self,\n        df,\n        window_size=20,\n        stride=5,\n        feature_cols=(\"rot_w\", \"rot_x\", \"rot_y\", \"rot_z\"),\n        label_col=\"gesture\"\n    ):\n        self.samples = []\n\n        for seq_id, seq_df in df.groupby(\"sequence_id\"):\n            data = seq_df[feature_cols].values\n            labels = seq_df[label_col].values\n\n            L = len(seq_df)\n            for start in range(0, L - window_size + 1, stride):\n                end = start + window_size\n\n                x = data[start:end].T   # (4, T)\n                y = labels[start:end]\n\n                # Skip windows with NaNs\n                if np.isnan(x).any():\n                    continue\n\n                print(y[:5], y.dtype)\n\n                # Majority label (or center label)\n                target = np.bincount(y).argmax()\n\n                self.samples.append((\n                    torch.tensor(x, dtype=torch.float32),\n                    torch.tensor(target, dtype=torch.long)\n                ))\n\n    def __len__(self):\n        return len(self.samples)\n\n    def __getitem__(self, idx):\n        return self.samples[idx]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:23:21.845816Z","iopub.execute_input":"2025-12-17T11:23:21.846447Z","iopub.status.idle":"2025-12-17T11:23:21.856042Z","shell.execute_reply.started":"2025-12-17T11:23:21.846417Z","shell.execute_reply":"2025-12-17T11:23:21.854928Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:23:21.857047Z","iopub.execute_input":"2025-12-17T11:23:21.857336Z","iopub.status.idle":"2025-12-17T11:23:21.882465Z","shell.execute_reply.started":"2025-12-17T11:23:21.857315Z","shell.execute_reply":"2025-12-17T11:23:21.881409Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class AxisCNN(nn.Module):\n    def __init__(self, hidden_dim=32):\n        super().__init__()\n\n        self.net = nn.Sequential(\n            nn.Conv1d(1, hidden_dim, kernel_size=5, padding=2),\n            #nn.BatchNorm1d(hidden_dim),\n            nn.GroupNorm(4, hidden_dim),\n            nn.ReLU(),\n\n            nn.Conv1d(hidden_dim, hidden_dim, kernel_size=5, padding=2),\n            #nn.BatchNorm1d(hidden_dim),\n            nn.GroupNorm(4, hidden_dim),\n            nn.ReLU(),\n\n            nn.Conv1d(hidden_dim, hidden_dim, kernel_size=3, padding=1),\n            #nn.BatchNorm1d(hidden_dim),\n            nn.GroupNorm(4, hidden_dim),\n            nn.ReLU(),\n        )\n\n    def forward(self, x):\n        \"\"\"\n        x: (B, 1, T)\n        return: (B, hidden_dim)\n        \"\"\"\n        x = self.net(x)\n        x = x.mean(dim=-1)  # temporal pooling\n        return x\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:23:21.884266Z","iopub.execute_input":"2025-12-17T11:23:21.884581Z","iopub.status.idle":"2025-12-17T11:23:21.902334Z","shell.execute_reply.started":"2025-12-17T11:23:21.884546Z","shell.execute_reply":"2025-12-17T11:23:21.901244Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class RotationEncoder(nn.Module):\n    def __init__(\n        self,\n        axis_hidden_dim=32,\n        fusion_hidden_dim=128,\n        k_classes=12\n    ):\n        super().__init__()\n\n        # One CNN per axis\n        self.axis_cnns = nn.ModuleList([\n            AxisCNN(axis_hidden_dim) for _ in range(4)\n        ])\n\n        fusion_input_dim = 4 * axis_hidden_dim\n\n        # Late fusion MLP\n        self.fusion_mlp = nn.Sequential(\n            nn.Linear(fusion_input_dim, fusion_hidden_dim),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n\n            nn.Linear(fusion_hidden_dim, fusion_hidden_dim),\n            nn.ReLU(),\n        )\n\n        # k-class motion primitive head\n        self.motion_head = nn.Linear(fusion_hidden_dim, k_classes)\n\n    def forward(self, x):\n        \"\"\"\n        x: (B, 4, T)\n        returns:\n            logits: (B, k_classes)\n            embedding: (B, fusion_hidden_dim)\n        \"\"\"\n        axis_features = []\n\n        for i in range(4):\n            xi = x[:, i:i+1, :]      # (B, 1, T)\n            zi = self.axis_cnns[i](xi)  # (B, axis_hidden_dim)\n            axis_features.append(zi)\n\n        z = torch.cat(axis_features, dim=1)  # (B, 4 * axis_hidden_dim)\n\n        embedding = self.fusion_mlp(z)        # (B, fusion_hidden_dim)\n        logits = self.motion_head(embedding)  # (B, k_classes)\n\n        return logits, embedding","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:23:21.903284Z","iopub.execute_input":"2025-12-17T11:23:21.903582Z","iopub.status.idle":"2025-12-17T11:23:21.921588Z","shell.execute_reply.started":"2025-12-17T11:23:21.903555Z","shell.execute_reply":"2025-12-17T11:23:21.920395Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR = \"/kaggle/input/cmi-detect-behavior-with-sensor-data\"\n\ntrain_df = pd.read_csv(f\"{DATA_DIR}/train.csv\")\nprint(train_df.columns)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:23:21.922567Z","iopub.execute_input":"2025-12-17T11:23:21.922909Z","iopub.status.idle":"2025-12-17T11:23:45.726552Z","shell.execute_reply.started":"2025-12-17T11:23:21.922876Z","shell.execute_reply":"2025-12-17T11:23:45.725608Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import DataLoader\n\nlabels = train_df[\"gesture\"].unique()\nlabel2id = {label: i for i, label in enumerate(labels)}\nid2label = {i: label for label, i in label2id.items()}\n\ntrain_df[\"gesture_id\"] = train_df[\"gesture\"].map(label2id)\n\nfrom sklearn.model_selection import train_test_split\n\n# Unique sequences\nall_sequences = train_df[\"sequence_id\"].unique()\n\n# 80 / 20 split\ntrain_seqs, test_seqs = train_test_split(\n    all_sequences,\n    test_size=0.2,\n    random_state=42,\n    shuffle=True\n)\n\ntrain_df_split = train_df[train_df[\"sequence_id\"].isin(train_seqs)]\ntest_df_split  = train_df[train_df[\"sequence_id\"].isin(test_seqs)]\n\nprint(\"Train sequences:\", len(train_seqs))\nprint(\"Test sequences:\", len(test_seqs))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:23:45.727628Z","iopub.execute_input":"2025-12-17T11:23:45.727907Z","iopub.status.idle":"2025-12-17T11:23:46.661768Z","shell.execute_reply.started":"2025-12-17T11:23:45.727879Z","shell.execute_reply":"2025-12-17T11:23:46.660941Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#dataset = RotationWindowDataset(\n#    df=train_df,\n#    window_size=20,\n#    stride=5,\n#    feature_cols = [\"rot_w\", \"rot_x\", \"rot_y\", \"rot_z\"],\n#    label_col=\"gesture_id\"\n#)\n\n#loader = DataLoader(\n#    dataset,\n#    batch_size=32,\n#    shuffle=True,\n#    num_workers=2,\n#    pin_memory=True\n#)\n\ntrain_dataset = RotationWindowDataset(\n    df=train_df_split,\n    window_size=20,\n    stride=5,\n    feature_cols=[\"rot_w\", \"rot_x\", \"rot_y\", \"rot_z\"],\n    label_col=\"gesture_id\"\n)\n\ntest_dataset = RotationWindowDataset(\n    df=test_df_split,\n    window_size=20,\n    stride=5,\n    feature_cols=[\"rot_w\", \"rot_x\", \"rot_y\", \"rot_z\"],\n    label_col=\"gesture_id\"\n)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=32,\n    shuffle=True\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=32,\n    shuffle=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:23:46.662825Z","iopub.execute_input":"2025-12-17T11:23:46.663170Z","iopub.status.idle":"2025-12-17T11:24:03.012776Z","shell.execute_reply.started":"2025-12-17T11:23:46.663148Z","shell.execute_reply":"2025-12-17T11:24:03.011878Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#model = RotationEncoder()\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\nnum_classes = train_df[\"gesture_id\"].nunique()\n\nmodel = RotationEncoder(k_classes=num_classes).to(device)\n\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-3)\ncriterion = nn.CrossEntropyLoss()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:24:03.013837Z","iopub.execute_input":"2025-12-17T11:24:03.014180Z","iopub.status.idle":"2025-12-17T11:24:03.030524Z","shell.execute_reply.started":"2025-12-17T11:24:03.014156Z","shell.execute_reply":"2025-12-17T11:24:03.029604Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 5\n\nfor epoch in range(EPOCHS):\n    model.train()\n    total_loss = 0\n\n    for x, y in train_loader:\n        x = x.to(device)\n        y = y.to(device)\n\n        logits, _ = model(x)\n        loss = criterion(logits, y)\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        total_loss += loss.item()\n\n    avg_loss = total_loss / len(train_loader)\n    print(f\"Epoch {epoch+1}/{EPOCHS} - Train loss: {avg_loss:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:24:03.034018Z","iopub.execute_input":"2025-12-17T11:24:03.035030Z","iopub.status.idle":"2025-12-17T11:27:27.555560Z","shell.execute_reply.started":"2025-12-17T11:24:03.035000Z","shell.execute_reply":"2025-12-17T11:27:27.554337Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\n\nall_preds = []\nall_targets = []\n\nwith torch.no_grad():\n    for x, y in test_loader:\n        x = x.to(device)\n\n        logits, _ = model(x)\n        preds = torch.argmax(logits, dim=1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_targets.extend(y.numpy())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:27:27.556907Z","iopub.execute_input":"2025-12-17T11:27:27.557258Z","iopub.status.idle":"2025-12-17T11:27:30.387770Z","shell.execute_reply.started":"2025-12-17T11:27:27.557228Z","shell.execute_reply":"2025-12-17T11:27:30.386917Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(logits)\nprint(embedding)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:34:00.001507Z","iopub.execute_input":"2025-12-17T11:34:00.001888Z","iopub.status.idle":"2025-12-17T11:34:00.039478Z","shell.execute_reply.started":"2025-12-17T11:34:00.001864Z","shell.execute_reply":"2025-12-17T11:34:00.037936Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ACTIVITY\n\ntof_cols = [c for c in train_df.columns if c.startswith(\"tof\")]\ntof_data = train_df[tof_cols]\n\ncol_var = tof_data.var()\ncol_std = tof_data.std()\n\ncol_var.sort_values(ascending=False).head(10)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:34:04.058096Z","iopub.execute_input":"2025-12-17T11:34:04.058545Z","iopub.status.idle":"2025-12-17T11:34:06.002569Z","shell.execute_reply.started":"2025-12-17T11:34:04.058521Z","shell.execute_reply":"2025-12-17T11:34:06.001489Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# MOTION SENSIVITY\n\ntof_diff = tof_data.diff().abs()\ntemporal_energy = tof_diff.mean()\n\ntemporal_energy.sort_values(ascending=False).head(10)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:34:08.288997Z","iopub.execute_input":"2025-12-17T11:34:08.289593Z","iopub.status.idle":"2025-12-17T11:34:18.514888Z","shell.execute_reply.started":"2025-12-17T11:34:08.289556Z","shell.execute_reply":"2025-12-17T11:34:18.513922Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CORRELATION\n\nsample = tof_data.sample(n=5000, random_state=42)\ncorr = sample.corr()\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\nplt.figure(figsize=(10, 8))\nsns.heatmap(corr, cmap=\"coolwarm\", center=0)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:34:34.822231Z","iopub.execute_input":"2025-12-17T11:34:34.822615Z","iopub.status.idle":"2025-12-17T11:34:37.701792Z","shell.execute_reply.started":"2025-12-17T11:34:34.822590Z","shell.execute_reply":"2025-12-17T11:34:37.700431Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CLUSTERS\n\nfrom sklearn.impute import SimpleImputer\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.cluster import KMeans\n\n# impute\nimputer = SimpleImputer(strategy=\"median\")\ntof_clean = pd.DataFrame(imputer.fit_transform(tof_data), columns=tof_data.columns)\n\n# extract features\nfeatures = pd.DataFrame({\n    \"std\": tof_clean.std(),\n    \"energy\": tof_clean.diff().abs().mean(),\n    \"mean\": tof_clean.mean(),\n})\n\n# scale\nX = StandardScaler().fit_transform(features)\n\n# cluster\nkmeans = KMeans(n_clusters=4, random_state=42)\nclusters = kmeans.fit_predict(X)\n\nfeatures[\"cluster\"] = clusters\nprint(features)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:34:41.103729Z","iopub.execute_input":"2025-12-17T11:34:41.104263Z","iopub.status.idle":"2025-12-17T11:35:23.302077Z","shell.execute_reply.started":"2025-12-17T11:34:41.104232Z","shell.execute_reply":"2025-12-17T11:35:23.300599Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# DIMENSIONALITY\n\nfrom sklearn.decomposition import PCA\n\npca = PCA()\npca.fit(StandardScaler().fit_transform(tof_data.dropna()))\n\nexplained = np.cumsum(pca.explained_variance_ratio_)\n\nplt.plot(explained)\nplt.axhline(0.9, color='r')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:35:44.625453Z","iopub.execute_input":"2025-12-17T11:35:44.625750Z","iopub.status.idle":"2025-12-17T11:36:09.949651Z","shell.execute_reply.started":"2025-12-17T11:35:44.625729Z","shell.execute_reply":"2025-12-17T11:36:09.948490Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# VISUALIZER\n\ntop_cols = temporal_energy.sort_values(ascending=False).head(5).index\ntof_data[top_cols].iloc[:500].plot()\n\ncluster_cols = features[features.cluster == 1].index\ntof_data[cluster_cols[:5]].iloc[:500].plot()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:36:13.342821Z","iopub.execute_input":"2025-12-17T11:36:13.343164Z","iopub.status.idle":"2025-12-17T11:36:13.970466Z","shell.execute_reply.started":"2025-12-17T11:36:13.343133Z","shell.execute_reply":"2025-12-17T11:36:13.969424Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"grouped = train_df.groupby(\"gesture\")[tof_cols].mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T11:37:50.920758Z","iopub.execute_input":"2025-12-17T11:37:50.921248Z","iopub.status.idle":"2025-12-17T11:37:53.511603Z","shell.execute_reply.started":"2025-12-17T11:37:50.921218Z","shell.execute_reply":"2025-12-17T11:37:53.510497Z"}},"outputs":[],"execution_count":null}]}