{
  "id": 418420,
  "title": "3rd place Solution - Matcha & Object Detection",
  "url": "/competitions/benetech-making-graphs-accessible/writeups/camembert-3rd-place-solution-matcha-object-detecti",
  "author_name": "",
  "post_date": "2023-06-20T14:15:42.020226800Z",
  "votes": 54,
  "comment_count": 20,
  "views": 0,
  "content": "<p>First of all, congrats to all the participants especially to those that managed to stick it to the end. This was overall a really interesting challenge, and we really enjoyed tackling it. Anyone in 0.86+ public scores could've taken the win so we're glad we dropped only 2 spots.</p>\n<p>Thanks to <a href=\"https://www.kaggle.com/crodoc\" target=\"_blank\">@crodoc</a> for the great teaming experience, we had two different opinions on how to tackle the problem : he went for and end-to-end approach whereas I wanted to do object detection &amp; OCR. This proved to be ideal for this competition as object detection compensated for end-to-end weaknesses.</p>\n<h2>Overview</h2>\n<p><a href=\"https://ibb.co/qFXF9vq\"><img src=\"https://i.ibb.co/84T42Hv/cls.png\" alt=\"cls\"></a></p>\n<p>Our solution is a two-step pipeline, where the first step is a simple classification task and the second step solves the task for the different chart types. For scatter and dot plots, we used a detection approach. For line and bar charts, Matcha was really strong. Here are our LB scores :</p>\n<table>\n<thead>\n<tr>\n<th></th>\n<th>Overall</th>\n<th>Scatter</th>\n<th>Dots</th>\n<th>Line</th>\n<th>Horizontal Bar</th>\n<th>Vertical Bar</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Public</strong></td>\n<td>0.87</td>\n<td>0.09</td>\n<td>0</td>\n<td>0.33</td>\n<td>0.04</td>\n<td>0.39</td>\n</tr>\n<tr>\n<td><strong>Private</strong></td>\n<td>0.71</td>\n<td>0.28</td>\n<td>0.01</td>\n<td>0.13</td>\n<td>0.01</td>\n<td>0.27</td>\n</tr>\n</tbody>\n</table>\n<h2>Validation &amp; Insights</h2>\n<p>From the description page : </p>\n<blockquote>\n  <p>The extracted figures in the training and public test sets are drawn from the same set of sources. The figures in the private test set are drawn from a distinct set of sources.</p>\n</blockquote>\n<p>Public LB is leaky, we need to create models that generalize to new data sources. Therefore, we use all the extracted data as validation, and train only on generated data. Data provided by the hosts is not diverse enough to allow for generalization so we generated our own. Matcha and classification models were fullfit on all the data after careful parameter optimization, but detection models were not trained on any extracted data.</p>\n<p>For validating on dots, we used a small curated dataset of 20 images found on google images. We estimated via probing that there are between 100 and 125 dots in the test set, all of them most likely being in the private test set. We did not probe for scatter though, this would've been helpful. </p>\n<h2>Step 1 - Classification</h2>\n<p>Nothing too fancy here, we trained models on (benetech + theo +  crodoc) generated data.</p>\n<p><strong>Main parameters:</strong></p>\n<ul>\n<li>2 epochs on 88k images. </li>\n<li>lr <code>3e-4</code> or <code>5e-4</code>  (2 lrs for blend, we also used 2 seeds).</li>\n<li>Mixup and some color augmentations mostly.</li>\n<li>256x384 image size.</li>\n<li>NfNet-l2 with 0.2 dropout.</li>\n</ul>\n<h2>Step 2.a - Scatter</h2>\n<p><a href=\"https://ibb.co/5v4cM9C\"><img src=\"https://i.ibb.co/ct3TgvR/scatter-pipe.png\" alt=\"scatter-pipe\"></a></p>\n<p>For scatter, we relied on <a href=\"https://github.com/Megvii-BaseDetection/YOLOX\" target=\"_blank\">YoloX</a> to detect all markers. Cached is used to take care of the other useful elements. If all the points are detected correctly, it’s not that complicated to infer the target : detect the ticks &amp; labels, read the labels, and interpolate !</p>\n<p><strong>More details:</strong></p>\n<ul>\n<li>Ensemble YoloX-m and YoloX-l with NMS, helps reduce the number of FNs.</li>\n<li>Models are trained on scatter + dot generated benetech data + a bunch of plots I generated and pseudo labeled, for 10 epochs.</li>\n<li>CV 0.67, public LB 0.09~, private 0.29 - performance drop almost exclusively comes from overlapping/too hard to detect markers.</li>\n<li>Bunch of post-processing to make the pipeline more robust to OCR mistakes and detection mistakes.</li>\n<li>We were initially using Yolo-v7, but had to switch to YoloX because of the first rule change. It took us a week to match Yolo-v7 performance with YoloX.</li>\n</ul>\n<h2>Step 2.b - Dots</h2>\n<p><a href=\"https://ibb.co/sgMLzxG\"><img src=\"https://i.ibb.co/TwFzdjS/dot-pipe.png\" alt=\"dot-pipe\"></a></p>\n<p>Dot pipeline is similar to scatter, but a bit more simple. We detect the points and cluster them, and map them to the detected x-labels. Labels with no assigned cluster are given the target 0, others the number of detected points. Counting points is not really robust to detection mistakes, so instead we used the height of the uppermost point and interpolated.</p>\n<h2>Step 2.c - Bars &amp; Lines</h2>\n<p>Matcha was really powerful here. We used <code>matcha-base</code> and set <code>is_vqa=False</code> to avoid giving texts as input to the model.</p>\n<p>We trained Matcha to predict chart type, xs and ys for an image. The ground truth looked the same as <a href=\"https://www.kaggle.com/nbroad\" target=\"_blank\">@nbroad</a> used for his donut approach (except we dropped the prompt token). We tried other approaches, but this worked best :</p>\n<pre><code> = X_START + .join(list(map(str, xs))) + X_END\n = Y_START + .join(list(map(str, ys))) + Y_END\n =  + chart_type +  + x_str + y_str\n</code></pre>\n<p>e.g: <code>&lt;line&gt;&lt;x_start&gt;0;2;4;6&lt;x_end&gt;&lt;y_start&gt;2.7;2.2;3.6;5.2;&lt;y_end&gt;</code></p>\n<p>The most valuable boost we got by generating additional charts using matplotlib. We reused the values and texts from the train dataset to generate ticks &amp; values, with different styles/patterns/fonts/colors for diversity. The code for generating additional images has around 1000 lines and basically covers most cases where the model was failing when validated on the \"extracted\" dataset (e.g. negative values, line edges, missing bars, multiline text, text rotations).</p>\n<p><strong>Other things that helped :</strong></p>\n<ul>\n<li>Fix the number of decimal points for numerical values using their range :<br>\n<code>number_of_decimals = max(0, round(np.log10(1/ (max(y_ticks) - min(y_ticks))) + 3)</code></li>\n<li>We use an additional chart type : histograms, to learn that such charts have one less y value. </li>\n<li>Add an additional cross entropy loss for chart types.</li>\n<li>Ensemble several (4) models  (+0.01 public) :<ul>\n<li>Voting to compute the number of outputs and fix obvious mistakes.</li>\n<li>Voting for categorical predictions.</li>\n<li>Averaging for continuous predictions.</li></ul></li>\n</ul>\n<p><strong>More details:</strong></p>\n<ul>\n<li>lr 3e-5, cosine with 0.25 cycle.</li>\n<li>10 Epochs.</li>\n<li>Save weights every 0.25 epochs and use a model soup of all checkpoints from epoch &gt; 1.</li>\n<li>Augs : Color transforms, image compression and random scaling.</li>\n</ul>\n<h2>Final Words</h2>\n<p>We were greatly perturbed by the unexpected second rule change, especially considering the fact that I was on vacation. Matcha and classification models were retrained on ICDAR during the last 3 days, this gave a 0.01 public LB boost but nothing on private. </p>\n<p>Thanks for reading !</p>",
  "messages": [
    {
      "id": "2310638",
      "postDate": "06/20/2023 14:15:42",
      "content": "<p>First of all, congrats to all the participants especially to those that managed to stick it to the end. This was overall a really interesting challenge, and we really enjoyed tackling it. Anyone in 0.86+ public scores could've taken the win so we're glad we dropped only 2 spots.</p>\n<p>Thanks to <a href=\"https://www.kaggle.com/crodoc\" target=\"_blank\">@crodoc</a> for the great teaming experience, we had two different opinions on how to tackle the problem : he went for and end-to-end approach whereas I wanted to do object detection &amp; OCR. This proved to be ideal for this competition as object detection compensated for end-to-end weaknesses.</p>\n<h2>Overview</h2>\n<p><a href=\"https://ibb.co/qFXF9vq\"><img src=\"https://i.ibb.co/84T42Hv/cls.png\" alt=\"cls\"></a></p>\n<p>Our solution is a two-step pipeline, where the first step is a simple classification task and the second step solves the task for the different chart types. For scatter and dot plots, we used a detection approach. For line and bar charts, Matcha was really strong. Here are our LB scores :</p>\n<table>\n<thead>\n<tr>\n<th></th>\n<th>Overall</th>\n<th>Scatter</th>\n<th>Dots</th>\n<th>Line</th>\n<th>Horizontal Bar</th>\n<th>Vertical Bar</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Public</strong></td>\n<td>0.87</td>\n<td>0.09</td>\n<td>0</td>\n<td>0.33</td>\n<td>0.04</td>\n<td>0.39</td>\n</tr>\n<tr>\n<td><strong>Private</strong></td>\n<td>0.71</td>\n<td>0.28</td>\n<td>0.01</td>\n<td>0.13</td>\n<td>0.01</td>\n<td>0.27</td>\n</tr>\n</tbody>\n</table>\n<h2>Validation &amp; Insights</h2>\n<p>From the description page : </p>\n<blockquote>\n  <p>The extracted figures in the training and public test sets are drawn from the same set of sources. The figures in the private test set are drawn from a distinct set of sources.</p>\n</blockquote>\n<p>Public LB is leaky, we need to create models that generalize to new data sources. Therefore, we use all the extracted data as validation, and train only on generated data. Data provided by the hosts is not diverse enough to allow for generalization so we generated our own. Matcha and classification models were fullfit on all the data after careful parameter optimization, but detection models were not trained on any extracted data.</p>\n<p>For validating on dots, we used a small curated dataset of 20 images found on google images. We estimated via probing that there are between 100 and 125 dots in the test set, all of them most likely being in the private test set. We did not probe for scatter though, this would've been helpful. </p>\n<h2>Step 1 - Classification</h2>\n<p>Nothing too fancy here, we trained models on (benetech + theo +  crodoc) generated data.</p>\n<p><strong>Main parameters:</strong></p>\n<ul>\n<li>2 epochs on 88k images. </li>\n<li>lr <code>3e-4</code> or <code>5e-4</code>  (2 lrs for blend, we also used 2 seeds).</li>\n<li>Mixup and some color augmentations mostly.</li>\n<li>256x384 image size.</li>\n<li>NfNet-l2 with 0.2 dropout.</li>\n</ul>\n<h2>Step 2.a - Scatter</h2>\n<p><a href=\"https://ibb.co/5v4cM9C\"><img src=\"https://i.ibb.co/ct3TgvR/scatter-pipe.png\" alt=\"scatter-pipe\"></a></p>\n<p>For scatter, we relied on <a href=\"https://github.com/Megvii-BaseDetection/YOLOX\" target=\"_blank\">YoloX</a> to detect all markers. Cached is used to take care of the other useful elements. If all the points are detected correctly, it’s not that complicated to infer the target : detect the ticks &amp; labels, read the labels, and interpolate !</p>\n<p><strong>More details:</strong></p>\n<ul>\n<li>Ensemble YoloX-m and YoloX-l with NMS, helps reduce the number of FNs.</li>\n<li>Models are trained on scatter + dot generated benetech data + a bunch of plots I generated and pseudo labeled, for 10 epochs.</li>\n<li>CV 0.67, public LB 0.09~, private 0.29 - performance drop almost exclusively comes from overlapping/too hard to detect markers.</li>\n<li>Bunch of post-processing to make the pipeline more robust to OCR mistakes and detection mistakes.</li>\n<li>We were initially using Yolo-v7, but had to switch to YoloX because of the first rule change. It took us a week to match Yolo-v7 performance with YoloX.</li>\n</ul>\n<h2>Step 2.b - Dots</h2>\n<p><a href=\"https://ibb.co/sgMLzxG\"><img src=\"https://i.ibb.co/TwFzdjS/dot-pipe.png\" alt=\"dot-pipe\"></a></p>\n<p>Dot pipeline is similar to scatter, but a bit more simple. We detect the points and cluster them, and map them to the detected x-labels. Labels with no assigned cluster are given the target 0, others the number of detected points. Counting points is not really robust to detection mistakes, so instead we used the height of the uppermost point and interpolated.</p>\n<h2>Step 2.c - Bars &amp; Lines</h2>\n<p>Matcha was really powerful here. We used <code>matcha-base</code> and set <code>is_vqa=False</code> to avoid giving texts as input to the model.</p>\n<p>We trained Matcha to predict chart type, xs and ys for an image. The ground truth looked the same as <a href=\"https://www.kaggle.com/nbroad\" target=\"_blank\">@nbroad</a> used for his donut approach (except we dropped the prompt token). We tried other approaches, but this worked best :</p>\n<pre><code> = X_START + .join(list(map(str, xs))) + X_END\n = Y_START + .join(list(map(str, ys))) + Y_END\n =  + chart_type +  + x_str + y_str\n</code></pre>\n<p>e.g: <code>&lt;line&gt;&lt;x_start&gt;0;2;4;6&lt;x_end&gt;&lt;y_start&gt;2.7;2.2;3.6;5.2;&lt;y_end&gt;</code></p>\n<p>The most valuable boost we got by generating additional charts using matplotlib. We reused the values and texts from the train dataset to generate ticks &amp; values, with different styles/patterns/fonts/colors for diversity. The code for generating additional images has around 1000 lines and basically covers most cases where the model was failing when validated on the \"extracted\" dataset (e.g. negative values, line edges, missing bars, multiline text, text rotations).</p>\n<p><strong>Other things that helped :</strong></p>\n<ul>\n<li>Fix the number of decimal points for numerical values using their range :<br>\n<code>number_of_decimals = max(0, round(np.log10(1/ (max(y_ticks) - min(y_ticks))) + 3)</code></li>\n<li>We use an additional chart type : histograms, to learn that such charts have one less y value. </li>\n<li>Add an additional cross entropy loss for chart types.</li>\n<li>Ensemble several (4) models  (+0.01 public) :<ul>\n<li>Voting to compute the number of outputs and fix obvious mistakes.</li>\n<li>Voting for categorical predictions.</li>\n<li>Averaging for continuous predictions.</li></ul></li>\n</ul>\n<p><strong>More details:</strong></p>\n<ul>\n<li>lr 3e-5, cosine with 0.25 cycle.</li>\n<li>10 Epochs.</li>\n<li>Save weights every 0.25 epochs and use a model soup of all checkpoints from epoch &gt; 1.</li>\n<li>Augs : Color transforms, image compression and random scaling.</li>\n</ul>\n<h2>Final Words</h2>\n<p>We were greatly perturbed by the unexpected second rule change, especially considering the fact that I was on vacation. Matcha and classification models were retrained on ICDAR during the last 3 days, this gave a 0.01 public LB boost but nothing on private. </p>\n<p>Thanks for reading !</p>",
      "rawMarkdown": "First of all, congrats to all the participants especially to those that managed to stick it to the end. This was overall a really interesting challenge, and we really enjoyed tackling it. Anyone in 0.86+ public scores could've taken the win so we're glad we dropped only 2 spots.\n\nThanks to @crodoc for the great teaming experience, we had two different opinions on how to tackle the problem : he went for and end-to-end approach whereas I wanted to do object detection & OCR. This proved to be ideal for this competition as object detection compensated for end-to-end weaknesses.\n\n## Overview\n\n<a href=\"https://ibb.co/qFXF9vq\"><img src=\"https://i.ibb.co/84T42Hv/cls.png\" alt=\"cls\" border=\"0\"></a>\n\nOur solution is a two-step pipeline, where the first step is a simple classification task and the second step solves the task for the different chart types. For scatter and dot plots, we used a detection approach. For line and bar charts, Matcha was really strong. Here are our LB scores :\n\n| |  Overall | Scatter | Dots | Line | Horizontal Bar | Vertical Bar|\n| --- |\n| **Public** | 0.87 | 0.09 | 0| 0.33 | 0.04| 0.39|\n| **Private** | 0.71 | 0.28 | 0.01| 0.13 | 0.01 | 0.27 |\n\n## Validation & Insights\n\nFrom the description page : \n> The extracted figures in the training and public test sets are drawn from the same set of sources. The figures in the private test set are drawn from a distinct set of sources.\n\nPublic LB is leaky, we need to create models that generalize to new data sources. Therefore, we use all the extracted data as validation, and train only on generated data. Data provided by the hosts is not diverse enough to allow for generalization so we generated our own. Matcha and classification models were fullfit on all the data after careful parameter optimization, but detection models were not trained on any extracted data.\n\nFor validating on dots, we used a small curated dataset of 20 images found on google images. We estimated via probing that there are between 100 and 125 dots in the test set, all of them most likely being in the private test set. We did not probe for scatter though, this would've been helpful. \n\n## Step 1 - Classification\n\nNothing too fancy here, we trained models on (benetech + theo +  crodoc) generated data.\n\n**Main parameters:**\n- 2 epochs on 88k images. \n- lr `3e-4` or `5e-4`  (2 lrs for blend, we also used 2 seeds).\n- Mixup and some color augmentations mostly.\n- 256x384 image size.\n- NfNet-l2 with 0.2 dropout.\n\n## Step 2.a - Scatter\n\n<a href=\"https://ibb.co/5v4cM9C\"><img src=\"https://i.ibb.co/ct3TgvR/scatter-pipe.png\" alt=\"scatter-pipe\" border=\"0\"></a>\n\nFor scatter, we relied on [YoloX](https://github.com/Megvii-BaseDetection/YOLOX) to detect all markers. Cached is used to take care of the other useful elements. If all the points are detected correctly, it’s not that complicated to infer the target : detect the ticks & labels, read the labels, and interpolate !\n\n**More details:**\n- Ensemble YoloX-m and YoloX-l with NMS, helps reduce the number of FNs.\n- Models are trained on scatter + dot generated benetech data + a bunch of plots I generated and pseudo labeled, for 10 epochs.\n- CV 0.67, public LB 0.09~, private 0.29 - performance drop almost exclusively comes from overlapping/too hard to detect markers.\n- Bunch of post-processing to make the pipeline more robust to OCR mistakes and detection mistakes.\n- We were initially using Yolo-v7, but had to switch to YoloX because of the first rule change. It took us a week to match Yolo-v7 performance with YoloX.\n\n## Step 2.b - Dots\n\n<a href=\"https://ibb.co/sgMLzxG\"><img src=\"https://i.ibb.co/TwFzdjS/dot-pipe.png\" alt=\"dot-pipe\" border=\"0\"></a>\n\nDot pipeline is similar to scatter, but a bit more simple. We detect the points and cluster them, and map them to the detected x-labels. Labels with no assigned cluster are given the target 0, others the number of detected points. Counting points is not really robust to detection mistakes, so instead we used the height of the uppermost point and interpolated.\n\n## Step 2.c - Bars & Lines\n\nMatcha was really powerful here. We used `matcha-base` and set `is_vqa=False` to avoid giving texts as input to the model.\n\nWe trained Matcha to predict chart type, xs and ys for an image. The ground truth looked the same as @nbroad used for his donut approach (except we dropped the prompt token). We tried other approaches, but this worked best :\n```\nx_str = X_START + \";\".join(list(map(str, xs))) + X_END\ny_str = Y_START + \";\".join(list(map(str, ys))) + Y_END\nground_truth = '<' + chart_type + '>' + x_str + y_str\n```\ne.g: `<line><x_start>0;2;4;6<x_end><y_start>2.7;2.2;3.6;5.2;<y_end>`\n\nThe most valuable boost we got by generating additional charts using matplotlib. We reused the values and texts from the train dataset to generate ticks & values, with different styles/patterns/fonts/colors for diversity. The code for generating additional images has around 1000 lines and basically covers most cases where the model was failing when validated on the \"extracted\" dataset (e.g. negative values, line edges, missing bars, multiline text, text rotations).\n\n**Other things that helped :**\n- Fix the number of decimal points for numerical values using their range :\n`number_of_decimals = max(0, round(np.log10(1/ (max(y_ticks) - min(y_ticks))) + 3)`\n- We use an additional chart type : histograms, to learn that such charts have one less y value. \n- Add an additional cross entropy loss for chart types.\n- Ensemble several (4) models  (+0.01 public) :\n   - Voting to compute the number of outputs and fix obvious mistakes.\n   - Voting for categorical predictions.\n   - Averaging for continuous predictions.\n\n**More details:**\n- lr 3e-5, cosine with 0.25 cycle.\n- 10 Epochs.\n- Save weights every 0.25 epochs and use a model soup of all checkpoints from epoch > 1.\n- Augs : Color transforms, image compression and random scaling.\n\n## Final Words\n\nWe were greatly perturbed by the unexpected second rule change, especially considering the fact that I was on vacation. Matcha and classification models were retrained on ICDAR during the last 3 days, this gave a 0.01 public LB boost but nothing on private. \n\nThanks for reading !",
      "votes": null
    },
    {
      "id": "2310655",
      "postDate": "06/20/2023 14:23:37",
      "content": "<p>Our inference code is here : <a href=\"https://www.kaggle.com/code/crodoc/benetech-mit-ensemble?scriptVersionId=134055662\" target=\"_blank\">https://www.kaggle.com/code/crodoc/benetech-mit-ensemble?scriptVersionId=134055662</a><br>\nWe will be sharing and open-sourcing the rest of our solution in the upcoming days. </p>\n<p>We are prize eligible, but I still have not understood which prize we will get :)</p>",
      "rawMarkdown": "Our inference code is here : https://www.kaggle.com/code/crodoc/benetech-mit-ensemble?scriptVersionId=134055662\nWe will be sharing and open-sourcing the rest of our solution in the upcoming days. \n\nWe are prize eligible, but I still have not understood which prize we will get :)",
      "votes": null
    },
    {
      "id": "2310664",
      "postDate": "06/20/2023 14:29:14",
      "content": "<p>This is really cool strategy, and thanks for the nice write-up as well. <br>\nCongratulations. </p>",
      "rawMarkdown": "This is really cool strategy, and thanks for the nice write-up as well. \nCongratulations.",
      "votes": null
    },
    {
      "id": "2310676",
      "postDate": "06/20/2023 14:36:42",
      "content": "<p>Great approach, definetly good ideas embedded. Congratulations becoming 3rd place!</p>",
      "rawMarkdown": "Great approach, definetly good ideas embedded. Congratulations becoming 3rd place!",
      "votes": null
    },
    {
      "id": "2311031",
      "postDate": "06/20/2023 20:50:13",
      "content": "<blockquote>\n  <p>Anyone in 0.86+ public scores could've taken the win</p>\n</blockquote>\n<p>I second that. </p>\n<p>Great work and interesting solution <a href=\"https://www.kaggle.com/theoviel\" target=\"_blank\">@theoviel</a> and <a href=\"https://www.kaggle.com/crodoc\" target=\"_blank\">@crodoc</a> </p>",
      "rawMarkdown": ">Anyone in 0.86+ public scores could've taken the win\n\nI second that. \n\nGreat work and interesting solution @theoviel and @crodoc",
      "votes": null
    },
    {
      "id": "2311039",
      "postDate": "06/20/2023 21:12:10",
      "content": "<p>Yea. We all expected a shake-up in the last digits.</p>\n<p>Where we failed was not probing for the number of all chart types in the test set. We just probed dots 🤦‍♂️ then scatter slaps you on private 🤣</p>",
      "rawMarkdown": "Yea. We all expected a shake-up in the last digits.\n\nWhere we failed was not probing for the number of all chart types in the test set. We just probed dots 🤦‍♂️ then scatter slaps you on private 🤣",
      "votes": null
    },
    {
      "id": "2311489",
      "postDate": "06/21/2023 07:29:30",
      "content": "<p>Congrats, great work. How could you get the score per chart type in public and private?</p>",
      "rawMarkdown": "Congrats, great work. How could you get the score per chart type in public and private?",
      "votes": null
    },
    {
      "id": "2311519",
      "postDate": "06/21/2023 07:55:06",
      "content": "<p>Congrats on 3rd place, I learned a lot from reading your solution! </p>",
      "rawMarkdown": "Congrats on 3rd place, I learned a lot from reading your solution!",
      "votes": null
    },
    {
      "id": "2311522",
      "postDate": "06/21/2023 07:56:27",
      "content": "<p>Thanks <a href=\"https://www.kaggle.com/thedrcat\" target=\"_blank\">@thedrcat</a>. Nice to hear from an old teamate :)</p>",
      "rawMarkdown": "Thanks @thedrcat. Nice to hear from an old teamate :)",
      "votes": null
    },
    {
      "id": "2311526",
      "postDate": "06/21/2023 07:59:56",
      "content": "<p>To get score for scatter, replace all non-scatter rows with: <code>';'.join(['0']*200)</code></p>",
      "rawMarkdown": "To get score for scatter, replace all non-scatter rows with: `';'.join(['0']*200)`",
      "votes": null
    },
    {
      "id": "2311527",
      "postDate": "06/21/2023 08:00:49",
      "content": "<p>Ok, thanks. I got it. </p>",
      "rawMarkdown": "Ok, thanks. I got it.",
      "votes": null
    },
    {
      "id": "2311542",
      "postDate": "06/21/2023 08:26:37",
      "content": "<p>Congratulations for third place!!! <a href=\"https://www.kaggle.com/crodoc\" target=\"_blank\">@crodoc</a> </p>",
      "rawMarkdown": "Congratulations for third place!!! @crodoc",
      "votes": null
    },
    {
      "id": "2311577",
      "postDate": "06/21/2023 08:49:04",
      "content": "<p>Thank you <a href=\"https://www.kaggle.com/akshitsharma1\" target=\"_blank\">@akshitsharma1</a> :)</p>",
      "rawMarkdown": "Thank you @akshitsharma1 :)",
      "votes": null
    },
    {
      "id": "2311830",
      "postDate": "06/21/2023 13:28:04",
      "content": "<p>Congrats <a href=\"https://www.kaggle.com/theoviel\" target=\"_blank\">@theoviel</a> and <a href=\"https://www.kaggle.com/crodoc\" target=\"_blank\">@crodoc</a> !</p>\n<p>And thanks for the overview (nice graphs as always) !</p>",
      "rawMarkdown": "Congrats @theoviel and @crodoc !\n\nAnd thanks for the overview (nice graphs as always) !",
      "votes": null
    },
    {
      "id": "2311893",
      "postDate": "06/21/2023 14:11:46",
      "content": "<p>Thanks Vincent :) Theo is the one making nice graphs :D</p>",
      "rawMarkdown": "Thanks Vincent :) Theo is the one making nice graphs :D",
      "votes": null
    },
    {
      "id": "2312016",
      "postDate": "06/21/2023 15:52:11",
      "content": "<p>The third-place solution showcases a thoughtful and effective approach, combining classification and object detection techniques to tackle the competition task. Their strategy for generalization and careful parameter optimization contributed to their impressive results. Well done!</p>",
      "rawMarkdown": "The third-place solution showcases a thoughtful and effective approach, combining classification and object detection techniques to tackle the competition task. Their strategy for generalization and careful parameter optimization contributed to their impressive results. Well done!",
      "votes": null
    },
    {
      "id": "2315067",
      "postDate": "06/23/2023 19:37:10",
      "content": "<p>Congrats on your gold.<br>\nDid you notice improvement using Augs? Also, what do you mean by \"model soup of all checkpoints\"?</p>",
      "rawMarkdown": "Congrats on your gold.\nDid you notice improvement using Augs? Also, what do you mean by \"model soup of all checkpoints\"?",
      "votes": null
    },
    {
      "id": "2315296",
      "postDate": "06/24/2023 03:19:45",
      "content": "<p>Congrats <a href=\"https://www.kaggle.com/theoviel\" target=\"_blank\">@theoviel</a> and <a href=\"https://www.kaggle.com/crodoc\" target=\"_blank\">@crodoc</a> !</p>\n<p>And thanks for this wonderful overview !</p>",
      "rawMarkdown": "Congrats @theoviel and @crodoc !\n\nAnd thanks for this wonderful overview !",
      "votes": null
    },
    {
      "id": "2315868",
      "postDate": "06/24/2023 13:36:11",
      "content": "<p>Great work :) I feel I learned a lot reading this through. I have a few questions. If you or someone else could respond, I'd be grateful. I'm a newbie though, so they might be trivial.</p>\n<ul>\n<li><p>Did you guys have any issues with inference computation time using matcha? I tried it and a few models like it, and running a prediction took 10s of seconds for one single plot with kaggle GPUs. So I figured an inference notebook would exceed the time limit. Same question goes for training.</p></li>\n<li><p>What is this \"cached\" that you ensembled with YoloX?</p></li>\n</ul>",
      "rawMarkdown": "Great work :) I feel I learned a lot reading this through. I have a few questions. If you or someone else could respond, I'd be grateful. I'm a newbie though, so they might be trivial.\n\n- Did you guys have any issues with inference computation time using matcha? I tried it and a few models like it, and running a prediction took 10s of seconds for one single plot with kaggle GPUs. So I figured an inference notebook would exceed the time limit. Same question goes for training.\n\n- What is this \"cached\" that you ensembled with YoloX?",
      "votes": null
    },
    {
      "id": "2316230",
      "postDate": "06/24/2023 18:09:32",
      "content": "<p>As the model gets better, inference time decreases. At the beginning of training, the model produces a lot of tokens (up to max length, which we set to 512). This is slow. It needs to learn to predict the end token and stop generating more tokens. This is why we always skipped doing validation in the first epoch.</p>\n<p>Cached is <a href=\"https://arxiv.org/pdf/2305.04151.pdf\" target=\"_blank\">this</a>. And <a href=\"https://www.kaggle.com/code/junkoda/cached-chart-element-detection\" target=\"_blank\">this</a> is a good notebook by <a href=\"https://www.kaggle.com/junkoda\" target=\"_blank\">@junkoda</a>.</p>",
      "rawMarkdown": "As the model gets better, inference time decreases. At the beginning of training, the model produces a lot of tokens (up to max length, which we set to 512). This is slow. It needs to learn to predict the end token and stop generating more tokens. This is why we always skipped doing validation in the first epoch.\n\nCached is [this](https://arxiv.org/pdf/2305.04151.pdf). And [this](https://www.kaggle.com/code/junkoda/cached-chart-element-detection) is a good notebook by @junkoda.",
      "votes": null
    },
    {
      "id": "2319402",
      "postDate": "06/27/2023 05:31:24",
      "content": "<p><a href=\"https://www.kaggle.com/theoviel\" target=\"_blank\">@theoviel</a> congrats with 3 place! Nice graphics!</p>",
      "rawMarkdown": "theoviel congrats with 3 place! Nice graphics!",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 2310655,
      "author_name": "theoviel",
      "author_url": "",
      "post_date": "06/20/2023 14:23:37",
      "content": "<p>Our inference code is here : <a href=\"https://www.kaggle.com/code/crodoc/benetech-mit-ensemble?scriptVersionId=134055662\" target=\"_blank\">https://www.kaggle.com/code/crodoc/benetech-mit-ensemble?scriptVersionId=134055662</a><br>\nWe will be sharing and open-sourcing the rest of our solution in the upcoming days. </p>\n<p>We are prize eligible, but I still have not understood which prize we will get :)</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2310664,
      "author_name": "cooleel",
      "author_url": "",
      "post_date": "06/20/2023 14:29:14",
      "content": "<p>This is really cool strategy, and thanks for the nice write-up as well. <br>\nCongratulations. </p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2310676,
      "author_name": "aliabdin1",
      "author_url": "",
      "post_date": "06/20/2023 14:36:42",
      "content": "<p>Great approach, definetly good ideas embedded. Congratulations becoming 3rd place!</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2311031,
      "author_name": "christofhenkel",
      "author_url": "",
      "post_date": "06/20/2023 20:50:13",
      "content": "<blockquote>\n  <p>Anyone in 0.86+ public scores could've taken the win</p>\n</blockquote>\n<p>I second that. </p>\n<p>Great work and interesting solution <a href=\"https://www.kaggle.com/theoviel\" target=\"_blank\">@theoviel</a> and <a href=\"https://www.kaggle.com/crodoc\" target=\"_blank\">@crodoc</a> </p>",
      "votes": null,
      "replies": [
        {
          "id": 2311039,
          "author_name": "crodoc",
          "author_url": "",
          "post_date": "06/20/2023 21:12:10",
          "content": "<p>Yea. We all expected a shake-up in the last digits.</p>\n<p>Where we failed was not probing for the number of all chart types in the test set. We just probed dots 🤦‍♂️ then scatter slaps you on private 🤣</p>",
          "votes": null,
          "replies": []
        }
      ]
    },
    {
      "id": 2311489,
      "author_name": "hamzahabduljalil",
      "author_url": "",
      "post_date": "06/21/2023 07:29:30",
      "content": "<p>Congrats, great work. How could you get the score per chart type in public and private?</p>",
      "votes": null,
      "replies": [
        {
          "id": 2311526,
          "author_name": "crodoc",
          "author_url": "",
          "post_date": "06/21/2023 07:59:56",
          "content": "<p>To get score for scatter, replace all non-scatter rows with: <code>';'.join(['0']*200)</code></p>",
          "votes": null,
          "replies": [
            {
              "id": 2311527,
              "author_name": "hamzahabduljalil",
              "author_url": "",
              "post_date": "06/21/2023 08:00:49",
              "content": "<p>Ok, thanks. I got it. </p>",
              "votes": null,
              "replies": []
            }
          ]
        }
      ]
    },
    {
      "id": 2311519,
      "author_name": "thedrcat",
      "author_url": "",
      "post_date": "06/21/2023 07:55:06",
      "content": "<p>Congrats on 3rd place, I learned a lot from reading your solution! </p>",
      "votes": null,
      "replies": [
        {
          "id": 2311522,
          "author_name": "crodoc",
          "author_url": "",
          "post_date": "06/21/2023 07:56:27",
          "content": "<p>Thanks <a href=\"https://www.kaggle.com/thedrcat\" target=\"_blank\">@thedrcat</a>. Nice to hear from an old teamate :)</p>",
          "votes": null,
          "replies": []
        }
      ]
    },
    {
      "id": 2311542,
      "author_name": "akshitsharma1",
      "author_url": "",
      "post_date": "06/21/2023 08:26:37",
      "content": "<p>Congratulations for third place!!! <a href=\"https://www.kaggle.com/crodoc\" target=\"_blank\">@crodoc</a> </p>",
      "votes": null,
      "replies": [
        {
          "id": 2311577,
          "author_name": "crodoc",
          "author_url": "",
          "post_date": "06/21/2023 08:49:04",
          "content": "<p>Thank you <a href=\"https://www.kaggle.com/akshitsharma1\" target=\"_blank\">@akshitsharma1</a> :)</p>",
          "votes": null,
          "replies": []
        }
      ]
    },
    {
      "id": 2311830,
      "author_name": "vincentschuler",
      "author_url": "",
      "post_date": "06/21/2023 13:28:04",
      "content": "<p>Congrats <a href=\"https://www.kaggle.com/theoviel\" target=\"_blank\">@theoviel</a> and <a href=\"https://www.kaggle.com/crodoc\" target=\"_blank\">@crodoc</a> !</p>\n<p>And thanks for the overview (nice graphs as always) !</p>",
      "votes": null,
      "replies": [
        {
          "id": 2311893,
          "author_name": "crodoc",
          "author_url": "",
          "post_date": "06/21/2023 14:11:46",
          "content": "<p>Thanks Vincent :) Theo is the one making nice graphs :D</p>",
          "votes": null,
          "replies": []
        }
      ]
    },
    {
      "id": 2312016,
      "author_name": "poojach7611",
      "author_url": "",
      "post_date": "06/21/2023 15:52:11",
      "content": "<p>The third-place solution showcases a thoughtful and effective approach, combining classification and object detection techniques to tackle the competition task. Their strategy for generalization and careful parameter optimization contributed to their impressive results. Well done!</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2315067,
      "author_name": "tanjiroll",
      "author_url": "",
      "post_date": "06/23/2023 19:37:10",
      "content": "<p>Congrats on your gold.<br>\nDid you notice improvement using Augs? Also, what do you mean by \"model soup of all checkpoints\"?</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2315296,
      "author_name": "abhishek14398",
      "author_url": "",
      "post_date": "06/24/2023 03:19:45",
      "content": "<p>Congrats <a href=\"https://www.kaggle.com/theoviel\" target=\"_blank\">@theoviel</a> and <a href=\"https://www.kaggle.com/crodoc\" target=\"_blank\">@crodoc</a> !</p>\n<p>And thanks for this wonderful overview !</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2315868,
      "author_name": "esedx12",
      "author_url": "",
      "post_date": "06/24/2023 13:36:11",
      "content": "<p>Great work :) I feel I learned a lot reading this through. I have a few questions. If you or someone else could respond, I'd be grateful. I'm a newbie though, so they might be trivial.</p>\n<ul>\n<li><p>Did you guys have any issues with inference computation time using matcha? I tried it and a few models like it, and running a prediction took 10s of seconds for one single plot with kaggle GPUs. So I figured an inference notebook would exceed the time limit. Same question goes for training.</p></li>\n<li><p>What is this \"cached\" that you ensembled with YoloX?</p></li>\n</ul>",
      "votes": null,
      "replies": [
        {
          "id": 2316230,
          "author_name": "crodoc",
          "author_url": "",
          "post_date": "06/24/2023 18:09:32",
          "content": "<p>As the model gets better, inference time decreases. At the beginning of training, the model produces a lot of tokens (up to max length, which we set to 512). This is slow. It needs to learn to predict the end token and stop generating more tokens. This is why we always skipped doing validation in the first epoch.</p>\n<p>Cached is <a href=\"https://arxiv.org/pdf/2305.04151.pdf\" target=\"_blank\">this</a>. And <a href=\"https://www.kaggle.com/code/junkoda/cached-chart-element-detection\" target=\"_blank\">this</a> is a good notebook by <a href=\"https://www.kaggle.com/junkoda\" target=\"_blank\">@junkoda</a>.</p>",
          "votes": null,
          "replies": []
        }
      ]
    },
    {
      "id": 2319402,
      "author_name": "serangu",
      "author_url": "",
      "post_date": "06/27/2023 05:31:24",
      "content": "<p><a href=\"https://www.kaggle.com/theoviel\" target=\"_blank\">@theoviel</a> congrats with 3 place! Nice graphics!</p>",
      "votes": null,
      "replies": []
    }
  ],
  "raw_markdown_by_id": {
    "2310638": "First of all, congrats to all the participants especially to those that managed to stick it to the end. This was overall a really interesting challenge, and we really enjoyed tackling it. Anyone in 0.86+ public scores could've taken the win so we're glad we dropped only 2 spots.\n\nThanks to @crodoc for the great teaming experience, we had two different opinions on how to tackle the problem : he went for and end-to-end approach whereas I wanted to do object detection & OCR. This proved to be ideal for this competition as object detection compensated for end-to-end weaknesses.\n\n## Overview\n\n<a href=\"https://ibb.co/qFXF9vq\"><img src=\"https://i.ibb.co/84T42Hv/cls.png\" alt=\"cls\" border=\"0\"></a>\n\nOur solution is a two-step pipeline, where the first step is a simple classification task and the second step solves the task for the different chart types. For scatter and dot plots, we used a detection approach. For line and bar charts, Matcha was really strong. Here are our LB scores :\n\n| |  Overall | Scatter | Dots | Line | Horizontal Bar | Vertical Bar|\n| --- |\n| **Public** | 0.87 | 0.09 | 0| 0.33 | 0.04| 0.39|\n| **Private** | 0.71 | 0.28 | 0.01| 0.13 | 0.01 | 0.27 |\n\n## Validation & Insights\n\nFrom the description page : \n> The extracted figures in the training and public test sets are drawn from the same set of sources. The figures in the private test set are drawn from a distinct set of sources.\n\nPublic LB is leaky, we need to create models that generalize to new data sources. Therefore, we use all the extracted data as validation, and train only on generated data. Data provided by the hosts is not diverse enough to allow for generalization so we generated our own. Matcha and classification models were fullfit on all the data after careful parameter optimization, but detection models were not trained on any extracted data.\n\nFor validating on dots, we used a small curated dataset of 20 images found on google images. We estimated via probing that there are between 100 and 125 dots in the test set, all of them most likely being in the private test set. We did not probe for scatter though, this would've been helpful. \n\n## Step 1 - Classification\n\nNothing too fancy here, we trained models on (benetech + theo +  crodoc) generated data.\n\n**Main parameters:**\n- 2 epochs on 88k images. \n- lr `3e-4` or `5e-4`  (2 lrs for blend, we also used 2 seeds).\n- Mixup and some color augmentations mostly.\n- 256x384 image size.\n- NfNet-l2 with 0.2 dropout.\n\n## Step 2.a - Scatter\n\n<a href=\"https://ibb.co/5v4cM9C\"><img src=\"https://i.ibb.co/ct3TgvR/scatter-pipe.png\" alt=\"scatter-pipe\" border=\"0\"></a>\n\nFor scatter, we relied on [YoloX](https://github.com/Megvii-BaseDetection/YOLOX) to detect all markers. Cached is used to take care of the other useful elements. If all the points are detected correctly, it’s not that complicated to infer the target : detect the ticks & labels, read the labels, and interpolate !\n\n**More details:**\n- Ensemble YoloX-m and YoloX-l with NMS, helps reduce the number of FNs.\n- Models are trained on scatter + dot generated benetech data + a bunch of plots I generated and pseudo labeled, for 10 epochs.\n- CV 0.67, public LB 0.09~, private 0.29 - performance drop almost exclusively comes from overlapping/too hard to detect markers.\n- Bunch of post-processing to make the pipeline more robust to OCR mistakes and detection mistakes.\n- We were initially using Yolo-v7, but had to switch to YoloX because of the first rule change. It took us a week to match Yolo-v7 performance with YoloX.\n\n## Step 2.b - Dots\n\n<a href=\"https://ibb.co/sgMLzxG\"><img src=\"https://i.ibb.co/TwFzdjS/dot-pipe.png\" alt=\"dot-pipe\" border=\"0\"></a>\n\nDot pipeline is similar to scatter, but a bit more simple. We detect the points and cluster them, and map them to the detected x-labels. Labels with no assigned cluster are given the target 0, others the number of detected points. Counting points is not really robust to detection mistakes, so instead we used the height of the uppermost point and interpolated.\n\n## Step 2.c - Bars & Lines\n\nMatcha was really powerful here. We used `matcha-base` and set `is_vqa=False` to avoid giving texts as input to the model.\n\nWe trained Matcha to predict chart type, xs and ys for an image. The ground truth looked the same as @nbroad used for his donut approach (except we dropped the prompt token). We tried other approaches, but this worked best :\n```\nx_str = X_START + \";\".join(list(map(str, xs))) + X_END\ny_str = Y_START + \";\".join(list(map(str, ys))) + Y_END\nground_truth = '<' + chart_type + '>' + x_str + y_str\n```\ne.g: `<line><x_start>0;2;4;6<x_end><y_start>2.7;2.2;3.6;5.2;<y_end>`\n\nThe most valuable boost we got by generating additional charts using matplotlib. We reused the values and texts from the train dataset to generate ticks & values, with different styles/patterns/fonts/colors for diversity. The code for generating additional images has around 1000 lines and basically covers most cases where the model was failing when validated on the \"extracted\" dataset (e.g. negative values, line edges, missing bars, multiline text, text rotations).\n\n**Other things that helped :**\n- Fix the number of decimal points for numerical values using their range :\n`number_of_decimals = max(0, round(np.log10(1/ (max(y_ticks) - min(y_ticks))) + 3)`\n- We use an additional chart type : histograms, to learn that such charts have one less y value. \n- Add an additional cross entropy loss for chart types.\n- Ensemble several (4) models  (+0.01 public) :\n   - Voting to compute the number of outputs and fix obvious mistakes.\n   - Voting for categorical predictions.\n   - Averaging for continuous predictions.\n\n**More details:**\n- lr 3e-5, cosine with 0.25 cycle.\n- 10 Epochs.\n- Save weights every 0.25 epochs and use a model soup of all checkpoints from epoch > 1.\n- Augs : Color transforms, image compression and random scaling.\n\n## Final Words\n\nWe were greatly perturbed by the unexpected second rule change, especially considering the fact that I was on vacation. Matcha and classification models were retrained on ICDAR during the last 3 days, this gave a 0.01 public LB boost but nothing on private. \n\nThanks for reading !",
    "2310655": "Our inference code is here : https://www.kaggle.com/code/crodoc/benetech-mit-ensemble?scriptVersionId=134055662\nWe will be sharing and open-sourcing the rest of our solution in the upcoming days. \n\nWe are prize eligible, but I still have not understood which prize we will get :)",
    "2310664": "This is really cool strategy, and thanks for the nice write-up as well. \nCongratulations.",
    "2310676": "Great approach, definetly good ideas embedded. Congratulations becoming 3rd place!",
    "2311031": ">Anyone in 0.86+ public scores could've taken the win\n\nI second that. \n\nGreat work and interesting solution @theoviel and @crodoc",
    "2311039": "Yea. We all expected a shake-up in the last digits.\n\nWhere we failed was not probing for the number of all chart types in the test set. We just probed dots 🤦‍♂️ then scatter slaps you on private 🤣",
    "2311489": "Congrats, great work. How could you get the score per chart type in public and private?",
    "2311519": "Congrats on 3rd place, I learned a lot from reading your solution!",
    "2311522": "Thanks @thedrcat. Nice to hear from an old teamate :)",
    "2311526": "To get score for scatter, replace all non-scatter rows with: `';'.join(['0']*200)`",
    "2311527": "Ok, thanks. I got it.",
    "2311542": "Congratulations for third place!!! @crodoc",
    "2311577": "Thank you @akshitsharma1 :)",
    "2311830": "Congrats @theoviel and @crodoc !\n\nAnd thanks for the overview (nice graphs as always) !",
    "2311893": "Thanks Vincent :) Theo is the one making nice graphs :D",
    "2312016": "The third-place solution showcases a thoughtful and effective approach, combining classification and object detection techniques to tackle the competition task. Their strategy for generalization and careful parameter optimization contributed to their impressive results. Well done!",
    "2315067": "Congrats on your gold.\nDid you notice improvement using Augs? Also, what do you mean by \"model soup of all checkpoints\"?",
    "2315296": "Congrats @theoviel and @crodoc !\n\nAnd thanks for this wonderful overview !",
    "2315868": "Great work :) I feel I learned a lot reading this through. I have a few questions. If you or someone else could respond, I'd be grateful. I'm a newbie though, so they might be trivial.\n\n- Did you guys have any issues with inference computation time using matcha? I tried it and a few models like it, and running a prediction took 10s of seconds for one single plot with kaggle GPUs. So I figured an inference notebook would exceed the time limit. Same question goes for training.\n\n- What is this \"cached\" that you ensembled with YoloX?",
    "2316230": "As the model gets better, inference time decreases. At the beginning of training, the model produces a lot of tokens (up to max length, which we set to 512). This is slow. It needs to learn to predict the end token and stop generating more tokens. This is why we always skipped doing validation in the first epoch.\n\nCached is [this](https://arxiv.org/pdf/2305.04151.pdf). And [this](https://www.kaggle.com/code/junkoda/cached-chart-element-detection) is a good notebook by @junkoda.",
    "2319402": "theoviel congrats with 3 place! Nice graphics!"
  },
  "source": "meta"
}