{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12139340,"sourceType":"datasetVersion","datasetId":7645099},{"sourceId":12293285,"sourceType":"datasetVersion","datasetId":7748073},{"sourceId":12411879,"sourceType":"datasetVersion","datasetId":7827797},{"sourceId":242954653,"sourceType":"kernelVersion"},{"sourceId":246893721,"sourceType":"kernelVersion"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"pip install keras-tuner --upgrade","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T05:29:14.489016Z","iopub.execute_input":"2025-07-14T05:29:14.489238Z","iopub.status.idle":"2025-07-14T05:29:18.797515Z","shell.execute_reply.started":"2025-07-14T05:29:14.489215Z","shell.execute_reply":"2025-07-14T05:29:18.796632Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, json, joblib, numpy as np, pandas as pd\nfrom pathlib import Path\nimport warnings \nwarnings.filterwarnings(\"ignore\")\n\n\nfrom sklearn.model_selection import train_test_split, StratifiedKFold\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, SGD, RMSprop\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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T05:29:18.79995Z","iopub.execute_input":"2025-07-14T05:29:18.800214Z","iopub.status.idle":"2025-07-14T05:29:32.865797Z","shell.execute_reply.started":"2025-07-14T05:29:18.80019Z","shell.execute_reply":"2025-07-14T05:29:32.865227Z"},"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import random\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'\nseed_everything(seed=42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T05:29:32.868573Z","iopub.execute_input":"2025-07-14T05:29:32.868784Z","iopub.status.idle":"2025-07-14T05:29:32.87349Z","shell.execute_reply.started":"2025-07-14T05:29:32.868769Z","shell.execute_reply":"2025-07-14T05:29:32.872606Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# (Competition metric will only be imported when TRAINing)\nTRAIN = True                     # ← 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\n\nprint(\"▶ imports ready · tensorflow\", tf.__version__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T05:29:32.874286Z","iopub.execute_input":"2025-07-14T05:29:32.874576Z","iopub.status.idle":"2025-07-14T05:29:32.957076Z","shell.execute_reply.started":"2025-07-14T05:29:32.874552Z","shell.execute_reply":"2025-07-14T05:29:32.956381Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T05:29:32.957843Z","iopub.execute_input":"2025-07-14T05:29:32.9581Z","iopub.status.idle":"2025-07-14T05:29:32.972922Z","shell.execute_reply.started":"2025-07-14T05:29:32.958078Z","shell.execute_reply":"2025-07-14T05:29:32.972167Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 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)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T05:29:32.974899Z","iopub.execute_input":"2025-07-14T05:29:32.975103Z","iopub.status.idle":"2025-07-14T05:29:32.991937Z","shell.execute_reply.started":"2025-07-14T05:29:32.975088Z","shell.execute_reply":"2025-07-14T05:29:32.991251Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def 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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T05:29:32.992687Z","iopub.execute_input":"2025-07-14T05:29:32.992939Z","iopub.status.idle":"2025-07-14T05:29:33.014866Z","shell.execute_reply.started":"2025-07-14T05:29:32.992919Z","shell.execute_reply":"2025-07-14T05:29:33.014113Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def 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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T05:29:33.015406Z","iopub.execute_input":"2025-07-14T05:29:33.015596Z","iopub.status.idle":"2025-07-14T05:29:33.033567Z","shell.execute_reply.started":"2025-07-14T05:29:33.015581Z","shell.execute_reply":"2025-07-14T05:29:33.032858Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_two_branch_model(pad_len, imu_dim, tof_dim, n_classes, num_filters_arr, kernel_size_arr, lstm_units_arr, dropout_rate_arr, 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, num_filters_arr[0], kernel_size_arr[0], drop=0.1, wd=wd)\n    x1 = residual_se_cnn_block(x1, num_filters_arr[1], kernel_size_arr[1], 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(lstm_units_arr[0], return_sequences=True, kernel_regularizer=l2(wd)))(merged)\n    xb = Bidirectional(GRU(lstm_units_arr[1], 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, dropout_rate_arr[0]), (128, dropout_rate_arr[1])]:\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\n# tmp_model = build_two_branch_model(127,7,325,18)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T05:29:33.034323Z","iopub.execute_input":"2025-07-14T05:29:33.034707Z","iopub.status.idle":"2025-07-14T05:29:33.051898Z","shell.execute_reply.started":"2025-07-14T05:29:33.03469Z","shell.execute_reply":"2025-07-14T05:29:33.051277Z"},"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import keras_tuner as kt\nfrom sklearn.model_selection import StratifiedGroupKFold\nimport numpy as np\n\nclass MyHyperModel(kt.HyperModel):\n    def residual_se_cnn_block(self, x, cnn_id, hp):\n        filters = hp.Choice(f\"num_filters_{cnn_id}\", [32, 64, 128])\n        kernel_size = hp.Int(f\"kernel_size_{cnn_id}\", 3, 9, step=2)\n        activation = hp.Choice(f'activation_{cnn_id}', ['relu', 'leaky_relu', 'elu', 'swish'])\n        activation_rec = hp.Choice(f'activation_rec_{cnn_id}', ['relu', 'leaky_relu', 'elu', 'swish'])\n        pool_size = hp.Choice('pool_size', values=[2, 3, 4, 5])\n        use_dropout = hp.Boolean(f'use_dropout_{cnn_id}')\n        wd = 1e-4\n        \n        shortcut = x\n        for _ in range(hp.Int(f\"num_cnn_layers_{cnn_id}\", 1, 3)):\n            x = Conv1D(filters, kernel_size, padding='same', use_bias=False,\n                       kernel_regularizer=l2(wd))(x)\n            x = BatchNormalization()(x)\n            if activation == 'leaky_relu':\n                x = tf.keras.layers.LeakyReLU()(x)\n            else:\n                x = Activation(activation)(x)\n\n        if hp.Boolean(f'use_se_block_{cnn_id}'):\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        if activation_rec == 'leaky_relu':\n            x = tf.keras.layers.LeakyReLU()(x)\n        else:\n            x = Activation(activation_rec)(x)\n        x = MaxPooling1D(pool_size)(x)\n        if use_dropout:\n            dropout_rate = hp.Float(f'dropout_rate_{cnn_id}', 0.1, 0.3, step=0.1)\n            x = Dropout(dropout_rate)(x)\n        return x\n\n    def build(self, hp):\n        pad_len = 127\n        imu_dim = 17\n        tof_dim = 25\n        wd = 1e-4\n        n_classes = 18\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        x1 = self.residual_se_cnn_block(imu, 0, hp)\n        x1 = self.residual_se_cnn_block(x1, 1, hp)\n\n        filters_0 = hp.Choice(\"num_filters_0\", [32, 64, 128])\n        kernel_size_0 = hp.Int(\"kernel_size_0\", 3, 9, step=2)\n        filters_1 = hp.Choice(\"num_filters_1\", [32, 64, 128])\n        kernel_size_1 = hp.Int(\"kernel_size_1\", 3, 9, step=2)\n        pool_size = hp.Choice('pool_size', values=[2, 3, 4, 5])\n\n        # tof_filters_0 = hp.Choice(\"tof_filters_0\", [32, 64, 128])\n        # tof_kernel_size_0 = hp.Int(\"tof_kernel_size_0\", 3, 9, step=2)\n        tof_filters_0 = filters_0\n        tof_kernel_size_0 = kernel_size_0\n        tof_activation_0 = hp.Choice('tof_activation_0', ['relu', 'leaky_relu', 'elu', 'swish'])\n        # tof_pool_size_0 = hp.Choice('tof_pool_size_0', values=[2, 3, 4, 5])\n        tof_pool_size_0 = pool_size\n        tof_use_dropout_0 = hp.Boolean('tof_use_dropout_0')\n\n        # tof_filters_1 = hp.Choice(\"tof_filters_1\", [32, 64, 128])\n        # tof_kernel_size_1 = hp.Int(\"tof_kernel_size_1\", 3, 9, step=2)\n        tof_filters_1 = filters_1\n        tof_kernel_size_1 = kernel_size_1\n        tof_activation_1 = hp.Choice('tof_activation_1', ['relu', 'leaky_relu', 'elu', 'swish'])\n        # tof_pool_size_1 = hp.Choice('tof_pool_size_1', values=[2, 3, 4, 5])\n        tof_pool_size_1 = pool_size\n        tof_use_dropout_1 = hp.Boolean('tof_use_dropout_1')\n        \n        x2 = Conv1D(tof_filters_0, tof_kernel_size_0, padding='same', use_bias=False, kernel_regularizer=l2(wd))(tof)\n        x2 = BatchNormalization()(x2)\n        if tof_activation_0 == 'leaky_relu':\n            x2 = tf.keras.layers.LeakyReLU()(x2)\n        else:\n            x2 = Activation(tof_activation_0)(x2)\n        x2 = MaxPooling1D(tof_pool_size_0)(x2)\n        if tof_use_dropout_0:\n            tof_dropout_rate_0 = hp.Float('tof_dropout_rate_0', 0.1, 0.3, step=0.1)\n            x2 = Dropout(tof_dropout_rate_0)(x2)\n        x2 = Conv1D(tof_filters_1, tof_kernel_size_1, padding='same', use_bias=False, kernel_regularizer=l2(wd))(x2)\n        x2 = BatchNormalization()(x2)\n        if tof_activation_1 == 'leaky_relu':\n            x2 = tf.keras.layers.LeakyReLU()(x2)\n        else:\n            x2 = Activation(tof_activation_1)(x2)\n        x2 = MaxPooling1D(tof_pool_size_1)(x2)\n        if tof_use_dropout_1:\n            tof_dropout_rate_1 = hp.Float('tof_dropout_rate_1', 0.1, 0.3, step=0.1)\n            x2 = Dropout(tof_dropout_rate_1)(x2)\n\n        merged = Concatenate()([x1, x2])\n\n        use_bidir = hp.Boolean('use_bidirectional')\n        merge_mode_lstm = hp.Choice('merge_mode_lstm', ['concat', 'sum', 'ave', 'mul']) if use_bidir else None\n        merge_mode_gru = hp.Choice('merge_mode_gru', ['concat', 'sum', 'ave', 'mul']) if use_bidir else None\n        \n        lstm_layer = LSTM(\n            units=hp.Int('lstm_units', 64, 256, step=64),\n            activation=hp.Choice('activation_lstm', ['tanh', 'relu']),\n            recurrent_activation=hp.Choice('recurrent_activation_lstm', ['sigmoid', 'hard_sigmoid']),\n            dropout=hp.Float('dropout_lstm', 0.0, 0.5, step=0.1),\n            recurrent_dropout=hp.Float('recurrent_dropout_lstm', 0.0, 0.5, step=0.1),\n            return_sequences=True,\n            kernel_regularizer=l2(wd)\n        )\n\n        gru_layer = GRU(\n            units=hp.Int('gru_units', 64, 256, step=64),\n            activation=hp.Choice('activation_gru', ['tanh', 'relu']),\n            recurrent_activation=hp.Choice('recurrent_activation_gru', ['sigmoid', 'hard_sigmoid']),\n            dropout=hp.Float('dropout_gru', 0.0, 0.5, step=0.1),\n            recurrent_dropout=hp.Float('recurrent_dropout_gru', 0.0, 0.5, step=0.1),\n            return_sequences=True,\n            kernel_regularizer=l2(wd)\n        )\n\n        if use_bidir:\n            xa = Bidirectional(lstm_layer, merge_mode=merge_mode_lstm)(merged)\n            xb = Bidirectional(gru_layer, merge_mode=merge_mode_gru)(merged)\n        else:\n            xa = lstm_layer(merged)\n            xb = gru_layer(merged)\n\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        dense_units_0 = hp.Choice(\"dense_units_0\", [64, 128, 256])\n        dense_units_1 = hp.Choice(\"dense_units_1\", [64, 128, 256])\n        dense_dropout_0 =hp.Float('dense_dropout_0', 0.0, 0.5, step=0.1)\n        dense_dropout_1 =hp.Float('dense_dropout_1', 0.0, 0.5, step=0.1)\n\n        for units, drop in [(dense_units_0, dense_dropout_0), (dense_units_1, dense_dropout_1)]:\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        # out = Dense(n_classes, activation='softmax', kernel_regularizer=l2(wd))(merged)\n        model = Model(inp, out)\n        \n        model.compile(\n            optimizer=tf.keras.optimizers.Adam(hp.Float(\"lr\", 1e-4, 1e-2, sampling=\"log\")),\n            loss=tf.keras.losses.CategoricalCrossentropy(label_smoothing=0.1),\n            metrics=[\"accuracy\"]\n        )\n        return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T06:28:55.781215Z","iopub.execute_input":"2025-07-14T06:28:55.781526Z","iopub.status.idle":"2025-07-14T06:28:55.804502Z","shell.execute_reply.started":"2025-07-14T06:28:55.781499Z","shell.execute_reply":"2025-07-14T06:28:55.803775Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install iterative-stratification","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T06:02:16.619543Z","iopub.execute_input":"2025-07-14T06:02:16.620076Z","iopub.status.idle":"2025-07-14T06:02:19.915521Z","shell.execute_reply.started":"2025-07-14T06:02:16.620055Z","shell.execute_reply":"2025-07-14T06:02:19.914782Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from iterstrat.ml_stratifiers import MultilabelStratifiedKFold\nfrom keras_tuner import BayesianOptimization\n\nclass MyTuner(kt.BayesianOptimization):\n    def run_trial(self, trial, X, y, batch_size=64, epochs=140, **fit_kwargs):\n        print(\"Using device:\", tf.test.gpu_device_name())\n        mskf = MultilabelStratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n        val_accuracies = []\n\n        for train_index, val_index in mskf.split(X, y):\n            X_train, X_val = X[train_index], X[val_index]\n            y_train, y_val = y[train_index], y[val_index]\n\n            model = self.hypermodel.build(trial.hyperparameters)\n            history = model.fit(\n                X_train, y_train,\n                validation_data=(X_val, y_val),\n                batch_size=batch_size,\n                epochs=epochs,\n                verbose=2,\n                **fit_kwargs\n            )\n\n            val_accuracies.append(history.history[\"val_accuracy\"][-1])\n\n        # Report the average accuracy over folds\n        mean_val_accuracy = np.mean(val_accuracies)\n        self.oracle.update_trial(trial.trial_id, {\"val_accuracy\": mean_val_accuracy})\n        self.save_model(trial.trial_id, model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T06:57:34.073107Z","iopub.execute_input":"2025-07-14T06:57:34.073394Z","iopub.status.idle":"2025-07-14T06:57:34.079755Z","shell.execute_reply.started":"2025-07-14T06:57:34.073371Z","shell.execute_reply":"2025-07-14T06:57:34.079016Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if TRAIN:\n    print(\"▶ TRAIN MODE – loading dataset …\")\n    df = pd.read_csv(RAW_DIR / \"train.csv\")\n\n    train_dem_df = pd.read_csv(RAW_DIR / \"train_demographics.csv\")\n    df_for_groups = pd.merge(df.copy(), train_dem_df, on='subject', how='left')\n\n    le = LabelEncoder()\n    df['gesture_int'] = le.fit_transform(df['gesture'])\n    np.save(EXPORT_DIR / \"gesture_classes.npy\", le.classes_)\n    gesture_classes = le.classes_\n\n    print(\"  Calculating base engineered IMU features (magnitude, angle)...\")\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    \n    print(\"  Calculating engineered IMU derivatives (jerk, angular velocity) for original acc_mag...\")\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    print(\"  Removing gravity and calculating linear acceleration features...\")\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    \n    df_linear_accel = pd.concat(linear_accel_list)\n    df = pd.concat([df, df_linear_accel], axis=1)\n\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    print(\"  Calculating angular velocity from quaternion derivatives...\")\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    \n    df_angular_vel = pd.concat(angular_vel_list)\n    df = pd.concat([df, df_angular_vel], axis=1)\n\n    print(\"  Calculating angular distance between successive quaternions...\")\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    \n    df_angular_distance = pd.concat(angular_distance_list)\n    df = pd.concat([df, df_angular_distance], axis=1)\n\n    meta_cols = { } # This was an empty dict in your provided code, keeping it as is.\n\n    imu_cols_base = ['linear_acc_x', 'linear_acc_y', 'linear_acc_z']\n    imu_cols_base.extend([c for c in df.columns if c.startswith('rot_') and c not in ['rot_angle', 'rot_angle_vel']])\n    \n    imu_engineered_features = [\n        'acc_mag', 'rot_angle',\n        'acc_mag_jerk', 'rot_angle_vel',\n        'linear_acc_mag', 'linear_acc_mag_jerk',\n        'angular_vel_x', 'angular_vel_y', 'angular_vel_z', # Existing new features\n        'angular_distance' # Added new feature\n    ]\n    imu_cols = imu_cols_base + imu_engineered_features\n    imu_cols = list(dict.fromkeys(imu_cols)) # Для удаления дубликатов\n\n    thm_cols_original = [c for c in df.columns if c.startswith('thm_')]\n    \n    tof_aggregated_cols_template = []\n    for i in range(1, 6):\n        tof_aggregated_cols_template.extend([f'tof_{i}_mean', f'tof_{i}_std', f'tof_{i}_min', f'tof_{i}_max'])\n\n    final_feature_cols = imu_cols + thm_cols_original + tof_aggregated_cols_template\n    imu_dim_final = len(imu_cols)\n    tof_thm_aggregated_dim_final = len(thm_cols_original) + len(tof_aggregated_cols_template)\n    \n    print(f\"  IMU (incl. engineered & derivatives) {imu_dim_final} | THM + Aggregated TOF {tof_thm_aggregated_dim_final} | total {len(final_feature_cols)} features\")\n    np.save(EXPORT_DIR / \"feature_cols.npy\", np.array(final_feature_cols))\n\n    print(\"  Building sequences with aggregated TOF and preparing data for scaler...\")\n    seq_gp = df.groupby('sequence_id') \n    \n    all_steps_for_scaler_list = []\n    X_list_unscaled, y_list_int_for_stratify, lens = [], [], [] \n\n    for seq_id, seq_df_orig in seq_gp:\n        seq_df = seq_df_orig.copy()\n\n        for i in range(1, 6):\n            pixel_cols_tof = [f\"tof_{i}_v{p}\" for p in range(64)]\n            tof_sensor_data = seq_df[pixel_cols_tof].replace(-1, np.nan)\n            seq_df[f'tof_{i}_mean'] = tof_sensor_data.mean(axis=1)\n            seq_df[f'tof_{i}_std']  = tof_sensor_data.std(axis=1)\n            seq_df[f'tof_{i}_min']  = tof_sensor_data.min(axis=1)\n            seq_df[f'tof_{i}_max']  = tof_sensor_data.max(axis=1)\n        \n        mat_unscaled = seq_df[final_feature_cols].ffill().bfill().fillna(0).values.astype('float32')\n        \n        all_steps_for_scaler_list.append(mat_unscaled)\n        X_list_unscaled.append(mat_unscaled)\n        y_list_int_for_stratify.append(seq_df['gesture_int'].iloc[0])\n        lens.append(len(mat_unscaled))\n\n    print(\"  Fitting StandardScaler...\")\n    all_steps_concatenated = np.concatenate(all_steps_for_scaler_list, axis=0)\n    scaler = StandardScaler().fit(all_steps_concatenated)\n    joblib.dump(scaler, EXPORT_DIR / \"scaler.pkl\")\n    del all_steps_for_scaler_list, all_steps_concatenated\n\n    print(\"  Scaling and padding sequences...\")\n    X_scaled_list = [scaler.transform(x_seq) for x_seq in X_list_unscaled]\n    del X_list_unscaled\n\n    pad_len = int(np.percentile(lens, PAD_PERCENTILE))\n    np.save(EXPORT_DIR / \"sequence_maxlen.npy\", pad_len)\n    \n    X = pad_sequences(X_scaled_list, maxlen=pad_len, padding='post', truncating='post', dtype='float32')\n    del X_scaled_list\n    \n    y_int_for_stratify = np.array(y_list_int_for_stratify)\n    y = to_categorical(y_int_for_stratify, num_classes=len(le.classes_))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T05:38:11.840585Z","iopub.execute_input":"2025-07-14T05:38:11.840829Z","iopub.status.idle":"2025-07-14T05:43:57.577781Z","shell.execute_reply.started":"2025-07-14T05:38:11.840812Z","shell.execute_reply":"2025-07-14T05:43:57.576933Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tuner = MyTuner(\n    hypermodel=MyHyperModel(),\n    objective=\"val_accuracy\",\n    max_trials=20,\n    directory=\"tuner_dir\",\n    project_name=\"kfold_tuning\",\n    overwrite=True\n)\n\ntuner.search(X, y)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T06:58:36.810625Z","iopub.execute_input":"2025-07-14T06:58:36.811561Z","execution_failed":"2025-07-14T10:55:28.947Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"GPUs available:\", tf.config.list_physical_devices('GPU'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T06:34:52.74481Z","iopub.execute_input":"2025-07-14T06:34:52.745093Z","iopub.status.idle":"2025-07-14T06:34:52.750149Z","shell.execute_reply.started":"2025-07-14T06:34:52.745071Z","shell.execute_reply":"2025-07-14T06:34:52.749444Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tuner = keras_tuner.RandomSearch(\n    build_model,\n    objective='val_loss',\n    max_trials=5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-09T11:54:33.597117Z","iopub.execute_input":"2025-07-09T11:54:33.597821Z","iopub.status.idle":"2025-07-09T11:54:37.257406Z","shell.execute_reply.started":"2025-07-09T11:54:33.597784Z","shell.execute_reply":"2025-07-09T11:54:37.256889Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_tr, X_val, y_tr, y_val = train_test_split(X, y, test_size=0.2, random_state=82, stratify=y_int_for_stratify)\ntuner.search(X_tr, y_tr, epochs=160, validation_data=(X_val, y_val))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-09T11:56:12.281508Z","iopub.execute_input":"2025-07-09T11:56:12.282064Z","execution_failed":"2025-07-09T14:04:20.268Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if TRAIN:\n    print(\"  Splitting data and preparing for training...\")\n    X_tr, X_val, y_tr, y_val = train_test_split(X, y, test_size=0.2, random_state=82, stratify=y_int_for_stratify)\n\n    cw_vals = compute_class_weight('balanced', classes=np.arange(len(le.classes_)), y=y_int_for_stratify)\n    class_weight = dict(enumerate(cw_vals))\n\n    model = build_two_branch_model(pad_len, imu_dim_final, tof_thm_aggregated_dim_final, len(le.classes_), wd=WD)\n    \n    steps = len(X_tr) // BATCH_SIZE\n    lr_sched = tf.keras.optimizers.schedules.CosineDecayRestarts(5e-4, first_decay_steps=15 * steps) \n    \n    model.compile(optimizer=Adam(lr_sched),\n                  loss=tf.keras.losses.CategoricalCrossentropy(label_smoothing=0.1),\n                  metrics=['accuracy'])\n\n    train_gen = MixupGenerator(X_tr, y_tr, batch_size=BATCH_SIZE, alpha=MIXUP_ALPHA)\n    cb = EarlyStopping(patience=PATIENCE, restore_best_weights=True, verbose=1, monitor='val_accuracy', mode='max')\n    print(\"  Starting model training...\")\n    model.fit(train_gen, epochs=EPOCHS, validation_data=(X_val, y_val),\n              class_weight=class_weight, callbacks=[cb], verbose=1)\n\n    model.save(EXPORT_DIR / \"gesture_two_branch_mixup.h5\")\n    print(\"✔ Training done – artefacts saved in\", EXPORT_DIR)\n\n    from cmi_2025_metric_copy_for_import import CompetitionMetric\n    preds_val = model.predict(X_val).argmax(1)\n    true_val_int  = y_val.argmax(1)\n    \n    h_f1 = CompetitionMetric().calculate_hierarchical_f1(\n        pd.DataFrame({'gesture': le.classes_[true_val_int]}),\n        pd.DataFrame({'gesture': le.classes_[preds_val]}))\n    print(\"Hold‑out H‑F1 =\", round(h_f1, 4))\nelse:\n    print(\"▶ INFERENCE MODE – loading artefacts from\", PRETRAINED_DIR)\n    final_feature_cols = np.load(PRETRAINED_DIR / \"feature_cols.npy\", allow_pickle=True).tolist()\n    pad_len        = int(np.load(PRETRAINED_DIR / \"sequence_maxlen.npy\"))\n    scaler         = joblib.load(PRETRAINED_DIR / \"scaler.pkl\")\n    gesture_classes = np.load(PRETRAINED_DIR / \"gesture_classes.npy\", allow_pickle=True)\n\n    # Re-calculate imu_dim_final based on the actual features that will be used\n    # Убедитесь, что 'angular_distance' учитывается здесь при инференсе\n    imu_features_in_final_cols = [c for c in final_feature_cols if any(c.startswith(prefix) for prefix in ['linear_acc_', 'acc_', 'rot_', 'angular_vel_', 'angular_distance'])]\n    imu_dim_final = len(imu_features_in_final_cols)\n\n    tof_thm_aggregated_dim_final = len(final_feature_cols) - imu_dim_final\n\n    custom_objs = {\n        'time_sum': time_sum,\n        'squeeze_last_axis': squeeze_last_axis,\n        'expand_last_axis': expand_last_axis,\n        'se_block': se_block,\n        'residual_se_cnn_block': residual_se_cnn_block,\n        'attention_layer': attention_layer,\n    }\n    model = load_model(PRETRAINED_DIR / \"gesture_two_branch_mixup.h5\",\n                       compile=False, custom_objects=custom_objs)\n    print(\"  Model, scaler, feature_cols, pad_len loaded – ready for evaluation\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-09T03:43:49.09436Z","iopub.execute_input":"2025-07-09T03:43:49.094851Z","iopub.status.idle":"2025-07-09T04:57:54.120656Z","shell.execute_reply.started":"2025-07-09T03:43:49.09483Z","shell.execute_reply":"2025-07-09T04:57:54.119637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"grid_result.cv_results_","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-09T07:52:02.312969Z","iopub.execute_input":"2025-07-09T07:52:02.313547Z","iopub.status.idle":"2025-07-09T07:52:02.319859Z","shell.execute_reply.started":"2025-07-09T07:52:02.313528Z","shell.execute_reply":"2025-07-09T07:52:02.319128Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"▶ INFERENCE MODE – 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\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\nmodels = []\nprint(f\"  Loading models for ensemble inference...\")\nfor fold in range(10):\n    MODEL_DIR = \"/kaggle/input/cmi-d-111\"\n    \n    model_path = f\"{MODEL_DIR}/D-111_{fold}.h5\"\n    print(\">>>LoadModel>>>\",model_path)\n    model = load_model(model_path, compile=False, custom_objects=custom_objs)\n    models.append(model)\nprint(\"-\"*50)\n\nfor fold in range(10):\n    MODEL_DIR = \"/kaggle/input/cmi-d-111\"\n    \n    model_path = f\"{MODEL_DIR}/v0629_{fold}.h5\"\n    print(\">>>LoadModel>>>\",model_path)\n    model = load_model(model_path, compile=False, custom_objects=custom_objs)\n    models.append(model)\nprint(\"-\"*50)\nprint(f\"[INFO]NumUseModels:{len(models)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:27:19.102056Z","iopub.execute_input":"2025-07-08T16:27:19.10228Z","iopub.status.idle":"2025-07-08T16:27:29.457321Z","shell.execute_reply.started":"2025-07-08T16:27:19.102258Z","shell.execute_reply":"2025-07-08T16:27:29.456677Z"},"scrolled":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 》》》**Predict**","metadata":{}},{"cell_type":"code","source":"def predict(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 models] # 主出力のみ取得\n    avg_pred = np.mean(all_preds, axis=0)\n    return str(gesture_classes[avg_pred.argmax()])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:27:29.458002Z","iopub.execute_input":"2025-07-08T16:27:29.458269Z","iopub.status.idle":"2025-07-08T16:27:29.465954Z","shell.execute_reply.started":"2025-07-08T16:27:29.458245Z","shell.execute_reply":"2025-07-08T16:27:29.465111Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 》》》**Submit Inference server**","metadata":{}},{"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    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:27:29.466592Z","iopub.execute_input":"2025-07-08T16:27:29.46683Z","iopub.status.idle":"2025-07-08T16:27:53.094881Z","shell.execute_reply.started":"2025-07-08T16:27:29.466806Z","shell.execute_reply":"2025-07-08T16:27:53.094265Z"},"_kg_hide-output":true},"outputs":[],"execution_count":null}]}