{"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,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":14300986,"sourceType":"datasetVersion","datasetId":9129096},{"sourceId":14374989,"sourceType":"datasetVersion","datasetId":9147887},{"sourceId":14376272,"sourceType":"datasetVersion","datasetId":9181082},{"sourceId":14410665,"sourceType":"datasetVersion","datasetId":9203761}],"dockerImageVersionId":31260,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport re\nimport torch\nimport pandas as pd\nfrom torch.utils.data import DataLoader, Dataset\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm.auto import tqdm\n\n# Your CONFIG (unchanged - perfect)\nCONFIG = {\n    \"data_path\": \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\",\n    \"models\": [\n        {\"path\": \"/kaggle/input/mt5-akkadian-v4-colab/content/mt5_akkadian_model\", \"weight\": 2.0},\n        {\"path\": \"/kaggle/input/byt5-akkadian-model\", \"weight\": 1.0},\n        {\"path\": \"/kaggle/input/train-gap-all-2/byt5-base-akkadian_gap_setence2\", \"weight\": 0.8},\n    ],\n    \"device\": torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\"),\n    \"max_len\": 512,\n    \"batch_size\": 4,\n    \"gen_params\": {\n        \"num_beams\": 8, \"max_new_tokens\": 512, \"length_penalty\": 1.10,\n        \"early_stopping\": True, \"repetition_penalty\": 1.2, \"no_repeat_ngram_size\": 3,\n    }\n}\nprint(\"⚙️ CONFIGURATION LOADED\")\nprint(f\" Models: {len(CONFIG['models'])} | Device: {CONFIG['device']}\")\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-25T15:55:30.972226Z","iopub.execute_input":"2026-01-25T15:55:30.972581Z","iopub.status.idle":"2026-01-25T15:55:30.979384Z","shell.execute_reply.started":"2026-01-25T15:55:30.972534Z","shell.execute_reply":"2026-01-25T15:55:30.978639Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def teammate_postprocess(raw_text):\n    \"\"\"Jan11_mod5's battle-tested pipeline (24.9 score)\"\"\"\n    if not isinstance(raw_text, str) or len(raw_text) < 5:\n        return \"The tablet is damaged.\"\n    \n    text = raw_text\n    \n    # 1. Dictionary fixes (truncation errors)\n    replacements = {\n        \"messenge.\": \"messenger.\", \"messenge \": \"messenger \",\n        \"investmen.\": \"investment.\", \"colony. The Kanesh colony.\": \"colony.\"\n    }\n    for bad, good in replacements.items():\n        text = text.replace(bad, good)\n    \n    # 2. Normalize chars\n    text = text.replace('ḫ', 'h').replace('Ḫ', 'H')\n    \n    # 3. Sentence deduplication (CORE trick)\n    sentences = re.split(r'(?<=[.!?])\\s+', text)\n    unique_sentences = []\n    seen = set()\n    for s in sentences:\n        s_clean = s.strip().lower()\n        if len(s_clean) > 10 and s_clean in seen: continue\n        if s.strip(): \n            unique_sentences.append(s.strip())\n            seen.add(s_clean)\n    text = \" \".join(unique_sentences)\n    \n    # 4. Bad endings removal\n    bad_endings = {'the', 'a', 'an', 'and', 'or', 'but', 'of', 'in', 'on', 'at', 'to', 'for', 'with', 'by', 'from'}\n    words = text.split()\n    while words and words[-1].lower().rstrip('.,!?') in bad_endings:\n        words.pop()\n    text = \" \".join(words)\n    \n    # 5. Repetition killer\n    words = text.split()\n    for n in range(3, 8):\n        i, new_words = 0, []\n        while i < len(words):\n            chunk = words[i:i+n]\n            if i + 2*n <= len(words) and words[i+n:i+2*n] == chunk:\n                new_words.extend(chunk)\n                i += 2*n\n            else:\n                new_words.append(words[i])\n                i += 1\n        words = new_words\n    text = \" \".join(words)\n    \n    # 6. Final cleanup\n    text = re.sub(r'\\s+', ' ', text).strip()\n    if text and text[-1] not in '.!?': text += \".\"\n    return text[:500]\n\nprint(\"✅ Teammate post-processing loaded\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-25T15:55:31.046369Z","iopub.execute_input":"2026-01-25T15:55:31.046693Z","iopub.status.idle":"2026-01-25T15:55:31.056454Z","shell.execute_reply.started":"2026-01-25T15:55:31.046666Z","shell.execute_reply":"2026-01-25T15:55:31.055742Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def weighted_ensemble_predictions(all_predictions, weights):\n    ensemble_results = []\n    total_weight = sum(weights)\n    normalized_weights = [w/total_weight for w in weights]\n    \n    for sample_idx in range(len(all_predictions[0])):\n        candidate_translations, candidate_weights = [], []\n        for model_idx, predictions in enumerate(all_predictions):\n            translation = predictions[sample_idx]\n            weight = normalized_weights[model_idx]\n            # SAFETY FILTER\n            if 15 < len(translation) < 500:  # Good length\n                if translation not in candidate_translations:\n                    candidate_translations.append(translation)\n                    candidate_weights.append(weight)\n                else:\n                    idx = candidate_translations.index(translation)\n                    candidate_weights[idx] += weight\n        \n        # Fallback\n        if not candidate_translations:\n            ensemble_results.append(\"The tablet is damaged.\")\n        else:\n            best_idx = candidate_weights.index(max(candidate_weights))\n            ensemble_results.append(candidate_translations[best_idx])\n    return ensemble_results\n\nclass AkkadianTranslationDataset(Dataset):\n    def __init__(self, dataframe):\n        self.ids = dataframe['id'].tolist()\n        self.texts = [\"Translate Akkadian to English: \" + str(t) for t in dataframe['transliteration']]\n    def __len__(self): return len(self.ids)\n    def __getitem__(self, idx): return self.ids[idx], self.texts[idx]\n\nprint(\"✅ Ensemble + Dataset fixed\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-25T15:55:31.057946Z","iopub.execute_input":"2026-01-25T15:55:31.058343Z","iopub.status.idle":"2026-01-25T15:55:31.078769Z","shell.execute_reply.started":"2026-01-25T15:55:31.058321Z","shell.execute_reply":"2026-01-25T15:55:31.078159Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_inference():\n    print(\"\\n\" + \"=\"*60)\n    print(\"🚀 FIXED INFERENCE (Your Models + Teammate Postproc)\")\n    print(\"=\"*60)\n    \n    # Load + preprocess\n    dataframe = pd.read_csv(CONFIG['data_path'])\n    print(f\"📂 Loaded {len(dataframe)} samples\")\n    \n    all_predictions, weights = [], []\n    \n    # Generate raw predictions from each model\n    for model_config in CONFIG['models']:\n        model_path, weight = model_config['path'], model_config['weight']\n        print(f\"\\n🔄 {model_path.split('/')[-1]} (w={weight})\")\n        \n        try:\n            model = AutoModelForSeq2SeqLM.from_pretrained(model_path)\n            tokenizer = AutoTokenizer.from_pretrained(model_path)\n            model.to(CONFIG['device']).eval()\n            \n            dataset = AkkadianTranslationDataset(dataframe)\n            loader = DataLoader(dataset, batch_size=CONFIG['batch_size'], \n                              shuffle=False, num_workers=0,\n                              collate_fn=lambda b: ([i[0] for i in b],\n                                                  tokenizer([i[1] for i in b], \n                                                         max_length=CONFIG['max_len'],\n                                                         padding=True, truncation=True, \n                                                         return_tensors=\"pt\")))\n            \n            model_preds = []\n            with torch.inference_mode():\n                for _, inputs in tqdm(loader, desc=\"Translating\"):\n                    outputs = model.generate(inputs.input_ids.to(CONFIG['device']),\n                                           attention_mask=inputs.attention_mask.to(CONFIG['device']),\n                                           **CONFIG['gen_params'])\n                    decoded = tokenizer.batch_decode(outputs, skip_special_tokens=True)\n                    model_preds.extend(decoded)  # RAW predictions\n            \n            all_predictions.append(model_preds)\n            weights.append(weight)\n            print(f\"   ✓ {len(model_preds)} raw predictions\")\n            \n            del model, tokenizer\n            torch.cuda.empty_cache()\n            \n        except Exception as e:\n            print(f\"   ✗ {e}\")\n            continue\n    \n    # ENSEMBLE RAW PREDICTIONS\n    print(\"\\n🔀 Weighted ensemble...\")\n    if len(all_predictions) == 1:\n        raw_ensemble = all_predictions[0]\n    else:\n        raw_ensemble = weighted_ensemble_predictions(all_predictions, weights)\n    \n    # APPLY TEAMMATE POST-PROCESSING\n    final_predictions = [teammate_postprocess(p) for p in raw_ensemble]\n    \n    return list(zip(dataframe['id'], final_predictions))\n\n# ===== RUN + SUBMIT =====\nresults = run_inference()\nsubmission_df = pd.DataFrame(results, columns=['id', 'translation'])\nsubmission_df.to_csv('submission.csv', index=False)\nprint(f\"\\n✅ SAVED submission.csv ({len(submission_df)} rows)\")\n\nprint(\"\\n📋 SAMPLE OUTPUTS:\")\nfor i, row in enumerate(submission_df.head(3).itertuples()):\n    print(f\"[{row.id}]: {row.translation}\")\n\nprint(\"\\n🎯 Expected: 26-30+ | SUBMIT NOW!\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-25T15:55:31.122646Z","iopub.execute_input":"2026-01-25T15:55:31.122881Z","iopub.status.idle":"2026-01-25T15:55:52.055295Z","shell.execute_reply.started":"2026-01-25T15:55:31.122860Z","shell.execute_reply":"2026-01-25T15:55:52.054258Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}