{"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":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\nUsing My best ByT5 Optimized Model\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# ==============\n\nINFERENCE_CONFIG = {\n    # Path to test data\n    \"test_data_path\": \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\",\n    \n    # Path to trained model\n    \"trained_model_path\": \"/kaggle/input/final-byt5/byt5-akkadian-optimized-34x\",\n    \n    # Device configuration\n    \"computation_device\": torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\"),\n    \n    # Tokenization settings\n    \"maximum_sequence_length\": 512,\n    \n    # Batch processing\n    \"inference_batch_size\": 8,\n    \n    # Text generation parameters\n    \"generation_parameters\": {\n        \"num_beams\": 8,              # Number of beam search paths\n        \"max_new_tokens\": 512,        # Maximum output length\n        \"length_penalty\": 1.09,       # Penalty for longer sequences\n        \"early_stopping\": True        # Stop when all beams finish\n    }\n}\n\n\n# INPUT PREPROCESSING FUNCTIONS\n# =============================\n\ndef preprocess_input_text(transliteration_text):\n    \"\"\"\n    Clean and normalize input Akkadian transliteration\n    \n    Args:\n        transliteration_text: Raw transliteration from dataset\n        \n    Returns:\n        Cleaned transliteration with normalized gap markers\n    \"\"\"\n    # Handle missing values\n    if pd.isna(transliteration_text): \n        return \"\"\n    \n    # Convert to string\n    cleaned_text = str(transliteration_text)\n    \n    # Normalize large gaps (multiple dots or ellipsis to <big_gap>)\n    cleaned_text = re.sub(r'(\\.{3,}|…+|……)', '<big_gap>', cleaned_text)\n    \n    # Normalize small gaps (x patterns to <gap>)\n    cleaned_text = re.sub(r'(xx+|\\s+x\\s+)', '<gap>', cleaned_text)\n    \n    return cleaned_text\n\n\n# OUTPUT POSTPROCESSING FUNCTIONS\n# ===============================\n\ndef postprocess_translation_output(translation_text):\n    \"\"\"\n    Clean and normalize model output translation\n    \n    Args:\n        translation_text: Raw output from model\n        \n    Returns:\n        Cleaned and formatted English translation\n    \"\"\"\n    # Handle invalid outputs\n    if not isinstance(translation_text, str) or not translation_text.strip(): \n        return \"\"\n    \n    processed_translation = translation_text\n    \n    # Step 1: Normalize special Akkadian characters\n    processed_translation = processed_translation.replace('ḫ', 'h').replace('Ḫ', 'H')\n    \n    # Step 2: Convert subscript numbers to regular numbers\n    subscript_to_normal = str.maketrans(\"₀₁₂₃₄₅₆₇₈₉\", \"0123456789\")\n    processed_translation = processed_translation.translate(subscript_to_normal)\n    \n    # Step 3: Normalize gap markers in output\n    processed_translation = re.sub(r'(\\[x\\]|\\(x\\)|\\bx\\b)', '<gap>', processed_translation, flags=re.I)\n    processed_translation = re.sub(r'(\\.{3,}|…|\\[\\.+\\])', '<big_gap>', processed_translation)\n    \n    # Step 4: Merge adjacent gap markers\n    processed_translation = re.sub(r'<gap>\\s*<gap>', ' <big_gap> ', processed_translation)\n    processed_translation = re.sub(r'<big_gap>\\s*<big_gap>', ' <big_gap> ', processed_translation)\n    \n    # Step 5: Remove grammatical annotations\n    processed_translation = re.sub(\n        r'\\((fem|plur|pl|sing|singular|plural|\\?|!)\\.?\\s*\\w*\\)', \n        '', \n        processed_translation, \n        flags=re.I\n    )\n    \n    # Step 6: Protect gap markers during character removal\n    processed_translation = processed_translation.replace('<gap>', '\\x00GAP\\x00')\n    processed_translation = processed_translation.replace('<big_gap>', '\\x00BIG\\x00')\n    \n    # Step 7: Remove unwanted punctuation and symbols\n    forbidden_characters = '!?()\"—–<>⌈⌋⌊[]+ʾ/;'\n    processed_translation = processed_translation.translate(str.maketrans('', '', forbidden_characters))\n    \n    # Step 8: Restore gap markers\n    processed_translation = processed_translation.replace('\\x00GAP\\x00', ' <gap> ')\n    processed_translation = processed_translation.replace('\\x00BIG\\x00', ' <big_gap> ')\n    \n    # Step 9: Convert decimal fractions to Unicode fraction symbols\n    fraction_patterns = {\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 decimal_pattern, fraction_symbol in fraction_patterns.items():\n        processed_translation = re.sub(r'(\\d+)' + decimal_pattern, r'\\1' + fraction_symbol, processed_translation)\n        processed_translation = re.sub(r'\\b0' + decimal_pattern, fraction_symbol.strip(), processed_translation)\n    \n    # Step 10: Remove repeated words\n    processed_translation = re.sub(r'\\b(\\w+)(?:\\s+\\1\\b)+', r'\\1', processed_translation)\n    \n    # Step 11: Remove repeated n-grams (phrases)\n    for ngram_size in range(4, 1, -1):\n        ngram_pattern = r'\\b((?:\\w+\\s+){' + str(ngram_size-1) + r'}\\w+)(?:\\s+\\1\\b)+'\n        processed_translation = re.sub(ngram_pattern, r'\\1', processed_translation)\n    \n    # Step 12: Fix spacing around punctuation\n    processed_translation = re.sub(r'\\s+([.,:])', r'\\1', processed_translation)\n    processed_translation = re.sub(r'([.,])\\1+', r'\\1', processed_translation)\n    \n    # Step 13: Final whitespace normalization\n    processed_translation = re.sub(r'\\s+', ' ', processed_translation).strip().strip('-').strip()\n    \n    return processed_translation\n\n\n# DATASET CLASS\n# ==============\n\nclass AkkadianTranslationDataset(Dataset):\n    \"\"\"\n    PyTorch Dataset for Akkadian transliterations\n    \"\"\"\n    def __init__(self, dataframe):\n        \"\"\"\n        Initialize dataset with transliteration data\n        \n        Args:\n            dataframe: Pandas DataFrame with 'id' and 'transliteration' columns\n        \"\"\"\n        self.sample_ids = dataframe['id'].tolist()\n        \n        # Add task prefix to each transliteration\n        self.input_texts = [\n            \"translate Akkadian to English: \" + str(transliteration) \n            for transliteration in dataframe['transliteration']\n        ]\n    \n    def __len__(self):\n        \"\"\"Return total number of samples\"\"\"\n        return len(self.sample_ids)\n    \n    def __getitem__(self, index):\n        \"\"\"\n        Get single sample\n        \n        Args:\n            index: Sample index\n            \n        Returns:\n            Tuple of (sample_id, input_text)\n        \"\"\"\n        return self.sample_ids[index], self.input_texts[index]\n\n\n# MAIN INFERENCE PIPELINE\n# ========================\n\n# Load test data\nprint(\"Loading test data...\")\ntest_dataframe = pd.read_csv(INFERENCE_CONFIG['test_data_path'])\ntest_dataframe['transliteration'] = test_dataframe['transliteration'].apply(preprocess_input_text)\nprint(f\"Loaded {len(test_dataframe)} test samples\")\n\n# Load trained model\nprint(\"\\nLoading trained model...\")\ntranslation_model = AutoModelForSeq2SeqLM.from_pretrained(\n    INFERENCE_CONFIG['trained_model_path']\n).to(INFERENCE_CONFIG['computation_device']).eval()\n\n# Load tokenizer\nmodel_tokenizer = AutoTokenizer.from_pretrained(INFERENCE_CONFIG['trained_model_path'])\nprint(\"Model and tokenizer loaded\")\n\n# Create dataset\ninference_dataset = AkkadianTranslationDataset(test_dataframe)\n\n# Create dataloader with custom collate function\ndef collate_batch_function(batch_samples):\n    \"\"\"\n    Collate function to prepare batches\n    \n    Args:\n        batch_samples: List of (id, text) tuples\n        \n    Returns:\n        Tuple of (list_of_ids, tokenized_inputs)\n    \"\"\"\n    batch_ids = [sample[0] for sample in batch_samples]\n    batch_texts = [sample[1] for sample in batch_samples]\n    \n    # Tokenize all texts in batch\n    tokenized_batch = model_tokenizer(\n        batch_texts,\n        max_length=INFERENCE_CONFIG['maximum_sequence_length'],\n        padding=True,\n        truncation=True,\n        return_tensors=\"pt\"\n    )\n    \n    return batch_ids, tokenized_batch\n\ninference_dataloader = DataLoader(\n    inference_dataset,\n    batch_size=INFERENCE_CONFIG['inference_batch_size'],\n    shuffle=False,\n    num_workers=2,\n    collate_fn=collate_batch_function\n)\n\nprint(f\"\\nDataloader created with {len(inference_dataloader)} batches\")\n\n# Run inference\nprint(\"\\nRunning inference...\")\nprediction_results = []\n\nwith torch.inference_mode():\n    for batch_ids, tokenized_inputs in inference_dataloader:\n        # Generate translations\n        generated_outputs = translation_model.generate(\n            input_ids=tokenized_inputs.input_ids.to(INFERENCE_CONFIG['computation_device']),\n            attention_mask=tokenized_inputs.attention_mask.to(INFERENCE_CONFIG['computation_device']),\n            **INFERENCE_CONFIG['generation_parameters']\n        )\n        \n        # Decode generated token IDs to text\n        decoded_translations = model_tokenizer.batch_decode(\n            generated_outputs, \n            skip_special_tokens=True\n        )\n        \n        # Postprocess each translation\n        cleaned_translations = [\n            postprocess_translation_output(translation) \n            for translation in decoded_translations\n        ]\n        \n        # Store results\n        prediction_results.extend(zip(batch_ids, cleaned_translations))\n\nprint(\"Inference complete\")\n\n# Create submission file\nprint(\"\\nCreating submission file...\")\nsubmission_dataframe = pd.DataFrame(\n    prediction_results, \n    columns=['id', 'translation']\n)\n\nsubmission_dataframe.to_csv(\"submission.csv\", index=False)\nprint(f\"Submission saved with {len(submission_dataframe)} translations\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-29T09:58:48.721938Z","iopub.execute_input":"2026-01-29T09:58:48.722218Z","iopub.status.idle":"2026-01-29T10:00:02.443140Z","shell.execute_reply.started":"2026-01-29T09:58:48.722194Z","shell.execute_reply":"2026-01-29T10:00:02.442368Z"}},"outputs":[],"execution_count":null}]}