{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":245501000,"sourceType":"kernelVersion"},{"sourceId":245905680,"sourceType":"kernelVersion"}],"dockerImageVersionId":31041,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport polars as pl\nimport joblib\nimport traceback\n\n# モデルとエンコーダーの読み込み（エラーハンドリング追加）\ntry:\n    models = []\n    for fold in range(5):\n        model = joblib.load(f'/kaggle/input/exp001-training/model_lgb{fold}.joblib')\n        models.append(model)\n    le = joblib.load('/kaggle/input/exp001-training/le.joblib')\n    print(f\"Successfully loaded {len(models)} models and label encoder\")\nexcept Exception as e:\n    print(f\"Error loading models: {e}\")\n    raise\n\nfrom sklearn.base import BaseEstimator, TransformerMixin\nimport polars as pl\nimport numpy as np\nfrom tqdm import tqdm\n\nclass PolarsBasicStatsExtractor(BaseEstimator, TransformerMixin):\n    def __init__(self, sensor_cols, group_col=\"sequence_id\"):\n        self.sensor_cols = sensor_cols\n        self.group_col = group_col\n    \n    def fit(self, X, y=None):\n        return self\n    \n    def transform(self, X):\n        try:\n            if not isinstance(X, pl.DataFrame):\n                X = pl.DataFrame(X)\n            \n            aggs = []\n            for col in self.sensor_cols:\n                # NaNやInfの処理を追加\n                aggs.extend([\n                    pl.col(col).drop_nans().mean().alias(f\"{col}_mean\"),\n                    pl.col(col).drop_nans().std().alias(f\"{col}_std\"), \n                    pl.col(col).drop_nans().min().alias(f\"{col}_min\"),\n                    pl.col(col).drop_nans().max().alias(f\"{col}_max\"),\n                    pl.col(col).drop_nans().median().alias(f\"{col}_median\"),\n                    pl.col(col).drop_nans().skew().alias(f\"{col}_skew\"),\n                ])\n            \n            result = X.group_by(self.group_col).agg(aggs)\n            # NaNを0で埋める\n            return result.fill_nan(0).fill_null(0)\n        except Exception as e:\n            print(f\"Error in PolarsBasicStatsExtractor: {e}\")\n            raise\n\nclass PolarsDiffRateExtractor(BaseEstimator, TransformerMixin):\n    def __init__(self, sensor_cols, group_col=\"sequence_id\"):\n        self.sensor_cols = sensor_cols\n        self.group_col = group_col\n    \n    def fit(self, X, y=None):\n        return self\n    \n    def transform(self, X):\n        try:\n            X_sorted = X.sort(by=[self.group_col])\n            \n            for col in self.sensor_cols:\n                X_sorted = X_sorted.with_columns([\n                    pl.col(col).diff().over(self.group_col).fill_nan(0).alias(f\"{col}_diff\"),\n                    pl.col(col).pct_change().over(self.group_col).fill_nan(0).alias(f\"{col}_rate\")\n                ])\n            return X_sorted\n        except Exception as e:\n            print(f\"Error in PolarsDiffRateExtractor: {e}\")\n            raise\n\nclass PolarsFFTFeatureExtractor(BaseEstimator, TransformerMixin):\n    def __init__(self, sensor_cols, group_col=\"sequence_id\"):\n        self.sensor_cols = sensor_cols\n        self.group_col = group_col\n    \n    def fit(self, X, y=None):\n        return self\n    \n    def transform(self, X):\n        try:\n            features = []\n            groups = list(X.group_by(self.group_col, maintain_order=True))\n            \n            for seq_id, group in groups:\n                if isinstance(seq_id, tuple):\n                    seq_id = seq_id[0]\n                \n                row = {self.group_col: seq_id}\n                for col in self.sensor_cols:\n                    try:\n                        signal = group[col].to_numpy()\n                        # NaNや無限値をチェック\n                        signal = signal[~np.isnan(signal)]\n                        if len(signal) == 0:\n                            # データがない場合のデフォルト値\n                            row[f\"{col}_fft_peak_freq\"] = 0\n                            row[f\"{col}_fft_energy\"] = 0.0\n                        else:\n                            fft_vals = np.fft.fft(signal)\n                            fft_abs = np.abs(fft_vals)\n                            \n                            # エラーハンドリング強化\n                            if len(fft_abs) > 1:\n                                peak_freq = int(np.argmax(fft_abs[1:]) + 1)\n                                energy = float(np.sum(fft_abs ** 2))\n                            else:\n                                peak_freq = 0\n                                energy = 0.0\n                            \n                            # 無限値チェック\n                            if np.isinf(energy) or np.isnan(energy):\n                                energy = 0.0\n                            \n                            row[f\"{col}_fft_peak_freq\"] = peak_freq\n                            row[f\"{col}_fft_energy\"] = energy\n                    except Exception as e:\n                        print(f\"Error processing FFT for {col}: {e}\")\n                        row[f\"{col}_fft_peak_freq\"] = 0\n                        row[f\"{col}_fft_energy\"] = 0.0\n                        \n                features.append(row)\n            return pl.DataFrame(features)\n        except Exception as e:\n            print(f\"Error in PolarsFFTFeatureExtractor: {e}\")\n            raise\n\nclass PolarsSegmentStatsExtractor(BaseEstimator, TransformerMixin):\n    def __init__(self, sensor_cols, group_col=\"sequence_id\", n_segments=3):\n        self.sensor_cols = sensor_cols\n        self.group_col = group_col\n        self.n_segments = n_segments\n    \n    def fit(self, X, y=None):\n        return self\n    \n    def transform(self, X):\n        try:\n            rows = []\n            groups = list(X.group_by(self.group_col, maintain_order=True))\n            \n            for seq_id, group in groups:\n                if isinstance(seq_id, tuple):\n                    seq_id = seq_id[0]\n                    \n                segment_size = max(1, len(group) // self.n_segments)\n                feature = {self.group_col: seq_id}\n                \n                for i in range(self.n_segments):\n                    start_idx = i * segment_size\n                    end_idx = min((i + 1) * segment_size, len(group))\n                    seg = group[start_idx:end_idx]\n                    \n                    for col in self.sensor_cols:\n                        try:\n                            mean_val = seg[col].mean()\n                            std_val = seg[col].std()\n                            \n                            # NaN/Inf チェック\n                            feature[f\"{col}_seg{i}_mean\"] = 0.0 if (mean_val is None or np.isnan(mean_val) or np.isinf(mean_val)) else float(mean_val)\n                            feature[f\"{col}_seg{i}_std\"] = 0.0 if (std_val is None or np.isnan(std_val) or np.isinf(std_val)) else float(std_val)\n                        except:\n                            feature[f\"{col}_seg{i}_mean\"] = 0.0\n                            feature[f\"{col}_seg{i}_std\"] = 0.0\n                            \n                rows.append(feature)\n            return pl.DataFrame(rows)\n        except Exception as e:\n            print(f\"Error in PolarsSegmentStatsExtractor: {e}\")\n            raise\n\n# 特徴量エンジニアリングパイプライン\ndef fe_pipeline(df, sensor_cols):\n    try:\n        print(\"Starting feature engineering pipeline...\")\n        if not isinstance(df, pl.DataFrame):\n            df = pl.DataFrame(df)\n        \n        print(\"\\n=== Step 1/4: Basic Statistics ===\")\n        basic_stats = PolarsBasicStatsExtractor(sensor_cols).transform(df)\n        \n        print(\"\\n=== Step 2/4: Diff/Rate Features ===\")\n        df_diff = PolarsDiffRateExtractor(sensor_cols).transform(df)\n        \n        print(\"\\n=== Step 3/4: FFT Features ===\")\n        fft_features = PolarsFFTFeatureExtractor(sensor_cols).transform(df_diff)\n        \n        print(\"\\n=== Step 4/4: Segment Statistics ===\")\n        segment_stats = PolarsSegmentStatsExtractor(sensor_cols).transform(df)\n        \n        print(\"\\n=== Joining all features ===\")\n        result = (\n            basic_stats\n            .join(fft_features, on=\"sequence_id\", how=\"inner\")\n            .join(segment_stats, on=\"sequence_id\", how=\"inner\")\n        )\n        print(f\"Feature engineering completed! Shape: {result.shape}\")\n        return result\n    except Exception as e:\n        print(f\"Error in fe_pipeline: {e}\")\n        print(traceback.format_exc())\n        raise\n\nsensor_cols = [\n    \"acc_x\", \"acc_y\", \"acc_z\",\n    \"rot_w\", \"rot_x\", \"rot_y\", \"rot_z\"\n]\n\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    try:\n        print(f\"Processing sequence with shape: {sequence.shape}\")\n        \n        # 特徴量抽出\n        cleaned_data = fe_pipeline(sequence, sensor_cols)\n        \n        # Pandasに変換して予測\n        pdf = cleaned_data.to_pandas().drop(columns=[\"sequence_id\"])\n        print(f\"Features shape for prediction: {pdf.shape}\")\n        \n        # アンサンブル予測\n        predictions = []\n        for i, model in enumerate(models):\n            try:\n                pred = model.predict(pdf)\n                if isinstance(pred, np.ndarray):\n                    pred = pred[0]\n                predictions.append(int(pred))\n                print(f\"Model {i} prediction: {pred}\")\n            except Exception as e:\n                print(f\"Error with model {i}: {e}\")\n                # デフォルト予測を追加\n                predictions.append(0)\n        \n        # 多数決\n        if predictions:\n            predicted_label_id = max(set(predictions), key=predictions.count)\n        else:\n            predicted_label_id = 0  # デフォルト\n        \n        # ラベルを文字列に変換\n        try:\n            predicted_gesture_str = le.inverse_transform([predicted_label_id])[0]\n        except:\n            # エラーの場合はデフォルトラベルを返す\n            predicted_gesture_str = le.classes_[0] if hasattr(le, 'classes_') else \"unknown\"\n        \n        print(f\"Final prediction: {predicted_gesture_str}\")\n        return predicted_gesture_str\n        \n    except Exception as e:\n        print(f\"Error in predict function: {e}\")\n        print(traceback.format_exc())\n        # エラーの場合はデフォルト値を返す\n        try:\n            return le.classes_[0] if hasattr(le, 'classes_') else \"unknown\"\n        except:\n            return \"unknown\"\n\n# 推論サーバーの実行\nimport kaggle_evaluation.cmi_inference_server\nimport os\n\ntry:\n    inference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n    \n    if os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n        print(\"Running in competition mode\")\n        inference_server.serve()\n    else:\n        print(\"Running in local test mode\")\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        )\nexcept Exception as e:\n    print(f\"Error running inference server: {e}\")\n    print(traceback.format_exc())\n    raise","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-17T10:33:22.952398Z","iopub.execute_input":"2025-06-17T10:33:22.952941Z","iopub.status.idle":"2025-06-17T10:33:31.598812Z","shell.execute_reply.started":"2025-06-17T10:33:22.952917Z","shell.execute_reply":"2025-06-17T10:33:31.598090Z"}},"outputs":[],"execution_count":null}]}