{
  "id": 459637,
  "title": "4th Place Solution - Nikhil's Part (Modified Unet + Transformer and Weighted Box Fusion)",
  "url": "/competitions/child-mind-institute-detect-sleep-states/writeups/penguin46-nikhilmishra-4th-place-solution-nikhil-s",
  "author_name": "",
  "post_date": "2023-12-16T05:40:19.993Z",
  "votes": 68,
  "comment_count": 6,
  "views": 0,
  "content": "<p>Firstly thank you to my teammate <a href=\"https://www.kaggle.com/ryotayoshinobu\" target=\"_blank\">@ryotayoshinobu</a> for the amazing performance. Please read his part of the <a href=\"https://www.kaggle.com/competitions/child-mind-institute-detect-sleep-states/discussion/459597\" target=\"_blank\">solution</a>.<br>\nI am really glad I teamed up with him and learnt a lot</p>\n<p>A big thanks to the organizers as well for such an interesting competition. A really tough one.</p>\n<p>You can find the solution code here:</p>\n<p><a href=\"https://github.com/nikhilmishradevelop/kaggle-child-mind-institute-detect-sleep-states\" target=\"_blank\">https://github.com/nikhilmishradevelop/kaggle-child-mind-institute-detect-sleep-states</a></p>\n<h1>Solution Summary</h1>\n<p><strong>Validation Strategy</strong>: GroupKFold on Series Ids</p>\n<p><strong>Model Inputs</strong>:  17280 x n_features length sequences as input (17280 = 12 steps_per_minute x 60_minutes * 24 hours)<br>\n<strong>Model Outputs</strong>: 17280 x 2 (one for onset and other for wakeup)<br>\n<strong>Model Type</strong>: Regression Model<br>\n<strong>Loss Type</strong>: Cubic Loss i.e. abs(y_true-y_pred)**3</p>\n<p>The last remaining part of the sequences with length &lt; 17280 were padded to make them equal to 17280</p>\n<p>My solution is a modified UNET (averaged over 4 models, 2 LSTMs and 2 GRUs) using normalized Gaussian targets similar to what <a href=\"https://www.kaggle.com/tolgadincer\" target=\"_blank\">@tolgadincer</a> described. A big thanks to him for sharing a good method, early on in this competition.</p>\n<h2>Features:</h2>\n<ol>\n<li>Original Sequence Features: Enmo, Anglez</li>\n<li>TimeStamp Features: Hour and Weekday</li>\n<li>Derived Sequence Features: Anglez difference, Enmo Difference, HDCZA features etc)</li>\n</ol>\n<p>Good Features help in faster convergence and much better scores, so adding good features were important (Read penguin's solution for some other good features)</p>\n<p><strong>My final 4 model ensemble results</strong>: </p>\n<p><strong>CV</strong>: 0.828<br>\n<strong>Public LB</strong>: 0.789<br>\n<strong>Private LB</strong>: 0.841</p>\n<h1><strong>Model Architecture</strong></h1>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1265131%2Fad78d36028ed6dc7ab1d3e79631b0172%2FFireShot%20Capture%20019%20-%20Online%20FlowChart%20%20Diagrams%20Editor%20-%20Mermaid%20Live%20Editor%20-%20mermaid.live%20(1).png?generation=1701841137052567&amp;alt=media\" alt=\"Model Architecture\"></p>\n<h2>Patching to reduce sequence length</h2>\n<p>Since 17280 is a very long sequence length (makes training very slow and harder to train), we reduce the sequence length by patching it.</p>\n<p><strong>Input:</strong>  17280 * n_features (34)<br>\n<strong>Patch Size:</strong> (Used different patch sizes 3, 4, 5, or 6 in different models).</p>\n<p><strong>Modified Sequence Length:</strong> 17280 to 17280//Patch_Size sequence length</p>\n<p><strong>Modified Feature Size:</strong> k * 4 * num_features (where k is a Dense layer output dim)</p>\n<h2>Modified Unet Part</h2>\n<p><strong><em>UNET Encoder -&gt; Bottleneck -&gt; Transformer -&gt; GRU or LSTM -&gt; Unet Decoder</em></strong></p>\n<p>Each layer of UNET encoder had concatenated connections to the 1st layer</p>\n<p><strong>Initial Output size:</strong> 17280//Patch_Size, 2 * Patch_size</p>\n<p><strong>Reshaped Output Size:</strong> (17280, 2)</p>\n<h1>Post Processing</h1>\n<p>Initially I was doing just doing simple peak detection when I was competing solo.</p>\n<p>I wanted to, but could not formulate a good way to rerank model predictions using lightgbm, but <a href=\"https://www.kaggle.com/kmat2019\" target=\"_blank\">@kmat2019</a> is a great read for that.</p>\n<p>Thanks to <a href=\"https://www.kaggle.com/ryotayoshinobu\" target=\"_blank\">@ryotayoshinobu</a> , I started applying NMS, and finally experimented with a kind of  WBF algorithm and made it work. This WBF algorithm gave a score of 0.79 to 0.793 on last day.</p>\n<p>But it was worse by 0.001 in our private LB lol, so WBF hurt.</p>\n<p>WBF Working</p>\n<ol>\n<li><p><strong>Initialization and Convolution</strong>: The function starts by applying convolution with the specified <code>convolution_kernel</code> to smooth the data.</p></li>\n<li><p><strong>Peak Detection Loop</strong>: It iteratively searches for peaks in the data. The loop runs until the maximum count (<code>max_count</code>) is reached or the peak value falls below a threshold (<code>max_thresh</code>).</p></li>\n<li><p><strong>Adaptive Window and Weight Calculation</strong>:</p>\n<ul>\n<li>A dynamic window size around each peak is determined based on the current maximum value's power (<code>curr_max_power</code>) and a distance parameter (<code>k_dist</code>).</li>\n<li>Depending on the <code>section_weight_method</code> (logarithmic or linear), weights are calculated for each section of the data around the peak.</li></ul></li>\n<li><p><strong>Weighted Average and Score Calculation</strong>: For each detected peak, a weighted average is calculated to determine the score of the peak. <br>\nThis is done by considering the peak and k of its neighbours. This score is influenced by the method of weight calculation and other hyperparameters like <code>log_base</code>, <code>log_scale</code>, and <code>weight_coeff</code>.</p></li>\n<li><p><strong>Suppression and Updating Predictions</strong>:</p>\n<ul>\n<li>After each peak is detected, the function suppresses the neighboring values to avoid detecting the same peak multiple times. This is controlled by <code>overlap_coeff</code> and <code>preds_reduction_power</code>.</li>\n<li>The indices and scores of detected peaks are stored and returned after the loop completes.</li></ul></li>\n</ol>\n<p>I will share the implementation of the WBF part code, you can see if tuning these hyperparameters help in your models too. This accepts predictions per series id.</p>\n<p>(I found hyperparameters by a mix of manual and automated hyperparamter tuning approach)</p>\n<pre><code> ():\n    k_dist = hyperparams[]\n    log_base = hyperparams[]\n    log_scale = hyperparams[]\n    curr_max_power = hyperparams[]\n    weight_coeff = hyperparams[]\n    convolution_kernel = hyperparams[]\n    section_weight_method = hyperparams[]\n    preds_reduction_power = hyperparams[]\n    overlap_coeff = hyperparams[]\n    min_distance = hyperparams[]\n\n    preds = preds_orig.copy()\n    preds = np.convolve(preds, convolution_kernel, mode=)\n\n    count = \n    indices = []\n    scores = []\n\n     count &lt; max_count:\n        curr_max_idx = np.argmax(preds)\n        curr_max = preds[curr_max_idx]\n\n         curr_max &lt; max_thresh:\n            \n\n        k = (k_dist - (min_distance, (curr_max**curr_max_power)))\n\n        start_idx = (curr_max_idx - k, )\n        end_idx = (curr_max_idx + k + , (preds))\n\n        section = preds[start_idx:end_idx]\n\n        \n        distances = np.(np.arange((section)) - k)\n         section_weight_method == :\n            weights =  / (log_base ** (distances / (k * log_scale)))\n         section_weight_method == :\n            weights =  - (distances / k) * weight_coeff\n        \n\n        weighted_avg = np.(section * weights) / np.(weights)\n\n        scores.append(weighted_avg)\n        indices.append(curr_max_idx)\n\n        preds[start_idx:end_idx] *= (( - weights * overlap_coeff))**preds_reduction_power\n\n        count += \n\n     indices, scores\n</code></pre>\n<h2>Final Ensemble</h2>\n<p>Final ensemble was a blended weight of my and Penguin's subs regression   postprocessed using WBF</p>\n<p>Final_Sub = WBF(Penguins_Predictions * 0.25 + Nikhil's Predictions*0.75)</p>\n<p>Since our predictions had slightly different scales of regression targets, Penguin's Predictions were first scaled by using a simple power transform i.e Penguin's_Predictions ** 0.7</p>\n<p><strong>Ensemble CV:</strong> 0.835<br>\n<strong>Ensemble Public LB:</strong> 0.793<br>\n<strong>Ensemble Private LB:</strong> 0.845</p>\n<p>P.S: This competition is the first time I did not use gradient boosting, even after I knew it worked so well, me being a tabular guy. I mistake I guess. I love lightgbm and will definitely use one in the next competition.</p>\n<p>Congratulations to all the the top performers, it was a good fight, till it lasted :)</p>",
  "messages": [
    {
      "id": "2550489",
      "postDate": "12/06/2023 04:20:15",
      "content": "<p>Firstly thank you to my teammate <a href=\"https://www.kaggle.com/ryotayoshinobu\" target=\"_blank\">@ryotayoshinobu</a> for the amazing performance. Please read his part of the <a href=\"https://www.kaggle.com/competitions/child-mind-institute-detect-sleep-states/discussion/459597\" target=\"_blank\">solution</a>.<br>\nI am really glad I teamed up with him and learnt a lot</p>\n<p>A big thanks to the organizers as well for such an interesting competition. A really tough one.</p>\n<p>You can find the solution code here:</p>\n<p><a href=\"https://github.com/nikhilmishradevelop/kaggle-child-mind-institute-detect-sleep-states\" target=\"_blank\">https://github.com/nikhilmishradevelop/kaggle-child-mind-institute-detect-sleep-states</a></p>\n<h1>Solution Summary</h1>\n<p><strong>Validation Strategy</strong>: GroupKFold on Series Ids</p>\n<p><strong>Model Inputs</strong>:  17280 x n_features length sequences as input (17280 = 12 steps_per_minute x 60_minutes * 24 hours)<br>\n<strong>Model Outputs</strong>: 17280 x 2 (one for onset and other for wakeup)<br>\n<strong>Model Type</strong>: Regression Model<br>\n<strong>Loss Type</strong>: Cubic Loss i.e. abs(y_true-y_pred)**3</p>\n<p>The last remaining part of the sequences with length &lt; 17280 were padded to make them equal to 17280</p>\n<p>My solution is a modified UNET (averaged over 4 models, 2 LSTMs and 2 GRUs) using normalized Gaussian targets similar to what <a href=\"https://www.kaggle.com/tolgadincer\" target=\"_blank\">@tolgadincer</a> described. A big thanks to him for sharing a good method, early on in this competition.</p>\n<h2>Features:</h2>\n<ol>\n<li>Original Sequence Features: Enmo, Anglez</li>\n<li>TimeStamp Features: Hour and Weekday</li>\n<li>Derived Sequence Features: Anglez difference, Enmo Difference, HDCZA features etc)</li>\n</ol>\n<p>Good Features help in faster convergence and much better scores, so adding good features were important (Read penguin's solution for some other good features)</p>\n<p><strong>My final 4 model ensemble results</strong>: </p>\n<p><strong>CV</strong>: 0.828<br>\n<strong>Public LB</strong>: 0.789<br>\n<strong>Private LB</strong>: 0.841</p>\n<h1><strong>Model Architecture</strong></h1>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1265131%2Fad78d36028ed6dc7ab1d3e79631b0172%2FFireShot%20Capture%20019%20-%20Online%20FlowChart%20%20Diagrams%20Editor%20-%20Mermaid%20Live%20Editor%20-%20mermaid.live%20(1).png?generation=1701841137052567&amp;alt=media\" alt=\"Model Architecture\"></p>\n<h2>Patching to reduce sequence length</h2>\n<p>Since 17280 is a very long sequence length (makes training very slow and harder to train), we reduce the sequence length by patching it.</p>\n<p><strong>Input:</strong>  17280 * n_features (34)<br>\n<strong>Patch Size:</strong> (Used different patch sizes 3, 4, 5, or 6 in different models).</p>\n<p><strong>Modified Sequence Length:</strong> 17280 to 17280//Patch_Size sequence length</p>\n<p><strong>Modified Feature Size:</strong> k * 4 * num_features (where k is a Dense layer output dim)</p>\n<h2>Modified Unet Part</h2>\n<p><strong><em>UNET Encoder -&gt; Bottleneck -&gt; Transformer -&gt; GRU or LSTM -&gt; Unet Decoder</em></strong></p>\n<p>Each layer of UNET encoder had concatenated connections to the 1st layer</p>\n<p><strong>Initial Output size:</strong> 17280//Patch_Size, 2 * Patch_size</p>\n<p><strong>Reshaped Output Size:</strong> (17280, 2)</p>\n<h1>Post Processing</h1>\n<p>Initially I was doing just doing simple peak detection when I was competing solo.</p>\n<p>I wanted to, but could not formulate a good way to rerank model predictions using lightgbm, but <a href=\"https://www.kaggle.com/kmat2019\" target=\"_blank\">@kmat2019</a> is a great read for that.</p>\n<p>Thanks to <a href=\"https://www.kaggle.com/ryotayoshinobu\" target=\"_blank\">@ryotayoshinobu</a> , I started applying NMS, and finally experimented with a kind of  WBF algorithm and made it work. This WBF algorithm gave a score of 0.79 to 0.793 on last day.</p>\n<p>But it was worse by 0.001 in our private LB lol, so WBF hurt.</p>\n<p>WBF Working</p>\n<ol>\n<li><p><strong>Initialization and Convolution</strong>: The function starts by applying convolution with the specified <code>convolution_kernel</code> to smooth the data.</p></li>\n<li><p><strong>Peak Detection Loop</strong>: It iteratively searches for peaks in the data. The loop runs until the maximum count (<code>max_count</code>) is reached or the peak value falls below a threshold (<code>max_thresh</code>).</p></li>\n<li><p><strong>Adaptive Window and Weight Calculation</strong>:</p>\n<ul>\n<li>A dynamic window size around each peak is determined based on the current maximum value's power (<code>curr_max_power</code>) and a distance parameter (<code>k_dist</code>).</li>\n<li>Depending on the <code>section_weight_method</code> (logarithmic or linear), weights are calculated for each section of the data around the peak.</li></ul></li>\n<li><p><strong>Weighted Average and Score Calculation</strong>: For each detected peak, a weighted average is calculated to determine the score of the peak. <br>\nThis is done by considering the peak and k of its neighbours. This score is influenced by the method of weight calculation and other hyperparameters like <code>log_base</code>, <code>log_scale</code>, and <code>weight_coeff</code>.</p></li>\n<li><p><strong>Suppression and Updating Predictions</strong>:</p>\n<ul>\n<li>After each peak is detected, the function suppresses the neighboring values to avoid detecting the same peak multiple times. This is controlled by <code>overlap_coeff</code> and <code>preds_reduction_power</code>.</li>\n<li>The indices and scores of detected peaks are stored and returned after the loop completes.</li></ul></li>\n</ol>\n<p>I will share the implementation of the WBF part code, you can see if tuning these hyperparameters help in your models too. This accepts predictions per series id.</p>\n<p>(I found hyperparameters by a mix of manual and automated hyperparamter tuning approach)</p>\n<pre><code> ():\n    k_dist = hyperparams[]\n    log_base = hyperparams[]\n    log_scale = hyperparams[]\n    curr_max_power = hyperparams[]\n    weight_coeff = hyperparams[]\n    convolution_kernel = hyperparams[]\n    section_weight_method = hyperparams[]\n    preds_reduction_power = hyperparams[]\n    overlap_coeff = hyperparams[]\n    min_distance = hyperparams[]\n\n    preds = preds_orig.copy()\n    preds = np.convolve(preds, convolution_kernel, mode=)\n\n    count = \n    indices = []\n    scores = []\n\n     count &lt; max_count:\n        curr_max_idx = np.argmax(preds)\n        curr_max = preds[curr_max_idx]\n\n         curr_max &lt; max_thresh:\n            \n\n        k = (k_dist - (min_distance, (curr_max**curr_max_power)))\n\n        start_idx = (curr_max_idx - k, )\n        end_idx = (curr_max_idx + k + , (preds))\n\n        section = preds[start_idx:end_idx]\n\n        \n        distances = np.(np.arange((section)) - k)\n         section_weight_method == :\n            weights =  / (log_base ** (distances / (k * log_scale)))\n         section_weight_method == :\n            weights =  - (distances / k) * weight_coeff\n        \n\n        weighted_avg = np.(section * weights) / np.(weights)\n\n        scores.append(weighted_avg)\n        indices.append(curr_max_idx)\n\n        preds[start_idx:end_idx] *= (( - weights * overlap_coeff))**preds_reduction_power\n\n        count += \n\n     indices, scores\n</code></pre>\n<h2>Final Ensemble</h2>\n<p>Final ensemble was a blended weight of my and Penguin's subs regression   postprocessed using WBF</p>\n<p>Final_Sub = WBF(Penguins_Predictions * 0.25 + Nikhil's Predictions*0.75)</p>\n<p>Since our predictions had slightly different scales of regression targets, Penguin's Predictions were first scaled by using a simple power transform i.e Penguin's_Predictions ** 0.7</p>\n<p><strong>Ensemble CV:</strong> 0.835<br>\n<strong>Ensemble Public LB:</strong> 0.793<br>\n<strong>Ensemble Private LB:</strong> 0.845</p>\n<p>P.S: This competition is the first time I did not use gradient boosting, even after I knew it worked so well, me being a tabular guy. I mistake I guess. I love lightgbm and will definitely use one in the next competition.</p>\n<p>Congratulations to all the the top performers, it was a good fight, till it lasted :)</p>",
      "rawMarkdown": "Firstly thank you to my teammate @ryotayoshinobu for the amazing performance. Please read his part of the [solution](https://www.kaggle.com/competitions/child-mind-institute-detect-sleep-states/discussion/459597).\nI am really glad I teamed up with him and learnt a lot\n\nA big thanks to the organizers as well for such an interesting competition. A really tough one.\n\nYou can find the solution code here:\n\nhttps://github.com/nikhilmishradevelop/kaggle-child-mind-institute-detect-sleep-states\n\n# Solution Summary\n\n**Validation Strategy**: GroupKFold on Series Ids\n\n**Model Inputs**:  17280 x n_features length sequences as input (17280 = 12 steps_per_minute x 60_minutes * 24 hours)\n**Model Outputs**: 17280 x 2 (one for onset and other for wakeup)\n**Model Type**: Regression Model\n**Loss Type**: Cubic Loss i.e. abs(y_true-y_pred)**3\n\nThe last remaining part of the sequences with length < 17280 were padded to make them equal to 17280\n\nMy solution is a modified UNET (averaged over 4 models, 2 LSTMs and 2 GRUs) using normalized Gaussian targets similar to what @tolgadincer described. A big thanks to him for sharing a good method, early on in this competition.\n\n## Features:\n\n1. Original Sequence Features: Enmo, Anglez\n2. TimeStamp Features: Hour and Weekday\n3. Derived Sequence Features: Anglez difference, Enmo Difference, HDCZA features etc)\n\nGood Features help in faster convergence and much better scores, so adding good features were important (Read penguin's solution for some other good features)\n\n**My final 4 model ensemble results**: \n\n**CV**: 0.828\n**Public LB**: 0.789\n**Private LB**: 0.841\n\n\n# **Model Architecture**\n\n\n![Model Architecture](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1265131%2Fad78d36028ed6dc7ab1d3e79631b0172%2FFireShot%20Capture%20019%20-%20Online%20FlowChart%20%20Diagrams%20Editor%20-%20Mermaid%20Live%20Editor%20-%20mermaid.live%20(1).png?generation=1701841137052567&alt=media)\n\n\n##Patching to reduce sequence length\n\nSince 17280 is a very long sequence length (makes training very slow and harder to train), we reduce the sequence length by patching it.\n\n**Input:**  17280 * n_features (34)\n**Patch Size:** (Used different patch sizes 3, 4, 5, or 6 in different models).\n\n**Modified Sequence Length:** 17280 to 17280//Patch_Size sequence length\n\n**Modified Feature Size:** k * 4 * num_features (where k is a Dense layer output dim)\n\n## Modified Unet Part\n\n***UNET Encoder -> Bottleneck -> Transformer -> GRU or LSTM -> Unet Decoder***\n\nEach layer of UNET encoder had concatenated connections to the 1st layer\n\n**Initial Output size:** 17280//Patch_Size, 2 * Patch_size\n\n**Reshaped Output Size:** (17280, 2)\n\n\n\n\n# Post Processing\n\nInitially I was doing just doing simple peak detection when I was competing solo.\n\nI wanted to, but could not formulate a good way to rerank model predictions using lightgbm, but @kmat2019 is a great read for that.\n\nThanks to @ryotayoshinobu , I started applying NMS, and finally experimented with a kind of  WBF algorithm and made it work. This WBF algorithm gave a score of 0.79 to 0.793 on last day.\n\nBut it was worse by 0.001 in our private LB lol, so WBF hurt.\n\nWBF Working\n\n1. **Initialization and Convolution**: The function starts by applying convolution with the specified `convolution_kernel` to smooth the data.\n\n2. **Peak Detection Loop**: It iteratively searches for peaks in the data. The loop runs until the maximum count (`max_count`) is reached or the peak value falls below a threshold (`max_thresh`).\n\n3. **Adaptive Window and Weight Calculation**:\n   - A dynamic window size around each peak is determined based on the current maximum value's power (`curr_max_power`) and a distance parameter (`k_dist`).\n   - Depending on the `section_weight_method` (logarithmic or linear), weights are calculated for each section of the data around the peak.\n\n4. **Weighted Average and Score Calculation**: For each detected peak, a weighted average is calculated to determine the score of the peak. \nThis is done by considering the peak and k of its neighbours. This score is influenced by the method of weight calculation and other hyperparameters like `log_base`, `log_scale`, and `weight_coeff`.\n\n5. **Suppression and Updating Predictions**:\n   - After each peak is detected, the function suppresses the neighboring values to avoid detecting the same peak multiple times. This is controlled by `overlap_coeff` and `preds_reduction_power`.\n   - The indices and scores of detected peaks are stored and returned after the loop completes.\n\n\n\nI will share the implementation of the WBF part code, you can see if tuning these hyperparameters help in your models too. This accepts predictions per series id.\n\n(I found hyperparameters by a mix of manual and automated hyperparamter tuning approach)\n\n```\ndef wbf_nikhil(preds_orig, max_thresh=0.1, max_count=700, hyperparams=None):\n    k_dist = hyperparams['k_dist']\n    log_base = hyperparams['log_base']\n    log_scale = hyperparams['log_scale']\n    curr_max_power = hyperparams['curr_max_power']\n    weight_coeff = hyperparams['weight_coeff']\n    convolution_kernel = hyperparams['convolution_kernel']\n    section_weight_method = hyperparams['section_weight_method']\n    preds_reduction_power = hyperparams['preds_reduction_power']\n    overlap_coeff = hyperparams['overlap_coeff']\n    min_distance = hyperparams['min_distance']\n    \n    preds = preds_orig.copy()\n    preds = np.convolve(preds, convolution_kernel, mode='same')\n\n    count = 0\n    indices = []\n    scores = []\n\n    while count < max_count:\n        curr_max_idx = np.argmax(preds)\n        curr_max = preds[curr_max_idx]\n\n        if curr_max < max_thresh:\n            break\n\n        k = int(k_dist - max(min_distance, (curr_max**curr_max_power)))\n\n        start_idx = max(curr_max_idx - k, 0)\n        end_idx = min(curr_max_idx + k + 1, len(preds))\n\n        section = preds[start_idx:end_idx]\n\n        # Different weight calculation methods\n        distances = np.abs(np.arange(len(section)) - k)\n        if section_weight_method == 'logarithmic':\n            weights = 1 / (log_base ** (distances / (k * log_scale)))\n        elif section_weight_method == 'linear':\n            weights = 1 - (distances / k) * weight_coeff\n        # Add more methods as needed\n\n        weighted_avg = np.sum(section * weights) / np.sum(weights)\n\n        scores.append(weighted_avg)\n        indices.append(curr_max_idx)\n\n        preds[start_idx:end_idx] *= ((1 - weights * overlap_coeff))**preds_reduction_power\n\n        count += 1\n\n    return indices, scores\n```\n\n## Final Ensemble\n\n\nFinal ensemble was a blended weight of my and Penguin's subs regression   postprocessed using WBF\n\nFinal_Sub = WBF(Penguins_Predictions * 0.25 + Nikhil's Predictions*0.75)\n\nSince our predictions had slightly different scales of regression targets, Penguin's Predictions were first scaled by using a simple power transform i.e Penguin's_Predictions ** 0.7\n\n**Ensemble CV:** 0.835\n**Ensemble Public LB:** 0.793\n**Ensemble Private LB:** 0.845\n\n\nP.S: This competition is the first time I did not use gradient boosting, even after I knew it worked so well, me being a tabular guy. I mistake I guess. I love lightgbm and will definitely use one in the next competition.\n\nCongratulations to all the the top performers, it was a good fight, till it lasted :)",
      "votes": null
    },
    {
      "id": "2550672",
      "postDate": "12/06/2023 07:51:23",
      "content": "<p>A big congratulations bhai</p>",
      "rawMarkdown": "A big congratulations bhai",
      "votes": null
    },
    {
      "id": "2551529",
      "postDate": "12/06/2023 18:27:22",
      "content": "<p>how u understand what layers should u use in your model?</p>",
      "rawMarkdown": "how u understand what layers should u use in your model?",
      "votes": null
    },
    {
      "id": "2551748",
      "postDate": "12/06/2023 23:45:50",
      "content": "<p>Great solution writeup <a href=\"https://www.kaggle.com/nikhilmishradev\" target=\"_blank\">@nikhilmishradev</a> Thank you for sharing! Additional thanks for sharing the WBF implementation!</p>",
      "rawMarkdown": "Great solution writeup @nikhilmishradev Thank you for sharing! Additional thanks for sharing the WBF implementation!",
      "votes": null
    },
    {
      "id": "2553913",
      "postDate": "12/08/2023 16:01:23",
      "content": "<p>more of a combination of standard practices, reading papers to see what works best, and a bit of intuition with hit and trial. Usually you would want to start with a standard practice, like UNet for segmentation like tasks, and build upon it</p>",
      "rawMarkdown": "more of a combination of standard practices, reading papers to see what works best, and a bit of intuition with hit and trial. Usually you would want to start with a standard practice, like UNet for segmentation like tasks, and build upon it",
      "votes": null
    },
    {
      "id": "2556803",
      "postDate": "12/11/2023 01:56:09",
      "content": "<p>Congratulations <a href=\"https://www.kaggle.com/nikhilmishradev\" target=\"_blank\">@nikhilmishradev</a> <a href=\"https://www.kaggle.com/ryotayoshinobu\" target=\"_blank\">@ryotayoshinobu</a> ! When you two teamed up, i knew you would make a formidable team because you two has the best CV LB single model scores in the CV LB discussion post.</p>\n<p>Congratulations on strong finish achieving cash gold 4th place!</p>",
      "rawMarkdown": "Congratulations @nikhilmishradev @ryotayoshinobu ! When you two teamed up, i knew you would make a formidable team because you two has the best CV LB single model scores in the CV LB discussion post.\n\nCongratulations on strong finish achieving cash gold 4th place!",
      "votes": null
    },
    {
      "id": "2557123",
      "postDate": "12/11/2023 08:10:20",
      "content": "<p>Thank you <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> , would definitely want to team up with a legend like you in some competition</p>",
      "rawMarkdown": "Thank you @cdeotte , would definitely want to team up with a legend like you in some competition",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 2550672,
      "author_name": "nasere",
      "author_url": "",
      "post_date": "12/06/2023 07:51:23",
      "content": "<p>A big congratulations bhai</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2551529,
      "author_name": "yanlukianchik",
      "author_url": "",
      "post_date": "12/06/2023 18:27:22",
      "content": "<p>how u understand what layers should u use in your model?</p>",
      "votes": null,
      "replies": [
        {
          "id": 2553913,
          "author_name": "nikhilmishradev",
          "author_url": "",
          "post_date": "12/08/2023 16:01:23",
          "content": "<p>more of a combination of standard practices, reading papers to see what works best, and a bit of intuition with hit and trial. Usually you would want to start with a standard practice, like UNet for segmentation like tasks, and build upon it</p>",
          "votes": null,
          "replies": []
        }
      ]
    },
    {
      "id": 2551748,
      "author_name": "snnclsr",
      "author_url": "",
      "post_date": "12/06/2023 23:45:50",
      "content": "<p>Great solution writeup <a href=\"https://www.kaggle.com/nikhilmishradev\" target=\"_blank\">@nikhilmishradev</a> Thank you for sharing! Additional thanks for sharing the WBF implementation!</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2556803,
      "author_name": "cdeotte",
      "author_url": "",
      "post_date": "12/11/2023 01:56:09",
      "content": "<p>Congratulations <a href=\"https://www.kaggle.com/nikhilmishradev\" target=\"_blank\">@nikhilmishradev</a> <a href=\"https://www.kaggle.com/ryotayoshinobu\" target=\"_blank\">@ryotayoshinobu</a> ! When you two teamed up, i knew you would make a formidable team because you two has the best CV LB single model scores in the CV LB discussion post.</p>\n<p>Congratulations on strong finish achieving cash gold 4th place!</p>",
      "votes": null,
      "replies": [
        {
          "id": 2557123,
          "author_name": "nikhilmishradev",
          "author_url": "",
          "post_date": "12/11/2023 08:10:20",
          "content": "<p>Thank you <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> , would definitely want to team up with a legend like you in some competition</p>",
          "votes": null,
          "replies": []
        }
      ]
    }
  ],
  "raw_markdown_by_id": {
    "2550489": "Firstly thank you to my teammate @ryotayoshinobu for the amazing performance. Please read his part of the [solution](https://www.kaggle.com/competitions/child-mind-institute-detect-sleep-states/discussion/459597).\nI am really glad I teamed up with him and learnt a lot\n\nA big thanks to the organizers as well for such an interesting competition. A really tough one.\n\nYou can find the solution code here:\n\nhttps://github.com/nikhilmishradevelop/kaggle-child-mind-institute-detect-sleep-states\n\n# Solution Summary\n\n**Validation Strategy**: GroupKFold on Series Ids\n\n**Model Inputs**:  17280 x n_features length sequences as input (17280 = 12 steps_per_minute x 60_minutes * 24 hours)\n**Model Outputs**: 17280 x 2 (one for onset and other for wakeup)\n**Model Type**: Regression Model\n**Loss Type**: Cubic Loss i.e. abs(y_true-y_pred)**3\n\nThe last remaining part of the sequences with length < 17280 were padded to make them equal to 17280\n\nMy solution is a modified UNET (averaged over 4 models, 2 LSTMs and 2 GRUs) using normalized Gaussian targets similar to what @tolgadincer described. A big thanks to him for sharing a good method, early on in this competition.\n\n## Features:\n\n1. Original Sequence Features: Enmo, Anglez\n2. TimeStamp Features: Hour and Weekday\n3. Derived Sequence Features: Anglez difference, Enmo Difference, HDCZA features etc)\n\nGood Features help in faster convergence and much better scores, so adding good features were important (Read penguin's solution for some other good features)\n\n**My final 4 model ensemble results**: \n\n**CV**: 0.828\n**Public LB**: 0.789\n**Private LB**: 0.841\n\n\n# **Model Architecture**\n\n\n![Model Architecture](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1265131%2Fad78d36028ed6dc7ab1d3e79631b0172%2FFireShot%20Capture%20019%20-%20Online%20FlowChart%20%20Diagrams%20Editor%20-%20Mermaid%20Live%20Editor%20-%20mermaid.live%20(1).png?generation=1701841137052567&alt=media)\n\n\n##Patching to reduce sequence length\n\nSince 17280 is a very long sequence length (makes training very slow and harder to train), we reduce the sequence length by patching it.\n\n**Input:**  17280 * n_features (34)\n**Patch Size:** (Used different patch sizes 3, 4, 5, or 6 in different models).\n\n**Modified Sequence Length:** 17280 to 17280//Patch_Size sequence length\n\n**Modified Feature Size:** k * 4 * num_features (where k is a Dense layer output dim)\n\n## Modified Unet Part\n\n***UNET Encoder -> Bottleneck -> Transformer -> GRU or LSTM -> Unet Decoder***\n\nEach layer of UNET encoder had concatenated connections to the 1st layer\n\n**Initial Output size:** 17280//Patch_Size, 2 * Patch_size\n\n**Reshaped Output Size:** (17280, 2)\n\n\n\n\n# Post Processing\n\nInitially I was doing just doing simple peak detection when I was competing solo.\n\nI wanted to, but could not formulate a good way to rerank model predictions using lightgbm, but @kmat2019 is a great read for that.\n\nThanks to @ryotayoshinobu , I started applying NMS, and finally experimented with a kind of  WBF algorithm and made it work. This WBF algorithm gave a score of 0.79 to 0.793 on last day.\n\nBut it was worse by 0.001 in our private LB lol, so WBF hurt.\n\nWBF Working\n\n1. **Initialization and Convolution**: The function starts by applying convolution with the specified `convolution_kernel` to smooth the data.\n\n2. **Peak Detection Loop**: It iteratively searches for peaks in the data. The loop runs until the maximum count (`max_count`) is reached or the peak value falls below a threshold (`max_thresh`).\n\n3. **Adaptive Window and Weight Calculation**:\n   - A dynamic window size around each peak is determined based on the current maximum value's power (`curr_max_power`) and a distance parameter (`k_dist`).\n   - Depending on the `section_weight_method` (logarithmic or linear), weights are calculated for each section of the data around the peak.\n\n4. **Weighted Average and Score Calculation**: For each detected peak, a weighted average is calculated to determine the score of the peak. \nThis is done by considering the peak and k of its neighbours. This score is influenced by the method of weight calculation and other hyperparameters like `log_base`, `log_scale`, and `weight_coeff`.\n\n5. **Suppression and Updating Predictions**:\n   - After each peak is detected, the function suppresses the neighboring values to avoid detecting the same peak multiple times. This is controlled by `overlap_coeff` and `preds_reduction_power`.\n   - The indices and scores of detected peaks are stored and returned after the loop completes.\n\n\n\nI will share the implementation of the WBF part code, you can see if tuning these hyperparameters help in your models too. This accepts predictions per series id.\n\n(I found hyperparameters by a mix of manual and automated hyperparamter tuning approach)\n\n```\ndef wbf_nikhil(preds_orig, max_thresh=0.1, max_count=700, hyperparams=None):\n    k_dist = hyperparams['k_dist']\n    log_base = hyperparams['log_base']\n    log_scale = hyperparams['log_scale']\n    curr_max_power = hyperparams['curr_max_power']\n    weight_coeff = hyperparams['weight_coeff']\n    convolution_kernel = hyperparams['convolution_kernel']\n    section_weight_method = hyperparams['section_weight_method']\n    preds_reduction_power = hyperparams['preds_reduction_power']\n    overlap_coeff = hyperparams['overlap_coeff']\n    min_distance = hyperparams['min_distance']\n    \n    preds = preds_orig.copy()\n    preds = np.convolve(preds, convolution_kernel, mode='same')\n\n    count = 0\n    indices = []\n    scores = []\n\n    while count < max_count:\n        curr_max_idx = np.argmax(preds)\n        curr_max = preds[curr_max_idx]\n\n        if curr_max < max_thresh:\n            break\n\n        k = int(k_dist - max(min_distance, (curr_max**curr_max_power)))\n\n        start_idx = max(curr_max_idx - k, 0)\n        end_idx = min(curr_max_idx + k + 1, len(preds))\n\n        section = preds[start_idx:end_idx]\n\n        # Different weight calculation methods\n        distances = np.abs(np.arange(len(section)) - k)\n        if section_weight_method == 'logarithmic':\n            weights = 1 / (log_base ** (distances / (k * log_scale)))\n        elif section_weight_method == 'linear':\n            weights = 1 - (distances / k) * weight_coeff\n        # Add more methods as needed\n\n        weighted_avg = np.sum(section * weights) / np.sum(weights)\n\n        scores.append(weighted_avg)\n        indices.append(curr_max_idx)\n\n        preds[start_idx:end_idx] *= ((1 - weights * overlap_coeff))**preds_reduction_power\n\n        count += 1\n\n    return indices, scores\n```\n\n## Final Ensemble\n\n\nFinal ensemble was a blended weight of my and Penguin's subs regression   postprocessed using WBF\n\nFinal_Sub = WBF(Penguins_Predictions * 0.25 + Nikhil's Predictions*0.75)\n\nSince our predictions had slightly different scales of regression targets, Penguin's Predictions were first scaled by using a simple power transform i.e Penguin's_Predictions ** 0.7\n\n**Ensemble CV:** 0.835\n**Ensemble Public LB:** 0.793\n**Ensemble Private LB:** 0.845\n\n\nP.S: This competition is the first time I did not use gradient boosting, even after I knew it worked so well, me being a tabular guy. I mistake I guess. I love lightgbm and will definitely use one in the next competition.\n\nCongratulations to all the the top performers, it was a good fight, till it lasted :)",
    "2550672": "A big congratulations bhai",
    "2551529": "how u understand what layers should u use in your model?",
    "2551748": "Great solution writeup @nikhilmishradev Thank you for sharing! Additional thanks for sharing the WBF implementation!",
    "2553913": "more of a combination of standard practices, reading papers to see what works best, and a bit of intuition with hit and trial. Usually you would want to start with a standard practice, like UNet for segmentation like tasks, and build upon it",
    "2556803": "Congratulations @nikhilmishradev @ryotayoshinobu ! When you two teamed up, i knew you would make a formidable team because you two has the best CV LB single model scores in the CV LB discussion post.\n\nCongratulations on strong finish achieving cash gold 4th place!",
    "2557123": "Thank you @cdeotte , would definitely want to team up with a legend like you in some competition"
  },
  "source": "meta"
}