{
  "id": 459610,
  "title": "75th Place Detailed Solution - Spec2DCNN + CenterNet + Transformer + NMS",
  "url": "/competitions/child-mind-institute-detect-sleep-states/writeups/snore-like-thunder-75th-place-detailed-solution-sp",
  "author_name": "",
  "post_date": "2023-12-06T01:50:15.983Z",
  "votes": 24,
  "comment_count": 6,
  "views": 0,
  "content": "<p>First of all, I would like to thank <a href=\"https://www.kaggle.com/tubotubo\" target=\"_blank\">@tubotubo</a> for sharing your high-quality code, and also thank my teammates <a href=\"https://www.kaggle.com/liruiqi577\" target=\"_blank\">@liruiqi577</a> <a href=\"https://www.kaggle.com/brickcoder\" target=\"_blank\">@brickcoder</a> <a href=\"https://www.kaggle.com/xtzhou\" target=\"_blank\">@xtzhou</a> for their contributions in the competition. Here, I am going to share our team’s “<strong>snore like thunder</strong>” solution from the following aspects:</p>\n<ol>\n<li>Data preprocessing</li>\n<li>Feature Engineering</li>\n<li>Model</li>\n<li>Post Processing</li>\n<li>Model Ensemble</li>\n</ol>\n<h2>1. Data preprocessing</h2>\n<p>We made EDA and readed open discussions found that there are 4 types of data anomalies:</p>\n<ul>\n<li>Some series have a high missing rate and some of them do not even have any event labels;</li>\n<li>In some series , there are no event annotations in the middle and tail (possibly because the collection activity has stopped);</li>\n<li>The sleep record is incomplete (a period of sleep is only marked with onset or wakeup).</li>\n<li>There are outliers in the enmo value.</li>\n</ul>\n<p>To this end, we have some attempts, such as:</p>\n<ul>\n<li>Eliminate series with high missing rates;</li>\n<li>Cut the tail of the series without event labels;</li>\n<li>Upper clip enmo to 1.</li>\n</ul>\n<p>But the above methods didn't completely work. In the end, our preprocessing method was:</p>\n<p>We split the dataset group by series into 5 folds. For each fold, we eliminate series with a label missing rate of 100% in the training dataset while without performing any data preprocessing on the validation set. This is done to avoid introducing noise to the training set, and to ensure that the evaluation results of the validation set are more biased towards the real data distribution, which improve our LB score + 0.006.</p>\n<p>Part of our experiments as below:</p>\n<table>\n<thead>\n<tr>\n<th>Experiment</th>\n<th>Fold0</th>\n<th>Public (single fold)</th>\n<th>Private (5-fold)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>No preprocess missing data</td>\n<td>0.751</td>\n<td>0.718</td>\n<td>0.744</td>\n</tr>\n<tr>\n<td>Eliminate unlabeled data at the end of train_series &amp; series with missing rate &gt;80%</td>\n<td>0.739</td>\n<td>0.709</td>\n<td>0.741</td>\n</tr>\n<tr>\n<td>Drop train series which don’t have any event labels</td>\n<td>0.752</td>\n<td>0.724</td>\n<td>0.749</td>\n</tr>\n</tbody>\n</table>\n<h2>2. Feature Engineering</h2>\n<ul>\n<li>Sensor features: After smoothing the enmo and anglez features, a first-order difference is made to obtain the absolute value. Then replace the original enmo and anglez features with these features, which improve our LB score + 0.01.</li>\n</ul>\n<pre><code>train_series[] = train_series[].diff().()\ntrain_series[] = train_series[].rolling(window=, center=, min_periods=).mean()\ntrain_series[] = train_series[].diff().()\ntrain_series[] = train_series[].rolling(window=, center=, min_periods=).mean()\n</code></pre>\n<ul>\n<li>Time features: sin and cos hour.</li>\n</ul>\n<p>In addition, we also made the following features based on open notebooks and our EDA, such as: differential features with different orders, rolling window statistical features, interactive features of enmo and anglez (such as anglez's differential abs * enmo, etc.), anglez_rad_sin/cos, dayofweek/is_weekend (I find that children have different sleeping habits on weekdays and weekends). But strangely enough, too much feature engineering didn’t bring us much benefit.</p>\n<table>\n<thead>\n<tr>\n<th>Experiment</th>\n<th>Fold0</th>\n<th>Public (5-fold)</th>\n<th>Private (5-fold)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>anglez + enmo + hour_sin + hour_cos</td>\n<td>0.763</td>\n<td>0.731</td>\n<td>0.768</td>\n</tr>\n<tr>\n<td>anglez_abs_diff + enmo_abs_diff + hour_sin + hour_cos</td>\n<td>0.771</td>\n<td>0.741</td>\n<td>0.781</td>\n</tr>\n</tbody>\n</table>\n<h2>3. Model</h2>\n<p>We used 4 models:</p>\n<ul>\n<li>CNNSpectrogram + Spec2DCNN + UNet1DDecoder;</li>\n<li>PANNsFeatureExtractor + Spec2DCNN + UNet1DDecoder.</li>\n<li>PANNsFeatureExtractor + CenterNet + UNet1DDecoder.</li>\n<li>TransformerAutoModel (xsmall, downsample_rate=8).</li>\n</ul>\n<p><strong>Parameter Tunning</strong>: Add more kernel_size 8 for CNNSpectrogram can gain +0.002 online.</p>\n<p><strong>Multi-Task Learning Objectives</strong>: sleep status, onset, wake.</p>\n<p><strong>Loss Function</strong>: For Spec2DCNN and TransformerAutoModel, we use BCE, but with multi-task target weighting, sleep:onset:wake = 0.5:1:1. The purpose of this is to allow the model to focus on learning the last two columns.  We tried to train only for the onset and wake columns, but the score was not good. The reason is speculated that <strong>the positive samples in these two columns are sparse, and MTL needs to be used to transfer the information from positive samples in the sleep status to the prediction of sleep activity events.</strong> Also, I tried KL Loss but it didn't work that well. </p>\n<pre><code>self.loss_fn = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([,,]))\n</code></pre>\n<p>At the same time, we adjusted epoch to 70 and added early stopping with patience=15. The early stopping criterion is the AP of the validation dataset, not the loss of the validation set. batch_size=32.</p>\n<table>\n<thead>\n<tr>\n<th>Experiment</th>\n<th>Fold0</th>\n<th>Public (single fold)</th>\n<th>Private (5-fold)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>earlystop by val_loss</td>\n<td>0.750</td>\n<td>0.697</td>\n<td>0.742</td>\n</tr>\n<tr>\n<td>earlystop by val_score</td>\n<td>0.751</td>\n<td>0.718</td>\n<td>0.744</td>\n</tr>\n<tr>\n<td>loss_wgt = 1:1:1</td>\n<td>0.752</td>\n<td>0.724</td>\n<td>0.749</td>\n</tr>\n<tr>\n<td>loss_wgt = 0.5:1:1</td>\n<td>0.755</td>\n<td>0.723</td>\n<td>0.753</td>\n</tr>\n</tbody>\n</table>\n<p>Note: we used the model_weight.pth with the best offline val_score to submit our LB instead of using the best_model.pth with the best offline val_loss。</p>\n<h2>4. Post Processing</h2>\n<p>Our post-processing mainly includes:</p>\n<ul>\n<li><strong>find_peaks()</strong>: scipy.signal.find_peaks;</li>\n<li><strong>NMS</strong>: This task can be treated as object detection. [onset, wakeup] is regarded as a bounding boxes, and score is the confident of the box. Therefore, I used a time-series NMS. Using NMS can eliminate redundant boxes with high IOU, which increase our AP.</li>\n</ul>\n<pre><code> ():\n    x1 = dets_arr[:, ]\n    x2 = dets_arr[:, ]\n    scores = dets_arr[:, ]\n\n    areas = x2 - x1\n    order = scores.argsort()[::-]\n\n    keep = []\n     order.size &gt; :\n        i = order[]\n        keep.append(i)\n        xx1 = np.maximum(x1[i], x1[order[:]])\n        xx2 = np.minimum(x2[i], x2[order[:]])\n        inter = np.maximum(, xx2 - xx1 + )\n        ovr = inter / (areas[i] + areas[order[:]] - inter)\n        inds = np.where(ovr &lt;= thresh)[]\n        order = order[inds + ]\n\n    dets_nms_arr = dets_arr[keep,:]\n    onset_steps = dets_nms_arr[:, ].tolist()\n    wakeup_steps = dets_nms_arr[:, ].tolist()\n    nms_save_steps = np.unique(onset_steps + wakeup_steps).tolist()\n     nms_save_steps\n</code></pre>\n<p>In addition, we set score_th=0.005 (If it is set too low, a large number of events will be detected and cause online scoring errors, so it is fixed at 0.005 here), and use optuna to simultaneously search the parameter distance in find_peaks and the parameter iou_threshold of NMS. Finally, when distance=72 and iou_threshold=0.995, the best performance is achieved.</p>\n<pre><code> optuna\n\n ():\n    score_th =  \n    distance = trial.suggest_int(, , )\n    thresh = trial.suggest_float(, , )\n\n    \n    val_pred_df = post_process_for_seg(\n        keys=keys,\n        preds=preds[:, :, [, ]],\n        score_th=score_th,\n        distance=distance,\n    )\n\n    \n    val_pred_df = val_pred_df.to_pandas()\n    nms_pred_dfs = NMS_prediction(val_pred_df, thresh, verbose=)\n    score = event_detection_ap(valid_event_df.to_pandas(), nms_pred_dfs)\n     -score\n\nstudy = optuna.create_study()\nstudy.optimize(objective, n_trials=)\n(, study.best_params)\n(, study.best_value)\n</code></pre>\n<table>\n<thead>\n<tr>\n<th>Experiment</th>\n<th>Fold0</th>\n<th>Pubic (5-fold)</th>\n<th>Private (5-fold)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>find_peak</td>\n<td>-</td>\n<td>0.745</td>\n<td>0.787</td>\n</tr>\n<tr>\n<td>find_peak+NMS+optuna</td>\n<td>-</td>\n<td>0.746</td>\n<td>0.789</td>\n</tr>\n</tbody>\n</table>\n<h2>5. Model Ensemble</h2>\n<p>Finally, we average the output probabilities of the following models and then feed into the post processing methods to detect events. By the way, I tried post-processing the detection events for each model and then concating them, but this resulted in too many detections. Even with NMS, I didn't get a better score.</p>\n<p>The number of ensemble models: 4 (types of models) * 5 (fold number) = 20.</p>\n<table>\n<thead>\n<tr>\n<th>Experiment</th>\n<th>Fold0</th>\n<th>Pubic (5-fold)</th>\n<th>Private (5-fold)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>model1: CNNSpectrogram + Spec2DCNN + UNet1DDecoder</td>\n<td>0.77209</td>\n<td>0.743</td>\n<td>0.784</td>\n</tr>\n<tr>\n<td>model2: PANNsFeatureExtractor + Spec2DCNN + UNet1DDecoder</td>\n<td>0.777</td>\n<td>0.743</td>\n<td>0.782</td>\n</tr>\n<tr>\n<td>model3: PANNsFeatureExtractor + CenterNet + UNet1DDecoder</td>\n<td>0.75968</td>\n<td>0.634</td>\n<td>0.68</td>\n</tr>\n<tr>\n<td>model4: TransformerAutoModel</td>\n<td>0.74680</td>\n<td>-</td>\n<td>-</td>\n</tr>\n<tr>\n<td>model1 + model2(1:1)</td>\n<td>-</td>\n<td>0.746</td>\n<td>0.789</td>\n</tr>\n<tr>\n<td>model1 + model2+model3(1:1:0.4)</td>\n<td>-</td>\n<td>0.75</td>\n<td>0.786</td>\n</tr>\n<tr>\n<td>model1 + model2+model3+model4(1:1:0.4:0.2)</td>\n<td></td>\n<td>0.752</td>\n<td>0.787</td>\n</tr>\n</tbody>\n</table>\n<p>Unfortunately, we only considered CenterNet and Transformer to model ensemble with a tentative attitude on the last day, but surprisingly found that <strong>a low-CV-scoring model still has a probability of improving final performance as long as it is heterogeneous compared with your previous models</strong>. But we didn’t have more opportunities to submit more, which was a profound lesson for me.</p>\n<p>Thoughts not done:</p>\n<ul>\n<li><p><strong>Data Augmentation</strong>: Shift the time within the batch to increase more time diversity and reduce dependence on hour features.</p></li>\n<li><p><strong>Model</strong>: Try more models. Although we try transformer and it didn’t work for us. I am veryyy looking forward to the solutions from top-ranking players.</p></li>\n</ul>\n<p>Thanks again to Kaggle and all Kaggle players. This was a good competition and we learned a lot from it. If you think our solution is useful for you, welcome to upvote and discuss with us.</p>\n<p>In addition, this is my first 🥈 silver medal. Thank you everyone for letting me learn a lot. I will continue to work hard. :)</p>",
  "messages": [
    {
      "id": "2550342",
      "postDate": "12/06/2023 01:40:30",
      "content": "<p>First of all, I would like to thank <a href=\"https://www.kaggle.com/tubotubo\" target=\"_blank\">@tubotubo</a> for sharing your high-quality code, and also thank my teammates <a href=\"https://www.kaggle.com/liruiqi577\" target=\"_blank\">@liruiqi577</a> <a href=\"https://www.kaggle.com/brickcoder\" target=\"_blank\">@brickcoder</a> <a href=\"https://www.kaggle.com/xtzhou\" target=\"_blank\">@xtzhou</a> for their contributions in the competition. Here, I am going to share our team’s “<strong>snore like thunder</strong>” solution from the following aspects:</p>\n<ol>\n<li>Data preprocessing</li>\n<li>Feature Engineering</li>\n<li>Model</li>\n<li>Post Processing</li>\n<li>Model Ensemble</li>\n</ol>\n<h2>1. Data preprocessing</h2>\n<p>We made EDA and readed open discussions found that there are 4 types of data anomalies:</p>\n<ul>\n<li>Some series have a high missing rate and some of them do not even have any event labels;</li>\n<li>In some series , there are no event annotations in the middle and tail (possibly because the collection activity has stopped);</li>\n<li>The sleep record is incomplete (a period of sleep is only marked with onset or wakeup).</li>\n<li>There are outliers in the enmo value.</li>\n</ul>\n<p>To this end, we have some attempts, such as:</p>\n<ul>\n<li>Eliminate series with high missing rates;</li>\n<li>Cut the tail of the series without event labels;</li>\n<li>Upper clip enmo to 1.</li>\n</ul>\n<p>But the above methods didn't completely work. In the end, our preprocessing method was:</p>\n<p>We split the dataset group by series into 5 folds. For each fold, we eliminate series with a label missing rate of 100% in the training dataset while without performing any data preprocessing on the validation set. This is done to avoid introducing noise to the training set, and to ensure that the evaluation results of the validation set are more biased towards the real data distribution, which improve our LB score + 0.006.</p>\n<p>Part of our experiments as below:</p>\n<table>\n<thead>\n<tr>\n<th>Experiment</th>\n<th>Fold0</th>\n<th>Public (single fold)</th>\n<th>Private (5-fold)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>No preprocess missing data</td>\n<td>0.751</td>\n<td>0.718</td>\n<td>0.744</td>\n</tr>\n<tr>\n<td>Eliminate unlabeled data at the end of train_series &amp; series with missing rate &gt;80%</td>\n<td>0.739</td>\n<td>0.709</td>\n<td>0.741</td>\n</tr>\n<tr>\n<td>Drop train series which don’t have any event labels</td>\n<td>0.752</td>\n<td>0.724</td>\n<td>0.749</td>\n</tr>\n</tbody>\n</table>\n<h2>2. Feature Engineering</h2>\n<ul>\n<li>Sensor features: After smoothing the enmo and anglez features, a first-order difference is made to obtain the absolute value. Then replace the original enmo and anglez features with these features, which improve our LB score + 0.01.</li>\n</ul>\n<pre><code>train_series[] = train_series[].diff().()\ntrain_series[] = train_series[].rolling(window=, center=, min_periods=).mean()\ntrain_series[] = train_series[].diff().()\ntrain_series[] = train_series[].rolling(window=, center=, min_periods=).mean()\n</code></pre>\n<ul>\n<li>Time features: sin and cos hour.</li>\n</ul>\n<p>In addition, we also made the following features based on open notebooks and our EDA, such as: differential features with different orders, rolling window statistical features, interactive features of enmo and anglez (such as anglez's differential abs * enmo, etc.), anglez_rad_sin/cos, dayofweek/is_weekend (I find that children have different sleeping habits on weekdays and weekends). But strangely enough, too much feature engineering didn’t bring us much benefit.</p>\n<table>\n<thead>\n<tr>\n<th>Experiment</th>\n<th>Fold0</th>\n<th>Public (5-fold)</th>\n<th>Private (5-fold)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>anglez + enmo + hour_sin + hour_cos</td>\n<td>0.763</td>\n<td>0.731</td>\n<td>0.768</td>\n</tr>\n<tr>\n<td>anglez_abs_diff + enmo_abs_diff + hour_sin + hour_cos</td>\n<td>0.771</td>\n<td>0.741</td>\n<td>0.781</td>\n</tr>\n</tbody>\n</table>\n<h2>3. Model</h2>\n<p>We used 4 models:</p>\n<ul>\n<li>CNNSpectrogram + Spec2DCNN + UNet1DDecoder;</li>\n<li>PANNsFeatureExtractor + Spec2DCNN + UNet1DDecoder.</li>\n<li>PANNsFeatureExtractor + CenterNet + UNet1DDecoder.</li>\n<li>TransformerAutoModel (xsmall, downsample_rate=8).</li>\n</ul>\n<p><strong>Parameter Tunning</strong>: Add more kernel_size 8 for CNNSpectrogram can gain +0.002 online.</p>\n<p><strong>Multi-Task Learning Objectives</strong>: sleep status, onset, wake.</p>\n<p><strong>Loss Function</strong>: For Spec2DCNN and TransformerAutoModel, we use BCE, but with multi-task target weighting, sleep:onset:wake = 0.5:1:1. The purpose of this is to allow the model to focus on learning the last two columns.  We tried to train only for the onset and wake columns, but the score was not good. The reason is speculated that <strong>the positive samples in these two columns are sparse, and MTL needs to be used to transfer the information from positive samples in the sleep status to the prediction of sleep activity events.</strong> Also, I tried KL Loss but it didn't work that well. </p>\n<pre><code>self.loss_fn = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([,,]))\n</code></pre>\n<p>At the same time, we adjusted epoch to 70 and added early stopping with patience=15. The early stopping criterion is the AP of the validation dataset, not the loss of the validation set. batch_size=32.</p>\n<table>\n<thead>\n<tr>\n<th>Experiment</th>\n<th>Fold0</th>\n<th>Public (single fold)</th>\n<th>Private (5-fold)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>earlystop by val_loss</td>\n<td>0.750</td>\n<td>0.697</td>\n<td>0.742</td>\n</tr>\n<tr>\n<td>earlystop by val_score</td>\n<td>0.751</td>\n<td>0.718</td>\n<td>0.744</td>\n</tr>\n<tr>\n<td>loss_wgt = 1:1:1</td>\n<td>0.752</td>\n<td>0.724</td>\n<td>0.749</td>\n</tr>\n<tr>\n<td>loss_wgt = 0.5:1:1</td>\n<td>0.755</td>\n<td>0.723</td>\n<td>0.753</td>\n</tr>\n</tbody>\n</table>\n<p>Note: we used the model_weight.pth with the best offline val_score to submit our LB instead of using the best_model.pth with the best offline val_loss。</p>\n<h2>4. Post Processing</h2>\n<p>Our post-processing mainly includes:</p>\n<ul>\n<li><strong>find_peaks()</strong>: scipy.signal.find_peaks;</li>\n<li><strong>NMS</strong>: This task can be treated as object detection. [onset, wakeup] is regarded as a bounding boxes, and score is the confident of the box. Therefore, I used a time-series NMS. Using NMS can eliminate redundant boxes with high IOU, which increase our AP.</li>\n</ul>\n<pre><code> ():\n    x1 = dets_arr[:, ]\n    x2 = dets_arr[:, ]\n    scores = dets_arr[:, ]\n\n    areas = x2 - x1\n    order = scores.argsort()[::-]\n\n    keep = []\n     order.size &gt; :\n        i = order[]\n        keep.append(i)\n        xx1 = np.maximum(x1[i], x1[order[:]])\n        xx2 = np.minimum(x2[i], x2[order[:]])\n        inter = np.maximum(, xx2 - xx1 + )\n        ovr = inter / (areas[i] + areas[order[:]] - inter)\n        inds = np.where(ovr &lt;= thresh)[]\n        order = order[inds + ]\n\n    dets_nms_arr = dets_arr[keep,:]\n    onset_steps = dets_nms_arr[:, ].tolist()\n    wakeup_steps = dets_nms_arr[:, ].tolist()\n    nms_save_steps = np.unique(onset_steps + wakeup_steps).tolist()\n     nms_save_steps\n</code></pre>\n<p>In addition, we set score_th=0.005 (If it is set too low, a large number of events will be detected and cause online scoring errors, so it is fixed at 0.005 here), and use optuna to simultaneously search the parameter distance in find_peaks and the parameter iou_threshold of NMS. Finally, when distance=72 and iou_threshold=0.995, the best performance is achieved.</p>\n<pre><code> optuna\n\n ():\n    score_th =  \n    distance = trial.suggest_int(, , )\n    thresh = trial.suggest_float(, , )\n\n    \n    val_pred_df = post_process_for_seg(\n        keys=keys,\n        preds=preds[:, :, [, ]],\n        score_th=score_th,\n        distance=distance,\n    )\n\n    \n    val_pred_df = val_pred_df.to_pandas()\n    nms_pred_dfs = NMS_prediction(val_pred_df, thresh, verbose=)\n    score = event_detection_ap(valid_event_df.to_pandas(), nms_pred_dfs)\n     -score\n\nstudy = optuna.create_study()\nstudy.optimize(objective, n_trials=)\n(, study.best_params)\n(, study.best_value)\n</code></pre>\n<table>\n<thead>\n<tr>\n<th>Experiment</th>\n<th>Fold0</th>\n<th>Pubic (5-fold)</th>\n<th>Private (5-fold)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>find_peak</td>\n<td>-</td>\n<td>0.745</td>\n<td>0.787</td>\n</tr>\n<tr>\n<td>find_peak+NMS+optuna</td>\n<td>-</td>\n<td>0.746</td>\n<td>0.789</td>\n</tr>\n</tbody>\n</table>\n<h2>5. Model Ensemble</h2>\n<p>Finally, we average the output probabilities of the following models and then feed into the post processing methods to detect events. By the way, I tried post-processing the detection events for each model and then concating them, but this resulted in too many detections. Even with NMS, I didn't get a better score.</p>\n<p>The number of ensemble models: 4 (types of models) * 5 (fold number) = 20.</p>\n<table>\n<thead>\n<tr>\n<th>Experiment</th>\n<th>Fold0</th>\n<th>Pubic (5-fold)</th>\n<th>Private (5-fold)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>model1: CNNSpectrogram + Spec2DCNN + UNet1DDecoder</td>\n<td>0.77209</td>\n<td>0.743</td>\n<td>0.784</td>\n</tr>\n<tr>\n<td>model2: PANNsFeatureExtractor + Spec2DCNN + UNet1DDecoder</td>\n<td>0.777</td>\n<td>0.743</td>\n<td>0.782</td>\n</tr>\n<tr>\n<td>model3: PANNsFeatureExtractor + CenterNet + UNet1DDecoder</td>\n<td>0.75968</td>\n<td>0.634</td>\n<td>0.68</td>\n</tr>\n<tr>\n<td>model4: TransformerAutoModel</td>\n<td>0.74680</td>\n<td>-</td>\n<td>-</td>\n</tr>\n<tr>\n<td>model1 + model2(1:1)</td>\n<td>-</td>\n<td>0.746</td>\n<td>0.789</td>\n</tr>\n<tr>\n<td>model1 + model2+model3(1:1:0.4)</td>\n<td>-</td>\n<td>0.75</td>\n<td>0.786</td>\n</tr>\n<tr>\n<td>model1 + model2+model3+model4(1:1:0.4:0.2)</td>\n<td></td>\n<td>0.752</td>\n<td>0.787</td>\n</tr>\n</tbody>\n</table>\n<p>Unfortunately, we only considered CenterNet and Transformer to model ensemble with a tentative attitude on the last day, but surprisingly found that <strong>a low-CV-scoring model still has a probability of improving final performance as long as it is heterogeneous compared with your previous models</strong>. But we didn’t have more opportunities to submit more, which was a profound lesson for me.</p>\n<p>Thoughts not done:</p>\n<ul>\n<li><p><strong>Data Augmentation</strong>: Shift the time within the batch to increase more time diversity and reduce dependence on hour features.</p></li>\n<li><p><strong>Model</strong>: Try more models. Although we try transformer and it didn’t work for us. I am veryyy looking forward to the solutions from top-ranking players.</p></li>\n</ul>\n<p>Thanks again to Kaggle and all Kaggle players. This was a good competition and we learned a lot from it. If you think our solution is useful for you, welcome to upvote and discuss with us.</p>\n<p>In addition, this is my first 🥈 silver medal. Thank you everyone for letting me learn a lot. I will continue to work hard. :)</p>",
      "rawMarkdown": "First of all, I would like to thank @tubotubo for sharing your high-quality code, and also thank my teammates @liruiqi577 @brickcoder @xtzhou for their contributions in the competition. Here, I am going to share our team’s “**snore like thunder**” solution from the following aspects:\n\n1. Data preprocessing\n2. Feature Engineering\n3. Model\n4. Post Processing\n5. Model Ensemble\n\n## 1. Data preprocessing\nWe made EDA and readed open discussions found that there are 4 types of data anomalies:\n- Some series have a high missing rate and some of them do not even have any event labels;\n- In some series , there are no event annotations in the middle and tail (possibly because the collection activity has stopped);\n- The sleep record is incomplete (a period of sleep is only marked with onset or wakeup).\n- There are outliers in the enmo value.\n\nTo this end, we have some attempts, such as:\n- Eliminate series with high missing rates;\n- Cut the tail of the series without event labels;\n- Upper clip enmo to 1.\n\nBut the above methods didn't completely work. In the end, our preprocessing method was:\n\nWe split the dataset group by series into 5 folds. For each fold, we eliminate series with a label missing rate of 100% in the training dataset while without performing any data preprocessing on the validation set. This is done to avoid introducing noise to the training set, and to ensure that the evaluation results of the validation set are more biased towards the real data distribution, which improve our LB score + 0.006.\n\nPart of our experiments as below:\n| Experiment | Fold0 | Public (single fold) | Private (5-fold) |\n| --- | --- | --- | --- |\n| No preprocess missing data | 0.751 | 0.718 | 0.744 |\n| Eliminate unlabeled data at the end of train_series & series with missing rate >80% | 0.739 | 0.709 | 0.741 |\n| Drop train series which don’t have any event labels | 0.752 | 0.724 | 0.749 |\n\n## 2. Feature Engineering\n- Sensor features: After smoothing the enmo and anglez features, a first-order difference is made to obtain the absolute value. Then replace the original enmo and anglez features with these features, which improve our LB score + 0.01.\n\n```python\ntrain_series['enmo_abs_diff'] = train_series['enmo'].diff().abs()\ntrain_series['enmo'] = train_series['enmo_abs_diff'].rolling(window=5, center=True, min_periods=1).mean()\ntrain_series['anglez_abs_diff'] = train_series['anglez'].diff().abs()\ntrain_series['anglez'] = train_series['anglez_abs_diff'].rolling(window=5, center=True, min_periods=1).mean()\n```\n\n- Time features: sin and cos hour.\n\nIn addition, we also made the following features based on open notebooks and our EDA, such as: differential features with different orders, rolling window statistical features, interactive features of enmo and anglez (such as anglez's differential abs * enmo, etc.), anglez_rad_sin/cos, dayofweek/is_weekend (I find that children have different sleeping habits on weekdays and weekends). But strangely enough, too much feature engineering didn’t bring us much benefit.\n\n| Experiment | Fold0 | Public (5-fold) | Private (5-fold) |\n| --- | --- | --- | --- |\n| anglez + enmo + hour_sin + hour_cos | 0.763 | 0.731 | 0.768 |\n| anglez_abs_diff + enmo_abs_diff + hour_sin + hour_cos | 0.771 | 0.741 | 0.781 |\n\n## 3. Model\nWe used 4 models:\n\n- CNNSpectrogram + Spec2DCNN + UNet1DDecoder;\n- PANNsFeatureExtractor + Spec2DCNN + UNet1DDecoder.\n- PANNsFeatureExtractor + CenterNet + UNet1DDecoder.\n- TransformerAutoModel (xsmall, downsample_rate=8).\n\n**Parameter Tunning**: Add more kernel_size 8 for CNNSpectrogram can gain +0.002 online.\n\n**Multi-Task Learning Objectives**: sleep status, onset, wake.\n\n**Loss Function**: For Spec2DCNN and TransformerAutoModel, we use BCE, but with multi-task target weighting, sleep:onset:wake = 0.5:1:1. The purpose of this is to allow the model to focus on learning the last two columns.  We tried to train only for the onset and wake columns, but the score was not good. The reason is speculated that **the positive samples in these two columns are sparse, and MTL needs to be used to transfer the information from positive samples in the sleep status to the prediction of sleep activity events.** Also, I tried KL Loss but it didn't work that well. \n\n```python\nself.loss_fn = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([0.5,1.,1.]))\n```\n\nAt the same time, we adjusted epoch to 70 and added early stopping with patience=15. The early stopping criterion is the AP of the validation dataset, not the loss of the validation set. batch_size=32.\n\n| Experiment | Fold0 | Public (single fold) | Private (5-fold) |\n| --- | --- | --- | --- |\n| earlystop by val_loss | 0.750 | 0.697 | 0.742 |\n| earlystop by val_score | 0.751 | 0.718 | 0.744 |\n|  loss_wgt = 1:1:1 | 0.752 | 0.724 | 0.749 |\n| loss_wgt = 0.5:1:1 | 0.755 | 0.723 | 0.753 |\n\nNote: we used the model_weight.pth with the best offline val_score to submit our LB instead of using the best_model.pth with the best offline val_loss。\n\n## 4. Post Processing\n\nOur post-processing mainly includes:\n\n- **find_peaks()**: scipy.signal.find_peaks;\n- **NMS**: This task can be treated as object detection. [onset, wakeup] is regarded as a bounding boxes, and score is the confident of the box. Therefore, I used a time-series NMS. Using NMS can eliminate redundant boxes with high IOU, which increase our AP.\n\n```python\ndef apply_nms(dets_arr, thresh):\n    x1 = dets_arr[:, 0]\n    x2 = dets_arr[:, 1]\n    scores = dets_arr[:, 2]\n    \n    areas = x2 - x1\n    order = scores.argsort()[::-1]\n    \n    keep = []\n    while order.size > 0:\n        i = order[0]\n        keep.append(i)\n        xx1 = np.maximum(x1[i], x1[order[1:]])\n        xx2 = np.minimum(x2[i], x2[order[1:]])\n        inter = np.maximum(0.0, xx2 - xx1 + 1)\n        ovr = inter / (areas[i] + areas[order[1:]] - inter)\n        inds = np.where(ovr <= thresh)[0]\n        order = order[inds + 1]\n    \n    dets_nms_arr = dets_arr[keep,:]\n    onset_steps = dets_nms_arr[:, 0].tolist()\n    wakeup_steps = dets_nms_arr[:, 1].tolist()\n    nms_save_steps = np.unique(onset_steps + wakeup_steps).tolist()\n    return nms_save_steps\n```\n\nIn addition, we set score_th=0.005 (If it is set too low, a large number of events will be detected and cause online scoring errors, so it is fixed at 0.005 here), and use optuna to simultaneously search the parameter distance in find_peaks and the parameter iou_threshold of NMS. Finally, when distance=72 and iou_threshold=0.995, the best performance is achieved.\n\n```python\nimport optuna\n\ndef objective(trial):\n    score_th = 0.005 # trial.suggest_float('score_th', 0.003, 0.006)\n    distance = trial.suggest_int('distance', 20, 80)\n    thresh = trial.suggest_float('thresh', 0.75, 1.)\n\n    # find peak\n    val_pred_df = post_process_for_seg(\n        keys=keys,\n        preds=preds[:, :, [1, 2]],\n        score_th=score_th,\n        distance=distance,\n    )\n\n    # nms\n    val_pred_df = val_pred_df.to_pandas()\n    nms_pred_dfs = NMS_prediction(val_pred_df, thresh, verbose=False)\n    score = event_detection_ap(valid_event_df.to_pandas(), nms_pred_dfs)\n    return -score\n\nstudy = optuna.create_study()\nstudy.optimize(objective, n_trials=100)\nprint('Best hyperparameters: ', study.best_params)\nprint('Best score: ', study.best_value)\n```\n\n| Experiment | Fold0 | Pubic (5-fold) | Private (5-fold) |\n| --- | --- | --- | --- |\n| find_peak | - | 0.745 | 0.787 |\n| find_peak+NMS+optuna | - | 0.746 | 0.789 |\n\n## 5. Model Ensemble\n\nFinally, we average the output probabilities of the following models and then feed into the post processing methods to detect events. By the way, I tried post-processing the detection events for each model and then concating them, but this resulted in too many detections. Even with NMS, I didn't get a better score.\n\nThe number of ensemble models: 4 (types of models) * 5 (fold number) = 20.\n\n| Experiment | Fold0 | Pubic (5-fold) | Private (5-fold) |\n| --- | --- | --- | --- |\n| model1: CNNSpectrogram + Spec2DCNN + UNet1DDecoder | 0.77209 | 0.743 | 0.784 |\n| model2: PANNsFeatureExtractor + Spec2DCNN + UNet1DDecoder | 0.777 | 0.743 | 0.782 |\n| model3: PANNsFeatureExtractor + CenterNet + UNet1DDecoder | 0.75968 | 0.634 | 0.68 |\n| model4: TransformerAutoModel | 0.74680 | - | - |\n| model1 + model2(1:1) | - | 0.746 | 0.789 |\n| model1 + model2+model3(1:1:0.4) | - | 0.75 | 0.786 |\n| model1 + model2+model3+model4(1:1:0.4:0.2) |  | 0.752 | 0.787 |\n\nUnfortunately, we only considered CenterNet and Transformer to model ensemble with a tentative attitude on the last day, but surprisingly found that **a low-CV-scoring model still has a probability of improving final performance as long as it is heterogeneous compared with your previous models**. But we didn’t have more opportunities to submit more, which was a profound lesson for me.\n\nThoughts not done:\n- **Data Augmentation**: Shift the time within the batch to increase more time diversity and reduce dependence on hour features.\n\n- **Model**: Try more models. Although we try transformer and it didn’t work for us. I am veryyy looking forward to the solutions from top-ranking players.\n\nThanks again to Kaggle and all Kaggle players. This was a good competition and we learned a lot from it. If you think our solution is useful for you, welcome to upvote and discuss with us.\n\nIn addition, this is my first 🥈 silver medal. Thank you everyone for letting me learn a lot. I will continue to work hard. :)",
      "votes": null
    },
    {
      "id": "2550417",
      "postDate": "12/06/2023 02:54:25",
      "content": "<p>Thanks for sharing~</p>",
      "rawMarkdown": "Thanks for sharing~",
      "votes": null
    },
    {
      "id": "2550422",
      "postDate": "12/06/2023 03:03:34",
      "content": "<p>Thanks for sharing~</p>",
      "rawMarkdown": "Thanks for sharing~",
      "votes": null
    },
    {
      "id": "2550432",
      "postDate": "12/06/2023 03:11:48",
      "content": "<p>I also forked <a href=\"https://www.kaggle.com/tubotubo\" target=\"_blank\">@tubotubo</a> 's code, but I mainly focused on Spec2DCNN's model architecture, e.g. replacing smp.UNet with smp.DeepLabV3Plus, etc. Also, you can get a nice boost by mixing feature_extractor and decoder. Thank you very much for sharing!🥳</p>",
      "rawMarkdown": "I also forked @tubotubo 's code, but I mainly focused on Spec2DCNN's model architecture, e.g. replacing smp.UNet with smp.DeepLabV3Plus, etc. Also, you can get a nice boost by mixing feature_extractor and decoder. Thank you very much for sharing!🥳",
      "votes": null
    },
    {
      "id": "2550460",
      "postDate": "12/06/2023 03:34:46",
      "content": "<p>Nice, I haven't try smp.DeepLabV3Plus. Thanks for sharing.</p>",
      "rawMarkdown": "Nice, I haven't try smp.DeepLabV3Plus. Thanks for sharing.",
      "votes": null
    },
    {
      "id": "2550611",
      "postDate": "12/06/2023 06:25:12",
      "content": "<p>Also noticed that feature engineering didnt seem to improve performance, not even sinusoidal time features - was left quite confused!</p>",
      "rawMarkdown": "Also noticed that feature engineering didnt seem to improve performance, not even sinusoidal time features - was left quite confused!",
      "votes": null
    },
    {
      "id": "2550619",
      "postDate": "12/06/2023 06:40:23",
      "content": "<p>Same feeling as your, we have done with many feature works but they don't work at all.</p>",
      "rawMarkdown": "Same feeling as your, we have done with many feature works but they don't work at all.",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 2550417,
      "author_name": "liruiqi577",
      "author_url": "",
      "post_date": "12/06/2023 02:54:25",
      "content": "<p>Thanks for sharing~</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2550422,
      "author_name": "brickcoder",
      "author_url": "",
      "post_date": "12/06/2023 03:03:34",
      "content": "<p>Thanks for sharing~</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2550432,
      "author_name": "jimmyisme1",
      "author_url": "",
      "post_date": "12/06/2023 03:11:48",
      "content": "<p>I also forked <a href=\"https://www.kaggle.com/tubotubo\" target=\"_blank\">@tubotubo</a> 's code, but I mainly focused on Spec2DCNN's model architecture, e.g. replacing smp.UNet with smp.DeepLabV3Plus, etc. Also, you can get a nice boost by mixing feature_extractor and decoder. Thank you very much for sharing!🥳</p>",
      "votes": null,
      "replies": [
        {
          "id": 2550460,
          "author_name": "alvinai9603",
          "author_url": "",
          "post_date": "12/06/2023 03:34:46",
          "content": "<p>Nice, I haven't try smp.DeepLabV3Plus. Thanks for sharing.</p>",
          "votes": null,
          "replies": []
        }
      ]
    },
    {
      "id": 2550611,
      "author_name": "exjustice",
      "author_url": "",
      "post_date": "12/06/2023 06:25:12",
      "content": "<p>Also noticed that feature engineering didnt seem to improve performance, not even sinusoidal time features - was left quite confused!</p>",
      "votes": null,
      "replies": [
        {
          "id": 2550619,
          "author_name": "alvinai9603",
          "author_url": "",
          "post_date": "12/06/2023 06:40:23",
          "content": "<p>Same feeling as your, we have done with many feature works but they don't work at all.</p>",
          "votes": null,
          "replies": []
        }
      ]
    }
  ],
  "raw_markdown_by_id": {
    "2550342": "First of all, I would like to thank @tubotubo for sharing your high-quality code, and also thank my teammates @liruiqi577 @brickcoder @xtzhou for their contributions in the competition. Here, I am going to share our team’s “**snore like thunder**” solution from the following aspects:\n\n1. Data preprocessing\n2. Feature Engineering\n3. Model\n4. Post Processing\n5. Model Ensemble\n\n## 1. Data preprocessing\nWe made EDA and readed open discussions found that there are 4 types of data anomalies:\n- Some series have a high missing rate and some of them do not even have any event labels;\n- In some series , there are no event annotations in the middle and tail (possibly because the collection activity has stopped);\n- The sleep record is incomplete (a period of sleep is only marked with onset or wakeup).\n- There are outliers in the enmo value.\n\nTo this end, we have some attempts, such as:\n- Eliminate series with high missing rates;\n- Cut the tail of the series without event labels;\n- Upper clip enmo to 1.\n\nBut the above methods didn't completely work. In the end, our preprocessing method was:\n\nWe split the dataset group by series into 5 folds. For each fold, we eliminate series with a label missing rate of 100% in the training dataset while without performing any data preprocessing on the validation set. This is done to avoid introducing noise to the training set, and to ensure that the evaluation results of the validation set are more biased towards the real data distribution, which improve our LB score + 0.006.\n\nPart of our experiments as below:\n| Experiment | Fold0 | Public (single fold) | Private (5-fold) |\n| --- | --- | --- | --- |\n| No preprocess missing data | 0.751 | 0.718 | 0.744 |\n| Eliminate unlabeled data at the end of train_series & series with missing rate >80% | 0.739 | 0.709 | 0.741 |\n| Drop train series which don’t have any event labels | 0.752 | 0.724 | 0.749 |\n\n## 2. Feature Engineering\n- Sensor features: After smoothing the enmo and anglez features, a first-order difference is made to obtain the absolute value. Then replace the original enmo and anglez features with these features, which improve our LB score + 0.01.\n\n```python\ntrain_series['enmo_abs_diff'] = train_series['enmo'].diff().abs()\ntrain_series['enmo'] = train_series['enmo_abs_diff'].rolling(window=5, center=True, min_periods=1).mean()\ntrain_series['anglez_abs_diff'] = train_series['anglez'].diff().abs()\ntrain_series['anglez'] = train_series['anglez_abs_diff'].rolling(window=5, center=True, min_periods=1).mean()\n```\n\n- Time features: sin and cos hour.\n\nIn addition, we also made the following features based on open notebooks and our EDA, such as: differential features with different orders, rolling window statistical features, interactive features of enmo and anglez (such as anglez's differential abs * enmo, etc.), anglez_rad_sin/cos, dayofweek/is_weekend (I find that children have different sleeping habits on weekdays and weekends). But strangely enough, too much feature engineering didn’t bring us much benefit.\n\n| Experiment | Fold0 | Public (5-fold) | Private (5-fold) |\n| --- | --- | --- | --- |\n| anglez + enmo + hour_sin + hour_cos | 0.763 | 0.731 | 0.768 |\n| anglez_abs_diff + enmo_abs_diff + hour_sin + hour_cos | 0.771 | 0.741 | 0.781 |\n\n## 3. Model\nWe used 4 models:\n\n- CNNSpectrogram + Spec2DCNN + UNet1DDecoder;\n- PANNsFeatureExtractor + Spec2DCNN + UNet1DDecoder.\n- PANNsFeatureExtractor + CenterNet + UNet1DDecoder.\n- TransformerAutoModel (xsmall, downsample_rate=8).\n\n**Parameter Tunning**: Add more kernel_size 8 for CNNSpectrogram can gain +0.002 online.\n\n**Multi-Task Learning Objectives**: sleep status, onset, wake.\n\n**Loss Function**: For Spec2DCNN and TransformerAutoModel, we use BCE, but with multi-task target weighting, sleep:onset:wake = 0.5:1:1. The purpose of this is to allow the model to focus on learning the last two columns.  We tried to train only for the onset and wake columns, but the score was not good. The reason is speculated that **the positive samples in these two columns are sparse, and MTL needs to be used to transfer the information from positive samples in the sleep status to the prediction of sleep activity events.** Also, I tried KL Loss but it didn't work that well. \n\n```python\nself.loss_fn = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([0.5,1.,1.]))\n```\n\nAt the same time, we adjusted epoch to 70 and added early stopping with patience=15. The early stopping criterion is the AP of the validation dataset, not the loss of the validation set. batch_size=32.\n\n| Experiment | Fold0 | Public (single fold) | Private (5-fold) |\n| --- | --- | --- | --- |\n| earlystop by val_loss | 0.750 | 0.697 | 0.742 |\n| earlystop by val_score | 0.751 | 0.718 | 0.744 |\n|  loss_wgt = 1:1:1 | 0.752 | 0.724 | 0.749 |\n| loss_wgt = 0.5:1:1 | 0.755 | 0.723 | 0.753 |\n\nNote: we used the model_weight.pth with the best offline val_score to submit our LB instead of using the best_model.pth with the best offline val_loss。\n\n## 4. Post Processing\n\nOur post-processing mainly includes:\n\n- **find_peaks()**: scipy.signal.find_peaks;\n- **NMS**: This task can be treated as object detection. [onset, wakeup] is regarded as a bounding boxes, and score is the confident of the box. Therefore, I used a time-series NMS. Using NMS can eliminate redundant boxes with high IOU, which increase our AP.\n\n```python\ndef apply_nms(dets_arr, thresh):\n    x1 = dets_arr[:, 0]\n    x2 = dets_arr[:, 1]\n    scores = dets_arr[:, 2]\n    \n    areas = x2 - x1\n    order = scores.argsort()[::-1]\n    \n    keep = []\n    while order.size > 0:\n        i = order[0]\n        keep.append(i)\n        xx1 = np.maximum(x1[i], x1[order[1:]])\n        xx2 = np.minimum(x2[i], x2[order[1:]])\n        inter = np.maximum(0.0, xx2 - xx1 + 1)\n        ovr = inter / (areas[i] + areas[order[1:]] - inter)\n        inds = np.where(ovr <= thresh)[0]\n        order = order[inds + 1]\n    \n    dets_nms_arr = dets_arr[keep,:]\n    onset_steps = dets_nms_arr[:, 0].tolist()\n    wakeup_steps = dets_nms_arr[:, 1].tolist()\n    nms_save_steps = np.unique(onset_steps + wakeup_steps).tolist()\n    return nms_save_steps\n```\n\nIn addition, we set score_th=0.005 (If it is set too low, a large number of events will be detected and cause online scoring errors, so it is fixed at 0.005 here), and use optuna to simultaneously search the parameter distance in find_peaks and the parameter iou_threshold of NMS. Finally, when distance=72 and iou_threshold=0.995, the best performance is achieved.\n\n```python\nimport optuna\n\ndef objective(trial):\n    score_th = 0.005 # trial.suggest_float('score_th', 0.003, 0.006)\n    distance = trial.suggest_int('distance', 20, 80)\n    thresh = trial.suggest_float('thresh', 0.75, 1.)\n\n    # find peak\n    val_pred_df = post_process_for_seg(\n        keys=keys,\n        preds=preds[:, :, [1, 2]],\n        score_th=score_th,\n        distance=distance,\n    )\n\n    # nms\n    val_pred_df = val_pred_df.to_pandas()\n    nms_pred_dfs = NMS_prediction(val_pred_df, thresh, verbose=False)\n    score = event_detection_ap(valid_event_df.to_pandas(), nms_pred_dfs)\n    return -score\n\nstudy = optuna.create_study()\nstudy.optimize(objective, n_trials=100)\nprint('Best hyperparameters: ', study.best_params)\nprint('Best score: ', study.best_value)\n```\n\n| Experiment | Fold0 | Pubic (5-fold) | Private (5-fold) |\n| --- | --- | --- | --- |\n| find_peak | - | 0.745 | 0.787 |\n| find_peak+NMS+optuna | - | 0.746 | 0.789 |\n\n## 5. Model Ensemble\n\nFinally, we average the output probabilities of the following models and then feed into the post processing methods to detect events. By the way, I tried post-processing the detection events for each model and then concating them, but this resulted in too many detections. Even with NMS, I didn't get a better score.\n\nThe number of ensemble models: 4 (types of models) * 5 (fold number) = 20.\n\n| Experiment | Fold0 | Pubic (5-fold) | Private (5-fold) |\n| --- | --- | --- | --- |\n| model1: CNNSpectrogram + Spec2DCNN + UNet1DDecoder | 0.77209 | 0.743 | 0.784 |\n| model2: PANNsFeatureExtractor + Spec2DCNN + UNet1DDecoder | 0.777 | 0.743 | 0.782 |\n| model3: PANNsFeatureExtractor + CenterNet + UNet1DDecoder | 0.75968 | 0.634 | 0.68 |\n| model4: TransformerAutoModel | 0.74680 | - | - |\n| model1 + model2(1:1) | - | 0.746 | 0.789 |\n| model1 + model2+model3(1:1:0.4) | - | 0.75 | 0.786 |\n| model1 + model2+model3+model4(1:1:0.4:0.2) |  | 0.752 | 0.787 |\n\nUnfortunately, we only considered CenterNet and Transformer to model ensemble with a tentative attitude on the last day, but surprisingly found that **a low-CV-scoring model still has a probability of improving final performance as long as it is heterogeneous compared with your previous models**. But we didn’t have more opportunities to submit more, which was a profound lesson for me.\n\nThoughts not done:\n- **Data Augmentation**: Shift the time within the batch to increase more time diversity and reduce dependence on hour features.\n\n- **Model**: Try more models. Although we try transformer and it didn’t work for us. I am veryyy looking forward to the solutions from top-ranking players.\n\nThanks again to Kaggle and all Kaggle players. This was a good competition and we learned a lot from it. If you think our solution is useful for you, welcome to upvote and discuss with us.\n\nIn addition, this is my first 🥈 silver medal. Thank you everyone for letting me learn a lot. I will continue to work hard. :)",
    "2550417": "Thanks for sharing~",
    "2550422": "Thanks for sharing~",
    "2550432": "I also forked @tubotubo 's code, but I mainly focused on Spec2DCNN's model architecture, e.g. replacing smp.UNet with smp.DeepLabV3Plus, etc. Also, you can get a nice boost by mixing feature_extractor and decoder. Thank you very much for sharing!🥳",
    "2550460": "Nice, I haven't try smp.DeepLabV3Plus. Thanks for sharing.",
    "2550611": "Also noticed that feature engineering didnt seem to improve performance, not even sinusoidal time features - was left quite confused!",
    "2550619": "Same feeling as your, we have done with many feature works but they don't work at all."
  },
  "source": "meta"
}