{"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":[{"sourceType":"competition","sourceId":121150,"databundleVersionId":15833391},{"sourceType":"datasetVersion","sourceId":14899351,"datasetId":9366631,"databundleVersionId":15763950},{"sourceType":"kernelVersion","sourceId":294739278}],"dockerImageVersionId":31260,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"header","cell_type":"markdown","source":"# Akkadian → English Inference v2 --- scored 33\n\n**Exact 35.1 baseline + conservative improvements:**\n- `num_beams=10` (up from 8)\n- `length_penalty=1.6` (up from 1.5)\n- No MBR, no sampling, no extra preprocessing\n- Single model: `byt5-akkadian-optimized-34x`","metadata":{}},{"id":"env_setup","cell_type":"code","source":"# ============================================================\n# CELL 1: Environment Setup\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 logging\nimport warnings\nimport random\nfrom pathlib import Path\nfrom dataclasses import dataclass\n\nimport pandas as pd\nimport numpy as np\nimport torch\nfrom torch.utils.data import Dataset, DataLoader, Sampler\nfrom torch.cuda.amp import autocast\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm.auto import tqdm\n\nwarnings.filterwarnings('ignore')\nlogging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')\nlogger = logging.getLogger(__name__)\n\nprint(f'PyTorch: {torch.__version__}, CUDA: {torch.cuda.is_available()}')\nif torch.cuda.is_available():\n    print(f'GPU: {torch.cuda.get_device_name(0)}')\n    print(f'GPU Memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.2f} GB')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T01:22:39.326756Z","iopub.execute_input":"2026-03-03T01:22:39.327550Z","iopub.status.idle":"2026-03-03T01:22:39.340286Z","shell.execute_reply.started":"2026-03-03T01:22:39.327483Z","shell.execute_reply":"2026-03-03T01:22:39.339259Z"}},"outputs":[],"execution_count":null},{"id":"config","cell_type":"code","source":"# ============================================================\n# CELL 2: Configuration\n# ============================================================\n\n@dataclass\nclass Config:\n    # Paths\n    test_data_path: str = '/kaggle/input/deep-past-initiative-machine-translation/test.csv'\n    model_path: str = '/kaggle/input/datasets/assiaben/final-byt5/byt5-akkadian-optimized-34x'\n    output_dir: str = '/kaggle/working/'\n\n    # Processing\n    max_length: int = 512\n    batch_size: int = 8\n    num_workers: int = 4\n\n    # Generation\n    num_beams: int = 10\n    max_new_tokens: int = 512\n    length_penalty: float = 1.6\n    early_stopping: bool = True\n\n    # Speed\n    use_mixed_precision: bool = True\n    use_bucket_batching: bool = True\n    num_buckets: int = 4\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\nconfig = Config()\nprint(f'Device: {config.device}')\nprint(f'Batch size: {config.batch_size}')\nprint(f'num_beams: {config.num_beams}')\nprint(f'length_penalty: {config.length_penalty}')\nprint(f'Mixed precision: {config.use_mixed_precision}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T01:22:39.341990Z","iopub.execute_input":"2026-03-03T01:22:39.342327Z","iopub.status.idle":"2026-03-03T01:22:39.361776Z","shell.execute_reply.started":"2026-03-03T01:22:39.342284Z","shell.execute_reply":"2026-03-03T01:22:39.360895Z"}},"outputs":[],"execution_count":null},{"id":"preprocessor","cell_type":"code","source":"# ============================================================\n# CELL 3: Preprocessor\n# ============================================================\n\nclass Preprocessor:\n    def __init__(self):\n        self.patterns = {\n            'big_gap': re.compile(r'(\\.{3,}|…+|……)'),\n            'small_gap': re.compile(r'(xx+|\\s+x\\s+)'),\n        }\n\n    def preprocess(self, text):\n        if pd.isna(text):\n            return ''\n        text = str(text)\n        text = self.patterns['big_gap'].sub('<big_gap>', text)\n        text = self.patterns['small_gap'].sub('<gap>', text)\n        return text\n\n    def preprocess_batch(self, texts):\n        s = pd.Series(texts).fillna('').astype(str)\n        s = s.str.replace(self.patterns['big_gap'], '<big_gap>', regex=True)\n        s = s.str.replace(self.patterns['small_gap'], '<gap>', regex=True)\n        return s.tolist()\n\npreprocessor = Preprocessor()\nprint('✅ Preprocessor ready')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T01:22:39.363082Z","iopub.execute_input":"2026-03-03T01:22:39.363334Z","iopub.status.idle":"2026-03-03T01:22:39.383107Z","shell.execute_reply.started":"2026-03-03T01:22:39.363313Z","shell.execute_reply":"2026-03-03T01:22:39.382250Z"}},"outputs":[],"execution_count":null},{"id":"postprocessor","cell_type":"code","source":"# ============================================================\n# CELL 4: Postprocessor (FIXED)\n# ============================================================\n\nclass Postprocessor:\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(\n                r'\\((fem|plur|pl|sing|singular|plural|\\?|!)\\.?\\s*\\w*\\)', re.I\n            ),\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_trans = str.maketrans('ḫḪ', 'hH')\n        self.forbidden_trans = str.maketrans('', '', '!?()\\\"—–<>⌈⌋⌊[]+ʾ/;')\n\n    def postprocess_batch(self, translations):\n        s = pd.Series(translations)\n\n        # ✅ FIX: returns bool not str\n        valid = s.apply(lambda x: isinstance(x, str) and len(x.strip()) > 0)\n        s[~valid] = ''\n\n        # Character normalization\n        s = s.str.translate(self.special_trans)\n        s = s.str.translate(self.subscript_trans)\n        s = s.str.replace(self.patterns['whitespace'], ' ', regex=True).str.strip()\n\n        # Gap normalization\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\n        # Remove annotations\n        s = s.str.replace(self.patterns['annotations'], '', regex=True)\n\n        # Protect gaps, remove forbidden chars\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        # Fractions\n        s = s.str.replace(r'(\\d+)\\.5\\b', r'\\g<1>½', regex=True)\n        s = s.str.replace(r'\\b0\\.5\\b', '½', regex=True)\n        s = s.str.replace(r'(\\d+)\\.25\\b', r'\\g<1>¼', regex=True)\n        s = s.str.replace(r'\\b0\\.25\\b', '¼', regex=True)\n        s = s.str.replace(r'(\\d+)\\.75\\b', r'\\g<1>¾', regex=True)\n        s = s.str.replace(r'\\b0\\.75\\b', '¾', regex=True)\n        s = s.str.replace(r'(\\d+)\\.3333\\b', r'\\g<1>⅓', regex=True)\n        s = s.str.replace(r'\\b0\\.3333\\b', '⅓', regex=True)\n        s = s.str.replace(r'(\\d+)\\.6666\\b', r'\\g<1>⅔', regex=True)\n        s = s.str.replace(r'\\b0\\.6666\\b', '⅔', regex=True)\n\n        # Remove repetitions\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        # Punctuation cleanup\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\npostprocessor = Postprocessor()\nprint('✅ Postprocessor ready (bug fixed)')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T01:22:39.384240Z","iopub.execute_input":"2026-03-03T01:22:39.384659Z","iopub.status.idle":"2026-03-03T01:22:39.402821Z","shell.execute_reply.started":"2026-03-03T01:22:39.384625Z","shell.execute_reply":"2026-03-03T01:22:39.402103Z"}},"outputs":[],"execution_count":null},{"id":"dataset","cell_type":"code","source":"# ============================================================\n# CELL 5: Dataset & Bucket Sampler\n# ============================================================\n\nclass BucketBatchSampler(Sampler):\n    def __init__(self, dataset, batch_size, num_buckets=4):\n        self.batch_size = batch_size\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            bucket = sorted_indices[start:end]\n            if bucket:\n                self.buckets.append(bucket)\n\n        for i, bucket in enumerate(self.buckets):\n            bucket_lens = [lengths[idx] for idx in bucket]\n            logger.info(f'  Bucket {i}: {len(bucket)} samples, '\n                        f'length [{min(bucket_lens)}-{max(bucket_lens)}]')\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        return sum(\n            (len(b) + self.batch_size - 1) // self.batch_size\n            for b in self.buckets\n        )\n\n\nclass AkkadianDataset(Dataset):\n    def __init__(self, df, preprocessor):\n        self.ids = df['id'].tolist()\n        texts = preprocessor.preprocess_batch(df['transliteration'].tolist())\n        self.texts = ['translate Akkadian to English: ' + t for t in texts]\n        logger.info(f'Dataset: {len(self.ids)} samples')\n\n    def __len__(self):\n        return len(self.ids)\n\n    def __getitem__(self, idx):\n        return self.ids[idx], self.texts[idx]\n\n\n# Load test data\ntest_df = pd.read_csv(config.test_data_path)\nlogger.info(f'Loaded {len(test_df)} test samples')\nprint(test_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T01:22:39.445473Z","iopub.execute_input":"2026-03-03T01:22:39.445766Z","iopub.status.idle":"2026-03-03T01:22:39.469179Z","shell.execute_reply.started":"2026-03-03T01:22:39.445743Z","shell.execute_reply":"2026-03-03T01:22:39.468306Z"}},"outputs":[],"execution_count":null},{"id":"load_model","cell_type":"code","source":"# ============================================================\n# CELL 6: Load Model\n# ============================================================\n\nlogger.info(f'Loading model: {config.model_path}')\n\nmodel = AutoModelForSeq2SeqLM.from_pretrained(\n    config.model_path\n).to(config.device).eval()\n\ntokenizer = AutoTokenizer.from_pretrained(config.model_path)\n\nnum_params = sum(p.numel() for p in model.parameters())\nlogger.info(f'Model loaded: {num_params:,} params')\n\n# Try BetterTransformer for speed\nif torch.cuda.is_available():\n    try:\n        from optimum.bettertransformer import BetterTransformer\n        model = BetterTransformer.transform(model)\n        logger.info('✅ BetterTransformer applied')\n    except ImportError:\n        logger.info('⚠️ BetterTransformer not available (install optimum)')\n    except Exception as e:\n        logger.info(f'⚠️ BetterTransformer failed: {e}')\n\n# Set matmul precision\nif torch.cuda.is_available():\n    try:\n        torch.set_float32_matmul_precision('high')\n    except Exception:\n        pass\n\nprint(f'✅ Model ready on {config.device}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T01:22:39.470797Z","iopub.execute_input":"2026-03-03T01:22:39.471039Z","iopub.status.idle":"2026-03-03T01:23:00.498744Z","shell.execute_reply.started":"2026-03-03T01:22:39.470998Z","shell.execute_reply":"2026-03-03T01:23:00.498079Z"}},"outputs":[],"execution_count":null},{"id":"inference","cell_type":"code","source":"# ============================================================\n# CELL 7: Inference\n# ============================================================\n\ndataset = AkkadianDataset(test_df, preprocessor)\n\n# Collate function\ndef collate_fn(batch):\n    ids = [b[0] for b in batch]\n    texts = [b[1] for b in batch]\n    tokens = tokenizer(\n        texts,\n        max_length=config.max_length,\n        padding=True,\n        truncation=True,\n        return_tensors='pt'\n    )\n    return ids, tokens\n\n# Create dataloader\nif config.use_bucket_batching:\n    sampler = BucketBatchSampler(dataset, config.batch_size, config.num_buckets)\n    loader = DataLoader(\n        dataset,\n        batch_sampler=sampler,\n        num_workers=config.num_workers,\n        collate_fn=collate_fn,\n        pin_memory=True,\n        prefetch_factor=2,\n        persistent_workers=True if config.num_workers > 0 else False,\n    )\nelse:\n    loader = DataLoader(\n        dataset,\n        batch_size=config.batch_size,\n        shuffle=False,\n        num_workers=config.num_workers,\n        collate_fn=collate_fn,\n        pin_memory=True,\n        prefetch_factor=2,\n        persistent_workers=True if config.num_workers > 0 else False,\n    )\n\n# Generation config\ngen_config = {\n    'num_beams': config.num_beams,\n    'max_new_tokens': config.max_new_tokens,\n    'length_penalty': config.length_penalty,\n    'early_stopping': config.early_stopping,\n    'use_cache': True,\n}\n\nlogger.info(f'Generation config: {gen_config}')\nlogger.info(f'Total batches: {len(loader)}')\nlogger.info(f'Mixed precision: {config.use_mixed_precision}')\n\n# Run inference\nresults = []\nerrors = 0\n\nwith torch.inference_mode():\n    for batch_idx, (ids, tokens) in enumerate(tqdm(loader, desc='🚀 Translating')):\n        try:\n            input_ids = tokens.input_ids.to(config.device)\n            attention_mask = tokens.attention_mask.to(config.device)\n\n            if config.use_mixed_precision and torch.cuda.is_available():\n                with autocast():\n                    outputs = model.generate(\n                        input_ids=input_ids,\n                        attention_mask=attention_mask,\n                        **gen_config\n                    )\n            else:\n                outputs = model.generate(\n                    input_ids=input_ids,\n                    attention_mask=attention_mask,\n                    **gen_config\n                )\n\n            translations = tokenizer.batch_decode(outputs, skip_special_tokens=True)\n            cleaned = postprocessor.postprocess_batch(translations)\n            results.extend(zip(ids, cleaned))\n\n            # Periodic GPU cache clear\n            if torch.cuda.is_available() and batch_idx % 10 == 0:\n                torch.cuda.empty_cache()\n\n        except Exception as e:\n            logger.error(f'❌ Batch {batch_idx} error: {e}')\n            results.extend([(bid, '') for bid in ids])\n            errors += 1\n            if torch.cuda.is_available():\n                torch.cuda.empty_cache()\n\nlogger.info(f'✅ Generated {len(results)} translations ({errors} errors)')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T01:23:00.500044Z","iopub.execute_input":"2026-03-03T01:23:00.500298Z","iopub.status.idle":"2026-03-03T01:23:11.749884Z","shell.execute_reply.started":"2026-03-03T01:23:00.500278Z","shell.execute_reply":"2026-03-03T01:23:11.749072Z"}},"outputs":[],"execution_count":null},{"id":"submission","cell_type":"code","source":"# ============================================================\n# CELL 8: Create & Save Submission\n# ============================================================\n\nresult_df = pd.DataFrame(results, columns=['id', 'translation'])\n\n# Merge to ensure all test IDs present in correct order\nsubmission = test_df[['id']].merge(result_df, on='id', how='left')\nsubmission['translation'] = submission['translation'].fillna('')\n\n# Save\noutput_path = Path(config.output_dir) / 'submission.csv'\nsubmission.to_csv(output_path, index=False)\nlogger.info(f'✅ Saved {len(submission)} rows to {output_path}')\n\n# ============================================================\n# Validation Report\n# ============================================================\nprint('\\n' + '=' * 60)\nprint('📊 VALIDATION REPORT')\nprint('=' * 60)\n\nempty = submission['translation'].str.strip().eq('').sum()\nlengths = submission['translation'].str.len()\n\nprint(f'\\nTotal translations: {len(submission)}')\nprint(f'Empty translations: {empty} ({empty/len(submission)*100:.1f}%)')\nprint(f'\\n📏 Length stats:')\nprint(f'   Mean:   {lengths.mean():.1f} chars')\nprint(f'   Median: {lengths.median():.1f} chars')\nprint(f'   Min:    {lengths.min()} chars')\nprint(f'   Max:    {lengths.max()} chars')\n\nshort = ((lengths < 5) & (lengths > 0)).sum()\nif short > 0:\n    print(f'   ⚠️ {short} very short translations (<5 chars)')\n\nprint(f'\\n📝 Sample translations:')\nn_samples = min(5, len(submission))\nfor i in range(n_samples):\n    row = submission.iloc[i]\n    src = test_df.loc[test_df['id'] == row['id'], 'transliteration'].values\n    src_preview = str(src[0])[:60] + '...' if len(src) > 0 else 'N/A'\n    tgt = str(row['translation'])\n    tgt_preview = tgt[:100] + '...' if len(tgt) > 100 else tgt\n    print(f'\\n   [{row[\"id\"]}] SRC: {src_preview}')\n    print(f'        TGT: {tgt_preview}')\n\nprint('\\n' + '=' * 60)\nprint('🎉 INFERENCE COMPLETE!')\nprint('=' * 60)\nprint(f'Submission: {output_path}')\nprint(f'Total rows: {len(submission)}')\nprint(f'Empty: {empty}')\nprint('=' * 60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T01:23:11.751617Z","iopub.execute_input":"2026-03-03T01:23:11.751882Z","iopub.status.idle":"2026-03-03T01:23:11.794442Z","shell.execute_reply.started":"2026-03-03T01:23:11.751849Z","shell.execute_reply":"2026-03-03T01:23:11.793803Z"}},"outputs":[],"execution_count":null}]}