{"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":[{"sourceType":"competition","sourceId":121150,"databundleVersionId":15833391},{"sourceType":"modelInstanceVersion","sourceId":692580,"databundleVersionId":15023827,"modelInstanceId":525132,"modelId":539167},{"sourceType":"modelInstanceVersion","sourceId":766576,"databundleVersionId":15842388,"modelInstanceId":585752,"modelId":598037}],"dockerImageVersionId":31259,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport gc\nimport re\nimport pandas as pd\nimport numpy as np\nimport torch\nfrom sklearn.model_selection import train_test_split\nfrom datasets import Dataset\nfrom transformers import (\n    AutoTokenizer, \n    AutoModelForSeq2SeqLM, \n    DataCollatorForSeq2Seq, \n    Seq2SeqTrainingArguments, \n    Seq2SeqTrainer,\n    EarlyStoppingCallback\n\n)\nfrom sentence_transformers import SentenceTransformer, util","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-26T17:31:01.782164Z","iopub.execute_input":"2026-02-26T17:31:01.782426Z","iopub.status.idle":"2026-02-26T17:31:43.419983Z","shell.execute_reply.started":"2026-02-26T17:31:01.782399Z","shell.execute_reply":"2026-02-26T17:31:43.418962Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# STEP 4: OPTIMIZED INFERENCE FOR SUBMISSION\n# ============================================================================\n\nimport torch\nfrom torch.utils.data import Dataset, DataLoader, Sampler\nfrom tqdm.auto import tqdm\nimport pandas as pd\nimport re\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"STEP 4: INFERENCE PREPARATION\")\nprint(\"=\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-26T17:31:43.421887Z","iopub.execute_input":"2026-02-26T17:31:43.422608Z","iopub.status.idle":"2026-02-26T17:31:43.429071Z","shell.execute_reply.started":"2026-02-26T17:31:43.422573Z","shell.execute_reply":"2026-02-26T17:31:43.428028Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# INFERENCE CONFIGURATION\n# ============================================================================\n\nclass InferenceConfig:\n    \"\"\"\n    Optimized inference settings based on community best practices.\n    \n    Key optimizations:\n    - Bucket batching (groups similar-length samples)\n    - Length penalty 1.3 (encourages complete translations)\n    - Beam search 8 (quality vs speed balance)\n    \"\"\"\n    MODEL_PATH = \"/kaggle/working/byt5_with_lex_tags\"\n    TEST_DATA_PATH = \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\"\n    \n    DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n    BATCH_SIZE = 16          # Can use larger batch for inference\n    MAX_LENGTH = 512\n    \n    # === Generation parameters (from 35.1 stack) ===\n    NUM_BEAMS = 8            # ★ Increased from 4 (better quality)\n    LENGTH_PENALTY = 1.3     # ★ Encourages longer, complete translations\n    MAX_NEW_TOKENS = 512     # ★ Allow full-length outputs\n    EARLY_STOPPING = True\n    \n    # Bucket batching\n    USE_BUCKET_BATCHING = True\n    NUM_BUCKETS = 4","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-26T17:31:43.433913Z","iopub.execute_input":"2026-02-26T17:31:43.434201Z","iopub.status.idle":"2026-02-26T17:31:43.450985Z","shell.execute_reply.started":"2026-02-26T17:31:43.434169Z","shell.execute_reply":"2026-02-26T17:31:43.450018Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# BUCKET BATCH SAMPLER (SERA'S OPTIMIZATION)\n# ============================================================================\n\nclass BucketBatchSampler(Sampler):\n    \"\"\"\n    Groups samples by similar length to minimize padding waste.\n    \n    Benefits:\n    - Reduces padding by 20-40%\n    - Faster inference\n    - Less GPU memory waste\n    \"\"\"\n    def __init__(self, lengths, batch_size, num_buckets=4):\n        self.lengths = lengths\n        self.batch_size = batch_size\n        self.num_buckets = num_buckets\n        \n        # Sort indices by length\n        sorted_indices = sorted(range(len(lengths)), key=lambda i: lengths[i])\n        \n        # Divide into buckets\n        bucket_size = len(sorted_indices) // num_buckets\n        self.buckets = []\n        for i in range(num_buckets):\n            start = i * bucket_size\n            end = start + bucket_size if i < num_buckets - 1 else len(sorted_indices)\n            self.buckets.append(sorted_indices[start:end])\n    \n    def __iter__(self):\n        for bucket in self.buckets:\n            indices = bucket.copy()\n            for i in range(0, len(indices), self.batch_size):\n                yield indices[i:i + self.batch_size]\n    \n    def __len__(self):\n        return sum(len(bucket) // self.batch_size for bucket in self.buckets)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-26T17:31:43.452950Z","iopub.execute_input":"2026-02-26T17:31:43.453223Z","iopub.status.idle":"2026-02-26T17:31:43.469040Z","shell.execute_reply.started":"2026-02-26T17:31:43.453196Z","shell.execute_reply":"2026-02-26T17:31:43.467906Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Looking for personal names and not translate them\ndef normalize(s):\n    return (\n        str(s)\n        .replace(\"Ḫ\", \"H\")\n        .replace(\"ḫ\", \"h\")\n        .strip()\n    )\n    \nname_path = \"/kaggle/input/deep-past-initiative-machine-translation/OA_Lexicon_eBL.csv\"\nlex_df = pd.read_csv(name_path)\npn_set = set(\n    lex_df.loc[lex_df[\"type\"] == \"PN\", \"form\"]\n    .dropna()\n    .map(normalize)\n)\n\ngn_set = set(\n    lex_df.loc[lex_df[\"type\"] == \"GN\", \"form\"]\n    .dropna()\n    .map(normalize))\n\ndef tag_person_names(text: str) -> str:\n    tokens = text.split()\n    out = []\n    for t in tokens:\n        nt = normalize(t)\n        if nt in pn_set:\n            out.append(f\"<PN>{t}</PN>\")\n        elif nt in gn_set:\n            out.append(f\"<GN>{t}</GN>\")\n        else:\n            out.append(t)\n    return \" \".join(out)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-26T17:32:56.848473Z","iopub.execute_input":"2026-02-26T17:32:56.849430Z","iopub.status.idle":"2026-02-26T17:32:57.000228Z","shell.execute_reply.started":"2026-02-26T17:32:56.849333Z","shell.execute_reply":"2026-02-26T17:32:56.998954Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# PREPROCESSING FOR TEST DATA\n# ============================================================================\n\ndef preprocess_test_input(text):\n    \"\"\"\n    Apply same preprocessing as training data.\n    \n    CRITICAL: Must match training preprocessing exactly!\n    \"\"\"\n    if pd.isna(text):\n        return \"\"\n    \n    text = str(text)\n    \n    # Normalize Ḫ/ḫ → H/h\n    text = text.replace('Ḫ', 'H').replace('ḫ', 'h')\n    \n    # Gap normalization\n    text = re.sub(r'\\[x\\]', '<gap>', text)\n    text = re.sub(r'\\(x\\)', '<gap>', text)\n    text = re.sub(r'\\bx\\b', '<gap>', text)\n    text = re.sub(r'\\.{3,}', '<big_gap>', text)\n    text = re.sub(r'…+', '<big_gap>', text)\n    \n    # Collapse consecutive gaps\n    text = re.sub(r'<gap>\\s*<gap>', '<big_gap>', text)\n    text = re.sub(r'<big_gap>\\s*<gap>', '<big_gap>', text)\n    text = re.sub(r'<gap>\\s*<big_gap>', '<big_gap>', text)\n    text = re.sub(r'<big_gap>\\s*<big_gap>', '<big_gap>', text)\n    \n    # Clean whitespace\n    text = re.sub(r'\\s+', ' ', text).strip()\n    text = tag_person_names(text)\n    return text\n\n\ndef postprocess_output(text):\n    \"\"\"\n    Clean model output to match expected format.\n    \n    Steps:\n    1. Remove forbidden characters\n    2. Normalize remaining formatting\n    3. Handle edge cases\n    \"\"\"\n    if not isinstance(text, str) or not text.strip():\n        return \"broken text\"  # Fallback for empty outputs\n    \n    # === 1. Protect gap markers ===\n    text = text.replace('<gap>', '\\x00GAP\\x00')\n    text = text.replace('<big_gap>', '\\x00BIGGAP\\x00')\n    \n    # === 2. Remove forbidden characters ===\n    # Based on competition guidelines and community findings\n    forbidden_chars = '!?()[]<>\"\"—–⌈⌋⌊/;˹˺'\n    text = text.translate(str.maketrans('', '', forbidden_chars))\n    \n    # === 3. Normalize quotes and dashes ===\n    text = text.replace('\"', '\"').replace('\"', '\"')\n    text = text.replace(''', \"'\").replace(''', \"'\")\n    text = text.replace('—', '-').replace('–', '-')\n    \n    # === 4. Remove parenthetical annotations ===\n    text = re.sub(r'\\(fem\\.?\\)', '', text, flags=re.I)\n    text = re.sub(r'\\(plur?\\.?\\)', '', text, flags=re.I)\n    text = re.sub(r'\\(sing\\.?\\)', '', text, flags=re.I)\n    \n    # === 5. Restore gap markers ===\n    text = text.replace('\\x00GAP\\x00', '<gap>')\n    text = text.replace('\\x00BIGGAP\\x00', '<big_gap>')\n    \n    # === 6. Handle fractions (if model outputs decimals) ===\n    text = re.sub(r'(\\d+)\\.5\\b', r'\\1 ½', text)\n    text = re.sub(r'(\\d+)\\.25\\b', r'\\1 ¼', text)\n    \n    # === 7. Remove word repetitions (model artifact) ===\n    text = re.sub(r'\\b(\\w+)(?:\\s+\\1\\b)+', r'\\1', text)\n\n    # === 8. Remove the name marks ===\n    text = text.replace(\"<PN>\", \"\").replace(\"</PN>\", \"\")\n\n    # === 9. Final cleanup ===\n    text = re.sub(r'\\s+', ' ', text).strip()\n    \n    # Ensure non-empty\n    if not text or len(text) < 3:\n        return \"broken text\"\n    \n    return text","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-26T17:33:32.552953Z","iopub.execute_input":"2026-02-26T17:33:32.553297Z","iopub.status.idle":"2026-02-26T17:33:32.566444Z","shell.execute_reply.started":"2026-02-26T17:33:32.553269Z","shell.execute_reply":"2026-02-26T17:33:32.565391Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# INFERENCE DATASET\n# ============================================================================\n\nclass AkkadianInferenceDataset(Dataset):\n    \"\"\"\n    Dataset for test data with bucket batching support.\n    \"\"\"\n    def __init__(self, df, prefix=\"translate Akkadian to English: \"):\n        self.ids = df['id'].tolist()\n        \n        # Preprocess and add prefix\n        self.texts = [\n            prefix + preprocess_test_input(t) \n            for t in df['transliteration']\n        ]\n        \n        # Calculate lengths for bucket batching\n        self.lengths = [len(t.split()) for t in self.texts]\n    \n    def __len__(self):\n        return len(self.ids)\n    \n    def __getitem__(self, idx):\n        return self.ids[idx], self.texts[idx]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-26T17:33:37.391753Z","iopub.execute_input":"2026-02-26T17:33:37.392098Z","iopub.status.idle":"2026-02-26T17:33:37.399212Z","shell.execute_reply.started":"2026-02-26T17:33:37.392068Z","shell.execute_reply":"2026-02-26T17:33:37.397703Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# LOAD TEST DATA\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"LOADING TEST DATA\")\nprint(\"=\"*60)\n\ntest_df = pd.read_csv(\"/kaggle/input/deep-past-initiative-machine-translation/test.csv\")\nprint(f\"Test samples: {len(test_df)}\")\n\n# Show preprocessing example\nprint(\"\\nPreprocessing example:\")\nprint(f\"Original: {test_df['transliteration'].iloc[0][:100]}...\")\nprint(f\"Processed: {preprocess_test_input(test_df['transliteration'].iloc[0])[:100]}...\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-26T17:33:39.117531Z","iopub.execute_input":"2026-02-26T17:33:39.117883Z","iopub.status.idle":"2026-02-26T17:33:39.129896Z","shell.execute_reply.started":"2026-02-26T17:33:39.117852Z","shell.execute_reply":"2026-02-26T17:33:39.128737Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# LOAD TRAINED MODEL\n# ============================================================================\nimport gc\nprint(\"\\n\" + \"=\"*60)\nprint(\"LOADING TRAINED MODEL\")\nprint(\"=\"*60)\n\n# Clear memory first\ngc.collect()\ntorch.cuda.empty_cache()\n\n# Load model and tokenizer\nMODEL_DIR = \"/kaggle/input/models/loopassembly/akkadian-byt5-translator/pytorch/default/1/akkadian_byt5_trained\"\n\ninference_tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR)\ninference_model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_DIR).to(InferenceConfig.DEVICE)\ninference_model.eval()\n\nprint(f\"✓ Model loaded on {InferenceConfig.DEVICE}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-26T17:33:40.527021Z","iopub.execute_input":"2026-02-26T17:33:40.527344Z","iopub.status.idle":"2026-02-26T17:33:42.273763Z","shell.execute_reply.started":"2026-02-26T17:33:40.527315Z","shell.execute_reply":"2026-02-26T17:33:42.272880Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# SETUP DATALOADER WITH BUCKET BATCHING (FIXED)\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"PREPARING DATALOADER\")\nprint(\"=\"*60)\n\ntest_dataset = AkkadianInferenceDataset(test_df)\n\ndef collate_fn(batch):\n    \"\"\"Collate function for batching.\"\"\"\n    ids = [item[0] for item in batch]\n    texts = [item[1] for item in batch]\n    \n    # Tokenize\n    encoded = inference_tokenizer(\n        texts,\n        max_length=InferenceConfig.MAX_LENGTH,\n        padding=True,\n        truncation=True,\n        return_tensors=\"pt\"\n    )\n    \n    return ids, encoded\n\n# === FIXED: Separate DataLoader creation based on batching strategy ===\nif InferenceConfig.USE_BUCKET_BATCHING:\n    print(\"Using bucket batching (Sera's optimization)\")\n    sampler = BucketBatchSampler(\n        test_dataset.lengths, \n        InferenceConfig.BATCH_SIZE,\n        InferenceConfig.NUM_BUCKETS\n    )\n    \n    # When using batch_sampler, don't pass batch_size or shuffle\n    test_loader = DataLoader(\n        test_dataset,\n        batch_sampler=sampler,\n        collate_fn=collate_fn,\n        num_workers=2\n    )\nelse:\n    print(\"Using standard batching\")\n    \n    # Standard DataLoader without batch_sampler\n    test_loader = DataLoader(\n        test_dataset,\n        batch_size=InferenceConfig.BATCH_SIZE,\n        shuffle=False,\n        collate_fn=collate_fn,\n        num_workers=2\n    )\n\nprint(f\"✓ DataLoader ready\")\nprint(f\"Batch size: {InferenceConfig.BATCH_SIZE}\")\nprint(f\"Bucket batching: {InferenceConfig.USE_BUCKET_BATCHING}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-26T17:33:43.731884Z","iopub.execute_input":"2026-02-26T17:33:43.732216Z","iopub.status.idle":"2026-02-26T17:33:43.742257Z","shell.execute_reply.started":"2026-02-26T17:33:43.732188Z","shell.execute_reply":"2026-02-26T17:33:43.741357Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# RUN INFERENCE\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"RUNNING INFERENCE\")\nprint(\"=\"*60)\nprint(f\"Generation settings:\")\nprint(f\"  - Beam search: {InferenceConfig.NUM_BEAMS}\")\nprint(f\"  - Length penalty: {InferenceConfig.LENGTH_PENALTY}\")\nprint(f\"  - Max tokens: {InferenceConfig.MAX_NEW_TOKENS}\")\nprint(\"=\"*60 + \"\\n\")\n\npredictions = []\n\nwith torch.inference_mode():\n    for batch_ids, encoded_inputs in tqdm(test_loader, desc=\"Translating\"):\n        # Move to device\n        input_ids = encoded_inputs.input_ids.to(InferenceConfig.DEVICE)\n        attention_mask = encoded_inputs.attention_mask.to(InferenceConfig.DEVICE)\n        \n        # Generate translations\n        outputs = inference_model.generate(\n            input_ids=input_ids,\n            attention_mask=attention_mask,\n            num_beams=InferenceConfig.NUM_BEAMS,\n            max_new_tokens=InferenceConfig.MAX_NEW_TOKENS,\n            length_penalty=InferenceConfig.LENGTH_PENALTY,\n            early_stopping=InferenceConfig.EARLY_STOPPING\n        )\n        \n        # Decode\n        decoded = inference_tokenizer.batch_decode(outputs, skip_special_tokens=True)\n        \n        # Postprocess\n        cleaned = [postprocess_output(text) for text in decoded]\n        \n        # Store with IDs\n        predictions.extend(zip(batch_ids, cleaned))\n\nprint(f\"\\n✓ Generated {len(predictions)} translations\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-26T17:33:45.223328Z","iopub.execute_input":"2026-02-26T17:33:45.223775Z","execution_failed":"2026-02-26T17:35:07.447Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(cleaned)","metadata":{"trusted":true,"execution":{"execution_failed":"2026-02-26T17:35:07.448Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CREATE SUBMISSION\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"CREATING SUBMISSION FILE\")\nprint(\"=\"*60)\n\n# Create submission dataframe\nsubmission_df = pd.DataFrame(predictions, columns=['id', 'translation'])\n\n# Ensure correct order (match test.csv)\nsubmission_df = submission_df.sort_values('id').reset_index(drop=True)\n\n# Show examples\nprint(\"\\nSample predictions:\")\nfor i in range(min(3, len(submission_df))):\n    print(f\"\\n[{i}] ID: {submission_df.iloc[i]['id']}\")\n    print(f\"Translation: {submission_df.iloc[i]['translation'][:150]}...\")\n\n# Save\nsubmission_df.to_csv('submission.csv', index=False)\nprint(f\"\\n✓ Submission saved: submission.csv\")\nprint(f\"Total translations: {len(submission_df)}\")\n\n# Validation checks\nprint(\"\\n\" + \"=\"*60)\nprint(\"VALIDATION CHECKS\")\nprint(\"=\"*60)\nprint(f\"✓ All IDs present: {len(submission_df) == len(test_df)}\")\nprint(f\"✓ No missing translations: {submission_df['translation'].notna().all()}\")\nprint(f\"✓ No empty translations: {(submission_df['translation'].str.len() > 0).all()}\")\nprint(f\"Average translation length: {submission_df['translation'].str.len().mean():.1f} chars\")\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"✓ STEP 4 COMPLETE - READY TO SUBMIT!\")\nprint(\"=\"*60)","metadata":{"trusted":true,"execution":{"execution_failed":"2026-02-26T17:35:07.448Z"}},"outputs":[],"execution_count":null}]}