{
  "id": 684429,
  "title": "11th Place Solution",
  "url": "/competitions/deep-past-initiative-machine-translation/discussion/684429",
  "author_name": "kwa",
  "post_date": "2026-03-24T20:01:54.337000",
  "votes": 13,
  "comment_count": 0,
  "views": 0,
  "content": "<p>First of all, I'd like to thank the Deep Past Challenge team at Kaggle for hosting this fascinating competition. I also found many publicly available notebooks and discussions invaluable throughout the process, and I came away having learned a great deal.</p>\n<h2>Overview</h2>\n<ol>\n<li>I pre-train ByT5-large on multilingual translation pairs extracted from <code>publications.csv</code> to help the model understand Akkadian vocabularies and grammar</li>\n<li>I fine-tune the model on <code>train.csv</code> to align it with the competition format and domain knowledge</li>\n<li>I ensemble three separately fine-tuned ByT5-large models by pooling their outputs and selecting the best candidate via MBR decoding</li>\n</ol>\n<h2>1. Pre-training</h2>\n<p>I found that <code>page_text</code> in <code>publications.csv</code> contains many translations of Akkadian transliterations into multiple languages, including English, German, and French. I used an LLM to extract these pairs from the raw text, converting the unstructured PDF-extracted content into a clean, structured JSON format. I allowed the LLM to output <strong>null</strong> for cases where no corresponding translation exists, which I believe improved the overall quality of the dataset.</p>\n<pre><code>### Output Format (Strict JSON)\nReturn ONLY a JSON array of objects with the following structure. Do not include markdown headers or extra text.\n{\n  \"items\": [\n    {\n      \"akkadian_transliteration\": \"string\",\n      \"translation\": \"string or null\",\n      \"language\": \"string or null\",\n    }\n  ]\n}\n</code></pre>\n<p>The extracted data covers many languages (with some potential hallucinations from the LLM). The histogram below shows the language distribution in my dataset. I decided to use only English, French, German, and Turkish for pre-training, as these had sufficient data volume.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F2590026%2F4aff36dccd07fa82ba719f2d8b7752d6%2Fhist_of_languages.png?generation=1774382397183303&amp;alt=media\" alt=\"histogram_of_languages\"></p>\n<p>Training uses <code>Seq2SeqTrainer</code> and takes approximately 20 hours on a single A100 GPU in Colab.\nI preprocessed the data following the <a href=\"https://www.kaggle.com/competitions/deep-past-initiative-machine-translation/overview\" target=\"_blank\">dataset instructions</a> and <a href=\"https://www.kaggle.com/competitions/deep-past-initiative-machine-translation/discussion/668402\" target=\"_blank\">valuable discussions</a>.</p>\n<p>Hyperparameters:</p>\n<table>\n<thead>\n<tr>\n<th>Parameter</th>\n<th>Value</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>epochs</td>\n<td>3</td>\n</tr>\n<tr>\n<td>batch size</td>\n<td>8</td>\n</tr>\n<tr>\n<td>learning rate</td>\n<td>1e-4</td>\n</tr>\n<tr>\n<td>max length</td>\n<td>512</td>\n</tr>\n<tr>\n<td>label smoothing factor</td>\n<td>0.1</td>\n</tr>\n<tr>\n<td>EMA decay</td>\n<td>0.999</td>\n</tr>\n</tbody>\n</table>\n<h2>2. Fine-tuning</h2>\n<p>The training data contains translations aligned at the document level, whereas the test data is aligned at the sentence level. To bridge this gap, I used an LLM to segment the Akkadian transliterations and their English translations into sentence-by-sentence pairs.</p>\n<p>Since I was unsure about the typical length of test examples, I analyzed <code>test.csv</code> and found that transliteration lengths range from 129 to 267 characters. Based on this, I applied a length constraint of 100–400 characters per segment. The key instructions I used are as follows:</p>\n<pre><code>Maintain Sequence: Do NOT change the original order of sentences in either language.\nLength Constraints: Each Akkadian segment must be between 100 and 400 characters long.\n   - If a segment is **too short** (&lt; 100 chars): merge it with the adjacent clause.\n   - If a segment is **too long** (&gt; 400 chars): split it at a natural boundary such as a new clause, conjunction (ù, -ma), or topic shift.\n</code></pre>\n<p>I also noticed mismatching translation pairs in <code>train.csv</code>. The histogram below shows the distribution of translation-to-transliteration length ratios.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F2590026%2Ff5b62424f92cb3c44fbc372dafcf0ba9%2Fhist_of_ratio.png?generation=1774382431554487&amp;alt=media\" alt=\"\"></p>\n<p>I replaced outliers with LLM-generated translations, using <code>0.66</code> and <code>2.0</code> as the lower and upper thresholds. I provided few-shot examples to guide the correction:</p>\n<pre><code># Task\nImprove and correct the following English translation of an Akkadian text, using the original translation as a reference base.\n\n...\n\n# Examples\nAkkadian: 0.3333 ma-na 2.5 GÍN KÙ.BABBAR 20 NINDA i-ṣé-er tù-wa-ra-a-aḫ-šu a-lá-ḫu-um i-šu\nOriginal: Ali-ahum has 0.3333 mina 2.5 shekels of silver and 20 loaves of bread from Tuwar-ahšu.\nCorrected: Tuwar-ahšu owes 0.3333 mina 2.5 shekels of silver (and) 20 loaves of bread to Ali-ahum.\n\n...\n\n# Input\nAkkadian: {ak_input}\nOriginal: {original_translation}\nCorrected:\n</code></pre>\n<p>I also generated translations for transliterations in <code>publication_texts.csv</code> using a similar prompt for use in pre-training.</p>\n<p>Finally, I fine-tuned the pre-trained ByT5-large on the refined <code>train.csv</code> using the same training code as pre-training, with <code>epochs=2</code> and <code>lr=1e-5</code>.</p>\n<h2>3. Inference</h2>\n<p>I ensembled three ByT5-large models fine-tuned on different pre-training datasets. To evaluate the impact of MBR decoding, I ran experiments on the 100 hardest validation samples (selected by val score) to keep the process manageable. I compared four candidate generation methods: normal beam search, group beam search, nucleus sampling, and epsilon sampling. The best result came from using normal beam search for candidate generation and group beam search for the candidate pool. I used the competition metric directly as the utility function for MBR decoding. Applying a soft margin penalty based on the translation/transliteration ratio, along with PN/GN lemmatization, provided a small additional boost.</p>\n<pre><code>def calculate_soft_margin_penalty(ratio, low=0.9, high=2.0, sigma=0.2):\n    distance = max(0, low - ratio, ratio - high)\n    return np.exp(-(distance**2) / (2 * (sigma**2)))\n</code></pre>\n<h2>Model scores and final submission</h2>\n<p>The val scores were not well-correlated with the public leaderboard scores, which was puzzling. I believe the main cause is that my validation set was constructed by segmenting <code>train.csv</code> with an LLM and replacing mismatched pairs with LLM-generated translations. This made the val set share the same distribution and style as the training data, inflating the val scores and causing them to diverge from the actual test set distribution. That said, I'm relieved the final submission turned out reasonably well.</p>\n<p>D1: <code>published_texts.csv</code> D2: <code>publications.csv</code> D3: <code>train.csv</code></p>\n<table>\n<thead>\n<tr>\n<th>pre-training dataset</th>\n<th>pre-training steps</th>\n<th>val (w/o ft)</th>\n<th>val</th>\n<th>val hard</th>\n<th>public</th>\n<th>private</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>D1: <code>37910</code> D2: <code>79806</code></td>\n<td>42000</td>\n<td>36.2</td>\n<td>41.7</td>\n<td>23.8</td>\n<td>39.0</td>\n<td>38.9</td>\n</tr>\n<tr>\n<td>D1: <code>37910</code> D2: <code>126439</code> D3: <code>3987</code></td>\n<td>38000</td>\n<td>45.8</td>\n<td>47.9</td>\n<td>29.6</td>\n<td>38.0</td>\n<td>38.3</td>\n</tr>\n<tr>\n<td>D1: <code>37910</code> D2: <code>18834</code></td>\n<td>30000</td>\n<td>45.5</td>\n<td>46.6</td>\n<td>28.6</td>\n<td>38.2</td>\n<td>37.8</td>\n</tr>\n<tr>\n<td>(final submission)</td>\n<td>-</td>\n<td>-</td>\n<td>-</td>\n<td>30.14</td>\n<td>39.4</td>\n<td>39.8</td>\n</tr>\n</tbody>\n</table>\n<h2>Things that didn't work</h2>\n<ul>\n<li>PN/GN post-processing and normalization using <code>OA_Lexicon_eBL.csv</code></li>\n<li>Pre-training on dictionary data from <code>eBL_Dictionary.csv</code></li>\n<li>Minimum Risk Training for calibrating the model with the competition metric</li>\n</ul>",
  "messages": [
    {
      "id": 3428024,
      "postDate": "2026-03-24T20:01:54.337Z",
      "content": "<p>First of all, I'd like to thank the Deep Past Challenge team at Kaggle for hosting this fascinating competition. I also found many publicly available notebooks and discussions invaluable throughout the process, and I came away having learned a great deal.</p>\n<h2>Overview</h2>\n<ol>\n<li>I pre-train ByT5-large on multilingual translation pairs extracted from <code>publications.csv</code> to help the model understand Akkadian vocabularies and grammar</li>\n<li>I fine-tune the model on <code>train.csv</code> to align it with the competition format and domain knowledge</li>\n<li>I ensemble three separately fine-tuned ByT5-large models by pooling their outputs and selecting the best candidate via MBR decoding</li>\n</ol>\n<h2>1. Pre-training</h2>\n<p>I found that <code>page_text</code> in <code>publications.csv</code> contains many translations of Akkadian transliterations into multiple languages, including English, German, and French. I used an LLM to extract these pairs from the raw text, converting the unstructured PDF-extracted content into a clean, structured JSON format. I allowed the LLM to output <strong>null</strong> for cases where no corresponding translation exists, which I believe improved the overall quality of the dataset.</p>\n<pre><code>### Output Format (Strict JSON)\nReturn ONLY a JSON array of objects with the following structure. Do not include markdown headers or extra text.\n{\n  \"items\": [\n    {\n      \"akkadian_transliteration\": \"string\",\n      \"translation\": \"string or null\",\n      \"language\": \"string or null\",\n    }\n  ]\n}\n</code></pre>\n<p>The extracted data covers many languages (with some potential hallucinations from the LLM). The histogram below shows the language distribution in my dataset. I decided to use only English, French, German, and Turkish for pre-training, as these had sufficient data volume.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F2590026%2F4aff36dccd07fa82ba719f2d8b7752d6%2Fhist_of_languages.png?generation=1774382397183303&amp;alt=media\" alt=\"histogram_of_languages\"></p>\n<p>Training uses <code>Seq2SeqTrainer</code> and takes approximately 20 hours on a single A100 GPU in Colab.\nI preprocessed the data following the <a href=\"https://www.kaggle.com/competitions/deep-past-initiative-machine-translation/overview\" target=\"_blank\">dataset instructions</a> and <a href=\"https://www.kaggle.com/competitions/deep-past-initiative-machine-translation/discussion/668402\" target=\"_blank\">valuable discussions</a>.</p>\n<p>Hyperparameters:</p>\n<table>\n<thead>\n<tr>\n<th>Parameter</th>\n<th>Value</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>epochs</td>\n<td>3</td>\n</tr>\n<tr>\n<td>batch size</td>\n<td>8</td>\n</tr>\n<tr>\n<td>learning rate</td>\n<td>1e-4</td>\n</tr>\n<tr>\n<td>max length</td>\n<td>512</td>\n</tr>\n<tr>\n<td>label smoothing factor</td>\n<td>0.1</td>\n</tr>\n<tr>\n<td>EMA decay</td>\n<td>0.999</td>\n</tr>\n</tbody>\n</table>\n<h2>2. Fine-tuning</h2>\n<p>The training data contains translations aligned at the document level, whereas the test data is aligned at the sentence level. To bridge this gap, I used an LLM to segment the Akkadian transliterations and their English translations into sentence-by-sentence pairs.</p>\n<p>Since I was unsure about the typical length of test examples, I analyzed <code>test.csv</code> and found that transliteration lengths range from 129 to 267 characters. Based on this, I applied a length constraint of 100–400 characters per segment. The key instructions I used are as follows:</p>\n<pre><code>Maintain Sequence: Do NOT change the original order of sentences in either language.\nLength Constraints: Each Akkadian segment must be between 100 and 400 characters long.\n   - If a segment is **too short** (&lt; 100 chars): merge it with the adjacent clause.\n   - If a segment is **too long** (&gt; 400 chars): split it at a natural boundary such as a new clause, conjunction (ù, -ma), or topic shift.\n</code></pre>\n<p>I also noticed mismatching translation pairs in <code>train.csv</code>. The histogram below shows the distribution of translation-to-transliteration length ratios.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F2590026%2Ff5b62424f92cb3c44fbc372dafcf0ba9%2Fhist_of_ratio.png?generation=1774382431554487&amp;alt=media\" alt=\"\"></p>\n<p>I replaced outliers with LLM-generated translations, using <code>0.66</code> and <code>2.0</code> as the lower and upper thresholds. I provided few-shot examples to guide the correction:</p>\n<pre><code># Task\nImprove and correct the following English translation of an Akkadian text, using the original translation as a reference base.\n\n...\n\n# Examples\nAkkadian: 0.3333 ma-na 2.5 GÍN KÙ.BABBAR 20 NINDA i-ṣé-er tù-wa-ra-a-aḫ-šu a-lá-ḫu-um i-šu\nOriginal: Ali-ahum has 0.3333 mina 2.5 shekels of silver and 20 loaves of bread from Tuwar-ahšu.\nCorrected: Tuwar-ahšu owes 0.3333 mina 2.5 shekels of silver (and) 20 loaves of bread to Ali-ahum.\n\n...\n\n# Input\nAkkadian: {ak_input}\nOriginal: {original_translation}\nCorrected:\n</code></pre>\n<p>I also generated translations for transliterations in <code>publication_texts.csv</code> using a similar prompt for use in pre-training.</p>\n<p>Finally, I fine-tuned the pre-trained ByT5-large on the refined <code>train.csv</code> using the same training code as pre-training, with <code>epochs=2</code> and <code>lr=1e-5</code>.</p>\n<h2>3. Inference</h2>\n<p>I ensembled three ByT5-large models fine-tuned on different pre-training datasets. To evaluate the impact of MBR decoding, I ran experiments on the 100 hardest validation samples (selected by val score) to keep the process manageable. I compared four candidate generation methods: normal beam search, group beam search, nucleus sampling, and epsilon sampling. The best result came from using normal beam search for candidate generation and group beam search for the candidate pool. I used the competition metric directly as the utility function for MBR decoding. Applying a soft margin penalty based on the translation/transliteration ratio, along with PN/GN lemmatization, provided a small additional boost.</p>\n<pre><code>def calculate_soft_margin_penalty(ratio, low=0.9, high=2.0, sigma=0.2):\n    distance = max(0, low - ratio, ratio - high)\n    return np.exp(-(distance**2) / (2 * (sigma**2)))\n</code></pre>\n<h2>Model scores and final submission</h2>\n<p>The val scores were not well-correlated with the public leaderboard scores, which was puzzling. I believe the main cause is that my validation set was constructed by segmenting <code>train.csv</code> with an LLM and replacing mismatched pairs with LLM-generated translations. This made the val set share the same distribution and style as the training data, inflating the val scores and causing them to diverge from the actual test set distribution. That said, I'm relieved the final submission turned out reasonably well.</p>\n<p>D1: <code>published_texts.csv</code> D2: <code>publications.csv</code> D3: <code>train.csv</code></p>\n<table>\n<thead>\n<tr>\n<th>pre-training dataset</th>\n<th>pre-training steps</th>\n<th>val (w/o ft)</th>\n<th>val</th>\n<th>val hard</th>\n<th>public</th>\n<th>private</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>D1: <code>37910</code> D2: <code>79806</code></td>\n<td>42000</td>\n<td>36.2</td>\n<td>41.7</td>\n<td>23.8</td>\n<td>39.0</td>\n<td>38.9</td>\n</tr>\n<tr>\n<td>D1: <code>37910</code> D2: <code>126439</code> D3: <code>3987</code></td>\n<td>38000</td>\n<td>45.8</td>\n<td>47.9</td>\n<td>29.6</td>\n<td>38.0</td>\n<td>38.3</td>\n</tr>\n<tr>\n<td>D1: <code>37910</code> D2: <code>18834</code></td>\n<td>30000</td>\n<td>45.5</td>\n<td>46.6</td>\n<td>28.6</td>\n<td>38.2</td>\n<td>37.8</td>\n</tr>\n<tr>\n<td>(final submission)</td>\n<td>-</td>\n<td>-</td>\n<td>-</td>\n<td>30.14</td>\n<td>39.4</td>\n<td>39.8</td>\n</tr>\n</tbody>\n</table>\n<h2>Things that didn't work</h2>\n<ul>\n<li>PN/GN post-processing and normalization using <code>OA_Lexicon_eBL.csv</code></li>\n<li>Pre-training on dictionary data from <code>eBL_Dictionary.csv</code></li>\n<li>Minimum Risk Training for calibrating the model with the competition metric</li>\n</ul>",
      "rawMarkdown": "First of all, I'd like to thank the Deep Past Challenge team at Kaggle for hosting this fascinating competition. I also found many publicly available notebooks and discussions invaluable throughout the process, and I came away having learned a great deal.\n\n## Overview\n\n1. I pre-train ByT5-large on multilingual translation pairs extracted from `publications.csv` to help the model understand Akkadian vocabularies and grammar\n2. I fine-tune the model on `train.csv` to align it with the competition format and domain knowledge\n3. I ensemble three separately fine-tuned ByT5-large models by pooling their outputs and selecting the best candidate via MBR decoding\n\n## 1. Pre-training\n\nI found that `page_text` in `publications.csv` contains many translations of Akkadian transliterations into multiple languages, including English, German, and French. I used an LLM to extract these pairs from the raw text, converting the unstructured PDF-extracted content into a clean, structured JSON format. I allowed the LLM to output **null** for cases where no corresponding translation exists, which I believe improved the overall quality of the dataset.\n\n```\n### Output Format (Strict JSON)\nReturn ONLY a JSON array of objects with the following structure. Do not include markdown headers or extra text.\n{\n  \"items\": [\n    {\n      \"akkadian_transliteration\": \"string\",\n      \"translation\": \"string or null\",\n      \"language\": \"string or null\",\n    }\n  ]\n}\n```\n\nThe extracted data covers many languages (with some potential hallucinations from the LLM). The histogram below shows the language distribution in my dataset. I decided to use only English, French, German, and Turkish for pre-training, as these had sufficient data volume.\n\n![histogram_of_languages](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F2590026%2F4aff36dccd07fa82ba719f2d8b7752d6%2Fhist_of_languages.png?generation=1774382397183303&alt=media)\n\nTraining uses `Seq2SeqTrainer` and takes approximately 20 hours on a single A100 GPU in Colab.\nI preprocessed the data following the [dataset instructions](https://www.kaggle.com/competitions/deep-past-initiative-machine-translation/overview) and [valuable discussions](https://www.kaggle.com/competitions/deep-past-initiative-machine-translation/discussion/668402).\n\nHyperparameters:\n| Parameter | Value |\n| ---------------------- | :---: |\n| epochs | 3 |\n| batch size | 8 |\n| learning rate | 1e-4 |\n| max length | 512 |\n| label smoothing factor | 0.1 |\n| EMA decay | 0.999 |\n\n## 2. Fine-tuning\n\nThe training data contains translations aligned at the document level, whereas the test data is aligned at the sentence level. To bridge this gap, I used an LLM to segment the Akkadian transliterations and their English translations into sentence-by-sentence pairs.\n\nSince I was unsure about the typical length of test examples, I analyzed `test.csv` and found that transliteration lengths range from 129 to 267 characters. Based on this, I applied a length constraint of 100–400 characters per segment. The key instructions I used are as follows:\n\n```\nMaintain Sequence: Do NOT change the original order of sentences in either language.\nLength Constraints: Each Akkadian segment must be between 100 and 400 characters long.\n   - If a segment is **too short** (< 100 chars): merge it with the adjacent clause.\n   - If a segment is **too long** (> 400 chars): split it at a natural boundary such as a new clause, conjunction (ù, -ma), or topic shift.\n```\n\nI also noticed mismatching translation pairs in `train.csv`. The histogram below shows the distribution of translation-to-transliteration length ratios.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F2590026%2Ff5b62424f92cb3c44fbc372dafcf0ba9%2Fhist_of_ratio.png?generation=1774382431554487&alt=media)\n\nI replaced outliers with LLM-generated translations, using `0.66` and `2.0` as the lower and upper thresholds. I provided few-shot examples to guide the correction:\n\n```\n# Task\nImprove and correct the following English translation of an Akkadian text, using the original translation as a reference base.\n\n...\n\n# Examples\nAkkadian: 0.3333 ma-na 2.5 GÍN KÙ.BABBAR 20 NINDA i-ṣé-er tù-wa-ra-a-aḫ-šu a-lá-ḫu-um i-šu\nOriginal: Ali-ahum has 0.3333 mina 2.5 shekels of silver and 20 loaves of bread from Tuwar-ahšu.\nCorrected: Tuwar-ahšu owes 0.3333 mina 2.5 shekels of silver (and) 20 loaves of bread to Ali-ahum.\n\n...\n\n# Input\nAkkadian: {ak_input}\nOriginal: {original_translation}\nCorrected:\n```\n\nI also generated translations for transliterations in `publication_texts.csv` using a similar prompt for use in pre-training.\n\nFinally, I fine-tuned the pre-trained ByT5-large on the refined `train.csv` using the same training code as pre-training, with `epochs=2` and `lr=1e-5`.\n\n## 3. Inference\n\nI ensembled three ByT5-large models fine-tuned on different pre-training datasets. To evaluate the impact of MBR decoding, I ran experiments on the 100 hardest validation samples (selected by val score) to keep the process manageable. I compared four candidate generation methods: normal beam search, group beam search, nucleus sampling, and epsilon sampling. The best result came from using normal beam search for candidate generation and group beam search for the candidate pool. I used the competition metric directly as the utility function for MBR decoding. Applying a soft margin penalty based on the translation/transliteration ratio, along with PN/GN lemmatization, provided a small additional boost.\n\n```python\ndef calculate_soft_margin_penalty(ratio, low=0.9, high=2.0, sigma=0.2):\n    distance = max(0, low - ratio, ratio - high)\n    return np.exp(-(distance**2) / (2 * (sigma**2)))\n```\n\n## Model scores and final submission\n\nThe val scores were not well-correlated with the public leaderboard scores, which was puzzling. I believe the main cause is that my validation set was constructed by segmenting `train.csv` with an LLM and replacing mismatched pairs with LLM-generated translations. This made the val set share the same distribution and style as the training data, inflating the val scores and causing them to diverge from the actual test set distribution. That said, I'm relieved the final submission turned out reasonably well.\n\nD1: `published_texts.csv` D2: `publications.csv` D3: `train.csv`\n| pre-training dataset | pre-training steps | val (w/o ft) | val | val hard | public | private |\n| ----------------------- | :------------: | --------- | ------ | ------- | --- | --- |\n| D1: `37910` D2: `79806` | 42000 | 36.2 | 41.7 | 23.8 | 39.0 | 38.9 |\n| D1: `37910` D2: `126439` D3: `3987` | 38000 | 45.8 | 47.9 | 29.6 | 38.0 | 38.3 |\n| D1: `37910` D2: `18834` | 30000 | 45.5 | 46.6 | 28.6 | 38.2 | 37.8 |\n| (final submission) | - | - | - | 30.14 | 39.4 | 39.8 |\n\n## Things that didn't work\n\n- PN/GN post-processing and normalization using `OA_Lexicon_eBL.csv`\n- Pre-training on dictionary data from `eBL_Dictionary.csv`\n- Minimum Risk Training for calibrating the model with the competition metric\n",
      "votes": 13
    }
  ],
  "comments": [],
  "raw_markdown_by_id": {
    "3428024": "First of all, I'd like to thank the Deep Past Challenge team at Kaggle for hosting this fascinating competition. I also found many publicly available notebooks and discussions invaluable throughout the process, and I came away having learned a great deal.\n\n## Overview\n\n1. I pre-train ByT5-large on multilingual translation pairs extracted from `publications.csv` to help the model understand Akkadian vocabularies and grammar\n2. I fine-tune the model on `train.csv` to align it with the competition format and domain knowledge\n3. I ensemble three separately fine-tuned ByT5-large models by pooling their outputs and selecting the best candidate via MBR decoding\n\n## 1. Pre-training\n\nI found that `page_text` in `publications.csv` contains many translations of Akkadian transliterations into multiple languages, including English, German, and French. I used an LLM to extract these pairs from the raw text, converting the unstructured PDF-extracted content into a clean, structured JSON format. I allowed the LLM to output **null** for cases where no corresponding translation exists, which I believe improved the overall quality of the dataset.\n\n```\n### Output Format (Strict JSON)\nReturn ONLY a JSON array of objects with the following structure. Do not include markdown headers or extra text.\n{\n  \"items\": [\n    {\n      \"akkadian_transliteration\": \"string\",\n      \"translation\": \"string or null\",\n      \"language\": \"string or null\",\n    }\n  ]\n}\n```\n\nThe extracted data covers many languages (with some potential hallucinations from the LLM). The histogram below shows the language distribution in my dataset. I decided to use only English, French, German, and Turkish for pre-training, as these had sufficient data volume.\n\n![histogram_of_languages](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F2590026%2F4aff36dccd07fa82ba719f2d8b7752d6%2Fhist_of_languages.png?generation=1774382397183303&alt=media)\n\nTraining uses `Seq2SeqTrainer` and takes approximately 20 hours on a single A100 GPU in Colab.\nI preprocessed the data following the [dataset instructions](https://www.kaggle.com/competitions/deep-past-initiative-machine-translation/overview) and [valuable discussions](https://www.kaggle.com/competitions/deep-past-initiative-machine-translation/discussion/668402).\n\nHyperparameters:\n| Parameter | Value |\n| ---------------------- | :---: |\n| epochs | 3 |\n| batch size | 8 |\n| learning rate | 1e-4 |\n| max length | 512 |\n| label smoothing factor | 0.1 |\n| EMA decay | 0.999 |\n\n## 2. Fine-tuning\n\nThe training data contains translations aligned at the document level, whereas the test data is aligned at the sentence level. To bridge this gap, I used an LLM to segment the Akkadian transliterations and their English translations into sentence-by-sentence pairs.\n\nSince I was unsure about the typical length of test examples, I analyzed `test.csv` and found that transliteration lengths range from 129 to 267 characters. Based on this, I applied a length constraint of 100–400 characters per segment. The key instructions I used are as follows:\n\n```\nMaintain Sequence: Do NOT change the original order of sentences in either language.\nLength Constraints: Each Akkadian segment must be between 100 and 400 characters long.\n   - If a segment is **too short** (< 100 chars): merge it with the adjacent clause.\n   - If a segment is **too long** (> 400 chars): split it at a natural boundary such as a new clause, conjunction (ù, -ma), or topic shift.\n```\n\nI also noticed mismatching translation pairs in `train.csv`. The histogram below shows the distribution of translation-to-transliteration length ratios.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F2590026%2Ff5b62424f92cb3c44fbc372dafcf0ba9%2Fhist_of_ratio.png?generation=1774382431554487&alt=media)\n\nI replaced outliers with LLM-generated translations, using `0.66` and `2.0` as the lower and upper thresholds. I provided few-shot examples to guide the correction:\n\n```\n# Task\nImprove and correct the following English translation of an Akkadian text, using the original translation as a reference base.\n\n...\n\n# Examples\nAkkadian: 0.3333 ma-na 2.5 GÍN KÙ.BABBAR 20 NINDA i-ṣé-er tù-wa-ra-a-aḫ-šu a-lá-ḫu-um i-šu\nOriginal: Ali-ahum has 0.3333 mina 2.5 shekels of silver and 20 loaves of bread from Tuwar-ahšu.\nCorrected: Tuwar-ahšu owes 0.3333 mina 2.5 shekels of silver (and) 20 loaves of bread to Ali-ahum.\n\n...\n\n# Input\nAkkadian: {ak_input}\nOriginal: {original_translation}\nCorrected:\n```\n\nI also generated translations for transliterations in `publication_texts.csv` using a similar prompt for use in pre-training.\n\nFinally, I fine-tuned the pre-trained ByT5-large on the refined `train.csv` using the same training code as pre-training, with `epochs=2` and `lr=1e-5`.\n\n## 3. Inference\n\nI ensembled three ByT5-large models fine-tuned on different pre-training datasets. To evaluate the impact of MBR decoding, I ran experiments on the 100 hardest validation samples (selected by val score) to keep the process manageable. I compared four candidate generation methods: normal beam search, group beam search, nucleus sampling, and epsilon sampling. The best result came from using normal beam search for candidate generation and group beam search for the candidate pool. I used the competition metric directly as the utility function for MBR decoding. Applying a soft margin penalty based on the translation/transliteration ratio, along with PN/GN lemmatization, provided a small additional boost.\n\n```python\ndef calculate_soft_margin_penalty(ratio, low=0.9, high=2.0, sigma=0.2):\n    distance = max(0, low - ratio, ratio - high)\n    return np.exp(-(distance**2) / (2 * (sigma**2)))\n```\n\n## Model scores and final submission\n\nThe val scores were not well-correlated with the public leaderboard scores, which was puzzling. I believe the main cause is that my validation set was constructed by segmenting `train.csv` with an LLM and replacing mismatched pairs with LLM-generated translations. This made the val set share the same distribution and style as the training data, inflating the val scores and causing them to diverge from the actual test set distribution. That said, I'm relieved the final submission turned out reasonably well.\n\nD1: `published_texts.csv` D2: `publications.csv` D3: `train.csv`\n| pre-training dataset | pre-training steps | val (w/o ft) | val | val hard | public | private |\n| ----------------------- | :------------: | --------- | ------ | ------- | --- | --- |\n| D1: `37910` D2: `79806` | 42000 | 36.2 | 41.7 | 23.8 | 39.0 | 38.9 |\n| D1: `37910` D2: `126439` D3: `3987` | 38000 | 45.8 | 47.9 | 29.6 | 38.0 | 38.3 |\n| D1: `37910` D2: `18834` | 30000 | 45.5 | 46.6 | 28.6 | 38.2 | 37.8 |\n| (final submission) | - | - | - | 30.14 | 39.4 | 39.8 |\n\n## Things that didn't work\n\n- PN/GN post-processing and normalization using `OA_Lexicon_eBL.csv`\n- Pre-training on dictionary data from `eBL_Dictionary.csv`\n- Minimum Risk Training for calibrating the model with the competition metric\n"
  }
}