{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"isSourceIdPinned":false,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"##  1. Import Library\n___","metadata":{"editable":false}},{"cell_type":"code","source":"import os\nimport random\nimport gc, ctypes\nimport time\nfrom tqdm import tqdm\nimport copy\nfrom collections import defaultdict\n\nimport pandas as pd \nimport numpy as np\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom sklearn.model_selection import StratifiedGroupKFold\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.optim import lr_scheduler\nfrom torch.utils.data import DataLoader, Dataset\nimport torch.nn.functional as F\n\nfrom scipy.spatial.transform import Rotation as R\n\n\nimport pytorch_lightning as pl\nfrom pytorch_lightning.callbacks import LearningRateMonitor\nfrom pytorch_lightning.loggers import CSVLogger\n\nfrom colorama import Fore, Style\nc_ = Fore.BLUE\nsr_ = Style.BRIGHT\n\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:26:03.077362Z","iopub.execute_input":"2025-08-30T12:26:03.077567Z","iopub.status.idle":"2025-08-30T12:26:16.256853Z","shell.execute_reply.started":"2025-08-30T12:26:03.077541Z","shell.execute_reply":"2025-08-30T12:26:16.256232Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Login W&B(Weighted and Bias)","metadata":{"editable":false}},{"cell_type":"code","source":"import wandb \n\ntry: \n    from kaggle_secrets import UserSecretsClient\n    user_secrets = UserSecretsClient()\n    api_key = user_secrets.get_secret(\"wandb\")\n    wandb.login(key=api_key)\n\n    anonymous = None\nexcept:\n    anonymous = \"must\"\n    print(f'{c_}{sr_}To use your W&B account, \\n Go to Add-ons -> Secrets and provide your W&B access token')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:26:16.257481Z","iopub.execute_input":"2025-08-30T12:26:16.257927Z","iopub.status.idle":"2025-08-30T12:26:24.856452Z","shell.execute_reply.started":"2025-08-30T12:26:16.257906Z","shell.execute_reply":"2025-08-30T12:26:24.855881Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Global Settings","metadata":{"editable":false}},{"cell_type":"code","source":"class CFG:\n    save_dir = 'runs/'\n    seed = 2025\n    debug = False # Full Training\n    sequence_len = 128\n    project = 'CMI_2025'\n    model_name = 'WaveNet'\n    comment = \"Multi-Class\"\n    label_smoothing = 0.05\n    mix_up_prob = 0.15\n    mix_up_alpha = 0.4\n    num_classes = 18\n    log_model = 'all' # when offline=False or 'all'\n    offline = False\n    kernel_size = 3\n    train_bs = 32\n    valid_bs = 2 * train_bs\n    epochs= 20\n    lr = 5e-4\n    min_lr = lr*1e-3\n    wd = 1e-4\n    scheduler = 'CosineAnnealingLR'\n    T_mult = 1\n    warmup_epochs = int(epochs * 0.2)\n    n_accumulate = 1\n    n_fold = 5\n    n_splits = 5\n    fold = 0\n\n    device = 'cuda' if torch.cuda.is_available() else 'cpu'\n    device_count = torch.cuda.device_count()\n\nos.makedirs(CFG.save_dir, exist_ok=True)\nprint(f\"{sr_}{c_} => Device is {CFG.device}\")\nprint(f\"{sr_}{c_} => Num GPU of machine is \", CFG.device_count)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:26:24.858373Z","iopub.execute_input":"2025-08-30T12:26:24.858751Z","iopub.status.idle":"2025-08-30T12:26:24.886172Z","shell.execute_reply.started":"2025-08-30T12:26:24.858733Z","shell.execute_reply":"2025-08-30T12:26:24.885644Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Clean Memory","metadata":{"editable":false}},{"cell_type":"code","source":"def clean_memory():\n    ctypes.CDLL('libc.so.6').malloc_trim(0)\n    gc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:26:24.886794Z","iopub.execute_input":"2025-08-30T12:26:24.887065Z","iopub.status.idle":"2025-08-30T12:26:24.898386Z","shell.execute_reply.started":"2025-08-30T12:26:24.887048Z","shell.execute_reply":"2025-08-30T12:26:24.897668Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Reproducibility","metadata":{"editable":false}},{"cell_type":"code","source":"def seed_everything(SEED):\n    np.random.seed(SEED)\n    random.seed(SEED)\n    torch.manual_seed(SEED)\n    torch.cuda.manual_seed(SEED)\n\n    \"\"\"When running CuDNN backend, two further options must be set\"\"\"\n    \"\"\"Pytorch, TensorFlow Framework are both using CuDNN backend\"\"\"\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\n    os.environ['PYTHONHASHSEED'] = str(SEED)\n\nseed_everything(CFG.seed)\npl.seed_everything(CFG.seed)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:26:24.899015Z","iopub.execute_input":"2025-08-30T12:26:24.899264Z","iopub.status.idle":"2025-08-30T12:26:24.922369Z","shell.execute_reply.started":"2025-08-30T12:26:24.899238Z","shell.execute_reply":"2025-08-30T12:26:24.921705Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Utils\n___","metadata":{"editable":false}},{"cell_type":"code","source":"from scipy import interpolate\n\ndef inter_signal(x):\n    original_len = len(x)\n    interp_func = interpolate.interp1d(np.linspace(0, 1, original_len), x, kind='linear')\n    x_interp = interp_func(np.linspace(0, 1, CFG.sequence_len))\n    return x_interp","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:26:24.922974Z","iopub.execute_input":"2025-08-30T12:26:24.923545Z","iopub.status.idle":"2025-08-30T12:26:24.927097Z","shell.execute_reply.started":"2025-08-30T12:26:24.923520Z","shell.execute_reply":"2025-08-30T12:26:24.926469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def prepare_loader(fold, train_transform=True, valid_transform=False, collate_fn = torch.utils.data._utils.collate.default_collate):\n    train_df = df[df['fold'] != fold].reset_index(drop=True)\n    valid_df = df[df['fold'] == fold].reset_index(drop=True)\n\n    if CFG.debug:\n        train_sp = np.random.choice(train_df['sequence_id'].unique(), 500)\n        train_df = train_df[train_df['sequence_id'].isin(train_sp)]\n    \n        valid_sp = np.random.choice(valid_df['sequence_id'].unique(), 250)\n        valid_df = valid_df[valid_df['sequence_id'].isin(valid_sp)]\n\n\n    train_ds = CMI_Dataset(train_df, col=total_feature, transforms=train_transform, mix_up=True)\n    valid_ds = CMI_Dataset(valid_df, col=total_feature, transforms=valid_transform, mix_up=False)\n\n    train_loader = DataLoader(train_ds, shuffle=True, batch_size=CFG.train_bs,\n                              num_workers=4, pin_memory=True, drop_last = True,\n                              collate_fn = collate_fn)\n    valid_loader = DataLoader(valid_ds, shuffle=False, batch_size=CFG.valid_bs,\n                              num_workers=4, pin_memory=True, drop_last = False,\n                              collate_fn = torch.utils.data._utils.collate.default_collate)\n\n    return train_loader, valid_loader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:26:24.927726Z","iopub.execute_input":"2025-08-30T12:26:24.927965Z","iopub.status.idle":"2025-08-30T12:26:24.936773Z","shell.execute_reply.started":"2025-08-30T12:26:24.927949Z","shell.execute_reply":"2025-08-30T12:26:24.936261Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Data Cleaning\n___","metadata":{"editable":false}},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\nprint(f'{sr_}{c_} Shape of DataFrame: {df.shape}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:26:24.937505Z","iopub.execute_input":"2025-08-30T12:26:24.937776Z","iopub.status.idle":"2025-08-30T12:26:55.084694Z","shell.execute_reply.started":"2025-08-30T12:26:24.937751Z","shell.execute_reply":"2025-08-30T12:26:55.084017Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"For now, I've decided to exclude Non-Target samples and concentrate on distinguishing between the 8 BRFB Targets through multi-class classification.","metadata":{"editable":false}},{"cell_type":"code","source":"behavior_list = ['SEQ_011975'] # only 2 behaviors in sequence\ntmp_pause_list = ['SEQ_012617'] # Huge Variations in pause(TMP Device)\ntof_pause_list = ['SEQ_047121','SEQ_030299'] # Huge Variations in pause(TOF Device)\n\ndelete_list = []\ndelete_list.extend(behavior_list)\ndelete_list.extend(tmp_pause_list)\ndelete_list.extend(tof_pause_list)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:26:55.087278Z","iopub.execute_input":"2025-08-30T12:26:55.087493Z","iopub.status.idle":"2025-08-30T12:26:55.091657Z","shell.execute_reply.started":"2025-08-30T12:26:55.087476Z","shell.execute_reply":"2025-08-30T12:26:55.091116Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = df[~df['sequence_id'].isin(delete_list)].reset_index(drop=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:26:55.092355Z","iopub.execute_input":"2025-08-30T12:26:55.092735Z","iopub.status.idle":"2025-08-30T12:26:56.277613Z","shell.execute_reply.started":"2025-08-30T12:26:55.092718Z","shell.execute_reply":"2025-08-30T12:26:56.276850Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### IMU Null Sequence","metadata":{"editable":false}},{"cell_type":"code","source":"tmp = df.groupby(['sequence_id']).agg(\n    rot_x_na_ratio = ('rot_x', lambda x: x.isna().mean()*100),\n    rot_y_na_ratio = ('rot_y', lambda x: x.isna().mean()*100),\n    rot_z_na_ratio = ('rot_z', lambda x: x.isna().mean()*100),\n    rot_w_na_ratio = ('rot_w', lambda x: x.isna().mean()*100),\n\n).reset_index()\n\nrot_x_na_list = tmp[tmp['rot_x_na_ratio'] > 50]['sequence_id'].values\nrot_y_na_list = tmp[tmp['rot_y_na_ratio'] > 50]['sequence_id'].values\nrot_z_na_list = tmp[tmp['rot_z_na_ratio'] > 50]['sequence_id'].values\nrot_w_na_list = tmp[tmp['rot_w_na_ratio'] > 50]['sequence_id'].values\n\nall_ids = np.concatenate([rot_x_na_list, rot_y_na_list, rot_z_na_list, rot_w_na_list])\nrot_na_list = np.unique(all_ids)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:26:56.278450Z","iopub.execute_input":"2025-08-30T12:26:56.278716Z","iopub.status.idle":"2025-08-30T12:26:58.531862Z","shell.execute_reply.started":"2025-08-30T12:26:56.278690Z","shell.execute_reply":"2025-08-30T12:26:58.531063Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = df[~df['sequence_id'].isin(rot_na_list)].reset_index(drop=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:26:58.532717Z","iopub.execute_input":"2025-08-30T12:26:58.533130Z","iopub.status.idle":"2025-08-30T12:26:59.650725Z","shell.execute_reply.started":"2025-08-30T12:26:58.533105Z","shell.execute_reply":"2025-08-30T12:26:59.650148Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Termopile Null Sequence","metadata":{"editable":false}},{"cell_type":"code","source":"tmp = df.groupby(['sequence_id']).agg(\n    thm_1_na_ratio = ('thm_1', lambda x: x.isna().mean()*100),\n    thm_2_na_ratio = ('thm_2', lambda x: x.isna().mean()*100),\n    thm_3_na_ratio = ('thm_3', lambda x: x.isna().mean()*100),\n    thm_4_na_ratio = ('thm_4', lambda x: x.isna().mean()*100),\n    thm_5_na_ratio = ('thm_5', lambda x: x.isna().mean()*100),\n\n).reset_index()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:26:59.651395Z","iopub.execute_input":"2025-08-30T12:26:59.651575Z","iopub.status.idle":"2025-08-30T12:27:02.427270Z","shell.execute_reply.started":"2025-08-30T12:26:59.651561Z","shell.execute_reply":"2025-08-30T12:27:02.426402Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"thm_1_na_list = tmp[tmp['thm_1_na_ratio'] > 50]['sequence_id'].values\nthm_2_na_list = tmp[tmp['thm_2_na_ratio'] > 50]['sequence_id'].values\nthm_3_na_list = tmp[tmp['thm_3_na_ratio'] > 50]['sequence_id'].values\nthm_4_na_list = tmp[tmp['thm_4_na_ratio'] > 50]['sequence_id'].values\nthm_5_na_list = tmp[tmp['thm_5_na_ratio'] > 50]['sequence_id'].values\n\nall_ids = np.concatenate([thm_1_na_list, thm_2_na_list, thm_3_na_list, thm_4_na_list, thm_5_na_list])\nthm_na_list = np.unique(all_ids)\nlen(thm_na_list)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:02.428237Z","iopub.execute_input":"2025-08-30T12:27:02.428438Z","iopub.status.idle":"2025-08-30T12:27:02.438295Z","shell.execute_reply.started":"2025-08-30T12:27:02.428424Z","shell.execute_reply":"2025-08-30T12:27:02.437661Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = df[~df['sequence_id'].isin(thm_na_list)].reset_index(drop=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:02.438986Z","iopub.execute_input":"2025-08-30T12:27:02.439220Z","iopub.status.idle":"2025-08-30T12:27:03.516502Z","shell.execute_reply.started":"2025-08-30T12:27:02.439205Z","shell.execute_reply":"2025-08-30T12:27:03.515939Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### TOF Null Sequence","metadata":{"editable":false}},{"cell_type":"code","source":"tof_list = [col for col in df.columns if 'tof' in col ]\n\ntof_1_list = tof_list[:64]\ntof_2_list = tof_list[64:128]\ntof_3_list = tof_list[128:192]\ntof_4_list = tof_list[192:256]\ntof_5_list = tof_list[256:320]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:03.517239Z","iopub.execute_input":"2025-08-30T12:27:03.517486Z","iopub.status.idle":"2025-08-30T12:27:03.522257Z","shell.execute_reply.started":"2025-08-30T12:27:03.517465Z","shell.execute_reply":"2025-08-30T12:27:03.521513Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def nan_ratio_mean(df, cols):\n    return df[cols].isna().mean(axis=1).groupby(df['sequence_id']).mean() * 100\n\ntof_1_na = nan_ratio_mean(df, tof_1_list).rename('tof_1_na_ratio')\ntof_2_na = nan_ratio_mean(df, tof_2_list).rename('tof_2_na_ratio')\ntof_3_na = nan_ratio_mean(df, tof_3_list).rename('tof_3_na_ratio')\ntof_4_na = nan_ratio_mean(df, tof_4_list).rename('tof_4_na_ratio')\ntof_5_na = nan_ratio_mean(df, tof_5_list).rename('tof_5_na_ratio')\n\ntmp = pd.concat([tof_1_na, tof_2_na, tof_3_na, tof_4_na, tof_5_na], axis=1).reset_index()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:03.522912Z","iopub.execute_input":"2025-08-30T12:27:03.523092Z","iopub.status.idle":"2025-08-30T12:27:04.862071Z","shell.execute_reply.started":"2025-08-30T12:27:03.523079Z","shell.execute_reply":"2025-08-30T12:27:04.861470Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tof_1_na_list = tmp[tmp['tof_1_na_ratio'] > 50]['sequence_id'].values\ntof_2_na_list = tmp[tmp['tof_2_na_ratio'] > 50]['sequence_id'].values\ntof_3_na_list = tmp[tmp['tof_3_na_ratio'] > 50]['sequence_id'].values\ntof_4_na_list = tmp[tmp['tof_4_na_ratio'] > 50]['sequence_id'].values\ntof_5_na_list = tmp[tmp['tof_5_na_ratio'] > 50]['sequence_id'].values\n\nall_ids = np.concatenate([tof_1_na_list, tof_2_na_list, tof_3_na_list, tof_4_na_list, tof_5_na_list])\ntof_na_list = np.unique(all_ids)\nlen(tof_na_list)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:04.863071Z","iopub.execute_input":"2025-08-30T12:27:04.863296Z","iopub.status.idle":"2025-08-30T12:27:04.871644Z","shell.execute_reply.started":"2025-08-30T12:27:04.863279Z","shell.execute_reply":"2025-08-30T12:27:04.871092Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = df[~df['sequence_id'].isin(tof_na_list)].reset_index(drop=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:04.872343Z","iopub.execute_input":"2025-08-30T12:27:04.872791Z","iopub.status.idle":"2025-08-30T12:27:05.931015Z","shell.execute_reply.started":"2025-08-30T12:27:04.872774Z","shell.execute_reply":"2025-08-30T12:27:05.930209Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tmp = df.groupby(['subject','sequence_id'])['behavior'].value_counts().unstack().reset_index().fillna(0)\ntmp = tmp.rename(columns={'Hand at target location':'Pause_len', 'Performs gesture': 'Gesture_len'})\n\ndf = df.merge(tmp[['sequence_id','Gesture_len','Pause_len']], on='sequence_id', how='left')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:05.931785Z","iopub.execute_input":"2025-08-30T12:27:05.932021Z","iopub.status.idle":"2025-08-30T12:27:06.641199Z","shell.execute_reply.started":"2025-08-30T12:27:05.932005Z","shell.execute_reply":"2025-08-30T12:27:06.640435Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Sequence Augmentation\n___","metadata":{"editable":false}},{"cell_type":"code","source":"class CMI_Transform:\n    def __init__(self, always_apply=False, p=0.5):\n        self.always_apply = always_apply\n        self.p = p\n\n    def __call__(self, y):\n        if self.always_apply:\n            return self.apply(y)\n        else:\n            if np.random.rand() < self.p:\n                return self.apply(y)\n            else:\n                return y\n\n    def apply(self, y):\n        raise NotImplementedError\n\nclass Compose:\n    def __init__(self, transforms):\n        self.transforms = transforms\n\n    def __call__(self, y):\n        for trans in self.transforms:\n            y = trans(y)\n\n        return y\n\nclass OneOf:\n    def __init__(self, transforms):\n        self.transforms = transforms\n\n    def __call__(self, y):\n        n_trans = len(self.transforms)\n        trans_idx = np.random.choice(n_trans)\n        trans = self.transforms[trans_idx]\n        return trans(y)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:06.642007Z","iopub.execute_input":"2025-08-30T12:27:06.642205Z","iopub.status.idle":"2025-08-30T12:27:06.648082Z","shell.execute_reply.started":"2025-08-30T12:27:06.642189Z","shell.execute_reply":"2025-08-30T12:27:06.647347Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Phase_Locked_Time_Shift(CMI_Transform):\n    def __init__(self, start, end, always_apply=False, p=0.5):\n        super().__init__(always_apply, p)\n        self.start = int(start)\n        self.end = int(end)\n        \n    def apply(self, x):\n        L = x.shape[0]\n        trans_shift = int(np.random.uniform(-0.15*L, 0.15*L, size=1))\n        pause_shift = int(np.random.uniform(-0.15*L, 0.15*L, size=1))\n        gesture_shift = int(np.random.uniform(-0.05*L, 0.05*L, size=1))\n\n        x[:self.start] = np.roll(x[:self.start], trans_shift, axis = 0) # Transition Phase \n        x[self.start:self.end] = np.roll(x[self.start:self.end], pause_shift, axis = 0) # Pause Phase\n        x[self.end:] = np.roll(x[self.end:], gesture_shift, axis = 0)\n        \n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:06.648838Z","iopub.execute_input":"2025-08-30T12:27:06.649615Z","iopub.status.idle":"2025-08-30T12:27:06.666836Z","shell.execute_reply.started":"2025-08-30T12:27:06.649590Z","shell.execute_reply":"2025-08-30T12:27:06.666178Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Jitter_Scale(CMI_Transform):\n    def __init__(self, always_apply=False, p=0.5, max_noise_amplitude=0.05, scale=0.1):\n        super().__init__(always_apply, p)\n        self.max_noise_amplitude = max_noise_amplitude\n        self.scale=0.1\n\n    def apply(self, x):\n        noise  = np.random.randn(*x.shape) * self.max_noise_amplitude\n        scale  = np.random.uniform(1-self.scale,\n                                   1+self.scale,\n                                   size=(1, x.shape[1]))\n\n        return (x + noise) * scale\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:06.667591Z","iopub.execute_input":"2025-08-30T12:27:06.667784Z","iopub.status.idle":"2025-08-30T12:27:06.681681Z","shell.execute_reply.started":"2025-08-30T12:27:06.667770Z","shell.execute_reply":"2025-08-30T12:27:06.681124Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. IMU Feature Extractor\n___","metadata":{"editable":false}},{"cell_type":"code","source":"raw_acc_feature = ['acc_x','acc_y','acc_z']\nraw_rot_feature = ['rot_x','rot_y','rot_z','rot_w']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:06.682441Z","iopub.execute_input":"2025-08-30T12:27:06.682675Z","iopub.status.idle":"2025-08-30T12:27:06.698359Z","shell.execute_reply.started":"2025-08-30T12:27:06.682652Z","shell.execute_reply":"2025-08-30T12:27:06.697732Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def gravity_remove(imu_sequence):\n    \n    acc_values = imu_sequence[raw_acc_feature].values\n    rot_values = imu_sequence[raw_rot_feature].values\n\n    gravity_world = np.array([0,0,9.81])\n    \n    rotation = R.from_quat(rot_values)\n    gravity_sensor = rotation.apply(gravity_world, inverse=True)\n\n    linear_acc = acc_values - gravity_sensor\n    \n    return linear_acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:06.698982Z","iopub.execute_input":"2025-08-30T12:27:06.699186Z","iopub.status.idle":"2025-08-30T12:27:06.713220Z","shell.execute_reply.started":"2025-08-30T12:27:06.699171Z","shell.execute_reply":"2025-08-30T12:27:06.712576Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compute_angular_velocity(quat_sequence, sample_rate=128):\n    \n    time_steps = quat_sequence.shape[0]\n    delta_t = 1.0 / sample_rate\n    angular_velocity = np.zeros((time_steps, 3))\n    \n    for i in range(time_steps - 1):\n        q_current = quat_sequence[i]\n        q_next = quat_sequence[i + 1]\n\n        rot_current = R.from_quat(q_current)\n        rot_next = R.from_quat(q_next)\n\n        delta_rot = rot_current.inv() * rot_next\n\n        angular_velocity[i, :] = delta_rot.as_rotvec() / delta_t\n\n    angular_velocity[-1, :] = angular_velocity[-2, :] \n\n    return angular_velocity\n\n\n\ndef compute_angular_distance(quat_sequence):\n \n    seq_length = quat_sequence.shape[0]\n    angular_distance = np.zeros(seq_length)\n    \n    for i in range(seq_length - 1):\n        q_current = quat_sequence[i]\n        q_next = quat_sequence[i + 1]\n\n        r_current = R.from_quat(q_current)\n        r_next = R.from_quat(q_next)\n\n        delta_rot = r_current.inv() * r_next\n        angular_distance[i] = np.linalg.norm(delta_rot.as_rotvec())\n\n    angular_distance[-1] = angular_distance[-2]\n\n    return angular_distance","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:06.713803Z","iopub.execute_input":"2025-08-30T12:27:06.714040Z","iopub.status.idle":"2025-08-30T12:27:06.728468Z","shell.execute_reply.started":"2025-08-30T12:27:06.714024Z","shell.execute_reply":"2025-08-30T12:27:06.727836Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def IMU_Extractor(df):\n\n    # df[raw_acc_feature] = df[raw_acc_feature].apply(\n    # lambda row: row.fillna(\n    #     row.dropna().mean() if row.notna().any() else 0\n    # ),\n    # axis=1\n    # )\n\n    # df[raw_rot_feature] = df[raw_rot_feature].apply(\n    # lambda row: row.fillna(\n    #     row.dropna().mean() if row.notna().any() else 0\n    # ),\n    # axis=1\n    # )\n    \n    \n    ## Gravity Remove\n    linear_acc_df = df.groupby('sequence_id', group_keys=False).apply(\n        lambda df: pd.DataFrame(\n            gravity_remove(df[raw_acc_feature+raw_rot_feature]),\n            columns = raw_acc_feature,\n            index = df.index,\n        )\n    )\n    ## Acc Feature\n    df = df.drop(columns=raw_acc_feature)\n    df[raw_acc_feature] = linear_acc_df\n\n    for axis in ['x', 'y', 'z']:\n        df[f'acc_{axis}_jerk'] = df.groupby('sequence_id')[f'acc_{axis}'].diff().fillna(0)\n\n    df['acc_mag'] = np.sqrt(df['acc_x']**2 + df['acc_y']**2 + df['acc_z']**2)\n    df['acc_mag_jerk'] = df.groupby('sequence_id')['acc_mag'].diff().fillna(0)\n    df['acc_mag_snap'] = df.groupby('sequence_id')['acc_mag_jerk'].diff().fillna(0)\n\n    ## Rot Feature\n\n    df['rot_angle'] = 2 * np.arccos(df['rot_w'].clip(-1,1))\n    df['rot_angle_vel'] = df.groupby('sequence_id')['rot_angle'].diff().fillna(0)\n    df['rot_angle_snap'] = df.groupby('sequence_id')['rot_angle_vel'].diff().fillna(0)\n\n\n    ## Angular Velocity\n    angular_velocity_df = df.groupby('sequence_id', group_keys=False).apply(\n                lambda df: pd.DataFrame(\n                compute_angular_velocity(df[raw_rot_feature].to_numpy()),\n                columns=['angular_vel_x','angular_vel_y','angular_vel_z'],\n            index=df.index\n            )\n        )\n    df = df.join(angular_velocity_df)\n\n    for axis in ['x', 'y', 'z']:\n        df[f'angular_vel_{axis}_jerk'] = df.groupby('sequence_id')[f'angular_vel_{axis}'].diff().fillna(0)\n\n    df['angular_vel_mag'] = np.sqrt(df['angular_vel_x']**2 + df['angular_vel_y']**2 + df['angular_vel_z']**2)\n    df['angular_vel_mag_jerk'] = df.groupby('sequence_id')['angular_vel_mag'].diff().fillna(0)\n    df['angular_vel_mag_snap'] = df.groupby('sequence_id')['angular_vel_mag_jerk'].diff().fillna(0)\n  \n    ## Angular Distance\n    angular_distance_df = df.groupby('sequence_id', group_keys=False).apply(\n            lambda df: pd.DataFrame(\n                compute_angular_distance(df[raw_rot_feature].to_numpy()),\n                columns=['angular_distance'],\n                index=df.index,\n            )\n    )\n    df = df.join(angular_distance_df)\n\n    ## Hybrid Feature\n    df['acc_angular_vel'] = df['acc_mag'] * df['angular_vel_mag']\n    df['acc_angular_vel_ratio'] = df['acc_mag'] / (df['angular_vel_mag'] + 1e-6)\n    df['acc_angular_distance'] = df['acc_mag'] * df['angular_distance']\n    df['acc_angular_distance_ratio'] = df['acc_mag'] / (df['angular_distance'] + 1e-6)\n\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:06.733114Z","iopub.execute_input":"2025-08-30T12:27:06.733324Z","iopub.status.idle":"2025-08-30T12:27:06.747196Z","shell.execute_reply.started":"2025-08-30T12:27:06.733308Z","shell.execute_reply":"2025-08-30T12:27:06.746476Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"acc_feature = ['acc_x','acc_y','acc_z',\n               'acc_x_jerk', 'acc_y_jerk', 'acc_z_jerk',\n               'acc_mag','acc_mag_jerk','acc_mag_snap',\n               'acc_angular_vel', 'acc_angular_vel_ratio',  'acc_angular_distance', 'acc_angular_distance_ratio']\n\n\nrot_feature = ['rot_x','rot_y','rot_z','rot_w',\n               'rot_angle', 'rot_angle_vel', 'rot_angle_snap',\n               'angular_vel_x', 'angular_vel_y', 'angular_vel_z', \n               'angular_vel_x_jerk', 'angular_vel_y_jerk', 'angular_vel_z_jerk',\n               'angular_vel_mag', 'angular_vel_mag_jerk', 'angular_vel_mag_snap', \n               'angular_distance']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:06.747799Z","iopub.execute_input":"2025-08-30T12:27:06.748029Z","iopub.status.idle":"2025-08-30T12:27:06.763630Z","shell.execute_reply.started":"2025-08-30T12:27:06.748015Z","shell.execute_reply":"2025-08-30T12:27:06.762854Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\ndf = IMU_Extractor(df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:27:06.764324Z","iopub.execute_input":"2025-08-30T12:27:06.764577Z","iopub.status.idle":"2025-08-30T12:28:02.802891Z","shell.execute_reply.started":"2025-08-30T12:27:06.764552Z","shell.execute_reply":"2025-08-30T12:28:02.802061Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. THM, TOF Feature Extractor\n___","metadata":{"editable":false}},{"cell_type":"markdown","source":"### THM Feature","metadata":{"editable":false}},{"cell_type":"code","source":"raw_thm_feature = ['thm_1','thm_2','thm_3','thm_4','thm_5']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:02.803700Z","iopub.execute_input":"2025-08-30T12:28:02.804006Z","iopub.status.idle":"2025-08-30T12:28:02.807429Z","shell.execute_reply.started":"2025-08-30T12:28:02.803989Z","shell.execute_reply":"2025-08-30T12:28:02.806868Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def mean_abs_chain(diffs):\n    return sum(d.abs() for d in diffs) / len(diffs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:02.808195Z","iopub.execute_input":"2025-08-30T12:28:02.808474Z","iopub.status.idle":"2025-08-30T12:28:02.824788Z","shell.execute_reply.started":"2025-08-30T12:28:02.808458Z","shell.execute_reply":"2025-08-30T12:28:02.824141Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def THM_Extractor(df):\n\n    mask = df['thm_3'] == 0\n    df.loc[mask, 'thm_3'] = np.mean(df.loc[mask, 'thm_2'])\n    \n    # df[raw_thm_feature] = df[raw_thm_feature].apply(\n    # lambda row: row.fillna(\n    #     row.dropna().mean() if row.notna().any() else 0\n    # ),\n    # axis=1\n    # )\n    \n    ## Chain Feature\n    df['thm_right_chain'] = mean_abs_chain([\n        df['thm_3'] - df['thm_2'],\n        df['thm_3'] - df['thm_1'],\n        df['thm_3'] - df['thm_4'],\n    ])\n    df['thm_left_chain'] = mean_abs_chain([\n        df['thm_5'] - df['thm_2'],\n        df['thm_5'] - df['thm_1'],\n        df['thm_5'] - df['thm_4'],\n    ])\n    df['thm_center_chain'] = mean_abs_chain([\n        df['thm_1'] - df['thm_2'],\n        df['thm_1'] - df['thm_3'],\n        df['thm_1'] - df['thm_4'],\n        df['thm_1'] - df['thm_5'],\n\n    ])  \n    df['thm_height_chain'] = mean_abs_chain([\n        df['thm_2'] - df['thm_1'],\n        df['thm_1'] - df['thm_4'],\n    ]) \n\n    \n    ## Chaine Expansion Feature\n    df['thm_right_left_diff'] = df['thm_3'] - df['thm_5']\n    df['thm_top_bottom_diff'] = df[['thm_1','thm_2']].mean(axis=1) - df['thm_4']\n\n    \n    ## Stats Feature\n    df['thm_top_mean'] = df[['thm_1','thm_2']].mean(axis=1)\n    df['thm_middle_mean'] =df[['thm_5','thm_3']].mean(axis=1)\n    df['thm_mean'] = df[raw_thm_feature].mean(axis=1)\n    df['thm_std'] = df[raw_thm_feature].std(axis=1)\n    df['thm_range'] = df[raw_thm_feature].max(axis=1) - df[raw_thm_feature].min(axis=1)\n    \n    return df ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:02.825652Z","iopub.execute_input":"2025-08-30T12:28:02.825924Z","iopub.status.idle":"2025-08-30T12:28:02.841515Z","shell.execute_reply.started":"2025-08-30T12:28:02.825902Z","shell.execute_reply":"2025-08-30T12:28:02.840882Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"thm_feature = ['thm_1','thm_2','thm_3','thm_4','thm_5',\n               'thm_right_chain','thm_left_chain','thm_center_chain','thm_height_chain',\n               'thm_top_bottom_diff', 'thm_right_left_diff',\n               'thm_top_mean','thm_middle_mean','thm_mean','thm_std','thm_range'\n                   ]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:02.842273Z","iopub.execute_input":"2025-08-30T12:28:02.842516Z","iopub.status.idle":"2025-08-30T12:28:02.859417Z","shell.execute_reply.started":"2025-08-30T12:28:02.842499Z","shell.execute_reply":"2025-08-30T12:28:02.858709Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time \n\ndf = THM_Extractor(df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:02.860265Z","iopub.execute_input":"2025-08-30T12:28:02.860462Z","iopub.status.idle":"2025-08-30T12:28:03.319725Z","shell.execute_reply.started":"2025-08-30T12:28:02.860446Z","shell.execute_reply":"2025-08-30T12:28:03.319108Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### TOF Feature","metadata":{"editable":false}},{"cell_type":"code","source":"raw_tof_feature = ['tof_1_mean','tof_2_mean','tof_3_mean','tof_4_mean','tof_5_mean']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:03.320452Z","iopub.execute_input":"2025-08-30T12:28:03.320672Z","iopub.status.idle":"2025-08-30T12:28:03.324293Z","shell.execute_reply.started":"2025-08-30T12:28:03.320647Z","shell.execute_reply":"2025-08-30T12:28:03.323655Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tof_top_left = []; tof_top_right = [];\ntof_bottom_left = []; tof_bottom_right = []\n\ntof_list = [tof_1_list, tof_2_list, tof_3_list, tof_4_list, tof_5_list]\n\nfor list_ in tof_list: \n\n    tof_top_left.extend([list_[8*i + k] for i in range(0, 4) for k in range(0, 4)])\n    tof_top_right.extend([list_[8*i + k + 4] for i in range(0, 4) for k in range(0, 4)])\n    tof_bottom_left.extend([list_[8*i + k + 32] for i in range(0, 4) for k in range(0, 4)])\n    tof_bottom_right.extend([list_[8*i + k + 36] for i in range(0, 4) for k in range(0, 4)])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:03.324988Z","iopub.execute_input":"2025-08-30T12:28:03.325234Z","iopub.status.idle":"2025-08-30T12:28:03.340650Z","shell.execute_reply.started":"2025-08-30T12:28:03.325210Z","shell.execute_reply":"2025-08-30T12:28:03.339884Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def TOF_Extractor(df):\n\n    df[tof_1_list] = df[tof_1_list].replace(-1, 0)\n    df[tof_2_list] = df[tof_2_list].replace(-1, 0)\n    df[tof_3_list] = df[tof_3_list].replace(-1, 0)\n    df[tof_4_list] = df[tof_4_list].replace(-1, 0)\n    df[tof_5_list] = df[tof_5_list].replace(-1, 0)\n    \n    df['tof_1_mean'] = df[tof_1_list].fillna(0).mean(axis=1)\n    df['tof_2_mean'] = df[tof_2_list].fillna(0).mean(axis=1)\n    df['tof_3_mean'] = df[tof_3_list].fillna(0).mean(axis=1)\n    df['tof_4_mean'] = df[tof_4_list].fillna(0).mean(axis=1)\n    df['tof_5_mean'] = df[tof_5_list].fillna(0).mean(axis=1)\n\n    df['tof_1_std'] = df[tof_1_list].fillna(0).std(axis=1)\n    df['tof_2_std'] = df[tof_2_list].fillna(0).std(axis=1)\n    df['tof_3_std'] = df[tof_3_list].fillna(0).std(axis=1)\n    df['tof_4_std'] = df[tof_4_list].fillna(0).std(axis=1)\n    df['tof_5_std'] = df[tof_5_list].fillna(0).std(axis=1)\n\n    df['tof_1_min'] = df[tof_1_list].fillna(0).min(axis=1)\n    df['tof_2_min'] = df[tof_2_list].fillna(0).min(axis=1)\n    df['tof_3_min'] = df[tof_3_list].fillna(0).min(axis=1)\n    df['tof_4_min'] = df[tof_4_list].fillna(0).min(axis=1)\n    df['tof_5_min'] = df[tof_5_list].fillna(0).min(axis=1)\n\n    df['tof_1_max'] = df[tof_1_list].fillna(0).max(axis=1)\n    df['tof_2_max'] = df[tof_2_list].fillna(0).max(axis=1)\n    df['tof_3_max'] = df[tof_3_list].fillna(0).max(axis=1)\n    df['tof_4_max'] = df[tof_4_list].fillna(0).max(axis=1)\n    df['tof_5_max'] = df[tof_5_list].fillna(0).max(axis=1)\n\n    # df[raw_tof_feature] = df[raw_tof_feature].apply(\n    # lambda row: row.where(row != 0, row[row != 0].mean() if (row != 0).any() else 0),\n    # axis=1\n    # )\n\n    df['tof_mean'] = df[raw_tof_feature].mean(axis=1)\n\n    df['tof_top_left_mean'] = df[tof_top_left].fillna(0).mean(axis=1)\n    df['tof_top_right_mean'] = df[tof_top_right].fillna(0).mean(axis=1)\n    df['tof_bottom_left_mean'] = df[tof_bottom_left].fillna(0).mean(axis=1)\n    df['tof_bottom_right_mean'] = df[tof_bottom_right].fillna(0).mean(axis=1)\n\n\n    df['tof_top_diff'] = df['tof_top_left_mean'] - df['tof_top_right_mean']\n    df['tof_bottom_diff'] = df['tof_bottom_left_mean'] - df['tof_bottom_right_mean']\n    df['tof_vertical_diff'] = (df['tof_top_left_mean'] + df['tof_top_right_mean']) - (df['tof_bottom_left_mean'] + df['tof_bottom_right_mean'])\n    df['tof_horizontal_diff'] = (df['tof_top_left_mean'] + df['tof_bottom_left_mean']) - (df['tof_top_right_mean'] + df['tof_bottom_right_mean'])\n\n    df['tof_top_bottom_ratio'] = (df['tof_top_left_mean'] + df['tof_top_right_mean']) / (df['tof_bottom_left_mean'] + df['tof_bottom_right_mean'] + 1e-6)\n\n    df['tof_left_right_ratio'] = (df['tof_top_left_mean'] + df['tof_bottom_left_mean']) / (df['tof_top_right_mean'] + df['tof_bottom_right_mean'] + 1e-6)\n\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:03.341561Z","iopub.execute_input":"2025-08-30T12:28:03.341871Z","iopub.status.idle":"2025-08-30T12:28:03.361543Z","shell.execute_reply.started":"2025-08-30T12:28:03.341838Z","shell.execute_reply":"2025-08-30T12:28:03.360928Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tof_feature = ['tof_1_mean','tof_2_mean','tof_3_mean','tof_4_mean','tof_5_mean',\n               'tof_1_std','tof_2_std','tof_3_std','tof_4_std','tof_5_std',\n               'tof_1_min','tof_2_min','tof_3_min','tof_4_min','tof_5_min',\n               'tof_1_max','tof_2_max','tof_3_max','tof_4_max','tof_5_max',\n               'tof_top_left_mean','tof_top_right_mean','tof_bottom_left_mean','tof_bottom_right_mean',\n               'tof_mean',\n               'tof_top_diff','tof_bottom_diff','tof_vertical_diff','tof_horizontal_diff',\n               'tof_top_bottom_ratio', 'tof_left_right_ratio'\n              ]\ntof_feature.extend(tof_1_list)\ntof_feature.extend(tof_2_list)\ntof_feature.extend(tof_3_list)\ntof_feature.extend(tof_4_list)\ntof_feature.extend(tof_5_list)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:03.362182Z","iopub.execute_input":"2025-08-30T12:28:03.362372Z","iopub.status.idle":"2025-08-30T12:28:03.379946Z","shell.execute_reply.started":"2025-08-30T12:28:03.362357Z","shell.execute_reply":"2025-08-30T12:28:03.379193Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\ndf = TOF_Extractor(df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:03.380761Z","iopub.execute_input":"2025-08-30T12:28:03.381029Z","iopub.status.idle":"2025-08-30T12:28:23.282042Z","shell.execute_reply.started":"2025-08-30T12:28:03.381007Z","shell.execute_reply":"2025-08-30T12:28:23.281241Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"total_feature = acc_feature + rot_feature + thm_feature + tof_feature\n\nprint(f\"{c_}{sr_}=> Acc Feature Count is {len(acc_feature)}\\n\")\nprint(f\"{c_}{sr_}=> Rot Feature Count is {len(rot_feature)}\\n\")\nprint(f\"{c_}{sr_}=> Thm Feature Count is {len(thm_feature)}\\n\")\nprint(f\"{c_}{sr_}=> Tof Feature Count is {len(tof_feature)}\\n\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:23.282781Z","iopub.execute_input":"2025-08-30T12:28:23.283047Z","iopub.status.idle":"2025-08-30T12:28:23.288542Z","shell.execute_reply.started":"2025-08-30T12:28:23.283020Z","shell.execute_reply":"2025-08-30T12:28:23.287833Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7. Build Dataset, DataLoader\n___","metadata":{}},{"cell_type":"code","source":"label_to_num = {\n    'Above ear - pull hair': 0,  # < ------- TARGETS START\n    'Cheek - pinch skin': 1,\n    'Eyebrow - pull hair': 2,\n    'Eyelash - pull hair': 3,\n    'Forehead - pull hairline': 4,\n    'Forehead - scratch': 5,\n    'Neck - pinch skin': 6,\n    'Neck - scratch': 7,  # < ------- TARGETS END\n    'Drink from bottle/cup': 8,  # < ------- NON-TARGETS START\n    'Feel around in tray and pull out an object': 9,\n    'Glasses on/off': 10,\n    'Pinch knee/leg skin': 11,\n    'Pull air toward your face': 12,\n    'Scratch knee/leg skin': 13,\n    'Text on phone': 14,\n    'Wave hello': 15,\n    'Write name in air': 16,\n    'Write name on leg': 17  # < ------- NON-TARGETS END\n}\n\nnum_to_label = {k: v for k, v in enumerate(label_to_num)}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:23.289348Z","iopub.execute_input":"2025-08-30T12:28:23.289582Z","iopub.status.idle":"2025-08-30T12:28:23.306168Z","shell.execute_reply.started":"2025-08-30T12:28:23.289558Z","shell.execute_reply":"2025-08-30T12:28:23.305361Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# df['gesture'] = df['gesture'].map(label_to_num)\n\n# one hot encoding\nlabel_list = []\n\nfor label, _ in label_to_num.items():\n    df[label] = np.where(df['gesture'] == label, 1.0 - CFG.label_smoothing, CFG.label_smoothing / (CFG.num_classes - 1)).astype('float32')\n    label_list.append(label)\n\nprint(label_list)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:23.306938Z","iopub.execute_input":"2025-08-30T12:28:23.307142Z","iopub.status.idle":"2025-08-30T12:28:23.948071Z","shell.execute_reply.started":"2025-08-30T12:28:23.307122Z","shell.execute_reply":"2025-08-30T12:28:23.947374Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### StratifiedGroupKFold","metadata":{"editable":false}},{"cell_type":"code","source":"sgkf = StratifiedGroupKFold(n_splits=CFG.n_splits, shuffle=True, random_state=CFG.seed)\n\nfor i, (_, val_idx) in enumerate(sgkf.split(df, df['gesture'], groups=df['subject'])):\n    df.loc[val_idx, 'fold'] = i","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:23.949020Z","iopub.execute_input":"2025-08-30T12:28:23.949199Z","iopub.status.idle":"2025-08-30T12:28:25.524095Z","shell.execute_reply.started":"2025-08-30T12:28:23.949185Z","shell.execute_reply":"2025-08-30T12:28:25.523480Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CMI_Dataset(Dataset):\n    def __init__(self, data, col, transforms=False, mix_up=False):\n        super(CMI_Dataset, self).__init__()\n        self.data = data\n        self.seq_list = data['sequence_id'].unique()\n        self.col = col\n        self.transforms = transforms\n        self.mix_up = mix_up\n\n    def __len__(self):\n        return len(self.seq_list)\n\n    def __getitem__(self, index):\n\n        X, y= self.__data_generation(index)\n\n        if self.mix_up and (np.random.rand() < CFG.mix_up_prob):\n            mix_index = np.random.randint(len(self.seq_list))\n            mix_X, mix_y = self.__data_generation(mix_index)\n            \n            lam = np.random.beta(CFG.mix_up_alpha, CFG.mix_up_alpha)\n\n            X = lam * X + (1 - lam) * mix_X\n            y = lam * y + (1 - lam) * mix_y\n        \n\n        X = torch.tensor(X, dtype=torch.float32).permute(1,0)\n        y = torch.tensor(y, dtype=torch.float32)\n\n        return X, y\n\n    def __data_generation(self, index):\n        X = np.zeros((CFG.sequence_len,len(self.col)), dtype='float32')\n        y = np.zeros(CFG.num_classes, dtype='float32')\n\n        seq_id = self.seq_list[index]\n        row = self.data[self.data['sequence_id'] == seq_id]\n\n        inter_ratio = CFG.sequence_len / len(row)\n        end = int((CFG.sequence_len - row['Gesture_len'].values[0] * inter_ratio))\n        start = int((CFG.sequence_len - (row['Gesture_len'].values[0]+row['Pause_len'].values[0]) * inter_ratio))\n\n        for i, col in enumerate(self.col):\n            signal = inter_signal(row[col].values)\n\n            # normalization\n            if col not in raw_rot_feature:\n                mean = np.mean(signal)\n                eps = 1e-9\n                std = np.std(signal) + eps\n                signal = (signal - mean) / std\n\n            X[:,i] = signal\n\n        ## Augmentation\n        if self.transforms:\n            augment = Compose([\n                Phase_Locked_Time_Shift(start=start, end=end, p=0.5),\n                Jitter_Scale(max_noise_amplitude=0.02, scale=0.1, p=0.5),\n\n            ])\n\n            X = augment(X)\n\n        ## THM, TOF \n        if np.random.rand() < 0.3:\n            X[:,len(acc_feature+rot_feature):] = 0\n\n\n        y = row[label_list].values[0]\n\n        return X, y","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:25.524711Z","iopub.execute_input":"2025-08-30T12:28:25.524932Z","iopub.status.idle":"2025-08-30T12:28:25.535422Z","shell.execute_reply.started":"2025-08-30T12:28:25.524915Z","shell.execute_reply":"2025-08-30T12:28:25.534671Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"_, augmented_valid_loader = prepare_loader(fold=CFG.fold, valid_transform=True)\n_, valid_loader = prepare_loader(fold=CFG.fold, valid_transform=False)\n\naugmented_seqs, tars = next(iter(augmented_valid_loader))\nseqs, _ = next(iter(valid_loader))\n\ndel valid_loader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:25.536265Z","iopub.execute_input":"2025-08-30T12:28:25.536490Z","iopub.status.idle":"2025-08-30T12:28:37.861511Z","shell.execute_reply.started":"2025-08-30T12:28:25.536474Z","shell.execute_reply":"2025-08-30T12:28:37.860460Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ROWS = 2; COLS = 8\n\nfig, axes = plt.subplots(ROWS, COLS, figsize=(2*COLS, 2*ROWS))\naxes = axes.flatten()\n\n\nfor j in range(COLS):\n\n    seq = seqs[j].permute(1,0).cpu().numpy()\n    augmented_seq = augmented_seqs[j].permute(1,0).cpu().numpy()\n\n    for k in range(len(raw_acc_feature)):\n        axes[j].plot(seq[:,k])\n        axes[j].set_title('Before Augmented')\n\n    for k in range(len(raw_acc_feature)):\n        axes[COLS+j].plot(augmented_seq[:,k])\n        axes[COLS+j].set_title('After Augmented')\n        \n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:37.862599Z","iopub.execute_input":"2025-08-30T12:28:37.862803Z","iopub.status.idle":"2025-08-30T12:28:39.860144Z","shell.execute_reply.started":"2025-08-30T12:28:37.862780Z","shell.execute_reply":"2025-08-30T12:28:39.859424Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 8. Build WaveBlock, WaveNet \n___","metadata":{}},{"cell_type":"code","source":"class Wave_Block(nn.Module):\n    def __init__(self, in_channels, out_channels, dilation_rates, kernel_size=3):\n        super(Wave_Block, self).__init__()\n        self.num_rates = dilation_rates\n        self.convs = nn.ModuleList()\n        self.filter_convs = nn.ModuleList()\n        self.gate_convs = nn.ModuleList()\n        self.convs.append(nn.Conv1d(in_channels, out_channels, kernel_size=1, bias=True))\n        dilation_rates = [2 ** i for i in range(dilation_rates)]\n\n        for dilation_rate in dilation_rates:\n            self.filter_convs.append(\n                nn.Conv1d(out_channels, out_channels, kernel_size=kernel_size,\n                          padding=int((dilation_rate*(kernel_size-1))/2), dilation=dilation_rate)\n            )\n            self.gate_convs.append(\n                nn.Conv1d(out_channels, out_channels, kernel_size=kernel_size,\n                          padding=int((dilation_rate*(kernel_size-1))/2), dilation=dilation_rate)\n            )\n            self.convs.append(\n                nn.Conv1d(out_channels, out_channels, kernel_size=1, bias=True)\n            )\n\n        for i in range(len(self.convs)):\n            nn.init.xavier_uniform_(self.convs[i].weight, gain=nn.init.calculate_gain('relu'))\n            nn.init.zeros_(self.convs[i].bias)\n            \n        for i in range(len(self.filter_convs)):\n            nn.init.xavier_uniform_(self.filter_convs[i].weight, gain=nn.init.calculate_gain('relu'))\n            nn.init.zeros_(self.filter_convs[i].bias)\n            \n        for i in range(len(self.gate_convs)):\n            nn.init.xavier_uniform_(self.gate_convs[i].weight, gain=nn.init.calculate_gain('relu'))\n            nn.init.zeros_(self.gate_convs[i].bias)\n\n    def forward(self,x):\n        x = self.convs[0](x)\n        res = x # skip_connection\n        for i in range(self.num_rates):\n            tanh_out = torch.tanh(self.filter_convs[i](x))\n            sigmoid_out = torch.sigmoid(self.gate_convs[i](x))\n            x = tanh_out * sigmoid_out\n            x = self.convs[i+1](x)\n            res = res + x\n    \n        return res","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:39.861140Z","iopub.execute_input":"2025-08-30T12:28:39.861383Z","iopub.status.idle":"2025-08-30T12:28:39.871882Z","shell.execute_reply.started":"2025-08-30T12:28:39.861365Z","shell.execute_reply":"2025-08-30T12:28:39.871141Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class WaveNet(nn.Module):\n    def __init__(self, kernel_size, in_channels = 1):\n        super(WaveNet, self).__init__()\n\n        self.model = nn.Sequential(\n            Wave_Block(in_channels, 64, 2, kernel_size),\n             nn.MaxPool1d(2),\n             nn.Dropout(0.4),\n             Wave_Block(64, 128, 2, kernel_size),\n             nn.MaxPool1d(2),\n             nn.Dropout(0.2),\n        )\n\n    def forward(self, x):\n        output = self.model(x)\n        return output","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:39.872720Z","iopub.execute_input":"2025-08-30T12:28:39.872994Z","iopub.status.idle":"2025-08-30T12:28:39.890222Z","shell.execute_reply.started":"2025-08-30T12:28:39.872972Z","shell.execute_reply":"2025-08-30T12:28:39.889552Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TemporalAttentionPooling(nn.Module):\n    def __init__(self, input_channels, hidden_dim=64):\n        super(TemporalAttentionPooling, self).__init__()\n        self.attn = nn.Sequential(\n            nn.Conv1d(input_channels, hidden_dim, kernel_size=1),\n            nn.Tanh(),\n            nn.Conv1d(hidden_dim, 1, kernel_size=1)\n        )\n\n    def forward(self, x):\n        attn_scores = self.attn(x)\n        attn_weights = F.softmax(attn_scores, dim=-1)\n\n        weighted = x * attn_weights\n        pooled = weighted.sum(dim=-1)\n\n        return pooled","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:39.890933Z","iopub.execute_input":"2025-08-30T12:28:39.891142Z","iopub.status.idle":"2025-08-30T12:28:39.909802Z","shell.execute_reply.started":"2025-08-30T12:28:39.891126Z","shell.execute_reply":"2025-08-30T12:28:39.909161Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 9. Build 1D CNN Model\n___","metadata":{}},{"cell_type":"code","source":"class SEBlock(nn.Module):\n    def __init__(self, in_channels, reduction=8):\n        super(SEBlock, self).__init__()\n        self.avgpool = nn.AdaptiveAvgPool1d(1)\n\n        self.mlp = nn.Sequential(\n            nn.Conv1d(in_channels, in_channels//reduction, 1, bias=False),\n            nn.ReLU(inplace=True),\n            nn.Conv1d(in_channels//reduction, in_channels, 1, bias=False),\n            nn.Sigmoid(),\n        )\n\n    def forward(self, input):\n\n        scale = self.avgpool(input)\n        scale = self.mlp(scale)\n        \n\n        return input * scale\n\nclass Residual_Block(nn.Module):\n    def __init__(self, in_channels, out_channels, kernel_size):\n        super(Residual_Block, self).__init__()\n\n        self.model = nn.Sequential(\n            nn.Conv1d(in_channels, out_channels, kernel_size, padding='same', bias=False),\n            nn.BatchNorm1d(out_channels),\n            nn.ReLU(),\n            nn.Conv1d(out_channels, out_channels, kernel_size, padding='same', bias=False),\n            nn.BatchNorm1d(out_channels),\n            nn.ReLU()\n        )\n\n        self.se_block = SEBlock(out_channels)\n\n        self.shortcut = nn.Sequential(\n            nn.Conv1d(in_channels, out_channels, 1, padding='same', bias=False),\n            nn.BatchNorm1d(out_channels)\n        )\n\n        self.dropout = nn.Dropout(0.2)  \n        self.maxpool = nn.MaxPool1d(2)\n        \n    def forward(self, x):\n        shortcut = self.shortcut(x)\n        out = self.model(x)\n        out = self.se_block(out)\n        out = out + shortcut\n        out = F.relu(out)\n        out = self.maxpool(out)\n        out = self.dropout(out)\n\n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:39.910613Z","iopub.execute_input":"2025-08-30T12:28:39.910872Z","iopub.status.idle":"2025-08-30T12:28:39.925473Z","shell.execute_reply.started":"2025-08-30T12:28:39.910847Z","shell.execute_reply":"2025-08-30T12:28:39.924876Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Res_CNN_GRU_Attention(nn.Module):\n    def __init__(self, in_channels, out_channels, kernel_size):\n        super(Res_CNN_GRU_Attention, self).__init__()\n\n        \n        self.wave_model = WaveNet(kernel_size, in_channels=in_channels)\n\n        self.res_model1 = Residual_Block(in_channels=in_channels, out_channels=out_channels//2, kernel_size=kernel_size)\n        self.res_model2 = Residual_Block(in_channels=out_channels//2, out_channels=out_channels, kernel_size=kernel_size+2)\n\n        self.gru = nn.ModuleList([\n            nn.GRU(input_size=out_channels, hidden_size=out_channels//2, num_layers=1, \n                          batch_first=True, bidirectional=True),\n            nn.GRU(input_size=out_channels, hidden_size=out_channels//2, num_layers=1, \n                          batch_first=True, bidirectional=True),\n        \n            ])\n        self.attn_pool = nn.ModuleList([\n            TemporalAttentionPooling(input_channels=128, hidden_dim=64),\n            TemporalAttentionPooling(input_channels=128, hidden_dim=64),\n        ])\n            \n         \n     \n\n    def forward(self, x):\n        \n        x_trans, x_ges = x[:, :, :CFG.sequence_len//2], x[:, :, CFG.sequence_len//2:]\n\n        ## wave_branch\n        x_trans = self.wave_model(x_trans)\n        gru_trans, _ = self.gru[0](x_trans.permute(0,2,1))\n        trans_out = self.attn_pool[0](gru_trans.permute(0,2,1))\n\n        ## cnn_branch\n        x_ges = self.res_model1(x_ges)\n        x_ges = self.res_model2(x_ges)\n        gru_ges, _ = self.gru[1](x_ges.permute(0,2,1))\n        ges_out = self.attn_pool[1](gru_ges.permute(0,2,1))\n\n        return trans_out, ges_out\n         ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:39.926298Z","iopub.execute_input":"2025-08-30T12:28:39.926534Z","iopub.status.idle":"2025-08-30T12:28:39.943192Z","shell.execute_reply.started":"2025-08-30T12:28:39.926518Z","shell.execute_reply":"2025-08-30T12:28:39.942646Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CNN_GRU_Attention(nn.Module):\n    def __init__(self, device_feature):\n        super(CNN_GRU_Attention, self).__init__()\n\n        self.avg_pool = nn.AdaptiveAvgPool1d(1)\n\n        # Wave\n        self.wave_feature = nn.Sequential(\n             Wave_Block(device_feature, 32, 2, 3),\n             nn.MaxPool1d(2),\n             nn.Dropout(0.2),\n             Wave_Block(32, 64, 2, 3),\n             nn.MaxPool1d(2),\n             nn.Dropout(0.2),\n        )\n\n        # 1D CNN\n        self.conv1 = nn.Conv1d(in_channels=device_feature, out_channels=64, kernel_size=3, \n                               padding='same', bias=False)\n        self.batch_norm1 = nn.BatchNorm1d(64)\n        self.relu = nn.ReLU()\n        self.max_pool1 = nn.MaxPool1d(2)\n        self.max_pool2 = nn.MaxPool1d(2)\n        self.dropout1 = nn.Dropout(0.2)\n\n        self.conv2 = nn.Conv1d(in_channels=64, out_channels=128, kernel_size=3, \n                               padding='same', bias=False)\n        self.batch_norm2 = nn.BatchNorm1d(128)\n        self.dropout2 = nn.Dropout(0.2)\n        \n\n        # GRU\n        self.gru = nn.ModuleList([\n            nn.GRU(input_size=64, hidden_size=32, num_layers=1, \n                          batch_first=True, bidirectional=True),\n            nn.GRU(input_size=128, hidden_size=64, num_layers=1, \n                          batch_first=True, bidirectional=True),\n            ])\n        \n        self.attn_pool = nn.ModuleList([\n            TemporalAttentionPooling(input_channels=64, hidden_dim=32),\n            TemporalAttentionPooling(input_channels=128, hidden_dim=64),\n        ])\n            \n\n    def forward(self, x):\n        \n        x_trans, x_ges = x[:, :, :CFG.sequence_len//2], x[:, :, CFG.sequence_len//2:]\n\n        # wave branch with transition part\n        x_trans = self.wave_feature(x_trans)\n\n        gru_trans, _ = self.gru[0](x_trans.permute(0,2,1))\n        trans_out = self.attn_pool[0](gru_trans.permute(0,2,1))\n\n        # cnn_branch with gesture part\n        x_ges = self.conv1(x_ges)\n        x_ges = self.batch_norm1(x_ges)\n        x_ges = self.relu(x_ges)\n        x_ges = self.max_pool1(x_ges)\n        x_ges = self.dropout1(x_ges)\n\n        x_ges = self.conv2(x_ges)\n        x_ges = self.batch_norm2(x_ges)\n        x_ges = self.relu(x_ges)\n        x_ges = self.max_pool2(x_ges)\n        x_ges = self.dropout2(x_ges)\n\n        gru_ges, _ = self.gru[1](x_ges.permute(0,2,1))\n        ges_out = self.attn_pool[1](gru_ges.permute(0,2,1))\n\n        return trans_out, ges_out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:39.943885Z","iopub.execute_input":"2025-08-30T12:28:39.944131Z","iopub.status.idle":"2025-08-30T12:28:39.962174Z","shell.execute_reply.started":"2025-08-30T12:28:39.944111Z","shell.execute_reply":"2025-08-30T12:28:39.961585Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 10. Build Hybrid Model\n___","metadata":{}},{"cell_type":"code","source":"class Multi_Model(nn.Module):\n    def __init__(self, kernel_size):\n        super(Multi_Model, self).__init__()\n    \n        self.imu_model = Res_CNN_GRU_Attention(in_channels=len(acc_feature+rot_feature), out_channels=128, kernel_size=3)\n\n        self.thm_tof_model = CNN_GRU_Attention(device_feature = len(thm_feature+tof_feature))\n\n        self.dropout = nn.Dropout(0.2)\n\n    def forward(self, x):\n\n        # acc\n        imu_trans, imu_ges = self.imu_model(x[:,0:30,:])\n\n        # thm + tof\n        thm_tof_trans, thm_tof_ges = self.thm_tof_model(x[:,30:,:])\n\n        thm_tof_trans = thm_tof_trans\n        thm_tof_ges   = thm_tof_ges\n        \n        trans_out = torch.cat([imu_trans, thm_tof_trans], dim=1)\n        trans_out = self.dropout(trans_out)\n\n        ges_out = torch.cat([imu_ges, thm_tof_ges], dim=1)\n        ges_out = self.dropout(ges_out)\n\n        y = torch.cat([trans_out, ges_out], dim=1)\n\n        return y","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:39.962867Z","iopub.execute_input":"2025-08-30T12:28:39.963066Z","iopub.status.idle":"2025-08-30T12:28:39.980148Z","shell.execute_reply.started":"2025-08-30T12:28:39.963052Z","shell.execute_reply":"2025-08-30T12:28:39.979480Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -q torchinfo\nfrom torchinfo import summary\n\nsummary(\n        Multi_Model(kernel_size=CFG.kernel_size).cpu(), \n        input_size=(1, len(total_feature), CFG.sequence_len), \n        col_names=[\"input_size\", \"output_size\", \"num_params\", \"mult_adds\"],\n        depth=2,\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:39.980932Z","iopub.execute_input":"2025-08-30T12:28:39.981183Z","iopub.status.idle":"2025-08-30T12:28:44.732857Z","shell.execute_reply.started":"2025-08-30T12:28:39.981158Z","shell.execute_reply":"2025-08-30T12:28:44.732101Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 11. Pytorch Lightning Tranining\n___","metadata":{}},{"cell_type":"code","source":"def SoftCrossEntropy(pred, target, class_weight=None):\n    log_probs = torch.log_softmax(pred, dim=1)\n    if class_weight is not None:\n        loss = -(target * log_probs * class_weight).sum(dim=1).mean()\n    else:\n        loss = -(target * log_probs).sum(dim=1).mean()\n    return loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:44.733785Z","iopub.execute_input":"2025-08-30T12:28:44.734096Z","iopub.status.idle":"2025-08-30T12:28:44.738638Z","shell.execute_reply.started":"2025-08-30T12:28:44.734060Z","shell.execute_reply":"2025-08-30T12:28:44.738021Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_weight = torch.tensor([1.2]*8 + [0.8]*10).to(CFG.device)\nclass_weight","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:44.739511Z","iopub.execute_input":"2025-08-30T12:28:44.739765Z","iopub.status.idle":"2025-08-30T12:28:44.951227Z","shell.execute_reply.started":"2025-08-30T12:28:44.739742Z","shell.execute_reply":"2025-08-30T12:28:44.950514Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SupConLoss(torch.nn.Module):\n    def __init__(self, temperature=0.1):\n        super().__init__()\n        self.temperature = temperature\n\n    def forward(self, features, labels):\n        # features: (B, feature_dim)\n        # labels: (B,)\n        device = features.device\n        batch_size = features.shape[0]\n\n        features = F.normalize(features, dim=1)  # L2 normalization\n        mask = torch.eq(labels.unsqueeze(1), labels.unsqueeze(0)).float().to(device)  # (B, B)\n\n        anchor_dot_contrast = torch.div(\n            torch.matmul(features, features.T),\n            self.temperature\n        )\n\n        # negative mask\n        logits_mask = torch.ones_like(mask) - torch.eye(batch_size, device=device)\n        mask = mask * logits_mask  # remove self-comparison\n\n        # compute log_prob\n        exp_logits = torch.exp(anchor_dot_contrast) * logits_mask\n        log_prob = anchor_dot_contrast - torch.log(exp_logits.sum(1, keepdim=True) + 1e-12)\n\n        mean_log_prob_pos = (mask * log_prob).sum(1) / (mask.sum(1) + 1e-12)\n\n        loss = -mean_log_prob_pos.mean()\n        return loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:44.952145Z","iopub.execute_input":"2025-08-30T12:28:44.952430Z","iopub.status.idle":"2025-08-30T12:28:44.958627Z","shell.execute_reply.started":"2025-08-30T12:28:44.952406Z","shell.execute_reply":"2025-08-30T12:28:44.958019Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchmetrics.classification import MulticlassF1Score, BinaryF1Score\n\nclass Final_Model(pl.LightningModule):\n    def __init__(self):\n        super().__init__()\n        self.feature_extractor = Multi_Model(kernel_size=CFG.kernel_size)\n\n        dummy_input = torch.randn(1, len(total_feature), CFG.sequence_len)\n        with torch.no_grad():\n            dummy_out = self.feature_extractor(dummy_input)\n        feature_dim = dummy_out.shape[1]\n\n        self.head = nn.Sequential(\n            nn.Linear(feature_dim, feature_dim//2, bias=False),\n            nn.BatchNorm1d(feature_dim//2),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n            nn.Linear(feature_dim//2, CFG.num_classes)\n        )\n        self.supcon_criterion = SupConLoss(temperature=0.2)\n\n        self.train_f1 = MulticlassF1Score(num_classes=CFG.num_classes, average='macro')\n        self.valid_f1 = MulticlassF1Score(num_classes=CFG.num_classes, average='macro')\n\n    def forward(self, x):\n        out = self.feature_extractor(x)\n        out = self.head(out)\n        return out\n\n    def training_step(self, batch, batch_idx):\n\n        seqs, tars = batch\n        features = self.feature_extractor(seqs)\n\n        outs = self.head(features)\n\n        loss = SoftCrossEntropy(outs, tars, class_weight=class_weight)\n\n        preds = outs.argmax(dim=1)\n        hard_tars = tars.argmax(dim=1)\n        \n        supcon_loss = self.supcon_criterion(features, hard_tars)\n\n        self.train_f1.update(preds, hard_tars)\n\n        self.log('Train Loss', loss, on_step=False, on_epoch=True, prog_bar=True)\n        self.log('Train Supcon Loss', supcon_loss, on_step=False, on_epoch=True, prog_bar=True)\n        self.log('Train F1-Score', self.train_f1, on_step=False, on_epoch=True, prog_bar=True)\n        \n        \n        total_loss = loss * 1.0 + supcon_loss * 0.1\n\n        return loss\n\n    def validation_step(self, batch, batch_idx):\n\n        seqs, tars = batch\n        features = self.feature_extractor(seqs)\n        outs = self.head(features)\n\n        loss = SoftCrossEntropy(outs, tars, class_weight=class_weight)\n        \n        preds = outs.argmax(dim=1)\n        hard_tars = tars.argmax(dim=1)\n        supcon_loss = self.supcon_criterion(features, hard_tars)\n\n        self.valid_f1.update(preds, hard_tars)\n\n        self.log('Valid Loss', loss, on_step=False, on_epoch=True, prog_bar=True)\n        self.log('Valid Supcon Loss', supcon_loss, on_step=False, on_epoch=True, prog_bar=True)\n        self.log('Valid F1-Score', self.valid_f1, on_step=False, on_epoch=True, prog_bar=True)\n\n\n    def on_train_epoch_start(self):\n        self.train_f1.reset()\n\n    def on_validation_epoch_start(self):\n        self.valid_f1.reset()\n\n    def configure_optimizers(self):\n        optimizer = torch.optim.AdamW(self.parameters(), lr=CFG.lr, weight_decay=CFG.wd)\n        warmup_epochs = CFG.warmup_epochs\n        cosine_epochs = max(1, CFG.epochs - warmup_epochs)\n\n        warmup_scheduler = torch.optim.lr_scheduler.LinearLR(\n            optimizer, start_factor=0.1, total_iters=warmup_epochs\n        )\n        cosine_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n            optimizer, T_max=cosine_epochs, eta_min=CFG.min_lr\n        )\n        scheduler = torch.optim.lr_scheduler.SequentialLR(\n            optimizer,\n                schedulers=[warmup_scheduler, cosine_scheduler],\n            milestones=[warmup_epochs]\n        )\n\n        return {\n            'optimizer': optimizer,\n            'lr_scheduler': {\n            'scheduler': scheduler,\n            'interval': 'epoch',\n            'frequency': 1\n            }\n        }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:44.959431Z","iopub.execute_input":"2025-08-30T12:28:44.959653Z","iopub.status.idle":"2025-08-30T12:28:44.974877Z","shell.execute_reply.started":"2025-08-30T12:28:44.959637Z","shell.execute_reply":"2025-08-30T12:28:44.974170Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pytorch_lightning.callbacks import StochasticWeightAveraging\n\nswa_callback = StochasticWeightAveraging(\n    swa_epoch_start=int(CFG.epochs * 0.75),      \n    swa_lrs= 3e-4,          \n    annealing_epochs=int(CFG.epochs * 0.25),\n    annealing_strategy='cos',\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:44.975633Z","iopub.execute_input":"2025-08-30T12:28:44.975802Z","iopub.status.idle":"2025-08-30T12:28:44.994258Z","shell.execute_reply.started":"2025-08-30T12:28:44.975788Z","shell.execute_reply":"2025-08-30T12:28:44.993562Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pytorch_lightning.loggers import WandbLogger\n\nfor fold in range(CFG.n_fold):\n    print(f\"Training fold {fold}...\")\n\n\n    train_loader, valid_loader = prepare_loader(fold=fold)\n\n    wandb_logger = WandbLogger(\n        offline = CFG.offline,\n        log_model= CFG.log_model,\n        project=CFG.project,\n        name=f\"{CFG.model_name}_debug_{CFG.debug}\",\n        group=CFG.comment,\n        anonymous = anonymous,\n        config={\n            'cv': 'sgkf',\n            'fold': CFG.fold,\n            'seq_length': CFG.sequence_len,\n            'epoch': CFG.epochs,\n            'batch_size': CFG.train_bs,\n            'model_name': CFG.model_name,\n            'base_lr': CFG.lr,\n            'min_lr': CFG.min_lr,\n            'optimizer': 'AdamW',\n            'weight_decay': CFG.wd,\n            'scheduler': CFG.scheduler,\n        }\n        )\n\n    trainer = pl.Trainer(\n        max_epochs=CFG.epochs,\n        accelerator='gpu',\n        devices=1,\n        precision='16-mixed',\n        callbacks = [LearningRateMonitor(logging_interval='epoch'),\n        # swa_callback,\n             ],\n\n        logger=wandb_logger\n        )\n\n    cls_model = Final_Model();\n\n    trainer.fit(model = cls_model, train_dataloaders = train_loader,\n           val_dataloaders = valid_loader)\n    trainer.save_checkpoint(f\"{CFG.save_dir}last_fold{fold}.ckpt\")\n\n    del cls_model, trainer, train_loader, valid_loader\n    torch.cuda.empty_cache()\n\nwandb.finish()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:28:44.995194Z","iopub.execute_input":"2025-08-30T12:28:44.995514Z","iopub.status.idle":"2025-08-30T12:39:10.212626Z","shell.execute_reply.started":"2025-08-30T12:28:44.995497Z","shell.execute_reply":"2025-08-30T12:39:10.212044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"wandb_logger.experiment ## Click Display W&B","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:39:10.213579Z","iopub.execute_input":"2025-08-30T12:39:10.213869Z","iopub.status.idle":"2025-08-30T12:39:10.219096Z","shell.execute_reply.started":"2025-08-30T12:39:10.213811Z","shell.execute_reply":"2025-08-30T12:39:10.218364Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 12. Feature Embedding\n___","metadata":{}},{"cell_type":"code","source":"ckpt_path = f\"{CFG.save_dir}last_fold0.ckpt\"\n\ncls_model = Final_Model.load_from_checkpoint(ckpt_path)\ncls_model.eval();\n\n_, valid_loader = prepare_loader(fold=0)\n\nfeature_list = []\nlabels = []\n\nwith torch.no_grad():\n    for batch in tqdm(valid_loader):\n        seqs, tars = batch\n        feature = cls_model.feature_extractor(seqs)\n\n        feature_list.append(feature)\n        labels.append(tars)\n\nfeature_list = torch.concat(feature_list, dim=0).cpu().numpy()\nlabels = torch.concat(labels, dim=0).cpu().numpy()\n\ndel cls_model; clean_memory()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:39:44.844921Z","iopub.execute_input":"2025-08-30T12:39:44.845207Z","iopub.status.idle":"2025-08-30T12:39:51.290697Z","shell.execute_reply.started":"2025-08-30T12:39:44.845179Z","shell.execute_reply":"2025-08-30T12:39:51.289992Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import umap\n\nreducer = umap.UMAP(n_components=2, random_state=42)\nembedding = reducer.fit_transform(feature_list)\n\nplt.figure(figsize=(8,6))\nsns.scatterplot(\n    x=embedding[:,0], y=embedding[:,1],\n    hue=np.minimum(labels.argmax(axis=1), 8),\n    palette='tab10',\n    s=50,\n    alpha=0.8\n)\nplt.title('UMAP of Feature Extractor Output')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:39:51.292532Z","iopub.execute_input":"2025-08-30T12:39:51.292764Z","iopub.status.idle":"2025-08-30T12:40:22.505164Z","shell.execute_reply.started":"2025-08-30T12:39:51.292746Z","shell.execute_reply":"2025-08-30T12:40:22.504388Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 13. K-Fold Cross-Validation\n___","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import f1_score, accuracy_score\n\nall_val_pred = []; all_val_true = []\n\n\nfor fold in range(CFG.n_fold):\n    print(f\"Evaluating fold {fold}...\")\n\n    ckpt_path = f\"{CFG.save_dir}last_fold{fold}.ckpt\"\n\n    cls_model = Final_Model.load_from_checkpoint(ckpt_path)\n    cls_model.eval(); cls_model.to(CFG.device)\n\n    _, valid_loader = prepare_loader(fold=fold)\n\n    val_pred = []; val_true = []\n\n    with torch.no_grad():\n        for batch in tqdm(valid_loader):\n            seqs, tars = batch\n            seqs = seqs.to(CFG.device)\n            outs = cls_model(seqs)\n\n            val_pred.append(outs.argmax(dim=1).cpu().numpy())\n            val_true.append(tars.argmax(dim=1).cpu().numpy())\n\n    val_pred = np.concatenate(val_pred, axis=0)\n    val_true = np.concatenate(val_true, axis=0)\n\n    binary_f1 = f1_score((val_true < 8).astype(int), (val_pred < 8).astype(int), pos_label=True, zero_division=0, average='binary')\n    macro_f1 = f1_score(np.where(val_true >= 8, 8, val_true), np.where(val_pred >= 8, 8, val_pred), average='macro', zero_division=0)\n    total_f1 = binary_f1 * 0.5 + macro_f1 * 0.5\n\n    print(f'{c_}{sr_}#'*25)\n    print(f\"Fold {fold}\")\n    print(f\"binary f1: {binary_f1}\")\n    print(f\"macro f1: {macro_f1}\")\n    print(f\"total f1: {total_f1}\")\n    print('#'*25)\n\n    all_val_pred.append(val_pred)\n    all_val_true.append(val_true)\n\nall_val_pred = np.concatenate(all_val_pred, axis=0)\nall_val_true = np.concatenate(all_val_true, axis=0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:40:22.505937Z","iopub.execute_input":"2025-08-30T12:40:22.506475Z","iopub.status.idle":"2025-08-30T12:40:43.193577Z","shell.execute_reply.started":"2025-08-30T12:40:22.506456Z","shell.execute_reply":"2025-08-30T12:40:43.192707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\n\ncls_pred_df = pd.DataFrame(all_val_pred, columns=['pred'])\ncls_true_df = pd.DataFrame(all_val_true, columns=['true'])\n\nacc = accuracy_score((cls_true_df['true'] < 8).astype(int), (cls_pred_df['pred'] < 8).astype(int))\nf1 = f1_score((cls_true_df['true'] < 8).astype(int), (cls_pred_df['pred'] < 8).astype(int))\n\nx_min = 0\nx_max = 1.001\nbin_size = 0.05\n\ntrace1 = go.Histogram(\n    x=cls_pred_df['pred'],\n    opacity=0.5,\n    name='Predicted',\n    marker=dict(color='blue'),\n    xbins=dict(\n        start=x_min,\n        end=x_max,\n        size=bin_size\n    )\n)\n\ntrace2 = go.Histogram(\n    x=cls_true_df['true'],\n    opacity=0.5,\n    name='True',\n    marker=dict(color='pink'),\n    xbins=dict(\n        start=x_min,\n        end=x_max,\n        size=bin_size\n    )\n)\n\ndata = [trace1, trace2]\n\nlayout = go.Layout(\n    title=f\"Target(1) vs Non-Target(0): Accuracy is {acc*100:.1f}, F1-Score is {f1*100:.1f}%\",\n    barmode='overlay',\n    legend=dict(\n        bordercolor='black',\n        borderwidth=1\n    ),\n    xaxis_title=\"Value\",\n    yaxis_title=\"Count\"\n)\n\nfig = go.Figure(data=data, layout=layout)\n\nfig.show(renderer=\"iframe\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-30T12:40:43.552808Z","iopub.execute_input":"2025-08-30T12:40:43.553088Z","iopub.status.idle":"2025-08-30T12:40:43.600266Z","shell.execute_reply.started":"2025-08-30T12:40:43.553063Z","shell.execute_reply":"2025-08-30T12:40:43.599555Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Confusion Matrix","metadata":{}}]}