{
  "id": 679223,
  "title": "92th solution - coded with AI",
  "url": "/competitions/vesuvius-challenge-surface-detection/writeups/not-so-interesting-solution",
  "author_name": "",
  "post_date": "2026-02-28T01:45:03.670Z",
  "votes": 13,
  "comment_count": 10,
  "views": 0,
  "content": "<p>Sharing some bits that may be a bit different from others, who knows. My solution is limited because I discovered two weeks before end that I was using resized images and labels. I had to restart everything. Anyway, here are the main points.</p>\n<ul>\n<li>I use a single 3D unet (mednext large) trained for 1400 epochs. My submission runs in 2.5 hours with two processes using 2 T4. TTA with flips on 3 axis, i.e. 4 predictions, logit averaged.</li>\n<li>head is a single class with sigmoid. I don't get why people use softmax with 2 classes.</li>\n<li>loss function is dice loss per connected component of the labels, and bce loss. For each connected component, I dilate by 2 voxels to get relevant background, and compute dice loss of that. Voxels losses are weighted. Mask weight is 0, background weight is 1, label weights is 2 except for skeleton. I compute a skeleton by eroding labels with 26 connectivity once. Loss weight for pixels in skeleton is 4. I tried other ways to compute skeleton, like skeleton loss, but they were worse. I also tried weight base don surface distance, didn't work as well either.</li>\n<li>post processing. It was clear that the main issue was porous predictions. I worked on that last two days. What was quite effective was to apply a Gaussian filter of sigma 1 to the probabilities before thresholding. This fills small tunnels.</li>\n<li>I spent last 24 hours coding a tangent plane filling. volume is split in small cubes, and for each cube I compute the closest plane to the points in the cube. This is done using PCA on the covariance matrix of the points in the cube. I then fill the plane. This is related to what 4th place did, but less elaborate. I fill a circle in the tangent plan rather than using hole filling as 4th team did. As a result I fill outside the surface and it is detrimental. Still it improves a bit public LB but not private. I wonder how far it could go with a bit more time as I did not tune it at all</li>\n</ul>\n<p>What didn't work:</p>\n<ul>\n<li>ensembling: didn't improve CV, didn't submit. Probably because I ensemble variants of the same model (mednext). </li>\n<li>using curvature based on probabilities (e.g. frangi filter). I tried hard to train a postprocessing model that uses the eigenvalues of hessian of probabilities. I wanted to learn something like the frangi filter. I probably should have just used frangi filter or other edge filters as is.</li>\n<li>topograph loss. This is similar to the supervoxel paper share by host. I got an efficient implementation using cc3d graph, but it did not improve my model.</li>\n<li>focal loss, other skeleton losses, surface loss</li>\n<li>public notebooks postprocessing</li>\n</ul>\n<p>All the code, except high level pipeline, was written with codex or claude code.</p>\n<p>Looking forward to what top teams did. I hope I'll be able to understand.</p>",
  "messages": [
    {
      "id": "3414947",
      "postDate": "02/28/2026 01:39:13",
      "content": "<p>Sharing some bits that may be a bit different from others, who knows. My solution is limited because I discovered two weeks before end that I was using resized images and labels. I had to restart everything. Anyway, here are the main points.</p>\n<ul>\n<li>I use a single 3D unet (mednext large) trained for 1400 epochs. My submission runs in 2.5 hours with two processes using 2 T4. TTA with flips on 3 axis, i.e. 4 predictions, logit averaged.</li>\n<li>head is a single class with sigmoid. I don't get why people use softmax with 2 classes.</li>\n<li>loss function is dice loss per connected component of the labels, and bce loss. For each connected component, I dilate by 2 voxels to get relevant background, and compute dice loss of that. Voxels losses are weighted. Mask weight is 0, background weight is 1, label weights is 2 except for skeleton. I compute a skeleton by eroding labels with 26 connectivity once. Loss weight for pixels in skeleton is 4. I tried other ways to compute skeleton, like skeleton loss, but they were worse. I also tried weight base don surface distance, didn't work as well either.</li>\n<li>post processing. It was clear that the main issue was porous predictions. I worked on that last two days. What was quite effective was to apply a Gaussian filter of sigma 1 to the probabilities before thresholding. This fills small tunnels.</li>\n<li>I spent last 24 hours coding a tangent plane filling. volume is split in small cubes, and for each cube I compute the closest plane to the points in the cube. This is done using PCA on the covariance matrix of the points in the cube. I then fill the plane. This is related to what 4th place did, but less elaborate. I fill a circle in the tangent plan rather than using hole filling as 4th team did. As a result I fill outside the surface and it is detrimental. Still it improves a bit public LB but not private. I wonder how far it could go with a bit more time as I did not tune it at all</li>\n</ul>\n<p>What didn't work:</p>\n<ul>\n<li>ensembling: didn't improve CV, didn't submit. Probably because I ensemble variants of the same model (mednext). </li>\n<li>using curvature based on probabilities (e.g. frangi filter). I tried hard to train a postprocessing model that uses the eigenvalues of hessian of probabilities. I wanted to learn something like the frangi filter. I probably should have just used frangi filter or other edge filters as is.</li>\n<li>topograph loss. This is similar to the supervoxel paper share by host. I got an efficient implementation using cc3d graph, but it did not improve my model.</li>\n<li>focal loss, other skeleton losses, surface loss</li>\n<li>public notebooks postprocessing</li>\n</ul>\n<p>All the code, except high level pipeline, was written with codex or claude code.</p>\n<p>Looking forward to what top teams did. I hope I'll be able to understand.</p>",
      "rawMarkdown": "Sharing some bits that may be a bit different from others, who knows. My solution is limited because I discovered two weeks before end that I was using resized images and labels. I had to restart everything. Anyway, here are the main points.\n\n- I use a single 3D unet (mednext large) trained for 1400 epochs. My submission runs in 2.5 hours with two processes using 2 T4. TTA with flips on 3 axis, i.e. 4 predictions, logit averaged.\n- head is a single class with sigmoid. I don't get why people use softmax with 2 classes.\n- loss function is dice loss per connected component of the labels, and bce loss. For each connected component, I dilate by 2 voxels to get relevant background, and compute dice loss of that. Voxels losses are weighted. Mask weight is 0, background weight is 1, label weights is 2 except for skeleton. I compute a skeleton by eroding labels with 26 connectivity once. Loss weight for pixels in skeleton is 4. I tried other ways to compute skeleton, like skeleton loss, but they were worse. I also tried weight base don surface distance, didn't work as well either.\n- post processing. It was clear that the main issue was porous predictions. I worked on that last two days. What was quite effective was to apply a Gaussian filter of sigma 1 to the probabilities before thresholding. This fills small tunnels.\n- I spent last 24 hours coding a tangent plane filling. volume is split in small cubes, and for each cube I compute the closest plane to the points in the cube. This is done using PCA on the covariance matrix of the points in the cube. I then fill the plane. This is related to what 4th place did, but less elaborate. I fill a circle in the tangent plan rather than using hole filling as 4th team did. As a result I fill outside the surface and it is detrimental. Still it improves a bit public LB but not private. I wonder how far it could go with a bit more time as I did not tune it at all\n\nWhat didn't work:\n- ensembling: didn't improve CV, didn't submit. Probably because I ensemble variants of the same model (mednext). \n- using curvature based on probabilities (e.g. frangi filter). I tried hard to train a postprocessing model that uses the eigenvalues of hessian of probabilities. I wanted to learn something like the frangi filter. I probably should have just used frangi filter or other edge filters as is.\n- topograph loss. This is similar to the supervoxel paper share by host. I got an efficient implementation using cc3d graph, but it did not improve my model.\n- focal loss, other skeleton losses, surface loss\n- public notebooks postprocessing\n\nAll the code, except high level pipeline, was written with codex or claude code.\n\nLooking forward to what top teams did. I hope I'll be able to understand.",
      "votes": null
    },
    {
      "id": "3414954",
      "postDate": "02/28/2026 01:52:07",
      "content": "<p>Did you use AI to write the training/inference notebook for you? \nThe main problem for me is (always) that no good train notebook shared or mentioned in the inference only notebook. </p>",
      "rawMarkdown": "Did you use AI to write the training/inference notebook for you? \nThe main problem for me is (always) that no good train notebook shared or mentioned in the inference only notebook.",
      "votes": null
    },
    {
      "id": "3414956",
      "postDate": "02/28/2026 01:55:23",
      "content": "<p>I used AI to write python functions I call from my notebook. I wrote the notebook code. The training code was simple as I did not use distributed training at all. Just iterate with a dataloader and back propagate loss. Very basic.</p>",
      "rawMarkdown": "I used AI to write python functions I call from my notebook. I wrote the notebook code. The training code was simple as I did not use distributed training at all. Just iterate with a dataloader and back propagate loss. Very basic.",
      "votes": null
    },
    {
      "id": "3414958",
      "postDate": "02/28/2026 01:57:36",
      "content": "<p><a href=\"https://www.kaggle.com/yuanzhezhou\" target=\"_blank\">@yuanzhezhou</a> The top public notebook shared their train notebook <a href=\"https://www.kaggle.com/code/ipythonx/train-vesuvius-surface-3d-detection-on-tpu?scriptVersionId=294535277\" target=\"_blank\">here</a> during the competition. I used this and asked ChatGPT to modify it. (To find the train notebook, we need to find the original infer notebook, then search this Kaggler's code for the train notebook).</p>",
      "rawMarkdown": "yuanzhezhou The top public notebook shared their train notebook [here][1] during the competition. I used this and asked ChatGPT to modify it. (To find the train notebook, we need to find the original infer notebook, then search this Kaggler's code for the train notebook).\n\n[1]: https://www.kaggle.com/code/ipythonx/train-vesuvius-surface-3d-detection-on-tpu?scriptVersionId=294535277",
      "votes": null
    },
    {
      "id": "3414963",
      "postDate": "02/28/2026 02:07:56",
      "content": "<p>Thanks, I just realized that it was shared (from your writeup). I searched for it but gave up, the train notebook does not have a score so it is hard to find it if I sort the notebook by score. </p>",
      "rawMarkdown": "Thanks, I just realized that it was shared (from your writeup). I searched for it but gave up, the train notebook does not have a score so it is hard to find it if I sort the notebook by score.",
      "votes": null
    },
    {
      "id": "3415193",
      "postDate": "02/28/2026 12:13:02",
      "content": "<p>This uses keras and tensorflow, I use pytorch hence started my own code.</p>",
      "rawMarkdown": "This uses keras and tensorflow, I use pytorch hence started my own code.",
      "votes": null
    },
    {
      "id": "3415270",
      "postDate": "02/28/2026 15:09:02",
      "content": "<p>With sigmoid how are you handling the ignore label?\nI belive when using softmax we can cast the ignore label as a [0,0] vector </p>",
      "rawMarkdown": "With sigmoid how are you handling the ignore label?\nI belive when using softmax we can cast the ignore label as a [0,0] vector",
      "votes": null
    },
    {
      "id": "3415301",
      "postDate": "02/28/2026 16:39:54",
      "content": "<p>I weight each voxel loss. The weight for masked voxels is zero.</p>",
      "rawMarkdown": "I weight each voxel loss. The weight for masked voxels is zero.",
      "votes": null
    },
    {
      "id": "3415402",
      "postDate": "02/28/2026 20:57:09",
      "content": "<p>Simply convert a public inference notebook to PDF, and Claude Code will automatically generate a full training codebase and experimental setup tailored to your idea, without you having to write a single line of code. (The fact that it can generate python code to play around tif files is insane)</p>",
      "rawMarkdown": "Simply convert a public inference notebook to PDF, and Claude Code will automatically generate a full training codebase and experimental setup tailored to your idea, without you having to write a single line of code. (The fact that it can generate python code to play around tif files is insane)",
      "votes": null
    },
    {
      "id": "3415976",
      "postDate": "03/01/2026 18:29:39",
      "content": "<p><a href=\"https://www.kaggle.com/cpmpml\" target=\"_blank\">@cpmpml</a> \nIf you look at Keras 3, it supports a <code>torch</code> backend, allowing you to run everything, from the torch-dataloader to custom training loop using pure PyTorch under the hood. In that sense, it feels similar to PyTorch Lightning, providing a higher-level API while still leveraging the PyTorch ecosystem. You don't need to work with TensorFlow.</p>\n<p>Just for the reference, <a href=\"https://www.kaggle.com/code/ipythonx/train-vesuvius-surface-3d-detection-in-pytorch\" target=\"_blank\">code1</a>, <a href=\"https://www.kaggle.com/code/cdeotte/train-bronze-medal-uunet-by-chatgpt\" target=\"_blank\">code2</a>, <a href=\"https://keras.io/guides/writing_a_custom_training_loop_in_torch/\" target=\"_blank\">official-code1</a></p>",
      "rawMarkdown": "cpmpml \nIf you look at Keras 3, it supports a `torch` backend, allowing you to run everything, from the torch-dataloader to custom training loop using pure PyTorch under the hood. In that sense, it feels similar to PyTorch Lightning, providing a higher-level API while still leveraging the PyTorch ecosystem. You don't need to work with TensorFlow.\n\nJust for the reference, [code1](https://www.kaggle.com/code/ipythonx/train-vesuvius-surface-3d-detection-in-pytorch), [code2](https://www.kaggle.com/code/cdeotte/train-bronze-medal-uunet-by-chatgpt), [official-code1](https://keras.io/guides/writing_a_custom_training_loop_in_torch/)",
      "votes": null
    },
    {
      "id": "3416009",
      "postDate": "03/01/2026 19:55:29",
      "content": "<p>I know. APi is still keras, with its limits.</p>",
      "rawMarkdown": "I know. APi is still keras, with its limits.",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 3414954,
      "author_name": "yuanzhezhou",
      "author_url": "",
      "post_date": "02/28/2026 01:52:07",
      "content": "<p>Did you use AI to write the training/inference notebook for you? \nThe main problem for me is (always) that no good train notebook shared or mentioned in the inference only notebook. </p>",
      "votes": null,
      "replies": [
        {
          "id": 3414956,
          "author_name": "cpmpml",
          "author_url": "",
          "post_date": "02/28/2026 01:55:23",
          "content": "<p>I used AI to write python functions I call from my notebook. I wrote the notebook code. The training code was simple as I did not use distributed training at all. Just iterate with a dataloader and back propagate loss. Very basic.</p>",
          "votes": null,
          "replies": []
        },
        {
          "id": 3414958,
          "author_name": "cdeotte",
          "author_url": "",
          "post_date": "02/28/2026 01:57:36",
          "content": "<p><a href=\"https://www.kaggle.com/yuanzhezhou\" target=\"_blank\">@yuanzhezhou</a> The top public notebook shared their train notebook <a href=\"https://www.kaggle.com/code/ipythonx/train-vesuvius-surface-3d-detection-on-tpu?scriptVersionId=294535277\" target=\"_blank\">here</a> during the competition. I used this and asked ChatGPT to modify it. (To find the train notebook, we need to find the original infer notebook, then search this Kaggler's code for the train notebook).</p>",
          "votes": null,
          "replies": [
            {
              "id": 3414963,
              "author_name": "yuanzhezhou",
              "author_url": "",
              "post_date": "02/28/2026 02:07:56",
              "content": "<p>Thanks, I just realized that it was shared (from your writeup). I searched for it but gave up, the train notebook does not have a score so it is hard to find it if I sort the notebook by score. </p>",
              "votes": null,
              "replies": []
            },
            {
              "id": 3415193,
              "author_name": "cpmpml",
              "author_url": "",
              "post_date": "02/28/2026 12:13:02",
              "content": "<p>This uses keras and tensorflow, I use pytorch hence started my own code.</p>",
              "votes": null,
              "replies": []
            },
            {
              "id": 3415976,
              "author_name": "ipythonx",
              "author_url": "",
              "post_date": "03/01/2026 18:29:39",
              "content": "<p><a href=\"https://www.kaggle.com/cpmpml\" target=\"_blank\">@cpmpml</a> \nIf you look at Keras 3, it supports a <code>torch</code> backend, allowing you to run everything, from the torch-dataloader to custom training loop using pure PyTorch under the hood. In that sense, it feels similar to PyTorch Lightning, providing a higher-level API while still leveraging the PyTorch ecosystem. You don't need to work with TensorFlow.</p>\n<p>Just for the reference, <a href=\"https://www.kaggle.com/code/ipythonx/train-vesuvius-surface-3d-detection-in-pytorch\" target=\"_blank\">code1</a>, <a href=\"https://www.kaggle.com/code/cdeotte/train-bronze-medal-uunet-by-chatgpt\" target=\"_blank\">code2</a>, <a href=\"https://keras.io/guides/writing_a_custom_training_loop_in_torch/\" target=\"_blank\">official-code1</a></p>",
              "votes": null,
              "replies": [
                {
                  "id": 3416009,
                  "author_name": "cpmpml",
                  "author_url": "",
                  "post_date": "03/01/2026 19:55:29",
                  "content": "<p>I know. APi is still keras, with its limits.</p>",
                  "votes": null,
                  "replies": []
                }
              ]
            }
          ]
        },
        {
          "id": 3415402,
          "author_name": "tom99763",
          "author_url": "",
          "post_date": "02/28/2026 20:57:09",
          "content": "<p>Simply convert a public inference notebook to PDF, and Claude Code will automatically generate a full training codebase and experimental setup tailored to your idea, without you having to write a single line of code. (The fact that it can generate python code to play around tif files is insane)</p>",
          "votes": null,
          "replies": []
        }
      ]
    },
    {
      "id": 3415270,
      "author_name": "arjunashokbhandary",
      "author_url": "",
      "post_date": "02/28/2026 15:09:02",
      "content": "<p>With sigmoid how are you handling the ignore label?\nI belive when using softmax we can cast the ignore label as a [0,0] vector </p>",
      "votes": null,
      "replies": [
        {
          "id": 3415301,
          "author_name": "cpmpml",
          "author_url": "",
          "post_date": "02/28/2026 16:39:54",
          "content": "<p>I weight each voxel loss. The weight for masked voxels is zero.</p>",
          "votes": null,
          "replies": []
        }
      ]
    }
  ],
  "raw_markdown_by_id": {
    "3414947": "Sharing some bits that may be a bit different from others, who knows. My solution is limited because I discovered two weeks before end that I was using resized images and labels. I had to restart everything. Anyway, here are the main points.\n\n- I use a single 3D unet (mednext large) trained for 1400 epochs. My submission runs in 2.5 hours with two processes using 2 T4. TTA with flips on 3 axis, i.e. 4 predictions, logit averaged.\n- head is a single class with sigmoid. I don't get why people use softmax with 2 classes.\n- loss function is dice loss per connected component of the labels, and bce loss. For each connected component, I dilate by 2 voxels to get relevant background, and compute dice loss of that. Voxels losses are weighted. Mask weight is 0, background weight is 1, label weights is 2 except for skeleton. I compute a skeleton by eroding labels with 26 connectivity once. Loss weight for pixels in skeleton is 4. I tried other ways to compute skeleton, like skeleton loss, but they were worse. I also tried weight base don surface distance, didn't work as well either.\n- post processing. It was clear that the main issue was porous predictions. I worked on that last two days. What was quite effective was to apply a Gaussian filter of sigma 1 to the probabilities before thresholding. This fills small tunnels.\n- I spent last 24 hours coding a tangent plane filling. volume is split in small cubes, and for each cube I compute the closest plane to the points in the cube. This is done using PCA on the covariance matrix of the points in the cube. I then fill the plane. This is related to what 4th place did, but less elaborate. I fill a circle in the tangent plan rather than using hole filling as 4th team did. As a result I fill outside the surface and it is detrimental. Still it improves a bit public LB but not private. I wonder how far it could go with a bit more time as I did not tune it at all\n\nWhat didn't work:\n- ensembling: didn't improve CV, didn't submit. Probably because I ensemble variants of the same model (mednext). \n- using curvature based on probabilities (e.g. frangi filter). I tried hard to train a postprocessing model that uses the eigenvalues of hessian of probabilities. I wanted to learn something like the frangi filter. I probably should have just used frangi filter or other edge filters as is.\n- topograph loss. This is similar to the supervoxel paper share by host. I got an efficient implementation using cc3d graph, but it did not improve my model.\n- focal loss, other skeleton losses, surface loss\n- public notebooks postprocessing\n\nAll the code, except high level pipeline, was written with codex or claude code.\n\nLooking forward to what top teams did. I hope I'll be able to understand.",
    "3414954": "Did you use AI to write the training/inference notebook for you? \nThe main problem for me is (always) that no good train notebook shared or mentioned in the inference only notebook.",
    "3414956": "I used AI to write python functions I call from my notebook. I wrote the notebook code. The training code was simple as I did not use distributed training at all. Just iterate with a dataloader and back propagate loss. Very basic.",
    "3414958": "yuanzhezhou The top public notebook shared their train notebook [here][1] during the competition. I used this and asked ChatGPT to modify it. (To find the train notebook, we need to find the original infer notebook, then search this Kaggler's code for the train notebook).\n\n[1]: https://www.kaggle.com/code/ipythonx/train-vesuvius-surface-3d-detection-on-tpu?scriptVersionId=294535277",
    "3414963": "Thanks, I just realized that it was shared (from your writeup). I searched for it but gave up, the train notebook does not have a score so it is hard to find it if I sort the notebook by score.",
    "3415193": "This uses keras and tensorflow, I use pytorch hence started my own code.",
    "3415270": "With sigmoid how are you handling the ignore label?\nI belive when using softmax we can cast the ignore label as a [0,0] vector",
    "3415301": "I weight each voxel loss. The weight for masked voxels is zero.",
    "3415402": "Simply convert a public inference notebook to PDF, and Claude Code will automatically generate a full training codebase and experimental setup tailored to your idea, without you having to write a single line of code. (The fact that it can generate python code to play around tif files is insane)",
    "3415976": "cpmpml \nIf you look at Keras 3, it supports a `torch` backend, allowing you to run everything, from the torch-dataloader to custom training loop using pure PyTorch under the hood. In that sense, it feels similar to PyTorch Lightning, providing a higher-level API while still leveraging the PyTorch ecosystem. You don't need to work with TensorFlow.\n\nJust for the reference, [code1](https://www.kaggle.com/code/ipythonx/train-vesuvius-surface-3d-detection-in-pytorch), [code2](https://www.kaggle.com/code/cdeotte/train-bronze-medal-uunet-by-chatgpt), [official-code1](https://keras.io/guides/writing_a_custom_training_loop_in_torch/)",
    "3416009": "I know. APi is still keras, with its limits."
  },
  "source": "meta"
}