{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":"nvidiaTeslaT4","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom tqdm import tqdm\n\nfolder_path = \"/kaggle/input/cmi-detect-behavior-with-sensor-data\"\n\ntrain_demo = pd.read_csv(f\"{folder_path}/train_demographics.csv\")\ntrain = pd.read_csv(f\"{folder_path}/train.csv\")\ntest_demo = pd.read_csv(f\"{folder_path}/test_demographics.csv\")\ntest = pd.read_csv(f\"{folder_path}/test.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:13:41.296077Z","iopub.execute_input":"2025-07-28T04:13:41.296389Z","iopub.status.idle":"2025-07-28T04:14:09.587828Z","shell.execute_reply.started":"2025-07-28T04:13:41.296364Z","shell.execute_reply":"2025-07-28T04:14:09.587231Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:14:09.589323Z","iopub.execute_input":"2025-07-28T04:14:09.589817Z","iopub.status.idle":"2025-07-28T04:14:09.624380Z","shell.execute_reply.started":"2025-07-28T04:14:09.589797Z","shell.execute_reply":"2025-07-28T04:14:09.623833Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:14:09.625013Z","iopub.execute_input":"2025-07-28T04:14:09.625171Z","iopub.status.idle":"2025-07-28T04:14:09.656397Z","shell.execute_reply.started":"2025-07-28T04:14:09.625157Z","shell.execute_reply":"2025-07-28T04:14:09.655628Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_demo.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:14:09.657261Z","iopub.execute_input":"2025-07-28T04:14:09.657602Z","iopub.status.idle":"2025-07-28T04:14:09.673307Z","shell.execute_reply.started":"2025-07-28T04:14:09.657559Z","shell.execute_reply":"2025-07-28T04:14:09.672626Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(np.shape(train_demo), np.shape(train))\nprint(np.shape(test_demo), np.shape(test))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:14:09.675273Z","iopub.execute_input":"2025-07-28T04:14:09.675476Z","iopub.status.idle":"2025-07-28T04:14:09.686049Z","shell.execute_reply.started":"2025-07-28T04:14:09.675459Z","shell.execute_reply":"2025-07-28T04:14:09.685451Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print([col for col in train.columns if col not in test.columns])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:14:09.686694Z","iopub.execute_input":"2025-07-28T04:14:09.686944Z","iopub.status.idle":"2025-07-28T04:14:09.696750Z","shell.execute_reply.started":"2025-07-28T04:14:09.686929Z","shell.execute_reply":"2025-07-28T04:14:09.696120Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset, DataLoader\n\nclass SubjectDataset(Dataset):\n    def __init__(self, data_demo, data, have_labels=True):\n        self.have_labels = have_labels\n        self.unique_seequence_ids = sorted(data[\"sequence_id\"].unique())\n        self.sequence_id_to_subject = data.groupby(\"sequence_id\")[\"subject\"].first().to_dict()\n        \n        numerical_demo_cols = data_demo.select_dtypes(include=np.number).columns.tolist()\n        if 'subject' in numerical_demo_cols:\n            numerical_demo_cols.remove('subject')\n        \n        self.subject_to_demographics = {\n            subject: torch.tensor(row[numerical_demo_cols].values.astype(np.float32))\n            for subject, row in data_demo.set_index('subject').iterrows()\n        }\n\n        self.labels = None\n        \n        if have_labels:\n            all_unique_gestures = sorted(data[\"gesture\"].unique())\n            self.gesture_to_id = {gesture: i for i, gesture in enumerate(all_unique_gestures)}\n            sequence_labels_map_str = data.groupby(\"sequence_id\")[\"gesture\"].first().to_dict()\n            self.labels = torch.tensor(\n                [self.gesture_to_id[sequence_labels_map_str[sid]] for sid in self.unique_seequence_ids], \n                dtype=torch.long\n            )\n\n        cols_to_drop = [\"row_id\", \"subject\", \"sequence_id\", \"sequence_type\", \"orientation\", \"behavior\", \"phase\", \"gesture\"]\n        sequence_feature_cols = [col for col in data.columns if col not in cols_to_drop]\n        \n        self.preprocessed_sequences = {\n            seq_id: torch.tensor(\n                data[data[\"sequence_id\"] == seq_id][sequence_feature_cols].values.astype(np.float32)\n            ).T\n            for seq_id in tqdm(self.unique_seequence_ids)\n        }\n\n    def __len__(self):\n        return len(self.unique_seequence_ids)\n\n    def __getitem__(self, idx):\n        sequence_id = self.unique_seequence_ids[idx]\n        \n        subject_data_tensor = self.subject_to_demographics[self.sequence_id_to_subject[sequence_id]]\n        sequence_data_tensor = self.preprocessed_sequences[sequence_id]\n\n        if self.have_labels:\n            return subject_data_tensor, sequence_data_tensor, self.labels[idx]\n        else:\n            return sequence_id, subject_data_tensor, sequence_data_tensor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:14:09.697438Z","iopub.execute_input":"2025-07-28T04:14:09.697661Z","iopub.status.idle":"2025-07-28T04:14:13.796310Z","shell.execute_reply.started":"2025-07-28T04:14:09.697637Z","shell.execute_reply":"2025-07-28T04:14:13.795707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = SubjectDataset(train_demo, train)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:14:13.797061Z","iopub.execute_input":"2025-07-28T04:14:13.797462Z","iopub.status.idle":"2025-07-28T04:19:29.239188Z","shell.execute_reply.started":"2025-07-28T04:14:13.797436Z","shell.execute_reply":"2025-07-28T04:19:29.238493Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_dataset = SubjectDataset(test_demo, test, have_labels=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:19:29.239997Z","iopub.execute_input":"2025-07-28T04:19:29.240510Z","iopub.status.idle":"2025-07-28T04:19:29.251962Z","shell.execute_reply.started":"2025-07-28T04:19:29.240486Z","shell.execute_reply":"2025-07-28T04:19:29.251312Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"seed = 42\ntorch.manual_seed(seed)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:19:29.252626Z","iopub.execute_input":"2025-07-28T04:19:29.252876Z","iopub.status.idle":"2025-07-28T04:19:29.264151Z","shell.execute_reply.started":"2025-07-28T04:19:29.252838Z","shell.execute_reply":"2025-07-28T04:19:29.263443Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# trainとvalの分割\nfrom torch.utils.data import random_split\ntrain_size = int(0.8 * len(train_dataset))\nval_size = len(train_dataset) - train_size\ntrain_dataset, val_dataset = random_split(train_dataset, [train_size, val_size])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:19:29.264738Z","iopub.execute_input":"2025-07-28T04:19:29.264904Z","iopub.status.idle":"2025-07-28T04:19:29.279794Z","shell.execute_reply.started":"2025-07-28T04:19:29.264892Z","shell.execute_reply":"2025-07-28T04:19:29.279345Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn.functional as F # F.pad のために必要\n\n# --- custom_collate_fn の定義 ---\ndef custom_collate_fn(batch):\n    # バッチ内の最初の要素の長さで、ラベルがあるか（3つ要素）ないか（2つ要素）を判別\n    has_labels = (len(batch[0]) == 3) # (demo_tensor, sequence_tensor, label_tensor) なら3\n\n    if has_labels:\n        # ラベルがある場合、(デモデータ, シーケンスデータ, ラベル) のタプルを受け取る\n        demo_list, sequence_list, label_list = zip(*batch)\n    else:\n        # ラベルがない場合、(sequence_id, デモデータ, シーケンスデータ) のタプルを受け取る\n        sequence_id_list, demo_list, sequence_list = zip(*batch)\n\n    # 1. デモグラフィックデータは固定長と仮定し、そのままスタックしてバッチを作成\n    demographics_batch = torch.stack(demo_list)\n\n    # 2. シーケンスデータのパディング\n    # sequence_listは既に(C, L)のテンソルリストになっていることを前提\n    \n    # バッチ内で最も長いシーケンスの長さを取得\n    max_len = max(s.shape[1] for s in sequence_list) # s.shape[1] がシーケンス長 (L)\n\n    # 各シーケンスを最長シーケンスの長さにパディング\n    padded_sequences = []\n    for s in sequence_list:\n        # s.shape[1]は現在のシーケンス長、max_lenはバッチ内の最長シーケンス長\n        padding_needed = max_len - s.shape[1] \n        if padding_needed > 0:\n            # torch.nn.functional.pad を使って (C, L_new) にパディング\n            # pad=(0, padding_needed) は「最後の次元（ここではL）の右側にpadding_neededだけゼロを追加」\n            padded_s = F.pad(s, (0, padding_needed))\n        else:\n            padded_s = s # パディングが不要な場合はそのまま\n        padded_sequences.append(padded_s)\n    \n    # パディングされたテンソルを結合し、1つのバッチテンソルにする\n    sequences_batch = torch.stack(padded_sequences)\n\n\n    # 3. ラベルの処理\n    if has_labels:\n        # ラベルは全て同じ形状なので、そのままスタック\n        labels_batch = torch.stack(label_list)\n        return demographics_batch, sequences_batch, labels_batch\n    else:\n        # ラベルがない場合、sequence_idもバッチとして返す\n        # sequence_id_listは文字列なのでstackできないことに注意（リストのまま返す）\n        return sequence_id_list, demographics_batch, sequences_batch","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:19:29.280375Z","iopub.execute_input":"2025-07-28T04:19:29.280563Z","iopub.status.idle":"2025-07-28T04:19:29.286934Z","shell.execute_reply.started":"2025-07-28T04:19:29.280542Z","shell.execute_reply":"2025-07-28T04:19:29.286265Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BATCH_SIZE = 32\nNUM_WORKERS = 0\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, collate_fn=custom_collate_fn, num_workers=NUM_WORKERS)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, collate_fn=custom_collate_fn, num_workers=NUM_WORKERS)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:19:29.287712Z","iopub.execute_input":"2025-07-28T04:19:29.287925Z","iopub.status.idle":"2025-07-28T04:19:29.300523Z","shell.execute_reply.started":"2025-07-28T04:19:29.287901Z","shell.execute_reply":"2025-07-28T04:19:29.300005Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn as nn\nimport torch.nn.functional as F\n\nclass HybridGestureClassifier(nn.Module):\n    def __init__(self, num_sequence_features: int, sequence_length: int, num_subject_features: int, num_classes: int = 18):\n        super().__init__()\n\n        # CNNブロックをさらに深く、広くする\n        self.conv_block = nn.Sequential(\n            nn.Conv1d(in_channels=num_sequence_features, out_channels=128, kernel_size=3, padding=1),\n            nn.ReLU(),\n            nn.BatchNorm1d(128), # BatchNormを追加\n            nn.MaxPool1d(kernel_size=2),\n\n            nn.Conv1d(in_channels=128, out_channels=256, kernel_size=3, padding=1),\n            nn.ReLU(),\n            nn.BatchNorm1d(256), # BatchNormを追加\n            nn.MaxPool1d(kernel_size=2),\n\n            nn.Conv1d(in_channels=256, out_channels=512, kernel_size=3, padding=1), # 新しい層: 256 -> 512\n            nn.ReLU(),\n            nn.BatchNorm1d(512), # BatchNormを追加\n            nn.MaxPool1d(kernel_size=2) # 最終的なシーケンス長は元の1/8になる\n        )\n        \n        # MaxPool1dが3回になるので、シーケンス長は元の1/8になる\n        self.cnn_output_length = sequence_length // 8 \n        self.lstm_input_size = 512 # CNNの最終出力チャンネル数に合わせる\n\n        # LSTMをさらに深く、広くする\n        self.lstm = nn.LSTM(\n            input_size=self.lstm_input_size, \n            hidden_size=512, # 隠れ層のサイズをさらに増やす: 256 -> 512\n            num_layers=4,    # 層数をさらに増やす: 3 -> 4\n            batch_first=True, \n            dropout=0.4      # ドロップアウト率も調整\n        )\n        \n        # 被験者データ処理器を深くする\n        self.subject_fc = nn.Sequential(\n            nn.Linear(num_subject_features, 128), \n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(128, 256), # 新しい層: 128 -> 256\n            nn.ReLU(),\n            nn.Dropout(0.3)\n        )\n\n        # 結合される特徴量の次元が増える\n        combined_feature_dim = 512 + 256 # LSTMのhidden_size + subject_fcの出力 \n        \n        # 分類器を深く、広くする\n        self.classifier = nn.Sequential(\n            nn.Linear(combined_feature_dim, 512), # 入力を増やす\n            nn.ReLU(),\n            nn.Dropout(0.5), # ドロップアウト率を調整\n            nn.Linear(512, 256), # 新しい層: 512 -> 256\n            nn.ReLU(),\n            nn.Dropout(0.5), # ドロップアウト率を調整\n            nn.Linear(256, num_classes) # 最終出力\n        )\n\n    def forward(self, subject_data, sequence_data):\n        cnn_out = self.conv_block(sequence_data)\n        lstm_input = cnn_out.transpose(1, 2) \n        lstm_out, (h_n, c_n) = self.lstm(lstm_input)\n        sequence_features = h_n[-1] \n\n        subject_features = self.subject_fc(subject_data)\n\n        combined_features = torch.cat((sequence_features, subject_features), dim=1)\n\n        logits = self.classifier(combined_features)\n        return logits","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:19:29.302662Z","iopub.execute_input":"2025-07-28T04:19:29.302846Z","iopub.status.idle":"2025-07-28T04:19:29.314832Z","shell.execute_reply.started":"2025-07-28T04:19:29.302832Z","shell.execute_reply":"2025-07-28T04:19:29.314191Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.optim as optim\n\nDEMO_INPUT_DIM = train_dataset[0][0].shape[0]\nSEQ_INPUT_CHANNELS = train_dataset[0][1].shape[0]\nNUM_CLASSES = 18\n\n# モデルのインスタンスを作成\nmodel = HybridGestureClassifier(\n    num_sequence_features=SEQ_INPUT_CHANNELS,\n    sequence_length=300,\n    num_subject_features=DEMO_INPUT_DIM,\n    num_classes=NUM_CLASSES\n)\n\n# 損失関数の定義 (多クラス分類のためCrossEntropyLoss)\ncriterion = nn.CrossEntropyLoss()\n\n# 最適化アルゴリズムの定義 (Adamオプティマイザ)\noptimizer = optim.Adam(model.parameters(), lr=0.001)\n\n# モデルをGPUに移動（利用可能な場合）\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel.to(device)\nprint(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:19:29.315539Z","iopub.execute_input":"2025-07-28T04:19:29.315802Z","iopub.status.idle":"2025-07-28T04:19:32.197290Z","shell.execute_reply.started":"2025-07-28T04:19:29.315781Z","shell.execute_reply":"2025-07-28T04:19:32.196556Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"x,y,z = next(iter(train_loader))\nprint(f\"x: {x.shape}, y: {y.shape}, z: {z.shape if z is not None else 'No labels'}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:19:32.198123Z","iopub.execute_input":"2025-07-28T04:19:32.198480Z","iopub.status.idle":"2025-07-28T04:19:32.215274Z","shell.execute_reply.started":"2025-07-28T04:19:32.198461Z","shell.execute_reply":"2025-07-28T04:19:32.214520Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"x[torch.isnan(x)] = 0.0  # NaNを0で埋める\ny[torch.isnan(y)] = 0.0  # NaNを0で埋める","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:19:32.216068Z","iopub.execute_input":"2025-07-28T04:19:32.216301Z","iopub.status.idle":"2025-07-28T04:19:32.225155Z","shell.execute_reply.started":"2025-07-28T04:19:32.216276Z","shell.execute_reply":"2025-07-28T04:19:32.224360Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"has_nan_demo = torch.isnan(x).any()\nhas_nan_seq = torch.isnan(y).any()\nprint(f\"demo_data に NaN が含まれているか: {has_nan_demo}\")\nprint(f\"seq_data に NaN が含まれているか: {has_nan_seq}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:19:32.225895Z","iopub.execute_input":"2025-07-28T04:19:32.226083Z","iopub.status.idle":"2025-07-28T04:19:32.236702Z","shell.execute_reply.started":"2025-07-28T04:19:32.226068Z","shell.execute_reply":"2025-07-28T04:19:32.235971Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n# モデル、DataLoader、criterion、optimizer、deviceは前のセルで定義済みと仮定\n\n# --- 訓練ループ ---\nNUM_EPOCHS = 200 # 訓練エポック数\n\nprint(\"\\n--- 訓練開始 ---\")\nbest_val_accuracy = 0.0 # 最も良い検証精度を記録\n# モデルの重みを保存するパス（必要に応じて変更）\nmodel_save_path = 'best_gesture_classifier.pth' \n\nfor epoch in range(NUM_EPOCHS):\n    # 訓練フェーズ\n    model.train() # モデルを訓練モードに設定\n    train_loss = 0\n    train_correct = 0\n    train_total = 0\n\n    for demo_data, seq_data, labels in tqdm(train_loader, desc=f\"Epoch {epoch+1}/{NUM_EPOCHS} Train\"):\n        # --- ここから追加・修正 ---\n        # NaN/Infのチェックとゼロ補完\n        if torch.isnan(seq_data).any() or torch.isinf(seq_data).any():\n            seq_data[torch.isnan(seq_data)] = 0.0 # NaNを0で埋める\n            seq_data[torch.isinf(seq_data)] = 0.0 # Infを0で埋める\n        # --- ここまで追加・修正 ---\n        \n        demo_data, seq_data, labels = demo_data.to(device), seq_data.to(device), labels.to(device)\n\n        optimizer.zero_grad() # 勾配をゼロクリア\n        outputs = model(demo_data, seq_data) # 順伝播\n        loss = criterion(outputs, labels) # 損失計算\n        loss.backward() # 逆伝播\n        optimizer.step() # パラメータ更新\n\n        train_loss += loss.item()\n        _, predicted = torch.max(outputs.data, 1) # 最も高い確率を持つクラスIDを取得\n        train_total += labels.size(0)\n        train_correct += (predicted == labels).sum().item() # 正しく予測された数\n    \n    avg_train_loss = train_loss / len(train_loader)\n    train_accuracy = train_correct / train_total\n\n    # 検証フェーズ\n    model.eval() # モデルを評価モードに設定 (Dropoutなどが無効になる)\n    val_loss = 0\n    val_correct = 0\n    val_total = 0\n    with torch.no_grad(): # 勾配計算を無効化\n        for demo_data, seq_data, labels in tqdm(val_loader, desc=f\"Epoch {epoch+1}/{NUM_EPOCHS} Val\"):\n            if torch.isnan(seq_data).any() or torch.isinf(seq_data).any():\n                seq_data[torch.isnan(seq_data)] = 0.0 # NaNを0で埋める\n                seq_data[torch.isinf(seq_data)] = 0.0 # Infを0で埋める\n                \n            demo_data, seq_data, labels = demo_data.to(device), seq_data.to(device), labels.to(device)\n\n            outputs = model(demo_data, seq_data)\n            loss = criterion(outputs, labels)\n\n            val_loss += loss.item()\n            _, predicted = torch.max(outputs.data, 1)\n            val_total += labels.size(0)\n            val_correct += (predicted == labels).sum().item()\n    \n    avg_val_loss = val_loss / len(val_loader)\n    val_accuracy = val_correct / val_total\n\n    print(f\"Epoch [{epoch+1}/{NUM_EPOCHS}], \"\n          f\"Train Loss: {avg_train_loss:.4f}, Train Acc: {train_accuracy:.4f} | \"\n          f\"Val Loss: {avg_val_loss:.4f}, Val Acc: {val_accuracy:.4f}\")\n\n    # 最も良い検証精度を達成したモデルの重みを保存\n    if val_accuracy > best_val_accuracy:\n        best_val_accuracy = val_accuracy\n        torch.save(model.state_dict(), model_save_path) # モデルの重み（state_dict）を保存\n        print(f\"  --> Best model saved with Val Acc: {best_val_accuracy:.4f}\")\n\nprint(\"--- 訓練終了 ---\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T04:44:21.832112Z","iopub.execute_input":"2025-07-28T04:44:21.832732Z","iopub.status.idle":"2025-07-28T04:47:25.099004Z","shell.execute_reply.started":"2025-07-28T04:44:21.832709Z","shell.execute_reply":"2025-07-28T04:47:25.098031Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}