{"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":"datasetVersion","sourceId":14410665,"datasetId":9203761,"databundleVersionId":15226414},{"sourceType":"datasetVersion","sourceId":14899351,"datasetId":9366631,"databundleVersionId":15763950},{"sourceType":"datasetVersion","sourceId":14929273,"datasetId":9553164,"databundleVersionId":15796698}],"dockerImageVersionId":31287,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-03-09T17:56:07.825727Z","iopub.execute_input":"2026-03-09T17:56:07.825906Z","iopub.status.idle":"2026-03-09T17:56:08.849905Z","shell.execute_reply.started":"2026-03-09T17:56:07.825887Z","shell.execute_reply":"2026-03-09T17:56:08.849164Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport gc\nimport re\nimport json\nimport math\nimport random\nimport logging\nimport warnings\nfrom pathlib import Path\nfrom dataclasses import dataclass\nfrom contextlib import nullcontext\nfrom typing import List, Tuple, Dict, Optional\nfrom collections import Counter\nfrom nltk.translate.bleu_score import sentence_bleu, SmoothingFunction\n\nimport pandas as pd\nimport numpy as np\nimport torch\nfrom torch.utils.data import Dataset, DataLoader, Sampler\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm.auto import tqdm\n\nwarnings.filterwarnings(\"ignore\")\nos.environ.setdefault(\"OMP_NUM_THREADS\", \"4\")\nos.environ.setdefault(\"TOKENIZERS_PARALLELISM\", \"true\")\n\n# ── CONFIG ──────────────────────────────────────────────────────────────────\n\ndef _cuda_bf16_supported():\n    if not torch.cuda.is_available(): return False\n    try: return bool(getattr(torch.cuda, \"is_bf16_supported\", lambda: False)())\n    except: return False\n\ndef _bf16_ctx(device, enabled):\n    if enabled and device.type == \"cuda\" and _cuda_bf16_supported():\n        return torch.autocast(device_type=\"cuda\", dtype=torch.bfloat16)\n    return nullcontext()\n\n@dataclass\nclass Config:\n    test_data_path:  str = \"/kaggle/input/competitions/deep-past-initiative-machine-translation/test.csv\"\n    output_dir:      str = \"/kaggle/working/\"\n    model_a_path:    str = \"/kaggle/input/datasets/assiaben/final-byt5/byt5-akkadian-optimized-34x\"\n    model_b_path:    str = \"/kaggle/input/datasets/llkh0a/byt5-akkadian-model\"\n    model_c_path:    str = \"/kaggle/input/datasets/spencevanasperdt/mattiaangelibyt5-akkadian-mbrpytorchdefault6\"\n\n    max_input_length:  int   = 512\n    max_new_tokens:    int   = 384\n    batch_size:        int   = 2\n    num_workers:       int   = 2\n    num_buckets:       int   = 6\n    num_beam_cands:    int   = 6\n    num_beams:         int   = 10\n    length_penalty:    float = 1.25\n    early_stopping:    bool  = True\n    repetition_penalty:float = 1.15\n    num_sample_cands:  int   = 3\n    mbr_top_p:         float = 0.90\n    mbr_temperature:   float = 0.70\n    mbr_pool_cap:      int   = 48\n    use_mixed_precision: bool = True\n    checkpoint_freq:   int   = 200\n\n    def __post_init__(self):\n        self.device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n        Path(self.output_dir).mkdir(exist_ok=True, parents=True)\n        if not torch.cuda.is_available():\n            self.use_mixed_precision = False\n        self.use_bf16_amp = bool(\n            self.use_mixed_precision and\n            self.device.type == \"cuda\" and\n            _cuda_bf16_supported()\n        )\n\n# ── METRICS ─────────────────────────────────────────────────────────────────\n\ndef char_f_score(pred, ref, n=6, beta=2.0):\n    pred, ref = pred.strip().lower(), ref.strip().lower()\n    if not pred or not ref: return 0.0\n    def get_ngrams(text, n):\n        if len(text) < n: return Counter([text])\n        return Counter([text[i:i+n] for i in range(len(text)-n+1)])\n    pred_ng, ref_ng = get_ngrams(pred, n), get_ngrams(ref, n)\n    common = sum((pred_ng & ref_ng).values())\n    p = common / max(1, sum(pred_ng.values()))\n    r = common / max(1, sum(ref_ng.values()))\n    if p + r == 0: return 0.0\n    return (1 + beta**2) * p * r / (beta**2 * p + r)\n\ndef nltk_bleu(pred, ref):\n    p, r = pred.strip().lower().split(), ref.strip().lower().split()\n    if not p or not r: return 0.0\n    try: return sentence_bleu([r], p, smoothing_function=SmoothingFunction().method1)\n    except: return 0.0\n\ndef geo_score(pred, ref):\n    return math.sqrt((char_f_score(pred, ref) + 1e-6) * (nltk_bleu(pred, ref) + 1e-6))\n\n# ── PREPROCESSING ────────────────────────────────────────────────────────────\n\n_V2 = re.compile(r\"([aAeEiIuU])(?:2|₂)\")\n_V3 = re.compile(r\"([aAeEiIuU])(?:3|₃)\")\n_ACUTE = str.maketrans({\"a\":\"á\",\"e\":\"é\",\"i\":\"í\",\"u\":\"ú\",\"A\":\"Á\",\"E\":\"É\",\"I\":\"Í\",\"U\":\"Ú\"})\n_GRAVE = str.maketrans({\"a\":\"à\",\"e\":\"è\",\"i\":\"ì\",\"u\":\"ù\",\"A\":\"À\",\"E\":\"È\",\"I\":\"Ì\",\"U\":\"Ù\"})\n\ndef ascii_to_diacritics(s):\n    s = s.replace(\"sz\",\"š\").replace(\"SZ\",\"Š\")\n    s = s.replace(\"s,\",\"ṣ\").replace(\"S,\",\"Ṣ\")\n    s = s.replace(\"t,\",\"ṭ\").replace(\"T,\",\"Ṭ\")\n    s = _V2.sub(lambda m: m.group(1).translate(_ACUTE), s)\n    s = _V3.sub(lambda m: m.group(1).translate(_GRAVE), s)\n    return s\n\nOA_HINTS = {\n    \"KÙ.BABBAR\":\"silver\",\"KU.BABBAR\":\"silver\",\"AN.NA\":\"tin\",\"MA.NA\":\"mina\",\n    \"GÍN\":\"shekel\",\"GIN\":\"shekel\",\"TÚG\":\"textile\",\"TUG\":\"textile\",\n    \"GADA\":\"linen\",\"ŠE\":\"barley\",\"SE\":\"barley\",\"URUDU\":\"copper\",\n    \"ANŠE\":\"donkey\",\"É\":\"house\",\"DUMU\":\"son\",\"DUMU.MUNUS\":\"daughter\",\n}\n\n_GAP_RE = re.compile(\n    r\"<\\s*big[\\s_\\-]*gap\\s*>|<\\s*gap\\s*>|\\bbig[\\s_\\-]*gap\\b\"\n    r\"|\\bx(?:\\s+x)+\\b|\\.{3,}|…+|\\[\\.+\\]|\\[\\s*x\\s*\\]|\\(\\s*x\\s*\\)\"\n    r\"|(?<!\\w)x{2,}(?!\\w)|(?<!\\w)x(?!\\w)\"\n    r\"|\\(\\s*large\\s+break\\s*\\)|\\(\\s*break\\s*\\)|\\(\\s*\\d+\\s+broken\\s+lines?\\s*\\)\", re.I\n)\n_CHAR_TRANS = str.maketrans({\n    \"ḫ\":\"h\",\"Ḫ\":\"H\",\"ʾ\":\"\",\"₀\":\"0\",\"₁\":\"1\",\"₂\":\"2\",\"₃\":\"3\",\"₄\":\"4\",\n    \"₅\":\"5\",\"₆\":\"6\",\"₇\":\"7\",\"₈\":\"8\",\"₉\":\"9\",\"—\":\"-\",\"–\":\"-\",\n})\n_WS_RE = re.compile(r\"\\s+\")\n_FLOAT_RE = re.compile(r\"(?<![\\w/])(\\d+\\.\\d{4,})(?![\\w/])\")\n_ALLOWED_FRACS = [\n    (1/6,\"0.16666\"),(1/4,\"0.25\"),(1/3,\"0.33333\"),(1/2,\"0.5\"),\n    (2/3,\"0.66666\"),(3/4,\"0.75\"),(5/6,\"0.83333\"),\n]\ndef canon_decimal(x):\n    ip = int(math.floor(x+1e-12)); frac = x-ip\n    best = min(_ALLOWED_FRACS, key=lambda t: abs(frac-t[0]))\n    if abs(frac-best[0]) <= 2e-3:\n        dec = best[1]\n        if ip == 0: return dec\n        return f\"{ip}{dec[1:]}\" if dec.startswith(\"0.\") else f\"{ip}+{dec}\"\n    return f\"{x:.5f}\".rstrip(\"0\").rstrip(\".\")\n\ndef preprocess(texts: List[str]) -> List[str]:\n    results = []\n    for text in texts:\n        t = ascii_to_diacritics(str(text))\n        t = _GAP_RE.sub(\"<gap>\", t)\n        t = t.translate(_CHAR_TRANS).replace(\"ₓ\",\"\")\n        t = _FLOAT_RE.sub(lambda m: canon_decimal(float(m.group(1))), t)\n        t = _WS_RE.sub(\" \", t).strip()\n        # OA hints\n        found = [v for k,v in OA_HINTS.items() if k.upper() in t.upper()]\n        if found:\n            t += f\" [Commodities: {', '.join(list(dict.fromkeys(found))[:4])}]\"\n        results.append(\"translate Akkadian to English: \" + t)\n    return results\n\n# ── POSTPROCESSING ───────────────────────────────────────────────────────────\n\n_EXACT_FRAC_RE = re.compile(r\"0\\.8333|0\\.6666|0\\.3333|0\\.1666|0\\.625|0\\.75|0\\.25|0\\.5\")\n_EXACT_FRAC_MAP = {\"0.8333\":\"⅚\",\"0.6666\":\"⅔\",\"0.3333\":\"⅓\",\"0.1666\":\"⅙\",\"0.625\":\"⅝\",\"0.75\":\"¾\",\"0.25\":\"¼\",\"0.5\":\"½\"}\n_HINT_RE = re.compile(r'\\s*\\[Commodities:[^\\]]*\\]', re.I)\n_PN_RE = re.compile(r\"\\bPN\\b\")\n_MULTI_GAP_RE = re.compile(r'(?:<gap>\\s*){2,}')\n_REPEAT_WORD_RE = re.compile(r\"\\b(\\w+)(?:\\s+\\1\\b)+\")\n_REPEAT_PUNCT_RE = re.compile(r\"([.,])\\1+\")\n_PUNCT_SPACE_RE = re.compile(r\"\\s+([.,:])\")\n_FORBIDDEN = str.maketrans(\"\",\"\",\"()——<>⌈⌋⌊[]+ʾ;\")\n\ndef postprocess(translations: List[str]) -> List[str]:\n    results = []\n    for t in translations:\n        t = _HINT_RE.sub(\"\", t)\n        t = _GAP_RE.sub(\"<gap>\", t)\n        t = _PN_RE.sub(\"<gap>\", t)\n        t = _EXACT_FRAC_RE.sub(lambda m: _EXACT_FRAC_MAP[m.group(0)], t)\n        t = _MULTI_GAP_RE.sub(\"<gap>\", t)\n        t = t.replace(\"<gap>\",\"\\x00GAP\\x00\").translate(_FORBIDDEN).replace(\"\\x00GAP\\x00\",\" <gap> \")\n        t = _REPEAT_WORD_RE.sub(r\"\\1\", t)\n        t = _PUNCT_SPACE_RE.sub(r\"\\1\", t)\n        t = _REPEAT_PUNCT_RE.sub(r\"\\1\", t)\n        t = _WS_RE.sub(\" \", t).strip()\n        results.append(t)\n    return results\n\n# ── DATASET ──────────────────────────────────────────────────────────────────\n\nclass AkkadianDataset(Dataset):\n    def __init__(self, df):\n        self.ids = df[\"id\"].tolist()\n        self.texts = preprocess(df[\"transliteration\"].tolist())\n    def __len__(self): return len(self.ids)\n    def __getitem__(self, i): return self.ids[i], self.texts[i]\n\nclass BucketSampler(Sampler):\n    def __init__(self, dataset, batch_size, num_buckets):\n        lengths = [len(t.split()) for _, t in dataset]\n        idx = sorted(range(len(lengths)), key=lambda i: lengths[i])\n        bsize = max(1, len(idx) // num_buckets)\n        self.buckets = [idx[i*bsize:(i+1)*bsize if i<num_buckets-1 else None] for i in range(num_buckets)]\n        self.batch_size = batch_size\n    def __iter__(self):\n        for b in self.buckets:\n            for i in range(0, len(b), self.batch_size): yield b[i:i+self.batch_size]\n    def __len__(self):\n        return sum(math.ceil(len(b)/self.batch_size) for b in self.buckets)\n\n# ── MODEL ────────────────────────────────────────────────────────────────────\n\nclass ModelWrapper:\n    def __init__(self, path, cfg, label):\n        print(f\"[{label}] Yükleniyor: {path}\")\n        self.cfg = cfg\n        self.label = label\n        self.tok = AutoTokenizer.from_pretrained(path)\n        self.model = AutoModelForSeq2SeqLM.from_pretrained(path).to(cfg.device).eval()\n        n = sum(p.numel() for p in self.model.parameters())\n        print(f\"[{label}] {n:,} parametre | GPU: {torch.cuda.memory_allocated()/1e9:.2f} GB\")\n\n    def collate(self, batch):\n        ids = [b[0] for b in batch]\n        texts = [b[1] for b in batch]\n        enc = self.tok(texts, max_length=self.cfg.max_input_length, padding=True, truncation=True, return_tensors=\"pt\")\n        return ids, enc\n\n    def generate(self, input_ids, attn):\n        cfg = self.cfg\n        ctx = _bf16_ctx(cfg.device, cfg.use_bf16_amp)\n        with ctx:\n            beam_out = self.model.generate(\n                input_ids=input_ids, attention_mask=attn,\n                do_sample=False, num_beams=cfg.num_beams,\n                num_return_sequences=cfg.num_beam_cands,\n                max_new_tokens=cfg.max_new_tokens,\n                length_penalty=cfg.length_penalty,\n                repetition_penalty=cfg.repetition_penalty,\n                early_stopping=cfg.early_stopping, use_cache=True,\n            )\n            beam_texts = self.tok.batch_decode(beam_out, skip_special_tokens=True)\n\n            samp_out = self.model.generate(\n                input_ids=input_ids, attention_mask=attn,\n                do_sample=True, num_beams=1,\n                top_p=cfg.mbr_top_p, temperature=cfg.mbr_temperature,\n                num_return_sequences=cfg.num_sample_cands,\n                max_new_tokens=cfg.max_new_tokens,\n                repetition_penalty=cfg.repetition_penalty, use_cache=True,\n            )\n            samp_texts = self.tok.batch_decode(samp_out, skip_special_tokens=True)\n\n        B = input_ids.shape[0]\n        pools = []\n        for i in range(B):\n            p = list(beam_texts[i*cfg.num_beam_cands:(i+1)*cfg.num_beam_cands])\n            p += list(samp_texts[i*cfg.num_sample_cands:(i+1)*cfg.num_sample_cands])\n            pools.append(p)\n        return pools\n\n    def unload(self):\n        del self.model, self.tok\n        gc.collect()\n        if torch.cuda.is_available():\n            torch.cuda.empty_cache()\n            free = (torch.cuda.get_device_properties(0).total_memory - torch.cuda.memory_allocated()) / 1e9\n            print(f\"[{self.label}] Kaldırıldı. GPU boş: {free:.2f} GB\")\n\n# ── MBR ──────────────────────────────────────────────────────────────────────\n\ndef mbr_pick(candidates: List[str], pool_cap=48) -> str:\n    seen, cands = set(), []\n    for c in candidates:\n        c = str(c).strip()\n        if c and c not in seen:\n            cands.append(c); seen.add(c)\n    cands = cands[:pool_cap]\n    n = len(cands)\n    if n == 0: return \"\"\n    if n == 1: return cands[0]\n    best_i, best_s = 0, -1e9\n    for i in range(n):\n        s = sum(geo_score(cands[i], cands[j]) for j in range(n) if j != i) / max(1, n-1)\n        if s > best_s: best_s, best_i = s, i\n    return cands[best_i]\n\n# ── MAIN ──────────────────────────────────────────────────────────────────────\n\ncfg = Config()\nprint(f\"GPU: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'CPU'}\")\nprint(f\"BF16: {cfg.use_bf16_amp}\")\n\ntest_df = pd.read_csv(cfg.test_data_path)\nprint(f\"Test örnekleri: {len(test_df)}\")\n\ndataset = AkkadianDataset(test_df)\nall_pools = {str(sid): [] for sid in dataset.ids}\n\nfor label, path in [\n    (\"Model-A\", cfg.model_a_path),\n    (\"Model-B\", cfg.model_b_path),\n    (\"Model-C\", cfg.model_c_path),\n]:\n    wrapper = ModelWrapper(path, cfg, label)\n    sampler = BucketSampler(dataset, cfg.batch_size, cfg.num_buckets)\n    dl = DataLoader(dataset, batch_sampler=sampler, num_workers=cfg.num_workers,\n                    collate_fn=wrapper.collate, pin_memory=True)\n\n    with torch.inference_mode():\n        for batch_ids, enc in tqdm(dl, desc=label):\n            input_ids = enc.input_ids.to(cfg.device)\n            attn = enc.attention_mask.to(cfg.device)\n            try:\n                pools = wrapper.generate(input_ids, attn)\n                for sid, pool in zip(batch_ids, pools):\n                    all_pools[str(sid)].extend(pool)\n            except RuntimeError as e:\n                if \"out of memory\" in str(e).lower():\n                    print(f\"OOM! Batch atlandı.\")\n                    torch.cuda.empty_cache()\n                else:\n                    raise\n\n    wrapper.unload()\n\nprint(\"MBR seçimi yapılıyor...\")\nresults = []\nfor sid in [str(s) for s in dataset.ids]:\n    candidates = all_pools[sid]\n    pp = postprocess(candidates)\n    chosen = mbr_pick(pp, cfg.mbr_pool_cap)\n    if not chosen.strip():\n        chosen = \"The tablet is too damaged to translate.\"\n    results.append((sid, chosen))\n\nresult_df = pd.DataFrame(results, columns=[\"id\", \"translation\"])\n\nout_path = Path(cfg.output_dir) / \"submission.csv\"\nresult_df.to_csv(out_path, index=False)\n\nprint(f\"\\n✅ Tamamlandı! {len(result_df)} satır → {out_path}\")\nprint(result_df)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-09T17:58:50.130115Z","iopub.execute_input":"2026-03-09T17:58:50.130855Z","iopub.status.idle":"2026-03-09T18:03:00.401178Z","shell.execute_reply.started":"2026-03-09T17:58:50.130823Z","shell.execute_reply":"2026-03-09T18:03:00.400442Z"}},"outputs":[],"execution_count":null}]}