{"cells":[{"cell_type":"markdown","metadata":{},"source":"# CMI-BFRB Simple Two-Branch Light v5.6 Inference\n\n## v5.6の主な特徴:\n- **IMUブランチ強化**: 64→128（第1層）、128→256（第2層）に容量増強\n- **Modality Dropout最適化**: 確率を0.5→0.3に調整\n- **BatchNormの「バグが機能」**: バッチ統計がMD率を暗黙的に検出\n- **5-foldアンサンブル**: 安定した予測のための複数モデル平均\n- **CV結果**: 0.5251（検証時MD有効）- BatchNormの特性を活用"},{"cell_type":"code","metadata":{},"outputs":[],"source":"# ソースコードのコピー（Kaggle環境用）\nsrc_path = '/kaggle/input/cmi-bfrb-stbl-v56-improved-src'\nif os.path.exists(src_path):\n    import shutil\n    for file in ['models.py', 'feature_engineering.py', 'hierarchical_f1_cmi.py', 'dataset.py']:\n        src_file = os.path.join(src_path, file)\n        if os.path.exists(src_file):\n            shutil.copy(src_file, file)\n            print(f\"Copied {file}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# ソースコードのコピー（Kaggle環境用）\nsrc_path = '/kaggle/input/cmi-bfrb-simple-two-branch-light-v56-src'\nif os.path.exists(src_path):\n    import shutil\n    for file in ['models.py', 'feature_engineering.py', 'hierarchical_f1_cmi.py', 'dataset.py']:\n        src_file = os.path.join(src_path, file)\n        if os.path.exists(src_file):\n            shutil.copy(src_file, file)\n            print(f\"Copied {file}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# モデルとユーティリティのインポート\nfrom models import SimpleTwoBranchLight\nfrom feature_engineering import (\n    impute_sensor_nans,\n    add_imu_features,\n    aggregate_tof_features,\n    add_tof_spatial_features,\n    get_feature_columns\n)"},{"cell_type":"code","metadata":{},"outputs":[],"source":"# 5-foldモデルのロード\nmodels_path = '/kaggle/input/cmi-bfrb-stbl-v56-improved-models'\nmodels = []\nscalers = []\nlabel_encoders = []\n\n# CV結果の読み込み（もし存在すれば）\ncv_results_path = os.path.join(models_path, 'simple_two_branch_light_v5_6_improved_results.json')\nif os.path.exists(cv_results_path):\n    with open(cv_results_path, 'r') as f:\n        cv_results = json.load(f)\n    print(f\"CV Mean Score: {cv_results['mean_score']:.4f} ± {cv_results['std_score']:.4f}\")\n    print(f\"Individual fold scores: {cv_results['cv_scores']}\")\nelse:\n    print(\"CV results file not found\")\n\n# 各foldのモデルをロード\nfor fold in range(5):\n    checkpoint_path = os.path.join(models_path, f'simple_two_branch_light_v5_6_improved_fold{fold}_best.pth')\n    \n    if os.path.exists(checkpoint_path):\n        checkpoint = torch.load(checkpoint_path, map_location=DEVICE, weights_only=False)\n        \n        # モデルの復元\n        model = SimpleTwoBranchLight(\n            imu_dim=config.imu_dim,\n            tof_dim=config.tof_dim,\n            thermopile_dim=config.thermopile_dim,\n            hidden_dim=config.hidden_dim,\n            num_classes=config.num_classes,\n            modality_dropout_rate=0.0  # 推論時は0\n        )\n        model.load_state_dict(checkpoint['model_state_dict'])\n        model.to(DEVICE)\n        model.eval()\n        models.append(model)\n        \n        # スケーラーとラベルエンコーダーの復元\n        scalers.append(checkpoint['scaler'])\n        label_encoders.append(checkpoint['label_encoder'])\n        \n        print(f\"Loaded fold {fold} - Val F1: {checkpoint.get('best_val_f1', 'N/A'):.4f}\")\n    else:\n        print(f\"Warning: Checkpoint for fold {fold} not found at {checkpoint_path}\")\n\nif len(models) == 0:\n    print(\"\\nERROR: No models loaded!\")\n    raise RuntimeError(\"No models found. Please check the model input path.\")\nelse:\n    print(f\"\\nSuccessfully loaded {len(models)} models\")\n    print(f\"Classes: {label_encoders[0].classes_}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# 5-foldモデルのロード\nmodels_path = '/kaggle/input/cmi-bfrb-simple-two-branch-light-v56-models'\nmodels = []\nscalers = []\nlabel_encoders = []\n\n# CV結果の読み込み（もし存在すれば）\ncv_results_path = os.path.join(models_path, 'simple_two_branch_light_v5_6_improved_results.json')\nif os.path.exists(cv_results_path):\n    with open(cv_results_path, 'r') as f:\n        cv_results = json.load(f)\n    print(f\"CV Mean Score: {cv_results['mean_score']:.4f} ± {cv_results['std_score']:.4f}\")\n    print(f\"Individual fold scores: {cv_results['cv_scores']}\")\nelse:\n    print(\"CV results file not found\")\n\n# 各foldのモデルをロード\nfor fold in range(5):\n    checkpoint_path = os.path.join(models_path, f'simple_two_branch_light_v5_6_improved_fold{fold}_best.pth')\n    \n    if os.path.exists(checkpoint_path):\n        checkpoint = torch.load(checkpoint_path, map_location=DEVICE, weights_only=False)\n        \n        # モデルの復元\n        model = SimpleTwoBranchLight(\n            imu_dim=config.imu_dim,\n            tof_dim=config.tof_dim,\n            thermopile_dim=config.thermopile_dim,\n            hidden_dim=config.hidden_dim,\n            num_classes=config.num_classes,\n            modality_dropout_rate=0.0  # 推論時は0\n        )\n        model.load_state_dict(checkpoint['model_state_dict'])\n        model.to(DEVICE)\n        model.eval()\n        models.append(model)\n        \n        # スケーラーとラベルエンコーダーの復元\n        scalers.append(checkpoint['scaler'])\n        label_encoders.append(checkpoint['label_encoder'])\n        \n        print(f\"Loaded fold {fold} - Val F1: {checkpoint.get('best_val_f1', 'N/A'):.4f}\")\n    else:\n        print(f\"Warning: Checkpoint for fold {fold} not found at {checkpoint_path}\")\n\nif len(models) == 0:\n    print(\"\\nERROR: No models loaded!\")\n    raise RuntimeError(\"No models found. Please check the model input path.\")\nelse:\n    print(f\"\\nSuccessfully loaded {len(models)} models\")\n    print(f\"Classes: {label_encoders[0].classes_}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"def preprocess_data(test_df, config):\n    \"\"\"\n    v5.6用のデータ前処理\n    \n    Args:\n        test_df: 生のセンサーデータ\n        config: 設定オブジェクト\n    \n    Returns:\n        preprocessed_df: 前処理済みデータ\n        feature_array: モデル入力用の特徴量配列\n    \"\"\"\n    # コピーを作成（元データを保護）\n    df = test_df.copy()\n    \n    # NaN補完\n    df = impute_sensor_nans(df)\n    \n    # IMU特徴量追加\n    df = add_imu_features(df)\n    \n    # ToF統計量追加\n    df, tof_stat_cols = aggregate_tof_features(df)\n    \n    # 特徴量カラムの取得\n    imu_cols, thm_cols, _, _, all_feature_cols = get_feature_columns()\n    \n    # v5.6ではToF空間特徴量を使わない\n    if not config.use_tof_spatial:\n        # ToF空間特徴量カラムを除外\n        tof_spatial_cols = []\n        for i in range(1, 6):\n            tof_spatial_cols.extend([\n                f'tof_{i}_centroid_x', f'tof_{i}_centroid_y',\n                f'tof_{i}_spatial_var', f'tof_{i}_top_bottom_diff',\n                f'tof_{i}_left_right_diff', f'tof_{i}_active_area'\n            ])\n        all_feature_cols = [col for col in all_feature_cols if col not in tof_spatial_cols]\n    \n    # 特徴量配列の作成\n    feature_array = df[all_feature_cols].values.astype(np.float32)\n    \n    # NaN/Inf処理\n    feature_array = np.nan_to_num(feature_array, nan=0.0, posinf=0.0, neginf=0.0)\n    \n    return df, feature_array"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"def predict_ensemble(feature_array, models, scalers, label_encoders, config):\n    \"\"\"\n    5-foldアンサンブル予測\n    \n    Args:\n        feature_array: 特徴量配列\n        models: モデルのリスト\n        scalers: スケーラーのリスト\n        label_encoders: ラベルエンコーダーのリスト\n        config: 設定オブジェクト\n    \n    Returns:\n        pred_class: 予測クラス名\n        class_probs: クラス確率の平均\n    \"\"\"\n    all_probs = []\n    \n    # シーケンス長の調整\n    if len(feature_array) < config.sequence_length:\n        # パディング\n        pad_length = config.sequence_length - len(feature_array)\n        feature_array = np.pad(feature_array, ((0, pad_length), (0, 0)), mode='constant')\n    else:\n        # 最後のsequence_lengthフレームを使用\n        feature_array = feature_array[-config.sequence_length:]\n    \n    for i, (model, scaler) in enumerate(zip(models, scalers)):\n        # 標準化\n        features_scaled = scaler.transform(feature_array)\n        \n        # バッチ化\n        features_tensor = torch.FloatTensor(features_scaled).unsqueeze(0).to(config.device)\n        \n        # 予測\n        with torch.no_grad():\n            class_logits, _ = model(features_tensor)\n            probs = F.softmax(class_logits, dim=-1).cpu().numpy()\n            all_probs.append(probs)\n    \n    # 確率の平均を計算\n    avg_probs = np.mean(all_probs, axis=0)[0]  # (num_classes,)\n    \n    # 最も確率の高いクラスを選択\n    pred_idx = np.argmax(avg_probs)\n    \n    # クラス名に変換（すべてのエンコーダーは同じはず）\n    pred_class = label_encoders[0].inverse_transform([pred_idx])[0]\n    \n    return pred_class, avg_probs"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# CMI API v2対応のpredict関数\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"\n    CMI推論API v2に対応した予測関数\n    \n    Args:\n        sequence: Polars DataFrame with sensor sequences\n        demographics: Polars DataFrame with subject demographics (unused)\n    \n    Returns:\n        str: 予測されたクラス名\n    \"\"\"\n    try:\n        # Polars DataFrameをPandasに変換\n        test_df = sequence.to_pandas()\n        \n        # 前処理\n        _, feature_array = preprocess_data(test_df, config)\n        \n        # アンサンブル予測\n        pred_class, _ = predict_ensemble(feature_array, models, scalers, label_encoders, config)\n        \n        return pred_class\n        \n    except Exception as e:\n        print(f\"Error in predict function: {str(e)}\")\n        import traceback\n        traceback.print_exc()\n        # エラー時はデフォルトクラスを返す\n        return 'Neck - scratch'  # 最も頻度の高いクラス"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# CMI推論サーバーの実行\nprint(\"=\" * 60)\nprint(\"Starting CMI Inference Server for v5.6...\")\nprint(\"=\" * 60)\n\n# CMI推論APIのインポートと実行\ntry:\n    import kaggle_evaluation.cmi_inference_server\n    \n    print(\"Using CMI Inference Server API v2\")\n    \n    # 推論サーバーの起動\n    inference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n    \n    # サーバー実行\n    inference_server.serve()\n    \n    print(\"\\nInference completed successfully!\")\n    \nexcept ImportError as e:\n    print(f\"CMI inference environment not available: {e}\")\n    print(\"Running in development/debug mode...\")\n    \n    # 開発モードでのテスト\n    test_path = '/kaggle/input/cmi-detect-behavior-with-sensor-data/test.parquet'\n    \n    if os.path.exists(test_path):\n        print(f\"\\nTest data found at: {test_path}\")\n        \n        # テストデータの読み込み\n        test_df_full = pd.read_parquet(test_path)\n        print(f\"Test data shape: {test_df_full.shape}\")\n        \n        # 最初のシーケンスでテスト\n        if 'sequence_id' in test_df_full.columns:\n            first_seq_id = test_df_full['sequence_id'].iloc[0]\n            first_seq = test_df_full[test_df_full['sequence_id'] == first_seq_id]\n            \n            print(f\"\\nTesting on sequence {first_seq_id} with {len(first_seq)} timesteps\")\n            \n            # Polars DataFrameに変換してテスト\n            if POLARS_AVAILABLE:\n                first_seq_pl = pl.from_pandas(first_seq)\n                sample_prediction = predict(first_seq_pl, pl.DataFrame())\n                print(f\"Sample prediction: {sample_prediction}\")\n            else:\n                print(\"Polars not available for testing\")\n    else:\n        print(\"\\nTest parquet file not found.\")\n        print(\"Creating dummy data for testing...\")\n        \n        # ダミーデータでのテスト\n        # IMUセンサーデータ\n        dummy_imu = {\n            'acc_x': np.random.randn(config.sequence_length),\n            'acc_y': np.random.randn(config.sequence_length),\n            'acc_z': np.random.randn(config.sequence_length),\n            'rot_w': np.random.randn(config.sequence_length),\n            'rot_x': np.random.randn(config.sequence_length),\n            'rot_y': np.random.randn(config.sequence_length),\n            'rot_z': np.random.randn(config.sequence_length),\n        }\n        \n        # ToFセンサーデータ\n        dummy_tof = {}\n        for i in range(1, 6):\n            for j in range(64):\n                dummy_tof[f'tof_{i}_v{j}'] = np.random.randint(0, 1000, config.sequence_length)\n        \n        # Thermopileセンサーデータ\n        dummy_thm = {}\n        for i in range(1, 6):\n            dummy_thm[f'thm_{i}'] = np.random.randn(config.sequence_length)\n        \n        # 結合\n        dummy_data = {**dummy_imu, **dummy_tof, **dummy_thm}\n        dummy_df = pd.DataFrame(dummy_data)\n        \n        # テスト\n        if POLARS_AVAILABLE:\n            dummy_pl = pl.from_pandas(dummy_df)\n            dummy_prediction = predict(dummy_pl, pl.DataFrame())\n            print(f\"Dummy prediction: {dummy_prediction}\")\n        else:\n            print(\"Polars not available for dummy test\")\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"Inference notebook execution completed!\")\nprint(\"v5.6: IMU branch enhanced + BatchNorm 'bug as feature'\")\nprint(\"=\"*60)"}],"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.10.12"}},"nbformat":4,"nbformat_minor":4}