{"cells":[{"cell_type":"markdown","metadata":{},"source":"# CMI V11 Fixed: 合成データによる事前学習済みモデル構築\n\n実データ50% + 合成テストデータ50%でトレーニングし、Kaggle Datasetとして保存"},{"cell_type":"code","execution_count":null,"metadata":{},"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')","outputs":[]},{"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":{},"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\nprint(f'\\nData loaded successfully:')\nprint(f'  Total sequences: {len(train_metadata)}')\nprint(f'  Gesture distribution:\\n{train_metadata[\"gesture\"].value_counts()}')","outputs":[]},{"cell_type":"markdown","metadata":{},"source":"## 2. 合成テストデータの生成"},{"cell_type":"code","execution_count":null,"metadata":{},"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)))['sequence_id'].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': [np.percentile(acc_data, p, axis=0) for p in [5, 25, 50, 75, 95]]\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': [np.percentile(gyr_data, p, axis=0) for p in [5, 25, 50, 75, 95]]\n        }\n    \n    return gesture_dist, sensor_stats\n\n# 実データ分析\nprint('Analyzing real data distribution...')\ngesture_dist, sensor_stats = analyze_real_data_distribution()\nprint('Real data analysis completed')","outputs":[]},{"cell_type":"code","execution_count":null,"metadata":{},"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] = base_acc[:, j] if j < base_acc.shape[1] else np.zeros(seq_length)\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] = base_gyr[:, j] if j < base_gyr.shape[1] else np.zeros(seq_length)\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\nprint('Synthetic sequence generator ready')","outputs":[]},{"cell_type":"code","execution_count":null,"metadata":{},"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            '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        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(f'  Gesture distribution:\\n{synthetic_metadata_df[\"gesture\"].value_counts().sort_index()}')\n    \n    return synthetic_sequences, synthetic_metadata_df\n\n# 合成データ生成（実データと同数）\nn_synthetic = len(train_metadata)\nsynthetic_sequences, synthetic_metadata = generate_synthetic_dataset(n_synthetic)","outputs":[]},{"cell_type":"markdown","metadata":{},"source":"## 3. 特徴量エンジニアリング"},{"cell_type":"code","execution_count":null,"metadata":{},"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'] = np.mean(fft_vals) if len(fft_vals) > 0 else 0\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'] = values[-1] - values[0] if len(values) > 0 else 0\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([\n                np.mean(values),\n                np.std(values),\n                np.median(values)\n            ])\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\nprint('Feature extractor ready')","outputs":[]},{"cell_type":"markdown","metadata":{},"source":"## 4. データセット準備（実データ50% + 合成データ50%）"},{"cell_type":"code","execution_count":null,"metadata":{},"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')","outputs":[]},{"cell_type":"code","execution_count":null,"metadata":{},"source":"# 合成データから特徴量抽出（50%）\nsynthetic_sample_idx = np.random.choice(len(synthetic_sequences), \n                                       size=len(synthetic_sequences)//2, \n                                       replace=False)\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')","outputs":[]},{"cell_type":"code","execution_count":null,"metadata":{},"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(f'  Real data: {len(real_features)} ({len(real_features)/len(feature_df)*100:.1f}%)')\nprint(f'  Synthetic data: {len(synthetic_features)} ({len(synthetic_features)/len(feature_df)*100:.1f}%)')\nprint(f'  Features: {feature_df.shape[1] - 1}')\nprint(f'\\nGesture distribution:')\nprint(feature_df['gesture'].value_counts().sort_index())","outputs":[]},{"cell_type":"markdown","metadata":{},"source":"## 5. アンサンブルモデルのトレーニング"},{"cell_type":"code","execution_count":null,"metadata":{},"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}')","outputs":[]},{"cell_type":"code","execution_count":null,"metadata":{},"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}')","outputs":[]},{"cell_type":"code","execution_count":null,"metadata":{},"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}')","outputs":[]},{"cell_type":"code","execution_count":null,"metadata":{},"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(\n    X_train, y_train,\n    eval_set=(X_val, y_val),\n    use_best_model=True\n)\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}')","outputs":[]},{"cell_type":"markdown","metadata":{},"source":"## 6. アンサンブル予測"},{"cell_type":"code","execution_count":null,"metadata":{},"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))","outputs":[]},{"cell_type":"markdown","metadata":{},"source":"## 7. モデルの保存"},{"cell_type":"code","execution_count":null,"metadata":{},"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': {\n        'lgb': lgb_weight,\n        'xgb': xgb_weight,\n        'catboost': cat_weight\n    },\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}')","outputs":[]},{"cell_type":"markdown","metadata":{},"source":"## 8. 結果サマリー"},{"cell_type":"code","execution_count":null,"metadata":{},"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. スコアを確認して合成データの有効性を評価')","outputs":[]}],"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}