{"cells":[{"cell_type":"markdown","metadata":{},"source":"# CMI-BFRB CNN-LSTM Inference\n\nCNN-LSTM v6（CV 0.5789）の学習済みモデルを使用した推論ノートブック。\n\n**Version**: v1.0.0\n\n## 必要なDatasets:\n- Competition Data（自動）\n- `cmi-bfrb-utils-v132`（モジュール）\n- `cmi-bfrb-deep-learning-v1`（学習済みモデル）"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"import os\nimport sys\nimport numpy as np\nimport pandas as pd\nimport polars as pl\nimport torch\nfrom pathlib import Path\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# パス設定\nDATA_DIR = Path('/kaggle/input/cmi-detect-behavior-with-sensor-data')\nUTILS_DIR = Path('/kaggle/input/cmi-bfrb-utils-v132')\nMODEL_DIR = Path('/kaggle/input/cmi-bfrb-deep-learning-v1')\nOUTPUT_DIR = Path('/kaggle/working')\n\nsys.path.insert(0, str(UTILS_DIR))\n\nprint(\"環境設定完了\")\nprint(f\"PyTorch version: {torch.__version__}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# utilsのインポート\nfrom nn_models import CNNLSTMClassifier\nfrom device_utils import get_device\nimport pickle\nimport json\n\n# デバイス設定\ndevice = get_device()\nprint(f\"Using device: {device}\")\n\n# ジェスチャー定義\nTARGET_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\nNON_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\nALL_GESTURES = TARGET_GESTURES + NON_TARGET_GESTURES\nprint(f\"ジェスチャー数: {len(ALL_GESTURES)}\")"},{"cell_type":"markdown","metadata":{},"source":"## モデルの読み込み"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# 実験結果の読み込み\nwith open(MODEL_DIR / 'experiment_results.json', 'r') as f:\n    experiment_results = json.load(f)\n\nprint(\"実験結果:\")\nprint(f\"  Model: {experiment_results['model_type']}\")\nprint(f\"  Version: {experiment_results['version']}\")\nprint(f\"  CV Score: {experiment_results['mean_cv_score']:.4f} (± {experiment_results['std_cv_score']:.4f})\")\nprint(f\"  Input dim: {experiment_results['input_dim']}\")\nprint(f\"  Sequence length: {experiment_results['sequence_length']}\")\n\n# モデルパラメータ\nSEQUENCE_LENGTH = experiment_results['sequence_length']\nINPUT_DIM = experiment_results['input_dim']\nNUM_CLASSES = 18"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# 学習済みモデルの読み込み\nmodels = []\n\nfor fold in range(5):\n    model_path = MODEL_DIR / f'cnnlstm_fold_{fold}.pth'\n    if model_path.exists():\n        model = CNNLSTMClassifier(\n            input_dim=INPUT_DIM,\n            num_classes=NUM_CLASSES,\n            hidden_dim=128,\n            num_layers=2,\n            cnn_channels=[64, 128, 256],\n            kernel_size=3,\n            dropout=0.3,\n            use_batch_norm=True\n        )\n        \n        # 重みの読み込み\n        checkpoint = torch.load(model_path, map_location=device)\n        model.load_state_dict(checkpoint['model_state_dict'])\n        model.to(device)\n        model.eval()\n        \n        models.append(model)\n        print(f\"✓ Fold {fold} model loaded\")\n\nprint(f\"\\n合計 {len(models)} モデルを読み込みました\")"},{"cell_type":"markdown","metadata":{},"source":"## 特徴量作成関数"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"def create_sequence_features(df: pd.DataFrame, sequence_length: int = 100) -> torch.Tensor:\n    \"\"\"\n    シーケンスデータから特徴量テンソルを作成\n    \"\"\"\n    # センサーカラムを取得\n    sensor_cols = [col for col in df.columns if col not in ['sequence_id', 'subject', 'timestamp']]\n    \n    # データの長さを調整\n    if len(df) > sequence_length:\n        # 最後のsequence_length分を取る\n        df = df.iloc[-sequence_length:]\n    elif len(df) < sequence_length:\n        # パディング（前に0を追加）\n        padding_size = sequence_length - len(df)\n        padding_df = pd.DataFrame(\n            np.zeros((padding_size, len(sensor_cols))),\n            columns=sensor_cols\n        )\n        df = pd.concat([padding_df, df[sensor_cols]], ignore_index=True)\n    else:\n        df = df[sensor_cols]\n    \n    # 欠損値を0で埋める\n    df = df.fillna(0)\n    \n    # NumPy配列に変換\n    features = df.values.astype(np.float32)\n    \n    # PyTorchテンソルに変換\n    features_tensor = torch.from_numpy(features)\n    \n    return features_tensor"},{"cell_type":"markdown","metadata":{},"source":"## 推論関数"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"\n    CMI評価API用の推論関数\n    \n    Args:\n        sequence: センサーデータ（Polars DataFrame）\n        demographics: デモグラフィック情報（Polars DataFrame）\n        \n    Returns:\n        予測されたジェスチャー名\n    \"\"\"\n    try:\n        # Polars DataFrameをPandasに変換\n        test_df = sequence.to_pandas() if isinstance(sequence, pl.DataFrame) else sequence\n        \n        # デバッグ情報\n        if not hasattr(predict, 'call_count'):\n            predict.call_count = 0\n        predict.call_count += 1\n        \n        if predict.call_count == 1:\n            print(f\"最初の呼び出し - カラム数: {len(test_df.columns)}\")\n            print(f\"行数: {len(test_df)}\")\n        \n        # 特徴量テンソルを作成\n        features = create_sequence_features(test_df, SEQUENCE_LENGTH)\n        features = features.unsqueeze(0).to(device)  # バッチ次元を追加\n        \n        # 各モデルで予測\n        all_predictions = []\n        \n        with torch.no_grad():\n            for model in models:\n                output = model(features)\n                probs = torch.softmax(output, dim=1)\n                all_predictions.append(probs.cpu().numpy())\n        \n        # アンサンブル（平均）\n        ensemble_pred = np.mean(all_predictions, axis=0)\n        pred_class = np.argmax(ensemble_pred[0])\n        \n        # ジェスチャー名に変換\n        gesture = ALL_GESTURES[pred_class]\n        \n        # 進捗表示\n        if predict.call_count % 50 == 0:\n            print(f\"予測数: {predict.call_count}\")\n        \n        return gesture\n        \n    except Exception as e:\n        print(f\"エラー: {type(e).__name__}: {e}\")\n        import traceback\n        traceback.print_exc()\n        # エラー時は最頻値を返す\n        return 'Text on phone'\n\nprint(\"✓ 推論関数定義完了\")"},{"cell_type":"markdown","metadata":{},"source":"## 推論サーバーの実行"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# CMI評価APIを使用して推論を実行\nimport kaggle_evaluation.cmi_inference_server\n\nprint(\"推論サーバーを作成中...\")\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\n# 環境に応じて実行\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    print(\"Kaggle競技環境を検出。推論サーバーを開始します。\")\n    inference_server.serve()\nelse:\n    print(\"ローカルゲートウェイを実行します...\")\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    )\n\nprint(\"\\n推論完了\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# モデル情報の表示\nprint(\"\\n=== CNN-LSTM モデル情報 ===\")\nprint(f\"モデルタイプ: {experiment_results['model_type']}\")\nprint(f\"バージョン: {experiment_results['version']}\")\nprint(f\"CV スコア: {experiment_results['mean_cv_score']:.4f} (± {experiment_results['std_cv_score']:.4f})\")\nprint(f\"\\n改善点:\")\nfor improvement in experiment_results['improvements']:\n    print(f\"  - {improvement}\")\nprint(f\"\\n読み込んだモデル数: {len(models)}\")\nprint(f\"デバイス: {device}\")"}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.8.0"}},"nbformat":4,"nbformat_minor":4}