{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.13"},"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":12723935,"sourceType":"datasetVersion","datasetId":7869970},{"sourceId":240649816,"sourceType":"kernelVersion"},{"sourceId":245926974,"sourceType":"kernelVersion"},{"sourceId":246893721,"sourceType":"kernelVersion"},{"sourceId":248244433,"sourceType":"kernelVersion"},{"sourceId":251413288,"sourceType":"kernelVersion"},{"sourceId":470587,"sourceType":"modelInstanceVersion","modelInstanceId":379625,"modelId":398856},{"sourceId":471764,"sourceType":"modelInstanceVersion","modelInstanceId":380358,"modelId":400086},{"sourceId":517084,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":407853,"modelId":400086}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":333.100766,"end_time":"2025-08-22T01:19:49.846142","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-08-22T01:14:16.745376","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Model_1","metadata":{"_cell_guid":"a4d23588-2c6d-4a6b-bff7-278e3792f557","_uuid":"35446c71-3c04-4ea7-bb72-fd2280265520","collapsed":false,"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.004518,"end_time":"2025-08-22T01:14:45.187934","exception":false,"start_time":"2025-08-22T01:14:45.183416","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import os, json, joblib, numpy as np, pandas as pd\nimport random\nfrom pathlib import Path\nimport warnings \nwarnings.filterwarnings(\"ignore\")\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom sklearn.utils.class_weight import compute_class_weight\n\nfrom tensorflow.keras.utils import Sequence, to_categorical, pad_sequences\nfrom tensorflow.keras.models import Model, load_model\nfrom tensorflow.keras.layers import (\n    Input, Conv1D, BatchNormalization, Activation, add, MaxPooling1D, Dropout,\n    Bidirectional, LSTM, GlobalAveragePooling1D, Dense, Multiply, Reshape,\n    Lambda, Concatenate, GRU, GaussianNoise\n)\nfrom tensorflow.keras.regularizers import l2\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import EarlyStopping\nfrom tensorflow.keras import backend as K\nimport tensorflow as tf\nimport polars as pl\nfrom sklearn.model_selection import StratifiedGroupKFold\nfrom scipy.spatial.transform import Rotation as R\n\ndef seed_everything(seed):\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    os.environ['TF_CUDNN_DETERMINISTIC'] = '1'\n    os.environ['TF_DETERMINISTIC_OPS'] = '1'\n\nseed_everything(seed=42)\n# (Competition metric will only be imported when TRAINing)\nTRAIN = False                     # ← set to True when you want to train\nRAW_DIR = Path(\"/kaggle/input/cmi-detect-behavior-with-sensor-data\")\nPRETRAINED_DIR = Path(\"/kaggle/input/cmi-d-111\")\nEXPORT_DIR = Path(\"./\")                                    # artefacts will be saved here\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\n#Tensor Manipulations\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\n# Residual CNN Block with SE\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# Normalizes and cleans the time series sequence. \n\ndef preprocess_sequence(df_seq: pd.DataFrame, feature_cols: list[str], scaler: StandardScaler):\n    mat = df_seq[feature_cols].ffill().bfill().fillna(0).values\n    return scaler.transform(mat).astype('float32')\n\n# MixUp the data argumentation in order to regularize the neural network. \n\nclass MixupGenerator(Sequence):\n    def __init__(self, X, y, batch_size, alpha=0.2):\n        self.X, self.y = X, y\n        self.batch = batch_size\n        self.alpha = alpha\n        self.indices = np.arange(len(X))\n    def __len__(self):\n        return int(np.ceil(len(self.X) / self.batch))\n    def __getitem__(self, i):\n        idx = self.indices[i*self.batch:(i+1)*self.batch]\n        Xb, yb = self.X[idx], self.y[idx]\n        lam = np.random.beta(self.alpha, self.alpha)\n        perm = np.random.permutation(len(Xb))\n        X_mix = lam * Xb + (1-lam) * Xb[perm]\n        y_mix = lam * yb + (1-lam) * yb[perm]\n        return X_mix, y_mix\n    def on_epoch_end(self):\n        np.random.shuffle(self.indices)\n\n\ndef remove_gravity_from_acc(acc_data, rot_data):\n\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\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\n    num_samples = acc_values.shape[0]\n    linear_accel = np.zeros_like(acc_values)\n    \n    gravity_world = np.array([0, 0, 9.81])\n\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\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             \n    return linear_accel\n\ndef calculate_angular_velocity_from_quat(rot_data, time_delta=1/200): # Assuming 200Hz sampling rate\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\n    num_samples = quat_values.shape[0]\n    angular_vel = np.zeros((num_samples, 3))\n\n    for i in range(num_samples - 1):\n        q_t = quat_values[i]\n        q_t_plus_dt = quat_values[i+1]\n\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\n        try:\n            rot_t = R.from_quat(q_t)\n            rot_t_plus_dt = R.from_quat(q_t_plus_dt)\n\n            # Calculate the relative rotation\n            delta_rot = rot_t.inv() * rot_t_plus_dt\n            \n            # Convert delta rotation to angular velocity vector\n            # The rotation vector (Euler axis * angle) scaled by 1/dt\n            # is a good approximation for small delta_rot\n            angular_vel[i, :] = delta_rot.as_rotvec() / time_delta\n        except ValueError:\n            # If quaternion is invalid, angular velocity remains zero\n            pass\n            \n    return angular_vel\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\n    num_samples = quat_values.shape[0]\n    angular_dist = np.zeros(num_samples)\n\n    for i in range(num_samples - 1):\n        q1 = quat_values[i]\n        q2 = quat_values[i+1]\n\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 # Или np.nan, в зависимости от желаемого поведения\n            continue\n        try:\n            # Преобразование кватернионов в объекты Rotation\n            r1 = R.from_quat(q1)\n            r2 = R.from_quat(q2)\n\n            # Вычисление углового расстояния: 2 * arccos(|real(p * q*)|)\n            # где p* - сопряженный кватернион q\n            # В scipy.spatial.transform.Rotation, r1.inv() * r2 дает относительное вращение.\n            # Угол этого относительного вращения - это и есть угловое расстояние.\n            relative_rotation = r1.inv() * r2\n            \n            # Угол rotation vector соответствует угловому расстоянию\n            # Норма rotation vector - это угол в радианах\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            \n    return angular_dist\n\ndef build_two_branch_model(pad_len, imu_dim, tof_dim, n_classes, wd=1e-4):\n    inp = Input(shape=(pad_len, imu_dim+tof_dim))\n    imu = Lambda(lambda t: t[:, :, :imu_dim])(inp)\n    tof = Lambda(lambda t: t[:, :, imu_dim:])(inp)\n\n    # IMU deep branch\n    x1 = residual_se_cnn_block(imu, 64, 3, drop=0.1, wd=wd)\n    x1 = residual_se_cnn_block(x1, 128, 5, drop=0.1, wd=wd)\n\n    # TOF/Thermal lighter branch\n    x2 = Conv1D(64, 3, padding='same', use_bias=False, kernel_regularizer=l2(wd))(tof)\n    x2 = BatchNormalization()(x2); x2 = Activation('relu')(x2)\n    x2 = MaxPooling1D(2)(x2); x2 = Dropout(0.2)(x2)\n    x2 = Conv1D(128, 3, padding='same', use_bias=False, kernel_regularizer=l2(wd))(x2)\n    x2 = BatchNormalization()(x2); x2 = Activation('relu')(x2)\n    x2 = MaxPooling1D(2)(x2); x2 = Dropout(0.2)(x2)\n\n    merged = Concatenate()([x1, x2])\n\n    xa = Bidirectional(LSTM(128, return_sequences=True, kernel_regularizer=l2(wd)))(merged)\n    xb = Bidirectional(GRU(128, return_sequences=True, kernel_regularizer=l2(wd)))(merged)\n    xc = GaussianNoise(0.09)(merged)\n    xc = Dense(16, activation='elu')(xc)\n    \n    x = Concatenate()([xa, xb, xc])\n    x = Dropout(0.4)(x)\n    x = attention_layer(x)\n\n    for units, drop in [(256, 0.5), (128, 0.3)]:\n        x = Dense(units, use_bias=False, kernel_regularizer=l2(wd))(x)\n        x = BatchNormalization()(x); x = Activation('relu')(x)\n        x = Dropout(drop)(x)\n\n    out = Dense(n_classes, activation='softmax', kernel_regularizer=l2(wd))(x)\n    return Model(inp, out)\n\ntmp_model = build_two_branch_model(127,7,325,18)\n\ncustom_objs = {\n    'time_sum': time_sum, 'squeeze_last_axis': squeeze_last_axis, 'expand_last_axis': expand_last_axis,\n    'se_block': se_block, 'residual_se_cnn_block': residual_se_cnn_block, 'attention_layer': attention_layer,\n}\n\n# ----------------------------------------------------------------- #\n# Load any Models\n# * is 2 Train Model Load\n# ----------------------------------------------------------------- #\n\nPRETRAINED_DIR = Path(\"/kaggle/input/cmi-d-111\")\nprint(\"▶ INFERENCE MODE 1,2 – loading artefacts from\", PRETRAINED_DIR)\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\")\ngesture_classes = np.load(PRETRAINED_DIR / \"gesture_classes.npy\", allow_pickle=True)\n\nmodels1 = []\nprint(f\"  Loading models for ensemble inference...\")\nfor fold in range(10):\n    model_path = f\"{PRETRAINED_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)\nprint(\"-\"*50)\n\nfor fold in range(10):\n    model_path = f\"{PRETRAINED_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)\nprint(\"-\"*50)\nprint(f\"[INFO]NumUseModels:{len(models1)}\")\n\n\nPRETRAINED_DIR = Path(\"/kaggle/input/n-splits-10\")\nprint(\"▶ INFERENCE MODE 3 – loading artefacts from\", PRETRAINED_DIR)\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\")\ngesture_classes = np.load(PRETRAINED_DIR / \"gesture_classes.npy\", allow_pickle=True)\nfor fold in range(10):\n    model_path = f\"{PRETRAINED_DIR}/gesture_model_fold_{fold}.h5\"\n    print(\">>>LoadModel>>>\",model_path)\n    model = load_model(model_path, compile=False, custom_objects=custom_objs)\n    models1.append(model)\nprint(\"-\"*50)\nprint(f\"[INFO]NumUseModels:{len(models1)}\")\n\nfor fold in range(10):\n    MODEL_DIR = \"/kaggle/input/cmi-data-tensorflow-train\"\n    \n    model_path = f\"{MODEL_DIR}/gesture_model_fold_{fold}.h5\"\n    print(\">>>LoadModel>>>\",model_path)\n    model = load_model(model_path, compile=False, custom_objects=custom_objs)\n    models1.append(model)\nprint(\"-\"*50)\nprint(f\"[INFO]NumUseModels:{len(models1)}\")","metadata":{"_cell_guid":"e28baf78-1483-43c8-99c6-79fa005c6ee0","_uuid":"ce7454ad-b4eb-4fac-9b37-9c3e580dd6cd","execution":{"iopub.status.busy":"2025-08-26T17:55:59.787434Z","iopub.execute_input":"2025-08-26T17:55:59.787716Z","iopub.status.idle":"2025-08-26T17:56:46.391792Z","shell.execute_reply.started":"2025-08-26T17:55:59.787694Z","shell.execute_reply":"2025-08-26T17:56:46.391088Z"},"papermill":{"duration":27.587683,"end_time":"2025-08-22T01:15:12.780054","exception":false,"start_time":"2025-08-22T01:14:45.192371","status":"completed"},"tags":[],"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"outputs":[],"execution_count":null},{"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    \n    all_preds = [model.predict(pad_input, verbose=0)[0] for model in models1]\n    avg_pred = np.median(all_preds, axis=0) \n    return avg_pred","metadata":{"execution":{"iopub.status.busy":"2025-08-26T17:56:46.393125Z","iopub.execute_input":"2025-08-26T17:56:46.393641Z","iopub.status.idle":"2025-08-26T17:56:46.403017Z","shell.execute_reply.started":"2025-08-26T17:56:46.393620Z","shell.execute_reply":"2025-08-26T17:56:46.402331Z"},"papermill":{"duration":0.016092,"end_time":"2025-08-22T01:15:12.802841","exception":false,"start_time":"2025-08-22T01:15:12.786749","status":"completed"},"tags":[],"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model_2","metadata":{"papermill":{"duration":0.005801,"end_time":"2025-08-22T01:15:12.814702","exception":false,"start_time":"2025-08-22T01:15:12.808901","status":"completed"},"tags":[]}},{"cell_type":"code","source":"\nimport os\nimport torch\nimport kagglehub\nfrom pathlib import Path\nfrom torch.utils.data import Dataset, DataLoader, Subset\nfrom tqdm.notebook import tqdm\nfrom torch.amp import autocast\nimport pandas as pd\nimport polars as pl\nimport numpy as np\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom scipy.spatial.transform import Rotation as R\nfrom sklearn.preprocessing import StandardScaler, MinMaxScaler\nfrom collections import defaultdict\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom sklearn.model_selection import StratifiedGroupKFold\n\ndef 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_velocity_from_quat(rot_data, time_delta=1/200): # Assuming 200Hz sampling rate\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\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\nclass 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.use_cols) & set(self.raw_columns)))\n        self.generate_dataset(df)\n\n    def init_feature_names(self, data_path):\n        self.target_gestures = [\n            'Above ear - pull hair',\n            'Cheek - pinch skin',\n            'Eyebrow - pull hair',\n            'Eyelash - pull hair',\n            'Forehead - pull hairline',\n            'Forehead - scratch',\n            'Neck - pinch skin',\n            'Neck - scratch',\n        ]\n        self.non_target_gestures = [\n            'Write name on leg',\n            'Wave hello',\n            'Glasses on/off',\n            'Text on phone',\n            'Write name in air',\n            'Feel around in tray and pull out an object',\n            'Scratch knee/leg skin',\n            'Pull air toward your face',\n            'Drink from bottle/cup',\n            'Pinch knee/leg skin'\n        ]\n\n        self.acc_features = ['acc_mag', 'acc_mag_jerk', 'linear_acc_mag', 'linear_acc_mag_jerk']\n        self.rot_features = ['rot_angle', 'rot_angle_vel', 'angular_vel_x', 'angular_vel_y', 'angular_vel_z', 'angular_distance']\n        self.old_imu_features = [\n            'acc_mag', 'rot_angle','acc_mag_jerk', 'rot_angle_vel',\n            'linear_acc_mag', 'linear_acc_mag_jerk',\n            'angular_vel_x', 'angular_vel_y', 'angular_vel_z', 'angular_distance'\n        ]\n\n        self.extra_imu_features = self.config.get(\"imu_feats\", [])\n        self.imu_features = self.extra_imu_features.copy()\n        if self.config.get(\"add_imu_feat_default\", True):\n            if self.config.get(\"old_imu_feat\", True):\n                self.imu_features.extend(self.old_imu_features)\n            else:\n                self.imu_features.extend(self.acc_features)\n                self.imu_features.extend(self.rot_features)\n        self.er1_fearues = [\"er_x\", \"er_y\", \"er_z\"]\n        self.er2_fearues = ['er_r_xy', 'er_r_xz', 'er_r_yz', 'er_c_xy', 'er_c_xz', 'er_c_yz']\n        self.er_fearues = self.er1_fearues + self.er2_fearues\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\n        self.raw_columns = pd.read_csv(data_path, nrows=0).columns.tolist()\n        self.imu_acc_cols_base = ['acc_x', 'acc_y', 'acc_z', 'linear_acc_x', 'linear_acc_y', 'linear_acc_z'] if self.config.get(\"add_raw_acc\", False) else ['linear_acc_x', 'linear_acc_y', 'linear_acc_z']\n        self.imu_rot_cols_base = ['rot_w', 'rot_x', 'rot_y', 'rot_z']\n        self.imu_cols_base = self.imu_acc_cols_base + self.imu_rot_cols_base\n        self.imu_cols = list()\n        self.imu_channel_keys = defaultdict(list)\n        if self.config.get(\"add_imu_base\", True): \n            self.imu_cols.extend(self.imu_cols_base)\n            self.imu_channel_keys[\"acc\"] = self.imu_acc_cols_base\n            self.imu_channel_keys[\"rot\"] = self.imu_rot_cols_base\n        if self.config.get(\"add_imu_feats\", True): \n            self.imu_cols.extend(self.imu_features)\n            if self.config.get(\"split_imu_feat\", False):\n                if self.config.get(\"old_imu_feat\", True):\n                    assert False, \"split_imu_feat=True and old_imu_feat=True not supported\"\n                self.imu_channel_keys[\"acc_feat\"] = self.acc_features\n                self.imu_channel_keys[\"rot_feat\"] = self.rot_features\n            else:\n                if self.config.get(\"old_imu_feat\", True):\n                    self.imu_channel_keys[\"other\"].extend(self.old_imu_features)\n                else:\n                    self.imu_channel_keys[\"other\"].extend(self.acc_features)\n                    self.imu_channel_keys[\"other\"].extend(self.rot_features)\n        if self.config.get(\"add_imu_er_feats\", False): \n            self.imu_cols.extend(self.er_fearues)\n            if self.config.get(\"split_imu_feat\", False):\n                self.imu_channel_keys[\"er1_feat\"] = self.er1_fearues\n                self.imu_channel_keys[\"er2_feat\"] = self.er2_fearues\n            else:\n                self.imu_channel_keys[\"other\"].extend(self.er1_fearues)\n                self.imu_channel_keys[\"other\"].extend(self.er2_fearues)\n        self.flip_imu_cols = [f\"{col}_flip\" for col in self.imu_cols]\n        self.imu_channel_keys = {k: sorted(v) for k, v in self.imu_channel_keys.items()}\n        self.thm_cols = [c for c in self.raw_columns if c.startswith('thm_')]\n        self.thm_channel_keys = {k: [f\"thm_{k}\"] for k in range(1, 6)}\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',\n                          'rot_x', 'rot_y', 'rot_z', 'rot_w',\n                          'sequence_id', 'subject', \n                          'sequence_type', 'gesture', 'orientation'] + [c for c in self.raw_columns if c.startswith('thm_')] + [f\"tof_{i}_v{p}\" for i in range(1, 6) for p in range(64)]\n        self.use_cols = self.base_cols + self.feature_cols\n        if self.config.get(\"return_flip_imu\", False):\n            self.use_cols.extend(self.flip_imu_cols)\n        self.fold_cols = ['subject', 'sequence_type', 'gesture', 'orientation', 'sequence_id']\n        if self.config.get(\"use_dg\", False):\n            self.dg_cols = ['adult_child', 'age', 'sex', 'handedness', 'shoulder_to_wrist_height', 'elbow_to_wrist_height']\n        self.global_imu_indices = {k: sorted([self.imu_cols.index(feat) for feat in feats]) for k, feats in self.imu_channel_keys.items()}\n        self.global_thm_indices = {k: sorted([self.thm_cols.index(key) for key in self.thm_channel_keys[k]]) for k in range(1, 6)}\n        self.global_tof_indices = {k: sorted([self.tof_cols.index(key) for key in self.tof_channel_keys[k]]) for k in range(1, 6)}\n            \n    def generate_tof_feature_names(self):\n        features = list()\n        self.tof_channel_keys = defaultdict(list)\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                self.tof_channel_keys[i].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                    self.tof_channel_keys[i].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                            self.tof_channel_keys[i].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                                self.tof_channel_keys[i].append(f'tof{mode}_{i}_region_{r}_{stat}')\n        return features\n\n    def compute_cross_axis_energy(self, df):\n        axes=['x', 'y', 'z']\n        features = {}\n        for axis in axes:\n            fft_result = fft(df[f'acc_{axis}'].values)\n            energy = np.sum(np.abs(fft_result)**2)\n            features[f\"er_{axis}\"] = energy\n        for i, axis1 in enumerate(axes):\n            for axis2 in axes[i+1:]:\n                features[f'er_r_{axis1}{axis2}'] = features[f'er_{axis1}'] / (features[f'er_{axis2}'] + 1e-6)\n        for i, axis1 in enumerate(axes):\n            for axis2 in axes[i+1:]:\n                features[f'er_c_{axis1}{axis2}'] = np.corrcoef(np.abs(fft(df[f'acc_{axis1}'].values)), np.abs(fft(df[f'acc_{axis2}'].values)))[0, 1]\n        return {k: v for k, v in features.items() if k in self.er_fearues}\n\n    def compute_imu_features(self, df):\n        if self.config.get(\"rot_fillna\", False):\n            df['rot_w'] = df['rot_w'].fillna(1)\n            df[['rot_x', 'rot_y', 'rot_z']] = df[['rot_x', 'rot_y', 'rot_z']].fillna(0)\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            \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    \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    \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        return df\n\n    def compute_flip_features(self, df):\n        flip_df = df[['sequence_id', 'acc_x', 'acc_y', 'acc_z', 'rot_x', 'rot_y', 'rot_z', 'rot_w']].copy()\n        flip_df[['acc_x', 'acc_y', 'rot_x', 'rot_y']] *= -1\n        flip_df = self.compute_imu_features(flip_df)\n        for col in flip_df.columns:\n            if col != 'sequence_id':\n                df[f\"{col}_flip\"] = flip_df[col]\n        return df\n\n    def compute_features(self, df):\n        df = self.compute_imu_features(df)\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({\n                    f'tof_{i}_mean': tof_data.mean(axis=1),\n                    f'tof_{i}_std': tof_data.std(axis=1),\n                    f'tof_{i}_min': tof_data.min(axis=1),\n                    f'tof_{i}_max': tof_data.max(axis=1)\n                })\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({\n                            f'tof{self.tof_mode}_{i}_region_{r}_mean': region_data.mean(axis=1),\n                            f'tof{self.tof_mode}_{i}_region_{r}_std': region_data.std(axis=1),\n                            f'tof{self.tof_mode}_{i}_region_{r}_min': region_data.min(axis=1),\n                            f'tof{self.tof_mode}_{i}_region_{r}_max': region_data.max(axis=1)\n                        })\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({\n                                f'tof{mode}_{i}_region_{r}_mean': region_data.mean(axis=1),\n                                f'tof{mode}_{i}_region_{r}_std': region_data.std(axis=1),\n                                f'tof{mode}_{i}_region_{r}_min': region_data.min(axis=1),\n                                f'tof{mode}_{i}_region_{r}_max': region_data.max(axis=1)\n                            })\n            df = pd.concat([df, pd.DataFrame(new_columns)], axis=1)\n            \n        def _calc_features(group):\n            return pd.DataFrame(self.compute_cross_axis_energy(group), index=[group.index[0]])\n        features_df = df.groupby('sequence_id', group_keys=False).apply(_calc_features)\n        df = df.join(features_df, how='left')\n        df[features_df.columns] = df.groupby('sequence_id')[features_df.columns].ffill()\n        \n        return df\n        \n    def generate_features(self, df):\n        self.le = LabelEncoder()\n        if self.config.get(\"one_neg\", False):\n            neg_other = \"Write name on leg\"\n            df['gesture'] = df['gesture'].apply(lambda x: x if x in self.target_gestures else neg_other)\n        df['gesture_int'] = self.le.fit_transform(df['gesture'])\n        self.class_num = len(self.le.classes_)\n        self.target_ints = np.array([self.le.classes_.tolist().index(name) for name in self.target_gestures])\n        self.non_target_ints = np.array([self.le.classes_.tolist().index(name) for name in self.non_target_gestures])\n        \n        if all(c in df.columns for c in self.feature_cols):\n            print(\"Features have precomputed, skip compute.\")\n        else:\n            print(\"Features not precomputed, do compute.\")\n            df = self.compute_features(df)\n\n        if self.config.get(\"return_flip_imu\", False):\n            if all(c in df.columns for c in self.flip_imu_cols):\n                print(\"Flip have precomputed, skip compute.\")\n            else:\n                print(\"Flip not precomputed, do compute.\")\n                df = self.compute_flip_features(df)\n\n        if self.config.get(\"use_dg\", False):\n            dg_df = pd.read_csv(self.config[\"dg_path\"])\n            df = pd.merge(df, dg_df, how='left', on='subject')\n            df['age'] /= 100\n            df['shoulder_to_wrist_height'] = df['shoulder_to_wrist_cm'] / df['height_cm']\n            df['elbow_to_wrist_height'] = df['elbow_to_wrist_cm'] / df['height_cm']\n        \n        if self.config.get(\"save_precompute\", False):\n            df.to_csv(self.config.get(\"save_filename\", \"train.csv\"))\n        return df\n\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\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\n    def get_nan_value(self, data, ratio):\n        max_value = data.max().max()\n        nan_value = -max_value * ratio\n        print(f\"Max: {max_value}, set nan to {nan_value}\")\n        return nan_value\n\n    def generate_dataset(self, df):\n        seq_gp = df.groupby('sequence_id') \n        imu_unscaled, thm_unscaled, tof_unscaled = list(), list(), list()\n        if self.config.get(\"return_flip_imu\", False): flip_imu_unscaled = list()\n        classes, lens = list(), list()\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        if self.config.get(\"use_dg\", False):\n            self.dg = list()\n\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\n            if self.config.get(\"return_flip_imu\", False):\n                flip_imu_data = seq_df[self.flip_imu_cols]\n                if self.config[\"fbfill\"][\"imu\"]:\n                    flip_imu_data = flip_imu_data.ffill().bfill()\n                flip_imu_unscaled.append(flip_imu_data.fillna(self.imu_nan_value).values.astype('float32'))\n\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\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            \n            classes.append(seq_df['gesture_int'].iloc[0])\n            lens.append(len(imu_data))\n\n            for col in self.fold_cols:\n                self.fold_feats[col].append(seq_df[col].iloc[0])\n\n            if self.config.get(\"use_dg\", False):\n                self.dg.append(seq_df[self.dg_cols].iloc[0].values.astype('float32'))\n            \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\n            if self.config.get(\"return_flip_imu\", False):\n                flip_x_unscaled = [np.concatenate([flip_imu, thm, tof], axis=1) for flip_imu, thm, tof in zip(flip_imu_unscaled, thm_unscaled, tof_unscaled)]\n                flip_x_scaled = [self.x_scaler.transform(x) for x in flip_x_unscaled]\n                flip_x = self.pad(flip_x_scaled, self.imu_cols+self.thm_cols+self.tof_cols)\n                self.flip_imu = flip_x[..., :self.imu_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\n            if self.config.get(\"return_flip_imu\", False):\n                flip_imu_scaled = [self.imu_scaler.transform(x) for x in flip_imu_unscaled]\n                self.flip_imu = self.pad(flip_imu_scaled, self.imu_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.binary_class_ = np.isin(np.array(classes), self.target_ints).astype(np.float32)\n        self.class_weight = torch.FloatTensor(compute_class_weight('balanced', classes=np.arange(len(self.le.classes_)), y=classes))\n\n    def precompute_scaled_nan_values(self):\n        dummy_df = pd.DataFrame(\n            np.array([[self.imu_nan_value]*len(self.imu_cols) + \n                     [self.thm_nan_value]*len(self.thm_cols) +\n                     [self.tof_nan_value]*len(self.tof_cols)]),\n            columns=self.imu_cols + self.thm_cols + self.tof_cols\n        )\n        \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\n    def get_scaled_nan_tensors(self, imu, thm, tof):\n        return torch.full(imu.shape, self.imu_scaled_nan, device=imu.device), \\\n            torch.full(thm.shape, self.thm_scaled_nan, device=thm.device), \\\n            torch.full(tof.shape, self.tof_scaled_nan, device=tof.device)\n\n    def inference_process(self, sequence, demographics=None, reverse=False):\n        if self.config.get(\"use_dg\", False):\n            assert demographics is not None, \"Demographics needed\"\n            df_dg = demographics.to_pandas().copy()\n            df_dg['age'] /= 100\n            df_dg['shoulder_to_wrist_height'] = df_dg['shoulder_to_wrist_cm'] / df_dg['height_cm']\n            df_dg['elbow_to_wrist_height'] = df_dg['elbow_to_wrist_cm'] / df_dg['height_cm']\n        df_seq = sequence.to_pandas().copy()\n        if reverse:\n            df_seq[['acc_x', 'acc_y', 'rot_x', 'rot_y']] *= -1\n        if self.config.get(\"rot_fillna\", False):\n            df_seq['rot_w'] = df_seq['rot_w'].fillna(1)\n            df_seq[['rot_x', 'rot_y', 'rot_z']] = df_seq[['rot_x', 'rot_y', 'rot_z']].fillna(0)\n        if not all(c in df_seq.columns for c in self.imu_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(\n                    df_seq[['acc_x', 'acc_y', 'acc_z']], \n                    df_seq[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n                )\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\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({\n                    f'tof_{i}_mean': tof_data.mean(axis=1),\n                    f'tof_{i}_std': tof_data.std(axis=1),\n                    f'tof_{i}_min': tof_data.min(axis=1),\n                    f'tof_{i}_max': tof_data.max(axis=1)\n                })\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({\n                            f'tof{self.tof_mode}_{i}_region_{r}_mean': region_data.mean(axis=1),\n                            f'tof{self.tof_mode}_{i}_region_{r}_std': region_data.std(axis=1),\n                            f'tof{self.tof_mode}_{i}_region_{r}_min': region_data.min(axis=1),\n                            f'tof{self.tof_mode}_{i}_region_{r}_max': region_data.max(axis=1)\n                        })\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({\n                                f'tof{mode}_{i}_region_{r}_mean': region_data.mean(axis=1),\n                                f'tof{mode}_{i}_region_{r}_std': region_data.std(axis=1),\n                                f'tof{mode}_{i}_region_{r}_min': region_data.min(axis=1),\n                                f'tof{mode}_{i}_region_{r}_max': region_data.max(axis=1)\n                            })\n            df_seq = pd.concat([df_seq, pd.DataFrame(new_columns)], axis=1)\n        \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\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\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        \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\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\n        ret = [torch.from_numpy(imu).float().unsqueeze(0), torch.from_numpy(thm).float().unsqueeze(0), torch.from_numpy(tof).float().unsqueeze(0)]\n        if self.config.get(\"use_dg\", False):\n            dg = df_dg[self.dg_cols].values.astype('float32')\n            ret.append(torch.from_numpy(dg).float())\n        return ret\n\n    def split5(self, imu, thm, tof):\n        imus = [imu[:, :, self.global_imu_indices[k]] for k in self.global_imu_indices]\n        thms = [thm[:, :, self.global_thm_indices[k]] for k in range(1, 6)]\n        tofs = [tof[:, :, self.global_tof_indices[k]] for k in range(1, 6)]\n        return imus, thms, tofs\n\n    def slide(self, imu, thm, tof, ratio=1.0):\n        def slide_tensor(tensor, nan_value, ratio):\n            b, l, d = tensor.shape\n            length = int(l * ratio)\n            if length > l:\n                pad = torch.full((b, length-l, d), nan_value, device=tensor.device)\n                tensor = torch.cat([tensor, pad], dim=1)\n            elif length < l:\n                tensor = tensor[:, :length, :] \n            return tensor\n        return slide_tensor(imu, self.imu_scaled_nan, ratio), slide_tensor(thm, self.thm_scaled_nan, ratio), slide_tensor(tof, self.tof_scaled_nan, ratio)\n\n    def __getitem__(self, idx):\n        ret = [self.imu[idx], self.thm[idx], self.tof[idx], self.class_[idx], self.binary_class_[idx]]\n        if self.config.get(\"return_extra\", False):\n            fold_feat_info = [self.fold_feats[col][idx] for col in self.fold_cols]\n            ret.append((idx, fold_feat_info))\n        if self.config.get(\"use_dg\", False):\n            ret.append(self.dg[idx])\n        if self.config.get(\"return_flip_imu\", False):\n            ret.append(self.flip_imu[idx])\n        return ret\n\n    def __len__(self):\n        return len(self.class_)\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.sgkf = StratifiedGroupKFold(n_splits=n_folds, shuffle=True, random_state=random_seed)\n        self.fold_y = np.array(self.full_dataset.fold_feats[config.get(\"fold_y\", \"sequence_type\")])\n        self.fold_groups = np.array(self.full_dataset.fold_feats[config.get(\"fold_groups\", \"subject\")])\n        self.folds = list(self.sgkf.split(X=np.arange(len(self.full_dataset)), y=self.fold_y, groups=self.fold_groups))\n        self.exclude_subjects = set(config.get(\"exclude_subjects\", []))\n    \n    def get_fold_datasets(self, fold_idx):\n        if self.folds is None or fold_idx >= self.n_folds: return None, None\n        fold_train_idx, fold_valid_idx = self.folds[fold_idx]\n        subjects = np.array(self.full_dataset.fold_feats[\"subject\"])\n        train_subjects, valid_subjects = subjects[fold_train_idx], subjects[fold_valid_idx]\n        train_mask, valid_mask = ~np.isin(train_subjects, list(self.exclude_subjects)), ~np.isin(valid_subjects, list(self.exclude_subjects))\n        return Subset(self.full_dataset, np.array(fold_train_idx)[train_mask].tolist()), Subset(self.full_dataset, np.array(fold_valid_idx)[valid_mask].tolist())\n\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: 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        \n        print(\"\\n交叉验证折叠统计:\")\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\"{'类别':<50} {'训练集':<10} {'验证集':<10}\")\n            for name in self.class_names:\n                print(f\"{name:<50} {train_counts[name]:<10} {valid_counts[name]:<10}\")\n\n        for fold_idx, (train_idx, val_idx) in enumerate(self.folds):\n            train_subjects = set(self.fold_groups[train_idx])\n            val_subjects = set(self.fold_groups[val_idx])\n            print(f\"\\nFold {fold_idx + 1}:\")\n            print(\"训练集受试者:\", train_subjects)\n            print(\"验证集受试者:\", val_subjects)\n\n        self.print_filtered_stats()\n\n    def print_filtered_stats(self):\n        original_counts = defaultdict(int)\n        filtered_counts = defaultdict(int)\n        \n        for fold_idx in range(self.n_folds):\n            train_idx, val_idx = self.folds[fold_idx]\n            for idx in train_idx:\n                original_counts['train'] += 1\n            for idx in val_idx:\n                original_counts['valid'] += 1\n            train_set, val_set = self.get_fold_datasets(fold_idx)\n            filtered_counts['train'] += len(train_set)\n            filtered_counts['valid'] += len(val_set)\n        \n        print(f\"\\n排除subject {self.exclude_subjects} 后的数据量变化:\")\n        print(f\"原始训练集样本: {original_counts['train']}\")\n        print(f\"过滤后训练集样本: {filtered_counts['train']}\")\n        print(f\"原始验证集样本: {original_counts['valid']}\") \n        print(f\"过滤后验证集样本: {filtered_counts['valid']}\")\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        # x: (B, C, L)\n        se = F.adaptive_avg_pool1d(x, 1).squeeze(-1)      # -> (B, C)\n        se = F.relu(self.fc1(se), inplace=True)          # -> (B, C//r)\n        se = self.sigmoid(self.fc2(se)).unsqueeze(-1)    # -> (B, C, 1)\n        return x * se                \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,\n                               kernel_size=3, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm1d(out_channels)\n        self.conv2 = nn.Conv1d(out_channels, out_channels,\n                               kernel_size=3, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm1d(out_channels)\n        # SE\n        self.se = SEBlock(out_channels)\n        \n        if in_channels != out_channels:\n            self.shortcut = nn.Sequential(\n                nn.Conv1d(in_channels, out_channels, kernel_size=1,\n                          padding=0, bias=False),\n                nn.BatchNorm1d(out_channels)\n            )\n        else:\n            self.shortcut = nn.Identity()\n\n        self.relu = nn.ReLU(inplace=True)\n\n    def forward(self, x) :\n        identity = self.shortcut(x)              # (B, out, L)\n        out = self.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        out = self.se(out)                       # (B, out, L)\n        out = out + identity\n        return self.relu(out)\n\nclass AttentionLayer(nn.Module):\n    def __init__(self, feature_dim):\n        super().__init__()\n        self.score_fn = nn.Linear(feature_dim, 1, bias=True)\n        self.softmax = nn.Softmax(dim=1)\n\n    def forward(self, x):\n        # x: (B, L, F)\n        score = torch.tanh(self.score_fn(x))     # (B, L, 1)\n        weights = self.softmax(score.squeeze(-1))# (B, L)\n        weights = weights.unsqueeze(-1)          # (B, L, 1)\n        context = x * weights                    # (B, L, F)\n        return context.sum(dim=1)                # (B, F)\n\nclass GaussianNoise(nn.Module):\n    \"\"\"Add Gaussian noise to input tensor\"\"\"\n    def __init__(self, stddev):\n        super().__init__()\n        self.stddev = stddev\n    \n    def forward(self, x):\n        if self.training:\n            noise = torch.randn_like(x) * self.stddev\n            return x + noise\n        return x\nclass CMIBackbone(nn.Module):\n    def __init__(self, imu_dim, thm_dim, tof_dim, **kwargs):\n        super().__init__()\n        self.imu_acc_branch = nn.Sequential(\n            self.residual_feature_block(3, kwargs[\"imu1_channels\"], kwargs[\"imu1_layers\"], drop=kwargs[\"imu1_dropout\"]),\n            self.residual_feature_block(kwargs[\"imu1_channels\"], kwargs[\"imu2_channels\"], kwargs[\"imu2_layers\"], drop=kwargs[\"imu2_dropout\"])\n        )\n        self.imu_rot_branch = nn.Sequential(\n            self.residual_feature_block(4, kwargs[\"imu1_channels\"], kwargs[\"imu1_layers\"], drop=kwargs[\"imu1_dropout\"]),\n            self.residual_feature_block(kwargs[\"imu1_channels\"], kwargs[\"imu2_channels\"], kwargs[\"imu2_layers\"], drop=kwargs[\"imu2_dropout\"])\n        )\n        self.imu_other_branch = nn.Sequential(\n            self.residual_feature_block(imu_dim-7, kwargs[\"imu1_channels\"], kwargs[\"imu1_layers\"], drop=kwargs[\"imu1_dropout\"]),\n            self.residual_feature_block(kwargs[\"imu1_channels\"], kwargs[\"imu2_channels\"], kwargs[\"imu2_layers\"], drop=kwargs[\"imu2_dropout\"])\n        )\n\n        self.thm_branch1, self.tof_branch1 = self.init_thm_tof_branch(thm_dim//5, tof_dim//5, **kwargs)\n        self.thm_branch2, self.tof_branch2 = self.init_thm_tof_branch(thm_dim//5, tof_dim//5, **kwargs)\n        self.thm_branch3, self.tof_branch3 = self.init_thm_tof_branch(thm_dim//5, tof_dim//5, **kwargs)\n        self.thm_branch4, self.tof_branch4 = self.init_thm_tof_branch(thm_dim//5, tof_dim//5, **kwargs)\n        self.thm_branch5, self.tof_branch5 = self.init_thm_tof_branch(thm_dim//5, tof_dim//5, **kwargs)\n\n        self.imu_proj = ResNetSEBlock(in_channels=3*kwargs[\"imu2_channels\"], out_channels=kwargs[\"imu2_channels\"])\n        self.thm_proj = ResNetSEBlock(in_channels=5*kwargs[\"thm2_channels\"], out_channels=kwargs[\"thm2_channels\"])\n        self.tof_proj = ResNetSEBlock(in_channels=5*kwargs[\"tof2_channels\"], out_channels=kwargs[\"tof2_channels\"])\n\n        self.lstm = nn.LSTM(\n            input_size=kwargs['imu2_channels']+kwargs['thm2_channels']+kwargs['tof2_channels'],\n            hidden_size=kwargs['lstm_hidden_size'],\n            num_layers=1,\n            batch_first=True,\n            bidirectional=True\n        )\n        self.gru = nn.GRU(\n            input_size=kwargs['imu2_channels']+kwargs['thm2_channels']+kwargs['tof2_channels'],\n            hidden_size=kwargs['gru_hidden_size'],\n            num_layers=1,\n            batch_first=True,\n            bidirectional=True\n        )\n        \n        self.noise = GaussianNoise(kwargs['gaussian_noise_rate'])\n        self.dense = nn.Sequential(\n            nn.Linear(kwargs['imu2_channels']+kwargs['thm2_channels']+kwargs['tof2_channels'], kwargs['dense_channels']),\n            nn.ELU()\n        )\n        \n        self.attn = AttentionLayer(feature_dim=(kwargs['lstm_hidden_size']+kwargs['gru_hidden_size'])*2+kwargs['dense_channels'])  # lstm + gru + dense\n\n    def feature_block(self, in_channels, out_channels, num_layers, pool_size=2, drop=0.3):\n        return nn.Sequential(\n            *[ResNetSEBlock(in_channels=in_channels, out_channels=in_channels) for i in range(num_layers)],\n            nn.Conv1d(in_channels, out_channels, kernel_size=3, padding=1, bias=False),\n            nn.BatchNorm1d(out_channels),\n            nn.ReLU(inplace=True),\n            nn.MaxPool1d(pool_size, ceil_mode=True),\n            nn.Dropout(drop)\n        )\n\n    def residual_feature_block(self, in_channels, out_channels, num_layers, pool_size=2, drop=0.3):\n        return nn.Sequential(\n            *[ResNetSEBlock(in_channels=in_channels, out_channels=in_channels) for i in range(num_layers)],\n            ResNetSEBlock(in_channels, out_channels, wd=1e-4),\n            nn.MaxPool1d(pool_size, ceil_mode=True),\n            nn.Dropout(drop)\n        )\n\n    def init_thm_tof_branch(self, thm_dim, tof_dim, **kwargs):\n        thm_branch = nn.Sequential(\n            self.feature_block(thm_dim, kwargs[\"thm1_channels\"], kwargs[\"thm1_layers\"], drop=kwargs[\"thm1_dropout\"]),\n            self.feature_block(kwargs[\"thm1_channels\"], kwargs[\"thm2_channels\"], kwargs[\"thm2_layers\"], drop=kwargs[\"thm2_dropout\"]),\n        )\n        tof_branch = nn.Sequential(\n            self.feature_block(tof_dim, kwargs[\"tof1_channels\"], kwargs[\"tof1_layers\"], drop=kwargs[\"tof1_dropout\"]),\n            self.feature_block(kwargs[\"tof1_channels\"], kwargs[\"tof2_channels\"], kwargs[\"tof2_layers\"], drop=kwargs[\"tof2_dropout\"]),\n        )\n        return thm_branch, tof_branch\n    \n    def forward(self, imus, thms, tofs):\n        imu_acc, imu_rot, imu_other = imus\n        imu_acc_feat = self.imu_acc_branch(imu_acc.permute(0, 2, 1))\n        imu_rot_feat = self.imu_rot_branch(imu_rot.permute(0, 2, 1))\n        imu_other_feat = self.imu_other_branch(imu_other.permute(0, 2, 1))\n        imu_feat = self.imu_proj(torch.cat([imu_acc_feat, imu_rot_feat, imu_other_feat], dim=1))\n        \n        thm1, thm2, thm3, thm4, thm5 = thms\n        tof1, tof2, tof3, tof4, tof5 = tofs\n        \n        thm1_feat = self.thm_branch1(thm1.permute(0, 2, 1))\n        thm2_feat = self.thm_branch2(thm2.permute(0, 2, 1))\n        thm3_feat = self.thm_branch3(thm3.permute(0, 2, 1))\n        thm4_feat = self.thm_branch4(thm4.permute(0, 2, 1))\n        thm5_feat = self.thm_branch5(thm5.permute(0, 2, 1))\n        thm_feat = self.thm_proj(torch.cat([thm1_feat, thm2_feat, thm3_feat, thm4_feat, thm5_feat], dim=1))\n        \n        tof1_feat = self.tof_branch1(tof1.permute(0, 2, 1))\n        tof2_feat = self.tof_branch2(tof2.permute(0, 2, 1))\n        tof3_feat = self.tof_branch3(tof3.permute(0, 2, 1))\n        tof4_feat = self.tof_branch4(tof4.permute(0, 2, 1))\n        tof5_feat = self.tof_branch5(tof5.permute(0, 2, 1))\n        tof_feat = self.tof_proj(torch.cat([tof1_feat, tof2_feat, tof3_feat, tof4_feat, tof5_feat], dim=1))\n        \n        feat = torch.cat([imu_feat, thm_feat, tof_feat], dim=1).permute(0, 2, 1)\n        lstm_out, _ = self.lstm(feat)\n        gru_out, _ = self.gru(feat)\n        dense_out = self.dense(self.noise(feat))\n        \n        return self.attn(torch.cat([lstm_out, gru_out, dense_out], dim=-1))\n\nCUDA0 = \"cuda:0\"\nseed = 0\nbatch_size = 64\nnum_workers = 4\nn_folds = 5\n\nroot_dir = Path(\"/kaggle/input/cmi-detect-behavior-with-sensor-data\")\nuniverse_csv_path = Path(\"/kaggle/input/cmi-precompute/pytorch/all/1/tof-1_raw.csv\")\n\nimu_only = False\n\ndeterministic = kagglehub.package_import('wasupandceacar/deterministic').deterministic\ndeterministic.init_all(seed)\n\n\ndef init_dataset():\n    dataset_config = {\n        \"percent\": 99,\n        \"scaler_config\": StandardScaler(),\n        \"nan_ratio\": {\n            \"imu\": 0,\n            \"thm\": 0,\n            \"tof\": 0,\n        },\n        \"fbfill\": {\n            \"imu\": True,\n            \"thm\": True,\n            \"tof\": True,\n        },\n        \"one_scale\": False,\n        \"tof_raw\": True,\n        \"tof_mode\": 16,\n        \"save_precompute\": False,\n        \"fold_y\": \"gesture\",\n        \"fold_groups\": \"subject\",\n    }\n\n    dataset = CMIFoldDataset(universe_csv_path, dataset_config, full_dataset_function=CMIFeDataset, n_folds=n_folds, random_seed=seed)\n    dataset.print_fold_stats()\n    return dataset\n\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\n\ndataset = init_dataset()\n\nclass CMIModel(nn.Module):\n    def __init__(self, target_classes_num, non_target_classes_num, **kwargs):\n        super().__init__()\n        self.backbone = CMIBackbone(dataset.imu_dim, dataset.thm_dim, dataset.tof_dim, **kwargs)\n        self.target_classifier = nn.Sequential(\n            nn.Linear((kwargs['lstm_hidden_size']+kwargs['gru_hidden_size'])*2+kwargs['dense_channels'], kwargs[\"cls_channels1\"]),\n            nn.BatchNorm1d(kwargs[\"cls_channels1\"]),\n            nn.ReLU(),\n            nn.Dropout(kwargs[\"cls_dropout1\"]),\n            nn.Linear(kwargs[\"cls_channels1\"], kwargs[\"cls_channels2\"]),\n            nn.BatchNorm1d(kwargs[\"cls_channels2\"]),\n            nn.ReLU(),\n            nn.Dropout(kwargs[\"cls_dropout2\"]),\n            nn.Linear(kwargs[\"cls_channels2\"], target_classes_num)\n        )\n        self.non_target_classifier = nn.Sequential(\n            nn.Linear((kwargs['lstm_hidden_size']+kwargs['gru_hidden_size'])*2+kwargs['dense_channels'], kwargs[\"cls_channels1\"]),\n            nn.BatchNorm1d(kwargs[\"cls_channels1\"]),\n            nn.ReLU(),\n            nn.Dropout(kwargs[\"cls_dropout1\"]),\n            nn.Linear(kwargs[\"cls_channels1\"], kwargs[\"cls_channels2\"]),\n            nn.BatchNorm1d(kwargs[\"cls_channels2\"]),\n            nn.ReLU(),\n            nn.Dropout(kwargs[\"cls_dropout2\"]),\n            nn.Linear(kwargs[\"cls_channels2\"], non_target_classes_num)\n        )\n    \n    def forward(self, imu, thm, tof):\n        feat = self.backbone(imu, thm, tof)\n        targets_y = self.target_classifier(feat)\n        non_targets_y = self.non_target_classifier(feat)\n        return torch.cat([targets_y, non_targets_y], dim=1)\n\nmodel_function = CMIModel\nmodel_args = {\"imu1_channels\": 128, \"imu2_channels\": 256, \"imu1_dropout\": 0.3, \"imu2_dropout\": 0.25,\n              \"imu1_layers\": 0, \"imu2_layers\": 0, \n              \"thm1_channels\": 32, \"thm2_channels\": 64, \"thm1_dropout\": 0.25, \"thm2_dropout\": 0.2,\n              \"thm1_layers\": 0, \"thm2_layers\": 0, \n              \"tof1_channels\": 256, \"tof2_channels\": 512, \"tof1_dropout\": 0.4, \"tof2_dropout\": 0.3,\n              \"tof1_layers\": 0, \"tof2_layers\": 0, \n              \"lstm_hidden_size\": 128, \"gru_hidden_size\": 128, \"gaussian_noise_rate\": 0.1, \"dense_channels\": 32,\n              \"cls_channels1\": 256, \"cls_dropout1\": 0.2, \"cls_channels2\": 128, \"cls_dropout2\": 0.2,\n              \"target_classes_num\": 8, \"non_target_classes_num\": 10,}\nmodel_dir = Path(\"/kaggle/input/cmi-models-public/pytorch/base04/1\")\n\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    } for fold in range(n_folds)\n]\n\ndef replace(k):\n    k = k.replace(\"_orig_mod.\", \"\")\n    return k\n\nmodels2 = list()\nfor model_dict in model_dicts:\n    model_function = model_dict[\"model_function\"]\n    model_args = model_dict[\"model_args\"]\n    model_path = model_dict[\"model_path\"]\n    model = model_function(**model_args).to(CUDA0)\n    state_dict = {replace(k): v for k,v in torch.load(model_path).items()}\n    model.load_state_dict(state_dict)\n    model = model.eval()\n    models2.append(model)\n\nmetric_package = kagglehub.package_import('wasupandceacar/cmi-metric')\n\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 inference(model, imu, thm, tof):\n    imus, thms, tofs = dataset.full_dataset.split5(imu, thm, tof)\n    with autocast(device_type='cuda', dtype=torch.bfloat16):\n        pred_y = model(imus, thms, tofs)\n    return pred_y\n\ndef valid(model, valid_bar):\n    with torch.no_grad():\n        for imu, thm, tof, y, b in valid_bar:\n            imu, thm, tof, y = to_cuda(imu, thm, tof, y)\n            pred_y = inference(model, imu, thm, tof)\n            metric.add(dataset.le.classes_[y.argmax(dim=1).cpu()], dataset.le.classes_[pred_y.argmax(dim=1).cpu()])\n            _, thm, tof = dataset.full_dataset.get_scaled_nan_tensors(imu, thm, tof)\n            pred_y = inference(model, imu, thm, tof)\n            imu_only_metric.add(dataset.le.classes_[y.argmax(dim=1).cpu()], dataset.le.classes_[pred_y.argmax(dim=1).cpu()])\n\n\ndef avg_predict(models, imu, thm, tof):\n    outputs = []\n    with autocast(device_type='cuda', dtype=torch.bfloat16):\n        for model in models:\n            pred_y = inference(model, imu, thm, tof)\n            outputs.append(pred_y)\n    return torch.mean(torch.stack(outputs), dim=0)","metadata":{"_cell_guid":"80dac13f-ca07-4786-b31d-0f0c72694ebe","_uuid":"8e6b8a61-6c7d-4277-9465-f4b25b02c4f1","execution":{"iopub.status.busy":"2025-08-26T17:57:24.343314Z","iopub.execute_input":"2025-08-26T17:57:24.344256Z","iopub.status.idle":"2025-08-26T17:59:58.675759Z","shell.execute_reply.started":"2025-08-26T17:57:24.344227Z","shell.execute_reply":"2025-08-26T17:59:58.675141Z"},"papermill":{"duration":203.227592,"end_time":"2025-08-22T01:18:36.048379","exception":false,"start_time":"2025-08-22T01:15:12.820787","status":"completed"},"tags":[],"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def 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        # if imu_only:\n        #     _, thm, tof = dataset.full_dataset.get_scaled_nan_tensors(imu, thm, tof)\n        logits = avg_predict(models2, imu, thm, tof)\n        probabilities = F.softmax(logits, dim=1).float().cpu().numpy()\n    return probabilities","metadata":{"_cell_guid":"4ec7ba83-bd38-4944-acc5-c569d0b22991","_uuid":"9ee5932b-ffff-47c4-b5b3-abb1efdfab5c","execution":{"iopub.status.busy":"2025-08-26T18:03:43.834964Z","iopub.execute_input":"2025-08-26T18:03:43.835260Z","iopub.status.idle":"2025-08-26T18:03:43.840392Z","shell.execute_reply.started":"2025-08-26T18:03:43.835237Z","shell.execute_reply":"2025-08-26T18:03:43.839589Z"},"papermill":{"duration":0.013379,"end_time":"2025-08-22T01:18:36.069116","exception":false,"start_time":"2025-08-22T01:18:36.055737","status":"completed"},"tags":[],"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model_3","metadata":{"_cell_guid":"395b6b7b-0575-4570-9835-c8f7598b30cf","_uuid":"12e1abaa-c410-43c2-9d09-cb25e7a3a6c0","collapsed":false,"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.00609,"end_time":"2025-08-22T01:18:36.081519","exception":false,"start_time":"2025-08-22T01:18:36.075429","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import os\nimport json\nimport joblib\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nimport warnings\nimport random\nimport math\nimport matplotlib.pyplot as plt\nimport polars as pl\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, GRU, GlobalAveragePooling1D, Dense, Multiply, Reshape,\n    Lambda, Concatenate\n)\nfrom tensorflow.keras.optimizers import Adam as AdamTF\nfrom tensorflow.keras.regularizers import l2\nfrom tensorflow.keras.utils import Sequence, to_categorical, pad_sequences\nfrom tensorflow.keras.callbacks import EarlyStopping\nfrom tensorflow.keras.optimizers.schedules import CosineDecay\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.optim import Adam as AdamTorch\nfrom torch.optim.lr_scheduler import CosineAnnealingWarmRestarts\n\nfrom sklearn.model_selection import StratifiedGroupKFold\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom scipy.spatial.transform import Rotation as R\nfrom scipy.signal import firwin\n\n# 評価メトリクスはローカル検証/学習時にのみインポート\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    実行環境の乱数シードを統一的に設定する関数。\n    \"\"\"\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    random.seed(seed)\n    np.random.seed(2025)\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    # torch.backends.cudnn.deterministic = True # パフォーマンスが低下する可能性があるためコメントアウト\n    # torch.backends.cudnn.benchmark = False\n\nseed_everything(seed=42)\nwarnings.filterwarnings(\"ignore\")\n\nTRAIN = False\n\n# --- パス設定 ---\nRAW_DIR = Path(\"/kaggle/input/cmi-detect-behavior-with-sensor-data\")\n# YOUR_MODELS_DIRは自分の学習済みモデルが格納されているKaggleデータセットのパスに設定してください\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(\"./\") # 学習済みモデルやアーティファクトの保存先\n\n# --- モデル学習ハイパーパラメータ ---\nBATCH_SIZE = 64          # バッチサイズ\nPAD_PERCENTILE = 95      # シーケンス長のパディングを決めるためのパーセンタイル値\nLR_INIT = 4e-4           # 学習率の初期値 (微調整)\nWD = 3e-3                # Weight Decay（L2正則化）の係数\nMIXUP_ALPHA = 0.4        # Mixupのα値\nEPOCHS = 360             # 最大エポック数 (増加)\nPATIENCE = 50            # EarlyStoppingのpatience (増加)\nN_SPLITS = 10             # クロスバリデーションの分割数\nMASKING_PROB = 0.25      # 学習時にTOF/THMデータをマスクする確率\nGATE_LOSS_WEIGHT = 0.2   # Gatedモデルのゲート損失に対する重み\n\nprint(f\"▶ ライブラリのインポート完了\")\nprint(f\"  - TensorFlow: {tf.__version__}\")\nprint(f\"  - PyTorch: {torch.__version__}\")\nprint(f\"▶ TRAINモード: {TRAIN}\")\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\n# PyTorchモデル用の標準化パラメータ\nmean_pt = torch.tensor([\n    0, 0, 0, 0, 0, 0, 9.0319e-03, 1.0849e+00, -2.6186e-03, 3.7651e-03,\n    -5.3660e-03, -2.8177e-03, 1.3318e-03, -1.5876e-04, 6.3495e-01,\n    6.2877e-01, 6.0607e-01, 6.2142e-01, 6.3808e-01, 6.5420e-01,\n    7.4102e-03, -3.4159e-03, -7.5237e-03, -2.6034e-02, 2.9704e-02,\n    -3.1546e-02, -2.0610e-03, -4.6986e-03, -4.7216e-03, -2.6281e-02,\n    1.5799e-02, 1.0016e-02\n], dtype=torch.float32).view(1, -1, 1).to(device)\n\nstd_pt = torch.tensor([\n    1, 1, 1, 1, 1, 1, 0.2067, 0.8583, 0.3162,\n    0.2668, 0.2917, 0.2341, 0.3023, 0.3281, 1.0264, 0.8838, 0.8686, 1.0973,\n    1.0267, 0.9018, 0.4658, 0.2009, 0.2057, 1.2240, 0.9535, 0.6655, 0.2941,\n    0.3421, 0.8156, 0.6565, 1.1034, 1.5577\n], dtype=torch.float32).view(1, -1, 1).to(device) + 1e-8\n\nclass ImuFeatureExtractor(nn.Module):\n    \"\"\"\n    ★★★ PyTorchモデル用の特徴量抽出器 ★★★\n    公開モデルの重みと一致させるため、元の正しい定義に修正。\n    \"\"\"\n    def __init__(self, fs=100., add_quaternion=False):\n        super().__init__()\n        self.fs = fs\n        self.add_quaternion = add_quaternion\n\n        k = 15\n\n        # ▼▼▼【ここが修正点】▼▼▼\n        # 公開モデルの重みファイルに存在する 'self.lpf' 層を再度追加する\n        self.lpf = nn.Conv1d(6, 6, kernel_size=k, padding=k//2,\n                                 groups=6, bias=False)\n        nn.init.kaiming_uniform_(self.lpf.weight, a=math.sqrt(5))\n        # ▲▲▲【ここまでが修正点】▲▲▲\n\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\n    def forward(self, imu):\n        acc  = imu[:, 0:3, :]\n        gyro = imu[:, 3:6, :]\n\n        # 1) magnitude\n        acc_mag  = torch.norm(acc,  dim=1, keepdim=True)\n        gyro_mag = torch.norm(gyro, dim=1, keepdim=True)\n\n        # 2) jerk\n        jerk = F.pad(acc[:, :, 1:] - acc[:, :, :-1], (1,0))\n        gyro_delta = F.pad(gyro[:, :, 1:] - gyro[:, :, :-1], (1,0))\n\n        # 3) energy\n        acc_pow  = acc ** 2\n        gyro_pow = gyro ** 2\n\n        # 4) LPF / HPF\n        # self.lpf は forwardパスでは使われていないが、重み読み込みのために定義が必要\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\n        features = [\n            acc, gyro,\n            acc_mag, gyro_mag,\n            jerk, gyro_delta,\n            acc_pow, gyro_pow,\n            acc_lpf, acc_hpf,\n            gyro_lpf, gyro_hpf,\n        ]\n        return torch.cat(features, dim=1)\n\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(\n            nn.Linear(channels, channels // reduction, bias=False), nn.ReLU(inplace=True),\n            nn.Linear(channels // reduction, channels, bias=False), nn.Sigmoid()\n        )\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)\n\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)))\n\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 TwoBranchModel(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\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)\n\nclass PublicTwoBranchModel(nn.Module):\n    \"\"\"\n    ★★★ 公開されているPyTorchモデル（モデル群C）を読み込むための、元のアーキテクチャを持つクラス ★★★\n    \"\"\"\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) # GRUではなくLSTM\n        self.lstm_dropout = nn.Dropout(dropouts[4])\n        self.attention = AttentionLayer(256) # 128*2 for bidirectional\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\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        # mean_pt, std_pt は事前に定義されているグローバル変数\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)\n\ndef 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)\n\n# =============================================================================\n# ## 特徴量エンジニアリング関数\n# =============================================================================\ndef remove_gravity_from_acc3(acc_data, rot_data):\n    \"\"\"加速度データから重力成分を除去する\"\"\"\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\n\ndef calculate_angular_velocity_from_quat3(rot_data, time_delta=1/200):\n    \"\"\"クォータニオンから角速度を計算する\"\"\"\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\n\ndef calculate_angular_distance3(rot_data):\n    \"\"\"クォータニオンから角距離を計算する\"\"\"\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\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 se_block(x, reduction=8):\n    \"\"\"Squeeze-and-Excitationブロック\"\"\"\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    \"\"\"Residual SE-CNNブロック\"\"\"\n    shortcut = x\n    # 2層のConv1D\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    # SEブロック\n    x = se_block(x)\n    # ショートカット接続\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\n\ndef attention_layer(inputs):\n    \"\"\"アテンション層\"\"\"\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\nclass GatedMixupGenerator(Sequence):\n    \"\"\"Mixupとセンサーマスキングを適用するデータジェネレータ\"\"\"\n    def __init__(self, X, y, batch_size, imu_dim, class_weight=None, alpha=0.2, masking_prob=0.0):\n        self.X, self.y, self.batch, self.imu_dim = X, y, batch_size, imu_dim\n        self.class_weight, self.alpha, self.masking_prob = class_weight, alpha, masking_prob\n        self.indices = np.arange(len(X))\n\n    def __len__(self):\n        return int(np.ceil(len(self.X) / self.batch))\n\n    def __getitem__(self, i):\n        idx = self.indices[i*self.batch:(i+1)*self.batch]\n        Xb, yb = self.X[idx].copy(), self.y[idx].copy()\n\n        sample_weights = np.ones(len(Xb), dtype='float32')\n        if self.class_weight:\n            sample_weights = np.array([self.class_weight.get(i, 1.0) for i in yb.argmax(axis=1)])\n\n        gate_target = np.ones(len(Xb), dtype='float32')\n        if self.masking_prob > 0:\n            for j in range(len(Xb)):\n                if np.random.rand() < self.masking_prob:\n                    Xb[j, :, self.imu_dim:] = 0\n                    gate_target[j] = 0.0\n\n        if self.alpha > 0:\n            lam = np.random.beta(self.alpha, self.alpha)\n            perm = np.random.permutation(len(Xb))\n            X_mix = lam * Xb + (1 - lam) * Xb[perm]\n            y_mix = lam * yb + (1 - lam) * yb[perm]\n            gate_target_mix = lam * gate_target + (1 - lam) * gate_target[perm]\n            sample_weights_mix = lam * sample_weights + (1 - lam) * sample_weights[perm]\n            return X_mix, {'main_output': y_mix, 'tof_gate': gate_target_mix}, sample_weights_mix\n\n        return Xb, {'main_output': yb, 'tof_gate': gate_target}, sample_weights\n\n    def on_epoch_end(self):\n        np.random.shuffle(self.indices)\n\ndef build_gated_two_branch_model(pad_len, imu_dim, tof_dim, n_classes, wd=1e-4):\n    \"\"\"\n    自作のGated Two-Branchモデルを構築する関数。\n    [改良点] LSTMをGRUに変更、全結合層を1層追加。\n    \"\"\"\n    inp = Input(shape=(pad_len, imu_dim + tof_dim))\n    imu = Lambda(lambda t: t[:, :, :imu_dim])(inp)\n    tof = Lambda(lambda t: t[:, :, imu_dim:])(inp)\n\n    # IMUブランチ (Deep)\n    x1 = residual_se_cnn_block(imu, 64, 3, drop=0.1, wd=wd)\n    x1 = residual_se_cnn_block(x1, 128, 5, drop=0.1, wd=wd)\n\n    # TOF/THMブランチ (Light) with Gating\n    x2_base = Conv1D(64, 3, padding='same', use_bias=False, kernel_regularizer=l2(wd))(tof)\n    x2_base = BatchNormalization()(x2_base); x2_base = Activation('relu')(x2_base)\n    x2_base = MaxPooling1D(2)(x2_base); x2_base = Dropout(0.2)(x2_base)\n    x2_base = Conv1D(128, 3, padding='same', use_bias=False, kernel_regularizer=l2(wd))(x2_base)\n    x2_base = BatchNormalization()(x2_base); x2_base = Activation('relu')(x2_base)\n    x2_base = MaxPooling1D(2)(x2_base); x2_base = Dropout(0.2)(x2_base)\n\n    # Gating機構\n    gate_input = GlobalAveragePooling1D()(tof)\n    gate_input = Dense(16, activation='relu')(gate_input)\n    gate = Dense(1, activation='sigmoid', name='tof_gate')(gate_input)\n    x2 = Multiply()([x2_base, gate])\n\n    # ブランチのマージと後続層\n    merged = Concatenate()([x1, x2])\n    # ★改良点: LSTM -> GRU\n    x = Bidirectional(GRU(256, return_sequences=True, kernel_regularizer=l2(wd)))(merged)\n    x = Dropout(0.45)(x)\n    x = attention_layer(x)\n\n    # ★改良点: 全結合層を1層追加して表現力を向上\n    for units, drop in [(512, 0.5), (256, 0.4), (128, 0.3)]:\n        x = Dense(units, use_bias=False, kernel_regularizer=l2(wd))(x)\n        x = BatchNormalization()(x)\n        x = Activation('relu')(x)\n        x = Dropout(drop)(x)\n\n    out = Dense(n_classes, activation='softmax', name='main_output', kernel_regularizer=l2(wd))(x)\n\n    return Model(inputs=inp, outputs=[out, gate])\n\n# -----------------------------------------------------------------------------\n# ### 推論モード (`TRAIN = False`)\n# -----------------------------------------------------------------------------\n\nprint(\"▶ 推論モード開始 – 学習済みモデルとアーティファクトを読み込みます...\")\n\n# --- モデル群A (自作TF/Kerasモデル) の読み込み ---\nprint(\"  モデル群A (自作5-Fold Gated GRUモデル) を読み込み中...\")\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,\n                 '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\"  > {len(models_A)}個のモデルを正常に読み込みました。\")\n\n# --- モデル群B (公開TF/Kerasモデル) の読み込み ---\nprint(\"\\n  モデル群B (公開TF/Kerasモデル) を読み込み中...\")\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 # public modelも同じカスタムオブジェクトを使用\nmodel_B = load_model(PUBLIC_TF_MODEL_DIR / \"gesture_two_branch_mixup.h5\", compile=False, custom_objects=custom_objs_B)\nprint(\"  > 1個のモデルを正常に読み込みました。\")\n\n# --- モデル群C (公開PyTorchモデル) の読み込み ---\nprint(\"\\n  モデル群C (公開PyTorchモデル) を読み込み中...\")\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\")\n\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'],\n           '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\"  > {len(pt_models)}個のモデルを正常に読み込みました。\")","metadata":{"execution":{"iopub.status.busy":"2025-08-26T18:01:35.045970Z","iopub.execute_input":"2025-08-26T18:01:35.046590Z","iopub.status.idle":"2025-08-26T18:01:42.581995Z","shell.execute_reply.started":"2025-08-26T18:01:35.046563Z","shell.execute_reply":"2025-08-26T18:01:42.581089Z"},"papermill":{"duration":8.952511,"end_time":"2025-08-22T01:18:45.040452","exception":false,"start_time":"2025-08-22T01:18:36.087941","status":"completed"},"tags":[],"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## enumerate","metadata":{}},{"cell_type":"code","source":"def enumerate_weights():\n    import random\n    \n    # directed random scheme 1\n    weights00 = {'A': 0.5285, 'B': 0.1770, 'C': 0.2945}\n    weights01 = {'A': 0.5290, 'B': 0.1780, 'C': 0.2930}\n    weights02 = {'A': 0.5295, 'B': 0.1785, 'C': 0.2920}\n    weights03 = {'A': 0.5305, 'B': 0.1790, 'C': 0.2905}\n    weights04 = {'A': 0.5310, 'B': 0.1795, 'C': 0.2895}\n\n    # directed random scheme 2\n    weights05 = {'A': 0.533, 'B': 0.174, 'C': 0.293}\n    weights06 = {'A': 0.534, 'B': 0.176, 'C': 0.290}\n    weights07 = {'A': 0.535, 'B': 0.177, 'C': 0.288}\n    weights08 = {'A': 0.536, 'B': 0.178, 'C': 0.286}\n    weights09 = {'A': 0.537, 'B': 0.179, 'C': 0.284}\n\n    # directed random scheme 3\n    weights10 = {'A': 0.534, 'B': 0.158,  'C': 0.308}\n    weights11 = {'A': 0.534, 'B': 0.1586, 'C': 0.3074}\n    weights12 = {'A': 0.535, 'B': 0.159,  'C': 0.386}\n    weights13 = {'A': 0.536, 'B': 0.1595, 'C': 0.3045}\n    weights14 = {'A': 0.537, 'B': 0.160,  'C': 0.303}\n\n    # directed random scheme 4\n    weights15 = {'A': 0.527, 'B': 0.185,  'C': 0.288}\n    weights16 = {'A': 0.526, 'B': 0.190,  'C': 0.284}\n    weights17 = {'A': 0.525, 'B': 0.195,  'C': 0.280}\n    weights18 = {'A': 0.524, 'B': 0.200,  'C': 0.276}\n    weights19 = {'A': 0.523, 'B': 0.205,  'C': 0.272}\n\n    import random\n    weights_1 = random.choice([weights00,weights01,weights02,weights03,weights04])\n    weights_2 = random.choice([weights05,weights06,weights07,weights08,weights09])\n    weights_3 = random.choice([weights10,weights11,weights12,weights13,weights14])\n    weights_4 = random.choice([weights15,weights16,weights17,weights18,weights19])\n        \n    # return weights_1,weights_2,weights_3,weights_4  # LB = 0.854\n    # return           weights_2,weights_3            # LB = ?\n\n    return weights_1,weights_2,weights_3,weights_4   # in the enumeration function predict \n                                                     # the values for weights_2 and weights_3 \n                                                     # will be taken","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-26T18:01:42.583348Z","iopub.execute_input":"2025-08-26T18:01:42.583632Z","iopub.status.idle":"2025-08-26T18:01:42.592774Z","shell.execute_reply.started":"2025-08-26T18:01:42.583614Z","shell.execute_reply":"2025-08-26T18:01:42.591912Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## predict_3","metadata":{}},{"cell_type":"code","source":"# --- `predict`関数の定義 ---\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    \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    # (推論 A)\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\n    # --- 2. モデル群B (公開TFモデル) の予測 ---\n    df_seq_B = df_seq_orig.copy()\n    # (特徴量生成 B)\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    # (推論 B)\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\n    # --- 3. モデル群C (公開PyTorchモデル) の予測 ---\n    df_seq_C = df_seq_orig.copy() # Cは特徴量生成が不要なため、コピーのみ\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   \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\n    avg_pred_A = np.mean(preds_A_folds, axis=0)\n    avg_pred_C_logits = torch.median(torch.stack(preds_C_folds), dim=0).values\n    avg_pred_C = torch.softmax(avg_pred_C_logits, dim=1).cpu().numpy()\n    pred_B = model_B.predict(pad_input_B, verbose=0)\n    if isinstance(pred_B, list): pred_B = pred_B[0]\n    \n    # --- 4. 上位に近いランダムな決定を指示する\n    # --- 4. Directed random decision close to top\n    # weights = {'A': 0.53, 'B': 0.18, 'C': 0.29} current.Top\n\n    import copy\n\n    # w1,w2,w3,w4 = enumerate_weights()\n\n    # fpps_1 = (\n    #     w1['A']* copy.deepcopy(avg_pred_A)+ \n    #     w1['B']* copy.deepcopy(pred_B)    + \n    #     w1['C']* copy.deepcopy(avg_pred_C))\n\n    # fpps_2 = (\n    #     w2['A']* copy.deepcopy(avg_pred_A)+ \n    #     w2['B']* copy.deepcopy(pred_B)    + \n    #     w2['C']* copy.deepcopy(avg_pred_C))\n                                               #  LB = 0.854\n    # fpps_3 = (\n    #     w3['A']* copy.deepcopy(avg_pred_A)+ \n    #     w3['B']* copy.deepcopy(pred_B)    + \n    #     w3['C']* copy.deepcopy(avg_pred_C))\n\n    # fpps_4 = (\n    #     w4['A']* copy.deepcopy(avg_pred_A)+ \n    #     w4['B']* copy.deepcopy(pred_B)    + \n    #     w4['C']* copy.deepcopy(avg_pred_C))\n    \n    # return [fpps_1,fpps_2,fpps_3,fpps_4]\n\n\n    w1,w2,w3,w4 = enumerate_weights()\n\n    fpps_1 = (                                 #\n        w1['A']* copy.deepcopy(avg_pred_A)+    #  these values will not be considered\n        w1['B']* copy.deepcopy(pred_B)    +    #  in the final enumeration function ( v.3 )\n        w1['C']* copy.deepcopy(avg_pred_C))    #\n\n    fpps_2 = (\n        w2['A']* copy.deepcopy(avg_pred_A)+ \n        w2['B']* copy.deepcopy(pred_B)    + \n        w2['C']* copy.deepcopy(avg_pred_C))\n                                               #  LB = ?\n    fpps_3 = (\n        w3['A']* copy.deepcopy(avg_pred_A)+ \n        w3['B']* copy.deepcopy(pred_B)    + \n        w3['C']* copy.deepcopy(avg_pred_C))\n\n    fpps_4 = (                                 #\n        w4['A']* copy.deepcopy(avg_pred_A)+    #  these values will not be considered\n        w4['B']* copy.deepcopy(pred_B)    +    #  in the final enumeration function ( v.3 )\n        w4['C']* copy.deepcopy(avg_pred_C))    #\n    \n    return [fpps_1,fpps_2,fpps_3,fpps_4]","metadata":{"execution":{"iopub.status.busy":"2025-08-26T18:01:42.593588Z","iopub.execute_input":"2025-08-26T18:01:42.593811Z","iopub.status.idle":"2025-08-26T18:01:42.617745Z","shell.execute_reply.started":"2025-08-26T18:01:42.593795Z","shell.execute_reply":"2025-08-26T18:01:42.617222Z"},"papermill":{"duration":0.027586,"end_time":"2025-08-22T01:18:45.075355","exception":false,"start_time":"2025-08-22T01:18:45.047769","status":"completed"},"tags":[],"_kg_hide-input":false,"_kg_hide-output":true,"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Submission","metadata":{"papermill":{"duration":0.006171,"end_time":"2025-08-22T01:18:45.087961","exception":false,"start_time":"2025-08-22T01:18:45.08179","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import numpy as np\nimport copy\n\ndef h_blend(pred0,pred1,pred2, m_w,da_w,c_w):\n    m_wts, preds = np.asarray(m_w), []\n    for a,b,c in zip(pred0,pred1,pred2):    \n        l_abc = [\n            { 'wts':m_wts[0], 'pred':a, 'res':0 },\n            { 'wts':m_wts[1], 'pred':b, 'res':0 },\n            { 'wts':m_wts[2], 'pred':c, 'res':0 },\n        ]\n        l_asc  = sorted(copy.deepcopy(l_abc), key=lambda _:_['pred'],reverse=False)\n        l_desc = sorted(copy.deepcopy(l_abc), key=lambda _:_['pred'],reverse=True) \n        for asc, c_wts in zip(l_asc, c_w): asc ['res'] = asc ['pred'] * (asc ['wts'] +c_wts)\n        for desc,c_wts in zip(l_desc,c_w): desc['res'] = desc['pred'] * (desc['wts'] +c_wts)\n        result_asc  = sum([asc ['res'] for asc in l_asc])\n        result_desc = sum([desc['res'] for asc in l_desc])\n        result = result_asc * da_w[0] + da_w[1] * result_desc\n        preds.append(result)\n    avg_pred =  np.asarray(preds)\n    return avg_pred\n\n\ndef find_max_val(one_avg_pred):\n    max_val = -float('inf')\n    for i, val in enumerate(one_avg_pred):\n        if val > max_val:\n            max_val = val\n    return max_val\n\n\ndef predict(sequence, demographics):\n    pred0  = predict1(sequence, demographics)[0]\n    pred1  = predict2(sequence, demographics)[0]\n    preds3 = predict3(sequence, demographics)\n\n    # m_w,da_w,c_w = [0.271, 0.347, 0.382], [0.70, 0.30], [+0.0021,-0.00055,-0.00155]\n    # avg_pred_1 = h_blend(pred0,pred1,preds3[0][0], m_w,da_w,c_w)\n    # avg_pred_2 = h_blend(pred0,pred1,preds3[1][0], m_w,da_w,c_w)\n    # avg_pred_3 = h_blend(pred0,pred1,preds3[2][0], m_w,da_w,c_w)    # LB = 0.854\n    # avg_pred_4 = h_blend(pred0,pred1,preds3[3][0], m_w,da_w,c_w)\n\n    # avg_pred_s = [avg_pred_1, avg_pred_2, avg_pred_3, avg_pred_4]\n\n    # -------------------------------------------------------------------------------\n\n    m_w,da_w,c_w = [0.271, 0.347, 0.382], [0.70, 0.30], [+0.0021,-0.00055,-0.00155]\n    #avg_pred_1 = h_blend(pred0,pred1,preds3[0][0], m_w,da_w,c_w)\n    avg_pred_2  = h_blend(pred0,pred1,preds3[1][0], m_w,da_w,c_w)\n    avg_pred_3  = h_blend(pred0,pred1,preds3[2][0], m_w,da_w,c_w)\n    #avg_pred_4 = h_blend(pred0,pred1,preds3[3][0], m_w,da_w,c_w)\n\n    #               avg_pred_1,                         avg_pred_4\n    avg_pred_s = [             avg_pred_2, avg_pred_3,           ]   # LB = ? \n\n    max_idx,max_val = -1,0\n    for i, avg_pred in enumerate(avg_pred_s):\n        val = find_max_val(avg_pred)\n        if val > max_val:\n            max_val = val\n            max_idx = i                 # max_idx - principle.1\n            \n\n    return dataset.le.classes_[avg_pred_s[max_idx].argmax()]\n    \n                \n    #return dataset.le.classes_[avg_pred.argmax()] # original","metadata":{"execution":{"iopub.status.busy":"2025-08-26T18:03:51.018697Z","iopub.execute_input":"2025-08-26T18:03:51.019251Z","iopub.status.idle":"2025-08-26T18:03:51.029196Z","shell.execute_reply.started":"2025-08-26T18:03:51.019226Z","shell.execute_reply":"2025-08-26T18:03:51.028411Z"},"papermill":{"duration":0.017129,"end_time":"2025-08-22T01:18:45.111533","exception":false,"start_time":"2025-08-22T01:18:45.094404","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import 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\nif not os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    print(pd.read_parquet(\"submission.parquet\"))","metadata":{"_cell_guid":"d950fdf3-997f-4502-92ff-83a5d2014717","_uuid":"46f1f772-fc94-4243-b3bd-d7b91e64b825","papermill":{"duration":60.83139,"end_time":"2025-08-22T01:19:45.94991","exception":false,"start_time":"2025-08-22T01:18:45.11852","status":"completed"},"tags":[],"trusted":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2025-08-26T18:03:52.121790Z","iopub.execute_input":"2025-08-26T18:03:52.122078Z","iopub.status.idle":"2025-08-26T18:04:12.312879Z","shell.execute_reply.started":"2025-08-26T18:03:52.122055Z","shell.execute_reply":"2025-08-26T18:04:12.312124Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}