{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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":14398637,"sourceType":"datasetVersion","datasetId":9195725},{"sourceId":14523528,"sourceType":"datasetVersion","datasetId":9275882},{"sourceId":14661932,"sourceType":"datasetVersion","datasetId":9366631},{"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":"# ------------------------------------------------------------\n# Test 3: Adaptive Beams + Vectorized Postprocessing COMBINED\n# Expected: 35.1 or 35.2 (micro-gains might stack!)\n# ------------------------------------------------------------\n\nimport os\nos.environ[\"OMP_NUM_THREADS\"] = \"4\"\nos.environ[\"MKL_NUM_THREADS\"] = \"4\"\nos.environ[\"CUDA_LAUNCH_BLOCKING\"] = \"0\"\nos.environ[\"TORCH_CUDNN_V8_API_ENABLED\"] = \"1\"\nos.environ[\"TOKENIZERS_PARALLELISM\"] = \"true\"\n\nimport re\nimport pandas as pd\nimport torch\nfrom torch.utils.data import Dataset, DataLoader, Sampler\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm.auto import tqdm\n\n# ============================================================\n# CONFIG\n# ============================================================\n\nCONFIG = {\n    \"test_path\": \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\",\n    \"model_path\": \"/kaggle/input/final-byt5/byt5-akkadian-optimized-34x\",\n    \"max_length\": 512,\n    \"batch_size\": 8,\n    \"num_beams\": 8,\n    \"max_new_tokens\": 512,\n    \"length_penalty\": 1.3,\n    \"early_stopping\": True,\n    \"num_buckets\": 4,\n    \"device\": torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n}\n\nprint(\"=\"*60)\nprint(\"TEST 3: ADAPTIVE BEAMS + VECTORIZED POSTPROC\")\nprint(\"=\"*60)\nprint(f\"Device: {CONFIG['device']}\")\nprint(f\"✓ Adaptive beams: ENABLED\")\nprint(f\"✓ Vectorized postproc: ENABLED\")\nprint(\"=\"*60)\n\n# ============================================================\n# PREPROCESSING\n# ============================================================\n\ndef preprocess_input(text):\n    if pd.isna(text):\n        return \"\"\n    text = str(text)\n    text = re.sub(r'(\\.{3,}|…+|……)', '<big_gap>', text)\n    text = re.sub(r'(xx+|\\s+x\\s+)', '<gap>', text)\n    return text\n\n# ============================================================\n# VECTORIZED POSTPROCESSOR\n# ============================================================\n\nclass VectorizedPostprocessor:\n    def __init__(self):\n        self.patterns = {\n            \"gap\": re.compile(r\"(\\[x\\]|\\(x\\)|\\bx\\b)\", re.I),\n            \"big_gap\": re.compile(r\"(\\.{3,}|…|\\[\\.+\\])\"),\n            \"annotations\": re.compile(r\"\\((fem|plur|pl|sing|singular|plural|\\?|!)\\.?\\s*\\w*\\)\", re.I),\n            \"repeated_words\": re.compile(r\"\\b(\\w+)(?:\\s+\\1\\b)+\"),\n            \"whitespace\": re.compile(r\"\\s+\"),\n            \"punct_space\": re.compile(r\"\\s+([.,:])\"),\n            \"repeated_punct\": re.compile(r\"([.,])\\1+\"),\n        }\n        self.subscript_trans = str.maketrans(\"₀₁₂₃₄₅₆₇₈₉\", \"0123456789\")\n        self.special_chars_trans = str.maketrans(\"ḫḪ\", \"hH\")\n        self.forbidden_chars = '!?()\"—–<>⌈⌋⌊[]+ʾ/;'\n        self.forbidden_trans = str.maketrans(\"\", \"\", self.forbidden_chars)\n    \n    def postprocess_batch(self, translations):\n        \"\"\"Vectorized batch postprocessing using pandas\"\"\"\n        s = pd.Series(translations)\n        valid_mask = s.apply(lambda x: isinstance(x, str) and bool(x.strip()))\n        s.loc[~valid_mask] = \"\"\n        \n        s = s.str.translate(self.special_chars_trans)\n        s = s.str.translate(self.subscript_trans)\n        s = s.str.replace(self.patterns[\"gap\"], \"<gap>\", regex=True)\n        s = s.str.replace(self.patterns[\"big_gap\"], \"<big_gap>\", regex=True)\n        s = s.str.replace(\"<gap> <gap>\", \"<big_gap>\", regex=False)\n        s = s.str.replace(\"<big_gap> <big_gap>\", \"<big_gap>\", regex=False)\n        s = s.str.replace(self.patterns[\"annotations\"], \"\", regex=True)\n        \n        s = s.str.replace(\"<gap>\", \"\\x00GAP\\x00\", regex=False)\n        s = s.str.replace(\"<big_gap>\", \"\\x00BIG\\x00\", regex=False)\n        s = s.str.translate(self.forbidden_trans)\n        s = s.str.replace(\"\\x00GAP\\x00\", \" <gap> \", regex=False)\n        s = s.str.replace(\"\\x00BIG\\x00\", \" <big_gap> \", regex=False)\n        \n        s = s.str.replace(r\"(\\d+)\\.5\\b\", r\"\\1 ½\", regex=True)\n        s = s.str.replace(r\"\\b0\\.5\\b\", \"½\", regex=True)\n        s = s.str.replace(r\"(\\d+)\\.25\\b\", r\"\\1 ¼\", regex=True)\n        s = s.str.replace(r\"\\b0\\.25\\b\", \"¼\", regex=True)\n        s = s.str.replace(r\"(\\d+)\\.75\\b\", r\"\\1 ¾\", regex=True)\n        s = s.str.replace(r\"\\b0\\.75\\b\", \"¾\", regex=True)\n        s = s.str.replace(r\"(\\d+)\\.33+\\d*\\b\", r\"\\1 ⅓\", regex=True)\n        s = s.str.replace(r\"\\b0\\.33+\\d*\\b\", \"⅓\", regex=True)\n        s = s.str.replace(r\"(\\d+)\\.66+\\d*\\b\", r\"\\1 ⅔\", regex=True)\n        s = s.str.replace(r\"\\b0\\.66+\\d*\\b\", \"⅔\", regex=True)\n        \n        s = s.str.replace(self.patterns[\"repeated_words\"], r\"\\1\", regex=True)\n        for n in range(4, 1, -1):\n            pattern = r\"\\b((?:\\w+\\s+){\" + str(n-1) + r\"}\\w+)(?:\\s+\\1\\b)+\"\n            s = s.str.replace(pattern, r\"\\1\", regex=True)\n        \n        s = s.str.replace(self.patterns[\"punct_space\"], r\"\\1\", regex=True)\n        s = s.str.replace(self.patterns[\"repeated_punct\"], r\"\\1\", regex=True)\n        s = s.str.replace(self.patterns[\"whitespace\"], \" \", regex=True)\n        s = s.str.strip().str.strip(\"-\").str.strip()\n        \n        return s.tolist()\n\n# ============================================================\n# ADAPTIVE BEAM FUNCTION\n# ============================================================\n\ndef get_adaptive_beam_size(attention_mask, base_beams=8):\n    \"\"\"\n    Adaptive beam search:\n    - Short sequences (<100 tokens): Use 4 beams\n    - Long sequences (>=100 tokens): Use 8 beams\n    \"\"\"\n    lengths = attention_mask.sum(dim=1)\n    short_beams = max(4, base_beams // 2)\n    if lengths[0] < 100:\n        return short_beams\n    else:\n        return base_beams\n\n# ============================================================\n# BUCKET BATCH SAMPLER\n# ============================================================\n\nclass BucketBatchSampler(Sampler):\n    def __init__(self, dataset, batch_size, num_buckets):\n        self.dataset = dataset\n        self.batch_size = batch_size\n        \n        lengths = [len(text.split()) for _, text in dataset]\n        sorted_indices = sorted(range(len(lengths)), key=lambda i: lengths[i])\n        \n        bucket_size = max(1, len(sorted_indices) // max(1, num_buckets))\n        self.buckets = []\n        for i in range(num_buckets):\n            start = i * bucket_size\n            end = None if i == num_buckets - 1 else (i + 1) * bucket_size\n            self.buckets.append(sorted_indices[start:end])\n    \n    def __iter__(self):\n        for bucket in self.buckets:\n            for i in range(0, len(bucket), self.batch_size):\n                yield bucket[i : i + self.batch_size]\n    \n    def __len__(self):\n        total = 0\n        for bucket in self.buckets:\n            total += (len(bucket) + self.batch_size - 1) // self.batch_size\n        return total\n\n# ============================================================\n# DATASET\n# ============================================================\n\nclass AkkadianDataset(Dataset):\n    def __init__(self, dataframe):\n        self.sample_ids = dataframe['id'].tolist()\n        self.input_texts = [\n            \"translate Akkadian to English: \" + preprocess_input(text)\n            for text in dataframe['transliteration']\n        ]\n    \n    def __len__(self):\n        return len(self.sample_ids)\n    \n    def __getitem__(self, index):\n        return self.sample_ids[index], self.input_texts[index]\n\n# ============================================================\n# MAIN INFERENCE\n# ============================================================\n\nprint(\"\\nLoading test data...\")\ntest_df = pd.read_csv(CONFIG['test_path'])\nprint(f\"✓ {len(test_df)} samples\")\n\nprint(\"\\nLoading model...\")\nmodel = AutoModelForSeq2SeqLM.from_pretrained(CONFIG['model_path'])\nmodel = model.to(CONFIG['device']).eval()\ntokenizer = AutoTokenizer.from_pretrained(CONFIG['model_path'])\nprint(f\"✓ Model loaded\")\n\npostprocessor = VectorizedPostprocessor()\nprint(\"✓ Postprocessor ready\")\n\ndataset = AkkadianDataset(test_df)\nbatch_sampler = BucketBatchSampler(dataset, CONFIG['batch_size'], CONFIG['num_buckets'])\n\ndef collate_fn(batch):\n    ids = [s[0] for s in batch]\n    texts = [s[1] for s in batch]\n    tokenized = tokenizer(texts, max_length=CONFIG['max_length'], \n                         padding=True, truncation=True, return_tensors='pt')\n    return ids, tokenized\n\ndataloader = DataLoader(dataset, batch_sampler=batch_sampler, \n                       collate_fn=collate_fn, num_workers=2)\n\nprint(f\"✓ DataLoader ready: {len(dataloader)} batches\")\nprint(\"\\nGenerating translations...\")\nresults = []\n\nwith torch.inference_mode():\n    for batch_ids, tokenized in tqdm(dataloader, desc=\"Translating\"):\n        input_ids = tokenized.input_ids.to(CONFIG['device'])\n        attention_mask = tokenized.attention_mask.to(CONFIG['device'])\n        \n        # ADAPTIVE BEAMS\n        num_beams = get_adaptive_beam_size(attention_mask, CONFIG['num_beams'])\n        \n        outputs = model.generate(\n            input_ids=input_ids,\n            attention_mask=attention_mask,\n            num_beams=num_beams,\n            max_new_tokens=CONFIG['max_new_tokens'],\n            length_penalty=CONFIG['length_penalty'],\n            early_stopping=CONFIG['early_stopping']\n        )\n        \n        translations = tokenizer.batch_decode(outputs, skip_special_tokens=True)\n        \n        # VECTORIZED POSTPROCESSING\n        cleaned = postprocessor.postprocess_batch(translations)\n        \n        results.extend(zip(batch_ids, cleaned))\n\nsubmission = pd.DataFrame(results, columns=['id', 'translation'])\nsubmission.to_csv('submission.csv', index=False)\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"✓ DONE!\")\nprint(\"=\"*60)\nprint(f\"Translations: {len(submission)}\")\nprint(f\"Optimizations: Adaptive beams + Vectorized postproc\")\nprint(f\"\\nSample outputs:\")\nfor i in range(min(3, len(submission))):\n    print(f\"  {i}: {submission.iloc[i]['translation'][:80]}...\")\nprint(\"=\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-30T23:54:20.435982Z","iopub.execute_input":"2026-01-30T23:54:20.436194Z","iopub.status.idle":"2026-01-30T23:54:20.459067Z","shell.execute_reply.started":"2026-01-30T23:54:20.436177Z","shell.execute_reply":"2026-01-30T23:54:20.458340Z"}},"outputs":[],"execution_count":null}]}