{
  "id": 360236,
  "title": "public 3rd place solution",
  "content": "<p>Congrats to all the winners, especially for Team Hydrogen( <a href=\"https://www.kaggle.com/philippsinger\" target=\"_blank\">@philippsinger</a>, <a href=\"https://www.kaggle.com/ybabakhin\" target=\"_blank\">@ybabakhin</a>, <a href=\"https://www.kaggle.com/ilu000\" target=\"_blank\">@ilu000</a>) and <a href=\"https://www.kaggle.com/kmat2019\" target=\"_blank\">@kmat2019</a>, your scores are really impressive!</p>\n<p>I’ll write up a summary of my solution.</p>\n<h2>Baseline</h2>\n<p>It ends up being a very simple end2end 2.5d model.<br>\nBackbone is EfficientNet with TSM[1], and head is simple 1D UNet.<br>\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1554318%2F0059bfdb6ffbaee44c3c3ac0a20e8030%2FUntitled%20Diagram.drawio.png?generation=1665851779731895&amp;alt=media\" alt=\"\"><br>\nBut there are many tricks in both training and inference.</p>\n<hr>\n<h2>Training</h2>\n<h3>1. Pretrain with ball detection</h3>\n<p>Even though I used ImageNet pretrained model for efficientnet, inserting TSM module changes model structure drastically, so it was hard to control training. (ex. easy to overfit or diverge.) And intuitively it seems important to teach a model to focus around the ball, so I used ball annotation in the SoccerNet tracking dataset[2] to pretrain the model.  </p>\n<p>Pretrain set up is following:</p>\n<ul>\n<li>Efnet + TSM (+ shallow conv2d head)  </li>\n<li>Output is the same resolution as the final feature map of efnet, 1/32 scale heatmap.  </li>\n<li>Label is 2d gaussian heat map like CenterNet, but didn’t add regression branch for simplicity  </li>\n</ul>\n<p>At first I was not sure whether we can use SoccerNet or not, so I decided to annotate balls for the video clips by myself, it ended up being more than 20,000 frames. But after confirming we can use SoccerNet, I switched to it. Pretrain performance is better than mine, but the final score is not much different.</p>\n<p>Prediction is like this. I tried to use it as additional input to the next stage, but no successful result.<br>\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1554318%2F9c95367ddb5ea5437ec652ff2a9612d0%2Fepoch7_1606b0e6_0_5724_5786_ball.gif?generation=1665852280112233&amp;alt=media\" alt=\"\"></p>\n<h3>2.Label design</h3>\n<p>1d gaussian heat map +- 5 frames around the event. Here is an example for challenge at frame 13. (num_frames=16)<br>\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1554318%2Fb89a208458dc230637b4beaf60000c53%2FUntitled%20Diagram-Page-3.drawio.png?generation=1665852495588359&amp;alt=media\" alt=\"\"><br>\nAlso create a binary mask to represent whether the frame is in eval interval or not.</p>\n<h3>3.Sampling</h3>\n<p>Half pos and half neg. Pos means at least 1 event in the label and neg means randomly sampled from eval interval. </p>\n<h3>4. Augmentation</h3>\n<p>Basic ones like RandomHorizontalFlip, RandomRotation, ColorJitter.<br>\nAlso manifold mixup[3] works well. I applied mixup on image-wise features.</p>\n<h3>5. Model architecture</h3>\n<ul>\n<li>EfficientNet + TSM at the end of each blocks</li>\n<li>Simple 1d UNet (some variants have LSTM and GRU at the end)</li>\n<li>LayerNorm instead of BatchNorm in the UNet (BN didn’t converge maybe due to small batch size)</li>\n</ul>\n<h3>6. Loss</h3>\n<p>Base loss is focal loss[6]. <br>\nThe tasks for play, challenge and throwin are actually independent. But challenge and throwin are difficult to learn as there are much fewer samples than play. So I adjusted the alpha factor in focal loss based on the number of samples in each class. It’s inspired by class balanced loss[7], but the formulation is modified. This stabilizes training, especially before using the ball pretrained model.</p>\n<pre><code>def _calc_weight(self, samples_per_class, beta=0.9999):\n    effective_num = 1.0 - beta**samples_per_class\n    weights = (1.0 - beta) / effective_num\n    weights = weights / torch.sum(weights)\n    weights = weights.unsqueeze(0).float()\n    return weights.cuda()\n</code></pre>\n<h3>7. Fine tune with long sequence</h3>\n<p>After training with a relatively short clip like 32 frames, I fine tune the model with a long sequence like 128 frames, but with freezing backbone so that it fits in gpu memory.</p>\n<h3>8. Others</h3>\n<ul>\n<li>LR warmup was important somehow</li>\n<li>EMA[4]</li>\n<li>Gradient checkpointing[5] helps to fit large model in memory</li>\n<li>Mixed precision + cast image to float16 before feeding to the model</li>\n</ul>\n<hr>\n<h2>Inference</h2>\n<h3>1.Video loading</h3>\n<p>cv2.VideoCapture + multi worker pytorch dataloader doesn't work well. Video decoding can be easily bottleneck, so I brought imutils[8] library’s threading implementation and modified it a bit.  <br>\nAlso, I used multi image sizes [(540, 960), (480, 800), (360, 640)]. It’s faster to create small images by resizing (540, 960) image after transferring it to the GPU.</p>\n<h3>2.Overlapping predictions</h3>\n<p>My model predicts 64 frames at the same time. But I realized that predictions around edges are relatively inaccurate, so I decided to drop them and take only center 48 frames out of 64 frames. Below diagram shows how it works. <br>\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1554318%2F11ee7e5b0bc00a2d178a0ed1f50af8a1%2FUntitled%20Diagram-Page-2.drawio.png?generation=1665852445476883&amp;alt=media\" alt=\"\"></p>\n<h3>3.Postprocess</h3>\n<p>Simply apply nms. Thresholds are [12, 6, 6] for [challenge, play, throwin] respectively.</p>\n<h3>4.Ensemble</h3>\n<p>To maximize the effect of the ensemble, I prepared various models with different folds.</p>\n<ul>\n<li>Image size is one of [(540, 960), (480, 800), (360, 640)] </li>\n<li>Backbone is one of [b2, b3, b4]</li>\n<li>Sequence length is one of [32, 64, 128]  </li>\n</ul>\n<p>Single best model scores 0.768.   <br>\n((360,640), efnet-b2, seq_len=128, all data)  <br>\nEnsemble 6 models with various combination reaches 0.827.</p>\n<h2>What didn’t work</h2>\n<p>Too many to list up.</p>\n<ul>\n<li>Crop around ball area</li>\n<li>Crop out no player area</li>\n<li>Feed ball heatmap as additional input</li>\n<li>Transformer head like ActionFormer or VideoMAE</li>\n<li>Larger backbone</li>\n<li>Pseudo label</li>\n<li>Many others..</li>\n</ul>\n<h2>Reference</h2>\n<p>[1] <a href=\"https://github.com/MIT-HAN-LAB/temporal-shift-module\" target=\"_blank\">https://github.com/MIT-HAN-LAB/temporal-shift-module</a><br>\n[2] <a href=\"https://github.com/SoccerNet/sn-tracking\" target=\"_blank\">https://github.com/SoccerNet/sn-tracking</a><br>\n[3] <a href=\"https://arxiv.org/abs/1806.05236\" target=\"_blank\">https://arxiv.org/abs/1806.05236</a><br>\n[4] <a href=\"https://github.com/rwightman/pytorch-image-models/blob/a2727c1bf78ba0d7b5727f5f95e37fb7f8866b1f/timm/utils/model_ema.py\" target=\"_blank\">https://github.com/rwightman/pytorch-image-models/blob/a2727c1bf78ba0d7b5727f5f95e37fb7f8866b1f/timm/utils/model_ema.py</a><br>\n[5] <a href=\"https://github.com/cybertronai/gradient-checkpointing\" target=\"_blank\">https://github.com/cybertronai/gradient-checkpointing</a><br>\n[6] <a href=\"https://arxiv.org/abs/1708.02002\" target=\"_blank\">https://arxiv.org/abs/1708.02002</a><br>\n[7] <a href=\"https://arxiv.org/abs/1901.05555\" target=\"_blank\">https://arxiv.org/abs/1901.05555</a><br>\n[8] <a href=\"https://github.com/PyImageSearch/imutils\" target=\"_blank\">https://github.com/PyImageSearch/imutils</a>   </p>",
  "comments": [
    {
      "id": 1993567,
      "author_name": "Guillermo García Cobo",
      "author_url": "",
      "post_date": "2022-10-18T13:34:05.817000",
      "content": "<p>Congrats on your great solution <a href=\"https://www.kaggle.com/bamps53\" target=\"_blank\">@bamps53</a> and thank you very much for your write-up! I learned some new stuff like TSM and using 1D UNet for temporal information.</p>\n<p>I wanted to ask you about your label design. In one of our first trials, we tried the same approach based on the competition metric tolerance intervals. This also allowed us to have multi-label predictions for each window. However, due to the great number of zeros in the target tensor, many of the models converged to predict all zeros. Did you see anything like this in your training? Did the class weights solve it? We used <a href=\"https://scikit-learn.org/stable/modules/generated/sklearn.utils.class_weight.compute_class_weight.html\" target=\"_blank\">sklearn.compute_class_weight</a> with no success.</p>\n<p>Finally, you mention that your model is 2.5D. How many frames do you stack for each frame?</p>",
      "votes": 2,
      "replies": [
        {
          "id": 1995881,
          "author_name": "Camaro",
          "author_url": "",
          "post_date": "2022-10-19T22:13:34.710000",
          "content": "<p>I experienced similar things like training diverges or never learn anything especially when I haven't pretrained the weights. I used focal loss as it's designed for imblanced problem so it might helped, but other winner's still use BCE so it may not be necessary.</p>\n<p>I stacked 32, 64 or 128 frames depending on the model configuration .</p>",
          "votes": 1,
          "replies": []
        }
      ]
    },
    {
      "id": 2002815,
      "author_name": "arutema47",
      "author_url": "",
      "post_date": "2022-10-25T05:20:44.947000",
      "content": "<p>I'm quite confused on the use of TSM, it would be great if you can give some comments.</p>\n<blockquote>\n  <p>Efnet + TSM (+ shallow conv2d head)</p>\n</blockquote>\n<p>Do you insert the TSM at the Efnet's layer before GAP?<br>\nSo its like Efnet -&gt; TSM -&gt; conv2d -&gt; GAP?</p>",
      "votes": 1,
      "replies": [
        {
          "id": 2003290,
          "author_name": "Camaro",
          "author_url": "",
          "post_date": "2022-10-25T12:03:00.650000",
          "content": "<p><a href=\"https://www.kaggle.com/kyoshioka47\" target=\"_blank\">@kyoshioka47</a> <br>\nNo, I inserted TSM every after blocks.</p>",
          "votes": 0,
          "replies": []
        },
        {
          "id": 2003353,
          "author_name": "Camaro",
          "author_url": "",
          "post_date": "2022-10-25T12:52:08.517000",
          "content": "<p>Like this;</p>\n<pre><code>model_name = 'tf_efficientnet_b2_ns'\nnum_frames = 32\nn_div = 8\n\nmodel = timm.create_model(model_name)\nfor i, block in enumerate(model.blocks):\n    model.blocks[i] = TemporalShift(block, n_segment=num_frames, n_div=n_div)\n</code></pre>",
          "votes": 5,
          "replies": []
        },
        {
          "id": 2004037,
          "author_name": "arutema47",
          "author_url": "",
          "post_date": "2022-10-26T03:01:42.173000",
          "content": "<p>Thank you for the sample codes!<br>\nThis is really helpful, will try to replicate your design :)</p>",
          "votes": 0,
          "replies": []
        }
      ]
    },
    {
      "id": 1996076,
      "author_name": "Rahul Pandey",
      "author_url": "",
      "post_date": "2022-10-20T04:40:16.907000",
      "content": "<p>Good work <a href=\"https://www.kaggle.com/bamps53\" target=\"_blank\">@bamps53</a> and thank you so much for your great share.</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 1991142,
      "author_name": "makiji",
      "author_url": "",
      "post_date": "2022-10-17T02:36:36",
      "content": "<p>What didn’t work<br>\nToo many to list up.</p>",
      "votes": -2,
      "replies": []
    },
    {
      "id": 1989709,
      "author_name": "Prompt Cloud 2",
      "author_url": "",
      "post_date": "2022-10-16T06:26:20.913000",
      "content": "<p>What didn’t work<br>\nToo many to list up.</p>\n<p>Crop around ball area<br>\nCrop out no player area</p>",
      "votes": -1,
      "replies": []
    },
    {
      "id": 2004511,
      "author_name": "Adhithia",
      "author_url": "",
      "post_date": "2022-10-26T11:18:56.250000",
      "content": "<p>This is brilliant! And a big thanks for mentioning the references. I don't see a lot of authors posting the references which makes it difficult to understand the entire essence of the solution. Great stuff. <a href=\"https://www.kaggle.com/bamps53\" target=\"_blank\">@bamps53</a> </p>",
      "votes": 0,
      "replies": []
    },
    {
      "id": 2004370,
      "author_name": "Y-Haneji",
      "author_url": "",
      "post_date": "2022-10-26T08:13:55.400000",
      "content": "<p><a href=\"https://www.kaggle.com/bamps53\" target=\"_blank\">@bamps53</a> <br>\nThanks for sharing great solution!<br>\nI'm interested in your label design. How did you compute the soft label? (I understand the basics of gaussian distribution.)</p>",
      "votes": 0,
      "replies": [
        {
          "id": 2004730,
          "author_name": "Camaro",
          "author_url": "",
          "post_date": "2022-10-26T13:34:53.643000",
          "content": "<p>It's not easy to understand code, but I created the label like this;</p>\n<pre><code>def gaussian_kernel(length, sigma=3):\n    x = np.ogrid[-length:length+1]\n    h = np.exp(-(x**2) / (2 * sigma * sigma))\n    h[h &lt; np.finfo(h.dtype).eps * h.max()] = 0\n    return h\n\noffset = 5\nsigma = 3\n# labels has 1 in exact event frame and 0 otherwise. shape is (frame_len, 3).\nfor c, event in enumerate(EVENT_CLASSES):\n    labels[:, c] = np.convolve(labels[:, c], gaussian_kernel(offset, sigma), mode='same')\n</code></pre>",
          "votes": 2,
          "replies": []
        }
      ]
    },
    {
      "id": 1996759,
      "author_name": "AprilSixteen",
      "author_url": "",
      "post_date": "2022-10-20T12:16:15.620000",
      "content": "<p>Thank you for sharing great solution <a href=\"https://www.kaggle.com/bamps53\" target=\"_blank\">@bamps53</a> !<br>\nI'd like to ask you one thing. What 2.5D model implementation would be like?  If you don't mind, would you give me some hints or references to input multiple images into CNN backbone mentioned above?</p>",
      "votes": 0,
      "replies": []
    },
    {
      "id": 1995004,
      "author_name": "",
      "author_url": "",
      "post_date": "2022-10-19T12:16:23.873000",
      "content": "",
      "votes": 0,
      "replies": []
    },
    {
      "id": 1996894,
      "author_name": "Basel Ayman",
      "author_url": "",
      "post_date": "2022-10-20T13:36:39.253000",
      "content": "<p>Great! Thanks for sharing</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2207795,
      "author_name": "Maruf Hossain",
      "author_url": "",
      "post_date": "2023-04-03T16:26:36.517000",
      "content": "<p>Thanks for sharing, its great!</p>",
      "votes": 0,
      "replies": []
    },
    {
      "id": 2002974,
      "author_name": "tlx123",
      "author_url": "",
      "post_date": "2022-10-25T07:41:42.837000",
      "content": "<p>Thank you For Sharing!!</p>",
      "votes": 0,
      "replies": []
    },
    {
      "id": 1996901,
      "author_name": "Preferrrrr",
      "author_url": "",
      "post_date": "2022-10-20T13:40:13.777000",
      "content": "<p>Thank you For Sharing!!</p>",
      "votes": 0,
      "replies": []
    },
    {
      "id": 1995891,
      "author_name": "Max Wei",
      "author_url": "",
      "post_date": "2022-10-19T22:28:11.807000",
      "content": "<p>Thanks for sharing!</p>",
      "votes": 0,
      "replies": []
    },
    {
      "id": 1993321,
      "author_name": "nhuerp00",
      "author_url": "",
      "post_date": "2022-10-18T09:31:23.567000",
      "content": "<p>Thanks for sharing!</p>",
      "votes": 0,
      "replies": []
    },
    {
      "id": 1990583,
      "author_name": "Teja Kumar behves",
      "author_url": "",
      "post_date": "2022-10-16T16:40:22.057000",
      "content": "<p>Thanks for sharing dude!</p>",
      "votes": 0,
      "replies": []
    },
    {
      "id": 1990242,
      "author_name": "Harshit N Panchal",
      "author_url": "",
      "post_date": "2022-10-16T12:15:47.950000",
      "content": "<p>Thanks for sharing dude!</p>",
      "votes": 0,
      "replies": []
    }
  ],
  "comment_count": 22,
  "messages": [
    {
      "id": 1988998,
      "postDate": "2022-10-15T17:04:33.433Z",
      "content": "<p>Congrats to all the winners, especially for Team Hydrogen( <a href=\"https://www.kaggle.com/philippsinger\" target=\"_blank\">@philippsinger</a>, <a href=\"https://www.kaggle.com/ybabakhin\" target=\"_blank\">@ybabakhin</a>, <a href=\"https://www.kaggle.com/ilu000\" target=\"_blank\">@ilu000</a>) and <a href=\"https://www.kaggle.com/kmat2019\" target=\"_blank\">@kmat2019</a>, your scores are really impressive!</p>\n<p>I’ll write up a summary of my solution.</p>\n<h2>Baseline</h2>\n<p>It ends up being a very simple end2end 2.5d model.<br>\nBackbone is EfficientNet with TSM[1], and head is simple 1D UNet.<br>\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1554318%2F0059bfdb6ffbaee44c3c3ac0a20e8030%2FUntitled%20Diagram.drawio.png?generation=1665851779731895&amp;alt=media\" alt=\"\"><br>\nBut there are many tricks in both training and inference.</p>\n<hr>\n<h2>Training</h2>\n<h3>1. Pretrain with ball detection</h3>\n<p>Even though I used ImageNet pretrained model for efficientnet, inserting TSM module changes model structure drastically, so it was hard to control training. (ex. easy to overfit or diverge.) And intuitively it seems important to teach a model to focus around the ball, so I used ball annotation in the SoccerNet tracking dataset[2] to pretrain the model.  </p>\n<p>Pretrain set up is following:</p>\n<ul>\n<li>Efnet + TSM (+ shallow conv2d head)  </li>\n<li>Output is the same resolution as the final feature map of efnet, 1/32 scale heatmap.  </li>\n<li>Label is 2d gaussian heat map like CenterNet, but didn’t add regression branch for simplicity  </li>\n</ul>\n<p>At first I was not sure whether we can use SoccerNet or not, so I decided to annotate balls for the video clips by myself, it ended up being more than 20,000 frames. But after confirming we can use SoccerNet, I switched to it. Pretrain performance is better than mine, but the final score is not much different.</p>\n<p>Prediction is like this. I tried to use it as additional input to the next stage, but no successful result.<br>\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1554318%2F9c95367ddb5ea5437ec652ff2a9612d0%2Fepoch7_1606b0e6_0_5724_5786_ball.gif?generation=1665852280112233&amp;alt=media\" alt=\"\"></p>\n<h3>2.Label design</h3>\n<p>1d gaussian heat map +- 5 frames around the event. Here is an example for challenge at frame 13. (num_frames=16)<br>\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1554318%2Fb89a208458dc230637b4beaf60000c53%2FUntitled%20Diagram-Page-3.drawio.png?generation=1665852495588359&amp;alt=media\" alt=\"\"><br>\nAlso create a binary mask to represent whether the frame is in eval interval or not.</p>\n<h3>3.Sampling</h3>\n<p>Half pos and half neg. Pos means at least 1 event in the label and neg means randomly sampled from eval interval. </p>\n<h3>4. Augmentation</h3>\n<p>Basic ones like RandomHorizontalFlip, RandomRotation, ColorJitter.<br>\nAlso manifold mixup[3] works well. I applied mixup on image-wise features.</p>\n<h3>5. Model architecture</h3>\n<ul>\n<li>EfficientNet + TSM at the end of each blocks</li>\n<li>Simple 1d UNet (some variants have LSTM and GRU at the end)</li>\n<li>LayerNorm instead of BatchNorm in the UNet (BN didn’t converge maybe due to small batch size)</li>\n</ul>\n<h3>6. Loss</h3>\n<p>Base loss is focal loss[6]. <br>\nThe tasks for play, challenge and throwin are actually independent. But challenge and throwin are difficult to learn as there are much fewer samples than play. So I adjusted the alpha factor in focal loss based on the number of samples in each class. It’s inspired by class balanced loss[7], but the formulation is modified. This stabilizes training, especially before using the ball pretrained model.</p>\n<pre><code>def _calc_weight(self, samples_per_class, beta=0.9999):\n    effective_num = 1.0 - beta**samples_per_class\n    weights = (1.0 - beta) / effective_num\n    weights = weights / torch.sum(weights)\n    weights = weights.unsqueeze(0).float()\n    return weights.cuda()\n</code></pre>\n<h3>7. Fine tune with long sequence</h3>\n<p>After training with a relatively short clip like 32 frames, I fine tune the model with a long sequence like 128 frames, but with freezing backbone so that it fits in gpu memory.</p>\n<h3>8. Others</h3>\n<ul>\n<li>LR warmup was important somehow</li>\n<li>EMA[4]</li>\n<li>Gradient checkpointing[5] helps to fit large model in memory</li>\n<li>Mixed precision + cast image to float16 before feeding to the model</li>\n</ul>\n<hr>\n<h2>Inference</h2>\n<h3>1.Video loading</h3>\n<p>cv2.VideoCapture + multi worker pytorch dataloader doesn't work well. Video decoding can be easily bottleneck, so I brought imutils[8] library’s threading implementation and modified it a bit.  <br>\nAlso, I used multi image sizes [(540, 960), (480, 800), (360, 640)]. It’s faster to create small images by resizing (540, 960) image after transferring it to the GPU.</p>\n<h3>2.Overlapping predictions</h3>\n<p>My model predicts 64 frames at the same time. But I realized that predictions around edges are relatively inaccurate, so I decided to drop them and take only center 48 frames out of 64 frames. Below diagram shows how it works. <br>\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1554318%2F11ee7e5b0bc00a2d178a0ed1f50af8a1%2FUntitled%20Diagram-Page-2.drawio.png?generation=1665852445476883&amp;alt=media\" alt=\"\"></p>\n<h3>3.Postprocess</h3>\n<p>Simply apply nms. Thresholds are [12, 6, 6] for [challenge, play, throwin] respectively.</p>\n<h3>4.Ensemble</h3>\n<p>To maximize the effect of the ensemble, I prepared various models with different folds.</p>\n<ul>\n<li>Image size is one of [(540, 960), (480, 800), (360, 640)] </li>\n<li>Backbone is one of [b2, b3, b4]</li>\n<li>Sequence length is one of [32, 64, 128]  </li>\n</ul>\n<p>Single best model scores 0.768.   <br>\n((360,640), efnet-b2, seq_len=128, all data)  <br>\nEnsemble 6 models with various combination reaches 0.827.</p>\n<h2>What didn’t work</h2>\n<p>Too many to list up.</p>\n<ul>\n<li>Crop around ball area</li>\n<li>Crop out no player area</li>\n<li>Feed ball heatmap as additional input</li>\n<li>Transformer head like ActionFormer or VideoMAE</li>\n<li>Larger backbone</li>\n<li>Pseudo label</li>\n<li>Many others..</li>\n</ul>\n<h2>Reference</h2>\n<p>[1] <a href=\"https://github.com/MIT-HAN-LAB/temporal-shift-module\" target=\"_blank\">https://github.com/MIT-HAN-LAB/temporal-shift-module</a><br>\n[2] <a href=\"https://github.com/SoccerNet/sn-tracking\" target=\"_blank\">https://github.com/SoccerNet/sn-tracking</a><br>\n[3] <a href=\"https://arxiv.org/abs/1806.05236\" target=\"_blank\">https://arxiv.org/abs/1806.05236</a><br>\n[4] <a href=\"https://github.com/rwightman/pytorch-image-models/blob/a2727c1bf78ba0d7b5727f5f95e37fb7f8866b1f/timm/utils/model_ema.py\" target=\"_blank\">https://github.com/rwightman/pytorch-image-models/blob/a2727c1bf78ba0d7b5727f5f95e37fb7f8866b1f/timm/utils/model_ema.py</a><br>\n[5] <a href=\"https://github.com/cybertronai/gradient-checkpointing\" target=\"_blank\">https://github.com/cybertronai/gradient-checkpointing</a><br>\n[6] <a href=\"https://arxiv.org/abs/1708.02002\" target=\"_blank\">https://arxiv.org/abs/1708.02002</a><br>\n[7] <a href=\"https://arxiv.org/abs/1901.05555\" target=\"_blank\">https://arxiv.org/abs/1901.05555</a><br>\n[8] <a href=\"https://github.com/PyImageSearch/imutils\" target=\"_blank\">https://github.com/PyImageSearch/imutils</a>   </p>",
      "rawMarkdown": "Congrats to all the winners, especially for Team Hydrogen( @philippsinger, @ybabakhin, @ilu000) and @kmat2019, your scores are really impressive!\n\nI’ll write up a summary of my solution.\n\n## Baseline\nIt ends up being a very simple end2end 2.5d model.\nBackbone is EfficientNet with TSM[1], and head is simple 1D UNet.\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1554318%2F0059bfdb6ffbaee44c3c3ac0a20e8030%2FUntitled%20Diagram.drawio.png?generation=1665851779731895&alt=media)\nBut there are many tricks in both training and inference.\n\n---\n\n## Training\n\n### 1. Pretrain with ball detection\nEven though I used ImageNet pretrained model for efficientnet, inserting TSM module changes model structure drastically, so it was hard to control training. (ex. easy to overfit or diverge.) And intuitively it seems important to teach a model to focus around the ball, so I used ball annotation in the SoccerNet tracking dataset[2] to pretrain the model.  \n\nPretrain set up is following:\n- Efnet + TSM (+ shallow conv2d head)  \n- Output is the same resolution as the final feature map of efnet, 1/32 scale heatmap.  \n- Label is 2d gaussian heat map like CenterNet, but didn’t add regression branch for simplicity  \n\nAt first I was not sure whether we can use SoccerNet or not, so I decided to annotate balls for the video clips by myself, it ended up being more than 20,000 frames. But after confirming we can use SoccerNet, I switched to it. Pretrain performance is better than mine, but the final score is not much different.\n\nPrediction is like this. I tried to use it as additional input to the next stage, but no successful result.\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1554318%2F9c95367ddb5ea5437ec652ff2a9612d0%2Fepoch7_1606b0e6_0_5724_5786_ball.gif?generation=1665852280112233&alt=media)\n\n### 2.Label design\n1d gaussian heat map +- 5 frames around the event. Here is an example for challenge at frame 13. (num_frames=16)\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1554318%2Fb89a208458dc230637b4beaf60000c53%2FUntitled%20Diagram-Page-3.drawio.png?generation=1665852495588359&alt=media)\nAlso create a binary mask to represent whether the frame is in eval interval or not.\n\n### 3.Sampling\nHalf pos and half neg. Pos means at least 1 event in the label and neg means randomly sampled from eval interval. \n\n### 4. Augmentation\nBasic ones like RandomHorizontalFlip, RandomRotation, ColorJitter.\nAlso manifold mixup[3] works well. I applied mixup on image-wise features.\n\n### 5. Model architecture\n- EfficientNet + TSM at the end of each blocks\n- Simple 1d UNet (some variants have LSTM and GRU at the end)\n- LayerNorm instead of BatchNorm in the UNet (BN didn’t converge maybe due to small batch size)\n\n### 6. Loss\nBase loss is focal loss[6]. \nThe tasks for play, challenge and throwin are actually independent. But challenge and throwin are difficult to learn as there are much fewer samples than play. So I adjusted the alpha factor in focal loss based on the number of samples in each class. It’s inspired by class balanced loss[7], but the formulation is modified. This stabilizes training, especially before using the ball pretrained model.\n```\ndef _calc_weight(self, samples_per_class, beta=0.9999):\n    effective_num = 1.0 - beta**samples_per_class\n    weights = (1.0 - beta) / effective_num\n    weights = weights / torch.sum(weights)\n    weights = weights.unsqueeze(0).float()\n    return weights.cuda()\n```\n\n### 7. Fine tune with long sequence\nAfter training with a relatively short clip like 32 frames, I fine tune the model with a long sequence like 128 frames, but with freezing backbone so that it fits in gpu memory.\n\n### 8. Others\n- LR warmup was important somehow\n- EMA[4]\n- Gradient checkpointing[5] helps to fit large model in memory\n- Mixed precision + cast image to float16 before feeding to the model\n\n---\n\n## Inference\n### 1.Video loading\ncv2.VideoCapture + multi worker pytorch dataloader doesn't work well. Video decoding can be easily bottleneck, so I brought imutils[8] library’s threading implementation and modified it a bit.  \nAlso, I used multi image sizes [(540, 960), (480, 800), (360, 640)]. It’s faster to create small images by resizing (540, 960) image after transferring it to the GPU.\n\n### 2.Overlapping predictions\nMy model predicts 64 frames at the same time. But I realized that predictions around edges are relatively inaccurate, so I decided to drop them and take only center 48 frames out of 64 frames. Below diagram shows how it works. \n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1554318%2F11ee7e5b0bc00a2d178a0ed1f50af8a1%2FUntitled%20Diagram-Page-2.drawio.png?generation=1665852445476883&alt=media)\n\n### 3.Postprocess\nSimply apply nms. Thresholds are [12, 6, 6] for [challenge, play, throwin] respectively.\n\n### 4.Ensemble\nTo maximize the effect of the ensemble, I prepared various models with different folds.\n- Image size is one of [(540, 960), (480, 800), (360, 640)] \n- Backbone is one of [b2, b3, b4]\n- Sequence length is one of [32, 64, 128]  \n\nSingle best model scores 0.768.   \n((360,640), efnet-b2, seq_len=128, all data)  \nEnsemble 6 models with various combination reaches 0.827.\n\n## What didn’t work\n\nToo many to list up.\n- Crop around ball area\n- Crop out no player area\n- Feed ball heatmap as additional input\n- Transformer head like ActionFormer or VideoMAE\n- Larger backbone\n- Pseudo label\n- Many others..\n\n\n## Reference\n[1] https://github.com/MIT-HAN-LAB/temporal-shift-module\n[2] https://github.com/SoccerNet/sn-tracking\n[3] https://arxiv.org/abs/1806.05236\n[4] https://github.com/rwightman/pytorch-image-models/blob/a2727c1bf78ba0d7b5727f5f95e37fb7f8866b1f/timm/utils/model_ema.py\n[5] https://github.com/cybertronai/gradient-checkpointing\n[6] https://arxiv.org/abs/1708.02002\n[7] https://arxiv.org/abs/1901.05555\n[8] https://github.com/PyImageSearch/imutils   ",
      "votes": 85
    },
    {
      "id": 1993567,
      "postDate": "2022-10-18T13:34:05.817Z",
      "content": "<p>Congrats on your great solution <a href=\"https://www.kaggle.com/bamps53\" target=\"_blank\">@bamps53</a> and thank you very much for your write-up! I learned some new stuff like TSM and using 1D UNet for temporal information.</p>\n<p>I wanted to ask you about your label design. In one of our first trials, we tried the same approach based on the competition metric tolerance intervals. This also allowed us to have multi-label predictions for each window. However, due to the great number of zeros in the target tensor, many of the models converged to predict all zeros. Did you see anything like this in your training? Did the class weights solve it? We used <a href=\"https://scikit-learn.org/stable/modules/generated/sklearn.utils.class_weight.compute_class_weight.html\" target=\"_blank\">sklearn.compute_class_weight</a> with no success.</p>\n<p>Finally, you mention that your model is 2.5D. How many frames do you stack for each frame?</p>",
      "rawMarkdown": "Congrats on your great solution @bamps53 and thank you very much for your write-up! I learned some new stuff like TSM and using 1D UNet for temporal information.\n\nI wanted to ask you about your label design. In one of our first trials, we tried the same approach based on the competition metric tolerance intervals. This also allowed us to have multi-label predictions for each window. However, due to the great number of zeros in the target tensor, many of the models converged to predict all zeros. Did you see anything like this in your training? Did the class weights solve it? We used [sklearn.compute_class_weight](https://scikit-learn.org/stable/modules/generated/sklearn.utils.class_weight.compute_class_weight.html) with no success.\n\nFinally, you mention that your model is 2.5D. How many frames do you stack for each frame?",
      "votes": 2,
      "replies": [
        {
          "id": 1995881,
          "postDate": "2022-10-19T22:13:34.710Z",
          "content": "<p>I experienced similar things like training diverges or never learn anything especially when I haven't pretrained the weights. I used focal loss as it's designed for imblanced problem so it might helped, but other winner's still use BCE so it may not be necessary.</p>\n<p>I stacked 32, 64 or 128 frames depending on the model configuration .</p>",
          "rawMarkdown": "I experienced similar things like training diverges or never learn anything especially when I haven't pretrained the weights. I used focal loss as it's designed for imblanced problem so it might helped, but other winner's still use BCE so it may not be necessary.\n\nI stacked 32, 64 or 128 frames depending on the model configuration .",
          "votes": 1
        }
      ]
    },
    {
      "id": 2002815,
      "postDate": "2022-10-25T05:20:44.947Z",
      "content": "<p>I'm quite confused on the use of TSM, it would be great if you can give some comments.</p>\n<blockquote>\n  <p>Efnet + TSM (+ shallow conv2d head)</p>\n</blockquote>\n<p>Do you insert the TSM at the Efnet's layer before GAP?<br>\nSo its like Efnet -&gt; TSM -&gt; conv2d -&gt; GAP?</p>",
      "rawMarkdown": "I'm quite confused on the use of TSM, it would be great if you can give some comments.\n>Efnet + TSM (+ shallow conv2d head)\n\nDo you insert the TSM at the Efnet's layer before GAP?\nSo its like Efnet -> TSM -> conv2d -> GAP?",
      "votes": 1,
      "replies": [
        {
          "id": 2003290,
          "postDate": "2022-10-25T12:03:00.650Z",
          "content": "<p><a href=\"https://www.kaggle.com/kyoshioka47\" target=\"_blank\">@kyoshioka47</a> <br>\nNo, I inserted TSM every after blocks.</p>",
          "rawMarkdown": "@kyoshioka47 \nNo, I inserted TSM every after blocks."
        },
        {
          "id": 2003353,
          "postDate": "2022-10-25T12:52:08.517Z",
          "content": "<p>Like this;</p>\n<pre><code>model_name = 'tf_efficientnet_b2_ns'\nnum_frames = 32\nn_div = 8\n\nmodel = timm.create_model(model_name)\nfor i, block in enumerate(model.blocks):\n    model.blocks[i] = TemporalShift(block, n_segment=num_frames, n_div=n_div)\n</code></pre>",
          "rawMarkdown": "Like this;\n```\nmodel_name = 'tf_efficientnet_b2_ns'\nnum_frames = 32\nn_div = 8\n\nmodel = timm.create_model(model_name)\nfor i, block in enumerate(model.blocks):\n    model.blocks[i] = TemporalShift(block, n_segment=num_frames, n_div=n_div)\n```",
          "votes": 5
        },
        {
          "id": 2004037,
          "postDate": "2022-10-26T03:01:42.173Z",
          "content": "<p>Thank you for the sample codes!<br>\nThis is really helpful, will try to replicate your design :)</p>",
          "rawMarkdown": "Thank you for the sample codes!\nThis is really helpful, will try to replicate your design :)"
        }
      ]
    },
    {
      "id": 1996076,
      "postDate": "2022-10-20T04:40:16.907Z",
      "content": "<p>Good work <a href=\"https://www.kaggle.com/bamps53\" target=\"_blank\">@bamps53</a> and thank you so much for your great share.</p>",
      "rawMarkdown": "Good work @bamps53 and thank you so much for your great share.",
      "votes": 1
    },
    {
      "id": 1991142,
      "postDate": "2022-10-17T02:36:36Z",
      "content": "<p>What didn’t work<br>\nToo many to list up.</p>",
      "rawMarkdown": "What didn’t work\nToo many to list up.",
      "votes": -2
    },
    {
      "id": 1989709,
      "postDate": "2022-10-16T06:26:20.913Z",
      "content": "<p>What didn’t work<br>\nToo many to list up.</p>\n<p>Crop around ball area<br>\nCrop out no player area</p>",
      "rawMarkdown": "What didn’t work\nToo many to list up.\n\nCrop around ball area\nCrop out no player area\n",
      "votes": -1
    },
    {
      "id": 2004511,
      "postDate": "2022-10-26T11:18:56.250Z",
      "content": "<p>This is brilliant! And a big thanks for mentioning the references. I don't see a lot of authors posting the references which makes it difficult to understand the entire essence of the solution. Great stuff. <a href=\"https://www.kaggle.com/bamps53\" target=\"_blank\">@bamps53</a> </p>",
      "rawMarkdown": "This is brilliant! And a big thanks for mentioning the references. I don't see a lot of authors posting the references which makes it difficult to understand the entire essence of the solution. Great stuff. @bamps53 "
    },
    {
      "id": 2004370,
      "postDate": "2022-10-26T08:13:55.400Z",
      "content": "<p><a href=\"https://www.kaggle.com/bamps53\" target=\"_blank\">@bamps53</a> <br>\nThanks for sharing great solution!<br>\nI'm interested in your label design. How did you compute the soft label? (I understand the basics of gaussian distribution.)</p>",
      "rawMarkdown": "@bamps53 \nThanks for sharing great solution!\nI'm interested in your label design. How did you compute the soft label? (I understand the basics of gaussian distribution.)",
      "replies": [
        {
          "id": 2004730,
          "postDate": "2022-10-26T13:34:53.643Z",
          "content": "<p>It's not easy to understand code, but I created the label like this;</p>\n<pre><code>def gaussian_kernel(length, sigma=3):\n    x = np.ogrid[-length:length+1]\n    h = np.exp(-(x**2) / (2 * sigma * sigma))\n    h[h &lt; np.finfo(h.dtype).eps * h.max()] = 0\n    return h\n\noffset = 5\nsigma = 3\n# labels has 1 in exact event frame and 0 otherwise. shape is (frame_len, 3).\nfor c, event in enumerate(EVENT_CLASSES):\n    labels[:, c] = np.convolve(labels[:, c], gaussian_kernel(offset, sigma), mode='same')\n</code></pre>",
          "rawMarkdown": "It's not easy to understand code, but I created the label like this;\n```\ndef gaussian_kernel(length, sigma=3):\n    x = np.ogrid[-length:length+1]\n    h = np.exp(-(x**2) / (2 * sigma * sigma))\n    h[h < np.finfo(h.dtype).eps * h.max()] = 0\n    return h\n\noffset = 5\nsigma = 3\n# labels has 1 in exact event frame and 0 otherwise. shape is (frame_len, 3).\nfor c, event in enumerate(EVENT_CLASSES):\n    labels[:, c] = np.convolve(labels[:, c], gaussian_kernel(offset, sigma), mode='same')\n    \n```",
          "votes": 2
        }
      ]
    },
    {
      "id": 1996759,
      "postDate": "2022-10-20T12:16:15.620Z",
      "content": "<p>Thank you for sharing great solution <a href=\"https://www.kaggle.com/bamps53\" target=\"_blank\">@bamps53</a> !<br>\nI'd like to ask you one thing. What 2.5D model implementation would be like?  If you don't mind, would you give me some hints or references to input multiple images into CNN backbone mentioned above?</p>",
      "rawMarkdown": "Thank you for sharing great solution @bamps53 !\nI'd like to ask you one thing. What 2.5D model implementation would be like?  If you don't mind, would you give me some hints or references to input multiple images into CNN backbone mentioned above?"
    },
    {
      "id": 1995004,
      "postDate": "2022-10-19T12:16:23.873Z",
      "rawMarkdown": "",
      "isDeleted": true
    },
    {
      "id": 1996894,
      "postDate": "2022-10-20T13:36:39.253Z",
      "content": "<p>Great! Thanks for sharing</p>",
      "rawMarkdown": "Great! Thanks for sharing",
      "votes": 1
    },
    {
      "id": 2207795,
      "postDate": "2023-04-03T16:26:36.517Z",
      "content": "<p>Thanks for sharing, its great!</p>",
      "rawMarkdown": "Thanks for sharing, its great!"
    },
    {
      "id": 2002974,
      "postDate": "2022-10-25T07:41:42.837Z",
      "content": "<p>Thank you For Sharing!!</p>",
      "rawMarkdown": "Thank you For Sharing!!"
    },
    {
      "id": 1996901,
      "postDate": "2022-10-20T13:40:13.777Z",
      "content": "<p>Thank you For Sharing!!</p>",
      "rawMarkdown": "Thank you For Sharing!!"
    },
    {
      "id": 1995891,
      "postDate": "2022-10-19T22:28:11.807Z",
      "content": "<p>Thanks for sharing!</p>",
      "rawMarkdown": "Thanks for sharing!"
    },
    {
      "id": 1993321,
      "postDate": "2022-10-18T09:31:23.567Z",
      "content": "<p>Thanks for sharing!</p>",
      "rawMarkdown": "Thanks for sharing!"
    },
    {
      "id": 1990583,
      "postDate": "2022-10-16T16:40:22.057Z",
      "content": "<p>Thanks for sharing dude!</p>",
      "rawMarkdown": "Thanks for sharing dude!\n"
    },
    {
      "id": 1990242,
      "postDate": "2022-10-16T12:15:47.950Z",
      "content": "<p>Thanks for sharing dude!</p>",
      "rawMarkdown": "Thanks for sharing dude!"
    }
  ],
  "complete": true,
  "source": "live",
  "fetched_at": "2026-10-22T08:54:03Z",
  "competition_id": 37244,
  "competition_slug": "dfl-bundesliga-data-shootout",
  "team_id": 8957247,
  "solution_url": "/competitions/dfl-bundesliga-data-shootout/writeups/camaro-public-3rd-place-solution"
}