{
  "id": 604087,
  "title": "10th Place Solution",
  "url": "/competitions/cmi-detect-behavior-with-sensor-data/discussion/604087",
  "author_name": "ohkawa3",
  "post_date": "2025-09-06T05:56:20.838000",
  "votes": 28,
  "comment_count": 0,
  "views": 0,
  "content": "<p>First, I would like to express my gratitude for organizing this exciting competition. For me, this marks my first gold medal in nearly two years. Moreover, since I previously achieved 10th place at the same Child Mind Institute competition through shakeup, these results feel particularly meaningful to me.</p>\n<hr>\n<h2>Training Strategy</h2>\n<ul>\n<li><strong>Epochs &amp; Optimizer</strong>: 200 epochs with <strong>AdamW</strong> optimizer and <strong>EMA</strong> (decay=0.999), no learning rate scheduling.</li>\n<li><strong>Validation Strategy</strong>: <strong>StratifiedGroupKFold</strong> with <code>groups=\"subject\"</code>, <code>k=5</code>. CV splits were generated using three different seed values: 0, 1, and 2.</li>\n<li><strong>Experiment Management</strong>:<ul>\n<li>For clearly underperforming experiments, only the initial seed (seed 0) was run. All other experiments were evaluated across all three seeds.</li>\n<li>The F1 metric showed significant instability; in some cases, performance was high in seed=0 folds but low in seeds 1 and 2.</li></ul></li>\n<li><strong>Model Selection</strong>:<ul>\n<li>Adopted the best epoch based on the average performance across all 15 experiments (5 folds x 3 seeds).</li>\n<li>Additionally, if the F1 metric was unchanged but the loss improved, that model was selected.</li></ul></li>\n</ul>\n<hr>\n<h2>Preprocessing</h2>\n<h3>Handling of NaN values</h3>\n<ul>\n<li><strong>Rotation (<code>rot</code>)</strong>: Replaced missing values with unit quaternions.</li>\n<li><strong>Thermal (<code>thm</code>)</strong>:<ul>\n<li>For each sequence, if any sensor value was below 18 degrees, we replaced that sensor's corresponding value with NaN. This was done to mask potentially corrupted readings.</li>\n<li>Calculated the row-wise mean of NaN values and used it for imputation.</li>\n<li>For rows containing only NaN values, we assigned a global mean of <code>27.03</code>.</li></ul></li>\n</ul>\n<h3>Hand preference correction</h3>\n<p>Adjusted all sensor values to a right-handed orientation.</p>\n<pre><code>\ndf[] *= -\ndf[] *= -\ndf[] *= -\n\n\nthm_3 = df[].copy()\nthm_5 = df[].copy()\ndf[] = thm_3\ndf[] = thm_5\n\n\ntof_names = [  i  ()]\ntof = df[tof_names].values.copy()\ntof = tof.reshape(-, , , )\ntof = tof[:, [, , , , ]]\ntof = np.flip(tof, )\ntof = tof.reshape(-,  *  * )\ndf[tof_names] = tof.copy()\n</code></pre>\n<h3>Z-axis rotation</h3>\n<p>It was determined that <strong>SUBJ_019262</strong> and <strong>SUBJ_045235</strong> had their sensors rotated 180 degrees around the Z-axis. The following correction was applied to their data.</p>\n<pre><code>\ndf[] *= -\ndf[] *= -\n\n\nw = df[].copy()\nx = df[].copy()\ny = df[].copy()\nz = df[].copy()\ndf[] = -z\ndf[] = y\ndf[] = -x\ndf[] = w\n\n\nthm_2 = df[].copy()\nthm_3 = df[].copy()\nthm_4 = df[].copy()\nthm_5 = df[].copy()\ndf[] = thm_4\ndf[] = thm_2\ndf[] = thm_5\ndf[] = thm_3\n\n\ntof_names = [  i  ()]\ntof = df[tof_names].values.copy()\ntof = tof.reshape(-, , , )\ntof = np.rot90(tof, k=, axes=(, ))\ntof = tof[:, [, , , , ]]\ntof = tof.reshape(-,  *  * )\ndf[tof_names] = tof.copy()\n</code></pre>\n<hr>\n<h2>Data Augmentation</h2>\n<p>Due to the limited dataset size, data augmentation was crucial for model performance.</p>\n<h3>Time-series stretching</h3>\n<ul>\n<li><strong>Quaternions(rot)</strong>:<ul>\n<li><strong>Problem</strong>: Quaternions can represent the same orientation with inverted signs ($q$ and $-q$). This causes \"inversion points\" in sequences where interpolation-based stretching produces meaningless data.</li>\n<li><strong>Solution</strong>: We preprocessed sequences to ensure all pairwise inner products were positive, creating a smooth orientation path.</li>\n<li><strong>Method</strong>: Used <strong>Slerp</strong> or <strong>Nearest</strong> interpolation methods.</li></ul></li>\n<li><strong>Acceleration(acc)</strong>:<ul>\n<li><strong>Problem</strong>: Simple interpolation (e.g., stretching by 1.5x) incorrectly scales the integrated velocity and position derived from acceleration.</li>\n<li><strong>Solution</strong>: We implemented a correction to preserve the start and end coordinates after stretching.</li>\n<li><strong>Procedure</strong>:<ol>\n<li>Use the orientation (<code>rot</code>) data to separate each frame's acceleration into its <strong>gravitational</strong> and <strong>linear</strong> components.</li>\n<li>Apply stretching only to the linear acceleration component, then divide it by the square of the stretching factor.</li>\n<li>Recalculate the gravitational component using the stretched <code>rot</code> values.</li></ol></li></ul></li>\n</ul>\n<h3>Rotation</h3>\n<ul>\n<li>Rotating the data to simulate wristband misalignment was highly effective.</li>\n<li>We applied a random rotational bias to the entire sequence along the Y, X, and Z axes.</li>\n<li>The optimal standard deviations for the bias were: <code>std_Y=10</code>, <code>std_X=7</code>, and <code>std_Z=0.1</code>.</li>\n</ul>\n<h3>MIXUP</h3>\n<ul>\n<li><strong>MIXUP</strong> proved to be an extremely effective technique.</li>\n<li><strong>Implementation</strong>:<ul>\n<li>We applied MIXUP <em>after</em> feature engineering.</li>\n<li>To prevent overfitting and improve performance over longer training, we applied MIXUP in <strong>multiple stages</strong>: <code>z = Mixup(Mixup(Mixup(x)))</code>.</li>\n<li>To prevent excessive averaging, the original data <code>x</code> was concatenated with the mixed data <code>Mixup(x)</code> along the batch dimension.</li></ul></li>\n<li><strong>Parameters</strong>: The first stage used <code>beta=0.5</code>, while the second and third stages used <code>beta=16</code>.</li>\n<li>We also experimented with mixing three or more data samples by sampling from a Dirichlet distribution, but the multi-stage approach yielded better results.</li>\n</ul>\n<hr>\n<h2>Model Architecture</h2>\n<ul>\n<li><strong>Input</strong>: The model processes input sequences of <strong>128 frames</strong>.</li>\n<li><strong>Receptive Field</strong>: Long-term temporal dependencies were not required for this task. The model's receptive field spanned <strong>15 frames</strong> (~1.5 seconds), which was sufficient. This was achieved with 7 convolutional layers (kernel size 3) without pooling.</li>\n<li><strong>Attention</strong>: Features were weighted using <strong>additive attention</strong>, consolidated into a single representation, and then passed to a classifier.</li>\n<li><strong>Two-Stage Model</strong>:<ul>\n<li><strong>Stage 1</strong>: An initial model estimates 4 posture classes and feeds these probability values to the second stage.</li>\n<li><strong>Stage 2</strong>: A second model inputs both the raw sensor data and the posture probabilities to predict the final 18-class and 9-class targets.</li></ul></li>\n<li><strong>Ensemble</strong>: We created and used separate models: one for IMU-only data and one for all-sensor data.</li>\n</ul>\n<hr>\n<h2>Feature Engineering</h2>\n<ul>\n<li><strong>IMU (12 features)</strong>:<ul>\n<li>Acceleration, acceleration norm, linear acceleration, linear acceleration norm, angular velocity, angular velocity norm.</li></ul></li>\n<li><strong>THM (10 features)</strong>:<ul>\n<li>Temperature, temporal derivative of temperature.</li></ul></li>\n<li><strong>TOF (5 features)</strong>:<ul>\n<li>Sensor-wise averages.</li></ul></li>\n</ul>\n<hr>\n<h2>Reason for Significant Performance Improvement (\"Shake Up\")</h2>\n<ul>\n<li>We achieved a substantial improvement from <strong>24th place (Public LB) to 10th place (Private LB)</strong>.</li>\n<li><strong>Primary Reason</strong>: This improvement likely stems from the preprocessing step where we detected and corrected data from sensors that were flipped 180 degrees on the Z-axis.</li>\n<li><strong>Score Impact</strong>:<ul>\n<li>Public Leaderboard: <strong>0.874 (no change)</strong></li>\n<li>Private Leaderboard: <strong>0.857 → 0.867 (0.01 improvement)</strong></li></ul></li>\n<li>We did not perform any private probing.</li>\n<li>Given that the sensor-flip case occurred in 2 out of 82 subjects in the training data, the probability of it being present in at least one of the private test subjects was approximately 40%. Therefore, we consider this result to be fortunate.</li>\n</ul>",
  "messages": [
    {
      "id": 3282328,
      "postDate": "2025-09-06T05:56:20.840Z",
      "content": "<p>First, I would like to express my gratitude for organizing this exciting competition. For me, this marks my first gold medal in nearly two years. Moreover, since I previously achieved 10th place at the same Child Mind Institute competition through shakeup, these results feel particularly meaningful to me.</p>\n<hr>\n<h2>Training Strategy</h2>\n<ul>\n<li><strong>Epochs &amp; Optimizer</strong>: 200 epochs with <strong>AdamW</strong> optimizer and <strong>EMA</strong> (decay=0.999), no learning rate scheduling.</li>\n<li><strong>Validation Strategy</strong>: <strong>StratifiedGroupKFold</strong> with <code>groups=\"subject\"</code>, <code>k=5</code>. CV splits were generated using three different seed values: 0, 1, and 2.</li>\n<li><strong>Experiment Management</strong>:<ul>\n<li>For clearly underperforming experiments, only the initial seed (seed 0) was run. All other experiments were evaluated across all three seeds.</li>\n<li>The F1 metric showed significant instability; in some cases, performance was high in seed=0 folds but low in seeds 1 and 2.</li></ul></li>\n<li><strong>Model Selection</strong>:<ul>\n<li>Adopted the best epoch based on the average performance across all 15 experiments (5 folds x 3 seeds).</li>\n<li>Additionally, if the F1 metric was unchanged but the loss improved, that model was selected.</li></ul></li>\n</ul>\n<hr>\n<h2>Preprocessing</h2>\n<h3>Handling of NaN values</h3>\n<ul>\n<li><strong>Rotation (<code>rot</code>)</strong>: Replaced missing values with unit quaternions.</li>\n<li><strong>Thermal (<code>thm</code>)</strong>:<ul>\n<li>For each sequence, if any sensor value was below 18 degrees, we replaced that sensor's corresponding value with NaN. This was done to mask potentially corrupted readings.</li>\n<li>Calculated the row-wise mean of NaN values and used it for imputation.</li>\n<li>For rows containing only NaN values, we assigned a global mean of <code>27.03</code>.</li></ul></li>\n</ul>\n<h3>Hand preference correction</h3>\n<p>Adjusted all sensor values to a right-handed orientation.</p>\n<pre><code>\ndf[] *= -\ndf[] *= -\ndf[] *= -\n\n\nthm_3 = df[].copy()\nthm_5 = df[].copy()\ndf[] = thm_3\ndf[] = thm_5\n\n\ntof_names = [  i  ()]\ntof = df[tof_names].values.copy()\ntof = tof.reshape(-, , , )\ntof = tof[:, [, , , , ]]\ntof = np.flip(tof, )\ntof = tof.reshape(-,  *  * )\ndf[tof_names] = tof.copy()\n</code></pre>\n<h3>Z-axis rotation</h3>\n<p>It was determined that <strong>SUBJ_019262</strong> and <strong>SUBJ_045235</strong> had their sensors rotated 180 degrees around the Z-axis. The following correction was applied to their data.</p>\n<pre><code>\ndf[] *= -\ndf[] *= -\n\n\nw = df[].copy()\nx = df[].copy()\ny = df[].copy()\nz = df[].copy()\ndf[] = -z\ndf[] = y\ndf[] = -x\ndf[] = w\n\n\nthm_2 = df[].copy()\nthm_3 = df[].copy()\nthm_4 = df[].copy()\nthm_5 = df[].copy()\ndf[] = thm_4\ndf[] = thm_2\ndf[] = thm_5\ndf[] = thm_3\n\n\ntof_names = [  i  ()]\ntof = df[tof_names].values.copy()\ntof = tof.reshape(-, , , )\ntof = np.rot90(tof, k=, axes=(, ))\ntof = tof[:, [, , , , ]]\ntof = tof.reshape(-,  *  * )\ndf[tof_names] = tof.copy()\n</code></pre>\n<hr>\n<h2>Data Augmentation</h2>\n<p>Due to the limited dataset size, data augmentation was crucial for model performance.</p>\n<h3>Time-series stretching</h3>\n<ul>\n<li><strong>Quaternions(rot)</strong>:<ul>\n<li><strong>Problem</strong>: Quaternions can represent the same orientation with inverted signs ($q$ and $-q$). This causes \"inversion points\" in sequences where interpolation-based stretching produces meaningless data.</li>\n<li><strong>Solution</strong>: We preprocessed sequences to ensure all pairwise inner products were positive, creating a smooth orientation path.</li>\n<li><strong>Method</strong>: Used <strong>Slerp</strong> or <strong>Nearest</strong> interpolation methods.</li></ul></li>\n<li><strong>Acceleration(acc)</strong>:<ul>\n<li><strong>Problem</strong>: Simple interpolation (e.g., stretching by 1.5x) incorrectly scales the integrated velocity and position derived from acceleration.</li>\n<li><strong>Solution</strong>: We implemented a correction to preserve the start and end coordinates after stretching.</li>\n<li><strong>Procedure</strong>:<ol>\n<li>Use the orientation (<code>rot</code>) data to separate each frame's acceleration into its <strong>gravitational</strong> and <strong>linear</strong> components.</li>\n<li>Apply stretching only to the linear acceleration component, then divide it by the square of the stretching factor.</li>\n<li>Recalculate the gravitational component using the stretched <code>rot</code> values.</li></ol></li></ul></li>\n</ul>\n<h3>Rotation</h3>\n<ul>\n<li>Rotating the data to simulate wristband misalignment was highly effective.</li>\n<li>We applied a random rotational bias to the entire sequence along the Y, X, and Z axes.</li>\n<li>The optimal standard deviations for the bias were: <code>std_Y=10</code>, <code>std_X=7</code>, and <code>std_Z=0.1</code>.</li>\n</ul>\n<h3>MIXUP</h3>\n<ul>\n<li><strong>MIXUP</strong> proved to be an extremely effective technique.</li>\n<li><strong>Implementation</strong>:<ul>\n<li>We applied MIXUP <em>after</em> feature engineering.</li>\n<li>To prevent overfitting and improve performance over longer training, we applied MIXUP in <strong>multiple stages</strong>: <code>z = Mixup(Mixup(Mixup(x)))</code>.</li>\n<li>To prevent excessive averaging, the original data <code>x</code> was concatenated with the mixed data <code>Mixup(x)</code> along the batch dimension.</li></ul></li>\n<li><strong>Parameters</strong>: The first stage used <code>beta=0.5</code>, while the second and third stages used <code>beta=16</code>.</li>\n<li>We also experimented with mixing three or more data samples by sampling from a Dirichlet distribution, but the multi-stage approach yielded better results.</li>\n</ul>\n<hr>\n<h2>Model Architecture</h2>\n<ul>\n<li><strong>Input</strong>: The model processes input sequences of <strong>128 frames</strong>.</li>\n<li><strong>Receptive Field</strong>: Long-term temporal dependencies were not required for this task. The model's receptive field spanned <strong>15 frames</strong> (~1.5 seconds), which was sufficient. This was achieved with 7 convolutional layers (kernel size 3) without pooling.</li>\n<li><strong>Attention</strong>: Features were weighted using <strong>additive attention</strong>, consolidated into a single representation, and then passed to a classifier.</li>\n<li><strong>Two-Stage Model</strong>:<ul>\n<li><strong>Stage 1</strong>: An initial model estimates 4 posture classes and feeds these probability values to the second stage.</li>\n<li><strong>Stage 2</strong>: A second model inputs both the raw sensor data and the posture probabilities to predict the final 18-class and 9-class targets.</li></ul></li>\n<li><strong>Ensemble</strong>: We created and used separate models: one for IMU-only data and one for all-sensor data.</li>\n</ul>\n<hr>\n<h2>Feature Engineering</h2>\n<ul>\n<li><strong>IMU (12 features)</strong>:<ul>\n<li>Acceleration, acceleration norm, linear acceleration, linear acceleration norm, angular velocity, angular velocity norm.</li></ul></li>\n<li><strong>THM (10 features)</strong>:<ul>\n<li>Temperature, temporal derivative of temperature.</li></ul></li>\n<li><strong>TOF (5 features)</strong>:<ul>\n<li>Sensor-wise averages.</li></ul></li>\n</ul>\n<hr>\n<h2>Reason for Significant Performance Improvement (\"Shake Up\")</h2>\n<ul>\n<li>We achieved a substantial improvement from <strong>24th place (Public LB) to 10th place (Private LB)</strong>.</li>\n<li><strong>Primary Reason</strong>: This improvement likely stems from the preprocessing step where we detected and corrected data from sensors that were flipped 180 degrees on the Z-axis.</li>\n<li><strong>Score Impact</strong>:<ul>\n<li>Public Leaderboard: <strong>0.874 (no change)</strong></li>\n<li>Private Leaderboard: <strong>0.857 → 0.867 (0.01 improvement)</strong></li></ul></li>\n<li>We did not perform any private probing.</li>\n<li>Given that the sensor-flip case occurred in 2 out of 82 subjects in the training data, the probability of it being present in at least one of the private test subjects was approximately 40%. Therefore, we consider this result to be fortunate.</li>\n</ul>",
      "rawMarkdown": "First, I would like to express my gratitude for organizing this exciting competition. For me, this marks my first gold medal in nearly two years. Moreover, since I previously achieved 10th place at the same Child Mind Institute competition through shakeup, these results feel particularly meaningful to me.\n\n-----\n\n## Training Strategy\n\n  * **Epochs & Optimizer**: 200 epochs with **AdamW** optimizer and **EMA** (decay=0.999), no learning rate scheduling.\n  * **Validation Strategy**: **StratifiedGroupKFold** with `groups=\"subject\"`, `k=5`. CV splits were generated using three different seed values: 0, 1, and 2.\n  * **Experiment Management**:\n      * For clearly underperforming experiments, only the initial seed (seed 0) was run. All other experiments were evaluated across all three seeds.\n      * The F1 metric showed significant instability; in some cases, performance was high in seed=0 folds but low in seeds 1 and 2.\n  * **Model Selection**:\n      * Adopted the best epoch based on the average performance across all 15 experiments (5 folds x 3 seeds).\n      * Additionally, if the F1 metric was unchanged but the loss improved, that model was selected.\n\n-----\n\n## Preprocessing\n\n### Handling of NaN values\n\n  * **Rotation (`rot`)**: Replaced missing values with unit quaternions.\n  * **Thermal (`thm`)**:\n      * For each sequence, if any sensor value was below 18 degrees, we replaced that sensor's corresponding value with NaN. This was done to mask potentially corrupted readings.\n      * Calculated the row-wise mean of NaN values and used it for imputation.\n      * For rows containing only NaN values, we assigned a global mean of `27.03`.\n\n### Hand preference correction\n\nAdjusted all sensor values to a right-handed orientation.\n\n```python\n# Imu\ndf['acc_x'] *= -1.0\ndf['rot_y'] *= -1.0\ndf['rot_z'] *= -1.0\n\n# Thm\nthm_3 = df['thm_3'].copy()\nthm_5 = df['thm_5'].copy()\ndf['thm_5'] = thm_3\ndf['thm_3'] = thm_5\n\n# Tof\ntof_names = [f'tof_{i}' for i in range(200)]\ntof = df[tof_names].values.copy()\ntof = tof.reshape(-1, 5, 8, 8)\ntof = tof[:, [0, 1, 4, 3, 2]]\ntof = np.flip(tof, 3)\ntof = tof.reshape(-1, 5 * 8 * 8)\ndf[tof_names] = tof.copy()\n```\n\n### Z-axis rotation\n\nIt was determined that **SUBJ\\_019262** and **SUBJ\\_045235** had their sensors rotated 180 degrees around the Z-axis. The following correction was applied to their data.\n\n```python\n# acc\ndf['acc_x'] *= -1.0\ndf['acc_y'] *= -1.0\n\n# rot\nw = df['rot_w'].copy()\nx = df['rot_x'].copy()\ny = df['rot_y'].copy()\nz = df['rot_z'].copy()\ndf['rot_w'] = -z\ndf['rot_x'] = y\ndf['rot_y'] = -x\ndf['rot_z'] = w\n\n# thm\nthm_2 = df['thm_2'].copy()\nthm_3 = df['thm_3'].copy()\nthm_4 = df['thm_4'].copy()\nthm_5 = df['thm_5'].copy()\ndf['thm_2'] = thm_4\ndf['thm_4'] = thm_2\ndf['thm_3'] = thm_5\ndf['thm_5'] = thm_3\n\n# tof\ntof_names = [f'tof_{i}' for i in range(200)]\ntof = df[tof_names].values.copy()\ntof = tof.reshape(-1, 5, 8, 8)\ntof = np.rot90(tof, k=2, axes=(2, 3))\ntof = tof[:, [0, 3, 4, 1, 2]]\ntof = tof.reshape(-1, 5 * 8 * 8)\ndf[tof_names] = tof.copy()\n```\n\n-----\n\n## Data Augmentation\n\nDue to the limited dataset size, data augmentation was crucial for model performance.\n\n### Time-series stretching\n\n  * **Quaternions(rot)**:\n      * **Problem**: Quaternions can represent the same orientation with inverted signs ($q$ and $-q$). This causes \"inversion points\" in sequences where interpolation-based stretching produces meaningless data.\n      * **Solution**: We preprocessed sequences to ensure all pairwise inner products were positive, creating a smooth orientation path.\n      * **Method**: Used **Slerp** or **Nearest** interpolation methods.\n  * **Acceleration(acc)**:\n      * **Problem**: Simple interpolation (e.g., stretching by 1.5x) incorrectly scales the integrated velocity and position derived from acceleration.\n      * **Solution**: We implemented a correction to preserve the start and end coordinates after stretching.\n      * **Procedure**:\n        1.  Use the orientation (`rot`) data to separate each frame's acceleration into its **gravitational** and **linear** components.\n        2.  Apply stretching only to the linear acceleration component, then divide it by the square of the stretching factor.\n        3.  Recalculate the gravitational component using the stretched `rot` values.\n\n### Rotation\n\n  * Rotating the data to simulate wristband misalignment was highly effective.\n  * We applied a random rotational bias to the entire sequence along the Y, X, and Z axes.\n  * The optimal standard deviations for the bias were: `std_Y=10`, `std_X=7`, and `std_Z=0.1`.\n\n### MIXUP\n\n  * **MIXUP** proved to be an extremely effective technique.\n  * **Implementation**:\n      * We applied MIXUP *after* feature engineering.\n      * To prevent overfitting and improve performance over longer training, we applied MIXUP in **multiple stages**: `z = Mixup(Mixup(Mixup(x)))`.\n      * To prevent excessive averaging, the original data `x` was concatenated with the mixed data `Mixup(x)` along the batch dimension.\n  * **Parameters**: The first stage used `beta=0.5`, while the second and third stages used `beta=16`.\n  * We also experimented with mixing three or more data samples by sampling from a Dirichlet distribution, but the multi-stage approach yielded better results.\n\n-----\n\n## Model Architecture\n\n  * **Input**: The model processes input sequences of **128 frames**.\n  * **Receptive Field**: Long-term temporal dependencies were not required for this task. The model's receptive field spanned **15 frames** (\\~1.5 seconds), which was sufficient. This was achieved with 7 convolutional layers (kernel size 3) without pooling.\n  * **Attention**: Features were weighted using **additive attention**, consolidated into a single representation, and then passed to a classifier.\n  * **Two-Stage Model**:\n      * **Stage 1**: An initial model estimates 4 posture classes and feeds these probability values to the second stage.\n      * **Stage 2**: A second model inputs both the raw sensor data and the posture probabilities to predict the final 18-class and 9-class targets.\n  * **Ensemble**: We created and used separate models: one for IMU-only data and one for all-sensor data.\n\n-----\n\n## Feature Engineering\n\n  * **IMU (12 features)**:\n      * Acceleration, acceleration norm, linear acceleration, linear acceleration norm, angular velocity, angular velocity norm.\n  * **THM (10 features)**:\n      * Temperature, temporal derivative of temperature.\n  * **TOF (5 features)**:\n      * Sensor-wise averages.\n\n-----\n\n## Reason for Significant Performance Improvement (\"Shake Up\")\n\n  * We achieved a substantial improvement from **24th place (Public LB) to 10th place (Private LB)**.\n  * **Primary Reason**: This improvement likely stems from the preprocessing step where we detected and corrected data from sensors that were flipped 180 degrees on the Z-axis.\n  * **Score Impact**:\n      * Public Leaderboard: **0.874 (no change)**\n      * Private Leaderboard: **0.857 → 0.867 (0.01 improvement)**\n  * We did not perform any private probing.\n  * Given that the sensor-flip case occurred in 2 out of 82 subjects in the training data, the probability of it being present in at least one of the private test subjects was approximately 40%. Therefore, we consider this result to be fortunate.\n",
      "votes": 28
    }
  ],
  "comments": [],
  "raw_markdown_by_id": {
    "3282328": "First, I would like to express my gratitude for organizing this exciting competition. For me, this marks my first gold medal in nearly two years. Moreover, since I previously achieved 10th place at the same Child Mind Institute competition through shakeup, these results feel particularly meaningful to me.\n\n-----\n\n## Training Strategy\n\n  * **Epochs & Optimizer**: 200 epochs with **AdamW** optimizer and **EMA** (decay=0.999), no learning rate scheduling.\n  * **Validation Strategy**: **StratifiedGroupKFold** with `groups=\"subject\"`, `k=5`. CV splits were generated using three different seed values: 0, 1, and 2.\n  * **Experiment Management**:\n      * For clearly underperforming experiments, only the initial seed (seed 0) was run. All other experiments were evaluated across all three seeds.\n      * The F1 metric showed significant instability; in some cases, performance was high in seed=0 folds but low in seeds 1 and 2.\n  * **Model Selection**:\n      * Adopted the best epoch based on the average performance across all 15 experiments (5 folds x 3 seeds).\n      * Additionally, if the F1 metric was unchanged but the loss improved, that model was selected.\n\n-----\n\n## Preprocessing\n\n### Handling of NaN values\n\n  * **Rotation (`rot`)**: Replaced missing values with unit quaternions.\n  * **Thermal (`thm`)**:\n      * For each sequence, if any sensor value was below 18 degrees, we replaced that sensor's corresponding value with NaN. This was done to mask potentially corrupted readings.\n      * Calculated the row-wise mean of NaN values and used it for imputation.\n      * For rows containing only NaN values, we assigned a global mean of `27.03`.\n\n### Hand preference correction\n\nAdjusted all sensor values to a right-handed orientation.\n\n```python\n# Imu\ndf['acc_x'] *= -1.0\ndf['rot_y'] *= -1.0\ndf['rot_z'] *= -1.0\n\n# Thm\nthm_3 = df['thm_3'].copy()\nthm_5 = df['thm_5'].copy()\ndf['thm_5'] = thm_3\ndf['thm_3'] = thm_5\n\n# Tof\ntof_names = [f'tof_{i}' for i in range(200)]\ntof = df[tof_names].values.copy()\ntof = tof.reshape(-1, 5, 8, 8)\ntof = tof[:, [0, 1, 4, 3, 2]]\ntof = np.flip(tof, 3)\ntof = tof.reshape(-1, 5 * 8 * 8)\ndf[tof_names] = tof.copy()\n```\n\n### Z-axis rotation\n\nIt was determined that **SUBJ\\_019262** and **SUBJ\\_045235** had their sensors rotated 180 degrees around the Z-axis. The following correction was applied to their data.\n\n```python\n# acc\ndf['acc_x'] *= -1.0\ndf['acc_y'] *= -1.0\n\n# rot\nw = df['rot_w'].copy()\nx = df['rot_x'].copy()\ny = df['rot_y'].copy()\nz = df['rot_z'].copy()\ndf['rot_w'] = -z\ndf['rot_x'] = y\ndf['rot_y'] = -x\ndf['rot_z'] = w\n\n# thm\nthm_2 = df['thm_2'].copy()\nthm_3 = df['thm_3'].copy()\nthm_4 = df['thm_4'].copy()\nthm_5 = df['thm_5'].copy()\ndf['thm_2'] = thm_4\ndf['thm_4'] = thm_2\ndf['thm_3'] = thm_5\ndf['thm_5'] = thm_3\n\n# tof\ntof_names = [f'tof_{i}' for i in range(200)]\ntof = df[tof_names].values.copy()\ntof = tof.reshape(-1, 5, 8, 8)\ntof = np.rot90(tof, k=2, axes=(2, 3))\ntof = tof[:, [0, 3, 4, 1, 2]]\ntof = tof.reshape(-1, 5 * 8 * 8)\ndf[tof_names] = tof.copy()\n```\n\n-----\n\n## Data Augmentation\n\nDue to the limited dataset size, data augmentation was crucial for model performance.\n\n### Time-series stretching\n\n  * **Quaternions(rot)**:\n      * **Problem**: Quaternions can represent the same orientation with inverted signs ($q$ and $-q$). This causes \"inversion points\" in sequences where interpolation-based stretching produces meaningless data.\n      * **Solution**: We preprocessed sequences to ensure all pairwise inner products were positive, creating a smooth orientation path.\n      * **Method**: Used **Slerp** or **Nearest** interpolation methods.\n  * **Acceleration(acc)**:\n      * **Problem**: Simple interpolation (e.g., stretching by 1.5x) incorrectly scales the integrated velocity and position derived from acceleration.\n      * **Solution**: We implemented a correction to preserve the start and end coordinates after stretching.\n      * **Procedure**:\n        1.  Use the orientation (`rot`) data to separate each frame's acceleration into its **gravitational** and **linear** components.\n        2.  Apply stretching only to the linear acceleration component, then divide it by the square of the stretching factor.\n        3.  Recalculate the gravitational component using the stretched `rot` values.\n\n### Rotation\n\n  * Rotating the data to simulate wristband misalignment was highly effective.\n  * We applied a random rotational bias to the entire sequence along the Y, X, and Z axes.\n  * The optimal standard deviations for the bias were: `std_Y=10`, `std_X=7`, and `std_Z=0.1`.\n\n### MIXUP\n\n  * **MIXUP** proved to be an extremely effective technique.\n  * **Implementation**:\n      * We applied MIXUP *after* feature engineering.\n      * To prevent overfitting and improve performance over longer training, we applied MIXUP in **multiple stages**: `z = Mixup(Mixup(Mixup(x)))`.\n      * To prevent excessive averaging, the original data `x` was concatenated with the mixed data `Mixup(x)` along the batch dimension.\n  * **Parameters**: The first stage used `beta=0.5`, while the second and third stages used `beta=16`.\n  * We also experimented with mixing three or more data samples by sampling from a Dirichlet distribution, but the multi-stage approach yielded better results.\n\n-----\n\n## Model Architecture\n\n  * **Input**: The model processes input sequences of **128 frames**.\n  * **Receptive Field**: Long-term temporal dependencies were not required for this task. The model's receptive field spanned **15 frames** (\\~1.5 seconds), which was sufficient. This was achieved with 7 convolutional layers (kernel size 3) without pooling.\n  * **Attention**: Features were weighted using **additive attention**, consolidated into a single representation, and then passed to a classifier.\n  * **Two-Stage Model**:\n      * **Stage 1**: An initial model estimates 4 posture classes and feeds these probability values to the second stage.\n      * **Stage 2**: A second model inputs both the raw sensor data and the posture probabilities to predict the final 18-class and 9-class targets.\n  * **Ensemble**: We created and used separate models: one for IMU-only data and one for all-sensor data.\n\n-----\n\n## Feature Engineering\n\n  * **IMU (12 features)**:\n      * Acceleration, acceleration norm, linear acceleration, linear acceleration norm, angular velocity, angular velocity norm.\n  * **THM (10 features)**:\n      * Temperature, temporal derivative of temperature.\n  * **TOF (5 features)**:\n      * Sensor-wise averages.\n\n-----\n\n## Reason for Significant Performance Improvement (\"Shake Up\")\n\n  * We achieved a substantial improvement from **24th place (Public LB) to 10th place (Private LB)**.\n  * **Primary Reason**: This improvement likely stems from the preprocessing step where we detected and corrected data from sensors that were flipped 180 degrees on the Z-axis.\n  * **Score Impact**:\n      * Public Leaderboard: **0.874 (no change)**\n      * Private Leaderboard: **0.857 → 0.867 (0.01 improvement)**\n  * We did not perform any private probing.\n  * Given that the sensor-flip case occurred in 2 out of 82 subjects in the training data, the probability of it being present in at least one of the private test subjects was approximately 40%. Therefore, we consider this result to be fortunate.\n"
  }
}