{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12139340,"sourceType":"datasetVersion","datasetId":7645099},{"sourceId":12293285,"sourceType":"datasetVersion","datasetId":7748073},{"sourceId":12328761,"sourceType":"datasetVersion","datasetId":7771623},{"sourceId":12411879,"sourceType":"datasetVersion","datasetId":7827797},{"sourceId":12573306,"sourceType":"datasetVersion","datasetId":7932089},{"sourceId":240649816,"sourceType":"kernelVersion"},{"sourceId":246893721,"sourceType":"kernelVersion"},{"sourceId":251413288,"sourceType":"kernelVersion"},{"sourceId":470587,"sourceType":"modelInstanceVersion","modelInstanceId":379625,"modelId":398856},{"sourceId":471764,"sourceType":"modelInstanceVersion","modelInstanceId":380358,"modelId":400086}],"dockerImageVersionId":31089,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import kagglehub\nimport os\nimport json\nimport joblib\nimport numpy as np\nimport pandas as pd\nimport polars as pl\nimport random\nimport math\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\nimport warnings\nfrom collections import defaultdict\n\n# Sklearn imports\nfrom sklearn.model_selection import train_test_split, StratifiedGroupKFold\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom sklearn.utils.class_weight import compute_class_weight\n\n# TensorFlow/Keras imports\nimport tensorflow as tf\nfrom tensorflow.keras import backend as K\nfrom tensorflow.keras.models import Model, load_model\nfrom tensorflow.keras.layers import (\n    Input, Conv1D, BatchNormalization, Activation, add, MaxPooling1D, Dropout,\n    Bidirectional, LSTM, GRU, GlobalAveragePooling1D, Dense, Multiply, Reshape,\n    Lambda, Concatenate, GaussianNoise\n)\nfrom tensorflow.keras.regularizers import l2\nfrom tensorflow.keras.optimizers import Adam as AdamTF\nfrom tensorflow.keras.optimizers.schedules import CosineDecay\nfrom tensorflow.keras.utils import Sequence, to_categorical, pad_sequences\nfrom tensorflow.keras.callbacks import EarlyStopping\n\n# PyTorch imports\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader, Subset\nfrom torch.optim import Adam as AdamTorch\nfrom torch.optim.lr_scheduler import CosineAnnealingWarmRestarts\nfrom torch.amp import autocast\n\n# Scipy imports\nfrom scipy.spatial.transform import Rotation as R\nfrom scipy.signal import firwin\n\n# Other imports\nfrom tqdm.notebook import tqdm\nfrom transformers import BertConfig, BertModel\n\n# Competition metric (optional)\ntry:\n    from cmi_2025_metric_copy_for_import import CompetitionMetric\nexcept ImportError:\n    CompetitionMetric = None\n    print(\"CompetitionMetric could not be imported. OOF/CV score will not be calculated.\")\n\ndef seed_everything(seed=42):\n    \"\"\"設置所有隨機種子以確保結果可重現\"\"\"\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    random.seed(seed)\n    np.random.seed(seed)\n    tf.random.set_seed(seed)\n    tf.experimental.numpy.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    os.environ['TF_CUDNN_DETERMINISTIC'] = '1'\n    os.environ['TF_DETERMINISTIC_OPS'] = '1'\n\n# 設置隨機種子和警告\nseed_everything(seed=42)\nwarnings.filterwarnings(\"ignore\")\n\n# 配置常數\nTRAIN = False\nRAW_DIR = Path(\"/kaggle/input/cmi-detect-behavior-with-sensor-data\")\nPRETRAINED_DIR = Path(\"/kaggle/input/cmi-d-111\")\nEXPORT_DIR = Path(\"./\")\nBATCH_SIZE = 64\nPAD_PERCENTILE = 95\nLR_INIT = 5e-4\nWD = 3e-3\nMIXUP_ALPHA = 0.4\nEPOCHS = 160\nPATIENCE = 40\n\nprint(\"▶ imports ready · tensorflow\", tf.__version__)\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# =====Download dataset:  ==========\n","metadata":{}},{"cell_type":"code","source":"\ncmi_detect_behavior_with_sensor_data_path = kagglehub.competition_download('cmi-detect-behavior-with-sensor-data')\nhideyukizushi_cmi25_imu_thmtof_tf_bilstm_gru_attentionlb_xx_path = kagglehub.dataset_download('hideyukizushi/cmi25-imu-thmtof-tf-bilstm-gru-attentionlb-xx')\nhideyukizushi_20250627_cmi_b_102_b_105_path = kagglehub.dataset_download('hideyukizushi/20250627-cmi-b-102-b-105')\nmyso1987_cmi3_models_p_path = kagglehub.dataset_download('myso1987/cmi3-models-p')\nhideyukizushi_cmi_d_111_path = kagglehub.dataset_download('hideyukizushi/cmi-d-111')\nkerta27_cmi_data_gated_gru_path = kagglehub.dataset_download('kerta27/cmi-data-gated-gru')\nwasupandceacar_deterministic_path = kagglehub.package_import('wasupandceacar/deterministic')\nhideyukizushi_lb_0_78_quaternions_tf_bilstm_gru_attention_path = kagglehub.notebook_output_download('hideyukizushi/lb-0-78-quaternions-tf-bilstm-gru-attention')\nwasupandceacar_cmi_metric_path = kagglehub.package_import('wasupandceacar/cmi-metric')\nwasupandceacar_cmi_precompute_pytorch_all_1_path = kagglehub.model_download('wasupandceacar/cmi-precompute/PyTorch/all/1')\nwasupandceacar_cmi_models_public_pytorch_train_fold_model05_tof16_raw_1_path = kagglehub.model_download('wasupandceacar/cmi-models-public/PyTorch/train_fold_model05_tof16_raw/1')\n\nprint('Data source import complete.')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# === Definition of model2 dataset ====\n<br>  CMIFeDataset / CMIFold Dataset","metadata":{}},{"cell_type":"code","source":"class CMIFeDataset(Dataset):\n    def __init__(self, data_path, config):\n        self.config = config\n        self.init_feature_names(data_path)\n        df = self.generate_features(pd.read_csv(data_path, usecols=set(self.base_cols+self.feature_cols)))\n        self.generate_dataset(df)\n    def init_feature_names(self, data_path):\n        self.imu_engineered_features = ['acc_mag', 'rot_angle', 'acc_mag_jerk', 'rot_angle_vel', 'linear_acc_mag', 'linear_acc_mag_jerk', 'angular_vel_x', 'angular_vel_y', 'angular_vel_z', 'angular_distance']\n        self.tof_mode = self.config.get(\"tof_mode\", \"stats\")\n        self.tof_region_stats = ['mean', 'std', 'min', 'max']\n        self.tof_cols = self.generate_tof_feature_names()\n        columns = pd.read_csv(data_path, nrows=0).columns.tolist()\n        imu_cols_base = ['linear_acc_x', 'linear_acc_y', 'linear_acc_z']\n        imu_cols_base.extend([c for c in columns if c.startswith('rot_') and c not in ['rot_angle', 'rot_angle_vel']])\n        self.imu_cols = list(dict.fromkeys(imu_cols_base + self.imu_engineered_features))\n        self.thm_cols = [c for c in columns if c.startswith('thm_')]\n        self.feature_cols = self.imu_cols + self.thm_cols + self.tof_cols\n        self.imu_dim = len(self.imu_cols)\n        self.thm_dim = len(self.thm_cols)\n        self.tof_dim = len(self.tof_cols)\n        self.base_cols = ['acc_x', 'acc_y', 'acc_z', 'rot_x', 'rot_y', 'rot_z', 'rot_w', 'sequence_id', 'subject', 'sequence_type', 'gesture', 'orientation'] + [c for c in columns if c.startswith('thm_')] + [f\"tof_{i}_v{p}\" for i in range(1, 6) for p in range(64)]\n        self.fold_cols = ['subject', 'sequence_type', 'gesture', 'orientation']\n    def generate_tof_feature_names(self):\n        features = []\n        if self.config.get(\"tof_raw\", False):\n            for i in range(1, 6):\n                features.extend([f\"tof_{i}_v{p}\" for p in range(64)])\n        for i in range(1, 6):\n            if self.tof_mode != 0:\n                for stat in self.tof_region_stats:\n                    features.append(f'tof_{i}_{stat}')\n                if self.tof_mode > 1:\n                    for r in range(self.tof_mode):\n                        for stat in self.tof_region_stats:\n                            features.append(f'tof{self.tof_mode}_{i}_region_{r}_{stat}')\n                if self.tof_mode == -1:\n                    for mode in [2, 4, 8, 16, 32]:\n                        for r in range(mode):\n                            for stat in self.tof_region_stats:\n                                features.append(f'tof{mode}_{i}_region_{r}_{stat}')\n        return features\n    def compute_features(self, df):\n        df['acc_mag'] = np.sqrt(df['acc_x']**2 + df['acc_y']**2 + df['acc_z']**2)\n        df['rot_angle'] = 2 * np.arccos(df['rot_w'].clip(-1, 1))\n        df['acc_mag_jerk'] = df.groupby('sequence_id')['acc_mag'].diff().fillna(0)\n        df['rot_angle_vel'] = df.groupby('sequence_id')['rot_angle'].diff().fillna(0)\n        linear_accel_list = []\n        for _, group in df.groupby('sequence_id'):\n            acc_data_group = group[['acc_x', 'acc_y', 'acc_z']]\n            rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n            linear_accel_group = remove_gravity_from_acc(acc_data_group, rot_data_group)\n            linear_accel_list.append(pd.DataFrame(linear_accel_group, columns=['linear_acc_x', 'linear_acc_y', 'linear_acc_z'], index=group.index))\n        df_linear_accel = pd.concat(linear_accel_list)\n        df = pd.concat([df, df_linear_accel], axis=1)\n        df['linear_acc_mag'] = np.sqrt(df['linear_acc_x']**2 + df['linear_acc_y']**2 + df['linear_acc_z']**2)\n        df['linear_acc_mag_jerk'] = df.groupby('sequence_id')['linear_acc_mag'].diff().fillna(0)\n        angular_vel_list = []\n        for _, group in df.groupby('sequence_id'):\n            rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n            angular_vel_group = calculate_angular_velocity_from_quat(rot_data_group)\n            angular_vel_list.append(pd.DataFrame(angular_vel_group, columns=['angular_vel_x', 'angular_vel_y', 'angular_vel_z'], index=group.index))\n        df_angular_vel = pd.concat(angular_vel_list)\n        df = pd.concat([df, df_angular_vel], axis=1)\n        angular_distance_list = []\n        for _, group in df.groupby('sequence_id'):\n            rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n            angular_dist_group = calculate_angular_distance(rot_data_group)\n            angular_distance_list.append(pd.DataFrame(angular_dist_group, columns=['angular_distance'], index=group.index))\n        df_angular_distance = pd.concat(angular_distance_list)\n        df = pd.concat([df, df_angular_distance], axis=1)\n        if self.tof_mode != 0:\n            new_columns = {}\n            for i in range(1, 6):\n                pixel_cols = [f\"tof_{i}_v{p}\" for p in range(64)]\n                tof_data = df[pixel_cols].replace(-1, np.nan)\n                new_columns.update({f'tof_{i}_mean': tof_data.mean(axis=1), f'tof_{i}_std': tof_data.std(axis=1), f'tof_{i}_min': tof_data.min(axis=1), f'tof_{i}_max': tof_data.max(axis=1)})\n                if self.tof_mode > 1:\n                    region_size = 64 // self.tof_mode\n                    for r in range(self.tof_mode):\n                        region_data = tof_data.iloc[:, r*region_size : (r+1)*region_size]\n                        new_columns.update({f'tof{self.tof_mode}_{i}_region_{r}_mean': region_data.mean(axis=1), f'tof{self.tof_mode}_{i}_region_{r}_std': region_data.std(axis=1), f'tof{self.tof_mode}_{i}_region_{r}_min': region_data.min(axis=1), f'tof{self.tof_mode}_{i}_region_{r}_max': region_data.max(axis=1)})\n                if self.tof_mode == -1:\n                    for mode in [2, 4, 8, 16, 32]:\n                        region_size = 64 // mode\n                        for r in range(mode):\n                            region_data = tof_data.iloc[:, r*region_size : (r+1)*region_size]\n                            new_columns.update({f'tof{mode}_{i}_region_{r}_mean': region_data.mean(axis=1), f'tof{mode}_{i}_region_{r}_std': region_data.std(axis=1), f'tof{mode}_{i}_region_{r}_min': region_data.min(axis=1), f'tof{mode}_{i}_region_{r}_max': region_data.max(axis=1)})\n            df = pd.concat([df, pd.DataFrame(new_columns)], axis=1)\n        return df\n    def generate_features(self, df):\n        self.le = LabelEncoder()\n        df['gesture_int'] = self.le.fit_transform(df['gesture'])\n        self.class_num = len(self.le.classes_)\n        if all(c in df.columns for c in self.imu_engineered_features) and all(c in df.columns for c in self.tof_cols):\n            print(\"Have precomputed, skip compute.\")\n        else:\n            print(\"Not precomputed, do compute.\")\n            df = self.compute_features(df)\n        if self.config.get(\"save_precompute\", False):\n            df.to_csv(self.config.get(\"save_filename\", \"train.csv\"))\n        return df\n    def scale(self, data_unscaled):\n        scaler_function = self.config.get(\"scaler_function\", StandardScaler())\n        scaler = scaler_function.fit(np.concatenate(data_unscaled, axis=0))\n        return [scaler.transform(x) for x in data_unscaled], scaler\n    def pad(self, data_scaled, cols):\n        pad_data = np.zeros((len(data_scaled), self.pad_len, len(cols)), dtype='float32')\n        for i, seq in enumerate(data_scaled):\n            seq_len = min(len(seq), self.pad_len)\n            pad_data[i, :seq_len] = seq[:seq_len]\n        return pad_data\n    def get_nan_value(self, data, ratio):\n        max_value = data.max().max()\n        nan_value = -max_value * ratio\n        return nan_value\n    def generate_dataset(self, df):\n        seq_gp = df.groupby('sequence_id')\n        imu_unscaled, thm_unscaled, tof_unscaled = [], [], []\n        classes, lens = [], []\n        self.imu_nan_value = self.get_nan_value(df[self.imu_cols], self.config[\"nan_ratio\"][\"imu\"])\n        self.thm_nan_value = self.get_nan_value(df[self.thm_cols], self.config[\"nan_ratio\"][\"thm\"])\n        self.tof_nan_value = self.get_nan_value(df[self.tof_cols], self.config[\"nan_ratio\"][\"tof\"])\n        self.fold_feats = defaultdict(list)\n        for seq_id, seq_df in seq_gp:\n            imu_data = seq_df[self.imu_cols]\n            if self.config[\"fbfill\"][\"imu\"]:\n                imu_data = imu_data.ffill().bfill()\n            imu_unscaled.append(imu_data.fillna(self.imu_nan_value).values.astype('float32'))\n            thm_data = seq_df[self.thm_cols]\n            if self.config[\"fbfill\"][\"thm\"]:\n                thm_data = thm_data.ffill().bfill()\n            thm_unscaled.append(thm_data.fillna(self.thm_nan_value).values.astype('float32'))\n            tof_data = seq_df[self.tof_cols]\n            if self.config[\"fbfill\"][\"tof\"]:\n                tof_data = tof_data.ffill().bfill()\n            tof_unscaled.append(tof_data.fillna(self.tof_nan_value).values.astype('float32'))\n            classes.append(seq_df['gesture_int'].iloc[0])\n            lens.append(len(imu_data))\n            for col in self.fold_cols:\n                self.fold_feats[col].append(seq_df[col].iloc[0])\n        self.dataset_indices = classes\n        self.pad_len = int(np.percentile(lens, self.config.get(\"percent\", 95)))\n        if self.config.get(\"one_scale\", True):\n            x_unscaled = [np.concatenate([imu, thm, tof], axis=1) for imu, thm, tof in zip(imu_unscaled, thm_unscaled, tof_unscaled)]\n            x_scaled, self.x_scaler = self.scale(x_unscaled)\n            x = self.pad(x_scaled, self.imu_cols+self.thm_cols+self.tof_cols)\n            self.imu = x[..., :self.imu_dim]\n            self.thm = x[..., self.imu_dim:self.imu_dim+self.thm_dim]\n            self.tof = x[..., self.imu_dim+self.thm_dim:self.imu_dim+self.thm_dim+self.tof_dim]\n        else:\n            imu_scaled, self.imu_scaler = self.scale(imu_unscaled)\n            thm_scaled, self.thm_scaler = self.scale(thm_unscaled)\n            tof_scaled, self.tof_scaler = self.scale(tof_unscaled)\n            self.imu = self.pad(imu_scaled, self.imu_cols)\n            self.thm = self.pad(thm_scaled, self.thm_cols)\n            self.tof = self.pad(tof_scaled, self.tof_cols)\n        self.precompute_scaled_nan_values()\n        self.class_ = F.one_hot(torch.from_numpy(np.array(classes)).long(), num_classes=len(self.le.classes_)).float().numpy()\n        self.class_weight = torch.FloatTensor(compute_class_weight('balanced', classes=np.arange(len(self.le.classes_)), y=classes))\n    def precompute_scaled_nan_values(self):\n        dummy_df = pd.DataFrame(np.array([[self.imu_nan_value]*len(self.imu_cols) + [self.thm_nan_value]*len(self.thm_cols) + [self.tof_nan_value]*len(self.tof_cols)]), columns=self.imu_cols + self.thm_cols + self.tof_cols)\n        if self.config.get(\"one_scale\", True):\n            scaled = self.x_scaler.transform(dummy_df)\n            self.imu_scaled_nan = scaled[0, :self.imu_dim].mean()\n            self.thm_scaled_nan = scaled[0, self.imu_dim:self.imu_dim+self.thm_dim].mean()\n            self.tof_scaled_nan = scaled[0, self.imu_dim+self.thm_dim:self.imu_dim+self.thm_dim+self.tof_dim].mean()\n        else:\n            self.imu_scaled_nan = self.imu_scaler.transform(dummy_df[self.imu_cols])[0].mean()\n            self.thm_scaled_nan = self.thm_scaler.transform(dummy_df[self.thm_cols])[0].mean()\n            self.tof_scaled_nan = self.tof_scaler.transform(dummy_df[self.tof_cols])[0].mean()\n    def get_scaled_nan_tensors(self, imu, thm, tof):\n        return torch.full(imu.shape, self.imu_scaled_nan, device=imu.device), torch.full(thm.shape, self.thm_scaled_nan, device=thm.device), torch.full(tof.shape, self.tof_scaled_nan, device=tof.device)\n    def inference_process(self, sequence):\n        df_seq = sequence.to_pandas().copy()\n        if not all(c in df_seq.columns for c in self.imu_engineered_features):\n            df_seq['acc_mag'] = np.sqrt(df_seq['acc_x']**2 + df_seq['acc_y']**2 + df_seq['acc_z']**2)\n            df_seq['rot_angle'] = 2 * np.arccos(df_seq['rot_w'].clip(-1, 1))\n            df_seq['acc_mag_jerk'] = df_seq['acc_mag'].diff().fillna(0)\n            df_seq['rot_angle_vel'] = df_seq['rot_angle'].diff().fillna(0)\n            if all(col in df_seq.columns for col in ['acc_x', 'acc_y', 'acc_z', 'rot_x', 'rot_y', 'rot_z', 'rot_w']):\n                linear_accel = remove_gravity_from_acc(df_seq[['acc_x', 'acc_y', 'acc_z']], df_seq[['rot_x', 'rot_y', 'rot_z', 'rot_w']])\n                df_seq[['linear_acc_x', 'linear_acc_y', 'linear_acc_z']] = linear_accel\n            else:\n                df_seq['linear_acc_x'] = df_seq.get('acc_x', 0)\n                df_seq['linear_acc_y'] = df_seq.get('acc_y', 0)\n                df_seq['linear_acc_z'] = df_seq.get('acc_z', 0)\n            df_seq['linear_acc_mag'] = np.sqrt(df_seq['linear_acc_x']**2 + df_seq['linear_acc_y']**2 + df_seq['linear_acc_z']**2)\n            df_seq['linear_acc_mag_jerk'] = df_seq['linear_acc_mag'].diff().fillna(0)\n            if all(col in df_seq.columns for col in ['rot_x', 'rot_y', 'rot_z', 'rot_w']):\n                angular_vel = calculate_angular_velocity_from_quat(df_seq[['rot_x', 'rot_y', 'rot_z', 'rot_w']])\n                df_seq[['angular_vel_x', 'angular_vel_y', 'angular_vel_z']] = angular_vel\n            else:\n                df_seq[['angular_vel_x', 'angular_vel_y', 'angular_vel_z']] = 0\n            if all(col in df_seq.columns for col in ['rot_x', 'rot_y', 'rot_z', 'rot_w']):\n                df_seq['angular_distance'] = calculate_angular_distance(df_seq[['rot_x', 'rot_y', 'rot_z', 'rot_w']])\n            else:\n                df_seq['angular_distance'] = 0\n        if self.tof_mode != 0:\n            new_columns = {}\n            for i in range(1, 6):\n                pixel_cols = [f\"tof_{i}_v{p}\" for p in range(64)]\n                tof_data = df_seq[pixel_cols].replace(-1, np.nan)\n                new_columns.update({f'tof_{i}_mean': tof_data.mean(axis=1), f'tof_{i}_std': tof_data.std(axis=1), f'tof_{i}_min': tof_data.min(axis=1), f'tof_{i}_max': tof_data.max(axis=1)})\n                if self.tof_mode > 1:\n                    region_size = 64 // self.tof_mode\n                    for r in range(self.tof_mode):\n                        region_data = tof_data.iloc[:, r*region_size : (r+1)*region_size]\n                        new_columns.update({f'tof{self.tof_mode}_{i}_region_{r}_mean': region_data.mean(axis=1), f'tof{self.tof_mode}_{i}_region_{r}_std': region_data.std(axis=1), f'tof{self.tof_mode}_{i}_region_{r}_min': region_data.min(axis=1), f'tof{self.tof_mode}_{i}_region_{r}_max': region_data.max(axis=1)})\n                if self.tof_mode == -1:\n                    for mode in [2, 4, 8, 16, 32]:\n                        region_size = 64 // mode\n                        for r in range(mode):\n                            region_data = tof_data.iloc[:, r*region_size : (r+1)*region_size]\n                            new_columns.update({f'tof{mode}_{i}_region_{r}_mean': region_data.mean(axis=1), f'tof{mode}_{i}_region_{r}_std': region_data.std(axis=1), f'tof{mode}_{i}_region_{r}_min': region_data.min(axis=1), f'tof{mode}_{i}_region_{r}_max': region_data.max(axis=1)})\n            df_seq = pd.concat([df_seq, pd.DataFrame(new_columns)], axis=1)\n        imu_unscaled = df_seq[self.imu_cols]\n        if self.config[\"fbfill\"][\"imu\"]:\n            imu_unscaled = imu_unscaled.ffill().bfill()\n        imu_unscaled = imu_unscaled.fillna(self.imu_nan_value).values.astype('float32')\n        thm_unscaled = df_seq[self.thm_cols]\n        if self.config[\"fbfill\"][\"thm\"]:\n            thm_unscaled = thm_unscaled.ffill().bfill()\n        thm_unscaled = thm_unscaled.fillna(self.thm_nan_value).values.astype('float32')\n        tof_unscaled = df_seq[self.tof_cols]\n        if self.config[\"fbfill\"][\"tof\"]:\n            tof_unscaled = tof_unscaled.ffill().bfill()\n        tof_unscaled = tof_unscaled.fillna(self.tof_nan_value).values.astype('float32')\n        if self.config.get(\"one_scale\", True):\n            x_unscaled = np.concatenate([imu_unscaled, thm_unscaled, tof_unscaled], axis=1)\n            x_scaled = self.x_scaler.transform(x_unscaled)\n            imu_scaled = x_scaled[..., :self.imu_dim]\n            thm_scaled = x_scaled[..., self.imu_dim:self.imu_dim+self.thm_dim]\n            tof_scaled = x_scaled[..., self.imu_dim+self.thm_dim:self.imu_dim+self.thm_dim+self.tof_dim]\n        else:\n            imu_scaled = self.imu_scaler.transform(imu_unscaled)\n            thm_scaled = self.thm_scaler.transform(thm_unscaled)\n            tof_scaled = self.tof_scaler.transform(tof_unscaled)\n        combined = np.concatenate([imu_scaled, thm_scaled, tof_scaled], axis=1)\n        padded = np.zeros((self.pad_len, combined.shape[1]), dtype='float32')\n        seq_len = min(combined.shape[0], self.pad_len)\n        padded[:seq_len] = combined[:seq_len]\n        imu = padded[..., :self.imu_dim]\n        thm = padded[..., self.imu_dim:self.imu_dim+self.thm_dim]\n        tof = padded[..., self.imu_dim+self.thm_dim:self.imu_dim+self.thm_dim+self.tof_dim]\n        return torch.from_numpy(imu).float().unsqueeze(0), torch.from_numpy(thm).float().unsqueeze(0), torch.from_numpy(tof).float().unsqueeze(0)\n    def __getitem__(self, idx):\n        return self.imu[idx], self.thm[idx], self.tof[idx], self.class_[idx]\n    def __len__(self):\n        return len(self.class_)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import StratifiedKFold\nclass CMIFoldDataset:\n    def __init__(self, data_path, config, full_dataset_function, n_folds=5, random_seed=0):\n        self.full_dataset = full_dataset_function(data_path=data_path, config=config)\n        self.imu_dim = self.full_dataset.imu_dim\n        self.thm_dim = self.full_dataset.thm_dim\n        self.tof_dim = self.full_dataset.tof_dim\n        self.le = self.full_dataset.le\n        self.class_names = self.full_dataset.le.classes_\n        self.class_weight = self.full_dataset.class_weight\n        self.n_folds = n_folds\n        self.skf = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=random_seed)\n        self.folds = list(self.skf.split(np.arange(len(self.full_dataset)), np.array(self.full_dataset.dataset_indices)))\n    def get_fold_datasets(self, fold_idx):\n        if self.folds is None or fold_idx >= self.n_folds:\n            return None, None\n        fold_train_idx, fold_valid_idx = self.folds[fold_idx]\n        return Subset(self.full_dataset, fold_train_idx), Subset(self.full_dataset, fold_valid_idx)\n    def print_fold_stats(self):\n        def get_label_counts(subset):\n            counts = {name: 0 for name in self.class_names}\n            if subset is None:\n                return counts\n            for idx in subset.indices:\n                label_idx = self.full_dataset.dataset_indices[idx]\n                counts[self.class_names[label_idx]] += 1\n            return counts\n        print(\"\\nCross-validation fold statistics:\")\n        for fold_idx in range(self.n_folds):\n            train_fold, valid_fold = self.get_fold_datasets(fold_idx)\n            train_counts = get_label_counts(train_fold)\n            valid_counts = get_label_counts(valid_fold)\n            print(f\"\\nFold {fold_idx + 1}:\")\n            print(f\"{'Category':<50} {'Training Set':<10} {'Validation Set':<10}\")\n            for name in self.class_names:\n                print(f\"{name:<50} {train_counts[name]:<10} {valid_counts[name]:<10}\")\nCUDA0 = \"cuda:0\"\nseed = 0\nbatch_size = 64\nnum_workers = 4\nn_folds = 5\nuniverse_csv_path = Path(\"/kaggle/input/cmi-precompute/pytorch/all/1/tof-1_raw.csv\")\ndeterministic = kagglehub.package_import('wasupandceacar/deterministic').deterministic\ndeterministic.init_all(seed)\n\n\ndef init_dataset():\n    dataset_config = {\"percent\": 95, \"scaler_function\": StandardScaler(), \"nan_ratio\": {\"imu\": 0, \"thm\": 0, \"tof\": 0}, \"fbfill\": {\"imu\": True, \"thm\": True, \"tof\": True}, \"one_scale\": True, \"tof_raw\": True, \"tof_mode\": 16, \"save_precompute\": False}\n    dataset = CMIFoldDataset(universe_csv_path, dataset_config, n_folds=n_folds, random_seed=seed, full_dataset_function=CMIFeDataset)\n    dataset.print_fold_stats()\n    return dataset\ndef get_fold_dataset(dataset, fold):\n    _, valid_dataset = dataset.get_fold_datasets(fold)\n    valid_loader = DataLoader(valid_dataset, batch_size=batch_size, num_workers=num_workers, shuffle=False)\n    return valid_loader\ndataset = init_dataset()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Feature engineering function of 1~3:\n","metadata":{}},{"cell_type":"code","source":"def remove_gravity_from_acc(acc_data, rot_data):\n    if isinstance(acc_data, pd.DataFrame):\n        acc_values = acc_data[['acc_x', 'acc_y', 'acc_z']].values\n    else:\n        acc_values = acc_data\n    if isinstance(rot_data, pd.DataFrame):\n        quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    else:\n        quat_values = rot_data\n    num_samples = acc_values.shape[0]\n    linear_accel = np.zeros_like(acc_values)\n    gravity_world = np.array([0, 0, 9.81])\n    for i in range(num_samples):\n        if np.all(np.isnan(quat_values[i])) or np.all(np.isclose(quat_values[i], 0)):\n            linear_accel[i, :] = acc_values[i, :]\n            continue\n        try:\n            rotation = R.from_quat(quat_values[i])\n            gravity_sensor_frame = rotation.apply(gravity_world, inverse=True)\n            linear_accel[i, :] = acc_values[i, :] - gravity_sensor_frame\n        except ValueError:\n             linear_accel[i, :] = acc_values[i, :]\n    return linear_accel\n\ndef calculate_angular_distance(rot_data):\n    if isinstance(rot_data, pd.DataFrame):\n        quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    else:\n        quat_values = rot_data\n    num_samples = quat_values.shape[0]\n    angular_dist = np.zeros(num_samples)\n    for i in range(num_samples - 1):\n        q1 = quat_values[i]\n        q2 = quat_values[i+1]\n        if np.all(np.isnan(q1)) or np.all(np.isclose(q1, 0)) or \\\n           np.all(np.isnan(q2)) or np.all(np.isclose(q2, 0)):\n            angular_dist[i] = 0\n            continue\n        try:\n            r1 = R.from_quat(q1)\n            r2 = R.from_quat(q2)\n            relative_rotation = r1.inv() * r2\n            angle = np.linalg.norm(relative_rotation.as_rotvec())\n            angular_dist[i] = angle\n        except ValueError:\n            angular_dist[i] = 0\n            pass\n    return angular_dist\n\ndef calculate_angular_velocity_from_quat(rot_data, time_delta=1/200):\n    if isinstance(rot_data, pd.DataFrame):\n        quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    else:\n        quat_values = rot_data\n    num_samples = quat_values.shape[0]\n    angular_vel = np.zeros((num_samples, 3))\n    for i in range(num_samples - 1):\n        q_t = quat_values[i]\n        q_t_plus_dt = quat_values[i+1]\n        if np.all(np.isnan(q_t)) or np.all(np.isclose(q_t, 0)) or \\\n           np.all(np.isnan(q_t_plus_dt)) or np.all(np.isclose(q_t_plus_dt, 0)):\n            continue\n        try:\n            rot_t = R.from_quat(q_t)\n            rot_t_plus_dt = R.from_quat(q_t_plus_dt)\n            delta_rot = rot_t.inv() * rot_t_plus_dt\n            angular_vel[i, :] = delta_rot.as_rotvec() / time_delta\n        except ValueError:\n            pass\n    return angular_vel","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model 3: ","metadata":{}},{"cell_type":"code","source":"def pad_sequences_torch3(sequences, maxlen, padding='post', truncating='post', value=0.0):\n    result = []\n    for seq in sequences:\n        if len(seq) >= maxlen: seq = seq[:maxlen] if truncating == 'post' else seq[-maxlen:]\n        else:\n            pad_len = maxlen - len(seq)\n            pad_array = np.full((pad_len, seq.shape[1]), value)\n            seq = np.concatenate([seq, pad_array]) if padding == 'post' else np.concatenate([pad_array, seq])\n        result.append(seq)\n    return np.array(result, dtype=np.float32)\ndef remove_gravity_from_acc3(acc_data, rot_data):\n    acc_values = acc_data[['acc_x', 'acc_y', 'acc_z']].values\n    quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    linear_accel = np.zeros_like(acc_values)\n    gravity_world = np.array([0, 0, 9.81])\n    for i in range(len(acc_values)):\n        if np.all(np.isnan(quat_values[i])):\n            linear_accel[i, :] = acc_values[i, :]\n            continue\n        try:\n            rotation = R.from_quat(quat_values[i])\n            gravity_sensor_frame = rotation.apply(gravity_world, inverse=True)\n            linear_accel[i, :] = acc_values[i, :] - gravity_sensor_frame\n        except (ValueError, IndexError):\n            linear_accel[i, :] = acc_values[i, :]\n    return linear_accel\ndef calculate_angular_velocity_from_quat3(rot_data, time_delta=1/200):\n    quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    angular_vel = np.zeros((len(quat_values), 3))\n    for i in range(len(quat_values) - 1):\n        q_t, q_t_plus_dt = quat_values[i], quat_values[i+1]\n        if np.all(np.isnan(q_t)) or np.all(np.isnan(q_t_plus_dt)): continue\n        try:\n            rot_t = R.from_quat(q_t)\n            rot_t_plus_dt = R.from_quat(q_t_plus_dt)\n            delta_rot = rot_t.inv() * rot_t_plus_dt\n            angular_vel[i, :] = delta_rot.as_rotvec() / time_delta\n        except (ValueError, IndexError): pass\n    return angular_vel\ndef calculate_angular_distance3(rot_data):\n    quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    angular_dist = np.zeros(len(quat_values))\n    for i in range(len(quat_values) - 1):\n        q1, q2 = quat_values[i], quat_values[i+1]\n        if np.all(np.isnan(q1)) or np.all(np.isnan(q2)): continue\n        try:\n            r1 = R.from_quat(q1)\n            r2 = R.from_quat(q2)\n            relative_rotation = r1.inv() * r2\n            angular_dist[i] = np.linalg.norm(relative_rotation.as_rotvec())\n        except (ValueError, IndexError): pass\n    return angular_dist\n\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# ===Define model 1~3 : =====\n","metadata":{}},{"cell_type":"markdown","source":"## Model 1: ","metadata":{}},{"cell_type":"code","source":"# model1===============================\ndef time_sum(x):\n    return K.sum(x, axis=1)\n\ndef squeeze_last_axis(x):\n    return tf.squeeze(x, axis=-1)\n\ndef expand_last_axis(x):\n    return tf.expand_dims(x, axis=-1)\n\ndef se_block(x, reduction=8):\n    ch = x.shape[-1]\n    se = GlobalAveragePooling1D()(x)\n    se = Dense(ch // reduction, activation='relu')(se)\n    se = Dense(ch, activation='sigmoid')(se)\n    se = Reshape((1, ch))(se)\n    return Multiply()([x, se])\n\ndef residual_se_cnn_block(x, filters, kernel_size, pool_size=2, drop=0.3, wd=1e-4):\n    shortcut = x\n    for _ in range(2):\n        x = Conv1D(filters, kernel_size, padding='same', use_bias=False,\n                   kernel_regularizer=l2(wd))(x)\n        x = BatchNormalization()(x)\n        x = Activation('relu')(x)\n    x = se_block(x)\n    if shortcut.shape[-1] != filters:\n        shortcut = Conv1D(filters, 1, padding='same', use_bias=False,\n                          kernel_regularizer=l2(wd))(shortcut)\n        shortcut = BatchNormalization()(shortcut)\n    x = add([x, shortcut])\n    x = Activation('relu')(x)\n    x = MaxPooling1D(pool_size)(x)\n    x = Dropout(drop)(x)\n    return x\n\ndef attention_layer(inputs):\n    score = Dense(1, activation='tanh')(inputs)\n    score = Lambda(squeeze_last_axis)(score)\n    weights = Activation('softmax')(score)\n    weights = Lambda(expand_last_axis)(weights)\n    context = Multiply()([inputs, weights])\n    context = Lambda(time_sum)(context)\n    return context\n\n# Load necessary preprocessing components\nfinal_feature_cols = np.load(PRETRAINED_DIR / \"feature_cols.npy\", allow_pickle=True).tolist()\npad_len = int(np.load(PRETRAINED_DIR / \"sequence_maxlen.npy\"))\nscaler = joblib.load(PRETRAINED_DIR / \"scaler.pkl\")\n\n# Register custom Keras components\ncustom_objs = {\n    'time_sum': time_sum,\n    'squeeze_last_axis': squeeze_last_axis,\n    'expand_last_axis': expand_last_axis,\n    'se_block': se_block,\n    'residual_se_cnn_block': residual_se_cnn_block,\n    'attention_layer': attention_layer,\n}\n\n# Load Keras ensemble models\nmodels1 = []\nprint(f\"Loading models for ensemble inference...\")\n\nfor fold in range(10):\n    MODEL_DIR = \"/kaggle/input/cmi-d-111\"\n    model_path = f\"{MODEL_DIR}/D-111_{fold}.h5\"\n    print(\">>> LoadModel >>>\", model_path)\n    model = load_model(model_path, compile=False, custom_objects=custom_objs)\n    models1.append(model)\n\nprint(\"-\" * 50)\n\nfor fold in range(10):\n    MODEL_DIR = \"/kaggle/input/cmi-d-111\"\n    model_path = f\"{MODEL_DIR}/v0629_{fold}.h5\"\n    print(\">>> LoadModel >>>\", model_path)\n    model = load_model(model_path, compile=False, custom_objects=custom_objs)\n    models1.append(model)\n\nprint(\"-\" * 50)\nprint(f\"[INFO] NumUseModels: {len(models1)}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model 2: ","metadata":{}},{"cell_type":"code","source":"# model2 ========================\n\nclass SEBlock(nn.Module):\n    def __init__(self, channels, reduction=8):\n        super().__init__()\n        self.fc1 = nn.Linear(channels, channels // reduction, bias=True)\n        self.fc2 = nn.Linear(channels // reduction, channels, bias=True)\n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, x):\n        se = F.adaptive_avg_pool1d(x, 1).squeeze(-1)\n        se = F.relu(self.fc1(se), inplace=True)\n        se = self.sigmoid(self.fc2(se)).unsqueeze(-1)\n        return x * se\n\n\nclass ResNetSEBlock(nn.Module):\n    def __init__(self, in_channels, out_channels, wd=1e-4):\n        super().__init__()\n        self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size=3, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm1d(out_channels)\n        self.conv2 = nn.Conv1d(out_channels, out_channels, kernel_size=3, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm1d(out_channels)\n        self.se = SEBlock(out_channels)\n        self.relu = nn.ReLU(inplace=True)\n\n        if in_channels != out_channels:\n            self.shortcut = nn.Sequential(\n                nn.Conv1d(in_channels, out_channels, kernel_size=1, padding=0, bias=False),\n                nn.BatchNorm1d(out_channels)\n            )\n        else:\n            self.shortcut = nn.Identity()\n\n    def forward(self, x):\n        identity = self.shortcut(x)\n        out = self.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        out = self.se(out)\n        out = out + identity\n        return self.relu(out)\n\n\nclass CMIModel(nn.Module):\n    def __init__(self, imu_dim, thm_dim, tof_dim, n_classes, **kwargs):\n        super().__init__()\n\n        self.imu_branch = nn.Sequential(\n            self.residual_se_cnn_block(imu_dim, kwargs[\"imu1_channels\"], kwargs[\"imu1_layers\"], drop=kwargs[\"imu1_dropout\"]),\n            self.residual_se_cnn_block(kwargs[\"imu1_channels\"], kwargs[\"feat_dim\"], kwargs[\"imu2_layers\"], drop=kwargs[\"imu2_dropout\"])\n        )\n\n        self.thm_branch = nn.Sequential(\n            nn.Conv1d(thm_dim, kwargs[\"thm1_channels\"], kernel_size=3, padding=1, bias=False),\n            nn.BatchNorm1d(kwargs[\"thm1_channels\"]),\n            nn.ReLU(inplace=True),\n            nn.MaxPool1d(2, ceil_mode=True),\n            nn.Dropout(kwargs[\"thm1_dropout\"]),\n            nn.Conv1d(kwargs[\"thm1_channels\"], kwargs[\"feat_dim\"], kernel_size=3, padding=1, bias=False),\n            nn.BatchNorm1d(kwargs[\"feat_dim\"]),\n            nn.ReLU(inplace=True),\n            nn.MaxPool1d(2, ceil_mode=True),\n            nn.Dropout(kwargs[\"thm2_dropout\"])\n        )\n\n        self.tof_branch = nn.Sequential(\n            nn.Conv1d(tof_dim, kwargs[\"tof1_channels\"], kernel_size=3, padding=1, bias=False),\n            nn.BatchNorm1d(kwargs[\"tof1_channels\"]),\n            nn.ReLU(inplace=True),\n            nn.MaxPool1d(2, ceil_mode=True),\n            nn.Dropout(kwargs[\"tof1_dropout\"]),\n            nn.Conv1d(kwargs[\"tof1_channels\"], kwargs[\"feat_dim\"], kernel_size=3, padding=1, bias=False),\n            nn.BatchNorm1d(kwargs[\"feat_dim\"]),\n            nn.ReLU(inplace=True),\n            nn.MaxPool1d(2, ceil_mode=True),\n            nn.Dropout(kwargs[\"tof2_dropout\"])\n        )\n\n        self.cls_token = nn.Parameter(torch.zeros(1, 1, kwargs[\"feat_dim\"]))\n\n        self.bert = BertModel(\n            BertConfig(\n                hidden_size=kwargs[\"feat_dim\"],\n                num_hidden_layers=kwargs[\"bert_layers\"],\n                num_attention_heads=kwargs[\"bert_heads\"],\n                intermediate_size=kwargs[\"feat_dim\"] * 4\n            )\n        )\n\n        self.classifier = nn.Sequential(\n            nn.Linear(kwargs[\"feat_dim\"], kwargs[\"cls1_channels\"], bias=False),\n            nn.BatchNorm1d(kwargs[\"cls1_channels\"]),\n            nn.ReLU(inplace=True),\n            nn.Dropout(kwargs[\"cls1_dropout\"]),\n            nn.Linear(kwargs[\"cls1_channels\"], kwargs[\"cls2_channels\"], bias=False),\n            nn.BatchNorm1d(kwargs[\"cls2_channels\"]),\n            nn.ReLU(inplace=True),\n            nn.Dropout(kwargs[\"cls2_dropout\"]),\n            nn.Linear(kwargs[\"cls2_channels\"], n_classes)\n        )\n\n    def residual_se_cnn_block(self, in_channels, out_channels, num_layers, pool_size=2, drop=0.3, wd=1e-4):\n        return nn.Sequential(\n            *[ResNetSEBlock(in_channels=in_channels, out_channels=in_channels) for _ in range(num_layers)],\n            ResNetSEBlock(in_channels, out_channels, wd=wd),\n            nn.MaxPool1d(pool_size),\n            nn.Dropout(drop)\n        )\n\n    def forward(self, imu, thm, tof):\n        imu_feat = self.imu_branch(imu.permute(0, 2, 1))\n        thm_feat = self.thm_branch(thm.permute(0, 2, 1))\n        tof_feat = self.tof_branch(tof.permute(0, 2, 1))\n        bert_input = torch.cat([imu_feat, thm_feat, tof_feat], dim=-1).permute(0, 2, 1)\n        cls_token = self.cls_token.expand(bert_input.size(0), -1, -1)\n        bert_input = torch.cat([cls_token, bert_input], dim=1)\n        outputs = self.bert(inputs_embeds=bert_input)\n        pred_cls = outputs.last_hidden_state[:, 0, :]\n        return self.classifier(pred_cls)\n\n\n# ========== Load Model2 Ensemble ==========\n\nmodel_function = CMIModel\nmodel_args = {\n    \"feat_dim\": 500,\n    \"imu1_channels\": 219,\n    \"imu1_dropout\": 0.2946731587132302,\n    \"imu2_dropout\": 0.2697745571929592,\n    \"imu1_weight_decay\": 0.0014824054650601245,\n    \"imu2_weight_decay\": 0.002742543773142381,\n    \"imu1_layers\": 0,\n    \"imu2_layers\": 0,\n    \"thm1_channels\": 82,\n    \"thm1_dropout\": 0.2641274454844602,\n    \"thm2_dropout\": 0.302896343020985,\n    \"tof1_channels\": 82,\n    \"tof1_dropout\": 0.2641274454844602,\n    \"tof2_dropout\": 0.3028963430209852,\n    \"bert_layers\": 8,\n    \"bert_heads\": 10,\n    \"cls1_channels\": 937,\n    \"cls2_channels\": 303,\n    \"cls1_dropout\": 0.2281834512100508,\n    \"cls2_dropout\": 0.22502521933558461\n}\nmodel_args.update({\n    \"imu_dim\": dataset.full_dataset.imu_dim,\n    \"thm_dim\": dataset.full_dataset.thm_dim,\n    \"tof_dim\": dataset.full_dataset.tof_dim,\n    \"n_classes\": dataset.full_dataset.class_num\n})\n\nmodel_dir = Path(\"/kaggle/input/cmi-models-public/pytorch/train_fold_model05_tof16_raw/1\")\nmodel_dicts = [\n    {\n        \"model_function\": model_function,\n        \"model_args\": model_args,\n        \"model_path\": model_dir / f\"fold{fold}/best_ema.pt\"\n    }\n    for fold in range(n_folds)\n]\n\nmodels2 = []\nfor model_dict in model_dicts:\n    model = model_dict[\"model_function\"](**model_dict[\"model_args\"]).to(CUDA0)\n    state_dict = {k.replace(\"_orig_mod.\", \"\"): v for k, v in torch.load(model_dict[\"model_path\"]).items()}\n    model.load_state_dict(state_dict)\n    model.eval()\n    models2.append(model)\n\n\n# ========== Metric & Utility ==========\n\nmetric_package = kagglehub.package_import('wasupandceacar/cmi-metric')\nmetric = metric_package.Metric()\nimu_only_metric = metric_package.Metric()\n\ndef to_cuda(*tensors):\n    return [tensor.to(CUDA0) for tensor in tensors]\n\ndef avg_predict(models, imu, thm, tof):\n    outputs = []\n    with autocast(device_type='cuda'):\n        for model in models:\n            logits = model(imu, thm, tof)\n            outputs.append(logits)\n    return torch.mean(torch.stack(outputs), dim=0)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model 3:","metadata":{}},{"cell_type":"code","source":"TRAIN = False\nRAW_DIR = Path(\"/kaggle/input/cmi-detect-behavior-with-sensor-data\")\nYOUR_MODELS_DIR = Path(\"/kaggle/input/cmi-data-gated-gru\")\nPUBLIC_TF_MODEL_DIR = Path(\"/kaggle/input/lb-0-78-quaternions-tf-bilstm-gru-attention\")\nPUBLIC_PT_MODEL_DIR = Path(\"/kaggle/input/cmi3-models-p\")\nEXPORT_DIR = Path(\"./\")\nBATCH_SIZE = 64\nPAD_PERCENTILE = 95\nLR_INIT = 4e-4\nWD = 3e-3\nMIXUP_ALPHA = 0.4\nEPOCHS = 360\nPATIENCE = 50\nN_SPLITS = 10\nMASKING_PROB = 0.25\nGATE_LOSS_WEIGHT = 0.2\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmean_pt = torch.tensor([0, 0, 0, 0, 0, 0, 9.0319e-03, 1.0849e+00, -2.6186e-03, 3.7651e-03, -5.3660e-03, -2.8177e-03, 1.3318e-03, -1.5876e-04, 6.3495e-01, 6.2877e-01, 6.0607e-01, 6.2142e-01, 6.3808e-01, 6.5420e-01, 7.4102e-03, -3.4159e-03, -7.5237e-03, -2.6034e-02, 2.9704e-02, -3.1546e-02, -2.0610e-03, -4.6986e-03, -4.7216e-03, -2.6281e-02, 1.5799e-02, 1.0016e-02], dtype=torch.float32).view(1, -1, 1).to(device)\nstd_pt = torch.tensor([1, 1, 1, 1, 1, 1, 0.2067, 0.8583, 0.3162, 0.2668, 0.2917, 0.2341, 0.3023, 0.3281, 1.0264, 0.8838, 0.8686, 1.0973, 1.0267, 0.9018, 0.4658, 0.2009, 0.2057, 1.2240, 0.9535, 0.6655, 0.2941, 0.3421, 0.8156, 0.6565, 1.1034, 1.5577], dtype=torch.float32).view(1, -1, 1).to(device) + 1e-8\nclass ImuFeatureExtractor(nn.Module):\n    def __init__(self, fs=100., add_quaternion=False):\n        super().__init__()\n        self.fs = fs\n        self.add_quaternion = add_quaternion\n        k = 15\n        self.lpf = nn.Conv1d(6, 6, kernel_size=k, padding=k//2, groups=6, bias=False)\n        nn.init.kaiming_uniform_(self.lpf.weight, a=math.sqrt(5))\n        self.lpf_acc = nn.Conv1d(3, 3, k, padding=k//2, groups=3, bias=False)\n        self.lpf_gyro = nn.Conv1d(3, 3, k, padding=k//2, groups=3, bias=False)\n    def forward(self, imu):\n        acc = imu[:, 0:3, :]\n        gyro = imu[:, 3:6, :]\n        acc_mag = torch.norm(acc, dim=1, keepdim=True)\n        gyro_mag = torch.norm(gyro, dim=1, keepdim=True)\n        jerk = F.pad(acc[:, :, 1:] - acc[:, :, :-1], (1,0))\n        gyro_delta = F.pad(gyro[:, :, 1:] - gyro[:, :, :-1], (1,0))\n        acc_pow = acc ** 2\n        gyro_pow = gyro ** 2\n        acc_lpf = self.lpf_acc(acc)\n        acc_hpf = acc - acc_lpf\n        gyro_lpf = self.lpf_gyro(gyro)\n        gyro_hpf = gyro - gyro_lpf\n        features = [acc, gyro, acc_mag, gyro_mag, jerk, gyro_delta, acc_pow, gyro_pow, acc_lpf, acc_hpf, gyro_lpf, gyro_hpf]\n        return torch.cat(features, dim=1)\nclass SEBlock(nn.Module):\n    def __init__(self, channels, reduction=8):\n        super().__init__()\n        self.squeeze = nn.AdaptiveAvgPool1d(1)\n        self.excitation = nn.Sequential(nn.Linear(channels, channels // reduction, bias=False), nn.ReLU(inplace=True), nn.Linear(channels // reduction, channels, bias=False), nn.Sigmoid())\n    def forward(self, x):\n        b, c, _ = x.size()\n        y = self.squeeze(x).view(b, c)\n        y = self.excitation(y).view(b, c, 1)\n        return x * y.expand_as(x)\nclass ResidualSECNNBlock(nn.Module):\n    def __init__(self, in_channels, out_channels, kernel_size, pool_size=2, dropout=0.3):\n        super().__init__()\n        self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size, padding=kernel_size//2, bias=False)\n        self.bn1 = nn.BatchNorm1d(out_channels)\n        self.conv2 = nn.Conv1d(out_channels, out_channels, kernel_size, padding=kernel_size//2, bias=False)\n        self.bn2 = nn.BatchNorm1d(out_channels)\n        self.se = SEBlock(out_channels)\n        self.shortcut = nn.Sequential()\n        if in_channels != out_channels:\n            self.shortcut = nn.Sequential(nn.Conv1d(in_channels, out_channels, 1, bias=False), nn.BatchNorm1d(out_channels))\n        self.pool = nn.MaxPool1d(pool_size)\n        self.dropout = nn.Dropout(dropout)\n    def forward(self, x):\n        out = F.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        out = self.se(out)\n        out += self.shortcut(x)\n        return self.dropout(self.pool(F.relu(out)))\nclass AttentionLayer(nn.Module):\n    def __init__(self, hidden_dim):\n        super().__init__()\n        self.attention = nn.Linear(hidden_dim, 1)\n    def forward(self, x):\n        scores = torch.tanh(self.attention(x))\n        weights = F.softmax(scores.squeeze(-1), dim=1)\n        return torch.sum(x * weights.unsqueeze(-1), dim=1)\n\nclass PublicTwoBranchModel(nn.Module):\n    def __init__(self, pad_len, imu_dim_raw, tof_dim, n_classes, dropouts=[0.3, 0.3, 0.3, 0.3, 0.4, 0.5, 0.3], feature_engineering=True, **kwargs):\n        super().__init__()\n        self.feature_engineering = feature_engineering\n        imu_dim = 32 if feature_engineering else imu_dim_raw\n        self.imu_fe = ImuFeatureExtractor(**kwargs) if feature_engineering else nn.Identity()\n        self.fir_nchan = 7\n        numtaps = 33\n        fir_kernel = torch.tensor(firwin(numtaps, cutoff=1.0, fs=10.0, pass_zero=False), dtype=torch.float32).view(1, 1, -1).repeat(self.fir_nchan, 1, 1)\n        self.register_buffer(\"fir_kernel\", fir_kernel)\n        self.imu_block1 = ResidualSECNNBlock(imu_dim, 64, 3, dropout=dropouts[0])\n        self.imu_block2 = ResidualSECNNBlock(64, 128, 5, dropout=dropouts[1])\n        self.tof_conv1 = nn.Conv1d(tof_dim, 64, 3, padding=1, bias=False)\n        self.tof_bn1, self.tof_pool1, self.tof_drop1 = nn.BatchNorm1d(64), nn.MaxPool1d(2), nn.Dropout(dropouts[2])\n        self.tof_conv2 = nn.Conv1d(64, 128, 3, padding=1, bias=False)\n        self.tof_bn2, self.tof_pool2, self.tof_drop2 = nn.BatchNorm1d(128), nn.MaxPool1d(2), nn.Dropout(dropouts[3])\n        self.bilstm = nn.LSTM(256, 128, bidirectional=True, batch_first=True)\n        self.lstm_dropout = nn.Dropout(dropouts[4])\n        self.attention = AttentionLayer(256)\n        self.dense1, self.bn_dense1, self.drop1 = nn.Linear(256, 256, bias=False), nn.BatchNorm1d(256), nn.Dropout(dropouts[5])\n        self.dense2, self.bn_dense2, self.drop2 = nn.Linear(256, 128, bias=False), nn.BatchNorm1d(128), nn.Dropout(dropouts[6])\n        self.classifier = nn.Linear(128, n_classes)\n    def forward(self, x):\n        imu_raw = x[:, :, :self.fir_nchan].transpose(1, 2)\n        tof = x[:, :, self.fir_nchan:].transpose(1, 2)\n        imu_fe = self.imu_fe(imu_raw)\n        filtered = F.conv1d(imu_fe[:, :self.fir_nchan, :], self.fir_kernel, padding=self.fir_kernel.shape[-1] // 2, groups=self.fir_nchan)\n        imu = (torch.cat([filtered, imu_fe[:, self.fir_nchan:, :]], dim=1) - mean_pt) / std_pt\n        x1 = self.imu_block1(imu); x1 = self.imu_block2(x1)\n        x2 = self.tof_drop1(self.tof_pool1(F.relu(self.tof_bn1(self.tof_conv1(tof)))))\n        x2 = self.tof_drop2(self.tof_pool2(F.relu(self.tof_bn2(self.tof_conv2(x2)))))\n        merged = torch.cat([x1, x2], dim=1).transpose(1, 2)\n        lstm_out, _ = self.bilstm(merged); lstm_out = self.lstm_dropout(lstm_out)\n        attended = self.attention(lstm_out)\n        x = self.drop1(F.relu(self.bn_dense1(self.dense1(attended))))\n        x = self.drop2(F.relu(self.bn_dense2(self.dense2(x))))\n        return self.classifier(x)\ndef time_sum(x): return K.sum(x, axis=1)\ndef squeeze_last_axis(x): return tf.squeeze(x, axis=-1)\ndef expand_last_axis(x): return tf.expand_dims(x, axis=-1)\n\ndef residual_se_cnn_block(x, filters, kernel_size, pool_size=2, drop=0.3, wd=1e-4):\n    shortcut = x\n    for _ in range(2):\n        x = Conv1D(filters, kernel_size, padding='same', use_bias=False, kernel_regularizer=l2(wd))(x)\n        x = BatchNormalization()(x)\n        x = Activation('relu')(x)\n    x = se_block(x)\n    if shortcut.shape[-1] != filters:\n        shortcut = Conv1D(filters, 1, padding='same', use_bias=False, kernel_regularizer=l2(wd))(shortcut)\n        shortcut = BatchNormalization()(shortcut)\n    x = add([x, shortcut])\n    x = Activation('relu')(x)\n    x = MaxPooling1D(pool_size)(x)\n    x = Dropout(drop)(x)\n    return x\ndef attention_layer(inputs):\n    score = Dense(1, activation='tanh')(inputs)\n    score = Lambda(squeeze_last_axis)(score)\n    weights = Activation('softmax')(score)\n    weights = Lambda(expand_last_axis)(weights)\n    context = Multiply()([inputs, weights])\n    context = Lambda(time_sum)(context)\n    return context\n\n\nprint(\"▶ Inference mode start – loading trained models and artifacts...\")\nprint(\" Loading model group A (Custom 5-Fold Gated GRU model)...\")\nfinal_feature_cols_A = np.load(YOUR_MODELS_DIR / \"final_feature_cols.npy\", allow_pickle=True).tolist()\npad_len_A = int(np.load(YOUR_MODELS_DIR / \"sequence_maxlen.npy\"))\nscaler_A = joblib.load(YOUR_MODELS_DIR / \"scaler.pkl\")\ngesture_classes = np.load(YOUR_MODELS_DIR / \"gesture_classes.npy\", allow_pickle=True)\ncustom_objs_A = {'time_sum': time_sum, 'squeeze_last_axis': squeeze_last_axis, 'expand_last_axis': expand_last_axis, 'se_block': se_block, 'residual_se_cnn_block': residual_se_cnn_block, 'attention_layer': attention_layer}\nmodels_A = [load_model(YOUR_MODELS_DIR / f\"final_model_fold_{f}.h5\", compile=False, custom_objects=custom_objs_A) for f in range(N_SPLITS)]\nprint(f\" > Loaded {len(models_A)} models successfully.\")\nprint(\"\\n Loading model group B (Public TF/Keras model)...\")\nfinal_feature_cols_B = np.load(PUBLIC_TF_MODEL_DIR / \"feature_cols.npy\", allow_pickle=True).tolist()\npad_len_B = int(np.load(PUBLIC_TF_MODEL_DIR / \"sequence_maxlen.npy\"))\nscaler_B = joblib.load(PUBLIC_TF_MODEL_DIR / \"scaler.pkl\")\ncustom_objs_B = custom_objs_A\nmodel_B = load_model(PUBLIC_TF_MODEL_DIR / \"gesture_two_branch_mixup.h5\", compile=False, custom_objects=custom_objs_B)\nprint(\" > Loaded 1 model successfully.\")\nprint(\"\\n Loading model group C (Public PyTorch model)...\")\nfinal_feature_cols_C = np.load(PUBLIC_PT_MODEL_DIR / \"feature_cols.npy\", allow_pickle=True).tolist()\npad_len_C = int(np.load(PUBLIC_PT_MODEL_DIR / \"sequence_maxlen.npy\"))\nscaler_C = joblib.load(PUBLIC_PT_MODEL_DIR / \"scaler.pkl\")\npt_models = []\nfor f in range(5):\n    checkpoint = torch.load(PUBLIC_PT_MODEL_DIR / f\"gesture_two_branch_fold{f}.pth\", map_location=device)\n    cfg = {'pad_len': checkpoint['pad_len'], 'imu_dim_raw': checkpoint['imu_dim'], 'tof_dim': checkpoint['tof_dim'], 'n_classes': checkpoint['n_classes']}\n    m = PublicTwoBranchModel(**cfg).to(device)\n    m.load_state_dict(checkpoint['model_state_dict'])\n    m.eval()\n    pt_models.append(m)\nprint(f\" > Loaded {len(pt_models)} models successfully.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Predict 1~3 function","metadata":{}},{"cell_type":"code","source":"def predict1(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    df_seq = sequence.to_pandas()\n    linear_accel = remove_gravity_from_acc(df_seq, df_seq)\n    df_seq['linear_acc_x'], df_seq['linear_acc_y'], df_seq['linear_acc_z'] = linear_accel[:, 0], linear_accel[:, 1], linear_accel[:, 2]\n    df_seq['linear_acc_mag'] = np.sqrt(df_seq['linear_acc_x']**2 + df_seq['linear_acc_y']**2 + df_seq['linear_acc_z']**2)\n    df_seq['linear_acc_mag_jerk'] = df_seq['linear_acc_mag'].diff().fillna(0)\n    angular_vel = calculate_angular_velocity_from_quat(df_seq)\n    df_seq['angular_vel_x'], df_seq['angular_vel_y'], df_seq['angular_vel_z'] = angular_vel[:, 0], angular_vel[:, 1], angular_vel[:, 2]\n    df_seq['angular_distance'] = calculate_angular_distance(df_seq)\n\n    for i in range(1, 6):\n        pixel_cols = [f\"tof_{i}_v{p}\" for p in range(64)]; tof_data = df_seq[pixel_cols].replace(-1, np.nan)\n        df_seq[f'tof_{i}_mean'], df_seq[f'tof_{i}_std'], df_seq[f'tof_{i}_min'], df_seq[f'tof_{i}_max'] = tof_data.mean(axis=1), tof_data.std(axis=1), tof_data.min(axis=1), tof_data.max(axis=1)\n\n    mat_unscaled = df_seq[final_feature_cols].ffill().bfill().fillna(0).values.astype('float32')\n    mat_scaled = scaler.transform(mat_unscaled)\n    pad_input = pad_sequences([mat_scaled], maxlen=pad_len, padding='post', truncating='post', dtype='float32')\n    all_preds = [model.predict(pad_input, verbose=0)[0] for model in models1]\n    avg_pred = np.mean(all_preds, axis=0)\n    return avg_pred\n\ndef predict2(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    imu, thm, tof = dataset.full_dataset.inference_process(sequence)\n    with torch.no_grad():\n        imu, thm, tof = to_cuda(imu, thm, tof)\n        logits = avg_predict(models2, imu, thm, tof)\n        probabilities = F.softmax(logits, dim=1).cpu().numpy()\n    return probabilities\n\ndef predict3(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    df_seq_orig = sequence.to_pandas()\n    df_seq_A = df_seq_orig.copy()\n    linear_accel_A = remove_gravity_from_acc3(df_seq_A[['acc_x','acc_y','acc_z']], df_seq_A[['rot_x','rot_y','rot_z','rot_w']])\n    df_seq_A['linear_acc_x'], df_seq_A['linear_acc_y'], df_seq_A['linear_acc_z'] = linear_accel_A[:,0], linear_accel_A[:,1], linear_accel_A[:,2]\n    df_seq_A['linear_acc_mag'] = np.linalg.norm(linear_accel_A, axis=1)\n    df_seq_A['linear_acc_mag_jerk'] = df_seq_A['linear_acc_mag'].diff().fillna(0)\n    angular_vel_A = calculate_angular_velocity_from_quat3(df_seq_A[['rot_x','rot_y','rot_z','rot_w']])\n    df_seq_A['angular_vel_x'], df_seq_A['angular_vel_y'], df_seq_A['angular_vel_z'] = angular_vel_A[:,0], angular_vel_A[:,1], angular_vel_A[:,2]\n    df_seq_A['angular_distance'] = calculate_angular_distance3(df_seq_A[['rot_x','rot_y','rot_z','rot_w']])\n    for col in ['rot_x', 'rot_y', 'rot_z', 'rot_w']:\n        df_seq_A[f'{col}_diff'] = df_seq_A[col].diff().fillna(0)\n    cols_for_stats=['linear_acc_mag','linear_acc_mag_jerk','angular_distance']\n    for col in cols_for_stats:\n        df_seq_A[f'{col}_skew'], df_seq_A[f'{col}_kurt'] = df_seq_A[col].skew(), df_seq_A[col].kurtosis()\n    for i in range(1,6):\n        if f'tof_{i}_v0' in df_seq_A.columns:\n            pixel_cols=[f\"tof_{i}_v{p}\" for p in range(64)]; tof_data=df_seq_A[pixel_cols].replace(-1,np.nan)\n            df_seq_A[f'tof_{i}_mean'], df_seq_A[f'tof_{i}_std'], df_seq_A[f'tof_{i}_min'], df_seq_A[f'tof_{i}_max'] = tof_data.mean(axis=1),tof_data.std(axis=1),tof_data.min(axis=1),tof_data.max(axis=1)\n    tof_mean_cols=[f'tof_{i}_mean' for i in range(1,6) if f'tof_{i}_mean' in df_seq_A.columns]\n    if tof_mean_cols:\n        df_seq_A['tof_std_across_sensors']=df_seq_A[tof_mean_cols].std(axis=1)\n        df_seq_A['tof_range_across_sensors']=df_seq_A[tof_mean_cols].max(axis=1)-df_seq_A[tof_mean_cols].min(axis=1)\n    thm_cols=[f'thm_{i}' for i in range(1,6) if f'thm_{i}' in df_seq_A.columns]\n    if thm_cols:\n        df_seq_A['thm_std_across_sensors']=df_seq_A[thm_cols].std(axis=1)\n        df_seq_A['thm_range_across_sensors']=df_seq_A[thm_cols].max(axis=1)-df_seq_A[thm_cols].min(axis=1)\n    mat_A = df_seq_A[final_feature_cols_A].ffill().bfill().fillna(0).values.astype('float32')\n    mat_A = scaler_A.transform(mat_A)\n    pad_input_A = pad_sequences([mat_A], maxlen=pad_len_A, padding='post', dtype='float32')\n    preds_A_folds = [model.predict(pad_input_A, verbose=0)[0] for model in models_A]\n    avg_pred_A = np.mean(preds_A_folds, axis=0)\n    df_seq_B = df_seq_orig.copy()\n    df_seq_B['acc_mag']=np.sqrt(df_seq_B['acc_x']**2+df_seq_B['acc_y']**2+df_seq_B['acc_z']**2)\n    df_seq_B['rot_angle']=2*np.arccos(df_seq_B['rot_w'].clip(-1,1))\n    df_seq_B['acc_mag_jerk']=df_seq_B['acc_mag'].diff().fillna(0)\n    df_seq_B['rot_angle_vel']=df_seq_B['rot_angle'].diff().fillna(0)\n    linear_accel_B=remove_gravity_from_acc3(df_seq_B,df_seq_B)\n    df_seq_B['linear_acc_x'],df_seq_B['linear_acc_y'],df_seq_B['linear_acc_z']=linear_accel_B[:,0],linear_accel_B[:,1],linear_accel_B[:,2]\n    df_seq_B['linear_acc_mag']=np.sqrt(df_seq_B['linear_acc_x']**2+df_seq_B['linear_acc_y']**2+df_seq_B['linear_acc_z']**2)\n    df_seq_B['linear_acc_mag_jerk']=df_seq_B['linear_acc_mag'].diff().fillna(0)\n    angular_vel_B=calculate_angular_velocity_from_quat3(df_seq_B)\n    df_seq_B['angular_vel_x'],df_seq_B['angular_vel_y'],df_seq_B['angular_vel_z']=angular_vel_B[:,0],angular_vel_B[:,1],angular_vel_B[:,2]\n    df_seq_B['angular_distance']=calculate_angular_distance3(df_seq_B)\n    for i in range(1,6):\n        if f'tof_{i}_v0' in df_seq_B.columns:\n            pixel_cols=[f\"tof_{i}_v{p}\" for p in range(64)]; tof_data=df_seq_B[pixel_cols].replace(-1,np.nan)\n            df_seq_B[f\"tof_{i}_mean\"],df_seq_B[f\"tof_{i}_std\"],df_seq_B[f\"tof_{i}_min\"],df_seq_B[f\"tof_{i}_max\"]=tof_data.mean(axis=1),tof_data.std(axis=1),tof_data.min(axis=1),tof_data.max(axis=1)\n    mat_B = df_seq_B[final_feature_cols_B].ffill().bfill().fillna(0).values.astype('float32')\n    mat_B = scaler_B.transform(mat_B)\n    pad_input_B = pad_sequences([mat_B], maxlen=pad_len_B, padding='post', dtype='float32')\n    pred_B = model_B.predict(pad_input_B, verbose=0)\n    if isinstance(pred_B, list): pred_B = pred_B[0]\n    df_seq_C = df_seq_orig.copy()\n    mat_C = df_seq_C[final_feature_cols_C].ffill().bfill().fillna(0).values.astype('float32')\n    mat_C = scaler_C.transform(mat_C)\n    pad_input_C = pad_sequences_torch3([mat_C], maxlen=pad_len_C, padding='pre', truncating='pre')\n    with torch.no_grad():\n        pt_input = torch.from_numpy(pad_input_C).to(device)\n        preds_C_folds = [model(pt_input) for model in pt_models]\n        avg_pred_C_logits = torch.mean(torch.stack(preds_C_folds), dim=0)\n        avg_pred_C = torch.softmax(avg_pred_C_logits, dim=1).cpu().numpy()\n    weights = {'A': 0.50, 'B': 0.20, 'C': 0.30}\n    final_pred_proba = (weights['A'] * avg_pred_A + weights['B'] * pred_B + weights['C'] * avg_pred_C)\n    return final_pred_proba\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# ","metadata":{}},{"cell_type":"markdown","source":"# Predict function combine predict1~3","metadata":{}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport random\nimport torch\nimport tensorflow as tf\nfrom tensorflow.keras.models import load_model\nfrom torch import nn\nfrom torch.nn import functional as F\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.model_selection import GroupKFold\nfrom sklearn.metrics import f1_score\nfrom sklearn.preprocessing import LabelEncoder\nfrom keras.preprocessing.sequence import pad_sequences\n\n\ndef init_seed(seed=42):\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    random.seed(seed)\n    np.random.seed(seed)\n    tf.random.set_seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\n\ndef add_features(df):\n    df['acc_mag'] = np.sqrt(df['acc_x']**2 + df['acc_y']**2 + df['acc_z']**2)\n    df['angular_distance'] = np.sqrt(df['rot_x']**2 + df['rot_y']**2 + df['rot_z']**2 + df['rot_w']**2)\n    df['linear_acc_mag'] = np.sqrt(df['linear_acc_x']**2 + df['linear_acc_y']**2 + df['linear_acc_z']**2)\n    df['acc_mag_jerk'] = df.groupby('sequence_id')['acc_mag'].diff().fillna(0)\n    df['linear_acc_mag_jerk'] = df.groupby('sequence_id')['linear_acc_mag'].diff().fillna(0)\n    df['rot_angle_vel'] = df.groupby('sequence_id')['angular_distance'].diff().fillna(0)\n    return df\n\n\ndef get_sequence_features(df, demographics, pad_len, scaler):\n    df = add_features(df)\n    df = df.replace(-1, np.nan).groupby('sequence_id').ffill().bfill().fillna(0)\n    used_cols = [\n        'acc_mag', 'acc_mag_jerk', 'rot_angle_vel',\n        'linear_acc_mag', 'linear_acc_mag_jerk',\n        'angular_velocity_x', 'angular_velocity_y', 'angular_velocity_z',\n        'angular_distance'\n    ]\n    feats = df.groupby('sequence_id')[used_cols].apply(lambda x: x.values).values\n    feats = pad_sequences(feats, padding='post', dtype='float32', maxlen=pad_len)\n    N = len(feats)\n    demo = demographics.loc[df['sequence_id'].unique()]  # aligned\n    demo = scaler.transform(demo)\n    demo = np.repeat(demo[:, np.newaxis, :], pad_len, axis=1)\n    return np.concatenate([feats, demo], axis=-1)\n\n\ndef predict_model1_folds(seq_df, demographics, pad_len, scaler, model_paths):\n    x = get_sequence_features(seq_df, demographics, pad_len, scaler)\n    preds = [load_model(path).predict(x) for path in model_paths]\n    return np.mean(preds, axis=0)\n\n\ndef predict_model2(seq_df, demographics, pad_len, scaler, model_path):\n    x = get_sequence_features(seq_df, demographics, pad_len, scaler)\n    model = load_model(model_path)\n    return model.predict(x)\n\nclass MLP(nn.Module):\n    def __init__(self, input_dim, num_classes):\n        super().__init__()\n        self.net = nn.Sequential(\n            nn.Linear(input_dim, 128),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(128, num_classes)\n        )\n\n    def forward(self, x):\n        return self.net(x)\n\n\ndef predict_model3_folds(seq_df, demographics, pad_len, scaler, model_paths):\n    x = get_sequence_features(seq_df, demographics, pad_len, scaler)\n    x = torch.tensor(x).float()\n    logits_list = []\n    for path in model_paths:\n        model = MLP(x.shape[-1], 7)\n        model.load_state_dict(torch.load(path, map_location=torch.device('cpu')))\n        model.eval()\n        with torch.no_grad():\n            logits = model(x.mean(1))\n            logits_list.append(logits.numpy())\n    median_logits = np.median(np.stack(logits_list), axis=0)\n    probs = F.softmax(torch.tensor(median_logits), dim=1).numpy()\n    return probs\n\n\ndef ensemble_prediction(pred1, pred2, pred3):\n    return 0.5 * pred1 + 0.2 * pred2 + 0.3 * pred3\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# main predict function ========================================\n#sequnce will load test.csv file ,demographics will load test_demographics file\n# lps->original prediction weighted\n#lps ->acend/decend in predict\n#-> mix other weighted\n#-> ascend/descend weighted mix\n#-> pred*lps=wps\n\ndef predict(sequence, demographics):\n    import copy\n    pred0 = predict1(sequence, demographics)[0]\n    pred1 = predict2(sequence, demographics)[0]\n    pred2 = predict_model3_folds(sequence, demographics,pad_len_C,scaler_C,)[0]\n    preds = []\n    main_wts = np.asarray([0.271, 0.347, 0.382])\n    correct_wts = [+0.0021, -0.0007, -0.0014]\n    asc_desc_wts = [0.70, 0.30]\n    for a,b,c in zip(pred0,pred1,pred2):\n        l_abc = [{ 'wts':main_wts[0], 'pred':a, 'n':'p0', 'result':0 }, { 'wts':main_wts[1], 'pred':b, 'n':'p1', 'result':0 }, { 'wts':main_wts[2], 'pred':c, 'n':'p2', 'result':0 }]\n        lps_asc = sorted(copy.deepcopy(l_abc), key=lambda _:_['pred'],reverse=False)\n        lps_desc = sorted(copy.deepcopy(l_abc), key=lambda _:_['pred'],reverse=True)\n        for asc,correct_wt in zip(lps_asc, correct_wts): asc ['wts'] += correct_wt\n        for desc,correct_wt in zip(lps_desc, correct_wts): desc['wts'] += correct_wt\n        for asc in lps_asc: asc ['result'] = asc ['pred'] * asc ['wts']\n        for desc in lps_desc: desc['result'] = desc['pred'] * desc['wts']\n        result_asc = sum([asc ['result'] for asc in lps_asc])\n        result_desc = sum([desc['result'] for asc in lps_desc])\n        result = result_asc * asc_desc_wts[0] + result_desc * asc_desc_wts[1]\n        preds.append(result)\n    avg_pred = np.asarray(preds)\n    return dataset.le.classes_[avg_pred.argmax()]\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"k fold training for predict1~3","metadata":{}},{"cell_type":"code","source":"# ===== KFold 設定 =====\nN_FOLDS   = 5\nNUM_CLASS = 7\nSEED      = 42\n\n# 輸出目錄（Kaggle 上可改成 /kaggle/working）\nART_DIR = \"./working_artifacts\"\n\n# 產物命名（固定規則，推論載入會用）\n# model1（Keras）\nM1_SCALER_PKL = f\"{ART_DIR}/m1_scaler.pkl\"\nM1_PAD_JSON   = f\"{ART_DIR}/m1_padlen.json\"\nM1_FOLD_W     = f\"{ART_DIR}/m1_fold{0}_weights.keras\"   # 用 .format(i) 生每折\n\n# model2（PyTorch）\nM2_FOLD_W     = f\"{ART_DIR}/m2_fold{0}.pt\"\n\n# model3-A（Keras），3-B（Keras），3-C（PyTorch）\nM3A_SCALER_PKL= f\"{ART_DIR}/m3a_scaler.pkl\"\nM3A_PAD_JSON  = f\"{ART_DIR}/m3a_padlen.json\"\nM3A_FOLD_W    = f\"{ART_DIR}/m3a_fold{0}.keras\"\n\nM3B_SCALER_PKL= f\"{ART_DIR}/m3b_scaler.pkl\"\nM3B_PAD_JSON  = f\"{ART_DIR}/m3b_padlen.json\"\nM3B_FOLD_W    = f\"{ART_DIR}/m3b_fold{0}.keras\"\n\nM3C_PAD_JSON  = f\"{ART_DIR}/m3c_padlen.json\"\nM3C_FOLD_W    = f\"{ART_DIR}/m3c_fold{0}.pt\"\nimport os, json, joblib, glob, random\nimport numpy as np\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.preprocessing import StandardScaler\n\nos.makedirs(ART_DIR, exist_ok=True)\nrng = np.random.default_rng(SEED)\nrandom.seed(SEED)\n\ndef save_json(path, obj):\n    with open(path, \"w\") as f: json.dump(obj, f)\n\ndef load_json(path):\n    with open(path, \"r\") as f: return json.load(f)\n\n# 你若有 participant/user 的欄位，建議改成 GroupKFold/StratifiedGroupKFold 防洩漏\ndef make_folds(y):\n    skf = StratifiedKFold(n_splits=N_FOLDS, shuffle=True, random_state=SEED)\n    return list(skf.split(np.arange(len(y)), y))\n\n# 決定 pad_len（例如取 95% 分位）\ndef decide_pad_len(lengths, q=0.95, cap=None):\n    L = int(np.quantile(lengths, q))\n    if cap: L = min(L, cap)\n    return max(8, L)\n\n# Keras 的 pad_sequences（你已有同名可沿用）\nfrom tensorflow.keras.preprocessing.sequence import pad_sequences\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def fe_m1_from_sequence(df_seq):  # 單一 sequence → (T, F)\n    # 完全照你的 predict1 特徵工程\n    linear_accel = remove_gravity_from_acc(df_seq, df_seq)\n    df = df_seq.copy()\n    df['linear_acc_x'], df['linear_acc_y'], df['linear_acc_z'] = linear_accel[:,0], linear_accel[:,1], linear_accel[:,2]\n    df['linear_acc_mag']      = np.sqrt(df['linear_acc_x']**2 + df['linear_acc_y']**2 + df['linear_acc_z']**2)\n    df['linear_acc_mag_jerk'] = df['linear_acc_mag'].diff().fillna(0)\n    ang = calculate_angular_velocity_from_quat(df)\n    df['angular_vel_x'], df['angular_vel_y'], df['angular_vel_z'] = ang[:,0], ang[:,1], ang[:,2]\n    df['angular_distance'] = calculate_angular_distance(df)\n\n    for i in range(1, 6):\n        pixel_cols=[f\"tof_{i}_v{p}\" for p in range(64) if f\"tof_{i}_v{p}\" in df.columns]\n        if pixel_cols:\n            tof = df[pixel_cols].replace(-1, np.nan)\n            df[f\"tof_{i}_mean\"], df[f\"tof_{i}_std\"], df[f\"tof_{i}_min\"], df[f\"tof_{i}_max\"] = \\\n                tof.mean(axis=1), tof.std(axis=1), tof.min(axis=1), tof.max(axis=1)\n\n    mat = df[final_feature_cols].ffill().bfill().fillna(0).values.astype(\"float32\")\n    return mat\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\n\ndef build_model1(input_shape):  # 你原本的模型結構\n    # 這裡放你現有的 Keras 模型建構\n    # return tf.keras.Model(...)\n    raise NotImplementedError\n\ndef train_model1_kfold(train_sequences, train_demographics, y):\n    # 1) 特徵抽取\n    X_list = []\n    for seq in train_sequences:\n        df = seq.to_pandas() if hasattr(seq, \"to_pandas\") else seq\n        X_list.append(fe_m1_from_sequence(df))\n    lengths = [x.shape[0] for x in X_list]\n    pad_len = decide_pad_len(lengths, q=0.95)\n    save_json(M1_PAD_JSON, {\"pad_len\": pad_len})\n\n    # 2) scaler（用全部訓練資料 fit 一個，推論端亦用它）\n    scaler = StandardScaler()\n    X_all = np.vstack([x for x in X_list])      # (sumT, F)\n    scaler.fit(X_all)\n    joblib.dump(scaler, M1_SCALER_PKL)\n\n    # 3) 做折\n    folds = make_folds(np.asarray(y))\n    for fi, (tr_idx, va_idx) in enumerate(folds):\n        # 組出本折的 (N, pad_len, F) 與 y\n        def pack(indices):\n            mats = []\n            for i in indices:\n                mat = scaler.transform(X_list[i])\n                mats.append(mat)\n            X = pad_sequences(mats, maxlen=pad_len, padding=\"post\", truncating=\"post\", dtype=\"float32\")\n            Y = tf.keras.utils.to_categorical(np.asarray([y[i] for i in indices]), num_classes=NUM_CLASS)\n            return X, Y\n\n        Xtr, Ytr = pack(tr_idx)\n        Xva, Yva = pack(va_idx)\n\n        model = build_model1((pad_len, Xtr.shape[-1]))\n        model.compile(optimizer=\"adam\", loss=\"categorical_crossentropy\", metrics=[\"accuracy\"])\n        cb = [\n            tf.keras.callbacks.EarlyStopping(patience=8, restore_best_weights=True, monitor=\"val_accuracy\"),\n            tf.keras.callbacks.ReduceLROnPlateau(patience=4, factor=0.5),\n        ]\n        model.fit(Xtr, Ytr, validation_data=(Xva, Yva), epochs=100, batch_size=128, callbacks=cb, verbose=2)\n        model.save(M1_FOLD_W.format(fi))\n# 載入多折\ndef _load_model1_for_infer():\n    global models1, scaler, pad_len\n    if \"models1\" in globals(): return\n    scaler  = joblib.load(M1_SCALER_PKL)\n    pad_len = load_json(M1_PAD_JSON)[\"pad_len\"]\n    models1 = [tf.keras.models.load_model(p) for p in sorted(glob.glob(M1_FOLD_W.format(\"*\").replace(\"*\",\"*\")))]\n    assert len(models1) > 0\n\n# 你的原 predict1 只需在最前面加一行 _load_model1_for_infer()\ndef predict1_kfold(sequence: pl.DataFrame, demographics: pl.DataFrame):\n    _load_model1_for_infer()\n    df_seq = sequence.to_pandas()\n    # ……(以下照原本的 predict1)……\n    linear_accel = remove_gravity_from_acc(df_seq, df_seq)\n    df_seq['linear_acc_x'], df_seq['linear_acc_y'], df_seq['linear_acc_z'] = linear_accel[:, 0], linear_accel[:, 1], linear_accel[:, 2]\n    df_seq['linear_acc_mag'] = np.sqrt(df_seq['linear_acc_x']**2 + df_seq['linear_acc_y']**2 + df_seq['linear_acc_z']**2)\n    df_seq['linear_acc_mag_jerk'] = df_seq['linear_acc_mag'].diff().fillna(0)\n    angular_vel = calculate_angular_velocity_from_quat(df_seq)\n    df_seq['angular_vel_x'], df_seq['angular_vel_y'], df_seq['angular_vel_z'] = angular_vel[:, 0], angular_vel[:, 1], angular_vel[:, 2]\n    df_seq['angular_distance'] = calculate_angular_distance(df_seq)\n\n    for i in range(1, 6):\n        pixel_cols = [f\"tof_{i}_v{p}\" for p in range(64)]; tof_data = df_seq[pixel_cols].replace(-1, np.nan)\n        df_seq[f'tof_{i}_mean'], df_seq[f'tof_{i}_std'], df_seq[f'tof_{i}_min'], df_seq[f'tof_{i}_max'] = tof_data.mean(axis=1), tof_data.std(axis=1), tof_data.min(axis=1), tof_data.max(axis=1)\n\n    mat_unscaled = df_seq[final_feature_cols].ffill().bfill().fillna(0).values.astype('float32')\n    mat_scaled = scaler.transform(mat_unscaled)\n    pad_input = pad_sequences([mat_scaled], maxlen=pad_len, padding='post', truncating='post', dtype='float32')\n    all_preds = [model.predict(pad_input, verbose=0)[0] for model in models1]\n    avg_pred = np.mean(all_preds, axis=0)\n    return avg_pred","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# model2 kfold","metadata":{}},{"cell_type":"code","source":"import torch, torch.nn as nn\nfrom torch.utils.data import DataLoader, Dataset\n\n# 假設你有 MLP/Transformer 等模型構造\ndef build_model2():\n    # return YourNet(...)\n    raise NotImplementedError\n\nclass CMIDataset(Dataset):\n    def __init__(self, seqs, demos, ys):\n        self.seqs, self.demos, self.ys = seqs, demos, ys\n    def __len__(self): return len(self.ys)\n    def __getitem__(self, i):\n        return self.seqs[i], self.demos[i], self.ys[i]\n\ndef train_model2_kfold(train_sequences, train_demographics, y):\n    y = np.asarray(y)\n    folds = make_folds(y)\n\n    # 先把所有樣本轉成張量格式，避免每折重做很慢\n    all_imus, all_thms, all_tofs = [], [], []\n    for seq in train_sequences:\n        imu, thm, tof = dataset.full_dataset.inference_process(seq)  # 你現成的前處理\n        all_imus.append(imu); all_thms.append(thm); all_tofs.append(tof)\n\n    for fi, (tr_idx, va_idx) in enumerate(folds):\n        def pack(indices):\n            ims  = [all_imus[i] for i in indices]\n            thms = [all_thms[i] for i in indices]\n            tofs = [all_tofs[i] for i in indices]\n            ys   = [y[i] for i in indices]\n            return ims, thms, tofs, ys\n\n        ims_tr, thm_tr, tof_tr, ytr = pack(tr_idx)\n        ims_va, thm_va, tof_va, yva = pack(va_idx)\n\n        # 這裡你可以用現成的訓練迴圈；以下僅示意\n        model = build_model2()\n        model.to(\"cpu\")\n        opt = torch.optim.Adam(model.parameters(), lr=1e-3)\n        loss_fn = nn.CrossEntropyLoss()\n\n        for epoch in range(15):  # 示意\n            model.train()\n            for imu, thm, tof, yi in zip(ims_tr, thm_tr, tof_tr, ytr):\n                imu, thm, tof = to_cuda(imu, thm, tof)   # 你原本的 util；若只能 CPU 就改回 CPU\n                logits = model(imu, thm, tof)\n                loss = loss_fn(logits, torch.tensor([yi]))\n                opt.zero_grad(); loss.backward(); opt.step()\n\n        torch.save(model.state_dict(), M2_FOLD_W.format(fi))\ndef _load_model2_for_infer():\n    global models2\n    if \"models2\" in globals(): return\n    paths = sorted(glob.glob(M2_FOLD_W.format(\"*\").replace(\"*\",\"*\")))\n    models2 = []\n    for p in paths:\n        m = build_model2()\n        m.load_state_dict(torch.load(p, map_location=\"cpu\"))\n        m.eval()\n        models2.append(m)\n    assert len(models2) > 0\n\n# 你的 predict2 只要在最前面 _load_model2_for_infer()，其餘不變\ndef predict2_kfold(sequence: pl.DataFrame, demographics: pl.DataFrame):\n    _load_model2_for_infer()\n    imu, thm, tof = dataset.full_dataset.inference_process(sequence)\n    with torch.no_grad():\n        imu, thm, tof = to_cuda(imu, thm, tof)\n        logits = avg_predict(models2, imu, thm, tof)  # 本來就做 ensemble\n        probabilities = F.softmax(logits, dim=1).cpu().numpy()\n    return probabilities\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# K-FOLD TRAINING FOR MODEL3 (A/B: Keras, C: PyTorch)\n# Keep predict3 I/O unchanged\n# =========================\nimport os, json, glob, joblib, numpy as np, pandas as pd\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.preprocessing import StandardScaler\nfrom tensorflow.keras.preprocessing.sequence import pad_sequences\nimport tensorflow as tf\nimport torch, torch.nn as nn\nimport torch.nn.functional as F\n\n# --------- constants ---------\nNUM_CLASS = 7\nN_FOLDS   = 5\nSEED      = 42\nART_DIR   = \"./working_artifacts\"\nos.makedirs(ART_DIR, exist_ok=True)\n\n# A\nM3A_SCALER_PKL = f\"{ART_DIR}/m3a_scaler.pkl\"\nM3A_PAD_JSON   = f\"{ART_DIR}/m3a_padlen.json\"\nM3A_FOLD_W     = f\"{ART_DIR}/m3a_fold{{}}.keras\"\n\n# B\nM3B_SCALER_PKL = f\"{ART_DIR}/m3b_scaler.pkl\"\nM3B_PAD_JSON   = f\"{ART_DIR}/m3b_padlen.json\"\nM3B_FOLD_W     = f\"{ART_DIR}/m3b_fold{{}}.keras\"\n\n# C\nM3C_PAD_JSON   = f\"{ART_DIR}/m3c_padlen.json\"\nM3C_FOLD_W     = f\"{ART_DIR}/m3c_fold{{}}.pt\"\n\nrng = np.random.default_rng(SEED)\n\ndef save_json(path, obj):\n    with open(path, \"w\") as f: json.dump(obj, f)\ndef load_json(path):\n    with open(path, \"r\") as f: return json.load(f)\n\ndef make_folds(y):\n    skf = StratifiedKFold(n_splits=N_FOLDS, shuffle=True, random_state=SEED)\n    return list(skf.split(np.arange(len(y)), y))\n\ndef decide_pad_len(lengths, q=0.95, cap=None):\n    L = int(np.quantile(lengths, q))\n    if cap is not None:\n        L = min(L, cap)\n    return max(8, L)\n\n# ---------- FEATURES (same as your predict3) ----------\n# Expect the following globals already defined in your file:\n#   final_feature_cols_A, final_feature_cols_B, final_feature_cols_C\n#   remove_gravity_from_acc3, calculate_angular_velocity_from_quat3, calculate_angular_distance3\ndef _as_pd(df):\n    import pandas as pd\n    if hasattr(df, \"to_pandas\"):\n        return df.to_pandas()\n    if isinstance(df, pd.DataFrame):\n        return df.copy()\n    raise TypeError(f\"sequence must be Polars or pandas DataFrame, got {type(df)}\")\n\ndef _require_cols(df, cols, ctx=\"\"):\n    miss = [c for c in cols if c not in df.columns]\n    if miss:\n        raise ValueError(f\"[{ctx}] missing columns: {miss}. Available: {list(df.columns)[:10]}...\")\n\ndef fe_m3A(df_seq_orig: pd.DataFrame):\n    df = df_seq_orig.copy()\n    lin = remove_gravity_from_acc3(df[['acc_x','acc_y','acc_z']], df[['rot_x','rot_y','rot_z','rot_w']])\n    df['linear_acc_x'], df['linear_acc_y'], df['linear_acc_z'] = lin[:,0], lin[:,1], lin[:,2]\n    df['linear_acc_mag'] = np.linalg.norm(lin, axis=1)\n    df['linear_acc_mag_jerk'] = df['linear_acc_mag'].diff().fillna(0)\n    ang = calculate_angular_velocity_from_quat3(df[['rot_x','rot_y','rot_z','rot_w']])\n    df['angular_vel_x'], df['angular_vel_y'], df['angular_vel_z'] = ang[:,0], ang[:,1], ang[:,2]\n    df['angular_distance'] = calculate_angular_distance3(df[['rot_x','rot_y','rot_z','rot_w']])\n    for col in ['rot_x','rot_y','rot_z','rot_w']:\n        df[f'{col}_diff'] = df[col].diff().fillna(0)\n    for col in ['linear_acc_mag','linear_acc_mag_jerk','angular_distance']:\n        df[f'{col}_skew'] = df[col].skew()\n        df[f'{col}_kurt'] = df[col].kurtosis()\n    for i in range(1,6):\n        cols = [f\"tof_{i}_v{p}\" for p in range(64) if f\"tof_{i}_v{p}\" in df.columns]\n        if cols:\n            t = df[cols].replace(-1, np.nan)\n            df[f'tof_{i}_mean'], df[f'tof_{i}_std'], df[f'tof_{i}_min'], df[f'tof_{i}_max'] = \\\n                t.mean(axis=1), t.std(axis=1), t.min(axis=1), t.max(axis=1)\n    tof_mean_cols=[f'tof_{i}_mean' for i in range(1,6) if f'tof_{i}_mean' in df.columns]\n    if tof_mean_cols:\n        df['tof_std_across_sensors']   = df[tof_mean_cols].std(axis=1)\n        df['tof_range_across_sensors'] = df[tof_mean_cols].max(axis=1) - df[tof_mean_cols].min(axis=1)\n    thm_cols=[f'thm_{i}' for i in range(1,6) if f'thm_{i}' in df.columns]\n    if thm_cols:\n        df['thm_std_across_sensors']   = df[thm_cols].std(axis=1)\n        df['thm_range_across_sensors'] = df[thm_cols].max(axis=1) - df[thm_cols].min(axis=1)\n    mat = df[final_feature_cols_A].ffill().bfill().fillna(0).values.astype('float32')\n    return mat\n\ndef fe_m3B(df_seq_orig: pd.DataFrame):\n    df = df_seq_orig.copy()\n    _require_cols(df, ['acc_x','acc_y','acc_z','rot_x','rot_y','rot_z','rot_w'], \"fe_m3B\")\n\n    df['acc_mag'] = np.sqrt(df['acc_x']**2+df['acc_y']**2+df['acc_z']**2)\n    df['rot_angle'] = 2*np.arccos(df['rot_w'].clip(-1,1))\n    df['acc_mag_jerk'] = df['acc_mag'].diff().fillna(0)\n    df['rot_angle_vel'] = df['rot_angle'].diff().fillna(0)\n\n    lin = remove_gravity_from_acc3(df[['acc_x','acc_y','acc_z']],\n                                   df[['rot_x','rot_y','rot_z','rot_w']])\n    df['linear_acc_x'], df['linear_acc_y'], df['linear_acc_z'] = lin[:,0], lin[:,1], lin[:,2]\n    df['linear_acc_mag'] = np.sqrt(df['linear_acc_x']**2+df['linear_acc_y']**2+df['linear_acc_z']**2)\n    df['linear_acc_mag_jerk'] = df['linear_acc_mag'].diff().fillna(0)\n\n    ang = calculate_angular_velocity_from_quat3(df[['rot_x','rot_y','rot_z','rot_w']])\n    df['angular_vel_x'], df['angular_vel_y'], df['angular_vel_z'] = ang[:,0], ang[:,1], ang[:,2]\n    df['angular_distance'] = calculate_angular_distance3(df[['rot_x','rot_y','rot_z','rot_w']])\n\n    for i in range(1,6):\n        cols=[f\"tof_{i}_v{p}\" for p in range(64) if f\"tof_{i}_v{p}\" in df.columns]\n        if cols:\n            t=df[cols].replace(-1,np.nan)\n            df[f\"tof_{i}_mean\"],df[f\"tof_{i}_std\"],df[f\"tof_{i}_min\"],df[f\"tof_{i}_max\"]= \\\n                t.mean(axis=1),t.std(axis=1),t.min(axis=1),t.max(axis=1)\n\n    mat = df[final_feature_cols_B].ffill().bfill().fillna(0).values.astype('float32')\n    return mat\n\n\ndef fe_m3C(df_seq_orig: pd.DataFrame):\n    # 你 C 路原本就是取 final_feature_cols_C 然後前置 scaler，再 pad 到左側\n    df = df_seq_orig.copy()\n    mat = df[final_feature_cols_C].ffill().bfill().fillna(0).values.astype('float32')\n    return mat  # (T, F)\ndef build_model3A(input_shape):\n    inp = tf.keras.Input(shape=input_shape)\n    x = tf.keras.layers.Masking()(inp)\n    x = tf.keras.layers.Conv1D(128, 5, padding='same', activation='relu')(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.Conv1D(128, 5, padding='same', activation='relu')(x)\n    x = tf.keras.layers.GlobalAveragePooling1D()(x)\n    x = tf.keras.layers.Dropout(0.3)(x)\n    x = tf.keras.layers.Dense(128, activation='relu')(x)\n    out = tf.keras.layers.Dense(NUM_CLASS, activation='softmax')(x)\n    return tf.keras.Model(inp, out)\n\ndef build_model3B(input_shape):\n    inp = tf.keras.Input(shape=input_shape)\n    x = tf.keras.layers.Masking()(inp)\n    x = tf.keras.layers.Bidirectional(tf.keras.layers.GRU(96, return_sequences=True))(x)\n    x = tf.keras.layers.Bidirectional(tf.keras.layers.GRU(64))(x)\n    x = tf.keras.layers.Dropout(0.3)(x)\n    x = tf.keras.layers.Dense(128, activation='relu')(x)\n    out = tf.keras.layers.Dense(NUM_CLASS, activation='softmax')(x)\n    return tf.keras.Model(inp, out)\nclass M3CNet(nn.Module):\n    # 簡單穩定：時間平均 + MLP\n    def __init__(self, in_dim, hidden=128, num_classes=NUM_CLASS):\n        super().__init__()\n        self.fc1 = nn.Linear(in_dim, hidden)\n        self.fc2 = nn.Linear(hidden, num_classes)\n        self.dropout = nn.Dropout(0.3)\n\n    def forward(self, x):          # x: (B, T, F)\n        x = x.mean(dim=1)          # (B, F)\n        x = F.relu(self.fc1(x))\n        x = self.dropout(x)\n        x = self.fc2(x)            # (B, C) logits\n        return x\n\ndef build_model3C(in_dim):\n    return M3CNet(in_dim=in_dim, hidden=128, num_classes=NUM_CLASS)\ndef _pad_np_pre(mats, pad_len):\n    # mats: list of (T_i, F) -> (N, pad_len, F), pre-padding\n    Fdim = mats[0].shape[-1]\n    out = np.zeros((len(mats), pad_len, Fdim), dtype=np.float32)\n    for i, m in enumerate(mats):\n        t = m.shape[0]\n        if t >= pad_len:\n            out[i] = m[-pad_len:]                  # keep last pad_len\n        else:\n            out[i, -t:] = m                        # right align (pre-padding zeros)\n    return out\n\ndef train_model3C_kfold(train_sequences, y):\n    X_list = [fe_m3C(seq.to_pandas() if hasattr(seq,\"to_pandas\") else seq) for seq in train_sequences]\n    lengths = [x.shape[0] for x in X_list]\n    pad_len = decide_pad_len(lengths, q=0.95)\n    save_json(M3C_PAD_JSON, {\"pad_len\": pad_len})\n\n    folds = make_folds(np.asarray(y))\n    device = torch.device(\"cpu\")\n    for fi, (tr, va) in enumerate(folds):\n        def pack(idx):\n            mats = [X_list[i] for i in idx]\n            X = _pad_np_pre(mats, pad_len)                 # (N, pad_len, F)\n            Y = np.asarray([y[i] for i in idx], dtype=np.int64)\n            return X, Y\n\n        Xtr, Ytr = pack(tr); Xva, Yva = pack(va)\n        Fdim = Xtr.shape[-1]\n        model = build_model3C(in_dim=Fdim).to(device)\n        opt = torch.optim.Adam(model.parameters(), lr=1e-3)\n        crit = nn.CrossEntropyLoss()\n\n        best_va = 1e9\n        patience, bad = 6, 0\n        for epoch in range(60):\n            # train\n            model.train()\n            idxs = np.arange(len(Xtr))\n            rng.shuffle(idxs)\n            for s in range(0, len(idxs), 128):\n                j = idxs[s:s+128]\n                xb = torch.from_numpy(Xtr[j]).to(device)\n                yb = torch.from_numpy(Ytr[j]).to(device)\n                opt.zero_grad()\n                logits = model(xb)\n                loss = crit(logits, yb)\n                loss.backward(); opt.step()\n\n            # valid\n            model.eval()\n            with torch.no_grad():\n                xv = torch.from_numpy(Xva).to(device)\n                yv = torch.from_numpy(Yva).to(device)\n                lv = crit(model(xv), yv).item()\n            if lv < best_va:\n                best_va = lv; bad = 0\n                torch.save(model.state_dict(), M3C_FOLD_W.format(fi))\n            else:\n                bad += 1\n                if bad >= patience:\n                    break\n\n        # 若沒被早停存下，至少存最後一版\n        if not os.path.exists(M3C_FOLD_W.format(fi)):\n            torch.save(model.state_dict(), M3C_FOLD_W.format(fi))\n# =============== A 路：K-fold 訓練（Keras） ===============\ndef train_model3A_kfold(train_sequences, y):\n    # 1) 抽特徵\n    X_list = [fe_m3A(seq.to_pandas() if hasattr(seq, \"to_pandas\") else seq)\n              for seq in train_sequences]\n    lengths = [x.shape[0] for x in X_list]\n    pad_len = decide_pad_len(lengths, q=0.95)\n    save_json(M3A_PAD_JSON, {\"pad_len\": pad_len})\n\n    # 2) scaler 用全部樣本的 time-steps fit\n    scaler = StandardScaler()\n    scaler.fit(np.vstack(X_list))\n    joblib.dump(scaler, M3A_SCALER_PKL)\n\n    # 3) K-fold\n    folds = make_folds(np.asarray(y))\n\n    for fi, (tr_idx, va_idx) in enumerate(folds):\n        def pack(idxs):\n            mats = [scaler.transform(X_list[i]) for i in idxs]\n            X = pad_sequences(mats, maxlen=pad_len, padding=\"post\",\n                              truncating=\"post\", dtype=\"float32\")  # (N, pad_len, F)\n            Y = tf.keras.utils.to_categorical(np.asarray([y[i] for i in idxs]),\n                                              num_classes=NUM_CLASS)\n            return X, Y\n\n        Xtr, Ytr = pack(tr_idx)\n        Xva, Yva = pack(va_idx)\n\n        model = build_model3A((pad_len, Xtr.shape[-1]))\n        model.compile(optimizer=tf.keras.optimizers.Adam(1e-3),\n                      loss=\"categorical_crossentropy\",\n                      metrics=[\"accuracy\"])\n        cbs = [\n            tf.keras.callbacks.EarlyStopping(monitor=\"val_accuracy\",\n                                             patience=8, restore_best_weights=True),\n            tf.keras.callbacks.ReduceLROnPlateau(monitor=\"val_accuracy\",\n                                                 patience=4, factor=0.5),\n        ]\n        model.fit(Xtr, Ytr, validation_data=(Xva, Yva),\n                  epochs=60, batch_size=128, callbacks=cbs, verbose=2)\n\n        model.save(M3A_FOLD_W.format(fi))\n\n\n# =============== B 路：K-fold 訓練（Keras） ===============\ndef train_model3B_kfold(train_sequences, y):\n    # 1) 抽特徵\n    X_list = [fe_m3B(seq.to_pandas() if hasattr(seq, \"to_pandas\") else seq)\n              for seq in train_sequences]\n    lengths = [x.shape[0] for x in X_list]\n    pad_len = decide_pad_len(lengths, q=0.95)\n    save_json(M3B_PAD_JSON, {\"pad_len\": pad_len})\n\n    # 2) scaler\n    scaler = StandardScaler()\n    scaler.fit(np.vstack(X_list))\n    joblib.dump(scaler, M3B_SCALER_PKL)\n\n    # 3) K-fold\n    folds = make_folds(np.asarray(y))\n\n    for fi, (tr_idx, va_idx) in enumerate(folds):\n        def pack(idxs):\n            mats = [scaler.transform(X_list[i]) for i in idxs]\n            X = pad_sequences(mats, maxlen=pad_len, padding=\"post\",\n                              truncating=\"post\", dtype=\"float32\")\n            Y = tf.keras.utils.to_categorical(np.asarray([y[i] for i in idxs]),\n                                              num_classes=NUM_CLASS)\n            return X, Y\n\n        Xtr, Ytr = pack(tr_idx)\n        Xva, Yva = pack(va_idx)\n\n        model = build_model3B((pad_len, Xtr.shape[-1]))\n        model.compile(optimizer=tf.keras.optimizers.Adam(1e-3),\n                      loss=\"categorical_crossentropy\",\n                      metrics=[\"accuracy\"])\n        cbs = [\n            tf.keras.callbacks.EarlyStopping(monitor=\"val_accuracy\",\n                                             patience=8, restore_best_weights=True),\n            tf.keras.callbacks.ReduceLROnPlateau(monitor=\"val_accuracy\",\n                                                 patience=4, factor=0.5),\n        ]\n        model.fit(Xtr, Ytr, validation_data=(Xva, Yva),\n                  epochs=60, batch_size=128, callbacks=cbs, verbose=2)\n\n        model.save(M3B_FOLD_W.format(fi))\n\n\n# =============== 一鍵訓練（A/B/C 全部） ===============\ndef predict3_kfold(train_sequences, y):\n    \"\"\"\n    train_sequences: list[polars.DataFrame 或 pandas.DataFrame]\n    y             : list / np.array of int labels (0..NUM_CLASS-1)\n    \"\"\"\n    train_model3A_kfold(train_sequences, y)\n    train_model3B_kfold(train_sequences, y)\n    train_model3C_kfold(train_sequences, y)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(sequence, demographics):\n    import copy\n    pred0 = predict1(sequence, demographics)[0]\n    pred1 = predict2(sequence, demographics)[0]\n    pred2 = predict3(sequence, demographics)[0]\n    preds = []\n    main_wts = np.asarray([0.271, 0.347, 0.382])\n    correct_wts = [+0.0021, -0.0007, -0.0014]\n    asc_desc_wts = [0.70, 0.30]\n    for a,b,c in zip(pred0,pred1,pred2):\n        l_abc = [{ 'wts':main_wts[0], 'pred':a, 'n':'p0', 'result':0 }, { 'wts':main_wts[1], 'pred':b, 'n':'p1', 'result':0 }, { 'wts':main_wts[2], 'pred':c, 'n':'p2', 'result':0 }]\n        lps_asc = sorted(copy.deepcopy(l_abc), key=lambda _:_['pred'],reverse=False)\n        lps_desc = sorted(copy.deepcopy(l_abc), key=lambda _:_['pred'],reverse=True)\n        for asc,correct_wt in zip(lps_asc, correct_wts): asc ['wts'] += correct_wt\n        for desc,correct_wt in zip(lps_desc, correct_wts): desc['wts'] += correct_wt\n        for asc in lps_asc: asc ['result'] = asc ['pred'] * asc ['wts']\n        for desc in lps_desc: desc['result'] = desc['pred'] * desc['wts']\n        result_asc = sum([asc ['result'] for asc in lps_asc])\n        result_desc = sum([d['result'] for d in lps_desc])\n        result = result_asc * asc_desc_wts[0] + result_desc * asc_desc_wts[1]\n        preds.append(result)\n    avg_pred = np.asarray(preds)\n    return dataset.le.classes_[avg_pred.argmax()]\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Submit","metadata":{}},{"cell_type":"code","source":"import warnings\nwarnings.simplefilter(\"ignore\")\n\nimport kaggle_evaluation.cmi_inference_server\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    )\n\n\n\n\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}