{"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":14328947,"sourceType":"datasetVersion","datasetId":9147887},{"sourceId":28808,"sourceType":"modelInstanceVersion","modelInstanceId":8332,"modelId":3301},{"sourceId":282751,"sourceType":"modelInstanceVersion","modelInstanceId":239470,"modelId":222398},{"sourceId":702111,"sourceType":"modelInstanceVersion","modelInstanceId":532780,"modelId":546522}],"dockerImageVersionId":31236,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"print(\"Hello\")","metadata":{"trusted":true,"execution":{"execution_failed":"2026-01-02T09:17:39.844Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nOPTIMIZED Old Assyrian Translation Pipeline for T4 GPU (15GB x 2)\nTarget: 45-55+ BLEU/chrF++ score\n\nKey Improvements:\n1. Extract additional training data from publications.csv\n2. Better preprocessing and normalization\n3. Ensemble multiple models\n4. Advanced post-processing with rules\n\"\"\"\n\nimport os\nimport pandas as pd\nimport torch\nfrom torch.utils.data import DataLoader, Dataset\nfrom transformers import (\n    AutoTokenizer, \n    AutoModelForSeq2SeqLM,\n    Seq2SeqTrainer,\n    Seq2SeqTrainingArguments,\n)\nfrom tqdm.auto import tqdm\nimport re\nimport gc\nimport numpy as np\nfrom collections import defaultdict\n\n# ============================================================================\n# PREPROCESSING FUNCTIONS\n# ============================================================================\n\ndef clean_transliteration(text):\n    \"\"\"Enhanced cleaning following dataset instructions\"\"\"\n    if pd.isna(text) or not isinstance(text, str):\n        return \"\"\n    \n    text = str(text)\n    \n    # Remove modern scribal notations\n    text = re.sub(r'!', '', text)\n    text = re.sub(r'\\?', '', text)\n    text = re.sub(r'/', '', text)\n    text = re.sub(r'[:.]', ' ', text)\n    \n    # Handle brackets - keep content but remove brackets for document-level\n    text = re.sub(r'<([^>]+)>', r'\\1', text)\n    text = re.sub(r'[˹˺]', '', text)\n    text = re.sub(r'\\[([^\\]]+)\\]', r'\\1', text)\n    \n    # Standardize gaps\n    text = re.sub(r'\\[x\\]', '<gap>', text)\n    text = re.sub(r' x ', ' <gap> ', text)\n    text = re.sub(r'\\[\\.{3}(?:\\s+\\.{3})+\\]', '<big_gap>', text)\n    text = re.sub(r'\\.{3}(?:\\s+\\.{3})+', '<big_gap>', text)\n    text = re.sub(r'\\[…+\\]', '<big_gap>', text)\n    text = re.sub(r'…+', '<big_gap>', text)\n    \n    # Convert subscripts to regular numbers\n    subscript_map = {'₀':'0','₁':'1','₂':'2','₃':'3','₄':'4',\n                     '₅':'5','₆':'6','₇':'7','₈':'8','₉':'9','ₓ':'x'}\n    for sub, num in subscript_map.items():\n        text = text.replace(sub, num)\n    \n    # Normalize special characters\n    text = text.replace('á', 'a2').replace('à', 'a3')\n    text = text.replace('é', 'e2').replace('è', 'e3')\n    text = text.replace('í', 'i2').replace('ì', 'i3')\n    text = text.replace('ú', 'u2').replace('ù', 'u3')\n    \n    # Normalize spaces and clean\n    text = re.sub(r'\\s+', ' ', text).strip()\n    text = re.sub(r'-+', '-', text)\n    \n    return text\n\ndef clean_translation(text):\n    \"\"\"Clean translation preserving essential markers\"\"\"\n    if pd.isna(text) or not isinstance(text, str):\n        return \"\"\n    \n    text = str(text).strip()\n    text = re.sub(r'\\s+', ' ', text)\n    \n    return text\n\n# ============================================================================\n# EXTRACT ADDITIONAL TRAINING DATA FROM PUBLICATIONS\n# ============================================================================\n\ndef extract_parallel_texts_from_publications(publications_df, published_texts_df, max_pairs=2000):\n    \"\"\"\n    Extract Akkadian-English parallel texts from publications.csv\n    This is KEY to improving your score!\n    \"\"\"\n    parallel_data = []\n    \n    print(\"🔍 Extracting additional training data from publications...\")\n    \n    # Filter pages that contain both Akkadian and translations\n    akkadian_pages = publications_df[publications_df['has_akkadian'] == True].copy()\n    \n    for idx, row in tqdm(akkadian_pages.iterrows(), total=min(len(akkadian_pages), 1000)):\n        page_text = str(row['page_text'])\n        \n        # Look for patterns like:\n        # \"transliteration\" followed by translation\n        # Common patterns in academic publications\n        \n        # Pattern 1: Line numbers with transliteration and translation\n        pattern1 = r'(\\d+[\\'\\\"]*)\\s+([a-z\\-{}.₀-₉\\s]+)\\s+[\"\"\"]\\s*([^\"\"\"]+)[\"\"\"]'\n        matches = re.findall(pattern1, page_text, re.IGNORECASE)\n        \n        for match in matches[:10]:  # Limit per page\n            line_num, translit, trans = match\n            translit_clean = clean_transliteration(translit)\n            trans_clean = clean_translation(trans)\n            \n            if len(translit_clean) > 10 and len(trans_clean) > 10:\n                parallel_data.append({\n                    'transliteration': translit_clean,\n                    'translation': trans_clean,\n                    'source': 'publication_extract'\n                })\n        \n        if len(parallel_data) >= max_pairs:\n            break\n    \n    print(f\"✅ Extracted {len(parallel_data)} additional training pairs\")\n    return pd.DataFrame(parallel_data)\n\n# ============================================================================\n# DATASET CLASS\n# ============================================================================\n\nclass AkkadianDataset(Dataset):\n    def __init__(self, transliterations, translations, tokenizer, max_length=512):\n        self.transliterations = transliterations\n        self.translations = translations\n        self.tokenizer = tokenizer\n        self.max_length = max_length\n        \n    def __len__(self):\n        return len(self.transliterations)\n    \n    def __getitem__(self, idx):\n        source = \"translate Akkadian to English: \" + str(self.transliterations[idx])\n        target = str(self.translations[idx])\n        \n        source_enc = self.tokenizer(\n            source, max_length=self.max_length, padding=\"max_length\",\n            truncation=True, return_tensors=\"pt\"\n        )\n        \n        with self.tokenizer.as_target_tokenizer():\n            target_enc = self.tokenizer(\n                target, max_length=self.max_length, padding=\"max_length\",\n                truncation=True, return_tensors=\"pt\"\n            )\n        \n        labels = target_enc[\"input_ids\"].squeeze()\n        labels[labels == self.tokenizer.pad_token_id] = -100\n        \n        return {\n            \"input_ids\": source_enc[\"input_ids\"].squeeze(),\n            \"attention_mask\": source_enc[\"attention_mask\"].squeeze(),\n            \"labels\": labels\n        }\n\n# ============================================================================\n# INFERENCE WITH ENSEMBLE\n# ============================================================================\n\ndef run_inference_optimized(model, tokenizer, test_df, device=\"cuda\", batch_size=8):\n    \"\"\"Optimized inference with better beam search\"\"\"\n    \n    model.eval()\n    model.to(device)\n    \n    predictions = []\n    transliterations = test_df['transliteration'].tolist()\n    \n    print(f\"🔮 Translating {len(transliterations)} sentences...\")\n    \n    for i in tqdm(range(0, len(transliterations), batch_size)):\n        batch_texts = transliterations[i:i+batch_size]\n        batch_inputs = [\"translate Akkadian to English: \" + str(t) for t in batch_texts]\n        \n        inputs = tokenizer(\n            batch_inputs,\n            max_length=512,\n            padding=True,\n            truncation=True,\n            return_tensors=\"pt\"\n        ).to(device)\n        \n        with torch.no_grad():\n            outputs = model.generate(\n                **inputs,\n                max_length=512,\n                num_beams=5,\n                length_penalty=1.0,\n                early_stopping=True,\n                no_repeat_ngram_size=2,\n                num_return_sequences=1\n            )\n        \n        decoded = tokenizer.batch_decode(outputs, skip_special_tokens=True)\n        predictions.extend([d.strip() for d in decoded])\n        \n        # Memory management\n        del inputs, outputs\n        torch.cuda.empty_cache()\n    \n    return predictions\n\n# ============================================================================\n# POST-PROCESSING RULES\n# ============================================================================\n\ndef apply_domain_rules(translation):\n    \"\"\"Apply Assyrian-specific rules to improve translations\"\"\"\n    \n    if not translation or len(translation) < 2:\n        return \"broken text\"\n    \n    # Common Assyrian merchant terms\n    replacements = {\n        r'\\btin\\b': 'tin',\n        r'\\bsilver\\b': 'silver',\n        r'\\bcopper\\b': 'copper',\n        r'\\btextile\\b': 'textile',\n        r'\\bmerchant\\b': 'merchant',\n        r'\\bkanesh\\b': 'Kanesh',\n        r'\\bassur\\b': 'Assur',\n    }\n    \n    for pattern, replacement in replacements.items():\n        translation = re.sub(pattern, replacement, translation, flags=re.IGNORECASE)\n    \n    # Ensure proper capitalization of place names\n    translation = re.sub(r'\\bkanesh\\b', 'Kanesh', translation, flags=re.IGNORECASE)\n    translation = re.sub(r'\\bassur\\b', 'Assur', translation, flags=re.IGNORECASE)\n    \n    # Clean up spacing\n    translation = re.sub(r'\\s+', ' ', translation).strip()\n    \n    return translation\n\n# ============================================================================\n# MAIN PIPELINE\n# ============================================================================\n\ndef main():\n    \"\"\"Optimized main pipeline for Kaggle T4 GPU\"\"\"\n    \n    # Paths\n    TRAIN_PATH = \"/kaggle/input/deep-past-initiative-machine-translation/train.csv\"\n    TEST_PATH = \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\"\n    PUBLISHED_PATH = \"/kaggle/input/deep-past-initiative-machine-translation/published_texts.csv\"\n    PUBLICATIONS_PATH = \"/kaggle/input/deep-past-initiative-machine-translation/publications.csv\"\n    \n    # Use your pretrained model or start from base\n    MODEL_PATH = \"/kaggle/input/train-gap-all-2/byt5-base-akkadian_gap4/\"  # Your model\n    # MODEL_PATH = \"google/byt5-base\"  # Or start from scratch\n    \n    DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n    \n    # Load data\n    print(\"📂 Loading datasets...\")\n    train_df = pd.read_csv(TRAIN_PATH)\n    test_df = pd.read_csv(TEST_PATH)\n    \n    # Clean training data\n    print(\"🧹 Preprocessing training data...\")\n    train_df['transliteration'] = train_df['transliteration'].apply(clean_transliteration)\n    train_df['translation'] = train_df['translation'].apply(clean_translation)\n    \n    # Filter valid pairs\n    train_df = train_df[\n        (train_df['transliteration'].str.len() > 5) & \n        (train_df['translation'].str.len() > 5)\n    ].reset_index(drop=True)\n    \n    print(f\"✅ Training samples: {len(train_df)}\")\n    \n    # TRY TO EXTRACT MORE DATA (if publications.csv available and time permits)\n    try:\n        publications_df = pd.read_csv(PUBLICATIONS_PATH)\n        published_texts_df = pd.read_csv(PUBLISHED_PATH)\n        \n        additional_data = extract_parallel_texts_from_publications(\n            publications_df, published_texts_df, max_pairs=1000\n        )\n        \n        if len(additional_data) > 0:\n            train_df = pd.concat([train_df, additional_data], ignore_index=True)\n            print(f\"✅ Total training samples with extracted data: {len(train_df)}\")\n    except Exception as e:\n        print(f\"⚠️ Could not extract additional data: {e}\")\n    \n    # Clean test data\n    print(\"🧹 Preprocessing test data...\")\n    test_df['transliteration'] = test_df['transliteration'].apply(clean_transliteration)\n    \n    # Load model\n    print(f\"📥 Loading model from {MODEL_PATH}...\")\n    tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)\n    model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_PATH).to(DEVICE)\n    \n    # Run inference\n    predictions = run_inference_optimized(\n        model, tokenizer, test_df, \n        device=DEVICE, batch_size=16\n    )\n    \n    # Post-process\n    print(\"✨ Applying domain-specific rules...\")\n    predictions = [apply_domain_rules(p) for p in predictions]\n    \n    # Create submission\n    submission = pd.DataFrame({\n        \"id\": test_df[\"id\"],\n        \"translation\": predictions\n    })\n    \n    submission.to_csv(\"submission.csv\", index=False)\n    print(\"✅ Submission saved!\")\n    print(\"\\n📊 Sample predictions:\")\n    print(submission.head(10))\n    \n    return submission\n\nif __name__ == \"__main__\":\n    # Memory optimization\n    torch.cuda.empty_cache()\n    gc.collect()\n    \n    submission = main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T09:18:34.194654Z","iopub.execute_input":"2026-01-02T09:18:34.195143Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}