{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":242954653,"sourceType":"kernelVersion"}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, Subset, random_split\nimport pandas as pd\nimport numpy as np\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nimport os\nfrom pathlib import Path\nimport matplotlib.pyplot as plt\n#from torch.utils.tensorboard import SummaryWriter\nfrom datetime import datetime\nfrom sklearn.metrics import precision_score, recall_score, f1_score, confusion_matrix, precision_recall_curve\nimport io\nfrom PIL import Image\nimport polars as pl\n\nimport kaggle_evaluation.cmi_inference_server\nimport torch.nn.functional as F","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:18.718670Z","iopub.execute_input":"2025-12-07T16:52:18.719388Z","iopub.status.idle":"2025-12-07T16:52:18.724346Z","shell.execute_reply.started":"2025-12-07T16:52:18.719364Z","shell.execute_reply":"2025-12-07T16:52:18.723580Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"Using {device} device\")\nif device == \"cuda\":\n    print(f\"GPU: {torch.cuda.get_device_name()}\")\n\ndevice = \"cuda\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:18.725514Z","iopub.execute_input":"2025-12-07T16:52:18.725744Z","iopub.status.idle":"2025-12-07T16:52:18.740220Z","shell.execute_reply.started":"2025-12-07T16:52:18.725729Z","shell.execute_reply":"2025-12-07T16:52:18.739673Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data loading","metadata":{}},{"cell_type":"code","source":"training_data_path = Path(\"data/CMI/train.csv\")\ntraining_data_demo_path = Path(\"data/CMI/train_demographics.csv\")\n\ntest_data_path = Path(\"data/CMI/test.csv\")\ntest_data_demo_path = Path(\"data/CMI/test_demographics.csv\")\n\n\ntrain_df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\ntrain_demo_df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv')\ntest_df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv')\ntest_demo_df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:18.740916Z","iopub.execute_input":"2025-12-07T16:52:18.741441Z","iopub.status.idle":"2025-12-07T16:52:39.577173Z","shell.execute_reply.started":"2025-12-07T16:52:18.741410Z","shell.execute_reply":"2025-12-07T16:52:39.576306Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data visualization","metadata":{}},{"cell_type":"code","source":"train_df.head(10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:39.578084Z","iopub.execute_input":"2025-12-07T16:52:39.578291Z","iopub.status.idle":"2025-12-07T16:52:39.598377Z","shell.execute_reply.started":"2025-12-07T16:52:39.578275Z","shell.execute_reply":"2025-12-07T16:52:39.597764Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dataset","metadata":{}},{"cell_type":"code","source":"class CMIIMUDataset(Dataset):\n    def __init__(self, df, transform=None, device='cpu'):\n        self.device = device\n        self.df = df\n        self.transform = transform\n        self.features = []  # Initialize features list\n        self.scaler = StandardScaler()\n\n        excluded_cols = {\n            'gesture', 'sequence_type', 'behavior', 'orientation',  # train-only\n            'row_id', 'subject', 'phase',  # metadata\n            'sequence_id', 'sequence_counter'  # identifiers\n        }\n        \n        thermal_tof_cols = [col for col in df.columns if col.startswith('thm_') or col.startswith('tof_')]\n        excluded_cols.update(thermal_tof_cols)\n\n        # Get feature columns (all columns except excluded ones)\n        feature_cols = [col for col in df.columns if col not in excluded_cols]\n\n        # Build sequences\n        for seq_id, group in df.groupby('sequence_id'):\n            data = group[feature_cols].copy()\n\n            # fill missing values forward and backward\n            data = pd.DataFrame(data).ffill().bfill().fillna(0).values\n\n            # Standardize features\n            data = self.scaler.fit_transform(data)\n            self.features.append(data)  # Convert to numpy array\n\n        # Store both gesture labels and sequence types\n        self.labels = df.groupby('sequence_id')['gesture'].first().values\n        self.sequence_types = df.groupby('sequence_id')['sequence_type'].first().values\n\n        # Pad sequences to the same length\n        self.features = nn.utils.rnn.pad_sequence([torch.tensor(f, dtype=torch.float32, device=self.device) for f in self.features], batch_first=True)\n\n        # Encode labels\n        self.label_encoder = LabelEncoder()\n        self.labels = self.label_encoder.fit_transform(self.labels)\n        self.labels = torch.tensor(self.labels, dtype=torch.long, device=self.device)\n\n    def print(self):\n        print(self.features.shape)\n        print(self.labels.shape)\n        print(self.features[:3])\n        print(self.labels[:3])\n        print(\"\\nSequence types sample:\", self.sequence_types[:3])\n\n    def __len__(self):\n        return len(self.features)\n\n    def __getitem__(self, idx):\n        feature = self.features[idx]\n        label = self.labels[idx]\n\n        if self.transform:\n            feature = self.transform(feature)\n\n        return feature, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:39.600306Z","iopub.execute_input":"2025-12-07T16:52:39.600567Z","iopub.status.idle":"2025-12-07T16:52:39.613249Z","shell.execute_reply.started":"2025-12-07T16:52:39.600552Z","shell.execute_reply":"2025-12-07T16:52:39.612676Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset = CMIIMUDataset(train_df, device=device)\ndataset.print()\n\n# Print unique gestures and their encoded values\nprint(\"\\nUnique gestures and their encoded values:\")\nfor gesture, encoded in zip(np.unique(train_df['gesture']), torch.unique(dataset.labels)):\n    print(f\"Gesture: {gesture} -> Encoded: {encoded}\")\n\nprint(\"\\nNumber of unique gestures:\", len(np.unique(train_df['gesture'])))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:39.614055Z","iopub.execute_input":"2025-12-07T16:52:39.614246Z","iopub.status.idle":"2025-12-07T16:52:48.939445Z","shell.execute_reply.started":"2025-12-07T16:52:39.614225Z","shell.execute_reply":"2025-12-07T16:52:48.938691Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\nUnique sequence types:\")\nprint(train_df['sequence_type'].unique())\n\n# Print example gestures for each sequence type\nprint(\"\\nExample gestures for each sequence type:\")\nfor seq_type in train_df['sequence_type'].unique():\n    gestures = train_df[train_df['sequence_type'] == seq_type]['gesture'].unique()\n    print(f\"\\n{seq_type}:\")\n    for g in gestures:\n        print(f\"- {g}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:48.940132Z","iopub.execute_input":"2025-12-07T16:52:48.940351Z","iopub.status.idle":"2025-12-07T16:52:49.756335Z","shell.execute_reply.started":"2025-12-07T16:52:48.940326Z","shell.execute_reply":"2025-12-07T16:52:49.755735Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\nChecking for NaN values in features:\")\nprint(\"NaN values in features:\", torch.isnan(dataset.features).any())\nprint(\"Number of NaN values:\", torch.isnan(dataset.features).sum())\n\nprint(\"\\nChecking feature statistics:\")\nprint(\"Min value:\", torch.min(dataset.features))\nprint(\"Max value:\", torch.max(dataset.features))\nprint(\"Mean value:\", torch.mean(dataset.features))\nprint(\"Std value:\", torch.std(dataset.features))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.757045Z","iopub.execute_input":"2025-12-07T16:52:49.757244Z","iopub.status.idle":"2025-12-07T16:52:49.768761Z","shell.execute_reply.started":"2025-12-07T16:52:49.757219Z","shell.execute_reply":"2025-12-07T16:52:49.767994Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(dataset.features.shape)\nprint(dataset.labels.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.769576Z","iopub.execute_input":"2025-12-07T16:52:49.769934Z","iopub.status.idle":"2025-12-07T16:52:49.776197Z","shell.execute_reply.started":"2025-12-07T16:52:49.769917Z","shell.execute_reply":"2025-12-07T16:52:49.775431Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"type(dataset.features), type(dataset.labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.776983Z","iopub.execute_input":"2025-12-07T16:52:49.777483Z","iopub.status.idle":"2025-12-07T16:52:49.790933Z","shell.execute_reply.started":"2025-12-07T16:52:49.777460Z","shell.execute_reply":"2025-12-07T16:52:49.790213Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"markdown","source":"## Linear","metadata":{}},{"cell_type":"code","source":"class SimpleNN(nn.Module):\n    def __init__(self, input_size, hidden_size, num_classes):\n        super(SimpleNN, self).__init__()\n        self.fc1 = nn.Linear(input_size, hidden_size)\n        self.relu = nn.ReLU()\n        self.fc2 = nn.Linear(hidden_size, num_classes)\n\n    def forward(self, x):\n        out = self.fc1(x)\n        out = self.relu(out)\n        out = self.fc2(out)\n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.792485Z","iopub.execute_input":"2025-12-07T16:52:49.792686Z","iopub.status.idle":"2025-12-07T16:52:49.803482Z","shell.execute_reply.started":"2025-12-07T16:52:49.792671Z","shell.execute_reply":"2025-12-07T16:52:49.802806Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ComplexNN(nn.Module):\n    def __init__(self, input_size, hidden_size, num_classes):\n        super(ComplexNN, self).__init__()\n        self.fc1 = nn.Linear(input_size, hidden_size)\n        self.relu = nn.ReLU()\n        self.fc2 = nn.Linear(hidden_size, hidden_size // 2)\n        self.relu2 = nn.ReLU()\n        self.fc3 = nn.Linear(hidden_size // 2, hidden_size // 4)\n        self.relu3 = nn.ReLU()\n        self.fc4 = nn.Linear(hidden_size // 4, hidden_size // 4)\n        self.relu4 = nn.ReLU()\n        self.fcout = nn.Linear(hidden_size // 4, num_classes)\n\n    def forward(self, x):\n        out = self.fc1(x)\n        out = self.relu(out)\n        out = self.fc2(out)\n        out = self.relu2(out)\n        out = self.fc3(out)\n        out = self.relu3(out)\n        out = self.fc4(out)\n        out = self.relu4(out)\n        out = self.fcout(out)\n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.804255Z","iopub.execute_input":"2025-12-07T16:52:49.804455Z","iopub.status.idle":"2025-12-07T16:52:49.817222Z","shell.execute_reply.started":"2025-12-07T16:52:49.804440Z","shell.execute_reply":"2025-12-07T16:52:49.816574Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## LSTMs","metadata":{}},{"cell_type":"code","source":"class LSTMModel(nn.Module):\n    def __init__(self, input_size, hidden_size, num_layers, num_classes, dropout_rate=0.2):\n        super(LSTMModel, self).__init__()\n        self.hidden_size = hidden_size\n        self.num_layers = num_layers\n        \n        # Batch normalization dla danych wejściowych\n        self.batch_norm_input = nn.BatchNorm1d(input_size)\n        \n        # LSTM z dropout\n        self.lstm = nn.LSTM(\n            input_size, \n            hidden_size, \n            num_layers, \n            batch_first=True,\n            dropout=dropout_rate if num_layers > 1 else 0\n        )\n        \n        # Batch normalization po LSTM\n        self.batch_norm_hidden = nn.BatchNorm1d(hidden_size)\n        \n        # Dropout przed warstwą liniową\n        self.dropout = nn.Dropout(dropout_rate)\n        \n        # Warstwa liniowa\n        self.fc1 = nn.Linear(hidden_size, hidden_size)\n        self.relu1 = nn.ReLU()\n        self.fc2 = nn.Linear(hidden_size, hidden_size // 4)\n        self.relu2 = nn.ReLU()\n        self.dropout2 = nn.Dropout(dropout_rate)\n        self.fcout = nn.Linear(hidden_size // 4, num_classes)\n\n    def forward(self, x):\n\n        x = x.permute(0, 2, 1)  # (batch_size, input_size, seq_length)\n        # Normalizacja danych wejściowych\n        x = self.batch_norm_input(x)\n\n        x = x.permute(0, 2, 1)  # (batch_size, seq_length, input_size)\n        \n        # Forward LSTM\n        out, _ = self.lstm(x, None)  # None oznacza, że stany ukryte są inicjalizowane zerami\n        \n        # Bierzemy ostatni output\n        out = out[:, -1, :]\n        \n        # Normalizacja po LSTM\n        out = self.batch_norm_hidden(out)\n        \n        # Dropout\n        out = self.dropout(out)\n        \n        # Warstwa liniowa\n        out = self.fc1(out)\n        out = self.relu1(out)\n        out = self.fc2(out)\n        out = self.relu2(out)\n        out = self.dropout2(out)\n        out = self.fcout(out)\n        \n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.817962Z","iopub.execute_input":"2025-12-07T16:52:49.818170Z","iopub.status.idle":"2025-12-07T16:52:49.830951Z","shell.execute_reply.started":"2025-12-07T16:52:49.818156Z","shell.execute_reply":"2025-12-07T16:52:49.830432Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1D CNN","metadata":{}},{"cell_type":"code","source":"class CNN1D(nn.Module):\n    def __init__(self, input_size, num_classes):\n        super(CNN1D, self).__init__()\n\n        #Block in\n        self.convin = nn.Conv1d(in_channels=input_size, out_channels=512, kernel_size=7, padding=1)\n        self.bnin = nn.BatchNorm1d(512)\n        self.relubin = nn.ReLU()\n        self.poolin = nn.MaxPool1d(kernel_size=2)\n        self.dropoutin = nn.Dropout(0.2)\n        \n        #Block 1\n        self.conv1 = nn.Conv1d(in_channels=512, out_channels=768, kernel_size=5, padding=1)\n        self.bn1 = nn.BatchNorm1d(768)\n        self.relu1 = nn.ReLU()\n        self.pool1 = nn.MaxPool1d(kernel_size=2)\n        self.dropout1 = nn.Dropout(0.3)\n        \n        #Block 2\n        self.conv2 = nn.Conv1d(in_channels=768, out_channels=1024, kernel_size=3, padding=1)\n        self.bn2 = nn.BatchNorm1d(1024)\n        self.relu2 = nn.ReLU()\n        self.pool2 = nn.MaxPool1d(kernel_size=2)\n        self.dropout2 = nn.Dropout(0.3)\n        \n        #Block 3\n        self.conv3 = nn.Conv1d(in_channels=1024, out_channels=1536, kernel_size=3, padding=1)\n        self.bn3 = nn.BatchNorm1d(1536)\n        self.relu3 = nn.ReLU()\n        self.pool3 = nn.MaxPool1d(kernel_size=2)\n        self.dropout3 = nn.Dropout(0.4)\n\n        #Block 4\n        self.conv4 = nn.Conv1d(in_channels=1536, out_channels=2048, kernel_size=3, padding=1)\n        self.bn4 = nn.BatchNorm1d(2048)\n        self.relu4 = nn.ReLU()\n        self.pool4 = nn.AdaptiveAvgPool1d(1)\n        self.dropout4 = nn.Dropout(0.4)\n\n        #Dense layers\n        self.fc1 = nn.Linear(2048, 2048)\n        self.relu_fc1 = nn.ReLU()\n        self.dropout_fc1 = nn.Dropout(0.5)\n        \n        self.fc2 = nn.Linear(2048, 1024)\n        self.relu_fc2 = nn.ReLU()\n        self.dropout_fc2 = nn.Dropout(0.4)\n\n        self.fc3 = nn.Linear(1024, 512)\n        self.relu_fc3 = nn.ReLU()\n        self.dropout_fc3 = nn.Dropout(0.3)\n\n        self.fcout = nn.Linear(512, num_classes)\n\n\n    def forward(self, x):\n        # x shape: (batch_size, seq_length, input_size)\n        x = x.permute(0, 2, 1)  # Change to (batch_size, input_size, seq_length) for Conv1d\n\n        x = self.convin(x)\n        x = self.bnin(x)\n        x = self.relubin(x)\n        x = self.poolin(x)\n        x = self.dropoutin(x)\n        \n        x = self.conv1(x)\n        x = self.bn1(x)\n        x = self.relu1(x)\n        x = self.pool1(x)\n        x = self.dropout1(x)\n\n        x = self.conv2(x)\n        x = self.bn2(x)\n        x = self.relu2(x)\n        x = self.pool2(x)\n        x = self.dropout2(x)\n\n        x = self.conv3(x)\n        x = self.bn3(x)\n        x = self.relu3(x)\n        x = self.pool3(x)\n        x = self.dropout3(x)\n\n        x = self.conv4(x)\n        x = self.bn4(x)\n        x = self.relu4(x)\n        x = self.pool4(x)\n        x = self.dropout4(x)\n\n        x = x.squeeze(-1)\n\n        x = self.fc1(x)\n        x = self.relu_fc1(x)\n        x = self.dropout_fc1(x)\n\n        x = self.fc2(x)\n        x = self.relu_fc2(x)\n        x = self.dropout_fc2(x)\n\n        x = self.fc3(x)\n        x = self.relu_fc3(x)\n        x = self.dropout_fc3(x)\n\n        x = self.fcout(x)\n\n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.834740Z","iopub.execute_input":"2025-12-07T16:52:49.834985Z","iopub.status.idle":"2025-12-07T16:52:49.849318Z","shell.execute_reply.started":"2025-12-07T16:52:49.834961Z","shell.execute_reply":"2025-12-07T16:52:49.848709Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1D CNN + LSTM + Attn","metadata":{}},{"cell_type":"code","source":"class CNNLSTMAttention(nn.Module):\n    def __init__(self, input_channels, num_classes, lstm_hidden=256, lstm_layers=1, bidirectional=False, attn_hidden=128, dropout=0.3):\n        super(CNNLSTMAttention, self).__init__()\n\n        #CNN feature extractor\n        self.cnn = nn.Sequential(\n            nn.Conv1d(in_channels=input_channels, out_channels=512, kernel_size=7, padding=3),\n            nn.ReLU(inplace=True),\n            nn.BatchNorm1d(512),\n            nn.MaxPool1d(2),\n            nn.Dropout(dropout),\n\n            nn.Conv1d(in_channels=512, out_channels=768, kernel_size=5, padding=2),\n            nn.ReLU(inplace=True),\n            nn.BatchNorm1d(768),\n            nn.MaxPool1d(2),\n            nn.Dropout(dropout),\n\n            nn.Conv1d(in_channels=768, out_channels=1024, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.BatchNorm1d(1024),\n            nn.MaxPool1d(2),\n            nn.Dropout(dropout),\n\n            nn.Conv1d(in_channels=1024, out_channels=1536, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.BatchNorm1d(1536),\n            nn.MaxPool1d(2),\n            nn.Dropout(dropout),\n        )\n\n        #LSTM\n        self.lstm = nn.LSTM(\n            input_size=1536,\n            batch_first=True,\n            bidirectional=bidirectional,\n            dropout=0.0 if lstm_layers == 1 else 0.1,\n            num_layers=lstm_layers,\n            hidden_size=lstm_hidden\n        )\n\n        self.dir_mult = 2 if bidirectional else 1\n        feat_dim = lstm_hidden * self.dir_mult\n\n        #Additive Attention\n        self.attn_fc = nn.Linear(feat_dim, attn_hidden)\n        self.attn_v = nn.Linear(attn_hidden, 1, bias=False)\n\n        #MLP\n        self.head = nn.Sequential(\n            nn.Dropout(0.5),\n            nn.Linear(feat_dim, 2048),\n            nn.ReLU(inplace=True),\n\n            nn.Dropout(0.4),\n            nn.Linear(2048, 1024),\n            nn.ReLU(inplace=True),\n\n            nn.Dropout(0.3),\n            nn.Linear(1024, 512),\n            nn.ReLU(inplace=True),\n\n            nn.Dropout(0.3),\n            nn.Linear(512, num_classes)\n        )\n\n    def forward(self, x):\n        # x: (N, L, C_in) --> (N, C_in, L)\n        x = x.permute(0, 2, 1)\n        x = self.cnn(x)\n        x = x.permute(0, 2, 1)\n        # x: (N, C_feat, L') --> (N, L', C_feat)\n\n        outputs, _ = self.lstm(x)   # (N, L', H*dirs)\n\n        # Attention\n        attn_scores = torch.tanh(self.attn_fc(outputs))     # (N, L', attn_hidden)\n        attn_scores = self.attn_v(attn_scores).squeeze(-1)  # (N, L')\n        attn_weights = F.softmax(attn_scores, dim=1)\n        context = torch.bmm(attn_weights.unsqueeze(1), outputs).squeeze(1)  # (N, H*dirs)\n\n        #MLP\n        logits = self.head(context) # (N, num_classes)\n\n        return logits","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.850192Z","iopub.execute_input":"2025-12-07T16:52:49.850457Z","iopub.status.idle":"2025-12-07T16:52:49.866782Z","shell.execute_reply.started":"2025-12-07T16:52:49.850434Z","shell.execute_reply":"2025-12-07T16:52:49.866034Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training loops","metadata":{}},{"cell_type":"code","source":"generator1 = torch.Generator().manual_seed(42)\ntrain_data, val_data = random_split(dataset, [0.8, 0.2], generator=generator1)\ndef get_dataloaders(train_data, val_data, batch_size=1024):\n    train_dataloader = DataLoader(\n            train_data, \n            batch_size=batch_size, \n            shuffle=True,  # Włącz shuffle dla danych treningowych\n            num_workers=0  # Użyj wielu workerów do ładowania danych\n            #pin_memory=True  # Przyspiesz transfer do GPU\n        )\n        \n    val_dataloader = DataLoader(\n            val_data, \n            batch_size=batch_size, \n            shuffle=False,  # Nie shuffle'uj danych walidacyjnych\n            num_workers=0\n            #pin_memory=True\n        )\n    return train_dataloader, val_dataloader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.867630Z","iopub.execute_input":"2025-12-07T16:52:49.868258Z","iopub.status.idle":"2025-12-07T16:52:49.884794Z","shell.execute_reply.started":"2025-12-07T16:52:49.868240Z","shell.execute_reply":"2025-12-07T16:52:49.884054Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def calculate_training_metrics(predictions, labels):\n    accuracy = (predictions == labels).mean()\n    precision = precision_score(y_true=labels, y_pred=predictions, average='weighted', zero_division=0)\n    recall = recall_score(y_true=labels, y_pred=predictions, average='weighted', zero_division=0)\n    f1 = f1_score(y_true=labels, y_pred=predictions, average='weighted', zero_division=0)\n\n    # Add tp, fp, fn, tn calculations for each class\n    cm = confusion_matrix(labels, predictions)\n    tp = np.diag(cm)\n    fp = cm.sum(axis=0) - tp\n    fn = cm.sum(axis=1) - tp\n    tn = cm.sum() - (tp + fp + fn)\n\n    return accuracy, precision, recall, f1, cm, tp, fp, fn, tn, labels, predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.885448Z","iopub.execute_input":"2025-12-07T16:52:49.885616Z","iopub.status.idle":"2025-12-07T16:52:49.896741Z","shell.execute_reply.started":"2025-12-07T16:52:49.885602Z","shell.execute_reply":"2025-12-07T16:52:49.896032Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch(train_loader, model, loss_fn, optimizer, epoch_idx):\n    running_loss = 0.0\n    last_loss = 0.0\n    quarter = max(1, len(train_loader) // 4) # log every 1/4 of an epoch\n    n_in_window = 0\n    all_predictions = []\n    all_labels = []\n    all_probabilities = []\n\n    for i, data in enumerate(train_loader):\n        inputs, labels = data\n        inputs, labels = inputs.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n\n        outputs = model(inputs)\n        if outputs.dim() == 2:\n            logits = outputs\n        else:\n            logits = outputs.permute(0, 2, 1).mean(dim=2)  # Adjust dimensions for CrossEntropyLoss\n        loss = loss_fn(logits, labels)\n        loss.backward()\n        optimizer.step()\n\n        # Gather data and report\n        running_loss += loss.item()\n        n_in_window += 1\n\n        probabilities = torch.softmax(logits, dim=1)\n        _, predicted = torch.max(logits.data, 1)\n        all_predictions.extend(predicted.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n        all_probabilities.extend(probabilities.detach().cpu().numpy())\n\n        if ((i + 1) % quarter == 0) or ((i + 1) == len(train_loader)):\n            last_loss = running_loss / n_in_window  # średnia w oknie\n            pct = int(round(100 * (i + 1) / len(train_loader)))\n            print(f\"  epoch {epoch_idx+1} {pct}% ({i+1}/{len(train_loader)}) - loss: {last_loss:.6f}\")\n\n            # reset okna\n            running_loss = 0.\n            n_in_window = 0\n\n    # Calculate metrics at the end of epoch\n    all_predictions = np.array(all_predictions)\n    all_labels = np.array(all_labels)\n    all_probabilities = np.array(all_probabilities)\n    metrics = calculate_training_metrics(all_predictions, all_labels)\n\n    return last_loss, metrics\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.897536Z","iopub.execute_input":"2025-12-07T16:52:49.897803Z","iopub.status.idle":"2025-12-07T16:52:49.911695Z","shell.execute_reply.started":"2025-12-07T16:52:49.897779Z","shell.execute_reply":"2025-12-07T16:52:49.911012Z"}},"outputs":[],"execution_count":null},{"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        # 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 ValueError(f\"Invalid gesture values in submission: {invalid_types}\")\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-12-07T16:52:49.912358Z","iopub.execute_input":"2025-12-07T16:52:49.912590Z","iopub.status.idle":"2025-12-07T16:52:49.928895Z","shell.execute_reply.started":"2025-12-07T16:52:49.912575Z","shell.execute_reply":"2025-12-07T16:52:49.928194Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def validate(val_loader: DataLoader, model: nn.Module, loss_fn: nn):\n    running_loss = 0.0\n    n_in_window = 0\n    all_predictions = []\n    all_labels = []\n    all_probabilities = []\n    \n    with torch.no_grad():\n        for i, data in enumerate(val_loader):\n            inputs, labels = data\n            inputs, labels = inputs.to(device), labels.to(device)\n\n            outputs = model(inputs)\n            if outputs.dim() == 2:\n                logits = outputs\n            else:\n                logits = outputs.permute(0, 2, 1).mean(dim=2)  # Adjust dimensions for CrossEntropyLoss\n            loss = loss_fn(logits, labels)\n\n            running_loss += loss.item()\n            n_in_window += 1\n\n            probabilities = torch.softmax(logits, dim=1)\n            _, predicted = torch.max(logits.data, 1)\n            all_predictions.extend(predicted.cpu().numpy())\n            all_labels.extend(labels.cpu().numpy())\n            all_probabilities.extend(probabilities.cpu().numpy())\n\n    # Calculate standard metrics\n    all_predictions = np.array(all_predictions)\n    all_labels = np.array(all_labels)\n    all_probabilities = np.array(all_probabilities)\n    metrics = calculate_training_metrics(all_predictions, all_labels)\n    \n    avg_loss = running_loss / n_in_window\n    print(f\"  Validation - loss: {avg_loss:.6f}, accuracy: {metrics[0]*100:.2f}%, precision: {metrics[1]:.4f}, recall: {metrics[2]:.4f}, f1: {metrics[3]:.4f}\")\n\n    return avg_loss, metrics, all_probabilities","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.929543Z","iopub.execute_input":"2025-12-07T16:52:49.929704Z","iopub.status.idle":"2025-12-07T16:52:49.946810Z","shell.execute_reply.started":"2025-12-07T16:52:49.929691Z","shell.execute_reply":"2025-12-07T16:52:49.946285Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class EarlyStopping_Accuracy:\n    \"\"\"Early stops the training if validation accuracy doesn't improve after a given patience.\"\"\"\n    def __init__(self, patience=7, verbose=False, delta=0, path='checkpoint.pt'):\n        \"\"\"\n        Args:\n            patience (int): How long to wait after last time validation accuracy improved.\n                            Default: 7\n            verbose (bool): If True, prints a message for each validation accuracy improvement. \n                            Default: False\n            delta (float): Minimum change in the monitored quantity to qualify as an improvement.\n                            Default: 0\n            path (str): Path for the checkpoint to be saved to.\n                            Default: 'checkpoint.pt'\n        \"\"\"\n        self.patience = patience\n        self.verbose = verbose\n        self.counter = 0\n        self.best_score = None\n        self.early_stop = False\n        self.val_accuracy_max = 0.0\n        self.delta = delta\n        self.path = path\n\n    def __call__(self, val_accuracy, model):\n\n        score = val_accuracy\n\n        if self.best_score is None:\n            self.best_score = score\n            self.save_checkpoint(val_accuracy, model)\n        elif score < self.best_score + self.delta:\n            self.counter += 1\n            if self.verbose:\n                print(f'EarlyStopping counter: {self.counter} out of {self.patience}')\n            if self.counter >= self.patience:\n                self.early_stop = True\n        else:\n            self.best_score = score\n            self.save_checkpoint(val_accuracy, model)\n            self.counter = 0\n\n    def save_checkpoint(self, val_accuracy, model):\n        if self.verbose:\n            if self.best_score is None:\n                print(f'First improvement: Validation accuracy {val_accuracy:.6f}. Saving model...')\n            else:\n                print(f'Validation accuracy increased ({self.best_score:.6f} --> {val_accuracy:.6f}). Saving model...')\n        \n        torch.save(model.state_dict(), self.path)\n        self.val_accuracy_max = val_accuracy  # vẫn giữ để tương thích nếu ai dùng\n        # Quan trọng: cập nhật cả best_score (dù đã làm ở __call__ rồi nhưng an toàn thì cập nhật luôn)\n        self.best_score = val_accuracy","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.947469Z","iopub.execute_input":"2025-12-07T16:52:49.947656Z","iopub.status.idle":"2025-12-07T16:52:49.963497Z","shell.execute_reply.started":"2025-12-07T16:52:49.947642Z","shell.execute_reply":"2025-12-07T16:52:49.962795Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def calculate_metrics(predictions, labels):\n\n    precision = precision_score(labels, predictions, average='weighted')\n    recall = recall_score(labels, predictions, average='weighted')\n    f1 = f1_score(labels, predictions, average='weighted')\n    cm = confusion_matrix(labels, predictions)\n\n    return precision, recall, f1, cm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:52:49.964226Z","iopub.execute_input":"2025-12-07T16:52:49.964541Z","iopub.status.idle":"2025-12-07T16:52:49.979694Z","shell.execute_reply.started":"2025-12-07T16:52:49.964519Z","shell.execute_reply":"2025-12-07T16:52:49.979025Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def write_to_tensorboard(writer, epoch, train_loss, val_loss, val_metrics, train_metrics, learning_rate):\n    # val_metrics: accuracy, precision, recall, f1, cm, tp, fp, fn, tn, labels, predictions\n    #               0           1           2   3   4   5    6   7   8      9       10\n    # train_metrics: accuracy, precision, recall, f1, cm, tp, fp, fn, tn, labels, predictions\n    writer.add_scalar('Loss/train', train_loss, epoch + 1)\n    writer.add_scalar('Loss/val', val_loss, epoch + 1)\n    writer.add_scalars('Loss', {'Training': train_loss, 'Validation': val_loss}, epoch + 1)\n    writer.add_scalar('Accuracy/val', val_metrics[0]*100, epoch + 1)\n    writer.add_scalar('Precision/val', val_metrics[1], epoch + 1)\n    writer.add_scalar('Recall/val', val_metrics[2], epoch + 1)\n    writer.add_scalar('F1-score/val', val_metrics[3], epoch + 1)\n    writer.add_scalar('Accuracy/train', train_metrics[0]*100, epoch + 1)\n    writer.add_scalar('Precision/train', train_metrics[1], epoch + 1)\n    writer.add_scalar('Recall/train', train_metrics[2], epoch + 1)\n    writer.add_scalar('F1-score/train', train_metrics[3], epoch + 1)\n    writer.add_scalar('Learning Rate', learning_rate, epoch + 1)\n\n    writer.flush()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:56:49.925079Z","iopub.execute_input":"2025-12-07T16:56:49.925351Z","iopub.status.idle":"2025-12-07T16:56:49.931174Z","shell.execute_reply.started":"2025-12-07T16:56:49.925332Z","shell.execute_reply":"2025-12-07T16:56:49.930305Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_loop(train_data: Subset, val_data: Subset, model: nn.Module, loss_fn: nn, optimizer: torch.optim, scheduler: torch.optim.lr_scheduler, name: str, patience=10, epochs=10, batch_size=32, warmup_epochs=0, warmup_lr=1e-5):\n    timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')\n    #writer = SummaryWriter('/kaggle/working')\n    os.makedirs(\"/kaggle/working/models\", exist_ok=True)\n    early_stopping = EarlyStopping_Accuracy(patience=patience, verbose=True, path='/kaggle/working/models/best_accuracy_model.pt')\n    epoch_number = 0\n\n    all_metrics = []\n    all_vmetrics = []\n    all_probabilities = []\n\n    best_vloss = 1_000_000.\n    best_vloss_epoch = -1\n\n    train_dataloader, validation_loader = get_dataloaders(train_data, val_data, batch_size=batch_size)\n\n    for epoch in range(epochs):\n        print('EPOCH {}:'.format(epoch_number + 1))\n\n        # Make sure gradient tracking is on, and do a pass over the data\n        model.train(True)\n        avg_loss, metrics = train_one_epoch(train_dataloader, model, loss_fn, optimizer, epoch_number)\n\n        # Set the model to evaluation mode, disabling dropout and using population\n        # statistics for batch normalization.\n        model.eval()\n\n        avg_vloss, vmetrics, probabilities = validate(validation_loader, model, loss_fn)\n\n        # Step the scheduler\n        if epoch_number < warmup_epochs:\n            # Linear warmup\n            warmup_lr = (epoch_number + 1) / warmup_epochs * optimizer.defaults['lr']\n            for param_group in optimizer.param_groups:\n                param_group['lr'] = warmup_lr\n            print(f\"  Warmup LR: {warmup_lr:.6f}\")\n        else:\n            scheduler.step(vmetrics[0])  # Step based on validation accuracy\n\n        print('LOSS train {} valid {}'.format(avg_loss, avg_vloss))\n\n        # Log the running loss averaged per batch\n        # for both training and validation\n        write_to_tensorboard(writer, epoch_number, avg_loss, avg_vloss, vmetrics, metrics, \n                           optimizer.param_groups[0]['lr'])\n\n        # Track best performance, and save the model's state\n        if avg_vloss < best_vloss:\n            best_vloss = avg_vloss\n            best_vloss_epoch = epoch_number\n            model_path = '/kaggle/working/models/best_loss_model.pt'\n            torch.save(model.state_dict(), model_path)\n\n        all_metrics.append(metrics)\n        all_vmetrics.append(vmetrics)\n        all_probabilities.append(probabilities)\n\n        # Early stopping\n        early_stopping(vmetrics[0], model) # Monitor validation accuracy\n        if early_stopping.early_stop:\n            print(\"Early stopping\")\n            break\n\n        epoch_number += 1\n\n    print(\"Training complete.\")\n    print(f\"Best validation loss: {best_vloss:.6f} at epoch {best_vloss_epoch + 1}\")\n\n    data = {\n        \"model\": model,\n        \"writer\": writer,\n        \"all_metrics\": all_metrics,\n        \"all_vmetrics\": all_vmetrics,\n        \"all_probabilities\": all_probabilities,\n        \"model_path\": '/kaggle/working/models/best_accuracy_model.pt'\n    }\n    return data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:56:53.154252Z","iopub.execute_input":"2025-12-07T16:56:53.154551Z","iopub.status.idle":"2025-12-07T16:56:53.164289Z","shell.execute_reply.started":"2025-12-07T16:56:53.154529Z","shell.execute_reply":"2025-12-07T16:56:53.163566Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def evaluate_model(model, val_data, batch_size=128):\n    \"\"\"\n    Evaluate model on validation data, calculating both standard and competition metrics\n    \"\"\"\n    model.eval()\n    val_loader = DataLoader(val_data, batch_size=batch_size, shuffle=False)\n    \n    all_predictions = []\n    all_probabilities = []\n    all_true_labels = []\n    \n    with torch.no_grad():\n        for inputs, labels in val_loader:\n            inputs = inputs.to(device)\n            outputs = model(inputs)\n            \n            if outputs.dim() == 2:\n                logits = outputs\n            else:\n                logits = outputs.permute(0, 2, 1).mean(dim=2)\n                \n            probabilities = torch.softmax(logits, dim=1)\n            _, predicted = torch.max(logits.data, 1)\n            \n            all_predictions.extend(predicted.cpu().numpy())\n            all_probabilities.extend(probabilities.cpu().numpy())\n            all_true_labels.extend(labels.cpu().numpy())\n    \n    # Convert lists to numpy arrays\n    all_predictions = np.array(all_predictions)\n    all_probabilities = np.array(all_probabilities)\n    all_true_labels = np.array(all_true_labels)\n    \n    # Get sequence types for validation samples\n    val_indices = val_data.indices\n    sequence_types = dataset.sequence_types[val_indices]\n    \n    # Convert predictions and true labels back to original gesture names\n    original_predictions = dataset.label_encoder.inverse_transform(all_predictions)\n    original_labels = dataset.label_encoder.inverse_transform(all_true_labels)\n    \n    # Calculate standard metrics\n    accuracy = (all_predictions == all_true_labels).mean()\n    precision = precision_score(all_true_labels, all_predictions, average='weighted', zero_division=0)\n    recall = recall_score(all_true_labels, all_predictions, average='weighted', zero_division=0)\n    f1 = f1_score(all_true_labels, all_predictions, average='weighted', zero_division=0)\n    \n    print(\"\\nStandard Metrics:\")\n    print(f\"Accuracy: {accuracy*100:.2f}%\")\n    print(f\"Precision: {precision:.4f}\")\n    print(f\"Recall: {recall:.4f}\")\n    print(f\"F1 Score: {f1:.4f}\")\n    \n    # Calculate competition metrics using official implementation\n    metric = CompetitionMetric()\n    val_submission = pd.DataFrame({'gesture': original_predictions})\n    val_solution = pd.DataFrame({'gesture': original_labels})\n    competition_score = metric.calculate_hierarchical_f1(val_solution, val_submission)\n    \n    print(\"\\nCompetition Metrics (Official):\")\n    print(f\"Final Score: {competition_score:.4f}\")\n    \n    # Print sequence type statistics\n    target_mask = np.isin(original_labels, metric.target_gestures)\n    print(\"\\nSequence Statistics:\")\n    print(f\"Target sequences: {target_mask.sum()}\")\n    print(f\"Non-target sequences: {len(target_mask) - target_mask.sum()}\")\n    \n    if target_mask.sum() > 0:\n        print(\"\\nTarget gestures present in validation set:\")\n        target_gestures = np.unique(original_labels[target_mask])\n        for gesture in target_gestures:\n            print(f\"- {gesture}\")\n    \n        # Print confusion matrix for target gestures\n        target_indices = np.where(target_mask)[0]\n        target_predictions = original_predictions[target_indices]\n        target_true_labels = original_labels[target_indices]\n        unique_targets = np.unique(target_true_labels)\n        \n        print(\"\\nConfusion Matrix for Target Gestures:\")\n        cm = confusion_matrix(target_true_labels, target_predictions, labels=unique_targets)\n        print(\"True labels (rows) vs Predicted labels (columns):\")\n        print(\"Labels:\", unique_targets)\n        print(cm)\n    \n    return {\n        'standard_metrics': {\n            'accuracy': accuracy,\n            'precision': precision,\n            'recall': recall,\n            'f1': f1\n        },\n        'competition_score': competition_score,\n        'predictions': all_predictions,\n        'probabilities': all_probabilities,\n        'true_labels': all_true_labels,\n        'sequence_types': sequence_types\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:56:56.249093Z","iopub.execute_input":"2025-12-07T16:56:56.249383Z","iopub.status.idle":"2025-12-07T16:56:56.284865Z","shell.execute_reply.started":"2025-12-07T16:56:56.249363Z","shell.execute_reply":"2025-12-07T16:56:56.282205Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_pr_curve(y_true, probabilities, writer, class_names=None):\n    \"\"\"\n    Rysuje krzywe PR (Precision-Recall) dla każdej klasy.\n    \n    Args:\n        y_true: prawdziwe etykiety\n        probabilities: macierz prawdopodobieństw dla każdej klasy (wynik softmax)\n        writer: obiekt SummaryWriter z TensorBoard\n        class_names: opcjonalna lista nazw klas\n    \"\"\"\n    from sklearn.metrics import precision_recall_curve\n    import matplotlib.pyplot as plt\n    \n    num_classes = probabilities.shape[1]\n    \n    for i in range(num_classes):\n        class_name = class_names[i] if class_names is not None else f\"class_{i}\"\n        \n        # Convert labels to binary format (1 for a given class, 0 for the rest)\n        y_true_binary = (y_true == i).astype(np.int32)\n        \n        # Get probabilities for a given class\n        class_probs = probabilities[:, i]\n        \n        # Calculate precision and recall for different thresholds\n        precision, recall, thresholds = precision_recall_curve(y_true_binary, class_probs)\n        \n        # Draw the PR curve\n        plt.figure(figsize=(8, 6))\n        plt.plot(recall, precision, label=f'Class {class_name}')\n        plt.xlabel('Recall')\n        plt.ylabel('Precision')\n        plt.title(f'Precision-Recall Curve - {class_name}')\n        plt.grid(True)\n        plt.legend()\n        \n        # Save the graph to a buffer and add it to TensorBoard\n        buf = io.BytesIO()\n        plt.savefig(buf, format='png')\n        buf.seek(0)\n        \n        # Convert buffer to tensor\n        img = Image.open(buf)\n        img_tensor = torch.from_numpy(np.array(img))\n        img_tensor = img_tensor.permute(2, 0, 1)  # HWC -> CHW\n        \n        # Add a graph to TensorBoard\n        writer.add_image(f'PR_Curves/{class_name}', img_tensor, global_step=0)\n        \n        # Also add raw data to the interactive chart\n        writer.add_pr_curve(\n            f'PR_Curves_Raw/{class_name}',\n            y_true_binary,\n            class_probs,\n            global_step=0\n        )\n        \n        plt.close()\n    \n    writer.flush()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:56:59.108699Z","iopub.execute_input":"2025-12-07T16:56:59.109454Z","iopub.status.idle":"2025-12-07T16:56:59.116425Z","shell.execute_reply.started":"2025-12-07T16:56:59.109427Z","shell.execute_reply":"2025-12-07T16:56:59.115589Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train","metadata":{}},{"cell_type":"code","source":"model = LSTMModel(input_size=dataset.features.shape[2], hidden_size=512, num_layers=1, num_classes=len(torch.unique(dataset.labels)), dropout_rate=0.4)\nmodel.to(device)\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:57:02.135435Z","iopub.execute_input":"2025-12-07T16:57:02.136003Z","iopub.status.idle":"2025-12-07T16:57:02.156385Z","shell.execute_reply.started":"2025-12-07T16:57:02.135980Z","shell.execute_reply":"2025-12-07T16:57:02.155622Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = ComplexNN(input_size=dataset.features.shape[2], hidden_size=256, num_classes=len(torch.unique(dataset.labels)))\nmodel.to(device)\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:57:04.520352Z","iopub.execute_input":"2025-12-07T16:57:04.520794Z","iopub.status.idle":"2025-12-07T16:57:04.528912Z","shell.execute_reply.started":"2025-12-07T16:57:04.520765Z","shell.execute_reply":"2025-12-07T16:57:04.528127Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = CNN1D(input_size=dataset.features.shape[2], num_classes=len(torch.unique(dataset.labels)))\nmodel.to(device)\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:57:07.063249Z","iopub.execute_input":"2025-12-07T16:57:07.063545Z","iopub.status.idle":"2025-12-07T16:57:07.254687Z","shell.execute_reply.started":"2025-12-07T16:57:07.063525Z","shell.execute_reply":"2025-12-07T16:57:07.253949Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = CNNLSTMAttention(input_channels=dataset.features.shape[2], num_classes=len(torch.unique(dataset.labels)), lstm_layers=2, bidirectional=True, dropout=0.3)\nmodel.to(device)\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:57:09.647361Z","iopub.execute_input":"2025-12-07T16:57:09.647635Z","iopub.status.idle":"2025-12-07T16:57:09.783109Z","shell.execute_reply.started":"2025-12-07T16:57:09.647617Z","shell.execute_reply":"2025-12-07T16:57:09.782212Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pytorch_total_params = sum(p.numel() for p in model.parameters())\nprint(f\"Total model parameters: {pytorch_total_params:,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:57:13.207939Z","iopub.execute_input":"2025-12-07T16:57:13.208202Z","iopub.status.idle":"2025-12-07T16:57:13.212716Z","shell.execute_reply.started":"2025-12-07T16:57:13.208183Z","shell.execute_reply":"2025-12-07T16:57:13.211991Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pytorch_trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\nprint(f\"Trainable model parameters: {pytorch_trainable_params:,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:57:15.207572Z","iopub.execute_input":"2025-12-07T16:57:15.207835Z","iopub.status.idle":"2025-12-07T16:57:15.213049Z","shell.execute_reply.started":"2025-12-07T16:57:15.207816Z","shell.execute_reply":"2025-12-07T16:57:15.212171Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"name = 'CMI_1D_CNN_v1'\nloss_fn = nn.CrossEntropyLoss().to(device)\nlearning_rate = 1e-3\noptimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer, \n    mode='max', \n    factor=0.1, \n    patience=8,\n    min_lr=1e-7\n)\n\n# Initializing weights\ndef init_weights(m):\n    if isinstance(m, nn.Linear):\n        nn.init.xavier_uniform_(m.weight)\n        if hasattr(m, 'bias') and m.bias is not None:\n            nn.init.zeros_(m.bias)\n    elif isinstance(m, nn.LSTM):\n        for name, param in m.named_parameters():\n            if 'weight_ih' in name:\n                nn.init.xavier_uniform_(param)\n            elif 'weight_hh' in name:\n                nn.init.orthogonal_(param)\n            elif 'bias' in name:\n                nn.init.zeros_(param)\n    elif isinstance(m, nn.Conv1d):\n        nn.init.kaiming_uniform_(m.weight, nonlinearity='relu')\n        if hasattr(m, 'bias') and m.bias is not None:\n            nn.init.zeros_(m.bias)\n\nmodel.apply(init_weights)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:57:17.321286Z","iopub.execute_input":"2025-12-07T16:57:17.322104Z","iopub.status.idle":"2025-12-07T16:57:17.333979Z","shell.execute_reply.started":"2025-12-07T16:57:17.322075Z","shell.execute_reply":"2025-12-07T16:57:17.333127Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.cuda.empty_cache()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:57:20.955468Z","iopub.execute_input":"2025-12-07T16:57:20.955973Z","iopub.status.idle":"2025-12-07T16:57:20.971341Z","shell.execute_reply.started":"2025-12-07T16:57:20.955953Z","shell.execute_reply":"2025-12-07T16:57:20.970676Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# (Khởi chạy quá trình huấn luyện)\n'''\nresults = train_loop(\n    model=model,            # Mô hình cần huấn luyện\n    train_data=train_data,  # Dữ liệu huấn luyện\n    val_data=val_data,      # Dữ liệu validation để theo dõi overfitting\n    loss_fn=loss_fn,        # Hàm loss dùng cho training\n    optimizer=optimizer,    # Optimizer (Adam, SGD, v.v.)\n    patience=15,            # Early stopping: dừng nếu không cải thiện sau 15 epoch\n    epochs=100,              # Tổng số epoch tối đa\n    batch_size=64,          # Batch size lớn hơn giúp train ổn định hơn\n    scheduler=scheduler,    # Learning rate scheduler\n    warmup_epochs=15,       # Số epoch làm warm-up LR\n    warmup_lr=1e-4,         # Learning rate trong giai đoạn warm-up\n    name=name               # Tên mô hình / cấu hình để lưu log, checkpoint\n)\n\n#(Đánh giá cuối cùng trên tập validation)\nprint(\"\\nEwaluacja końcowa modelu:\")\n\nevaluation = evaluate_model(model, val_data)  # Chạy hàm evaluate để tính metrics\n'''","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:57:22.584319Z","iopub.execute_input":"2025-12-07T16:57:22.584980Z","iopub.status.idle":"2025-12-07T17:14:37.180988Z","shell.execute_reply.started":"2025-12-07T16:57:22.584958Z","shell.execute_reply":"2025-12-07T17:14:37.180065Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#plot_pr_curve(results[\"all_vmetrics\"][-1][9], results[\"all_probabilities\"][-1], results[\"writer\"], class_names=dataset.label_encoder.classes_)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T17:19:31.047621Z","iopub.execute_input":"2025-12-07T17:19:31.047917Z","iopub.status.idle":"2025-12-07T17:19:33.801667Z","shell.execute_reply.started":"2025-12-07T17:19:31.047896Z","shell.execute_reply":"2025-12-07T17:19:33.800803Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport torch\n\nMODEL_DIR = \"/kaggle/working/models\"\nBEST_ACC = f\"{MODEL_DIR}/best_accuracy_model.pt\"\nBEST_LOSS = f\"{MODEL_DIR}/best_loss_model.pt\"\n\nif os.path.exists(BEST_ACC):\n    print(\"Loading best_accuracy_model.pt ...\")\n    state = torch.load(BEST_ACC, map_location=\"cpu\")\n    model.load_state_dict(state)\nelif os.path.exists(BEST_LOSS):\n    print(\"best_accuracy_model.pt not found, loading best_loss_model.pt ...\")\n    state = torch.load(BEST_LOSS, map_location=\"cpu\")\n    model.load_state_dict(state)\nelse:\n    print(\"Không tìm thấy model trong /kaggle/working/models/\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T17:19:37.354241Z","iopub.execute_input":"2025-12-07T17:19:37.354531Z","iopub.status.idle":"2025-12-07T17:19:37.405371Z","shell.execute_reply.started":"2025-12-07T17:19:37.354512Z","shell.execute_reply":"2025-12-07T17:19:37.404706Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\nĐánh giá trên tập xác thực:\")\nval_evaluation = evaluate_model(model, val_data)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T17:19:40.875097Z","iopub.execute_input":"2025-12-07T17:19:40.875336Z","iopub.status.idle":"2025-12-07T17:19:41.708146Z","shell.execute_reply.started":"2025-12-07T17:19:40.875321Z","shell.execute_reply":"2025-12-07T17:19:41.707435Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_sequence(sequence_df: pd.DataFrame, model: nn.Module, device='cuda') -> str:\n    \"\"\"\n    Predict gesture for a single sequence\n    \n    Args:\n        sequence_df: DataFrame containing single sequence data\n        model: Trained model\n        device: Device to run inference on\n    \n    Returns:\n        str: Predicted gesture name\n    \"\"\"\n    # Get feature columns (same as in dataset)\n    excluded_cols = {\n        'gesture', 'sequence_type', 'behavior', 'orientation',  # train-only\n        'row_id', 'subject', 'phase',  # metadata\n        'sequence_id', 'sequence_counter'  # identifiers\n    }\n    \n    thermal_tof_cols = [col for col in sequence_df.columns if col.startswith('thm_') or col.startswith('tof_')]\n    excluded_cols.update(thermal_tof_cols)\n    \n    feature_cols = [col for col in sequence_df.columns if col not in excluded_cols]\n    \n    # Prepare features\n    data = sequence_df[feature_cols].copy()\n    data = data.ffill().bfill().fillna(0).values\n    \n    # Standardize features using the same scaler logic\n    scaler = StandardScaler()\n    data = scaler.fit_transform(data)\n    \n    # Convert to tensor and add batch dimension\n    data = torch.tensor(data, dtype=torch.float32, device=device)\n    data = data.unsqueeze(0)  # Add batch dimension\n    \n    # Get prediction\n    model.eval()\n    with torch.no_grad():\n        outputs = model(data)\n        if outputs.dim() > 2:\n            outputs = outputs.permute(0, 2, 1).mean(dim=2)\n        probabilities = torch.softmax(outputs, dim=1)\n        predicted = torch.argmax(probabilities, dim=1)\n        \n    # Convert back to gesture name\n    predicted_gesture = dataset.label_encoder.inverse_transform(predicted.cpu().numpy())[0]\n    \n    return predicted_gesture","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T17:19:48.155703Z","iopub.execute_input":"2025-12-07T17:19:48.156276Z","iopub.status.idle":"2025-12-07T17:19:48.163338Z","shell.execute_reply.started":"2025-12-07T17:19:48.156254Z","shell.execute_reply":"2025-12-07T17:19:48.162458Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"\n    Competition prediction function.\n    Called with one sequence at a time.\n    \n    Args:\n        sequence: Polars DataFrame with sequence data\n        demographics: Polars DataFrame with demographics data\n        \n    Returns:\n        str: Predicted gesture name\n    \"\"\"\n    # Convert Polars DataFrame to Pandas\n    sequence_pd = sequence.to_pandas()\n    \n    # Make prediction using our model\n    predicted_gesture = predict_sequence(sequence_pd, model)\n    \n    # Validate if prediction is valid\n    metric = CompetitionMetric()\n    if predicted_gesture not in metric.all_classes:\n        # If prediction is invalid, return a safe default\n        return 'Text on phone'  # One of the non-target gestures\n        \n    return predicted_gesture\n\n# Test the prediction function on a single sequence from validation set\ntest_sequence_idx = val_data.indices[0]\ntest_sequence = train_df[train_df['sequence_id'] == train_df['sequence_id'].unique()[test_sequence_idx]]\ntest_demographics = train_demo_df[train_demo_df['subject'] == test_sequence['subject'].iloc[0]]\n\n# Convert to Polars for testing\ntest_sequence_pl = pl.from_pandas(test_sequence)\ntest_demographics_pl = pl.from_pandas(test_demographics)\n\nprint(\"Testing prediction function:\")\nprint(f\"True gesture: {test_sequence['gesture'].iloc[0]}\")\nprint(f\"Predicted gesture: {predict(test_sequence_pl, test_demographics_pl)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T17:19:50.419769Z","iopub.execute_input":"2025-12-07T17:19:50.420036Z","iopub.status.idle":"2025-12-07T17:19:50.516836Z","shell.execute_reply.started":"2025-12-07T17:19:50.420017Z","shell.execute_reply":"2025-12-07T17:19:50.515975Z"}},"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-12-07T17:19:52.871765Z","iopub.execute_input":"2025-12-07T17:19:52.872056Z","iopub.status.idle":"2025-12-07T17:19:53.834682Z","shell.execute_reply.started":"2025-12-07T17:19:52.872033Z","shell.execute_reply":"2025-12-07T17:19:53.834028Z"}},"outputs":[],"execution_count":null}]}