{"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":"none","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12161592,"sourceType":"datasetVersion","datasetId":7587404}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import torch\nimport sys\nsys.path.append('/kaggle/input/weights')\nimport os, joblib\nimport warnings\nwarnings.filterwarnings('ignore')\nimport numpy as np\nimport pandas as pd\nimport polars as pl\nimport kaggle_evaluation.cmi_inference_server\nfrom cnn_AutoEncoder import *\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.model_selection import train_test_split\nfrom scipy.signal import find_peaks\nfrom tqdm import tqdm\nfrom sklearn.preprocessing import LabelEncoder","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-14T06:27:51.022171Z","iopub.execute_input":"2025-06-14T06:27:51.022570Z","iopub.status.idle":"2025-06-14T06:27:51.028871Z","shell.execute_reply.started":"2025-06-14T06:27:51.022542Z","shell.execute_reply":"2025-06-14T06:27:51.027808Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = CNNAutoEncoderWithGAP(latent_channels=32, dropout_p=0.0)\nmodel.load_state_dict(torch.load('/kaggle/input/weights/cnn_AE_250608.pth', map_location='cpu'))\n\nle = joblib.load('/kaggle/input/weights/label_encoder.pkl')\n\nscaler = joblib.load('/kaggle/input/weights/scaler_all_features_improved.pkl')\nscaler_imu = joblib.load('/kaggle/input/weights/scaler_imu_optimized.pkl')\n\nbest_model = joblib.load('/kaggle/input/weights/voting_ensemble_best_all_features_improved.pkl')\nimu_model = joblib.load('/kaggle/input/weights/voting_ensemble_best_imu_optimized_250613_1352.pkl')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T06:27:51.030297Z","iopub.execute_input":"2025-06-14T06:27:51.030702Z","iopub.status.idle":"2025-06-14T06:27:56.615294Z","shell.execute_reply.started":"2025-06-14T06:27:51.030566Z","shell.execute_reply":"2025-06-14T06:27:56.614285Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def feature_engineering(df: pd.DataFrame):\n    tqdm.pandas(desc=\"Sequence Features\")\n    imu_cols = ['acc_x','acc_y','acc_z','rot_w','rot_x','rot_y','rot_z','acc_mag']\n    vector_cols = [f'tof_v{i}' for i in range(32)]\n    \n    # 집계 대상 수치형 센서 컬럼 (id, 대상, 인구통계 제외)\n    stat_cols = [\n        c for c in df.columns\n        if c not in ['sequence_counter', \"sequence_id\", 'subject']+vector_cols\n    ]\n    \n    def extract_features(group):\n        feature = {}\n        # 시퀀스 길이\n        seq_len = group[\"sequence_counter\"].max()\n        feature[\"seq_len\"] = seq_len\n        \n        # 전역 구간에 대해 기본적인 통계 특징 추출\n        for col in stat_cols:\n            if col in imu_cols or 'thm' in col:\n                x = group[col]\n                feature[f\"{col}_mean\"] = x.mean()\n                feature[f\"{col}_std\"] = x.std()\n                feature[f\"{col}_min\"] = x.min()\n                feature[f\"{col}_max\"] = x.max()\n                feature[f\"{col}_q25\"] = x.quantile(0.25)\n                feature[f\"{col}_q50\"] = x.median()\n                feature[f\"{col}_q75\"] = x.quantile(0.75)\n                \n                diff = group[col].diff().fillna(0)\n                feature[f'{col}_diff_mean'] = diff.mean()\n                feature[f'{col}_diff_std'] = diff.std()\n\n        # 초기, 중간, 후기 phase로 나누어 조건부 집계\n        conditions = {\n            'first_30pct': group['sequence_counter'] < seq_len * 0.3,\n            'middle_40pct': (group['sequence_counter'] >= seq_len * 0.3) & (group['sequence_counter'] < seq_len * 0.7),\n            'last_10pct': group['sequence_counter'] >= seq_len * 0.9,\n            'last_20pct': group['sequence_counter'] >= seq_len * 0.8,\n            'last_30pct': group['sequence_counter'] >= seq_len * 0.7,\n            'last_40pct': group['sequence_counter'] >= seq_len * 0.6,\n            'window_10~20pct': (group['sequence_counter'] >= seq_len * 0.8) & (group['sequence_counter'] < seq_len * 0.9),\n            'window_20~30pct': (group['sequence_counter'] >= seq_len * 0.7) & (group['sequence_counter'] < seq_len * 0.8),\n            'window_30~40pct': (group['sequence_counter'] >= seq_len * 0.6) & (group['sequence_counter'] < seq_len * 0.7),\n        }\n\n        # phase에 대해 더 다양한 특징 추출\n        for part_name, mask in conditions.items():\n            part = group[mask]\n            for col in imu_cols:\n                x = part[col]\n                feature[f'{col}_mean_{part_name}'] = x.mean()\n                feature[f'{col}_std_{part_name}'] = x.std()\n                feature[f'{col}_median_{part_name}'] = x.median()\n                feature[f\"{col}_min_{part_name}\"] = x.min()\n                feature[f\"{col}_max_{part_name}\"] = x.max()\n                feature[f\"{col}_q25_{part_name}\"] = x.quantile(0.25)\n                feature[f\"{col}_q75_{part_name}\"] = x.quantile(0.75)\n                \n                diff = group[col].diff().fillna(0)\n                feature[f'{col}_diff_mean'] = diff.mean()\n                feature[f'{col}_diff_std'] = diff.std()\n        \n        # tof vector 통계\n        x = group[vector_cols].values.flatten() # (seq_len * 32)\n        feature[\"tof_vec_mean\"] = x.mean()\n        feature[\"tof_vec_std\"] = x.std()\n        feature[\"tof_vec_q25\"] = np.quantile(x, 0.25)\n        feature[\"tof_vec_q50\"] = np.median(x)\n        feature[\"tof_vec_q75\"] = np.quantile(x,0.75)\n\n        return pd.Series(feature)\n\n    # 시퀀스별 통계 집계\n    cleaned_df = df.groupby(\"sequence_id\").progress_apply(extract_features).reset_index()\n\n    # 결측치 0으로 채우기\n    cleaned_df = cleaned_df.fillna(0)\n    return cleaned_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T06:27:56.620773Z","iopub.execute_input":"2025-06-14T06:27:56.621110Z","iopub.status.idle":"2025-06-14T06:27:56.636169Z","shell.execute_reply.started":"2025-06-14T06:27:56.621087Z","shell.execute_reply":"2025-06-14T06:27:56.634758Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def feature_engineering2(df: pd.DataFrame): # IMU Only Feature Extractor\n    tqdm.pandas(desc=\"Sequence Features(IMU Only)\") \n    imu_cols = [col for col in df.columns if 'acc' in col or 'rot' in col]\n    \n    def extract_features(group):\n        feature = {}\n        \n        # 시퀀스 길이\n        seq_len = group[\"sequence_counter\"].max()\n        feature[\"seq_len\"] = seq_len\n        \n        # 전역 구간에 대해 기본적인 통계 특징 추출\n        for col in imu_cols:\n            x = group[col]\n            feature[f\"{col}_mean\"] = x.mean()\n            feature[f\"{col}_std\"] = x.std()\n            feature[f\"{col}_min\"] = x.min()\n            feature[f\"{col}_max\"] = x.max()\n            feature[f\"{col}_q25\"] = x.quantile(0.25)\n            feature[f\"{col}_q50\"] = x.median()\n            feature[f\"{col}_q75\"] = x.quantile(0.75)\n\n\n        # 후반 중점 세부 phase로 나누어 조건부 집계\n        conditions = {\n            'first_30pct': group['sequence_counter'] < seq_len * 0.3,\n            'middle_40pct': (group['sequence_counter'] >= seq_len * 0.3) & (group['sequence_counter'] < seq_len * 0.7),\n            'last_10pct': group['sequence_counter'] >= seq_len * 0.9,\n            'last_20pct': group['sequence_counter'] >= seq_len * 0.8,\n            'last_30pct': group['sequence_counter'] >= seq_len * 0.7,\n            'last_40pct': group['sequence_counter'] >= seq_len * 0.6,\n            'window_10~20pct': (group['sequence_counter'] >= seq_len * 0.8) & (group['sequence_counter'] < seq_len * 0.9),\n            'window_20~30pct': (group['sequence_counter'] >= seq_len * 0.7) & (group['sequence_counter'] < seq_len * 0.8),\n            'window_30~40pct': (group['sequence_counter'] >= seq_len * 0.6) & (group['sequence_counter'] < seq_len * 0.7),\n        }\n        \n        # phase에 대해 더 다양한 특징 추출\n        for part_name, mask in conditions.items():\n            part = group[mask]\n            for col in imu_cols:\n                x = part[col]\n                feature[f'{col}_mean_{part_name}'] = x.mean()\n                feature[f'{col}_std_{part_name}'] = x.std()\n                feature[f'{col}_median_{part_name}'] = x.median()\n                feature[f\"{col}_min_{part_name}\"] = x.min()\n                feature[f\"{col}_max_{part_name}\"] = x.max()\n                feature[f\"{col}_q25_{part_name}\"] = x.quantile(0.25)\n                feature[f\"{col}_q75_{part_name}\"] = x.quantile(0.75)\n                feature[f\"{col}_delta\"] = x.iloc[-1] - x.iloc[0]\n                feature[f\"{col}_n_changes\"] = (x.diff().abs() > 0).sum()\n                feature[f\"{col}_corr_time\"] = group[\"sequence_counter\"].corr(x) # 특성과 시간과의 상관계수 추가(추세 요약)\n                peak, _ = find_peaks(x.values)\n                feature[f\"{col}_peak_count\"] = len(peak)\n                \n                diff = group[col].diff().fillna(0)\n                feature[f'{col}_diff_mean'] = diff.mean()\n                feature[f'{col}_diff_std'] = diff.std()\n\n        return pd.Series(feature)\n\n    # 시퀀스별 집계\n    cleaned_df = df.groupby(\"sequence_id\").progress_apply(extract_features).reset_index()\n\n    # 결측치 0으로 채우기\n    cleaned_df = cleaned_df.fillna(0)\n    return cleaned_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T06:27:56.637467Z","iopub.execute_input":"2025-06-14T06:27:56.637910Z","iopub.status.idle":"2025-06-14T06:27:56.661772Z","shell.execute_reply.started":"2025-06-14T06:27:56.637869Z","shell.execute_reply":"2025-06-14T06:27:56.660703Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    df = sequence.to_pandas()\n    df = df.drop('row_id', axis=1) # 불필요 열 제거\n\n    # 결측치 처리\n    # tof NaN 값은 거리 센서로부터 신호를 받지 않은 것이므로 -1로 변경\n    tof_cols = [col for col in df.columns if 'tof' in col]\n    tof_missing_index = df[df[tof_cols].isnull().any(axis=1)].index\n    \n    df.loc[tof_missing_index, tof_cols] = df.loc[tof_missing_index, tof_cols].fillna(-1)\n        \n    # 나머지 결측치 0으로 채우기\n    not_tof_cols = [col for col in df.columns if col not in tof_cols]\n    for col in not_tof_cols:\n        df[col] = df[col].fillna(0)\n\n    # 픽셀 정보 -> 잠재 벡터\n    tof_data = torch.tensor(df[tof_cols].values.reshape(-1, 5,8,8), dtype=torch.float32)\n\n    model.eval()\n    with torch.no_grad():\n        _, z = model(tof_data)\n    z = np.array(z)\n    \n    # vector to DataFrame\n    vector_cols = [f'tof_v{i}' for i in range(32)]\n    tof_vec = pd.DataFrame(z, columns=vector_cols)\n\n    # tof 채널당 평균 추가\n    for i in range(1, 6):\n        cols = [col for col in df.columns if f'tof_{i}_v' in col]\n        \n        # -1을 NaN으로 바꾼 후 계산\n        masked = df[cols].replace(-1, np.nan)\n        df[f'tof_{i}_mean'] = masked.mean(axis=1)\n    \n    df = df.drop(tof_cols, axis=1)\n    df = pd.concat([df, tof_vec], axis=1)\n\n    # 가속도 크기 열 추가\n    df['acc_mag'] = np.sqrt(df['acc_x']**2 + df['acc_y']**2 + df['acc_z']**2)\n\n    # 테스트 데이터가 IMU only인지 all features인지 확인하여 모델을 따로 적용\n    non_valid = ['thm_1','thm_2','thm_3','thm_4','thm_5']\n    IMU_ONLY = True if (df[non_valid].values==0).all() else False\n\n    if IMU_ONLY:\n        # 통계\n        cleaned_data = feature_engineering2(df)\n        \n        valid_feature = [col for col in cleaned_data.columns if 'acc' in col or 'rot' in col] +['sequence_id','seq_len']\n        invalid_feature = [col for col in cleaned_data.columns if col not in valid_feature]\n        imu_only_data = cleaned_data.drop(invalid_feature, axis=1)\n\n        test_X = imu_only_data.drop('sequence_id', axis=1)\n        test_id = imu_only_data['sequence_id'].values\n        X_test_scaled = scaler_imu.transform(test_X)\n        y_pred = imu_model.predict(X_test_scaled).astype(int)\n        \n    else:\n        # 통계 수행\n        cleaned_data = feature_engineering(df)\n        \n        test_X = cleaned_data.drop('sequence_id', axis=1)\n        test_id = cleaned_data['sequence_id'].values\n        \n        # 스케일링\n        X_test_scaled = scaler.transform(test_X)\n    \n        # 한 시퀀스에 대해 예측\n        y_pred = best_model.predict(X_test_scaled).astype(int)\n        \n    out = le.inverse_transform(y_pred)[0]\n    return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T06:27:56.663793Z","iopub.execute_input":"2025-06-14T06:27:56.664192Z","iopub.status.idle":"2025-06-14T06:27:56.689100Z","shell.execute_reply.started":"2025-06-14T06:27:56.664166Z","shell.execute_reply":"2025-06-14T06:27:56.688157Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\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    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T06:27:56.689883Z","iopub.execute_input":"2025-06-14T06:27:56.690190Z","iopub.status.idle":"2025-06-14T06:27:58.557836Z","shell.execute_reply.started":"2025-06-14T06:27:56.690169Z","shell.execute_reply":"2025-06-14T06:27:58.556766Z"}},"outputs":[],"execution_count":null}]}