{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import sys\nimport pandas as pd\nimport torch\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport sys\nimport io\nimport os\nimport gc\nimport random\nimport time\nimport pickle\nfrom typing import Optional, Tuple\nfrom contextlib import contextmanager\nimport copy\nimport math\nfrom sklearn.model_selection import StratifiedGroupKFold\nfrom tqdm import tqdm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.amp import GradScaler, autocast\nfrom sklearn.metrics import f1_score\nfrom scipy.spatial.transform import Rotation as R\nfrom scipy.signal import savgol_filter, detrend,butter, lfilter\nimport warnings\nwarnings.filterwarnings(\"ignore\", category=UserWarning)\nwarnings.filterwarnings(\"ignore\", category=DeprecationWarning)\nwarnings.filterwarnings(\"ignore\", category=RuntimeWarning)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-28T11:56:09.192812Z","iopub.execute_input":"2025-08-28T11:56:09.193641Z","iopub.status.idle":"2025-08-28T11:56:14.659764Z","shell.execute_reply.started":"2025-08-28T11:56:09.193606Z","shell.execute_reply":"2025-08-28T11:56:14.658918Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Ver\n* based on All_M13\n* cfg change","metadata":{}},{"cell_type":"markdown","source":"# helper","metadata":{}},{"cell_type":"code","source":"@contextmanager\ndef suppress_print():\n    original_stdout = sys.stdout\n    try:\n        sys.stdout = open(os.devnull, 'w')\n        yield\n    finally:\n        sys.stdout.close()\n        sys.stdout = original_stdout\n\ndef comp_metric(pred,target):\n    pred=np.array(pred)\n    target=np.array(target)\n    \n    y_true_bin = target<8\n    y_pred_bin = pred<8\n    f1_binary = f1_score(\n        y_true_bin,\n        y_pred_bin,\n        pos_label=True,\n        zero_division=0,\n        average='binary'\n    )\n\n    y_true_mc = np.clip(target,a_max=8,a_min=None)\n    y_pred_mc = np.clip(pred,a_max=8,a_min=None)\n\n    f1_macro = f1_score(\n        y_true_mc,\n        y_pred_mc,\n        average='macro',\n        zero_division=0\n    )\n    return 0.5 * f1_binary + 0.5 * f1_macro\n\n\ndef prob_postprocess(prob):\n    return torch.argmax(prob,axis=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T11:56:14.661247Z","iopub.execute_input":"2025-08-28T11:56:14.661746Z","iopub.status.idle":"2025-08-28T11:56:14.667628Z","shell.execute_reply.started":"2025-08-28T11:56:14.661723Z","shell.execute_reply":"2025-08-28T11:56:14.666957Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# data pipline","metadata":{}},{"cell_type":"code","source":"%%writefile _data_pipline.py\nimport numpy as np\nimport pandas as pd\nimport random\nimport copy\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\nCLASS_CODE={\n    ### target\n    'Cheek - pinch skin': 0,\n    'Forehead - pull hairline': 1,\n    'Neck - scratch': 2,\n    'Neck - pinch skin': 3,\n    'Eyelash - pull hair': 4,\n    'Eyebrow - pull hair': 5,\n    'Forehead - scratch': 6,\n    'Above ear - pull hair': 7,\n    ### non-target\n    'Write name on leg': 8,\n    'Feel around in tray and pull out an object': 9,\n    'Wave hello': 10,\n    'Write name in air': 11,\n    'Text on phone': 12,\n    'Pull air toward your face': 13,\n    'Pinch knee/leg skin': 14,\n    'Scratch knee/leg skin': 15,\n    'Drink from bottle/cup': 16,\n    'Glasses on/off': 17\n}\nBHV_CODE={\n    'Relaxes and moves hand to target location':0,\n    'Hand at target location':1,\n    'Performs gesture':2,\n    'Moves hand to target location':3,\n    'MASK':4, # TBD\n}\n\nORI_CODE={\n    'Seated Lean Non Dom - FACE DOWN':0,\n    'Lie on Side - Non Dominant':1,\n    'Seated Straight':2,\n    'Lie on Back':3,\n}\n\n\ndef angular_velocity_from_quaternion(q_w, q_x, q_y, q_z, frame=\"world\"):\n    # Calculate quaternion derivatives using finite differences\n    dq_w = np.gradient(q_w)\n    dq_x = np.gradient(q_x)\n    dq_y = np.gradient(q_y)\n    dq_z = np.gradient(q_z)\n    \n    if frame.lower() == \"world\":\n        # World frame: ω = 2 * q_conjugate * dq/dt\n        # q_conjugate = [q_w, -q_x, -q_y, -q_z]\n        omega_w = 2 * (q_w * dq_w + q_x * dq_x + q_y * dq_y + q_z * dq_z)\n        omega_x = 2 * (q_w * dq_x - q_x * dq_w - q_y * dq_z + q_z * dq_y)\n        omega_y = 2 * (q_w * dq_y + q_x * dq_z - q_y * dq_w - q_z * dq_x)\n        omega_z = 2 * (q_w * dq_z - q_x * dq_y + q_y * dq_x - q_z * dq_w)\n        \n    elif frame.lower() in [\"device\", \"body\"]:\n        # Device/Body frame: ω = 2 * dq/dt * q_conjugate\n        # q_conjugate = [q_w, -q_x, -q_y, -q_z]\n        omega_w = 2 * (dq_w * q_w + dq_x * q_x + dq_y * q_y + dq_z * q_z)\n        omega_x = 2 * (dq_w * (-q_x) + dq_x * q_w + dq_y * (-q_z) + dq_z * q_y)\n        omega_y = 2 * (dq_w * (-q_y) + dq_x * q_z + dq_y * q_w + dq_z * (-q_x))\n        omega_z = 2 * (dq_w * (-q_z) + dq_x * (-q_y) + dq_y * q_x + dq_z * q_w)\n        \n    else:\n        raise ValueError(f\"Invalid frame '{frame}'. Use 'world', 'device', or 'body'.\")\n    \n    # The w component should be zero for pure rotations (not returned)\n    return omega_x, omega_y, omega_z\n\ndef remove_gravity_from_acc(acc_x, acc_y, acc_z, q_w_norm, q_x_norm, q_y_norm, q_z_norm):\n    gravity_world_x, gravity_world_y, gravity_world_z = 0.0, 0.0, 9.81\n\n    temp_w = q_z_norm * gravity_world_z\n    temp_x = -q_y_norm * gravity_world_z\n    temp_y = q_x_norm * gravity_world_z\n    temp_z = q_w_norm * gravity_world_z\n\n    gravity_sensor_w = temp_w * q_w_norm + temp_x * q_x_norm + temp_y * q_y_norm + temp_z * q_z_norm\n    gravity_sensor_x = temp_w * q_x_norm + temp_x * q_w_norm + temp_y * q_z_norm - temp_z * q_y_norm\n    gravity_sensor_y = temp_w * q_y_norm + temp_x * (-q_z_norm) + temp_y * q_w_norm + temp_z * q_x_norm\n    gravity_sensor_z = temp_w * q_z_norm + temp_x * q_y_norm + temp_y * (-q_x_norm) + temp_z * q_w_norm\n    \n    linear_acc_x = acc_x - gravity_sensor_x\n    linear_acc_y = acc_y - gravity_sensor_y\n    linear_acc_z = acc_z - gravity_sensor_z\n    return linear_acc_x, linear_acc_y, linear_acc_z\n\ndef rotate_quaternion_by_axis(q, theta, axis='z',frame='world'):\n    axis_idx={'x':1,'y':2,'z':3}\n    half_theta = theta / 2.0\n    cos_h = np.cos(half_theta)\n    sin_h = np.sin(half_theta)\n\n    q_theta = np.array([cos_h, 0.0, 0.0, 0.0])\n    q_theta[axis_idx[axis]]=sin_h\n    if frame=='world':\n        a_w, a_x, a_y, a_z = q_theta\n        b_w, b_x, b_y, b_z = q[:,0],q[:,1],q[:,2],q[:,3]\n    elif frame=='device':\n        b_w, b_x, b_y, b_z = q_theta  # device rotation\n        a_w, a_x, a_y, a_z = q[:,0], q[:,1], q[:,2], q[:,3]  # original quaternion\n    return np.array([\n        a_w * b_w - a_x * b_x - a_y * b_y - a_z * b_z,\n        a_w * b_x + a_x * b_w + a_y * b_z - a_z * b_y,\n        a_w * b_y - a_x * b_z + a_y * b_w + a_z * b_x,\n        a_w * b_z + a_x * b_y - a_y * b_x + a_z * b_w\n    ]).T\n\ndef rotate_acc_by_axis(acc_xyz, theta, axis='y'):\n    cos_theta = np.cos(theta)\n    sin_theta = np.sin(theta)\n    \n    acc_x_orig = acc_xyz[:, 0]\n    acc_y_orig = acc_xyz[:, 1]\n    acc_z_orig = acc_xyz[:, 2]\n    \n    if axis == 'x':\n        acc_x_new = acc_x_orig  # No change along rotation axis\n        acc_y_new = cos_theta * acc_y_orig + sin_theta * acc_z_orig\n        acc_z_new = -sin_theta * acc_y_orig + cos_theta * acc_z_orig       \n    elif axis == 'y':\n        acc_x_new = cos_theta * acc_x_orig + sin_theta * acc_z_orig\n        acc_y_new = acc_y_orig  # No change along rotation axis\n        acc_z_new = -sin_theta * acc_x_orig + cos_theta * acc_z_orig\n    elif axis == 'z':\n        acc_x_new = cos_theta * acc_x_orig + sin_theta * acc_y_orig\n        acc_y_new = -sin_theta * acc_x_orig + cos_theta * acc_y_orig\n        acc_z_new = acc_z_orig  # No change along rotation axis\n    return np.stack([acc_x_new, acc_y_new, acc_z_new], axis=1)\n\n\ndef get_gravity_proj(rot_mat):\n    q_w_norm, q_x_norm, q_y_norm, q_z_norm=rot_mat[:,0],rot_mat[:,1],rot_mat[:,2],rot_mat[:,3]\n    gravity_world_x, gravity_world_y, gravity_world_z = 0.0, 0.0, 9.81\n\n    temp_w = q_z_norm * gravity_world_z\n    temp_x = -q_y_norm * gravity_world_z\n    temp_y = q_x_norm * gravity_world_z\n    temp_z = q_w_norm * gravity_world_z\n    \n    gravity_sensor_w = temp_w * q_w_norm + temp_x * q_x_norm + temp_y * q_y_norm + temp_z * q_z_norm\n    gravity_sensor_x = temp_w * q_x_norm + temp_x * q_w_norm + temp_y * q_z_norm - temp_z * q_y_norm\n    gravity_sensor_y = temp_w * q_y_norm + temp_x * (-q_z_norm) + temp_y * q_w_norm + temp_z * q_x_norm\n    gravity_sensor_z = temp_w * q_z_norm + temp_x * q_y_norm + temp_y * (-q_x_norm) + temp_z * q_w_norm\n    res=np.array([\n        gravity_sensor_x,\n        gravity_sensor_y,\n        gravity_sensor_z,\n    ]).T\n    return res\n\n\ndef get_world_acc(v_x, v_y, v_z, q_w, q_x, q_y, q_z):\n    temp_w = -q_x * v_x - q_y * v_y - q_z * v_z\n    temp_x = q_w * v_x + q_y * v_z - q_z * v_y\n    temp_y = q_w * v_y + q_z * v_x - q_x * v_z\n    temp_z = q_w * v_z + q_x * v_y - q_y * v_x\n    \n    result_w = temp_w * q_w + temp_x * q_x + temp_y * q_y + temp_z * q_z\n    result_x = temp_w * (-q_x) + temp_x * q_w + temp_y * (-q_z) - temp_z * (-q_y)\n    result_y = temp_w * (-q_y) + temp_x * q_z + temp_y * q_w - temp_z * (-q_x)\n    result_z = temp_w * (-q_z) + temp_x * (-q_y) + temp_y * q_x + temp_z * q_w\n    result_z-=9.81\n    return result_x, result_y, result_z\n\ndef interpolate_vector_to_length(input_vec, expected_len,method='nearest'):\n    L = len(input_vec)\n    if expected_len == L:\n        return input_vec\n    input_vec = torch.tensor(input_vec)\n    ori_shape = input_vec.shape\n    if len(ori_shape)>2:\n        input_vec=input_vec.reshape(L,-1)\n    input_reshaped = input_vec.permute(1, 0).unsqueeze(0)\n    interpolated_reshaped = F.interpolate(input_reshaped,\n                                          size=expected_len,\n                                          mode=method,\n                                         ) \n    interpolated_vec = interpolated_reshaped.squeeze(0).permute(1, 0)\n    if len(ori_shape)>2:\n        interpolated_vec = interpolated_vec.reshape((expected_len,)+ori_shape[1:])\n    return interpolated_vec.numpy()\n\ndef nonuniform_time_streching(X, exp_L):\n    L = len(X)\n    if L==exp_L:\n        return X\n    full_index=np.arange(L)\n    if exp_L<L:\n        index=np.random.choice(full_index,exp_L,replace=False)\n    else:\n        index=np.concatenate([\n            full_index,\n            np.random.choice(full_index,exp_L-L,replace=True)\n        ])\n    index=np.sort(index)\n    X=X[index].copy()\n    return X\n\ndef pixel_jitter(x, max_shift=1):\n    L, C, H, W = x.shape\n    shifted = x.copy()\n    \n    # random shift dx, dy\n    dx = random.randint(-max_shift, max_shift)\n    dy = random.randint(-max_shift, max_shift)\n    \n    # define source & target slices\n    src_x_start = max(0, -dx)\n    src_x_end = min(W, W - dx) \n    src_y_start = max(0, -dy)\n    src_y_end = min(H, H - dy)\n    \n    tgt_x_start = max(0, dx)\n    tgt_x_end = min(W, W + dx)\n    tgt_y_start = max(0, dy)\n    tgt_y_end = min(H, H + dy)\n    \n    shifted[:, :, tgt_y_start:tgt_y_end, tgt_x_start:tgt_x_end] = \\\n        x[:, :, src_y_start:src_y_end, src_x_start:src_x_end]\n    \n    return shifted\n\ndef patch_erase(x,patch_size=2,erase_value=np.nan):\n    x=x.copy()\n    L, C, H, W = x.shape\n    top = random.randint(0, H - patch_size)\n    left = random.randint(0, W - patch_size)\n    x[:, :, top:top+patch_size, left:left+patch_size] = erase_value\n    return x\n\ndef pad_seq(X,L,value=0):\n    shape=list(X.shape)\n    if len(X)<L:\n        shape[0]=L-len(X)\n        X=np.concatenate([\n            value*np.ones(shape),\n            X,\n        ],axis=0)\n    return X\n\ndef get_signed_rot(rot_mat):\n    sign_vec=np.ones(len(rot_mat))\n    sign_vec[1:]=np.sign((rot_mat[1:]*rot_mat[:-1]).sum(axis=1))\n    sign_vec[~(sign_vec<0)]=1\n    sign_vec=np.cumprod(sign_vec)\n    rot_mat=rot_mat*sign_vec.reshape((-1,1))\n    rot_mat /= np.linalg.norm(rot_mat, axis=1, keepdims=True)\n    return rot_mat\n\ndef extract_data_dict(sub_df,sub_meta,test_mode=False):\n    data_dict={\n        'X_meta':None,\n        'X_imu':None,\n        'Y_class':None,\n        'Y_ori':None,\n        'Y_bhv':None,\n        'target_len':None,\n    }\n    ### 1.target info\n    if not test_mode:\n        Y_class=sub_df['gesture'].iloc[0]\n        Y_class=CLASS_CODE.get(Y_class,None)\n        Y_ori=sub_df['orientation'].iloc[0]\n        Y_ori=ORI_CODE.get(Y_ori,None)\n        # (multi) class labels\n        data_dict['Y_class']=int(Y_class)\n        data_dict['Y_ori']=int(Y_ori)\n        data_dict['target_len']=int((sub_df['behavior']=='Performs gesture').sum())\n        # 'orientation' (1 per seq)\n        # behavior (seq)\n        Y_bhv=sub_df['behavior'].apply(BHV_CODE.get).values\n        data_dict['Y_bhv']=Y_bhv.astype('int64')\n\n    ### 2.meta data\n    data_dict['X_meta']=sub_meta[['adult_child', 'age', 'sex', 'handedness', 'height_cm', 'shoulder_to_wrist_cm', 'elbow_to_wrist_cm']].values.astype('float32')\n\n    ### 3.IMU data\n    X_imu=sub_df[['acc_x', 'acc_y','acc_z', 'rot_w', 'rot_x', 'rot_y', 'rot_z']].values.copy()\n    X_imu[:,3:7]=get_signed_rot(X_imu[:,3:7])\n    data_dict['X_imu']=X_imu.astype('float32')\n    ### handedness flip\n    if data_dict['X_meta'][3]==0:\n        data_dict['X_imu'][:,0]*=-1 # acc_x\n        data_dict['X_imu'][:,5]*=-1 # rot_y\n        data_dict['X_imu'][:,6]*=-1 # rot_z\n\n    ### 4.thm data\n    X_thm=sub_df[['thm_1','thm_2','thm_3','thm_4','thm_5',]].values\n    # processing abnormal thm values\n    col_mean=np.nanmean(X_thm,axis=0)\n    col_std=np.nanstd(X_thm,axis=0)\n    abnormal_cond=(col_mean>40) | (col_mean<20) | (col_std==0)\n    X_thm[:,abnormal_cond]=np.nan\n    data_dict['X_thm']=X_thm.astype('float32')\n    ### handedness flip\n    # switch thm_3,thm_5\n    if data_dict['X_meta'][3]==0:\n        data_dict['X_thm'][:,[2,4]]=data_dict['X_thm'][:,[4,2]]\n\n    ### 5.tof data\n    tof_groups=[]\n    for i in range(1,6):\n        tof_cols=[]\n        for j in range(64):\n            tof_cols.append(f'tof_{i}_v{j}')\n        tof_groups.append(tof_cols)\n    X_tof=[]\n    for i,tof_cols in enumerate(tof_groups):\n        M=sub_df[tof_cols].replace(-1, np.nan)\n        # range beyond training set\n        M[(M<0)|(M>249)]=np.nan\n        M_valid=(~M.isna()).astype('float32').values.reshape((-1,1,8,8))\n        M=M.values\n        M=M.reshape((-1,1,8,8))\n        ### handedness flip\n        if data_dict['X_meta'][3]==0:\n            if i in [2,4]:\n                M=M[:,:,::-1]\n                M_valid=M_valid[:,:,::-1]\n            else:\n                M=M[:,:,:,::-1]\n                M_valid=M_valid[:,:,:,::-1]\n        X_tof.append(M)\n        X_tof.append(M_valid)\n    X_tof=np.concatenate(X_tof,axis=1)\n    ### handedness flip\n    # switch tof_3,tof_5\n    if data_dict['X_meta'][3]==0:\n        X_tof[:,[4,5,8,9]]=X_tof[:,[8,9,4,5]]\n    data_dict['X_tof']=X_tof.astype('float32')\n    return data_dict\n\n\nclass CMI3Dataset(Dataset):\n    def __init__(self, data_dicts, index=[], max_len=200,augmentation=False,random_clip_q=None,clip_neg_p=None,index_class=[],class_index={},\n                 mixup=False,mixup_prob=1.0,mixup_alpha=0.0,world_xy_rotate_prob=1.0,world_xy_rotate_span=1/6,\n                 acc_mask_prob=0.1,rot_mask_prob=0.1,random_mask_prob=0.5,random_mask_ratio=0.1,random_mask_type='random',\n                 time_scaling_prob=1.0,time_scaling_lb=0.8,time_scaling_ub=1.2,time_scaling_type='uniform',\n                 thm_mask_prob=0.1,tof_mask_prob=0.1,pixel_jitter_prob=0.5,pixel_jitter_shift=1,\n                 tof_patch_erase_prob=0.5,tof_patch_erase_size=2,rot_flip=True,\n                ):\n        self.data_dicts = data_dicts\n        self.index=index\n        ## mixup class\n        self.index_class=index_class\n        self.class_index=class_index\n        #\n        self.max_len=max_len\n        self.augmentation=augmentation\n\n        # augmentation\n        self.mixup=mixup\n        self.mixup_prob=mixup_prob\n        self.mixup_alpha=mixup_alpha\n\n        self.rot_flip=rot_flip\n        \n        self.world_xy_rotate_prob=world_xy_rotate_prob\n        self.world_xy_rotate_span=world_xy_rotate_span\n\n        self.random_mask_prob=random_mask_prob\n        self.random_mask_ratio=random_mask_ratio\n        self.random_mask_type=random_mask_type\n\n        self.time_scaling_prob=time_scaling_prob\n        self.time_scaling_type=time_scaling_type\n        self.time_scaling_range=(time_scaling_lb,time_scaling_ub)\n\n        self.pixel_jitter_prob=pixel_jitter_prob\n        self.pixel_jitter_shift=pixel_jitter_shift\n\n        self.tof_patch_erase_prob=tof_patch_erase_prob\n        self.tof_patch_erase_size=tof_patch_erase_size\n\n        self.acc_mask_prob=acc_mask_prob\n        self.rot_mask_prob=rot_mask_prob\n        self.thm_mask_prob=thm_mask_prob\n        self.tof_mask_prob=tof_mask_prob\n\n    def __len__(self):\n        return len(self.index)\n\n    def proc_data_dict(self, data_dict):\n        data_dict=copy.deepcopy(data_dict)\n        # augmentation on source cols\n        if self.augmentation:\n           data_dict=self.data_augmentation(data_dict)\n\n        batch_data={}\n        ### target\n        if data_dict.get('Y_class') is not None:\n            Y_class=data_dict['Y_class']\n            Y_prob=np.zeros(len(CLASS_CODE))\n            Y_prob[Y_class]=1\n            batch_data['Y_prob']=Y_prob\n            batch_data['Y_class']=Y_class\n            # class weighting\n            batch_data['weight']= 1 if Y_class<8 else 0.2\n            # orientation target\n            Y_ori=np.zeros(len(ORI_CODE))\n            Y_ori[data_dict['Y_ori']]=1\n            batch_data['Y_ori']=Y_ori\n            # target region ratio\n            batch_data['Y_ratio']=min(data_dict['target_len']/self.max_len,1.0)\n            batch_data['target_len']=data_dict['target_len']\n \n        ### X   \n        # 1.meta data\n        # adult_child', 'age', 'sex', 'handedness', 'height_cm', 'shoulder_to_wrist_cm', 'elbow_to_wrist_cm'\n        X_meta=data_dict['X_meta']\n \n        # 2.IMU\n        # 'acc_x', 'acc_y','acc_z', 'rot_w', 'rot_x', 'rot_y', 'rot_z'\n        X_imu=data_dict['X_imu']\n        \n        # ### handedness flip\n        # if X_meta[3]==0:\n        #     X_imu[:,0]*=-1 # acc_x\n        #     X_imu[:,5]*=-1 # rot_y\n        #     X_imu[:,6]*=-1 # rot_z\n\n        #######\n        X_imu=X_imu[-self.max_len:]\n        X_feat=np.zeros((len(X_imu),36),dtype=np.float32)\n        # work with bin size 4\n        start_idx=0\n        bin_size=4\n        ## 1.raw acc\n        X_feat[:,start_idx:start_idx+3]=X_imu[:,:3]\n        start_idx+=bin_size\n        ## 2.raw rot\n        X_feat[:,start_idx:start_idx+4]=X_imu[:,3:7]\n        start_idx+=bin_size\n        ## 3.angular velocity (wolrd frame)\n        rot_vel_x,rot_vel_y,rot_vel_z=angular_velocity_from_quaternion(X_imu[:,3],X_imu[:,4],X_imu[:,5],X_imu[:,6],frame='world')\n        X_feat[:,start_idx]=rot_vel_x\n        X_feat[:,start_idx+1]=rot_vel_y\n        X_feat[:,start_idx+2]=rot_vel_z\n        start_idx+=bin_size\n        ## 4.angular velocity (device frame)\n        rot_vel_x,rot_vel_y,rot_vel_z=angular_velocity_from_quaternion(X_imu[:,3],X_imu[:,4],X_imu[:,5],X_imu[:,6],frame='device')\n        X_feat[:,start_idx]=rot_vel_x\n        X_feat[:,start_idx+1]=rot_vel_y\n        X_feat[:,start_idx+2]=rot_vel_z\n        start_idx+=bin_size\n        ## 5.acc without gravity (world frame)\n        macc_x,macc_y,macc_z=get_world_acc(X_imu[:,0],X_imu[:,1],X_imu[:,2],X_imu[:,3],X_imu[:,4],X_imu[:,5],X_imu[:,6])\n        X_feat[:,start_idx]=macc_x\n        X_feat[:,start_idx+1]=macc_y\n        X_feat[:,start_idx+2]=macc_z\n        start_idx+=bin_size\n        ## 6.acc without gravity (device frame)\n        macc_x,macc_y,macc_z=remove_gravity_from_acc(X_imu[:,0],X_imu[:,1],X_imu[:,2],X_imu[:,3],X_imu[:,4],X_imu[:,5],X_imu[:,6])\n        X_feat[:,start_idx]=macc_x\n        X_feat[:,start_idx+1]=macc_y\n        X_feat[:,start_idx+2]=macc_z\n        start_idx+=bin_size\n        ## 7.rot_vel abs diff\n        X_feat[1:,start_idx:start_idx+3]=np.abs(X_feat[1:,8:11]-X_feat[:-1,8:11])\n        start_idx+=bin_size\n        ## 8.meta featrues\n        X_feat[:,start_idx]=X_meta[3] # handedness\n        start_idx+=bin_size\n        ## 9.gravity proj in device frame\n        X_feat[:,start_idx:start_idx+3]=get_gravity_proj(X_imu[:,3:7])\n        start_idx+=bin_size\n        ##\n        X_feat=np.sign(X_feat)*np.log1p(np.abs(X_feat))\n        X_feat=pad_seq(X_feat,self.max_len, 0.0)\n        X_feat=np.nan_to_num(X_feat)\n        batch_data['X_imu']=X_feat.astype('float32')\n\n        # 2.thm\n        X_thm=data_dict['X_thm']\n        X_thm=X_thm[-self.max_len:]\n        X_thm_feat=X_thm.copy()\n        # simple normalize since temp in stable range\n        X_thm_feat=(X_thm_feat-27)/3\n        ##\n        # zero comes: 1.pad 2.nan filling (from raw data and random mask) 3.channel mask in data\n        X_thm_feat=np.sign(X_thm_feat)*np.log1p(np.abs(X_thm_feat))\n        X_thm_feat=pad_seq(X_thm_feat,self.max_len, 0.0)\n        X_thm_feat=np.nan_to_num(X_thm_feat)\n        batch_data['X_thm']=X_thm_feat.astype('float32')\n\n        # 3.tof (L,5,8,8)\n        X_tof=data_dict['X_tof']\n        X_tof=X_tof[-self.max_len:]\n        X_tof_feat=X_tof.copy()\n        # simple normalize since tof in stable range\n        X_tof_feat[:,::2]=(250-X_tof_feat[:,::2])/65\n        #\n        X_tof_feat=pad_seq(X_tof_feat,self.max_len, 0.0)\n        X_tof_feat=np.nan_to_num(X_tof_feat)\n        batch_data['X_tof']=X_tof_feat.astype('float32')\n        return batch_data\n\n    def __getitem__(self, iter_idx):\n        idx=self.index[iter_idx]\n        data_dict=self.data_dicts[idx]\n        batch_data=self.proc_data_dict(data_dict)\n        if self.mixup and random.random()<=self.mixup_prob:\n            if len(self.index_class)>0:\n                # mixup within class\n                idx2=np.random.choice(self.class_index[self.index_class[iter_idx]])\n            else:\n                idx2=np.random.choice(self.index)\n            data_dict2=self.data_dicts[idx2]\n            batch_data2=self.proc_data_dict(data_dict2)\n            lambda_ = np.random.beta(self.mixup_alpha, self.mixup_alpha)\n            batch_data['Y_class']=np.nan\n            ### mixup\n            for k in ['X_imu',\n                      'X_thm','X_tof',\n                      'Y_prob','Y_ori','Y_ratio','weight']:\n                batch_data[k]=lambda_*batch_data[k]+(1-lambda_)*batch_data2[k]\n        return batch_data\n\n    def data_augmentation(self,data_dict):\n        '''\n        won't pass here while inference, TTA implement design later\n        '''\n        X_imu=data_dict['X_imu']\n        X_thm=data_dict['X_thm']\n        X_tof=data_dict['X_tof']\n        X_meta=data_dict['X_meta']\n        target_len=data_dict['target_len']\n\n        ### 1.signed rot flip\n        if self.rot_flip and random.random()<=0.5:\n            X_imu[:,3:7]*=-1\n\n        ### 2.face direction rotation\n        if random.random()<=self.world_xy_rotate_prob:\n            angle_span= self.world_xy_rotate_span\n            theta = np.random.uniform(-np.pi*angle_span, np.pi*angle_span)\n            X_imu[:,3:7]=rotate_quaternion_by_axis(X_imu[:,3:7],theta,axis='z',frame='world')\n\n        ### 3.time scaling\n        if random.random()<=self.time_scaling_prob:\n            lb,ub=self.time_scaling_range\n            c=np.random.uniform(lb,ub)\n            L=int(len(X_imu)*c)\n            if self.time_scaling_type=='uniform':\n                X_imu=interpolate_vector_to_length(X_imu,L)\n                X_thm=interpolate_vector_to_length(X_thm,L)\n                X_tof=interpolate_vector_to_length(X_tof,L)\n            else:\n                X_imu=nonuniform_time_streching(X_imu,L)\n                X_thm=nonuniform_time_streching(X_thm,L)\n                X_tof=nonuniform_time_streching(X_tof,L)\n            target_len=int(c*target_len)\n\n        ### 4.tof pixel shift\n        if random.random()<=self.pixel_jitter_prob:\n            C = X_tof.shape[1]\n            bin_size = C//5\n            for i in range(5):\n                X_tof[:,i*bin_size:(i+1)*bin_size]=pixel_jitter(X_tof[:,i*bin_size:(i+1)*bin_size], max_shift=self.pixel_jitter_shift)\n\n        ### 5.tof patch erase\n        if random.random()<=self.tof_patch_erase_prob:\n            C = X_tof.shape[1]\n            bin_size = C//5\n            for i in range(5):\n                X_tof[:,i*bin_size:(i+1)*bin_size]=patch_erase(\n                    X_tof[:,i*bin_size:(i+1)*bin_size],\n                    patch_size=self.tof_patch_erase_size,\n                    erase_value=np.nanmean(X_tof[:,i*bin_size:(i+1)*bin_size],axis=(0,2,3),keepdims=True)\n                    )\n\n        ### 6. random mask\n        if random.random()<=self.random_mask_prob:\n            max_mask_ratio=self.random_mask_ratio\n            seq_L=len(X_imu)\n            max_mask_len=max(1,int(max_mask_ratio*min(seq_L,self.max_len)))\n            if self.random_mask_type=='random':\n                mask_len=random.randint(1, max_mask_len)\n            else: # 'fixed'\n                mask_len=max_mask_len\n            mask_start_idx=random.randint(max(0,seq_L-self.max_len), seq_L-mask_len)\n            X_imu[mask_start_idx:mask_start_idx+mask_len]=np.nan\n            for X in [X_thm,X_tof]:\n                channel_bin_size=X.shape[1]//5\n                for i in range(5):\n                    if self.random_mask_type=='random':\n                        mask_len=random.randint(1, max_mask_len)\n                    else: # 'fixed'\n                        mask_len=max_mask_len\n                    mask_start_idx=random.randint(max(0,seq_L-self.max_len), seq_L-mask_len)\n                    X[mask_start_idx:mask_start_idx+mask_len,channel_bin_size*i:channel_bin_size*(i+1)]=np.nan\n        \n        ### 7.sensor mask\n        # acc&rot\n        if random.random()<self.rot_mask_prob:\n            # rot mask:cause many derived col be nan\n            X_imu[:,3:7]=np.nan\n        elif random.random()<self.acc_mask_prob:\n            # acc mask\n            X_imu[:,0:3]=np.nan\n        # thm\n        if random.random()<self.thm_mask_prob:\n            X_thm[:]=np.nan\n        if random.random()<self.tof_mask_prob:\n            X_tof[:]=np.nan\n        \n        ###\n        data_dict['X_imu']=X_imu\n        data_dict['X_thm']=X_thm\n        data_dict['X_tof']=X_tof\n        data_dict['X_meta']=X_meta\n        data_dict['target_len']=target_len\n        return data_dict","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T11:56:14.668844Z","iopub.execute_input":"2025-08-28T11:56:14.669317Z","iopub.status.idle":"2025-08-28T11:56:14.687023Z","shell.execute_reply.started":"2025-08-28T11:56:14.669287Z","shell.execute_reply":"2025-08-28T11:56:14.686224Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# training","metadata":{}},{"cell_type":"code","source":"def soft_cross_entropy(pred, soft_targets,label_smoothing=0.0,weight=None,reduce=True):\n    class_num=soft_targets.shape[1]\n    soft_targets=soft_targets+label_smoothing/class_num\n    soft_targets=soft_targets/soft_targets.sum(axis=1,keepdims=True)\n    \n    log_probs = F.log_softmax(pred, dim=1)\n    if weight is not None:\n        log_probs=log_probs*weight.reshape((-1,1))\n    loss = -(soft_targets * log_probs).sum(dim=1)\n    if reduce:\n        return loss.mean()\n    else:\n        return loss\n\ndef custom_dice_loss(logits, target, smooth=1.0):\n    B, C = logits.shape\n    assert C == 18, \"Expected 18 output classes\"\n\n    probs = F.softmax(logits, dim=1)  # (B, 18)\n\n    # ----- Binary Dice over first 8 classes -----\n    pred_bin = probs[:, :8].sum(dim=1)        # (B,)\n    target_bin = target[:, :8].sum(dim=1)     # (B,)\n\n    inter_bin = (pred_bin * target_bin).sum()\n    pred_bin_sum = pred_bin.sum()\n    target_bin_sum = target_bin.sum()\n\n    dice_binary = (2 * inter_bin + smooth) / (pred_bin_sum + target_bin_sum + smooth)\n    loss_binary = 1 - dice_binary\n\n    probs_mc = probs[:, :8]          \n    target_mc = target[:, :8]         \n\n    inter_mc = (probs_mc * target_mc).sum(dim=0)    \n    pred_mc_sum = probs_mc.sum(dim=0)              \n    target_mc_sum = target_mc.sum(dim=0)            \n\n    dice_mc = (2 * inter_mc + smooth) / (pred_mc_sum + target_mc_sum + smooth)  \n    loss_mc = 1 - dice_mc\n    loss_macro_mc = loss_mc.mean()  # average over 8 classes\n\n    # ----- Final loss: match the metric weighting -----\n    final_loss = 0.5 * loss_binary + 0.5 * loss_macro_mc\n\n    return final_loss\n    \nclass ModelEMA(nn.Module):\n    def __init__(self, model, decay=0.99, device=None):\n        super().__init__()\n        self.module = copy.deepcopy(model)\n        self.module.eval()\n        self.decay = decay\n        self.device = device\n        if self.device is not None:\n            self.module.to(device=device)\n\n    def _update(self, model, update_fn):\n        with torch.no_grad():\n            for ema_v, model_v in zip(self.module.state_dict().values(), model.state_dict().values()):\n                if self.device is not None:\n                    model_v = model_v.to(device=self.device)\n                ema_v.copy_(update_fn(ema_v, model_v))\n\n    def update(self, model):\n        self._update(model, update_fn=lambda e, m: self.decay * e + (1. - self.decay) * m)\n\n    def set(self, model):\n        self._update(model, update_fn=lambda e, m: m)\n\n\ndef train_NN_model(model,data_dicts,train_index,val_index,train_index_class=[],train_class_index={},verbose=1):\n    train_dataset = CMI3Dataset(\n        data_dicts,index=train_index,max_len=CFG.max_len,\n        augmentation=True,\n        random_clip_q=CFG.random_clip_q,\n        clip_neg_p =CFG.clip_neg_p,\n        index_class=train_index_class,\n        class_index=train_class_index,\n        **CFG.aug_cfg,\n    )\n    train_loader = DataLoader(train_dataset, batch_size=CFG.batch_size, shuffle=True, pin_memory=True, drop_last=True, num_workers=CFG.cores, persistent_workers=True)\n    val_dataset = CMI3Dataset(data_dicts,index=val_index,max_len=CFG.max_len)\n    val_loader = DataLoader(val_dataset, batch_size=CFG.batch_size, shuffle=False, pin_memory=True, drop_last=False, num_workers=CFG.cores, persistent_workers=True)\n    \n    model.to(CFG.device)\n    \n    if CFG.EMA:\n        ema_model= ModelEMA(\n            model, \n            decay=CFG.EMA_decay,\n            device=CFG.EMA_device\n        )\n        print('init EMA model with decay:',CFG.EMA_decay)\n    else:\n        ema_model=None\n\n    optimizer = torch.optim.AdamW(model.parameters(), lr=CFG.lr, weight_decay=CFG.weight_decay)\n    if CFG.lr_sch=='cos':\n        constant_epochs=int(CFG.constant_lr_ratio*CFG.epochs)\n        decay_epochs=CFG.epochs-constant_epochs\n        scheduler=torch.optim.lr_scheduler.CosineAnnealingLR(\n            optimizer,\n            T_max=decay_epochs*len(train_loader),\n        )\n    elif CFG.lr_sch=='onecycle':\n        scheduler = torch.optim.lr_scheduler.OneCycleLR(\n            optimizer, max_lr=CFG.lr, steps_per_epoch=len(train_loader), epochs=CFG.epochs,\n        )\n        constant_epochs,decay_epochs=0,CFG.epochs\n    scaler = GradScaler()\n    \n    res={\n        'train_loss':[],\n        'val_loss':[],\n        'best_weight':None,\n        'best_val_loss':None,\n        'best_epoch':None,\n        'val_target':None,\n        'val_pred':None,\n        }\n    \n    best_val_loss=1.0\n    for epoch in range(CFG.epochs):\n        # training\n        model.train()\n        total_loss=0\n        total_count=0\n        tbar=train_loader\n        for batch_data in tbar:\n            for k,v in batch_data.items():\n                batch_data[k]=v.to(CFG.device)\n            with autocast(device_type=CFG.device, dtype=CFG.cast_type):\n                Y_pred,Y_ori,Y_ratio,Y_entropy = model(batch_data,extra_heads=True)        \n                loss_ce = soft_cross_entropy(Y_pred,batch_data['Y_prob'],label_smoothing=CFG.label_smoothing) #,weight=batch_data['weight'])\n                loss_dice = custom_dice_loss(Y_pred,batch_data['Y_prob'])\n                ce_ratio = (CFG.epochs-epoch)/CFG.epochs\n                ce_ratio = ce_ratio**CFG.ce_ratio_power\n                loss1 = ce_ratio*loss_ce + (1-ce_ratio)*loss_dice\n                loss2 = soft_cross_entropy(Y_ori,batch_data['Y_ori'],label_smoothing=CFG.label_smoothing) #,weight=batch_data['weight'])\n                loss3 = ((Y_ratio-batch_data['Y_ratio'])**2).mean()\n                loss4 = torch.abs(soft_cross_entropy(Y_pred,batch_data['Y_prob'],reduce=False)-Y_entropy).mean()\n                loss = (loss1 + CFG.ori_loss_weight*loss2 + CFG.ratio_loss_weight*loss3 + CFG.entropy_loss_weight*loss4)/\\\n                       (1 + CFG.ori_loss_weight + CFG.ratio_loss_weight + CFG.entropy_loss_weight)\n            scaler.scale(loss).backward()\n            scaler.step(optimizer)\n            scaler.update()\n            optimizer.zero_grad()\n            if epoch>=constant_epochs:\n                scheduler.step()    \n            if ema_model is not None:\n                ema_model.update(model)\n            total_loss+=loss.item()\n            total_count+=1\n            del batch_data\n            del Y_pred\n\n        train_loss=total_loss/(total_count+1e-6)\n        \n        # evaluation\n        model.eval()\n        Y_val=[]\n        Y_pred_val=[]\n        for batch_data in val_loader:\n            for k,v in batch_data.items():\n                batch_data[k]=v.to(CFG.device)\n            with torch.inference_mode():\n                with autocast(device_type=CFG.device, dtype=CFG.cast_type):\n                    if ema_model is not None: \n                        Y_pred = ema_model.module(batch_data)\n                    else:\n                        Y_pred = model(batch_data)\n            Y_val.append(batch_data['Y_class'])\n            Y_pred_val.append(Y_pred)\n    \n            del batch_data\n            del Y_pred\n\n        Y_val=torch.cat(Y_val).cpu()\n        Y_pred_val=torch.cat(Y_pred_val).cpu()\n        # compute metric\n        val_loss=1-comp_metric(prob_postprocess(Y_pred_val),Y_val)\n        append_msg=f''\n        if val_loss<best_val_loss:\n            append_msg=f'(best updated from {round(1-best_val_loss,3)})'\n            best_val_loss=val_loss\n            res['best_val_loss']=best_val_loss\n            res['best_epoch']=epoch+1\n            if ema_model is not None:\n                res['best_weight'] = {k:v.cpu() for k,v in ema_model.module.state_dict().items()}\n            else:\n                res['best_weight'] = {k:v.cpu() for k,v in model.state_dict().items()}\n            res['val_target']=Y_val\n            res['val_pred']=Y_pred_val\n        if epoch%verbose==0 or len(append_msg)>0:\n            print(f'### epoch{epoch+1} train_loss:{round(train_loss,3)} val_score:{round(1-val_loss,3)}'+append_msg)\n        res['train_loss'].append(train_loss)\n        res['val_loss'].append(val_loss)\n        del Y_val\n        del Y_pred_val\n        gc.collect()\n    return res\n    \n\ndef kfold_train(info,data_dicts):\n    sgkf = StratifiedGroupKFold(n_splits=5,random_state=777, shuffle=True)\n    splits=sgkf.split(info, info['target'].values, info['subject'].values)\n    res={}\n    fold_scores=[]\n    for i,(train_index,val_index) in enumerate(splits):\n        print(f'******************** START FOLD {i} TRAINING ********************')\n        model=CMI3Net(\n            **CFG.model_cfg,\n        )\n        ## global mixup\n        train_index_class,train_class_index=[],{}\n\n        fold_res=train_NN_model(model,data_dicts,train_index,val_index,train_index_class,train_class_index,verbose=10)\n        fold_res['info']=info.loc[val_index]\n        res[i]=fold_res\n        fold_scores.append(1-fold_res['best_val_loss'])\n    \n    Y=[]\n    Y_pred=[]\n    for k,v in res.items():\n        Y.append(v['val_target'])\n        Y_pred.append(v['val_pred'])\n    Y=torch.cat(Y)\n    Y_pred=torch.cat(Y_pred)\n    oof_score=comp_metric(prob_postprocess(Y_pred),Y)\n    avg_score=np.mean(fold_scores)\n    score_std=np.std(fold_scores)\n    print(f'**** oof score:{oof_score:.4f} avg score:{avg_score:.4f}(+-{score_std:.4f})****')\n    return res,oof_score,score_std","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T11:56:14.688756Z","iopub.execute_input":"2025-08-28T11:56:14.689354Z","iopub.status.idle":"2025-08-28T11:56:14.718387Z","shell.execute_reply.started":"2025-08-28T11:56:14.689325Z","shell.execute_reply":"2025-08-28T11:56:14.717675Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"%%writefile _model.py\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom typing import Optional, Tuple\n\nclass CBAM(nn.Module):\n    def __init__(self, channels, reduction=8, kernel_size=3):\n        super().__init__()\n        # Channel attention\n        self.channel_att = nn.Sequential(\n            nn.AdaptiveAvgPool1d(1),\n            nn.Conv1d(channels, channels // reduction, 1, bias=False),\n            nn.ReLU(inplace=True),\n            nn.Conv1d(channels // reduction, channels, 1, bias=False),\n        )\n        \n        # Spatial attention (temporal attention for 1D)\n        self.spatial_att = nn.Sequential(\n            nn.Conv1d(2, 1, kernel_size=kernel_size, padding=kernel_size//2, bias=False),\n            nn.Sigmoid()\n        )\n        \n    def forward(self, x):\n        # Channel attention\n        avg_pool = F.adaptive_avg_pool1d(x, 1)\n        max_pool = F.adaptive_max_pool1d(x, 1)\n        \n        avg_ca = self.channel_att(avg_pool)\n        max_ca = self.channel_att(max_pool)\n        ca = torch.sigmoid(avg_ca + max_ca)\n        x = x * ca\n        \n        # Spatial attention (temporal for 1D)\n        avg_sa = torch.mean(x, dim=1, keepdim=True)\n        max_sa = torch.max(x, dim=1, keepdim=True)[0]\n        sa = self.spatial_att(torch.cat([avg_sa, max_sa], dim=1))\n        x = x * sa\n        \n        return x\n\nclass ParallelConv(nn.Module):\n    def __init__(self, in_channels, out_channels, kernel_sizes=[3, 5, 7], groups=1):\n        super().__init__()\n        self.groups = groups\n        self.kernel_sizes = kernel_sizes\n        self.out_channels = out_channels \n        self.kernels = nn.ModuleList()\n        for ks in kernel_sizes:\n            self.kernels.append(nn.Sequential(\n                nn.Conv1d(in_channels, out_channels, kernel_size=ks, padding='same', groups=groups, bias=False),\n            ))\n        self.bn=nn.BatchNorm1d(out_channels*len(kernel_sizes))\n        self.silu=nn.SiLU(inplace=True)\n        self.shortcut = nn.Conv1d(in_channels, out_channels*len(kernel_sizes), kernel_size=1, groups=groups)\n    \n    def forward(self, x):\n        # x: (B, C, L)\n        B, C, L = x.shape\n        G = self.groups\n        K = len(self.kernel_sizes)\n        C_out = self.out_channels\n        c_out_per_group = C_out // G\n\n        shortcut = self.shortcut(x)\n\n        conv_outputs = []\n        for conv in self.kernels:\n            output = conv(x)\n            output = output.view(B, G, c_out_per_group, L)\n            conv_outputs.append(output)\n        \n        x = torch.stack(conv_outputs, dim=2)\n        x = x.view(B, G * K * c_out_per_group, L)\n        x = self.bn(x)\n\n        x = self.silu(x)\n        x = x + shortcut\n        return x\n        \nclass SCSEBlock(nn.Module):\n    def __init__(self, in_channels, reduction=8):\n        super().__init__()\n        self.cSE = nn.Sequential(\n            nn.AdaptiveAvgPool1d(1),\n            nn.Conv1d(in_channels, in_channels // reduction, 1),\n            nn.Tanh(),\n            nn.Conv1d(in_channels // reduction, in_channels, 1),\n            nn.Sigmoid(),\n        )\n        self.sSE = nn.Sequential(\n            nn.Conv1d(in_channels, 1, 1), \n            nn.Sigmoid(),\n            )\n\n    def forward(self, x):\n        return x * self.cSE(x) + x * self.sSE(x)\n\nclass ResBlock(nn.Module):\n    def __init__(self, in_channels: int, out_channels: int, kernel_size: int = 3,groups=1 ,\n                  dropout: float = 0.0,norm_f='BN',act_f='relu',with_att=True, att_type='SCSE'):\n        super().__init__()\n        if norm_f=='BN':\n            norm_f = nn.BatchNorm1d\n        elif norm_f=='Id':\n            norm_f = lambda x: nn.Identity()\n        elif norm_f=='IN':\n            norm_f = nn.InstanceNorm1d\n        if act_f=='relu':\n            self.act_f=F.relu\n        elif act_f=='silu':\n            self.act_f=F.silu\n        elif act_f=='gelu':\n            self.act_f=F.gelu\n        self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size, groups=groups, padding='same')\n        self.bn1=norm_f(out_channels)\n        self.conv2 = nn.Conv1d(out_channels, out_channels, kernel_size, groups=groups, padding='same')\n        self.bn2 = norm_f(out_channels)\n        self.dropout = nn.Dropout(dropout)\n\n        # Shortcut connection if dimensions differ\n        self.shortcut = nn.Conv1d(in_channels, out_channels, kernel_size=1, groups=groups) if in_channels != out_channels else nn.Identity()\n        if not with_att:\n            self.att = nn.Identity()\n        elif att_type=='SCSE':\n            self.att = SCSEBlock(out_channels)\n        elif att_type=='CBAM':\n            self.att = CBAM(out_channels)\n\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        shortcut = self.shortcut(x)\n        x = self.act_f(self.bn1(self.conv1(x)))\n        x = self.dropout(x)\n        x = self.bn2(self.conv2(x))\n        x = self.att(x)\n        x = x + shortcut\n        x =self.act_f(x)\n        return x\n\nclass SCSEBlock3D(nn.Module):\n    def __init__(self, in_channels, reduction=8):\n        super().__init__()\n        self.cSE = nn.Sequential(\n            nn.AdaptiveAvgPool3d(1),\n            nn.Conv3d(in_channels, in_channels // reduction, 1),\n            nn.Tanh(),\n            nn.Conv3d(in_channels // reduction, in_channels, 1),\n            nn.Sigmoid(),\n        )\n        self.sSE = nn.Sequential(\n            nn.Conv3d(in_channels, 1, 1), \n            nn.Sigmoid(),\n            )\n\n    def forward(self, x):\n        return x * self.cSE(x) + x * self.sSE(x)\n    \n\nclass CBAM3D(nn.Module):\n    def __init__(self, channels, reduction=8):\n        super().__init__()\n        # Channel attention (like SE but with both avg + max pool)\n        self.channel_att = nn.Sequential(\n            nn.AdaptiveAvgPool3d(1),\n            nn.Conv3d(channels, channels//reduction, 1),\n            nn.ReLU(),\n            nn.Conv3d(channels//reduction, channels, 1)\n        )\n        self.max_pool = nn.AdaptiveMaxPool3d(1)\n        \n        # Spatial attention\n        self.spatial_att = nn.Sequential(\n            nn.Conv3d(2, 1, kernel_size=3, padding='same'),\n            nn.Sigmoid()\n        )\n        \n    def forward(self, x):\n        # Channel attention (avg + max pooling)\n        avg_ca = self.channel_att(x)\n        max_ca = self.channel_att(self.max_pool(x).expand_as(x))\n        ca = torch.sigmoid(avg_ca + max_ca)\n        x = x * ca\n        \n        # Spatial attention\n        avg_sa = torch.mean(x, dim=1, keepdim=True)\n        max_sa = torch.max(x, dim=1, keepdim=True)[0]\n        sa = self.spatial_att(torch.cat([avg_sa, max_sa], dim=1))\n        x = x * sa\n        return x\n\nclass ResBlock3D(nn.Module):\n    def __init__(self, in_channels: int, out_channels: int, kernel_size = (3,3,3), groups=1, \n                 dropout = 0.0, D21=False, norm_f='BN', act_f='relu', with_att=True, att_type='SCSE'):\n        super().__init__()\n        if norm_f == 'BN':\n            norm_f = nn.BatchNorm3d\n        elif norm_f == 'Id':\n            norm_f = lambda x: nn.Identity()\n        elif norm_f == 'IN':\n            norm_f = nn.InstanceNorm3d\n        elif norm_f == 'GN':  \n            norm_f = lambda x: nn.GroupNorm(8, x) \n\n        if act_f == 'relu':\n            self.act_f = F.relu\n        elif act_f == 'silu':\n            self.act_f = F.silu\n        elif act_f == 'gelu':\n            self.act_f = F.gelu\n\n        if D21 and len(kernel_size) == 3:\n            ks1 = (1, kernel_size[1], kernel_size[2])\n            ks2 = (kernel_size[0], 1, 1)\n        else:\n            ks1 = kernel_size\n            ks2 = kernel_size\n\n        self.conv1 = nn.Conv3d(in_channels, out_channels, kernel_size=ks1, \n                              groups=groups, padding='same')\n        self.bn1 = norm_f(out_channels)\n        self.conv2 = nn.Conv3d(out_channels, out_channels, kernel_size=ks2, \n                              groups=groups, padding='same')\n        self.bn2 = norm_f(out_channels)\n        self.dropout = nn.Dropout3d(dropout)\n\n        self.shortcut = (nn.Conv3d(in_channels, out_channels, kernel_size=1, groups=groups) \n                        if in_channels != out_channels else nn.Identity())\n\n        if not with_att:\n            self.att = nn.Identity()\n        elif att_type=='SCSE':\n            self.att = SCSEBlock3D(out_channels)\n        elif att_type=='CBAM':\n            self.att = CBAM3D(out_channels)\n\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        shortcut = self.shortcut(x)\n        x = self.act_f(self.bn1(self.conv1(x)))\n        x = self.dropout(x)\n        x = self.bn2(self.conv2(x))\n        x = self.att(x)\n        x = x + shortcut\n        x = self.act_f(x)\n        return x\n\nclass AttentionPooling(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 GRUBlock(nn.Module):\n    def __init__(self, channels, dropout=0.1,norm_f='',act_f=''):\n        super(GRUBlock, self).__init__()\n        self.channels = channels\n        \n        self.gru = nn.GRU(\n            input_size=channels,\n            hidden_size=channels,\n            num_layers=1,\n            batch_first=True,\n            bidirectional=True,\n            dropout=dropout,\n        )\n        # Layer normalization\n        self.layer_norm = nn.LayerNorm(2*channels)\n        self.dropout = nn.Dropout(dropout)\n        self.proj=nn.Linear(2*channels,channels)\n        \n    def forward(self, x):\n        shortcut = x\n        x = x.transpose(1, 2)\n        x, _ = self.gru(x)\n        x = self.layer_norm(x)\n        x = self.dropout(x)\n        x = self.proj(x)\n        x = x.transpose(1, 2)\n        x = x + shortcut\n        x = F.silu(x)\n        return x\n    \nclass RandomChannelMask(nn.Module):\n    def __init__(self, mask_prob=0.1, mask_all_prob=0.0, mask_value=0.0):\n        super().__init__()\n        self.mask_prob = mask_prob\n        self.mask_value = mask_value\n        self.mask_all_prob=mask_all_prob\n    \n    def forward(self, x):\n        # x: (B, C, ...)\n        if not self.training or (self.mask_prob == 0.0 and self.mask_all_prob == 0.0):\n            return x\n        \n        shape = list(x.shape)\n        for i in range(2,len(shape)):\n            shape[i] = 1\n        mask = torch.rand(*shape, device=x.device) > self.mask_prob\n\n        if self.mask_all_prob > 0.0:\n            batch_mask_all = torch.rand(x.shape[0], device=x.device) < self.mask_all_prob\n            if batch_mask_all.any():\n                mask[batch_mask_all] = False\n\n        return torch.where(mask, x, self.mask_value)\n    \nclass RotaryPositionalEmbedding(nn.Module):\n    def __init__(self, head_dim: int, max_len: int = 256, base: float = 10000.0):\n        super().__init__()\n        self.head_dim = head_dim\n        self.max_len = max_len\n        self.base = base\n        \n        # Create inverse frequencies for head_dim//2 pairs\n        inv_freq = 1.0 / (base ** (torch.arange(0, head_dim, 2).float() / head_dim))\n        self.register_buffer('inv_freq', inv_freq)\n        \n    def forward(self, seq_len: int, device: torch.device) -> Tuple[torch.Tensor, torch.Tensor]:\n        t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)\n        freqs = torch.outer(t, self.inv_freq)  # (seq_len, head_dim//2)\n        # Duplicate frequencies for complex rotation\n        freqs = torch.cat([freqs, freqs], dim=-1)  # (seq_len, head_dim)\n        cos = freqs.cos()\n        sin = freqs.sin()\n        return cos, sin\n\ndef rotate_half(x):\n    x1, x2 = x[..., :x.shape[-1]//2], x[..., x.shape[-1]//2:]\n    return torch.cat((-x2, x1), dim=-1)\n\ndef apply_rotary_pos_emb(q, k, cos, sin):\n    # Reshape cos/sin to match q/k dimensions\n    cos = cos.unsqueeze(0).unsqueeze(0)  # (1, 1, seq_len, head_dim)\n    sin = sin.unsqueeze(0).unsqueeze(0)  # (1, 1, seq_len, head_dim)\n    \n    q_rot = (q * cos) + (rotate_half(q) * sin)\n    k_rot = (k * cos) + (rotate_half(k) * sin)\n    return q_rot, k_rot\n\n\nclass RoPEAttention(nn.Module):\n    def __init__(self, dim: int, heads: int = 8, dropout: float = 0.1):\n        super().__init__()\n        assert dim % heads == 0, f\"dim {dim} must be divisible by heads {heads}\"\n        \n        self.dim = dim\n        self.heads = heads\n        self.head_dim = dim // heads\n        self.scale = self.head_dim ** -0.5\n        \n        self.qkv = nn.Linear(dim, dim * 3, bias=False)\n        self.out_proj = nn.Linear(dim, dim)\n        self.dropout = nn.Dropout(dropout)\n        \n        # Head-aware rotary embedding - each head gets same head_dim\n        self.rotary_emb = RotaryPositionalEmbedding(self.head_dim)\n        \n    def forward(self, x,y,z) -> torch.Tensor:\n        B, L, D = x.shape\n        \n        # Generate QKV\n        qkv = self.qkv(x).reshape(B, L, 3, self.heads, self.head_dim).permute(2, 0, 3, 1, 4)\n        q, k, v = qkv[0], qkv[1], qkv[2]  # Each: (B, heads, L, head_dim)\n        \n        # Get rotary embeddings for this sequence length\n        cos, sin = self.rotary_emb(L, x.device)\n        \n        # Apply rotary position embedding (now properly head-aware)\n        q, k = apply_rotary_pos_emb(q, k, cos, sin)\n        \n        # Attention computation\n        attn = (q @ k.transpose(-2, -1)) * self.scale\n        attn = F.softmax(attn, dim=-1)\n        attn = self.dropout(attn)\n        \n        # Apply attention to values\n        out = (attn @ v).transpose(1, 2).reshape(B, L, D)\n        return self.out_proj(out),None\n    \nclass MHAModule(nn.Module):\n    def __init__(self, dim, heads=4, dropout=0.1,ver='normal', act_f='',norm_f=''):\n        super().__init__()\n        self.norm = nn.LayerNorm(dim)\n        if ver=='normal':\n            self.mha = nn.MultiheadAttention(embed_dim=dim, num_heads=heads, batch_first=True, dropout=dropout)\n        elif ver=='RoPE':\n            self.mha = RoPEAttention(dim=dim, heads=heads, dropout=dropout)\n        self.dropout = nn.Dropout(dropout)\n\n    def forward(self, x):\n        residual = x\n        x = x.transpose(1, 2)\n        x = self.norm(x)\n        x, _ = self.mha(x, x, x)\n        x = self.dropout(x)\n        x = x.transpose(1, 2)\n        return x + residual\n    \nclass DimPool(nn.Module):\n    def __init__(self,dims):\n        super().__init__()\n        self.dims=dims\n        return\n\n    def forward(self, x):\n        return x.mean(dim=self.dims)\n    \n\nclass CMI3Net(nn.Module):\n    def __init__(self, imu_dim=32, thm_dim=5, tof_dim=5, emb_dim = 64, imu_expand=24, thm_expand=24, kernel_sizes=[3,],\n                 imu_input_mask=0.0, thm_input_mask=0.0, tof_expand=16, tof_input_mask=0.1,\n                 head_dropout=0.0, num_classes=18, block_cfg={}):\n        super().__init__()\n        '''\n        IMU branch\n        '''\n        imu_groups=imu_dim//4\n        if imu_dim%4!=0:\n            print(\"feature dim can't be grouped!! imu_dim:\",imu_dim)\n        imu_emb_dim=imu_dim*imu_expand*len(kernel_sizes)\n        self.imu_branch=nn.Sequential(\n            nn.BatchNorm1d(imu_dim,momentum=0.005,affine=False),\n            RandomChannelMask(imu_input_mask),\n            ParallelConv(imu_dim, imu_expand*imu_dim, kernel_sizes=kernel_sizes, groups=imu_groups),\n            ResBlock(imu_emb_dim, imu_emb_dim, groups=imu_groups, norm_f='BN', **block_cfg),\n            nn.MaxPool1d(2),\n            ResBlock(imu_emb_dim, imu_emb_dim, groups=imu_groups, norm_f='BN', **block_cfg),\n            nn.Conv1d(imu_emb_dim, emb_dim, kernel_size=1),\n        )\n\n        '''\n        thm branch\n        '''\n        thm_groups=1\n        thm_emb_dim=thm_dim*thm_expand*len(kernel_sizes)\n        self.thm_branch=nn.Sequential(\n            # thm data already normalized so no more normalize here\n            RandomChannelMask(thm_input_mask), # result in exact zero even with mixup\n            ParallelConv(thm_dim, thm_expand*thm_dim, kernel_sizes=kernel_sizes, groups=thm_groups),\n            ResBlock(thm_emb_dim, thm_emb_dim, groups=thm_groups, norm_f='BN', **block_cfg),\n            nn.MaxPool1d(2),\n            ResBlock(thm_emb_dim, thm_emb_dim, groups=thm_groups, norm_f='BN', **block_cfg),\n            nn.Conv1d(thm_emb_dim, emb_dim, kernel_size=1),\n        ) \n\n\n        '''\n        tof branch 3D\n        '''\n        tof_groups=5\n        tof_emb_dim=tof_dim*tof_expand\n        self.tof_branch=nn.Sequential(\n            RandomChannelMask(tof_input_mask),\n            ResBlock3D(tof_dim, tof_emb_dim, groups=tof_groups, kernel_size=(1,3,3), **block_cfg),\n            nn.MaxPool3d((1,2,2)),\n            ResBlock3D(tof_emb_dim, tof_emb_dim, groups=tof_groups, kernel_size=(1,3,3), **block_cfg),\n            nn.MaxPool3d((2,2,2)),\n            ResBlock3D(tof_emb_dim, tof_emb_dim, groups=tof_groups, kernel_size=(3,1,1), **block_cfg),\n            DimPool(dims=(3,4)),# (B,C,L,8,8) -> (B,C,L)\n            nn.Conv1d(tof_emb_dim, emb_dim, kernel_size=1),\n        ) \n\n        # fusion layers\n        self.fusion_layers=nn.Sequential(\n            GRUBlock(emb_dim, **block_cfg),\n            GRUBlock(emb_dim, **block_cfg),\n            MHAModule(emb_dim, ver='RoPE', **block_cfg),       \n        )\n\n        # Classification Head\n        self.att_pooling=AttentionPooling(emb_dim)\n        \n        self.class_head = nn.Sequential(\n            nn.Dropout(head_dropout),\n            nn.Linear(emb_dim, emb_dim),\n            nn.SiLU(inplace=True),\n            nn.Dropout(head_dropout),\n            nn.Linear(emb_dim, num_classes),\n        )\n        \n        self.ori_head = nn.Sequential(\n            nn.Dropout(head_dropout),\n            nn.Linear(emb_dim, emb_dim),\n            nn.SiLU(inplace=True),\n            nn.Dropout(head_dropout),\n            nn.Linear(emb_dim, 4)\n        )\n\n        self.ratio_head = nn.Sequential(\n            nn.Linear(emb_dim, emb_dim),\n            nn.SiLU(inplace=True),\n            nn.Linear(emb_dim, 1),\n        )\n\n        self.entropy_head = nn.Sequential(\n            nn.Linear(emb_dim, emb_dim),\n            nn.SiLU(inplace=True),\n            nn.Linear(emb_dim, 1),\n        )\n\n    \n    def forward(self, data_dict,extra_heads=False) -> torch.Tensor:\n        x_imu = data_dict['X_imu'].permute(0, 2, 1)   # (B,C, L)\n        x_imu = self.imu_branch(x_imu)\n\n        x_thm = data_dict['X_thm'].permute(0, 2, 1)   # (B,C, L)\n        x_thm = self.thm_branch(x_thm)\n\n        x_tof = data_dict['X_tof'] # (B,L,C,8,8)\n        x_tof = x_tof.permute(0, 2, 1, 3, 4) # (B,C,L,8,8)\n        x_tof = self.tof_branch(x_tof)\n\n        x = x_imu + x_thm + x_tof\n        x = self.fusion_layers(x)\n        x = x.permute(0, 2, 1)\n        \n        x = self.att_pooling(x)\n        \n        y_class=self.class_head(x)\n        if not extra_heads:\n            return y_class\n        else:\n            y_ori=self.ori_head(x)\n            y_ratio=self.ratio_head(x).reshape(-1)\n            y_entropy=self.entropy_head(x).reshape(-1)\n            return y_class,y_ori,y_ratio,y_entropy","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T11:56:14.719401Z","iopub.execute_input":"2025-08-28T11:56:14.720179Z","iopub.status.idle":"2025-08-28T11:56:14.741346Z","shell.execute_reply.started":"2025-08-28T11:56:14.720157Z","shell.execute_reply":"2025-08-28T11:56:14.740492Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# CFG","metadata":{}},{"cell_type":"code","source":"%%writefile _CFG.py\nimport torch\n\nclass CFG:\n    device='cuda'\n    cores=4\n    cast_type=torch.float16\n    ###\n    max_len=120\n    ### optimization\n    EMA=True\n    EMA_decay=0.99\n    EMA_device='cuda'\n    \n    ce_ratio_power=2.0\n    ori_loss_weight=0.4\n    ratio_loss_weight=12.0\n    entropy_loss_weight=0.0\n    \n    epochs=100\n    lr=3.5e-4\n    lr_sch='onecycle' # 'onecycle' or 'cos'\n    constant_lr_ratio=0.0\n    batch_size=64\n    weight_decay=0.35\n    label_smoothing=0.0\n    random_clip_q=None\n    clip_neg_p=None # check later with postprob\n    ### model\n    model='AllDataModel1'\n    model_cfg={\n        'imu_dim':36,\n        'thm_dim':5,\n        'tof_dim':10,\n        'num_classes':18,\n        'emb_dim': 288,\n        'imu_expand':144,\n        'thm_expand':12,\n        'tof_expand':28,\n        'imu_input_mask':0.1,\n        'thm_input_mask':0.1,\n        'tof_input_mask':0.1,\n        'head_dropout':0.15,\n        'block_cfg':{\n            'dropout':0.1,\n            'act_f':'silu',\n        },\n    }\n    ### augmentation\n    aug_cfg={\n        'mixup':True,\n        'mixup_prob':1.0,\n        'mixup_alpha':0.6,\n\n        'rot_flip':True,\n        \n        'world_xy_rotate_prob':0.8,\n        'world_xy_rotate_span':1/2,\n\n        'time_scaling_prob':1.0,\n        'time_scaling_type':'uniform',\n        'time_scaling_lb':0.85,\n        'time_scaling_ub':1.25,\n\n        'random_mask_prob':0.65,\n        'random_mask_ratio':0.4,\n        'random_mask_type':'random',\n\n        'pixel_jitter_prob':0.5,\n        'pixel_jitter_shift':1,\n\n        'tof_patch_erase_prob':0.15,\n        'tof_patch_erase_size':2,\n\n        'acc_mask_prob':0.1,\n        'rot_mask_prob':0.1,\n        'thm_mask_prob':0.1,\n        'tof_mask_prob':0.1,\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T11:56:14.742507Z","iopub.execute_input":"2025-08-28T11:56:14.742884Z","iopub.status.idle":"2025-08-28T11:56:14.756733Z","shell.execute_reply.started":"2025-08-28T11:56:14.742843Z","shell.execute_reply":"2025-08-28T11:56:14.756075Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Main","metadata":{}},{"cell_type":"code","source":"# from _data_pipline import *\n# from _model import *\n# from _CFG import CFG","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T11:56:14.757731Z","iopub.execute_input":"2025-08-28T11:56:14.758293Z","iopub.status.idle":"2025-08-28T11:56:14.782095Z","shell.execute_reply.started":"2025-08-28T11:56:14.758271Z","shell.execute_reply":"2025-08-28T11:56:14.781472Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train_df=pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\n# train_meta=pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv')\n# train_meta.set_index('subject',inplace=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T11:56:14.782893Z","iopub.execute_input":"2025-08-28T11:56:14.783174Z","iopub.status.idle":"2025-08-28T11:56:47.605128Z","shell.execute_reply.started":"2025-08-28T11:56:14.783148Z","shell.execute_reply":"2025-08-28T11:56:47.604399Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# meta_df=train_meta\n# seq_df=train_df\n# data_dicts=[]\n# info={'sequence_id':[],'subject':[],'target':[]}\n# # nan processing\n# # encoding\n# for seq_id,sub_df in tqdm(seq_df.groupby('sequence_id')):\n#     subject=sub_df['subject'].iloc[0]\n#     sub_meta=meta_df.loc[subject]\n#     data_dict=extract_data_dict(sub_df,sub_meta)\n#     ### append dict_data\n#     data_dicts.append(data_dict)\n#     info['sequence_id'].append(seq_id)\n#     info['subject'].append(subject)\n#     info['target'].append(sub_df['gesture'].iloc[0] if 'gesture' in sub_df.columns else np.nan)\n# info=pd.DataFrame(info)\n# info=info.merge(train_meta,on='subject',how='left')\n# info=info.merge(\n#     train_df[['orientation','gesture','sequence_id']].drop_duplicates('sequence_id'),\n#     on='sequence_id',how='left'\n# )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T11:56:47.606053Z","iopub.execute_input":"2025-08-28T11:56:47.606355Z","iopub.status.idle":"2025-08-28T11:58:17.445398Z","shell.execute_reply.started":"2025-08-28T11:56:47.606329Z","shell.execute_reply":"2025-08-28T11:58:17.444371Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# torch.set_num_threads(4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T11:58:17.447969Z","iopub.execute_input":"2025-08-28T11:58:17.448246Z","iopub.status.idle":"2025-08-28T11:58:17.465522Z","shell.execute_reply.started":"2025-08-28T11:58:17.448223Z","shell.execute_reply":"2025-08-28T11:58:17.464729Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# res,score,score_std=kfold_train(info,data_dicts)\n# pd.to_pickle(res,'res.pkl')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T11:58:17.49539Z","iopub.execute_input":"2025-08-28T11:58:17.496046Z","execution_failed":"2025-08-28T11:58:53.686Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import gdown","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!rm ./*","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# https://drive.google.com/file/d/1kcS8q-Kykvteo4MOsU0rd0gGpEL4AlqN/view?usp=sharing\nfile_id = '1kcS8q-Kykvteo4MOsU0rd0gGpEL4AlqN' # Replace with your actual file ID\noutput_path = 'All_M14_res.zip' # Replace with the desired filename in Kaggle\n\ngdown.download(f'https://drive.google.com/uc?id={file_id}', output_path, fuzzy=True)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!unzip All_M14_res.zip","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}