{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12703912,"sourceType":"datasetVersion","datasetId":8028914},{"sourceId":12728378,"sourceType":"datasetVersion","datasetId":8040013}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport sys\nimport numpy as np\nimport pandas as pd\nimport polars as pl\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\nimport kaggle_evaluation.cmi_inference_server\n\nis_kaggle_kernel = os.path.isdir(\"/kaggle/working/\")\nif is_kaggle_kernel:\n    folder_path = \"/kaggle/input/cmi-detect-behavior-with-sensor-data/\"\n    split_data_path = \"/kaggle/input/cmi2025-split-train-data/\"\nelse:\n    folder_path = \"../../../data/cmi-detect-behavior-with-sensor-data/\"\n    split_data_path = folder_path + \"split_train_data/\"\nsys.path.append(folder_path)\nimport kaggle_evaluation.cmi_inference_server\n\ntrain_demo = pd.read_csv(f\"{folder_path}/train_demographics.csv\")\ntrain = pd.read_csv(f\"{split_data_path}/train_1.csv\")\nval_demo = train_demo.copy()\nval = pd.read_csv(f\"{split_data_path}/val_1.csv\")\ntest_demo = pd.read_csv(f\"{folder_path}/test_demographics.csv\")\ntest = pd.read_csv(f\"{folder_path}/test.csv\")","metadata":{"execution":{"iopub.status.busy":"2025-08-10T20:22:11.700754Z","iopub.execute_input":"2025-08-10T20:22:11.701080Z","iopub.status.idle":"2025-08-10T20:22:47.172068Z","shell.execute_reply.started":"2025-08-10T20:22:11.701055Z","shell.execute_reply":"2025-08-10T20:22:47.171102Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nseed = 42\ntorch.manual_seed(seed)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T20:22:47.174001Z","iopub.execute_input":"2025-08-10T20:22:47.174378Z","iopub.status.idle":"2025-08-10T20:22:52.421693Z","shell.execute_reply.started":"2025-08-10T20:22:47.174352Z","shell.execute_reply":"2025-08-10T20:22:52.421051Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import json\nwith open('/kaggle/input/cmi2025-utils/utils/feature_cols.json', 'r') as f:\n    feature_cols = json.load(f)\nfeature_cols","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T20:22:52.422493Z","iopub.execute_input":"2025-08-10T20:22:52.422790Z","iopub.status.idle":"2025-08-10T20:22:52.433212Z","shell.execute_reply.started":"2025-08-10T20:22:52.422773Z","shell.execute_reply":"2025-08-10T20:22:52.432415Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if is_kaggle_kernel:\n    sys.path.append('/kaggle/input/cmi2025-utils')\nfrom utils.custom_features import customize_features\n\ntrain = customize_features(train)\nval   = customize_features(val)\ntest  = customize_features(test)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T20:22:52.434697Z","iopub.execute_input":"2025-08-10T20:22:52.434982Z","iopub.status.idle":"2025-08-10T20:26:01.286681Z","shell.execute_reply.started":"2025-08-10T20:22:52.434961Z","shell.execute_reply":"2025-08-10T20:26:01.285959Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = train[feature_cols + ['gesture']].copy()\nval   = val[feature_cols + ['gesture']].copy()\ntest  = test[feature_cols].copy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T20:26:43.049972Z","iopub.execute_input":"2025-08-10T20:26:43.050470Z","iopub.status.idle":"2025-08-10T20:26:43.312132Z","shell.execute_reply.started":"2025-08-10T20:26:43.050424Z","shell.execute_reply":"2025-08-10T20:26:43.311282Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# DatasetとDataLoaderのインポート\nfrom torch.utils.data import DataLoader\nfrom utils.dataset import SubjectDataset, custom_collate_fn\n\ntrain_dataset = SubjectDataset(train_demo, train)\nval_dataset = SubjectDataset(val_demo, val)\ntest_dataset = SubjectDataset(test_demo, test, have_labels=False)","metadata":{"execution":{"iopub.status.busy":"2025-08-10T20:26:45.097930Z","iopub.execute_input":"2025-08-10T20:26:45.098699Z","iopub.status.idle":"2025-08-10T20:26:49.592309Z","shell.execute_reply.started":"2025-08-10T20:26:45.098672Z","shell.execute_reply":"2025-08-10T20:26:49.591601Z"},"trusted":true},"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":{"execution":{"iopub.status.busy":"2025-08-10T20:26:52.248602Z","iopub.execute_input":"2025-08-10T20:26:52.248889Z","iopub.status.idle":"2025-08-10T20:26:52.253878Z","shell.execute_reply.started":"2025-08-10T20:26:52.248865Z","shell.execute_reply":"2025-08-10T20:26:52.253088Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# モデルのインポート\nfrom utils.models import HybridGestureClassifier","metadata":{"execution":{"iopub.status.busy":"2025-08-10T20:26:53.531314Z","iopub.execute_input":"2025-08-10T20:26:53.531884Z","iopub.status.idle":"2025-08-10T20:26:53.540229Z","shell.execute_reply.started":"2025-08-10T20:26:53.531862Z","shell.execute_reply":"2025-08-10T20:26:53.539527Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# スコアリング関数のインポート\nfrom utils.scoring_metric import score","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T20:26:54.381889Z","iopub.execute_input":"2025-08-10T20:26:54.382212Z","iopub.status.idle":"2025-08-10T20:26:55.319902Z","shell.execute_reply.started":"2025-08-10T20:26:54.382188Z","shell.execute_reply":"2025-08-10T20:26:55.319316Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.optim as optim\nfrom torch import nn\n\nDEMO_INPUT_DIM = train_dataset[0][0].shape[0]\nSEQ_INPUT_CHANNELS = train_dataset[0][1].shape[0]\nNUM_CLASSES = 18\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\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\nif False:\n    model_save_path = 'best_gesture_classifier.pth' # 訓練ループで保存したファイル名と同じにする\n\n    try:\n        loaded_state_dict = torch.load(model_save_path, map_location=device)\n        model.load_state_dict(loaded_state_dict)\n        model.eval()\n\n    except FileNotFoundError:\n        print(f\"エラー: モデルの重みファイル '{model_save_path}' が見つかりません。\")\n        print(\"ファイルパスが正しいか、訓練が正常に完了し保存されたか確認してください。\")\n    except Exception as e:\n        print(f\"モデルの読み込み中にエラーが発生しました: {e}\")\n\n# 損失関数の定義 (多クラス分類のためCrossEntropyLoss)\ncriterion = nn.CrossEntropyLoss()\n\n# 最適化アルゴリズムの定義 (Adamオプティマイザ)\noptimizer = optim.Adam(model.parameters(), lr=0.001)\n\n# モデルをGPUに移動（利用可能な場合）\nmodel.to(device)\nprint(device)","metadata":{"execution":{"iopub.status.busy":"2025-08-10T20:26:57.722025Z","iopub.execute_input":"2025-08-10T20:26:57.722524Z","iopub.status.idle":"2025-08-10T20:27:03.378292Z","shell.execute_reply.started":"2025-08-10T20:26:57.722500Z","shell.execute_reply":"2025-08-10T20:27:03.377346Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# モデルの構造図を描画 (オプション)\nif False:\n    from torchview import draw_graph\n\n    model_graph = draw_graph(model, \n                            input_size=[(1, DEMO_INPUT_DIM), (1, SEQ_INPUT_CHANNELS, 300)], \n                            expand_nested=True, # サブモジュールを展開して詳細に表示\n                            hide_module_functions=False, # nn.ReLUなどの関数も表示\n                            save_graph=True, # グラフを保存\n                            filename=\"model_structure_graph\", # 保存ファイル名 (拡張子なし)\n                            directory=\".\") # 保存ディレクトリ\n\n    print(\"モデルの構造図を 'model_structure_graph.png' (または .gv) として保存しました。\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T20:27:03.379783Z","iopub.execute_input":"2025-08-10T20:27:03.380274Z","iopub.status.idle":"2025-08-10T20:27:03.385597Z","shell.execute_reply.started":"2025-08-10T20:27:03.380251Z","shell.execute_reply":"2025-08-10T20:27:03.384821Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport matplotlib.pyplot as plt  # グラフ描画用ライブラリ\nfrom tqdm import tqdm  # プログレスバー表示\n\n# モデル、DataLoader、criterion、optimizer、deviceは前のセルで定義済みと仮定\n\n# --- 訓練ループ ---\nNUM_EPOCHS = 200 # 訓練エポック数\n\nprint(\"\\n--- 訓練開始 ---\")\nbest_val_accuracy = 0.0 # 最も良い検証精度を記録\nmodel_save_path = 'best_gesture_classifier.pth' \n\n# 各エポックの履歴を記録するリスト\ntrain_losses = []\nval_losses = []\ntrain_accuracies = []\nval_accuracies = []\n\nfor epoch in range(NUM_EPOCHS):\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        # NaN/Infの補正\n        if torch.isnan(seq_data).any() or torch.isinf(seq_data).any():\n            seq_data[torch.isnan(seq_data)] = 0.0\n            seq_data[torch.isinf(seq_data)] = 0.0\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)\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()\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\n                seq_data[torch.isinf(seq_data)] = 0.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    train_losses.append(avg_train_loss)\n    val_losses.append(avg_val_loss)\n    train_accuracies.append(train_accuracy)\n    val_accuracies.append(val_accuracy)\n\n    if val_accuracy > best_val_accuracy:\n        best_val_accuracy = val_accuracy\n        torch.save(model.state_dict(), model_save_path)\n        print(f\"  --> Best model saved with Val Acc: {best_val_accuracy:.4f}\")\n\nprint(\"--- 訓練終了 ---\")\n\n# --- グラフ描画 ---\nepochs = list(range(1, NUM_EPOCHS + 1))\n\nplt.figure(figsize=(12, 5))\n\n# 損失\nplt.subplot(1, 2, 1)\nplt.plot(epochs, train_losses, label='Train Loss')\nplt.plot(epochs, val_losses, label='Val Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Loss over Epochs')\nplt.legend()\n\n# 精度\nplt.subplot(1, 2, 2)\nplt.plot(epochs, train_accuracies, label='Train Acc')\nplt.plot(epochs, val_accuracies, label='Val Acc')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.title('Accuracy over Epochs')\nplt.legend()\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T20:27:03.386659Z","iopub.execute_input":"2025-08-10T20:27:03.386969Z","iopub.status.idle":"2025-08-10T20:27:15.359109Z","shell.execute_reply.started":"2025-08-10T20:27:03.386947Z","shell.execute_reply":"2025-08-10T20:27:15.358223Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Scoring","metadata":{}},{"cell_type":"code","source":"try:\n    loaded_state_dict = torch.load(model_save_path, map_location=device)\n    model.load_state_dict(loaded_state_dict)\n    model.eval()\n    print(f\"モデルの重みを '{model_save_path}' から読み込みました。\")\n\nexcept FileNotFoundError:\n    print(f\"エラー: モデルの重みファイル '{model_save_path}' が見つかりません。\")\n    print(\"ファイルパスが正しいか、訓練が正常に完了し保存されたか確認してください。\")\nexcept Exception as e:\n    print(f\"モデルの読み込み中にエラーが発生しました: {e}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T20:27:22.837213Z","iopub.execute_input":"2025-08-10T20:27:22.838009Z","iopub.status.idle":"2025-08-10T20:27:22.876294Z","shell.execute_reply.started":"2025-08-10T20:27:22.837975Z","shell.execute_reply":"2025-08-10T20:27:22.875500Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(val_dataset.gesture_to_id)\nid_to_gesture = {v: k for k, v in val_dataset.gesture_to_id.items()}\nprint(id_to_gesture)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T20:27:24.939521Z","iopub.execute_input":"2025-08-10T20:27:24.939799Z","iopub.status.idle":"2025-08-10T20:27:24.944561Z","shell.execute_reply.started":"2025-08-10T20:27:24.939777Z","shell.execute_reply":"2025-08-10T20:27:24.943786Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval() # モデルを評価モードに設定 (Dropoutなどが無効になる)\nval_loss = 0\nval_correct = 0\nval_total = 0\nall_predictions = []\nall_labels = []\nwith torch.no_grad(): # 勾配計算を無効化\n    for demo_data, seq_data, labels in val_loader:\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        all_predictions.extend(outputs.argmax(dim=1).cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\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\navg_val_loss = val_loss / len(val_loader)\nval_accuracy = val_correct / val_total\n\nsubmission = pd.DataFrame({\"gesture\": [id_to_gesture[pred] for pred in all_predictions]})\nsolution = pd.DataFrame({\"gesture\": [id_to_gesture[label] for label in all_labels]})\nsubmission[\"id\"] = submission.index\nsolution[\"id\"] = solution.index\ncompetition_score = score(solution, submission, row_id_column_name=\"id\")\n\nprint(f\"Val Loss: {avg_val_loss:.4f}, Val Acc: {val_accuracy:.4f}\")\nprint(f\"Score: {competition_score:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T20:27:31.860217Z","iopub.execute_input":"2025-08-10T20:27:31.860965Z","iopub.status.idle":"2025-08-10T20:27:32.411929Z","shell.execute_reply.started":"2025-08-10T20:27:31.860936Z","shell.execute_reply":"2025-08-10T20:27:32.411136Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# submit prediction","metadata":{}},{"cell_type":"code","source":"import torch.nn.functional as F\nfrom utils.custom_features import customize_features\nimport json\n\n\ndef predict(seq, demo):\n    if type(seq) != pd.DataFrame:\n        seq = seq.to_pandas()\n        demo = demo.to_pandas()\n\n    with open('/kaggle/input/cmi2025-utils/utils/feature_cols.json', 'r') as f:\n        feature_cols = json.load(f)\n    seq = customize_features(seq)\n    seq = seq[feature_cols].copy()\n\n    test_ds = SubjectDataset(demo, seq, have_labels=False)\n    test_loader = DataLoader(test_ds, batch_size=BATCH_SIZE, shuffle=False, collate_fn=custom_collate_fn, num_workers=NUM_WORKERS)\n    _, demo_data, seq_data = next(iter(test_loader))\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 = demo_data.to(device), seq_data.to(device)\n    outputs = model(demo_data, seq_data, )\n\n    # 1. ロジットをSoftmaxにかけて確率に変換\n    # dim=1 はクラスの次元を指定\n    probabilities = F.softmax(outputs, dim=1)\n    \n    # 2. 最も確率の高いクラスのインデックス（ID）を取得\n    # .argmax(dim=1) で各サンプルの最大値のインデックスを取得\n    # .item() でテンソルからPythonのスカラ値に変換\n    predicted_id = torch.argmax(probabilities, dim=1).item()\n    \n    # 3. 逆引き辞書を使って文字列に変換\n    predicted_gesture = id_to_gesture[predicted_id]\n\n    return predicted_gesture","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T20:27:41.544791Z","iopub.execute_input":"2025-08-10T20:27:41.545408Z","iopub.status.idle":"2025-08-10T20:27:41.552583Z","shell.execute_reply.started":"2025-08-10T20:27:41.545381Z","shell.execute_reply":"2025-08-10T20:27:41.551713Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\ninference_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-08-10T20:27:44.770483Z","iopub.execute_input":"2025-08-10T20:27:44.770783Z","iopub.status.idle":"2025-08-10T20:27:46.586463Z","shell.execute_reply.started":"2025-08-10T20:27:44.770761Z","shell.execute_reply":"2025-08-10T20:27:46.585613Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}