{
  "id": 417880,
  "title": "49th place solution: ensembles",
  "url": "/competitions/vesuvius-challenge-ink-detection/discussion/417880",
  "author_name": "",
  "post_date": "2023-06-17T16:15:43.735950400Z",
  "votes": 16,
  "comment_count": 5,
  "views": 0,
  "content": "<p>This was our first competition as a team: <a href=\"https://www.kaggle.com/kristoferschobert\" target=\"_blank\">@kristoferschobert</a>, <a href=\"https://www.kaggle.com/awgross\" target=\"_blank\">@awgross</a>, <a href=\"https://www.kaggle.com/mjulianimsj75\" target=\"_blank\">@mjulianimsj75</a>, and myself. We're very happy with the 49th place finish. We even managed to shake up a little from the 65th position on public.</p>\n<p><strong>TL;DR: Large models and validated ensembles built upon the popular 2.5d baseline</strong></p>\n<h3>What didn't work</h3>\n<p>We spent a significant amount of time building our own flexible fastai pipeline from scratch, but we weren't able to achieve the same scores as with our variations on <a href=\"https://www.kaggle.com/tanakar\" target=\"_blank\">@tanakar</a>'s public notebook that we were experimenting with on the side. We used fastai's <code>unet_learner</code>, extended to use <code>timm</code> image models as backbone. We had custom callbacks and a streamlined trained &amp; inference structure already prepared for ensembling. We used a custom dataloader that held the relevant layers of the full fragments in memory and was indexing into them dynamically at training &amp; inference time. Not sure whether it's more efficient than the 2.5d baseline dataloader, but it felt elegant ;-)</p>\n<h3>What kinda worked</h3>\n<p>What those fastai experiments gave us was a scan through all the (relevant) layers/slices of each fragment; showing where most of the signal was located. We used 3 slices per experiment, and stepped through successive starting minimum slices. E.g. the experiment for <code>min slice = 28</code> contained the slices 28, 29, 30. The results are below, and suggested that the top and bottom slices were lacking in detectable ink, which was primarily found in the center slices. There was a gradual increase and decline in signal, and the locations of these ingress &amp; egress were slightly different for each fragment. Using 3 slices per experiment gave us slightly reduced resolution in the z-axis, but made those validation scores somewhat more robust:</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1014468%2F5b0da37195853fc9dd5822eba9603843%2FScreen%20Shot%202023-06-17%20at%2012.09.12.png?generation=1687018176238566&amp;alt=media\" alt=\"\"></p>\n<h3>What worked</h3>\n<p>With an appropriate 2.5 weeks left to go, we decided to pivot fully to using the 2.5d public baseline as the core of our experimentation setup. Here are some things we did slightly differently from other solution write-ups:</p>\n<ul>\n<li><p>We settled on using 8 slices/channels starting from slice 28, partly based on the above depth scan.</p></li>\n<li><p>We focused on large models, most prominently <code>eca_nfnet_l2</code> and <code>maxvit_large</code> models from <code>timm</code> with image sizes 224, 384, and 512 (@awgross).</p></li>\n<li><p>In addition to the <code>segmentation-models-pytorch</code> <code>Unet</code> architecture, we found <code>Linknet</code> to perform even slightly better (@mjulianimsj75).</p></li>\n<li><p>We validated on fragments 1 &amp; 3, which gave us reliable correlations with the public LB (@kristoferschobert).</p></li>\n<li><p>We validated our ensemble selections using the out-of-fold scores for fragments 1 &amp; 3; tuning percentile thresholds simultaneously. Our best thresholds were usually 94%. Our local ensemble scores were also pretty well correlated with the public &amp; private LB.</p></li>\n</ul>\n<p>It was our best ensemble of 6 <code>maxvit</code> models (without the contribution of <code>eca_nfnet</code> or other backbones), which scored consistently highest on local validation, public LB, and private LB; and allowed us to pick our best submission to sneak into the top 50.</p>",
  "messages": [
    {
      "id": "2306837",
      "postDate": "06/17/2023 16:15:43",
      "content": "<p>This was our first competition as a team: <a href=\"https://www.kaggle.com/kristoferschobert\" target=\"_blank\">@kristoferschobert</a>, <a href=\"https://www.kaggle.com/awgross\" target=\"_blank\">@awgross</a>, <a href=\"https://www.kaggle.com/mjulianimsj75\" target=\"_blank\">@mjulianimsj75</a>, and myself. We're very happy with the 49th place finish. We even managed to shake up a little from the 65th position on public.</p>\n<p><strong>TL;DR: Large models and validated ensembles built upon the popular 2.5d baseline</strong></p>\n<h3>What didn't work</h3>\n<p>We spent a significant amount of time building our own flexible fastai pipeline from scratch, but we weren't able to achieve the same scores as with our variations on <a href=\"https://www.kaggle.com/tanakar\" target=\"_blank\">@tanakar</a>'s public notebook that we were experimenting with on the side. We used fastai's <code>unet_learner</code>, extended to use <code>timm</code> image models as backbone. We had custom callbacks and a streamlined trained &amp; inference structure already prepared for ensembling. We used a custom dataloader that held the relevant layers of the full fragments in memory and was indexing into them dynamically at training &amp; inference time. Not sure whether it's more efficient than the 2.5d baseline dataloader, but it felt elegant ;-)</p>\n<h3>What kinda worked</h3>\n<p>What those fastai experiments gave us was a scan through all the (relevant) layers/slices of each fragment; showing where most of the signal was located. We used 3 slices per experiment, and stepped through successive starting minimum slices. E.g. the experiment for <code>min slice = 28</code> contained the slices 28, 29, 30. The results are below, and suggested that the top and bottom slices were lacking in detectable ink, which was primarily found in the center slices. There was a gradual increase and decline in signal, and the locations of these ingress &amp; egress were slightly different for each fragment. Using 3 slices per experiment gave us slightly reduced resolution in the z-axis, but made those validation scores somewhat more robust:</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1014468%2F5b0da37195853fc9dd5822eba9603843%2FScreen%20Shot%202023-06-17%20at%2012.09.12.png?generation=1687018176238566&amp;alt=media\" alt=\"\"></p>\n<h3>What worked</h3>\n<p>With an appropriate 2.5 weeks left to go, we decided to pivot fully to using the 2.5d public baseline as the core of our experimentation setup. Here are some things we did slightly differently from other solution write-ups:</p>\n<ul>\n<li><p>We settled on using 8 slices/channels starting from slice 28, partly based on the above depth scan.</p></li>\n<li><p>We focused on large models, most prominently <code>eca_nfnet_l2</code> and <code>maxvit_large</code> models from <code>timm</code> with image sizes 224, 384, and 512 (@awgross).</p></li>\n<li><p>In addition to the <code>segmentation-models-pytorch</code> <code>Unet</code> architecture, we found <code>Linknet</code> to perform even slightly better (@mjulianimsj75).</p></li>\n<li><p>We validated on fragments 1 &amp; 3, which gave us reliable correlations with the public LB (@kristoferschobert).</p></li>\n<li><p>We validated our ensemble selections using the out-of-fold scores for fragments 1 &amp; 3; tuning percentile thresholds simultaneously. Our best thresholds were usually 94%. Our local ensemble scores were also pretty well correlated with the public &amp; private LB.</p></li>\n</ul>\n<p>It was our best ensemble of 6 <code>maxvit</code> models (without the contribution of <code>eca_nfnet</code> or other backbones), which scored consistently highest on local validation, public LB, and private LB; and allowed us to pick our best submission to sneak into the top 50.</p>",
      "rawMarkdown": "This was our first competition as a team: @kristoferschobert, @awgross, @mjulianimsj75, and myself. We're very happy with the 49th place finish. We even managed to shake up a little from the 65th position on public.\n\n**TL;DR: Large models and validated ensembles built upon the popular 2.5d baseline**\n\n### What didn't work\n\nWe spent a significant amount of time building our own flexible fastai pipeline from scratch, but we weren't able to achieve the same scores as with our variations on @tanakar's public notebook that we were experimenting with on the side. We used fastai's `unet_learner`, extended to use `timm` image models as backbone. We had custom callbacks and a streamlined trained & inference structure already prepared for ensembling. We used a custom dataloader that held the relevant layers of the full fragments in memory and was indexing into them dynamically at training & inference time. Not sure whether it's more efficient than the 2.5d baseline dataloader, but it felt elegant ;-)\n\n\n### What kinda worked\n\nWhat those fastai experiments gave us was a scan through all the (relevant) layers/slices of each fragment; showing where most of the signal was located. We used 3 slices per experiment, and stepped through successive starting minimum slices. E.g. the experiment for `min slice = 28` contained the slices 28, 29, 30. The results are below, and suggested that the top and bottom slices were lacking in detectable ink, which was primarily found in the center slices. There was a gradual increase and decline in signal, and the locations of these ingress & egress were slightly different for each fragment. Using 3 slices per experiment gave us slightly reduced resolution in the z-axis, but made those validation scores somewhat more robust:\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1014468%2F5b0da37195853fc9dd5822eba9603843%2FScreen%20Shot%202023-06-17%20at%2012.09.12.png?generation=1687018176238566&alt=media)\n\n\n### What worked\n\nWith an appropriate 2.5 weeks left to go, we decided to pivot fully to using the 2.5d public baseline as the core of our experimentation setup. Here are some things we did slightly differently from other solution write-ups:\n\n- We settled on using 8 slices/channels starting from slice 28, partly based on the above depth scan.\n\n- We focused on large models, most prominently `eca_nfnet_l2` and `maxvit_large` models from `timm` with image sizes 224, 384, and 512 (@awgross).\n\n- In addition to the `segmentation-models-pytorch` `Unet` architecture, we found `Linknet` to perform even slightly better (@mjulianimsj75).\n\n- We validated on fragments 1 & 3, which gave us reliable correlations with the public LB (@kristoferschobert).\n\n- We validated our ensemble selections using the out-of-fold scores for fragments 1 & 3; tuning percentile thresholds simultaneously. Our best thresholds were usually 94%. Our local ensemble scores were also pretty well correlated with the public & private LB.\n\nIt was our best ensemble of 6 `maxvit` models (without the contribution of `eca_nfnet` or other backbones), which scored consistently highest on local validation, public LB, and private LB; and allowed us to pick our best submission to sneak into the top 50.",
      "votes": null
    },
    {
      "id": "2309803",
      "postDate": "06/19/2023 22:29:40",
      "content": "<p>Congratulations on your silver medal, and thank you for taking the time to write up what worked and what didn't.</p>",
      "rawMarkdown": "Congratulations on your silver medal, and thank you for taking the time to write up what worked and what didn't.",
      "votes": null
    },
    {
      "id": "2310876",
      "postDate": "06/20/2023 17:03:34",
      "content": "<p>Congratulations! 🥳 And thank you for your interesting write-up! Initially I wanted to set something up with fastai as well but then switched to <a href=\"https://www.kaggle.com/tanakar\" target=\"_blank\">@tanakar</a>'s public notebook, just as you did.</p>\n<p>Since you really worked something out with fastai, it would be great, if you would share some code. I would be super interested in exploring what you did!</p>\n<p>Keep it up!</p>",
      "rawMarkdown": "Congratulations! 🥳 And thank you for your interesting write-up! Initially I wanted to set something up with fastai as well but then switched to @tanakar's public notebook, just as you did.\n\nSince you really worked something out with fastai, it would be great, if you would share some code. I would be super interested in exploring what you did!\n\nKeep it up!",
      "votes": null
    },
    {
      "id": "2312491",
      "postDate": "06/22/2023 00:34:45",
      "content": "<p>Thanks! I like to use fastai as a starter baseline because it's usually, well, fast to set up. It's kinda reassuring that other people were struggling with it as well in this competition. I still plan to do more investigating into what went wrong.</p>\n<p>The approach that gave the training scores from the plot above wasn't that complex, after all. Let me try to summarize:</p>\n<p>The trainer is a simple <code>unet_learner</code>, with some changes to enable <code>timm</code> models; following from <a href=\"https://github.com/fastai/fastai/pull/3717\" target=\"_blank\">this issue</a>. The <code>timm</code> tweaks seemed to work well, but we didn't really get to utilizing it because the learner didn't produce good LB models either with or without the tweaks.</p>\n<p>The dataloaders are build around an approach that loads the complete fragments (with the corresponding layers) into RAM as a dictionary; e.g. <code>fragments_all['1']</code> is fragment 1. Then it indexes into them like this:</p>\n<pre><code>def get_img(example, img_size):\n    fragment = example[]\n    x = example[] \n    y = example[]\n    return fragments_all[fragment][x:x+img_size, y:y+img_size, ...]\n</code></pre>\n<p>And the same for <code>get_labels</code> to be used in this datablock:</p>\n<pre><code>dblock = DataBlock(\n    (, mask_block),\n    get_x = partial(, img_size = img_size),\n    get_y = partial(, img_size = img_size),\n    n_inp=1,\n    splitter = ColSplitter(),\n    item_tfms = aug_tfms\n)\n</code></pre>\n<p>Where <code>img_block</code> and <code>mask_block</code> are essentially just <code>TransformBlock</code>s that take and return tensors. Everything is a tensor; no <code>PILImage</code> types involved after creating the <code>fragments_all</code> dictionary entries. It's possible that this is the source of some of the trouble, although when inspecting random batches they always looked good to me. The <code>aug_tfms</code> use an <a href=\"https://docs.fast.ai/tutorial.albumentations.html\" target=\"_blank\">Albumentation wrapper like here</a> and were also in charge of normalization.</p>\n<p>As the datablock suggests, it ingests a dataframe <code>df</code> to create the dataloaders. This dataframe holds the <code>x</code> and <code>y</code> coordinates for each image of img_size x img_size within the fragment. So that the dataloaders are dynamically indexing into the fragment at runtime. Essentially you put a grid like this over your fragment, then index into it:</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1014468%2Fba1db55ea919b4057b90169f41c0074a%2FScreen%20Shot%202023-06-21%20at%2020.24.36.png?generation=1687393519791527&amp;alt=media\" alt=\"\"></p>\n<p>That's pretty much it. We tried different losses, including <code>smp.losses.SoftBCEWithLogitsLoss</code> like in the public baseline, Adam and AdamW optimizers, different learning rates with and without <code>lr_find</code>, dynamic thresholds for final scores, and probably some other things that I'm forgetting right now.</p>\n<p>Hope that helps. Let me know if you have any feedback or question on any of this.</p>",
      "rawMarkdown": "Thanks! I like to use fastai as a starter baseline because it's usually, well, fast to set up. It's kinda reassuring that other people were struggling with it as well in this competition. I still plan to do more investigating into what went wrong.\n\nThe approach that gave the training scores from the plot above wasn't that complex, after all. Let me try to summarize:\n\nThe trainer is a simple `unet_learner`, with some changes to enable `timm` models; following from [this issue](https://github.com/fastai/fastai/pull/3717). The `timm` tweaks seemed to work well, but we didn't really get to utilizing it because the learner didn't produce good LB models either with or without the tweaks.\n\nThe dataloaders are build around an approach that loads the complete fragments (with the corresponding layers) into RAM as a dictionary; e.g. `fragments_all['1']` is fragment 1. Then it indexes into them like this:\n\n```\ndef get_img(example, img_size):\n    fragment = example['fragment']\n    x = example['x'] \n    y = example['y']\n    return fragments_all[fragment][x:x+img_size, y:y+img_size, ...]\n```\n\nAnd the same for `get_labels` to be used in this datablock:\n\n```\ndblock = DataBlock(\n    blocks=(img_block, mask_block),\n    get_x = partial(get_img, img_size = img_size),\n    get_y = partial(get_labels, img_size = img_size),\n    n_inp=1,\n    splitter = ColSplitter(),\n    item_tfms = aug_tfms\n)\n```\n\nWhere `img_block` and `mask_block` are essentially just `TransformBlock`s that take and return tensors. Everything is a tensor; no `PILImage` types involved after creating the `fragments_all` dictionary entries. It's possible that this is the source of some of the trouble, although when inspecting random batches they always looked good to me. The `aug_tfms` use an [Albumentation wrapper like here](https://docs.fast.ai/tutorial.albumentations.html) and were also in charge of normalization.\n\nAs the datablock suggests, it ingests a dataframe `df` to create the dataloaders. This dataframe holds the `x` and `y` coordinates for each image of img_size x img_size within the fragment. So that the dataloaders are dynamically indexing into the fragment at runtime. Essentially you put a grid like this over your fragment, then index into it:\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1014468%2Fba1db55ea919b4057b90169f41c0074a%2FScreen%20Shot%202023-06-21%20at%2020.24.36.png?generation=1687393519791527&alt=media)\n\nThat's pretty much it. We tried different losses, including `smp.losses.SoftBCEWithLogitsLoss` like in the public baseline, Adam and AdamW optimizers, different learning rates with and without `lr_find`, dynamic thresholds for final scores, and probably some other things that I'm forgetting right now.\n\nHope that helps. Let me know if you have any feedback or question on any of this.",
      "votes": null
    },
    {
      "id": "2313935",
      "postDate": "06/23/2023 04:05:24",
      "content": "<p>It helps a lot! Thank you :) I am trying fastai again on <a href=\"https://www.kaggle.com/competitions/google-research-identify-contrails-reduce-global-warming\" target=\"_blank\">https://www.kaggle.com/competitions/google-research-identify-contrails-reduce-global-warming</a> and will share as soon as I've got something running.</p>",
      "rawMarkdown": "It helps a lot! Thank you :) I am trying fastai again on https://www.kaggle.com/competitions/google-research-identify-contrails-reduce-global-warming and will share as soon as I've got something running.",
      "votes": null
    },
    {
      "id": "2342801",
      "postDate": "07/13/2023 06:59:10",
      "content": "<p><a href=\"https://www.kaggle.com/crustacean/train-fastai-baseline\" target=\"_blank\">https://www.kaggle.com/crustacean/train-fastai-baseline</a><br>\n<a href=\"https://www.kaggle.com/crustacean/inference-fastai-baseline\" target=\"_blank\">https://www.kaggle.com/crustacean/inference-fastai-baseline</a></p>",
      "rawMarkdown": "https://www.kaggle.com/crustacean/train-fastai-baseline\nhttps://www.kaggle.com/crustacean/inference-fastai-baseline",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 2309803,
      "author_name": "socated",
      "author_url": "",
      "post_date": "06/19/2023 22:29:40",
      "content": "<p>Congratulations on your silver medal, and thank you for taking the time to write up what worked and what didn't.</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2310876,
      "author_name": "crustacean",
      "author_url": "",
      "post_date": "06/20/2023 17:03:34",
      "content": "<p>Congratulations! 🥳 And thank you for your interesting write-up! Initially I wanted to set something up with fastai as well but then switched to <a href=\"https://www.kaggle.com/tanakar\" target=\"_blank\">@tanakar</a>'s public notebook, just as you did.</p>\n<p>Since you really worked something out with fastai, it would be great, if you would share some code. I would be super interested in exploring what you did!</p>\n<p>Keep it up!</p>",
      "votes": null,
      "replies": [
        {
          "id": 2312491,
          "author_name": "headsortails",
          "author_url": "",
          "post_date": "06/22/2023 00:34:45",
          "content": "<p>Thanks! I like to use fastai as a starter baseline because it's usually, well, fast to set up. It's kinda reassuring that other people were struggling with it as well in this competition. I still plan to do more investigating into what went wrong.</p>\n<p>The approach that gave the training scores from the plot above wasn't that complex, after all. Let me try to summarize:</p>\n<p>The trainer is a simple <code>unet_learner</code>, with some changes to enable <code>timm</code> models; following from <a href=\"https://github.com/fastai/fastai/pull/3717\" target=\"_blank\">this issue</a>. The <code>timm</code> tweaks seemed to work well, but we didn't really get to utilizing it because the learner didn't produce good LB models either with or without the tweaks.</p>\n<p>The dataloaders are build around an approach that loads the complete fragments (with the corresponding layers) into RAM as a dictionary; e.g. <code>fragments_all['1']</code> is fragment 1. Then it indexes into them like this:</p>\n<pre><code>def get_img(example, img_size):\n    fragment = example[]\n    x = example[] \n    y = example[]\n    return fragments_all[fragment][x:x+img_size, y:y+img_size, ...]\n</code></pre>\n<p>And the same for <code>get_labels</code> to be used in this datablock:</p>\n<pre><code>dblock = DataBlock(\n    (, mask_block),\n    get_x = partial(, img_size = img_size),\n    get_y = partial(, img_size = img_size),\n    n_inp=1,\n    splitter = ColSplitter(),\n    item_tfms = aug_tfms\n)\n</code></pre>\n<p>Where <code>img_block</code> and <code>mask_block</code> are essentially just <code>TransformBlock</code>s that take and return tensors. Everything is a tensor; no <code>PILImage</code> types involved after creating the <code>fragments_all</code> dictionary entries. It's possible that this is the source of some of the trouble, although when inspecting random batches they always looked good to me. The <code>aug_tfms</code> use an <a href=\"https://docs.fast.ai/tutorial.albumentations.html\" target=\"_blank\">Albumentation wrapper like here</a> and were also in charge of normalization.</p>\n<p>As the datablock suggests, it ingests a dataframe <code>df</code> to create the dataloaders. This dataframe holds the <code>x</code> and <code>y</code> coordinates for each image of img_size x img_size within the fragment. So that the dataloaders are dynamically indexing into the fragment at runtime. Essentially you put a grid like this over your fragment, then index into it:</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1014468%2Fba1db55ea919b4057b90169f41c0074a%2FScreen%20Shot%202023-06-21%20at%2020.24.36.png?generation=1687393519791527&amp;alt=media\" alt=\"\"></p>\n<p>That's pretty much it. We tried different losses, including <code>smp.losses.SoftBCEWithLogitsLoss</code> like in the public baseline, Adam and AdamW optimizers, different learning rates with and without <code>lr_find</code>, dynamic thresholds for final scores, and probably some other things that I'm forgetting right now.</p>\n<p>Hope that helps. Let me know if you have any feedback or question on any of this.</p>",
          "votes": null,
          "replies": [
            {
              "id": 2313935,
              "author_name": "crustacean",
              "author_url": "",
              "post_date": "06/23/2023 04:05:24",
              "content": "<p>It helps a lot! Thank you :) I am trying fastai again on <a href=\"https://www.kaggle.com/competitions/google-research-identify-contrails-reduce-global-warming\" target=\"_blank\">https://www.kaggle.com/competitions/google-research-identify-contrails-reduce-global-warming</a> and will share as soon as I've got something running.</p>",
              "votes": null,
              "replies": [
                {
                  "id": 2342801,
                  "author_name": "crustacean",
                  "author_url": "",
                  "post_date": "07/13/2023 06:59:10",
                  "content": "<p><a href=\"https://www.kaggle.com/crustacean/train-fastai-baseline\" target=\"_blank\">https://www.kaggle.com/crustacean/train-fastai-baseline</a><br>\n<a href=\"https://www.kaggle.com/crustacean/inference-fastai-baseline\" target=\"_blank\">https://www.kaggle.com/crustacean/inference-fastai-baseline</a></p>",
                  "votes": null,
                  "replies": []
                }
              ]
            }
          ]
        }
      ]
    }
  ],
  "raw_markdown_by_id": {
    "2306837": "This was our first competition as a team: @kristoferschobert, @awgross, @mjulianimsj75, and myself. We're very happy with the 49th place finish. We even managed to shake up a little from the 65th position on public.\n\n**TL;DR: Large models and validated ensembles built upon the popular 2.5d baseline**\n\n### What didn't work\n\nWe spent a significant amount of time building our own flexible fastai pipeline from scratch, but we weren't able to achieve the same scores as with our variations on @tanakar's public notebook that we were experimenting with on the side. We used fastai's `unet_learner`, extended to use `timm` image models as backbone. We had custom callbacks and a streamlined trained & inference structure already prepared for ensembling. We used a custom dataloader that held the relevant layers of the full fragments in memory and was indexing into them dynamically at training & inference time. Not sure whether it's more efficient than the 2.5d baseline dataloader, but it felt elegant ;-)\n\n\n### What kinda worked\n\nWhat those fastai experiments gave us was a scan through all the (relevant) layers/slices of each fragment; showing where most of the signal was located. We used 3 slices per experiment, and stepped through successive starting minimum slices. E.g. the experiment for `min slice = 28` contained the slices 28, 29, 30. The results are below, and suggested that the top and bottom slices were lacking in detectable ink, which was primarily found in the center slices. There was a gradual increase and decline in signal, and the locations of these ingress & egress were slightly different for each fragment. Using 3 slices per experiment gave us slightly reduced resolution in the z-axis, but made those validation scores somewhat more robust:\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1014468%2F5b0da37195853fc9dd5822eba9603843%2FScreen%20Shot%202023-06-17%20at%2012.09.12.png?generation=1687018176238566&alt=media)\n\n\n### What worked\n\nWith an appropriate 2.5 weeks left to go, we decided to pivot fully to using the 2.5d public baseline as the core of our experimentation setup. Here are some things we did slightly differently from other solution write-ups:\n\n- We settled on using 8 slices/channels starting from slice 28, partly based on the above depth scan.\n\n- We focused on large models, most prominently `eca_nfnet_l2` and `maxvit_large` models from `timm` with image sizes 224, 384, and 512 (@awgross).\n\n- In addition to the `segmentation-models-pytorch` `Unet` architecture, we found `Linknet` to perform even slightly better (@mjulianimsj75).\n\n- We validated on fragments 1 & 3, which gave us reliable correlations with the public LB (@kristoferschobert).\n\n- We validated our ensemble selections using the out-of-fold scores for fragments 1 & 3; tuning percentile thresholds simultaneously. Our best thresholds were usually 94%. Our local ensemble scores were also pretty well correlated with the public & private LB.\n\nIt was our best ensemble of 6 `maxvit` models (without the contribution of `eca_nfnet` or other backbones), which scored consistently highest on local validation, public LB, and private LB; and allowed us to pick our best submission to sneak into the top 50.",
    "2309803": "Congratulations on your silver medal, and thank you for taking the time to write up what worked and what didn't.",
    "2310876": "Congratulations! 🥳 And thank you for your interesting write-up! Initially I wanted to set something up with fastai as well but then switched to @tanakar's public notebook, just as you did.\n\nSince you really worked something out with fastai, it would be great, if you would share some code. I would be super interested in exploring what you did!\n\nKeep it up!",
    "2312491": "Thanks! I like to use fastai as a starter baseline because it's usually, well, fast to set up. It's kinda reassuring that other people were struggling with it as well in this competition. I still plan to do more investigating into what went wrong.\n\nThe approach that gave the training scores from the plot above wasn't that complex, after all. Let me try to summarize:\n\nThe trainer is a simple `unet_learner`, with some changes to enable `timm` models; following from [this issue](https://github.com/fastai/fastai/pull/3717). The `timm` tweaks seemed to work well, but we didn't really get to utilizing it because the learner didn't produce good LB models either with or without the tweaks.\n\nThe dataloaders are build around an approach that loads the complete fragments (with the corresponding layers) into RAM as a dictionary; e.g. `fragments_all['1']` is fragment 1. Then it indexes into them like this:\n\n```\ndef get_img(example, img_size):\n    fragment = example['fragment']\n    x = example['x'] \n    y = example['y']\n    return fragments_all[fragment][x:x+img_size, y:y+img_size, ...]\n```\n\nAnd the same for `get_labels` to be used in this datablock:\n\n```\ndblock = DataBlock(\n    blocks=(img_block, mask_block),\n    get_x = partial(get_img, img_size = img_size),\n    get_y = partial(get_labels, img_size = img_size),\n    n_inp=1,\n    splitter = ColSplitter(),\n    item_tfms = aug_tfms\n)\n```\n\nWhere `img_block` and `mask_block` are essentially just `TransformBlock`s that take and return tensors. Everything is a tensor; no `PILImage` types involved after creating the `fragments_all` dictionary entries. It's possible that this is the source of some of the trouble, although when inspecting random batches they always looked good to me. The `aug_tfms` use an [Albumentation wrapper like here](https://docs.fast.ai/tutorial.albumentations.html) and were also in charge of normalization.\n\nAs the datablock suggests, it ingests a dataframe `df` to create the dataloaders. This dataframe holds the `x` and `y` coordinates for each image of img_size x img_size within the fragment. So that the dataloaders are dynamically indexing into the fragment at runtime. Essentially you put a grid like this over your fragment, then index into it:\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1014468%2Fba1db55ea919b4057b90169f41c0074a%2FScreen%20Shot%202023-06-21%20at%2020.24.36.png?generation=1687393519791527&alt=media)\n\nThat's pretty much it. We tried different losses, including `smp.losses.SoftBCEWithLogitsLoss` like in the public baseline, Adam and AdamW optimizers, different learning rates with and without `lr_find`, dynamic thresholds for final scores, and probably some other things that I'm forgetting right now.\n\nHope that helps. Let me know if you have any feedback or question on any of this.",
    "2313935": "It helps a lot! Thank you :) I am trying fastai again on https://www.kaggle.com/competitions/google-research-identify-contrails-reduce-global-warming and will share as soon as I've got something running.",
    "2342801": "https://www.kaggle.com/crustacean/train-fastai-baseline\nhttps://www.kaggle.com/crustacean/inference-fastai-baseline"
  },
  "source": "meta"
}