{"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":"gpu","dataSources":[{"sourceId":121150,"databundleVersionId":15061024,"sourceType":"competition"},{"sourceId":14374989,"sourceType":"datasetVersion","datasetId":9147887},{"sourceId":14376272,"sourceType":"datasetVersion","datasetId":9181082},{"sourceId":14410665,"sourceType":"datasetVersion","datasetId":9203761},{"sourceId":294579726,"sourceType":"kernelVersion"},{"sourceId":295037618,"sourceType":"kernelVersion"},{"sourceId":282751,"sourceType":"modelInstanceVersion","modelInstanceId":239470,"modelId":222398}],"dockerImageVersionId":31236,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport pandas as pd\nimport torch\nfrom torch.utils.data import DataLoader, Dataset\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nimport re\n\n# =========================\n# CONFIG\n# =========================\nCONFIG = {\n    \"data_path\": \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\",\n    \"external_submission_path\": \"/kaggle/input/akkadian-english-byt5-optimized-again/submission.csv\",\n    \"models\": [\n        \"/kaggle/input/byt5-base-big-data2\",\n        \"/kaggle/input/byt5-akkadian-model\",\n        \"/kaggle/input/train-gap-all-2/byt5-base-akkadian_gap_setence2\"\n    ],\n    \"model_weights\": [0.995, 0.98, 0.395],\n    \"device\": torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\"),\n    \"max_len\": 512,\n    \"batch_size\": 8,\n    \"gen_params\": {\n        \"num_beams\": 8,\n        \"max_new_tokens\": 512,\n        \"length_penalty\": 1.10,\n        \"early_stopping\": True\n    },\n    # Blending weights: our model vs external submission\n    \"blend_weights\": [0.3, 0.7]  # 70% our model, 30% external\n}\n\nprint(f\"Blending weights: {CONFIG['blend_weights'][0]*100:.0f}% our model + {CONFIG['blend_weights'][1]*100:.0f}% external\")\n\n# =========================\n# PREPROCESSING\n# =========================\ndef preprocess_transliteration(text):\n    if pd.isna(text): return \"\"\n    processed_text = str(text)\n    processed_text = re.sub(r'(\\.{3,}|…+|……)', '<big_gap>', processed_text)\n    processed_text = re.sub(r'(xx+|\\s+x\\s+)', '<gap>', processed_text)\n    return processed_text\n\n# =========================\n# ENHANCED POSTPROCESSING\n# =========================\ndef postprocess_translation(text):\n    if not isinstance(text, str) or not text.strip(): \n        return \"The tablet contains fragmentary text.\"\n    \n    processed_text = text.replace('ḫ', 'h').replace('Ḫ', 'H')\n    sub_map = str.maketrans(\"₀₁₂₃₄₅₆₇₈₉\", \"0123456789\")\n    processed_text = processed_text.translate(sub_map)\n\n    processed_text = re.sub(r'(\\[x\\]|\\(x\\)|\\bx\\b)', '<gap>', processed_text, flags=re.I)\n    processed_text = re.sub(r'(\\.{3,}|…|\\[\\.+\\])', '<big_gap>', processed_text)\n    \n    processed_text = re.sub(r'<gap>\\s*<gap>', ' <big_gap> ', processed_text)\n    processed_text = re.sub(r'<big_gap>\\s*<big_gap>', ' <big_gap> ', processed_text)\n\n    processed_text = re.sub(r'\\((fem|plur|pl|sing|singular|plural|\\?|!)\\.?\\s*\\w*\\)', '', processed_text, flags=re.I)\n\n    processed_text = processed_text.replace('<gap>', '\\x00GAP\\x00').replace('<big_gap>', '\\x00BIG\\x00')\n    \n    # Remove bad characters\n    bad_chars = '!?()\"—–<>⌈⌋⌊[]+ʾ/;'\n    processed_text = processed_text.translate(str.maketrans('', '', bad_chars))\n\n    processed_text = processed_text.replace('\\x00GAP\\x00', ' <gap> ').replace('\\x00BIG\\x00', ' <big_gap> ')\n\n    # Handle fractions\n    frac_map = {\n        r'\\.5\\b': ' ½', r'\\.25\\b': ' ¼', r'\\.75\\b': ' ¾',\n        r'\\.33+\\d*\\b': ' ⅓', r'\\.66+\\d*\\b': ' ⅔'\n    }\n    for pat, rep in frac_map.items():\n        processed_text = re.sub(r'(\\d+)' + pat, r'\\1' + rep, processed_text)\n        processed_text = re.sub(r'\\b0' + pat, rep.strip(), processed_text)\n\n    # Remove repeated words\n    processed_text = re.sub(r'\\b(\\w+)(?:\\s+\\1\\b)+', r'\\1', processed_text)\n    for n in range(4, 1, -1):\n        pat = r'\\b((?:\\w+\\s+){' + str(n-1) + r'}\\w+)(?:\\s+\\1\\b)+'\n        processed_text = re.sub(pat, r'\\1', processed_text)\n    \n    # Additional improvements for blending\n    # Capitalize first letter\n    if processed_text and processed_text[0].islower():\n        processed_text = processed_text[0].upper() + processed_text[1:]\n    \n    # Ensure proper ending\n    if processed_text and processed_text[-1] not in '.!?':\n        processed_text = processed_text + '.'\n    \n    return re.sub(r'\\s+', ' ', processed_text).strip().strip('-')\n\n# =========================\n# TEXT BLENDING FUNCTION\n# =========================\ndef blend_translations(text1, text2, weight1=0.7, weight2=0.3):\n    \"\"\"\n    Blend two translations by selecting the better one based on heuristics\n    or combining them intelligently\n    \"\"\"\n    if not text1 or not text2:\n        return text1 if text1 else text2\n    \n    # Simple weighted selection based on quality heuristics\n    def score_text(text):\n        score = 0\n        words = text.split()\n        \n        # Length preference (optimal 10-40 words)\n        if 10 <= len(words) <= 40:\n            score += 2\n        elif 5 <= len(words) <= 50:\n            score += 1\n        \n        # Starts with capital\n        if text and text[0].isupper():\n            score += 1\n        \n        # Ends with punctuation\n        if text and text[-1] in '.!?':\n            score += 1\n        \n        # Contains Akkadian keywords\n        akkadian_keywords = ['tablet', 'king', 'city', 'god', 'said', 'wrote', 'year']\n        for keyword in akkadian_keywords:\n            if keyword in text.lower():\n                score += 0.5\n        \n        # No obvious artifacts\n        if '???' not in text and '...' not in text and 'xxx' not in text.lower():\n            score += 1\n        \n        return score\n    \n    score1 = score_text(text1)\n    score2 = score_text(text2)\n    \n    # Weighted score\n    weighted_score1 = score1 * weight1\n    weighted_score2 = score2 * weight2\n    \n    # Return the better one\n    if weighted_score1 >= weighted_score2:\n        return text1\n    else:\n        return text2\n\n# =========================\n# MODEL SOUP\n# =========================\ndef create_model_soup():\n    total_score = sum(CONFIG['model_weights'])\n    WEIGHTS = [w / total_score for w in CONFIG['model_weights']]\n    \n    print(f\"Normalized model weights: {[f'{w:.3f}' for w in WEIGHTS]}\")\n    \n    template_model = AutoModelForSeq2SeqLM.from_pretrained(CONFIG['models'][1])\n    soup_sd = template_model.state_dict()\n    \n    model_1_sd = AutoModelForSeq2SeqLM.from_pretrained(CONFIG['models'][0]).state_dict()\n    model_3_sd = AutoModelForSeq2SeqLM.from_pretrained(CONFIG['models'][2]).state_dict()\n    \n    for key in soup_sd:\n        weighted_value = WEIGHTS[1] * soup_sd[key]\n        norm_factor = WEIGHTS[1]\n        \n        if key in model_1_sd:\n            weighted_value += WEIGHTS[0] * model_1_sd[key]\n            norm_factor += WEIGHTS[0]\n        if key in model_3_sd:\n            weighted_value += WEIGHTS[2] * model_3_sd[key]\n            norm_factor += WEIGHTS[2]\n            \n        soup_sd[key] = weighted_value / norm_factor\n        \n    template_model.load_state_dict(soup_sd)\n    return template_model.to(CONFIG['device']).eval().float()\n\n# =========================\n# DATASET\n# =========================\nclass AkkadianTranslationDataset(Dataset):\n    def __init__(self, dataframe):\n        self.ids = dataframe['id'].tolist()\n        self.texts = [\n            \"translate Akkadian to English: \" \n            + str(t) for t in dataframe['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\n# =========================\n# MAIN EXECUTION\n# =========================\n# Load external submissions\nprint(\"Loading external submissions...\")\nexternal_submissions = pd.read_csv(CONFIG['external_submission_path'])\nexternal_dict = dict(zip(external_submissions['id'], external_submissions['translation']))\nprint(f\"Loaded {len(external_dict)} external translations\")\n\n# Load and preprocess test data\ndataframe = pd.read_csv(CONFIG['data_path'])\ndataframe['transliteration'] = dataframe['transliteration'].apply(preprocess_transliteration)\nprint(f\"Loaded {len(dataframe)} test samples\")\n\n# Create model\nmodel = create_model_soup()\ntokenizer = AutoTokenizer.from_pretrained(CONFIG['models'][1])\n\n# Create dataloader\ndata_loader = DataLoader(\n    AkkadianTranslationDataset(dataframe),\n    batch_size=CONFIG['batch_size'],\n    shuffle=False,\n    num_workers=2,\n    collate_fn=lambda batch: (\n        [item[0] for item in batch],\n        tokenizer(\n            [item[1] for item in batch], \n            max_length=CONFIG['max_len'], \n            padding=True, truncation=True, \n            return_tensors=\"pt\"\n        )\n    )\n)\n\n# Inference\nprint(\"\\nGenerating predictions...\")\nour_predictions = {}\n\nwith torch.inference_mode():\n    for batch_idx, (ids, inputs) in enumerate(data_loader):\n        outputs = model.generate(\n            input_ids=inputs.input_ids.to(CONFIG['device']),\n            attention_mask=inputs.attention_mask.to(CONFIG['device']),\n            **CONFIG['gen_params']\n        )\n        \n        decoded_texts = tokenizer.batch_decode(outputs, skip_special_tokens=True)\n        cleaned_translations = [postprocess_translation(text) for text in decoded_texts]\n        \n        for id_, translation in zip(ids, cleaned_translations):\n            our_predictions[id_] = translation\n        \n        if (batch_idx + 1) % 20 == 0:\n            print(f\"  Processed {batch_idx + 1} batches\")\n\n# =========================\n# BLEND PREDICTIONS\n# =========================\nprint(\"\\nBlending predictions...\")\nblended_results = []\n\nfor id_ in sorted(our_predictions.keys()):\n    our_translation = our_predictions[id_]\n    external_translation = external_dict.get(id_, \"\")\n    \n    # Blend the two translations\n    blended = blend_translations(\n        our_translation, \n        external_translation,\n        weight1=CONFIG['blend_weights'][0],\n        weight2=CONFIG['blend_weights'][1]\n    )\n    \n    # If blending failed, fall back to ours\n    if not blended or blended.strip() == \"\":\n        blended = our_translation\n    \n    blended_results.append((id_, blended))\n\n# =========================\n# CREATE FINAL SUBMISSION\n# =========================\nsubmission_df = pd.DataFrame(blended_results, columns=['id', 'translation'])\n\n# Quality check\nbroken_count = sum(1 for t in submission_df['translation'] \n                   if len(t.split()) < 3 or \"fragmentary\" in t.lower())\nprint(f\"\\nQuality check: {broken_count} potentially problematic translations\")\n\n# Fix any remaining issues\nsubmission_df['translation'] = submission_df['translation'].apply(\n    lambda x: \"The tablet contains an incomplete inscription.\" \n    if len(x.split()) < 3 else x\n)\n\n# Save\nsubmission_df.to_csv(\"submission.csv\", index=False)\nprint(f\"\\n✅ Saved blended submission with {len(submission_df)} rows\")\n\n# Show comparison\nprint(\"\\n\" + \"=\"*60)\nprint(\"BLENDING COMPARISON (first 3 samples):\")\nprint(\"=\"*60)\n\nfor i in range(min(3, len(submission_df))):\n    id_ = submission_df.iloc[i]['id']\n    print(f\"\\nID: {id_}\")\n    print(f\"Our model:    {our_predictions[id_][:80]}...\")\n    print(f\"External:     {external_dict.get(id_, 'N/A')[:80]}...\")\n    print(f\"Blended:      {submission_df.iloc[i]['translation'][:80]}...\")\n    print(\"-\" * 40)\n\nprint(\"\\n✅ Done! Submission saved as 'submission_blended.csv'\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}