{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":121150,"databundleVersionId":15061024,"sourceType":"competition"},{"sourceId":14661932,"sourceType":"datasetVersion","datasetId":9366631}],"dockerImageVersionId":31260,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"\"\"\"\nAkkadian to English Translation Inference\nByT5 Optimized — tuned decoding for better score\n\"\"\"\n\nimport re\nimport pandas as pd\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\n\n# ──────────────────────────────────────────────────────────────────────────────\n# CONFIGURATION\n# ──────────────────────────────────────────────────────────────────────────────\nINFERENCE_CONFIG = {\n    \"test_data_path\": \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\",\n    \"trained_model_path\": \"/kaggle/input/final-byt5/byt5-akkadian-optimized-34x\",\n    \"computation_device\": torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\"),\n    \"maximum_sequence_length\": 512,\n    \"inference_batch_size\": 8,\n\n    # 🔹 Tuned decoding (small but effective changes)\n    \"generation_parameters\": {\n        \"num_beams\": 12,\n        \"max_new_tokens\": 600,\n        \"min_new_tokens\": 20,          # NEW: avoids empty / trivial outputs\n        \"length_penalty\": 1.05,        # ↑ better for Akkadian → English expansion\n        \"early_stopping\": True,        # NEW: prevents tail hallucination\n        \"repetition_penalty\": 1.15,    # NEW: better than n-gram blocking for ByT5\n    }\n}\n\n# ──────────────────────────────────────────────────────────────────────────────\n# INPUT PREPROCESSING\n# ──────────────────────────────────────────────────────────────────────────────\ndef preprocess_input_text(text):\n    if pd.isna(text):\n        return \"\"\n    text = str(text)\n    text = re.sub(r'(\\.{3,}|…+|……)', '<big_gap>', text)\n    text = re.sub(r'(xx+|\\s+x\\s+)', '<gap>', text)\n    return text\n\n# ──────────────────────────────────────────────────────────────────────────────\n# OUTPUT POSTPROCESSING\n# ──────────────────────────────────────────────────────────────────────────────\ndef postprocess_translation_output(text):\n    if not isinstance(text, str) or not text.strip():\n        return \"\"\n\n    # Normalize special characters\n    text = text.replace('ḫ', 'h').replace('Ḫ', 'H')\n    text = text.translate(str.maketrans(\"₀₁₂₃₄₅₆₇₈₉\", \"0123456789\"))\n\n    # Normalize gaps\n    text = re.sub(r'(\\[x\\]|\\(x\\)|\\bx\\b)', '<gap>', text, flags=re.I)\n    text = re.sub(r'(\\.{3,}|…|\\[\\.+\\])', '<big_gap>', text)\n\n    # Merge adjacent gaps\n    text = re.sub(r'<gap>\\s*<gap>', ' <big_gap> ', text)\n    text = re.sub(r'<big_gap>\\s*<big_gap>', ' <big_gap> ', text)\n\n    # Remove grammatical annotations\n    text = re.sub(\n        r'\\((fem|plur|pl|sing|singular|plural|\\?|!)\\.?\\s*\\w*\\)',\n        '',\n        text,\n        flags=re.I\n    )\n\n    # Protect gap tokens\n    text = text.replace('<gap>', '\\x00GAP\\x00')\n    text = text.replace('<big_gap>', '\\x00BIG\\x00')\n\n    # Remove unwanted characters\n    forbidden = '!?()\"—–<>⌈⌋⌊[]+ʾ/;'\n    text = text.translate(str.maketrans('', '', forbidden))\n\n    # Restore gaps\n    text = text.replace('\\x00GAP\\x00', ' <gap> ')\n    text = text.replace('\\x00BIG\\x00', ' <big_gap> ')\n\n    # Decimal → fraction normalization\n    fraction_map = {\n        r'\\.5\\b': ' ½',\n        r'\\.25\\b': ' ¼',\n        r'\\.75\\b': ' ¾',\n        r'\\.33+\\d*\\b': ' ⅓',\n        r'\\.66+\\d*\\b': ' ⅔'\n    }\n    for pat, sym in fraction_map.items():\n        text = re.sub(r'(\\d+)' + pat, r'\\1' + sym, text)\n        text = re.sub(r'\\b0' + pat, sym.strip(), text)\n\n    # Remove repetitions\n    text = re.sub(r'\\b(\\w+)(?:\\s+\\1\\b)+', r'\\1', text)\n    for n in range(4, 1, -1):\n        pat = r'\\b((?:\\w+\\s+){' + str(n-1) + r'}\\w+)(?:\\s+\\1\\b)+'\n        text = re.sub(pat, r'\\1', text)\n\n    # Cleanup spacing\n    text = re.sub(r'\\s+([.,:])', r'\\1', text)\n    text = re.sub(r'([.,])\\1+', r'\\1', text)\n    text = re.sub(r'\\s+', ' ', text).strip().strip('-')\n\n    return text\n\n# ──────────────────────────────────────────────────────────────────────────────\n# DATASET\n# ──────────────────────────────────────────────────────────────────────────────\nclass AkkadianTranslationDataset(Dataset):\n    def __init__(self, df):\n        self.ids = df[\"id\"].tolist()\n        self.texts = [\n            \"translate Akkadian to English: \" + t\n            for t in df[\"transliteration\"]\n        ]\n\n    def __len__(self):\n        return len(self.ids)\n\n    def __getitem__(self, idx):\n        return self.ids[idx], self.texts[idx]\n\ndef collate_batch(batch):\n    ids = [b[0] for b in batch]\n    texts = [b[1] for b in batch]\n\n    tokens = tokenizer(\n        texts,\n        max_length=INFERENCE_CONFIG[\"maximum_sequence_length\"],\n        padding=True,\n        truncation=True,\n        return_tensors=\"pt\"\n    )\n    return ids, tokens\n\n# ──────────────────────────────────────────────────────────────────────────────\n# MAIN\n# ──────────────────────────────────────────────────────────────────────────────\nprint(\"Loading test data...\")\ntest_df = pd.read_csv(INFERENCE_CONFIG[\"test_data_path\"])\ntest_df[\"transliteration\"] = test_df[\"transliteration\"].apply(preprocess_input_text)\nprint(f\"Loaded {len(test_df)} samples\")\n\nprint(\"Loading model & tokenizer...\")\ntokenizer = AutoTokenizer.from_pretrained(\n    INFERENCE_CONFIG[\"trained_model_path\"],\n    use_fast=False      # safer for ByT5 byte-level tokenization\n)\n\nmodel = AutoModelForSeq2SeqLM.from_pretrained(\n    INFERENCE_CONFIG[\"trained_model_path\"]\n).to(INFERENCE_CONFIG[\"computation_device\"]).eval()\n\ndataset = AkkadianTranslationDataset(test_df)\nloader = DataLoader(\n    dataset,\n    batch_size=INFERENCE_CONFIG[\"inference_batch_size\"],\n    shuffle=False,\n    num_workers=2,\n    collate_fn=collate_batch\n)\n\nprint(\"Running inference...\")\npredictions = []\n\nwith torch.inference_mode():\n    for ids, inputs in loader:\n        outputs = model.generate(\n            input_ids=inputs.input_ids.to(INFERENCE_CONFIG[\"computation_device\"]),\n            attention_mask=inputs.attention_mask.to(INFERENCE_CONFIG[\"computation_device\"]),\n            **INFERENCE_CONFIG[\"generation_parameters\"]\n        )\n\n        decoded = tokenizer.batch_decode(outputs, skip_special_tokens=True)\n        cleaned = [postprocess_translation_output(t) for t in decoded]\n        predictions.extend(zip(ids, cleaned))\n\nprint(\"Saving submission...\")\nsubmission = pd.DataFrame(predictions, columns=[\"id\", \"translation\"])\nsubmission.to_csv(\"submission.csv\", index=False)\n\nprint(\"Done ✔\")\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}