{"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"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ======================= [Install Compatible Packages] =======================\nimport os\nos.environ['OPENBLAS_NUM_THREADS'] = '1'\nos.environ['OMP_NUM_THREADS'] = '1'\n\n# Purge pip cache\n!pip cache purge\n\n# Uninstall problematic packages\n!pip uninstall -y numpy scipy scikit-learn imbalanced-learn\n\n# Install known working versions\n!pip install -q numpy==1.24.4 scipy==1.10.1 scikit-learn==1.2.2 imbalanced-learn==0.10.1 h5py tqdm threadpoolctl\n\n# Restart the runtime to finalize installation\nimport os\nos._exit(0)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T07:07:58.152331Z","iopub.execute_input":"2025-07-04T07:07:58.152565Z","execution_failed":"2025-07-04T07:08:14.822Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======================= [Validate Installation] =======================\nimport os\nos.environ['OPENBLAS_NUM_THREADS'] = '1'\nos.environ['OMP_NUM_THREADS'] = '1'\n\n# Print Python path for debug\nimport sys\nprint(\"Python path:\", sys.path)\n\n# Confirm correct versions\nimport sklearn\nimport imblearn\nprint(f\"scikit-learn version: {sklearn.__version__}\")        # Must print 1.2.2\nprint(f\"imbalanced-learn version: {imblearn.__version__}\")  # Must print 0.10.1\n\n# Raise error if mismatch\nif sklearn.__version__ != '1.2.2' or imblearn.__version__ != '0.10.1':\n    raise ImportError(\"Incorrect package versions. Please re-run the install cell.\")\nelse:\n    print(\"✅ All versions correct. Ready to proceed.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T07:08:28.837935Z","iopub.execute_input":"2025-07-04T07:08:28.838517Z","iopub.status.idle":"2025-07-04T07:08:28.844108Z","shell.execute_reply.started":"2025-07-04T07:08:28.838495Z","shell.execute_reply":"2025-07-04T07:08:28.843176Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Imports\nimport pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.utils import compute_class_weight\nfrom sklearn.feature_selection import VarianceThreshold\nfrom sklearn.metrics import classification_report, confusion_matrix, f1_score\nimport h5py\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom torch.optim.lr_scheduler import CyclicLR\nfrom imblearn.over_sampling import SMOTE\nfrom threadpoolctl import threadpool_limits\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T07:08:32.428572Z","iopub.execute_input":"2025-07-04T07:08:32.428888Z","iopub.status.idle":"2025-07-04T07:08:34.709958Z","shell.execute_reply.started":"2025-07-04T07:08:32.428864Z","shell.execute_reply":"2025-07-04T07:08:34.709369Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Constants\nH5_FILE_PATH = '/kaggle/working/processed_data.h5'\nTEST_H5_FILE = '/kaggle/working/test_processed_data.h5'\nBATCH_SIZE = 16\nLEARNING_RATE = 1e-4\nMAX_LEN = 300\nHIDDEN_DIM = 512\nNUM_EPOCHS = 500\nEARLY_STOP_PATIENCE = 15\nMODEL_PATH = '/kaggle/working/best_model.pth'\n\n# Device configuration\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\n\n# Load Data\nbase_path = \"/kaggle/input/cmi-detect-behavior-with-sensor-data\"\ntrain_csv_path = os.path.join(base_path, \"train.csv\")\ntest_csv_path = os.path.join(base_path, \"test.csv\")\nbase_cols = ['sequence_id', 'gesture', 'acc_x', 'acc_y', 'acc_z', 'rot_w', 'rot_x', 'rot_y', 'rot_z', 'thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5']\ntof_cols = [f'tof_{i}_v{j}' for i in range(1, 3) for j in range(1, 11)]\nrequired_cols = base_cols + tof_cols\n\ntry:\n    train = pd.read_csv(train_csv_path, usecols=required_cols)\n    test = pd.read_csv(test_csv_path, usecols=['sequence_id'] + required_cols[2:])\nexcept FileNotFoundError as e:\n    print(f\"Dataset files not found: {e}\")\n    raise","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T07:08:38.69116Z","iopub.execute_input":"2025-07-04T07:08:38.691925Z","iopub.status.idle":"2025-07-04T07:08:46.73522Z","shell.execute_reply.started":"2025-07-04T07:08:38.691897Z","shell.execute_reply":"2025-07-04T07:08:46.734641Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create gesture mapping\ngesture_map = {gesture: idx for idx, gesture in enumerate(sorted(train['gesture'].unique()))}\nNUM_CLASSES = len(gesture_map)\nprint(f\"Found {NUM_CLASSES} unique gestures\")\nprint(f\"Gesture distribution:\\n{train['gesture'].value_counts()}\")\n\n# Preprocessing\ndef preprocess_data(df, is_train=True):\n    df = df.copy()\n    num_cols = [col for col in df.columns if col not in ['sequence_id', 'gesture']]\n    df[num_cols] = df[num_cols].fillna(-1)\n    constant_cols = df.columns[df.nunique() == 1]\n    if constant_cols.any():\n        print(f\"Removing constant columns: {list(constant_cols)}\")\n        df = df.drop(columns=constant_cols)\n    return df\n\ntrain = preprocess_data(train)\ntest = preprocess_data(test, is_train=False)\nfeature_cols = [col for col in train.columns if col not in ['sequence_id', 'gesture']]\nprint(f\"Using {len(feature_cols)} features\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T07:08:51.667974Z","iopub.execute_input":"2025-07-04T07:08:51.66854Z","iopub.status.idle":"2025-07-04T07:08:52.341434Z","shell.execute_reply.started":"2025-07-04T07:08:51.668518Z","shell.execute_reply":"2025-07-04T07:08:52.340635Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Feature selection with variance thresholding\ndef aggregate_sequences(df, feature_cols):\n    agg_funcs = ['mean', 'std']\n    seq_data = []\n    seq_labels = []\n    seq_ids = df['sequence_id'].unique()\n    for seq_id in tqdm(seq_ids, desc=\"Aggregating sequences\"):\n        seq_df = df[df['sequence_id'] == seq_id]\n        agg_features = []\n        for col in feature_cols:\n            for func in agg_funcs:\n                agg_features.append(seq_df[col].agg(func))\n        seq_data.append(agg_features)\n        seq_labels.append(seq_df['gesture'].iloc[0])\n    agg_cols = [f\"{col}_{func}\" for col in feature_cols for func in agg_funcs]\n    return pd.DataFrame(seq_data, columns=agg_cols), seq_labels, seq_ids\n\nselector = VarianceThreshold(threshold=0.01)\nX_agg_full, y_agg, seq_ids_agg = aggregate_sequences(train, feature_cols)\nselector.fit(X_agg_full.fillna(-1))\nselected_features = X_agg_full.columns[selector.get_support()]\nprint(f\"Selected {len(selected_features)} features after variance thresholding\")\n\n# Correlation filtering\ncorr_matrix = X_agg_full[selected_features].corr().abs()\nupper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))\nto_drop = [column for column in upper.columns if any(upper[column] > 0.8)]\nselected_features = [col for col in selected_features if col not in to_drop]\nX_agg = X_agg_full[selected_features].fillna(-1).astype(np.float32)\nprint(f\"Dropped {len(to_drop)} highly correlated features (>0.8)\")\n\n# Mutual information feature selection\nfrom sklearn.feature_selection import mutual_info_classif\nimport numpy as np\nsample_indices = X_agg.sample(n=min(2000, len(X_agg)), random_state=42).index\nmi_scores = mutual_info_classif(X_agg.loc[sample_indices], np.array(y_agg)[sample_indices], random_state=42)\nmi_ranking = pd.Series(mi_scores, index=X_agg.columns).sort_values(ascending=False)\nselected_features = mi_ranking.head(20).index\nX_agg = X_agg[selected_features].astype(np.float32)\nprint(f\"Selected top {len(selected_features)} features via mutual information\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T07:37:14.130332Z","iopub.execute_input":"2025-07-04T07:37:14.130967Z","iopub.status.idle":"2025-07-04T07:43:09.090209Z","shell.execute_reply.started":"2025-07-04T07:37:14.13094Z","shell.execute_reply":"2025-07-04T07:43:09.089446Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# SMOTE with refined parameters\nwith threadpool_limits(limits=1, user_api='blas'):\n    smote = SMOTE(random_state=42, k_neighbors=3, sampling_strategy='not majority')\n    X_agg_res, y_agg_res = smote.fit_resample(X_agg, y_agg)\n    \ntrain_res = pd.concat([\n    pd.DataFrame(X_agg_res, columns=selected_features),\n    pd.DataFrame({\n        'gesture': y_agg_res,\n        'sequence_id': [f'SMOTE_SEQ_{i}' if i >= len(seq_ids_agg) else seq_ids_agg[i] for i in range(len(X_agg_res))]\n    })\n], axis=1)\n\nprint(f\"SMOTE output: {X_agg_res.shape[0]} samples, {len(np.unique(y_agg_res))} classes\")\nprint(f\"train_res shape: {train_res.shape}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T07:43:36.001314Z","iopub.execute_input":"2025-07-04T07:43:36.001593Z","iopub.status.idle":"2025-07-04T07:43:36.073415Z","shell.execute_reply.started":"2025-07-04T07:43:36.001574Z","shell.execute_reply":"2025-07-04T07:43:36.072725Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Time-series processing\nts_feature_cols = ['acc_x', 'acc_y', 'acc_z', 'rot_x', 'rot_y', 'rot_z', 'thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5'] + tof_cols\nscalers = {col: StandardScaler() for col in ts_feature_cols}\nfor col in ts_feature_cols:\n    valid_data = train[train[col].notna()][col].values.reshape(-1, 1)\n    if len(valid_data) > 0:\n        scalers[col].fit(valid_data)\n\ndef process_sequence(df_seq, feature_cols, scalers):\n    current_feature_cols = [col for col in feature_cols if col in df_seq.columns]\n    if not current_feature_cols:\n        print(f\"No valid features in sequence: {df_seq['sequence_id'].iloc[0]}\")\n        return np.zeros((MAX_LEN, 0)), np.zeros((MAX_LEN, 0))\n    \n    X = df_seq[current_feature_cols].to_numpy(dtype=np.float32)\n    mask = (~np.isnan(X)).astype(np.float32)\n    X = np.nan_to_num(X, nan=-1.0)\n    \n    for i, col in enumerate(current_feature_cols):\n        X[:, i] = scalers[col].transform(X[:, i].reshape(-1, 1)).flatten()\n    \n    if len(X) < MAX_LEN:\n        pad_len = MAX_LEN - len(X)\n        pad = np.zeros((pad_len, X.shape[1]))\n        X = np.concatenate([X, pad], axis=0)\n        pad_mask = np.zeros((pad_len, mask.shape[1]))\n        mask = np.concatenate([mask, pad_mask], axis=0)\n    else:\n        X = X[:MAX_LEN]\n        mask = mask[:MAX_LEN]\n    \n    acc_cols = [col for col in current_feature_cols if 'acc_' in col]\n    if acc_cols:\n        acc_indices = [current_feature_cols.index(col) for col in acc_cols]\n        acc_data = X[:, acc_indices]\n        velocity = np.cumsum(acc_data, axis=0)\n        jerk = np.diff(acc_data, axis=0, prepend=acc_data[0:1, :])\n        X = np.concatenate([X, velocity, jerk], axis=1)\n        mask = np.concatenate([mask, np.ones_like(velocity), np.ones_like(jerk)], axis=1)\n        \n        if all(col in current_feature_cols for col in ['acc_x', 'acc_y', 'acc_z']):\n            acc_x_idx = current_feature_cols.index('acc_x')\n            acc_y_idx = current_feature_cols.index('acc_y')\n            acc_z_idx = current_feature_cols.index('acc_z')\n            acc_mag = np.sqrt(X[:, acc_x_idx]**2 + X[:, acc_y_idx]**2 + X[:, acc_z_idx]**2).reshape(-1, 1)\n            X = np.concatenate([X, acc_mag], axis=1)\n            mask = np.concatenate([mask, (mask[:, acc_x_idx] * mask[:, acc_y_idx] * mask[:, acc_z_idx]).reshape(-1, 1)], axis=1)\n    \n    return X, mask\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T07:43:09.189761Z","iopub.execute_input":"2025-07-04T07:43:09.190104Z","iopub.status.idle":"2025-07-04T07:43:14.461168Z","shell.execute_reply.started":"2025-07-04T07:43:09.190076Z","shell.execute_reply":"2025-07-04T07:43:14.460598Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Data augmentation with jittering\nimport h5py\nimport numpy as np\nfrom tqdm import tqdm\n\n# Define augment_data function\ndef augment_data(X, mask, gesture, gesture_map, minority_classes=[0, 14, 15]):\n    if gesture_map.get(gesture, -1) in minority_classes:\n        # Jittering\n        noise = np.random.normal(0, 0.05, X.shape)\n        X_aug = X + noise * mask\n        # Time warping\n        time_steps = np.linspace(0, 1, X.shape[0])\n        warp_factor = np.random.uniform(0.8, 1.2)\n        new_time = np.linspace(0, 1, MAX_LEN)\n        X_warped = np.zeros((MAX_LEN, X_aug.shape[1]), dtype=np.float32)\n        for i in range(X_aug.shape[1]):\n            X_warped[:, i] = np.interp(new_time, time_steps, X_aug[:, i])\n        return X_warped, mask\n    return X, mask\n\n# Class weights\ngesture_counts = train['gesture'].map(gesture_map).value_counts().sort_index()\nclass_weights = compute_class_weight('balanced', classes=np.arange(NUM_CLASSES), y=train['gesture'].map(gesture_map).values)\nclass_weights = torch.tensor(class_weights, dtype=torch.float32).to(device)\nprint(f\"Class weights: {class_weights}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T08:20:22.6382Z","iopub.execute_input":"2025-07-04T08:20:22.638473Z","iopub.status.idle":"2025-07-04T08:20:22.769613Z","shell.execute_reply.started":"2025-07-04T08:20:22.638454Z","shell.execute_reply":"2025-07-04T08:20:22.768912Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create HDF5 dataset\nif os.path.exists(H5_FILE_PATH):\n    os.remove(H5_FILE_PATH)\n\nwith h5py.File(H5_FILE_PATH, 'w') as f:\n    f.create_dataset('gesture_map', data=[f\"{k}:{v}\".encode() for k, v in gesture_map.items()])\n    seq_group = f.create_group('sequences')\n    seq_to_label = dict(zip(train_res['sequence_id'], train_res['gesture']))\n    \n    for seq_id in tqdm(train_res['sequence_id'], desc=\"Processing time-series sequences\"):\n        if 'SMOTE_SEQ' in str(seq_id):\n            orig_seq_id = np.random.choice(seq_ids_agg)\n            seq_df = train[train['sequence_id'] == orig_seq_id][ts_feature_cols + ['sequence_id', 'gesture']].copy()\n            seq_df['sequence_id'] = seq_id\n            seq_df['gesture'] = seq_to_label[seq_id]\n        else:\n            seq_df = train[train['sequence_id'] == seq_id][ts_feature_cols + ['sequence_id', 'gesture']].copy()\n        \n        X, mask = process_sequence(seq_df, ts_feature_cols, scalers)\n        X, mask = augment_data(X, mask, seq_df['gesture'].iloc[0], gesture_map)\n        seq_subgroup = seq_group.create_group(str(seq_id))\n        seq_subgroup.create_dataset('data', data=X)\n        seq_subgroup.create_dataset('mask', data=mask)\n        seq_subgroup.attrs['label'] = gesture_map[seq_to_label[seq_id]]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T08:20:25.197876Z","iopub.execute_input":"2025-07-04T08:20:25.198138Z","iopub.status.idle":"2025-07-04T08:28:37.247505Z","shell.execute_reply.started":"2025-07-04T08:20:25.198118Z","shell.execute_reply":"2025-07-04T08:28:37.246827Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Process test data\nif os.path.exists(TEST_H5_FILE):\n    os.remove(TEST_H5_FILE)\n\nwith h5py.File(TEST_H5_FILE, 'w') as f:\n    seq_group = f.create_group('sequences')\n    for seq_id in tqdm(test['sequence_id'].unique(), desc=\"Processing test sequences\"):\n        seq_df = test[test['sequence_id'] == seq_id].copy()\n        X, mask = process_sequence(seq_df, ts_feature_cols, scalers)\n        seq_subgroup = seq_group.create_group(str(seq_id))\n        seq_subgroup.create_dataset('data', data=X)\n        seq_subgroup.create_dataset('mask', data=mask)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T08:30:49.257942Z","iopub.execute_input":"2025-07-04T08:30:49.258422Z","iopub.status.idle":"2025-07-04T08:30:49.277816Z","shell.execute_reply.started":"2025-07-04T08:30:49.258398Z","shell.execute_reply":"2025-07-04T08:30:49.277168Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Dataset Class\nclass GestureDataset(Dataset):\n    def __init__(self, h5_file, is_train=True):\n        self.h5_file = h5_file\n        self.is_train = is_train\n        with h5py.File(h5_file, 'r') as f:\n            self.sequence_ids = list(f['sequences'].keys())\n    \n    def __len__(self):\n        return len(self.sequence_ids)\n    \n    def __getitem__(self, idx):\n        with h5py.File(self.h5_file, 'r') as f:\n            seq_id = self.sequence_ids[idx]\n            data = f[f'sequences/{seq_id}/data'][:]\n            mask = f[f'sequences/{seq_id}/mask'][:]\n            if self.is_train:\n                label = f[f'sequences/{seq_id}'].attrs['label']\n                return torch.tensor(data, dtype=torch.float), torch.tensor(mask, dtype=torch.float), torch.tensor(label, dtype=torch.long)\n            return torch.tensor(data, dtype=torch.float), torch.tensor(mask, dtype=torch.float), seq_id\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T08:30:51.289923Z","iopub.execute_input":"2025-07-04T08:30:51.290398Z","iopub.status.idle":"2025-07-04T08:30:51.296245Z","shell.execute_reply.started":"2025-07-04T08:30:51.290374Z","shell.execute_reply":"2025-07-04T08:30:51.29546Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Model Definition\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nclass MultiModalClassifier(nn.Module):\n    def __init__(self, input_dim_imu, input_dim_tof, input_dim_thermal, hidden_dim=128, fusion_dim=256, num_classes=18):\n        super(MultiModalClassifier, self).__init__()\n\n        # Modality-specific encoders\n        self.imu_fc = nn.Sequential(\n            nn.Linear(input_dim_imu, hidden_dim),\n            nn.LayerNorm(hidden_dim),\n            nn.ReLU(),\n            nn.Dropout(0.3)\n        )\n\n        self.tof_fc = nn.Sequential(\n            nn.Linear(input_dim_tof, hidden_dim),\n            nn.LayerNorm(hidden_dim),\n            nn.ReLU(),\n            nn.Dropout(0.3)\n        )\n\n        self.thermal_fc = nn.Sequential(\n            nn.Linear(input_dim_thermal, hidden_dim),\n            nn.LayerNorm(hidden_dim),\n            nn.ReLU(),\n            nn.Dropout(0.3)\n        )\n\n        # Fusion + classification head\n        self.fusion_fc = nn.Sequential(\n            nn.Linear(3 * hidden_dim, fusion_dim),\n            nn.LayerNorm(fusion_dim),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(fusion_dim, num_classes)\n        )\n\n    def forward(self, imu_input, tof_input, thermal_input):\n        imu_feat = self.imu_fc(imu_input)\n        tof_feat = self.tof_fc(tof_input)\n        thermal_feat = self.thermal_fc(thermal_input)\n\n        fused = torch.cat([imu_feat, tof_feat, thermal_feat], dim=1)\n        out = self.fusion_fc(fused)\n        return out\n\nclass GestureCNN(nn.Module):\n    def __init__(self, input_dim, hidden_dim, num_classes):\n        super().__init__()\n        self.conv_block = nn.Sequential(\n            nn.Conv1d(input_dim, hidden_dim, kernel_size=3, padding=1),\n            nn.BatchNorm1d(hidden_dim),\n            nn.ReLU(),\n            nn.MaxPool1d(2),\n            ResidualBlock(hidden_dim, hidden_dim),\n            nn.MaxPool1d(2),\n            ResidualBlock(hidden_dim, hidden_dim * 2),\n            nn.MaxPool1d(2),\n            ResidualBlock(hidden_dim * 2, hidden_dim * 2),\n            nn.Dropout(0.5)\n        )\n        with torch.no_grad():\n            dummy = torch.randn(1, input_dim, MAX_LEN)\n            dummy_out = self.conv_block(dummy)\n            conv_out_size = dummy_out.view(1, -1).size(1)\n        \n        self.classifier = nn.Sequential(\n            nn.Linear(conv_out_size, hidden_dim),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(hidden_dim, num_classes)\n        )\n\n    def forward(self, x, mask):\n        x = x.permute(0, 2, 1)\n        mask = mask.permute(0, 2, 1)\n        x = x * mask\n        features = self.conv_block(x)\n        features = features.view(features.size(0), -1)\n        return self.classifier(features)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T08:30:53.353482Z","iopub.execute_input":"2025-07-04T08:30:53.354257Z","iopub.status.idle":"2025-07-04T08:30:53.364274Z","shell.execute_reply.started":"2025-07-04T08:30:53.354224Z","shell.execute_reply":"2025-07-04T08:30:53.363379Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ========== Training Setup ==========\nfrom torch.utils.data import WeightedRandomSampler\nimport torch.nn.functional as F\n\n# Prepare datasets\ntrain_dataset = GestureDataset(H5_FILE_PATH, is_train=True)\ntrain_ids, val_ids = train_test_split(range(len(train_dataset)), test_size=0.2, random_state=42)\ntrain_subset = torch.utils.data.Subset(train_dataset, train_ids)\nval_dataset = torch.utils.data.Subset(train_dataset, val_ids)\n\n# Compute class weights using training subset\ntrain_labels = np.array([train_dataset[i][2] for i in train_ids])\nclass_counts = np.bincount(train_labels)\nclass_weights = 1. / class_counts\nweights = class_weights[train_labels]\nsampler = WeightedRandomSampler(weights, len(weights), replacement=True)\n\n# DataLoaders\ntrain_loader = DataLoader(train_subset, batch_size=cfg.batch_size, sampler=sampler)\nval_loader = DataLoader(val_dataset, batch_size=cfg.batch_size, shuffle=False)\n\n# Model Initialization\ndummy_sample = train_dataset[0][0]\ninput_dim = dummy_sample.shape[1]\nprint(f\"Model input dimension: {input_dim}\")\nmodel = GestureCNN(input_dim, HIDDEN_DIM, NUM_CLASSES).to(cfg.device)\n\n# Focal Loss\nclass FocalLoss(nn.Module):\n    def __init__(self, alpha=1, gamma=2, reduction='mean'):\n        super(FocalLoss, self).__init__()\n        self.alpha = alpha\n        self.gamma = gamma\n        self.reduction = reduction\n\n    def forward(self, inputs, targets):\n        CE_loss = F.cross_entropy(inputs, targets, reduction='none')\n        pt = torch.exp(-CE_loss)\n        F_loss = self.alpha * (1 - pt) ** self.gamma * CE_loss\n        return F_loss.mean() if self.reduction == 'mean' else F_loss.sum()\n\ncriterion = FocalLoss(alpha=1, gamma=2)\n\n# Optimizer & Scheduler\noptimizer = optim.AdamW(model.parameters(), lr=LEARNING_RATE, weight_decay=1e-4)\nscheduler = CyclicLR(optimizer, base_lr=1e-5, max_lr=1e-3, step_size_up=50, mode='triangular')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T08:30:56.524148Z","iopub.execute_input":"2025-07-04T08:30:56.52468Z","iopub.status.idle":"2025-07-04T08:30:56.894885Z","shell.execute_reply.started":"2025-07-04T08:30:56.524633Z","shell.execute_reply":"2025-07-04T08:30:56.894074Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training loop\ndef train_model():\n    best_f1 = 0\n    train_accuracies, val_accuracies = [], []\n    train_losses, val_losses = [], []\n    early_stop_counter = 0\n    best_epoch = 0\n    \n    for epoch in range(NUM_EPOCHS):\n        model.train()\n        train_loss, train_correct, train_total = 0, 0, 0\n        for data, mask, labels in tqdm(train_loader, desc=f\"Epoch {epoch+1}\"):\n            data, mask, labels = data.to(device), mask.to(device), labels.to(device)\n            optimizer.zero_grad()\n            outputs = model(data, mask)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            train_loss += loss.item() * labels.size(0)\n            train_correct += (torch.argmax(outputs, dim=1) == labels).sum().item()\n            train_total += labels.size(0)\n        \n        train_acc = train_correct / train_total\n        train_loss = train_loss / train_total\n        train_accuracies.append(train_acc)\n        train_losses.append(train_loss)\n        \n        model.eval()\n        all_preds, all_labels = [], []\n        val_loss, val_correct, val_total = 0, 0, 0\n        with torch.no_grad():\n            for data, mask, labels in val_loader:\n                data, mask, labels = data.to(device), mask.to(device), labels.to(device)\n                outputs = model(data, mask)\n                loss = criterion(outputs, labels)\n                val_loss += loss.item() * labels.size(0)\n                preds = torch.argmax(outputs, dim=1)\n                all_preds.extend(preds.cpu().numpy())\n                all_labels.extend(labels.cpu().numpy())\n                val_correct += (preds == labels).sum().item()\n                val_total += labels.size(0)\n        \n        val_acc = val_correct / val_total\n        val_loss = val_loss / val_total\n        val_f1 = f1_score(all_labels, all_preds, average='weighted')\n        print(f\"Epoch {epoch+1}/{NUM_EPOCHS} | Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f} | Val Acc: {val_acc:.4f} | Val F1: {val_f1:.4f}\")\n        \n        if val_f1 > best_f1:\n            best_f1 = val_f1\n            best_epoch = epoch + 1\n            torch.save(model.state_dict(), MODEL_PATH)\n            print(f\"Saved new best model at epoch {best_epoch}\")\n            early_stop_counter = 0\n        else:\n            early_stop_counter += 1\n            print(f\"Early stop counter: {early_stop_counter}/{EARLY_STOP_PATIENCE}\")\n        \n        if early_stop_counter >= EARLY_STOP_PATIENCE:\n            print(f\"Early stopping triggered at epoch {epoch+1}\")\n            break\n        \n        scheduler.step()\n    \n    # Final evaluation\n    model.eval()\n    all_preds, all_labels = [], []\n    with torch.no_grad():\n        for data, mask, labels in val_loader:\n            data, mask, labels = data.to(device), mask.to(device), labels.to(device)\n            outputs = model(data, mask)\n            preds = torch.argmax(outputs, dim=1)\n            all_preds.extend(preds.cpu().numpy())\n            all_labels.extend(labels.cpu().numpy())\n    \n    print(\"\\nFinal Validation Report:\")\n    target_names = [f\"Gesture {i}\" for i in sorted(np.unique(all_labels))]\n    print(classification_report(all_labels, all_preds, target_names=target_names))\n    \n    cm_labels = sorted(np.unique(all_labels + all_preds))\n    cm = confusion_matrix(all_labels, all_preds, labels=cm_labels)\n    print(\"\\nConfusion Matrix:\")\n    print(cm)\n    \n    plt.figure(figsize=(12, 10))\n    sns.heatmap(cm, annot=True, cmap=\"Blues\", fmt=\"g\",\n                xticklabels=[f\"Gesture {cm_labels[i]}\" for i in range(len(cm_labels))],\n                yticklabels=[f\"Gesture {cm_labels[i]}\" for i in range(len(cm_labels))])\n    plt.xlabel(\"Predicted\")\n    plt.ylabel(\"True\")\n    plt.title(\"Validation Confusion Matrix\")\n    plt.savefig('/kaggle/working/confusion_matrix.png')\n    plt.close()\n    \n    # Check if plotting variables exist before plotting\n    if 'train_accuracies' in locals() and 'val_accuracies' in locals():\n        plt.figure(figsize=(10, 5))\n        plt.plot(range(1, len(train_accuracies) + 1), train_accuracies, label='Train Accuracy')\n        plt.plot(range(1, len(val_accuracies) + 1), val_accuracies, label='Validation Accuracy')\n        plt.xlabel('Epoch')\n        plt.ylabel('Accuracy')\n        plt.title('Training and Validation Accuracy vs Epoch')\n        plt.legend()\n        plt.grid(True)\n        plt.savefig('/kaggle/working/accuracy_plot.png')\n        plt.close()\n    else:\n        print(\"Warning: Accuracy plot skipped due to undefined train_accuracies or val_accuracies\")\n    \n    if 'train_losses' in locals() and 'val_losses' in locals():\n        plt.figure(figsize=(10, 5))\n        plt.plot(range(1, len(train_losses) + 1), train_losses, label='Train Loss')\n        plt.plot(range(1, len(val_losses) + 1), val_losses, label='Validation Loss')\n        plt.xlabel('Epoch')\n        plt.ylabel('Loss')\n        plt.title('Training and Validation Loss vs Epoch')\n        plt.legend()\n        plt.grid(True)\n        plt.savefig('/kaggle/working/loss_plot.png')\n        plt.close()\n    else:\n        print(\"Warning: Loss plot skipped due to undefined train_losses or val_losses\")\n\n# Run training\ntrain_model()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T08:31:00.9015Z","iopub.execute_input":"2025-07-04T08:31:00.902351Z","iopub.status.idle":"2025-07-04T09:44:32.537199Z","shell.execute_reply.started":"2025-07-04T08:31:00.902318Z","shell.execute_reply":"2025-07-04T09:44:32.536581Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Test predictions\ntest_dataset = GestureDataset(TEST_H5_FILE, is_train=False)\ntest_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE)\nmodel.load_state_dict(torch.load(MODEL_PATH))\nmodel.eval()\n\npredictions = []\nsequence_ids = []\nwith torch.no_grad():\n    for data, mask, seq_ids_batch in test_loader:\n        data, mask = data.to(device), mask.to(device)\n        outputs = model(data, mask)\n        batch_preds = torch.argmax(outputs, dim=1).cpu().numpy()\n        predictions.extend(batch_preds)\n        sequence_ids.extend(seq_ids_batch)\n\n# Create submission\ninverse_gesture_map = {v: k for k, v in gesture_map.items()}\nsubmission = pd.DataFrame({\n    'sequence_id': sequence_ids,\n    'gesture': [inverse_gesture_map[p] for p in predictions]\n})\nsubmission.to_csv('/kaggle/working/submission.csv', index=False)\nprint(\"Submission file created\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T09:44:32.538491Z","iopub.execute_input":"2025-07-04T09:44:32.53883Z","iopub.status.idle":"2025-07-04T09:44:32.689063Z","shell.execute_reply.started":"2025-07-04T09:44:32.538803Z","shell.execute_reply":"2025-07-04T09:44:32.688423Z"}},"outputs":[],"execution_count":null}]}