{"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":"gpu","dataSources":[{"sourceId":4477,"databundleVersionId":44220,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!unzip /kaggle/input/grasp-and-lift-eeg-detection/train.zip -d /kaggle/working/train_data","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-03T22:55:56.240900Z","iopub.execute_input":"2025-12-03T22:55:56.241133Z","iopub.status.idle":"2025-12-03T22:56:24.583709Z","shell.execute_reply.started":"2025-12-03T22:55:56.241108Z","shell.execute_reply":"2025-12-03T22:56:24.582867Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!unzip /kaggle/input/grasp-and-lift-eeg-detection/test.zip -d /kaggle/working/test_data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T22:57:28.089117Z","iopub.execute_input":"2025-12-03T22:57:28.089861Z","iopub.status.idle":"2025-12-03T22:57:32.349013Z","shell.execute_reply.started":"2025-12-03T22:57:28.089822Z","shell.execute_reply":"2025-12-03T22:57:32.348374Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Grasp and Lift EEG Detection","metadata":{}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, TensorDataset\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix, classification_report","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T00:11:02.163963Z","iopub.execute_input":"2025-12-04T00:11:02.164596Z","iopub.status.idle":"2025-12-04T00:11:02.168855Z","shell.execute_reply.started":"2025-12-04T00:11:02.164567Z","shell.execute_reply":"2025-12-04T00:11:02.168131Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_DIR = \"/kaggle/working/train_data/train/\"\n\nSFREQ = 500      \nWINDOW = 250\nN_CHANNELS = 32\nN_CLASSES = 6\n\nevent_names = [\n    \"HandStart\",\n    \"FirstDigitTouch\",\n    \"BothStartLoadPhase\",\n    \"LiftOff\",\n    \"Replace\",\n    \"BothReleased\"\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T00:11:33.205971Z","iopub.execute_input":"2025-12-04T00:11:33.206260Z","iopub.status.idle":"2025-12-04T00:11:33.210560Z","shell.execute_reply.started":"2025-12-04T00:11:33.206242Z","shell.execute_reply":"2025-12-04T00:11:33.209717Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ex_data = pd.read_csv(\"/kaggle/working/train_data/train/subj10_series1_data.csv\")\nex_data.head(5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T00:41:04.734506Z","iopub.execute_input":"2025-12-04T00:41:04.735068Z","iopub.status.idle":"2025-12-04T00:41:05.357258Z","shell.execute_reply.started":"2025-12-04T00:41:04.735040Z","shell.execute_reply":"2025-12-04T00:41:05.356468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ex_event = pd.read_csv(\"/kaggle/working/train_data/train/subj10_series1_events.csv\")\nex_event.head(5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T00:42:18.021763Z","iopub.execute_input":"2025-12-04T00:42:18.022538Z","iopub.status.idle":"2025-12-04T00:42:18.183655Z","shell.execute_reply.started":"2025-12-04T00:42:18.022510Z","shell.execute_reply":"2025-12-04T00:42:18.182989Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(\"/kaggle/working/train_data/train/subj10_series1_data.csv\")\n\nchannels = df.columns[1:33]\n\nplt.figure(figsize=(14, 10))\n\noffset = 2000  \nfor i, ch in enumerate(channels):\n    plt.plot(df[ch].values + i*offset, label=ch)\n\nplt.title(\"Raw EEG - 32 Channels (offset vertically)\")\nplt.xlabel(\"Time (samples)\")\nplt.ylabel(\"Amplitude + offset\")\nplt.legend(loc=\"upper right\", fontsize=8)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T00:50:35.854201Z","iopub.execute_input":"2025-12-04T00:50:35.854840Z","iopub.status.idle":"2025-12-04T00:50:38.501321Z","shell.execute_reply.started":"2025-12-04T00:50:35.854815Z","shell.execute_reply":"2025-12-04T00:50:38.500134Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_files = sorted([f for f in os.listdir(TRAIN_DIR) if f.endswith(\"_data.csv\")])\nevent_files = sorted([f for f in os.listdir(TRAIN_DIR) if f.endswith(\"_events.csv\")])\n\nselected_subjects = [1, 2, 3, 4, 5.6,7,8]\nselected_series = [1,2,3,4,5,6,7]\n\ndef match_subject_series(filename, subjects, series):\n    parts = filename.replace(\".csv\",\"\").split(\"_\")\n    sid = int(parts[0].replace(\"subj\",\"\"))\n    ser = int(parts[1].replace(\"series\",\"\"))\n    return sid in subjects and ser in series\n\ndata_files_filtered = [f for f in data_files if match_subject_series(f, selected_subjects, selected_series)]\nevent_files_filtered = [f for f in event_files if match_subject_series(f, selected_subjects, selected_series)]\n\nprint(\"Dipakai data file :\", data_files_filtered)\nprint(\"Dipakai event file:\", event_files_filtered)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T00:16:02.802387Z","iopub.execute_input":"2025-12-04T00:16:02.803045Z","iopub.status.idle":"2025-12-04T00:16:02.810851Z","shell.execute_reply.started":"2025-12-04T00:16:02.803017Z","shell.execute_reply":"2025-12-04T00:16:02.810191Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def find_event_onsets(event_signal):\n    \"\"\"Ambil ONSET (0→1), bukan seluruh segmen bernilai 1.\"\"\"\n    return np.where((event_signal[1:] == 1) & (event_signal[:-1] == 0))[0] + 1\n    \ndef load_grasp_file(data_path, event_path):\n    data = pd.read_csv(os.path.join(TRAIN_DIR, data_path))\n    events = pd.read_csv(os.path.join(TRAIN_DIR, event_path))\n\n    data_eeg = data.values[:, 1:33]          \n    events_arr = events[event_names].values  \n\n    X_list, y_list = [], []\n\n    # Loop kelas 0..5\n    for cls_idx in range(N_CLASSES):\n        onsets = find_event_onsets(events_arr[:, cls_idx])\n\n        for start in onsets:\n            end = start + WINDOW\n            if end <= len(data_eeg):\n                seg = data_eeg[start:end].T\n                X_list.append(seg)\n                y_list.append(cls_idx)\n\n    return np.array(X_list), np.array(y_list)\n\nX_all = []\ny_all = []\n\nfor dfile, efile in zip(data_files_filtered, event_files_filtered):\n    print(\"Loading:\", dfile)\n    X_sub, y_sub = load_grasp_file(dfile, efile)\n    X_all.append(X_sub)\n    y_all.append(y_sub)\n\nX = np.concatenate(X_all, axis=0)\ny = np.concatenate(y_all, axis=0)\nX = np.array(X, dtype=np.float32)\ny = np.array(y, dtype=np.int64)\n\nprint(\"X dtype:\", X.dtype)\nprint(\"X shape:\", X.shape)\nprint(\"TOTAL DATASET:\")\nprint(\"X:\", X.shape)\nprint(\"y:\", y.shape)\nprint(\"Distribusi kelas:\", np.bincount(y))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T00:16:06.082581Z","iopub.execute_input":"2025-12-04T00:16:06.083327Z","iopub.status.idle":"2025-12-04T00:16:41.830269Z","shell.execute_reply.started":"2025-12-04T00:16:06.083299Z","shell.execute_reply":"2025-12-04T00:16:41.829581Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\nDistribusi per kelas:\")\nfor i, name in enumerate(event_names):\n    print(f\"{i} - {name:<20}: {np.sum(y == i)} samples\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T00:44:45.661562Z","iopub.execute_input":"2025-12-04T00:44:45.662381Z","iopub.status.idle":"2025-12-04T00:44:45.667490Z","shell.execute_reply.started":"2025-12-04T00:44:45.662355Z","shell.execute_reply":"2025-12-04T00:44:45.666888Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X = (X - X.mean(axis=2, keepdims=True)) / (X.std(axis=2, keepdims=True) + 1e-6)\n\nX = X[:, np.newaxis, :, :]\n\nprint(\"Final CNN input:\", X.shape)\n\nX_train, X_test, y_train, y_test = train_test_split(\n    X, y, test_size=0.2, random_state=42, stratify=y\n)\n\nprint(\"Train:\", X_train.shape, \"Test:\", X_test.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T00:16:43.850387Z","iopub.execute_input":"2025-12-04T00:16:43.850684Z","iopub.status.idle":"2025-12-04T00:16:44.327711Z","shell.execute_reply.started":"2025-12-04T00:16:43.850662Z","shell.execute_reply":"2025-12-04T00:16:44.326570Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class EEG1DCNN(nn.Module):\n    def __init__(self, n_channels=32, n_classes=6):\n        super().__init__()\n\n        # Conv temporal (filter 1 x 7)\n        self.conv_temporal = nn.Sequential(\n            nn.Conv2d(1, 16, kernel_size=(1,7), padding=(0,3), bias=False),\n            nn.BatchNorm2d(16),\n            nn.ReLU(),\n        )\n\n        # Depthwise conv across channels\n        self.conv_spatial = nn.Sequential(\n            nn.Conv2d(16, 32, kernel_size=(n_channels,1), groups=16, bias=False),\n            nn.BatchNorm2d(32),\n            nn.ReLU(),\n            nn.AvgPool2d((1,4)),\n            nn.Dropout(0.25)\n        )\n\n        self.conv_separable = nn.Sequential(\n            nn.Conv2d(32, 32, kernel_size=(1,15), padding=(0,7), groups=32, bias=False),\n            nn.Conv2d(32, 64, kernel_size=(1,1), bias=False),\n            nn.BatchNorm2d(64),\n            nn.ReLU(),\n            nn.AvgPool2d((1,4)),\n            nn.Dropout(0.25)\n        )\n\n        dummy = torch.zeros(1,1,n_channels,250)\n        out = self.forward_features(dummy)\n        flat = out.numel()\n\n        self.classifier = nn.Sequential(\n            nn.Linear(flat, 128),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(128, n_classes)\n        )\n\n    def forward_features(self, x):\n        x = self.conv_temporal(x)\n        x = self.conv_spatial(x)\n        x = self.conv_separable(x)\n        return x\n\n    def forward(self, x):\n        x = self.forward_features(x)\n        x = x.reshape(x.size(0), -1)\n        return self.classifier(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T00:16:46.235599Z","iopub.execute_input":"2025-12-04T00:16:46.236248Z","iopub.status.idle":"2025-12-04T00:16:46.243806Z","shell.execute_reply.started":"2025-12-04T00:16:46.236223Z","shell.execute_reply":"2025-12-04T00:16:46.243005Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nmodel = EEG1DCNN().to(device)\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=1e-3)\n\ntrain_ds = TensorDataset(\n    torch.tensor(X_train, dtype=torch.float32),\n    torch.tensor(y_train, dtype=torch.long)\n)\n\ntest_ds = TensorDataset(\n    torch.tensor(X_test, dtype=torch.float32),\n    torch.tensor(y_test, dtype=torch.long)\n)\n\ntrain_loader = DataLoader(train_ds, batch_size=64, shuffle=True)\ntest_loader = DataLoader(test_ds, batch_size=64)\n\nEPOCHS = 50\n\nfor epoch in range(EPOCHS):\n    model.train()\n    total_loss = 0\n\n    for xb, yb in train_loader:\n        xb, yb = xb.to(device), yb.to(device)\n\n        optimizer.zero_grad()\n        out = model(xb)\n        loss = criterion(out, yb)\n        loss.backward()\n        optimizer.step()\n\n        total_loss += loss.item()\n\n    model.eval()\n    correct = 0\n    total = 0\n    with torch.no_grad():\n        for xb, yb in test_loader:\n            xb, yb = xb.to(device), yb.to(device)\n            pred = model(xb).argmax(1)\n            correct += (pred == yb).sum().item()\n            total += yb.size(0)\n\n    acc = 100 * correct / total\n    print(f\"Epoch {epoch+1}/{EPOCHS}  Loss={total_loss:.4f}  Test Acc={acc:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T00:17:30.078371Z","iopub.execute_input":"2025-12-04T00:17:30.079074Z","iopub.status.idle":"2025-12-04T00:18:29.340300Z","shell.execute_reply.started":"2025-12-04T00:17:30.079047Z","shell.execute_reply":"2025-12-04T00:18:29.339606Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import (\n    confusion_matrix, classification_report,\n    accuracy_score, balanced_accuracy_score, cohen_kappa_score,\n    roc_auc_score\n)\nfrom sklearn.preprocessing import label_binarize\n\nmodel.eval()\ny_true, y_pred = [], []\ny_prob = []\n\nwith torch.no_grad():\n    for xb, yb in test_loader:\n        xb = xb.to(device)\n        logits = model(xb)\n        probs = torch.softmax(logits, dim=1).cpu().numpy()\n\n        preds = probs.argmax(axis=1)\n\n        y_pred.extend(preds)\n        y_true.extend(yb.numpy())\n        y_prob.extend(probs)\n\ny_true = np.array(y_true)\ny_pred = np.array(y_pred)\ny_prob = np.array(y_prob)\n\ncm = confusion_matrix(y_true, y_pred)\n\nplt.figure(figsize=(7,5))\nsns.heatmap(cm, annot=True, fmt=\"d\",\n            xticklabels=event_names,\n            yticklabels=event_names,\n            cmap=\"Blues\")\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"True\")\nplt.title(\"Confusion Matrix — 6-Class EEG\")\nplt.show()\n\nprint(\"\\nAccuracy           :\", accuracy_score(y_true, y_pred))\n\ny_true_bin = label_binarize(y_true, classes=np.arange(N_CLASSES))\n\nmacro_auc = roc_auc_score(y_true_bin, y_prob, average=\"macro\")\nweighted_auc = roc_auc_score(y_true_bin, y_prob, average=\"weighted\")\n\nprint(\"\\nROC-AUC (macro)    :\", macro_auc)\n\nprint(\"\\nClassification Report:\")\nprint(classification_report(y_true, y_pred, target_names=event_names))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T00:23:03.629060Z","iopub.execute_input":"2025-12-04T00:23:03.629346Z","iopub.status.idle":"2025-12-04T00:23:03.958957Z","shell.execute_reply.started":"2025-12-04T00:23:03.629323Z","shell.execute_reply":"2025-12-04T00:23:03.958270Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}