{"cells":[{"cell_type":"markdown","metadata":{},"source":"# CMI V11 Fixed: 合成データによる事前学習済みモデル構築\n\n実データ50% + 合成テストデータ50%でトレーニングし、Kaggle Datasetとして保存"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"import numpy as np\nimport pandas as pd\nimport os\nfrom pathlib import Path\nimport json\nimport pickle\nimport warnings\nfrom sklearn.model_selection import train_test_split, StratifiedKFold\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom sklearn.metrics import accuracy_score, classification_report\nfrom sklearn.ensemble import RandomForestClassifier, VotingClassifier\nimport lightgbm as lgb\nimport xgboost as xgb\nfrom catboost import CatBoostClassifier\n\nwarnings.filterwarnings(\"ignore\")\nprint(\"Libraries imported successfully\")"},{"cell_type":"markdown","metadata":{},"source":"# データパス設定（競技データセット参照）\nDATA_DIR = Path('/kaggle/input/cmi-detect-behavior-with-sensor-data')\n\n# train.csvの読み込み（時系列データ）\nprint('Loading CMI competition data...')\ntrain_data = pd.read_csv(DATA_DIR / 'train.csv')\nprint(f'  Loaded train.csv: {len(train_data)} rows')\n\n# sequence_idでグループ化してメタデータ作成\ntrain_metadata = train_data.groupby('sequence_id').agg({\n    'subject': 'first',\n    'gesture': 'first',\n    'sequence_counter': 'count'\n}).reset_index()\ntrain_metadata.columns = ['sequence_id', 'subject', 'gesture', 'sequence_length']\n\n# ジェスチャーのラベルマッピング作成（重要！）\ngesture_labels = sorted(train_metadata['gesture'].unique())\ngesture_to_id = {label: i for i, label in enumerate(gesture_labels)}\nid_to_gesture = {i: label for label, i in gesture_to_id.items()}\n\nprint(f'\\nData loaded successfully:')\nprint(f'  Total sequences: {len(train_metadata)}')\nprint(f'  Gesture labels: {len(gesture_labels)} types')\nprint(f'  Gesture distribution:\\n{train_metadata[\"gesture\"].value_counts()}')"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# データパス設定（競技データセット参照）\nDATA_DIR = Path(\"/kaggle/input/cmi-detect-behavior-with-sensor-data\")\n\n# train.csvの読み込み（時系列データ）\nprint(\"Loading CMI competition data...\")\ntrain_data = pd.read_csv(DATA_DIR / \"train.csv\")\nprint(f\"  Loaded train.csv: {len(train_data)} rows\")\n\n# sequence_idでグループ化してメタデータ作成\ntrain_metadata = (\n    train_data.groupby(\"sequence_id\")\n    .agg({\"subject\": \"first\", \"gesture\": \"first\", \"sequence_counter\": \"count\"})\n    .reset_index()\n)\ntrain_metadata.columns = [\"sequence_id\", \"subject\", \"gesture\", \"sequence_length\"]\n\nprint(f\"\\nData loaded successfully:\")\nprint(f\"  Total sequences: {len(train_metadata)}\")\nprint(f\"  Gesture distribution:\\n{train_metadata['gesture'].value_counts()}\")"},{"cell_type":"markdown","metadata":{},"source":"## 2. 合成テストデータの生成"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"def analyze_real_data_distribution():\n    \"\"\"実データの統計的特性を分析\"\"\"\n\n    # ジェスチャー分布\n    gesture_dist = train_metadata[\"gesture\"].value_counts(normalize=True)\n\n    # センサーデータの統計（サンプリング）\n    sensor_stats = {}\n    sample_sequences = train_metadata.sample(min(100, len(train_metadata)))[\n        \"sequence_id\"\n    ].tolist()\n\n    all_acc = []\n    all_gyr = []\n    all_quat = []\n\n    for seq_id in sample_sequences:\n        # train.csvから該当シーケンスのデータを取得\n        seq_data = train_data[train_data[\"sequence_id\"] == seq_id]\n\n        # 加速度計\n        acc_cols = [c for c in seq_data.columns if c.startswith(\"acc_\")]\n        if acc_cols:\n            all_acc.append(seq_data[acc_cols].values)\n\n        # ジャイロスコープ\n        gyr_cols = [c for c in seq_data.columns if c.startswith(\"gyr_\")]\n        if gyr_cols:\n            all_gyr.append(seq_data[gyr_cols].values)\n\n        # クォータニオン\n        quat_cols = [c for c in seq_data.columns if \"quat\" in c]\n        if quat_cols:\n            all_quat.append(seq_data[quat_cols].values)\n\n    # 統計量計算\n    if all_acc:\n        acc_data = np.vstack(all_acc)\n        sensor_stats[\"acc\"] = {\n            \"mean\": np.mean(acc_data, axis=0),\n            \"std\": np.std(acc_data, axis=0),\n            \"percentiles\": [\n                np.percentile(acc_data, p, axis=0) for p in [5, 25, 50, 75, 95]\n            ],\n        }\n\n    if all_gyr:\n        gyr_data = np.vstack(all_gyr)\n        sensor_stats[\"gyr\"] = {\n            \"mean\": np.mean(gyr_data, axis=0),\n            \"std\": np.std(gyr_data, axis=0),\n            \"percentiles\": [\n                np.percentile(gyr_data, p, axis=0) for p in [5, 25, 50, 75, 95]\n            ],\n        }\n\n    return gesture_dist, sensor_stats\n\n\n# 実データ分析\nprint(\"Analyzing real data distribution...\")\ngesture_dist, sensor_stats = analyze_real_data_distribution()\nprint(\"Real data analysis completed\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"def generate_synthetic_sequence(gesture, seq_length, sensor_stats):\n    \"\"\"単一の合成シーケンスを生成\"\"\"\n\n    # 時間軸\n    time_stamps = np.arange(seq_length) * 0.02  # 50Hz\n\n    sequence_data = {\"time\": time_stamps}\n\n    # 加速度計データ（テストデータはより多様と予想）\n    if \"acc\" in sensor_stats:\n        acc_mean = sensor_stats[\"acc\"][\"mean\"]\n        acc_std = sensor_stats[\"acc\"][\"std\"] * 1.3  # テストは少し分散大きめ\n\n        # 基本データ\n        base_acc = np.random.normal(acc_mean, acc_std, (seq_length, len(acc_mean)))\n\n        # ジェスチャー固有のパターン\n        freq = 0.5 + gesture * 0.3  # 各ジェスチャーの周波数\n        amplitude = 0.3 + (gesture % 6) * 0.1\n\n        for j in range(min(3, base_acc.shape[1])):\n            phase = np.random.rand() * 2 * np.pi\n            base_acc[:, j] += amplitude * np.sin(2 * np.pi * freq * time_stamps + phase)\n\n            # 時々スパイクを追加（急な動き）\n            if np.random.rand() < 0.3:\n                spike_pos = np.random.randint(seq_length // 4, 3 * seq_length // 4)\n                base_acc[spike_pos : spike_pos + 5, j] *= 1.5\n\n        for j, col in enumerate([\"acc_x\", \"acc_y\", \"acc_z\"]):\n            sequence_data[col] = (\n                base_acc[:, j] if j < base_acc.shape[1] else np.zeros(seq_length)\n            )\n\n    # ジャイロスコープデータ\n    if \"gyr\" in sensor_stats:\n        gyr_mean = sensor_stats[\"gyr\"][\"mean\"]\n        gyr_std = sensor_stats[\"gyr\"][\"std\"] * 1.2\n\n        base_gyr = np.random.normal(gyr_mean, gyr_std, (seq_length, len(gyr_mean)))\n\n        # 回転パターン\n        rotation_speed = 0.05 * (1 + gesture % 8)\n        for j in range(min(3, base_gyr.shape[1])):\n            base_gyr[:, j] += rotation_speed * np.cos(np.pi * time_stamps * (1 + j * 0.5))\n\n        for j, col in enumerate([\"gyr_x\", \"gyr_y\", \"gyr_z\"]):\n            sequence_data[col] = (\n                base_gyr[:, j] if j < base_gyr.shape[1] else np.zeros(seq_length)\n            )\n\n    # クォータニオン（単位クォータニオン）\n    angles = np.random.randn(seq_length, 3) * 0.1\n    angles = np.cumsum(angles, axis=0)\n\n    quat = np.zeros((seq_length, 4))\n    for i in range(seq_length):\n        roll, pitch, yaw = angles[i]\n        cy = np.cos(yaw * 0.5)\n        sy = np.sin(yaw * 0.5)\n        cp = np.cos(pitch * 0.5)\n        sp = np.sin(pitch * 0.5)\n        cr = np.cos(roll * 0.5)\n        sr = np.sin(roll * 0.5)\n\n        quat[i, 0] = cr * cp * cy + sr * sp * sy  # w\n        quat[i, 1] = sr * cp * cy - cr * sp * sy  # x\n        quat[i, 2] = cr * sp * cy + sr * cp * sy  # y\n        quat[i, 3] = cr * cp * sy - sr * sp * cy  # z\n\n    sequence_data[\"quat_w\"] = quat[:, 0]\n    sequence_data[\"quat_x\"] = quat[:, 1]\n    sequence_data[\"quat_y\"] = quat[:, 2]\n    sequence_data[\"quat_z\"] = quat[:, 3]\n\n    # ToFセンサー（距離データ）\n    for tof_id in range(8):\n        base_distance = np.random.uniform(50, 2500)\n\n        if gesture < 6:  # 近距離ジェスチャー\n            distance_pattern = base_distance - 200 * np.sin(2 * np.pi * 0.5 * time_stamps)\n        elif gesture < 12:  # 中距離ジェスチャー\n            distance_pattern = base_distance + 100 * np.cos(np.pi * time_stamps)\n        else:  # 遠距離ジェスチャー\n            distance_pattern = base_distance + np.random.normal(0, 50, seq_length)\n\n        sequence_data[f\"tof_{tof_id}\"] = np.maximum(0, distance_pattern)\n\n    return pd.DataFrame(sequence_data)\n\n\nprint(\"Synthetic sequence generator ready\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"def generate_synthetic_dataset(n_sequences=1000):\n    \"\"\"合成データセットを生成\"\"\"\n\n    synthetic_sequences = []\n    synthetic_metadata = []\n\n    # テストデータのジェスチャー分布を予想（より均等に）\n    gestures = list(range(18))\n\n    # 実データ分布30% + 均等分布70%\n    real_probs = gesture_dist.reindex(gestures, fill_value=0.01).values\n    uniform_probs = np.ones(18) / 18\n    test_probs = 0.3 * real_probs + 0.7 * uniform_probs\n    test_probs /= test_probs.sum()\n\n    print(f\"Generating {n_sequences} synthetic sequences...\")\n\n    for i in range(n_sequences):\n        # ジェスチャー選択\n        gesture = np.random.choice(gestures, p=test_probs)\n\n        # シーケンス長（テストでは少し長め）\n        seq_length = int(np.random.normal(130, 25))\n        seq_length = max(60, min(250, seq_length))\n\n        # シーケンス生成\n        seq_df = generate_synthetic_sequence(gesture, seq_length, sensor_stats)\n\n        synthetic_sequences.append(seq_df)\n        synthetic_metadata.append(\n            {\n                \"sequence_id\": f\"synthetic_{i:06d}\",\n                \"subject\": f\"synth_sub_{i % 30}\",  # 30人の合成被験者\n                \"gesture\": id_to_gesture[gesture],  # 整数IDを文字列ラベルに変換\n                \"sequence_length\": seq_length,\n            }\n        )\n\n        if (i + 1) % 200 == 0:\n            print(f\"  Generated {i + 1}/{n_sequences} sequences\")\n\n    synthetic_metadata_df = pd.DataFrame(synthetic_metadata)\n\n    print(f\"\\nSynthetic data generated:\")\n    print(f\"  Total sequences: {len(synthetic_sequences)}\")\n    print(f\"  Avg sequence length: {synthetic_metadata_df['sequence_length'].mean():.1f}\")\n    print(\n        f\"  Gesture distribution:\\n{synthetic_metadata_df['gesture'].value_counts().sort_index()}\"\n    )\n\n    return synthetic_sequences, synthetic_metadata_df\n\n\n# 合成データ生成（実データと同数）\nn_synthetic = len(train_metadata)\nsynthetic_sequences, synthetic_metadata = generate_synthetic_dataset(n_synthetic)"},{"cell_type":"markdown","metadata":{},"source":"## 3. 特徴量エンジニアリング"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"def extract_features(df, seq_id, metadata_row):\n    \"\"\"高度な特徴量抽出\"\"\"\n    features = {}\n\n    # メタデータ特徴\n    features[\"subject_id\"] = hash(metadata_row[\"subject\"]) % 1000\n    features[\"seq_length\"] = len(df)\n\n    # 加速度計特徴\n    for axis in [\"x\", \"y\", \"z\"]:\n        col = f\"acc_{axis}\"\n        if col in df.columns:\n            values = df[col].values\n            features[f\"acc_{axis}_mean\"] = np.mean(values)\n            features[f\"acc_{axis}_std\"] = np.std(values)\n            features[f\"acc_{axis}_max\"] = np.max(values)\n            features[f\"acc_{axis}_min\"] = np.min(values)\n            features[f\"acc_{axis}_range\"] = np.ptp(values)\n            features[f\"acc_{axis}_skew\"] = pd.Series(values).skew()\n            features[f\"acc_{axis}_kurt\"] = pd.Series(values).kurt()\n\n            # 周波数領域特徴\n            fft_vals = np.abs(np.fft.fft(values))[: len(values) // 2]\n            features[f\"acc_{axis}_fft_max\"] = np.max(fft_vals) if len(fft_vals) > 0 else 0\n            features[f\"acc_{axis}_fft_mean\"] = (\n                np.mean(fft_vals) if len(fft_vals) > 0 else 0\n            )\n\n            # ジャーク\n            if len(values) > 1:\n                jerk = np.diff(values)\n                features[f\"acc_{axis}_jerk_mean\"] = np.mean(np.abs(jerk))\n                features[f\"acc_{axis}_jerk_std\"] = np.std(jerk)\n            else:\n                features[f\"acc_{axis}_jerk_mean\"] = 0\n                features[f\"acc_{axis}_jerk_std\"] = 0\n\n    # 加速度の大きさ\n    if all(f\"acc_{axis}\" in df.columns for axis in [\"x\", \"y\", \"z\"]):\n        acc_magnitude = np.sqrt(df[\"acc_x\"] ** 2 + df[\"acc_y\"] ** 2 + df[\"acc_z\"] ** 2)\n        features[\"acc_magnitude_mean\"] = np.mean(acc_magnitude)\n        features[\"acc_magnitude_std\"] = np.std(acc_magnitude)\n        features[\"acc_magnitude_max\"] = np.max(acc_magnitude)\n\n    # ジャイロスコープ特徴\n    for axis in [\"x\", \"y\", \"z\"]:\n        col = f\"gyr_{axis}\"\n        if col in df.columns:\n            values = df[col].values\n            features[f\"gyr_{axis}_mean\"] = np.mean(values)\n            features[f\"gyr_{axis}_std\"] = np.std(values)\n            features[f\"gyr_{axis}_max\"] = np.max(values)\n            features[f\"gyr_{axis}_energy\"] = np.sum(values**2)\n\n    # 角速度の大きさ\n    if all(f\"gyr_{axis}\" in df.columns for axis in [\"x\", \"y\", \"z\"]):\n        gyr_magnitude = np.sqrt(df[\"gyr_x\"] ** 2 + df[\"gyr_y\"] ** 2 + df[\"gyr_z\"] ** 2)\n        features[\"gyr_magnitude_mean\"] = np.mean(gyr_magnitude)\n        features[\"gyr_magnitude_std\"] = np.std(gyr_magnitude)\n\n    # クォータニオン特徴\n    for comp in [\"w\", \"x\", \"y\", \"z\"]:\n        col = f\"quat_{comp}\"\n        if col in df.columns:\n            values = df[col].values\n            features[f\"quat_{comp}_mean\"] = np.mean(values)\n            features[f\"quat_{comp}_std\"] = np.std(values)\n            features[f\"quat_{comp}_change\"] = (\n                values[-1] - values[0] if len(values) > 0 else 0\n            )\n\n    # ToFセンサー特徴（次元削減）\n    tof_features = []\n    for i in range(8):\n        col = f\"tof_{i}\"\n        if col in df.columns:\n            values = df[col].values\n            tof_features.extend([np.mean(values), np.std(values), np.median(values)])\n\n    # ToF主成分（最初の5成分のみ）\n    if tof_features:\n        for i in range(min(5, len(tof_features))):\n            features[f\"tof_pc_{i}\"] = tof_features[i]\n\n    # ラベル（文字列の場合は整数IDに変換）\n    gesture_value = metadata_row[\"gesture\"]\n    if isinstance(gesture_value, str):\n        features[\"gesture\"] = gesture_to_id.get(gesture_value, 0)\n    else:\n        features[\"gesture\"] = gesture_value\n\n    return features\n\n\nprint(\"Feature extractor ready\")"},{"cell_type":"markdown","metadata":{},"source":"## 4. データセット準備（実データ50% + 合成データ50%）"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# 実データから特徴量抽出（50%サンプリング）\nreal_sample = train_metadata.sample(n=len(train_metadata) // 2, random_state=42)\nprint(f\"Processing {len(real_sample)} real sequences...\")\n\nreal_features = []\nfailed_count = 0\nfor idx, row in real_sample.iterrows():\n    try:\n        # train.csvから該当シーケンスのデータを取得\n        seq_data = train_data[train_data[\"sequence_id\"] == row[\"sequence_id\"]]\n        if len(seq_data) > 0:\n            features = extract_features(seq_data, row[\"sequence_id\"], row)\n            real_features.append(features)\n    except Exception as e:\n        failed_count += 1\n        if failed_count <= 5:  # 最初の5個のエラーのみ表示\n            print(f\"  Failed to process {row['sequence_id']}: {e}\")\n        continue\n\nprint(f\"Extracted features from {len(real_features)} real sequences\")\nif failed_count > 0:\n    print(f\"  Failed to process {failed_count} sequences\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# 合成データから特徴量抽出（50%）\nsynthetic_sample_idx = np.random.choice(\n    len(synthetic_sequences), size=len(synthetic_sequences) // 2, replace=False\n)\nprint(f\"Processing {len(synthetic_sample_idx)} synthetic sequences...\")\n\nsynthetic_features = []\nfor idx in synthetic_sample_idx:\n    row = synthetic_metadata.iloc[idx]\n    df = synthetic_sequences[idx]\n    features = extract_features(df, row[\"sequence_id\"], row)\n    synthetic_features.append(features)\n\nprint(f\"Extracted features from {len(synthetic_features)} synthetic sequences\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# 結合\nall_features = real_features + synthetic_features\nfeature_df = pd.DataFrame(all_features)\n\n# データ型の確認と修正\nfeature_df = feature_df.fillna(0)\n\nprint(f\"\\nCombined dataset:\")\nprint(f\"  Total samples: {len(feature_df)}\")\nprint(\n    f\"  Real data: {len(real_features)} ({len(real_features) / len(feature_df) * 100:.1f}%)\"\n)\nprint(\n    f\"  Synthetic data: {len(synthetic_features)} ({len(synthetic_features) / len(feature_df) * 100:.1f}%)\"\n)\nprint(f\"  Features: {feature_df.shape[1] - 1}\")\nprint(f\"\\nGesture distribution:\")\nprint(feature_df[\"gesture\"].value_counts().sort_index())"},{"cell_type":"markdown","metadata":{},"source":"## 5. アンサンブルモデルのトレーニング"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# 特徴量とラベルの分離\nX = feature_df.drop(\"gesture\", axis=1)\ny = feature_df[\"gesture\"]\n\n# スケーリング\nscaler = StandardScaler()\nX_scaled = scaler.fit_transform(X)\n\n# Train/Validation分割\nX_train, X_val, y_train, y_val = train_test_split(\n    X_scaled, y, test_size=0.2, random_state=42, stratify=y\n)\n\nprint(f\"Training set: {X_train.shape}\")\nprint(f\"Validation set: {X_val.shape}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# LightGBMモデル\nlgb_params = {\n    \"objective\": \"multiclass\",\n    \"num_class\": 18,\n    \"metric\": \"multi_logloss\",\n    \"boosting_type\": \"gbdt\",\n    \"num_leaves\": 63,\n    \"learning_rate\": 0.05,\n    \"feature_fraction\": 0.8,\n    \"bagging_fraction\": 0.8,\n    \"bagging_freq\": 5,\n    \"verbose\": 0,\n    \"seed\": 42,\n    \"n_jobs\": -1,\n}\n\nlgb_train = lgb.Dataset(X_train, label=y_train)\nlgb_val = lgb.Dataset(X_val, label=y_val, reference=lgb_train)\n\nprint(\"Training LightGBM...\")\nlgb_model = lgb.train(\n    lgb_params,\n    lgb_train,\n    valid_sets=[lgb_val],\n    num_boost_round=300,\n    callbacks=[lgb.early_stopping(50), lgb.log_evaluation(50)],\n)\n\n# 検証\nlgb_pred = lgb_model.predict(X_val, num_iteration=lgb_model.best_iteration)\nlgb_pred_class = np.argmax(lgb_pred, axis=1)\nlgb_acc = accuracy_score(y_val, lgb_pred_class)\nprint(f\"LightGBM Validation Accuracy: {lgb_acc:.4f}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# XGBoostモデル\nxgb_params = {\n    \"objective\": \"multi:softprob\",\n    \"num_class\": 18,\n    \"max_depth\": 6,\n    \"learning_rate\": 0.05,\n    \"subsample\": 0.8,\n    \"colsample_bytree\": 0.8,\n    \"seed\": 42,\n    \"n_jobs\": -1,\n    \"eval_metric\": \"mlogloss\",\n}\n\ndtrain = xgb.DMatrix(X_train, label=y_train)\ndval = xgb.DMatrix(X_val, label=y_val)\n\nprint(\"Training XGBoost...\")\nxgb_model = xgb.train(\n    xgb_params,\n    dtrain,\n    num_boost_round=300,\n    evals=[(dval, \"val\")],\n    early_stopping_rounds=50,\n    verbose_eval=50,\n)\n\n# 検証\nxgb_pred = xgb_model.predict(dval)\nxgb_pred_class = np.argmax(xgb_pred, axis=1)\nxgb_acc = accuracy_score(y_val, xgb_pred_class)\nprint(f\"XGBoost Validation Accuracy: {xgb_acc:.4f}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# CatBoostモデル\ncat_model = CatBoostClassifier(\n    iterations=300,\n    learning_rate=0.05,\n    depth=6,\n    loss_function=\"MultiClass\",\n    classes_count=18,\n    random_seed=42,\n    verbose=50,\n    early_stopping_rounds=50,\n)\n\nprint(\"Training CatBoost...\")\ncat_model.fit(X_train, y_train, eval_set=(X_val, y_val), use_best_model=True)\n\n# 検証\ncat_pred_class = cat_model.predict(X_val)\ncat_acc = accuracy_score(y_val, cat_pred_class)\nprint(f\"CatBoost Validation Accuracy: {cat_acc:.4f}\")"},{"cell_type":"markdown","metadata":{},"source":"## 6. アンサンブル予測"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# アンサンブル予測（加重平均）\nlgb_weight = 0.5\nxgb_weight = 0.2\ncat_weight = 0.3\n\n# 各モデルの予測確率\nlgb_proba = lgb_model.predict(X_val, num_iteration=lgb_model.best_iteration)\nxgb_proba = xgb_model.predict(dval)\ncat_proba = cat_model.predict_proba(X_val)\n\n# 加重平均\nensemble_proba = lgb_weight * lgb_proba + xgb_weight * xgb_proba + cat_weight * cat_proba\nensemble_pred = np.argmax(ensemble_proba, axis=1)\n\n# 最終精度\nensemble_acc = accuracy_score(y_val, ensemble_pred)\nprint(f\"\\n=== Final Results ===\")\nprint(f\"LightGBM: {lgb_acc:.4f}\")\nprint(f\"XGBoost: {xgb_acc:.4f}\")\nprint(f\"CatBoost: {cat_acc:.4f}\")\nprint(f\"Ensemble: {ensemble_acc:.4f}\")\n\nprint(f\"\\n分類レポート:\")\nprint(classification_report(y_val, ensemble_pred))"},{"cell_type":"markdown","metadata":{},"source":"## 7. モデルの保存"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# モデルとスケーラーを保存\nmodel_dir = Path(\"/kaggle/working/models\")\nmodel_dir.mkdir(exist_ok=True)\n\n# LightGBM\nlgb_model.save_model(str(model_dir / \"lgb_model_v11.txt\"))\n\n# XGBoost\nxgb_model.save_model(str(model_dir / \"xgb_model_v11.json\"))\n\n# CatBoost\ncat_model.save_model(str(model_dir / \"cat_model_v11.cbm\"))\n\n# スケーラーと特徴量名\nwith open(model_dir / \"scaler_v11.pkl\", \"wb\") as f:\n    pickle.dump(scaler, f)\n\nwith open(model_dir / \"feature_names_v11.pkl\", \"wb\") as f:\n    pickle.dump(list(X.columns), f)\n\n# モデル情報\nmodel_info = {\n    \"version\": \"v11_synthetic_fixed\",\n    \"training_data\": {\n        \"real_samples\": len(real_features),\n        \"synthetic_samples\": len(synthetic_features),\n        \"total_samples\": len(feature_df),\n    },\n    \"validation_accuracy\": {\n        \"lgb\": float(lgb_acc),\n        \"xgb\": float(xgb_acc),\n        \"catboost\": float(cat_acc),\n        \"ensemble\": float(ensemble_acc),\n    },\n    \"ensemble_weights\": {\"lgb\": lgb_weight, \"xgb\": xgb_weight, \"catboost\": cat_weight},\n    \"feature_count\": X.shape[1],\n}\n\nwith open(model_dir / \"model_info_v11.json\", \"w\") as f:\n    json.dump(model_info, f, indent=2)\n\nprint(f\"\\nModels saved to {model_dir}\")\nprint(f\"Files created:\")\nfor file in model_dir.glob(\"*v11*\"):\n    print(f\"  - {file.name}\")"},{"cell_type":"markdown","metadata":{},"source":"## 8. 結果サマリー"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"print(\"=\" * 50)\nprint(\"V11 合成データモデル トレーニング完了\")\nprint(\"=\" * 50)\nprint(f\"\\n実データ: {len(real_features)} samples\")\nprint(f\"合成データ: {len(synthetic_features)} samples\")\nprint(f\"\\n検証精度:\")\nprint(f\"  - アンサンブル: {ensemble_acc:.2%}\")\nprint(f\"  - LightGBM: {lgb_acc:.2%}\")\nprint(f\"  - XGBoost: {xgb_acc:.2%}\")\nprint(f\"  - CatBoost: {cat_acc:.2%}\")\nprint(f\"\\n次のステップ:\")\nprint(f\"1. このノートブックを実行してモデルを生成\")\nprint(f\"2. /kaggle/working/models のモデルをKaggle Datasetとしてアップロード\")\nprint(f\"3. 推論ノートブックからDatasetを参照して予測\")\nprint(f\"4. スコアを確認して合成データの有効性を評価\")"}],"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.11.0"}},"nbformat":4,"nbformat_minor":4}