{"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,"sourceType":"competition"},{"sourceId":14188526,"sourceType":"datasetVersion","datasetId":9046477},{"sourceId":14219211,"sourceType":"datasetVersion","datasetId":9070334}],"dockerImageVersionId":31234,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import shutil; shutil.copy(\"/kaggle/input/figures/Image13.png\", \"/kaggle/working/Image13.png\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:24:40.228666Z","iopub.execute_input":"2025-12-23T15:24:40.229003Z","iopub.status.idle":"2025-12-23T15:24:40.240169Z","shell.execute_reply.started":"2025-12-23T15:24:40.228971Z","shell.execute_reply":"2025-12-23T15:24:40.239243Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"\n<div style=\"text-align:center;\">\n    <img src=\"Image13.png\" style=\"max-width:100%; border-radius:14px; box-shadow:0 10px 30px rgba(0,0,0,.25);\">\n</div>\n","metadata":{}},{"cell_type":"markdown","source":"<div style=\"padding:22px 24px; border-radius:16px; border:1px solid #e5e7eb;\n            background:#ffffff; box-shadow:0 2px 12px rgba(15,23,42,0.08);\n            margin:14px 0;\n            font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,Arial,'Noto Sans','Liberation Sans',sans-serif;\">\n  <div style=\"display:flex; align-items:flex-start; gap:12px;\">\n    <div style=\"width:12px; height:38px; border-radius:10px; background:#2563eb; margin-top:2px;\"></div>\n    <div>\n      <div style=\"font-size:30px; font-weight:900; color:#0f172a; line-height:1.12;\">\n        Data Analysis Overview\n      </div>\n      <div style=\"margin-top:8px; font-size:16px; color:#475569; line-height:1.6;\">\n        This notebook analyzes the linguistic and statistical properties of the <b>Deep Past Initiative</b> dataset\n        (<b>Old Assyrian transliterations → English</b>) to understand what drives performance and where noise enters the pipeline.\n      </div>\n    </div>\n  </div>\n</div>\n\n<div style=\"padding:18px 20px; border-radius:16px; border:1px solid #e2e8f0;\n            background:#f8fafc; margin:10px 0;\n            font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,Arial,'Noto Sans','Liberation Sans',sans-serif;\">\n  <div style=\"font-size:16px; font-weight:900; color:#0f172a;\">\n    What we aim to learn\n  </div>\n  <ul style=\"margin:10px 0 0 0; padding-left:18px; font-size:16px; color:#334155; line-height:1.75;\">\n    <li><b>Data structure:</b> document lengths, segmentation behavior, and outliers.</li>\n    <li><b>Lexical repetition &amp; sparsity:</b> frequent formulaic tokens vs. rare forms.</li>\n    <li><b>Train–test overlap:</b> potential leakage signals (retrieval-like shortcuts).</li>\n    <li><b>Generalization needs:</b> whether success depends on memorization/retrieval or true translation robustness.</li>\n  </ul>\n\n  <div style=\"margin-top:12px; padding:12px 14px; border-radius:12px; border:1px solid #dbeafe; background:#eff6ff;\">\n    <div style=\"font-size:15px; font-weight:900; color:#1e3a8a;\">\n      Why this matters\n    </div>\n    <div style=\"margin-top:6px; font-size:15px; color:#1f2937; line-height:1.65;\">\n      These findings directly guide <b>model choice</b> (character/byte-level vs. subword), <b>normalization</b>,\n      <b>alignment strategy</b>, and whether retrieval-style baselines are competitive.\n    </div>\n  </div>\n</div>\n","metadata":{}},{"cell_type":"code","source":"import pandas as pd\n\nTRAIN_PATH = \"/kaggle/input/deep-past-initiative-machine-translation/train.csv\"\nTEST_PATH  = \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\"\n\ntrain_df = pd.read_csv(TRAIN_PATH)\ntest_df  = pd.read_csv(TEST_PATH)\n\nprint(\"train shape:\", train_df.shape)\nprint(\"test  shape:\", test_df.shape)\n\nprint(\"\\ntrain columns:\", train_df.columns.tolist())\nprint(\"test  columns:\", test_df.columns.tolist())\n\ndisplay(train_df.head(3))\ndisplay(test_df.head(3))\n\nprint(\"\\nMissing values (train):\")\ndisplay(train_df.isna().sum())\n\nprint(\"\\nMissing values (test):\")\ndisplay(test_df.isna().sum())\n\n# quick sanity checks\nfor col in [\"id\", \"transliteration\", \"translation\"]:\n    if col in train_df.columns:\n        print(f\"\\n[{col}] train sample:\")\n        display(train_df[[col]].head(5))\n\nfor col in [\"id\", \"transliteration\"]:\n    if col in test_df.columns:\n        print(f\"\\n[{col}] test sample:\")\n        display(test_df[[col]].head(5))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:27:35.155075Z","iopub.execute_input":"2025-12-23T15:27:35.155603Z","iopub.status.idle":"2025-12-23T15:27:35.244229Z","shell.execute_reply.started":"2025-12-23T15:27:35.155557Z","shell.execute_reply":"2025-12-23T15:27:35.243206Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<div style=\"padding:20px 22px; border-radius:16px; border:1px solid #e5e7eb;\n            background:#ffffff; box-shadow:0 2px 10px rgba(15,23,42,0.08);\n            margin:14px 0;\n            font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,Arial,'Noto Sans','Liberation Sans',sans-serif;\">\n  <div style=\"display:flex; align-items:center; gap:12px;\">\n    <div style=\"width:12px; height:34px; border-radius:8px; background:#2563eb;\"></div>\n    <div>\n      <div style=\"font-size:26px; font-weight:900; color:#0f172a; line-height:1.15;\">\n        Length Statistics &amp; Outliers (Document → Segments)\n      </div>\n      <div style=\"margin-top:6px; font-size:16px; color:#475569; line-height:1.45;\">\n        Exploratory analysis of document length, segmentation quality, and extreme cases.\n      </div>\n    </div>\n  </div>\n</div>\n\n<div style=\"padding:18px 20px; border-radius:16px; border:1px solid #e2e8f0;\n            background:#f8fafc; margin:10px 0;\n            font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,Arial,'Noto Sans','Liberation Sans',sans-serif;\">\n  <div style=\"font-size:16px; font-weight:900; color:#0f172a; letter-spacing:0.2px;\">\n    Key observation\n  </div>\n\n  <div style=\"margin-top:12px; font-size:16px; color:#334155; line-height:1.75;\">\n    The source segmentation currently appears to be failing:\n    <b style=\"color:#0f172a;\">src_n_lines = 1 for all samples</b> (std = 0; min = max = 1).\n    This indicates each transliteration document is treated as a single segment, while the English side is split into\n    multiple sentences (median ≈ 4; max = 111). As a consequence, alignment often becomes\n    <b style=\"color:#0f172a;\">1 source chunk → many target sentences</b>, introducing noise during training.\n  </div>\n</div>\n","metadata":{}},{"cell_type":"code","source":"import re, html\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom IPython.display import display, HTML\n\n\n# Helpers\n\ndef clean_ws(x: str) -> str:\n    if pd.isna(x):\n        return \"\"\n    return re.sub(r\"\\s+\", \" \", str(x)).strip()\n\ndef normalize_newlines(doc: str) -> str:\n    \"\"\"Convert literal '\\\\n' to real newlines and normalize.\"\"\"\n    doc = \"\" if pd.isna(doc) else str(doc)\n    doc = doc.replace(\"\\\\n\", \"\\n\")  \n    return doc\n\ndef split_src_lines(doc: str):\n    \"\"\"Split transliteration doc into line-like segments (robust).\"\"\"\n    doc = normalize_newlines(doc)\n\n    # primary: newline split\n    parts = re.split(r\"\\n+\", doc)\n    parts = [clean_ws(p) for p in parts if clean_ws(p)]\n\n    # fallback if still 1 chunk\n    if len(parts) <= 1:\n        parts = re.split(r\"\\s{2,}|;\\s+|:\\s+\", clean_ws(doc))\n        parts = [clean_ws(p) for p in parts if clean_ws(p)]\n\n    return parts\n\ndef split_tgt_sents(doc: str):\n    \"\"\"Split English translation into sentences (simple regex).\"\"\"\n    doc = clean_ws(doc)\n    if not doc:\n        return []\n    sents = re.split(r\"(?<=[.!?])\\s+\", doc)\n    sents = [clean_ws(s) for s in sents if clean_ws(s)]\n    return sents\n\ndef word_count(s: str) -> int:\n    s = clean_ws(s)\n    return 0 if not s else len(s.split())\n\ndef char_count(s: str) -> int:\n    s = \"\" if pd.isna(s) else str(s)\n    return len(s)\n\n# Build stats table (train)\n\nstats = []\nfor idx, row in train_df.iterrows():\n    src_doc = row.get(\"transliteration\", \"\")\n    tgt_doc = row.get(\"translation\", \"\")\n\n    src_lines = split_src_lines(src_doc)\n    tgt_sents = split_tgt_sents(tgt_doc)\n\n    src_words = word_count(src_doc)\n    tgt_words = word_count(tgt_doc)\n\n    stats.append({\n        \"row_idx\": idx,\n        \"src_doc_chars\": char_count(src_doc),\n        \"tgt_doc_chars\": char_count(tgt_doc),\n        \"src_doc_words\": src_words,\n        \"tgt_doc_words\": tgt_words,\n        \"src_n_lines\": len(src_lines),\n        \"tgt_n_sents\": len(tgt_sents),\n        \"src_avg_line_words\": (np.mean([word_count(x) for x in src_lines]) if src_lines else 0.0),\n        \"tgt_avg_sent_words\": (np.mean([word_count(x) for x in tgt_sents]) if tgt_sents else 0.0),\n        \"len_ratio_words_src_over_tgt\": (src_words / max(1, tgt_words)) if tgt_words > 0 else np.nan,\n    })\n\nstats_df = pd.DataFrame(stats)\n\nprint(\"Stats DF shape:\", stats_df.shape)\ndisplay(stats_df.head())\n\n# Summary stats\n\ndef summary(col):\n    x = stats_df[col].dropna()\n    return pd.Series({\n        \"count\": len(x),\n        \"mean\": x.mean(),\n        \"std\": x.std(ddof=1) if len(x) > 1 else 0.0,\n        \"min\": x.min(),\n        \"p05\": x.quantile(0.05),\n        \"p25\": x.quantile(0.25),\n        \"median\": x.quantile(0.50),\n        \"p75\": x.quantile(0.75),\n        \"p95\": x.quantile(0.95),\n        \"max\": x.max(),\n    })\n\ncols = [\n    \"src_doc_words\",\"tgt_doc_words\",\"src_n_lines\",\"tgt_n_sents\",\n    \"src_avg_line_words\",\"tgt_avg_sent_words\",\"len_ratio_words_src_over_tgt\"\n]\nsummary_df = pd.DataFrame({c: summary(c) for c in cols}).T\nprint(\"\\n=== Summary stats ===\")\ndisplay(summary_df)\n\n# 4 histograms on one page\n\nplots = [\n    (\"src_doc_words\", \"Source doc word count\"),\n    (\"tgt_doc_words\", \"Target doc word count\"),\n    (\"src_n_lines\", \"Source #lines (after fix)\"),\n    (\"tgt_n_sents\", \"Target #sentences\"),\n]\n\nfig, axes = plt.subplots(2, 2, figsize=(14, 8))\naxes = axes.ravel()\n\nfor ax, (col, title) in zip(axes, plots):\n    x = stats_df[col].dropna()\n    ax.hist(x, bins=40)\n    ax.set_title(title)\n    ax.set_xlabel(col)\n    ax.set_ylabel(\"count\")\n\nplt.tight_layout()\nplt.show()\n\n\n# Colored outlier display (HTML)\n\ndef show_section_title(title, color=\"#ff5722\"):\n    display(HTML(f\"\"\"\n    <div style=\"font-family: monospace; font-size: 14px; line-height: 1.35; margin: 8px 0 10px 0;\">\n      <div style=\"color:{color}; font-weight:700;\">{'='*80}</div>\n      <div style=\"color:{color}; font-weight:700;\">{html.escape(title)}</div>\n      <div style=\"color:{color}; font-weight:700;\">{'='*80}</div>\n    </div>\n    \"\"\"))\n\ndef show_examples_colored(idxs, k=5):\n    for ridx in idxs[:k]:\n        r = train_df.iloc[int(ridx)]\n        src = clean_ws(str(r.get(\"transliteration\",\"\")))\n        tgt = clean_ws(str(r.get(\"translation\",\"\")))\n\n        src_snip = html.escape(src[:350] + (\"...\" if len(src) > 350 else \"\"))\n        tgt_snip = html.escape(tgt[:350] + (\"...\" if len(tgt) > 350 else \"\"))\n\n        display(HTML(f\"\"\"\n        <div style=\"font-family: monospace; border:1px solid #eee; padding:10px; margin:10px 0; border-radius:10px;\">\n          <div><span style=\"color:#1565c0; font-weight:700;\">row_idx={int(ridx)}</span></div>\n          <div style=\"margin-top:6px;\">\n            <span style=\"color:#2e7d32; font-weight:700;\">SRC</span>:\n            <div style=\"white-space:pre-wrap; color:#111;\">{src_snip}</div>\n          </div>\n          <div style=\"margin-top:6px;\">\n            <span style=\"color:#c62828; font-weight:700;\">TGT</span>:\n            <div style=\"white-space:pre-wrap; color:#111;\">{tgt_snip}</div>\n          </div>\n        </div>\n        \"\"\"))\n\n# Outliers\n\ntop_src = stats_df.sort_values(\"src_doc_words\", ascending=False)[\"row_idx\"].tolist()\ntop_tgt = stats_df.sort_values(\"tgt_doc_words\", ascending=False)[\"row_idx\"].tolist()\n\nstats_df[\"abs_lines_minus_sents\"] = (stats_df[\"src_n_lines\"] - stats_df[\"tgt_n_sents\"]).abs()\ntop_mismatch = stats_df.sort_values(\"abs_lines_minus_sents\", ascending=False)[\"row_idx\"].tolist()\n\ntop_ratio_hi = stats_df.sort_values(\"len_ratio_words_src_over_tgt\", ascending=False)[\"row_idx\"].dropna().tolist()\ntop_ratio_lo = stats_df.sort_values(\"len_ratio_words_src_over_tgt\", ascending=True)[\"row_idx\"].dropna().tolist()\n\nshow_section_title(\"Top 3 longest SOURCE docs (by words)\", color=\"#7b1fa2\")\nshow_examples_colored(top_src, k=3)\n\nshow_section_title(\"Top 3 longest TARGET docs (by words)\", color=\"#7b1fa2\")\nshow_examples_colored(top_tgt, k=3)\n\nshow_section_title(\"Top 5 biggest mismatch: |#src_lines - #tgt_sents| (after fix)\", color=\"#1976d2\")\nshow_examples_colored(top_mismatch, k=5)\n\nshow_section_title(\"Top 5 highest word-length ratio src/tgt\", color=\"#00897b\")\nshow_examples_colored(top_ratio_hi, k=5)\n\nshow_section_title(\"Top 5 lowest word-length ratio src/tgt\", color=\"#ff5722\")\nshow_examples_colored(top_ratio_lo, k=5)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:28:44.031892Z","iopub.execute_input":"2025-12-23T15:28:44.032248Z","iopub.status.idle":"2025-12-23T15:28:46.089611Z","shell.execute_reply.started":"2025-12-23T15:28:44.032221Z","shell.execute_reply":"2025-12-23T15:28:46.088589Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<div style=\"padding:22px 24px; border-radius:16px; border:1px solid #e5e7eb;\n            background:#ffffff; box-shadow:0 2px 12px rgba(15,23,42,0.08);\n            margin:14px 0;\n            font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,Arial,'Noto Sans','Liberation Sans',sans-serif;\">\n  <div style=\"display:flex; align-items:flex-start; gap:12px;\">\n    <div style=\"width:12px; height:38px; border-radius:10px; background:#2563eb; margin-top:2px;\"></div>\n    <div>\n      <div style=\"font-size:28px; font-weight:900; color:#0f172a; line-height:1.12;\">\n        2) Token &amp; Special-Symbol Analysis (Transliteration)\n      </div>\n      <div style=\"margin-top:8px; font-size:16px; color:#475569; line-height:1.55;\">\n        We quantify the frequency and distribution of unknown markers, determinatives, numbers, punctuation, and other special symbols\n        to assess data quality and potential sources of training noise.\n      </div>\n    </div>\n  </div>\n</div>\n\n<div style=\"padding:18px 20px; border-radius:16px; border:1px solid #e2e8f0;\n            background:#f8fafc; margin:10px 0;\n            font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,Arial,'Noto Sans','Liberation Sans',sans-serif;\">\n  <div style=\"font-size:16px; font-weight:900; color:#0f172a;\">\n    Main finding\n  </div>\n\n  <div style=\"margin-top:10px; font-size:16px; color:#334155; line-height:1.75;\">\n    The transliteration text is strongly formulaic (high-frequency tokens such as <b>a-na</b>, <b>ša</b>, <b>um-ma</b>, <b>DUMU</b>),\n    while also containing substantial symbolic structure.\n    Hyphens dominate the character stream (~18%), numeric tokens are frequent (~7%),\n    and unknown/gap markers (<b>x</b>, <b>…</b>) are moderate on average (~3.9%) but produce severe outliers\n    (some documents exceed 50% unknown-rate).\n  </div>\n\n  <div style=\"margin-top:12px; padding:12px 14px; border-radius:12px; border:1px solid #dbeafe; background:#eff6ff;\">\n    <div style=\"font-size:15px; font-weight:900; color:#1e3a8a;\">\n      Implication for modeling\n    </div>\n    <div style=\"margin-top:6px; font-size:15px; color:#1f2937; line-height:1.65;\">\n      These statistics motivate (i) light normalization for dashes/ellipses and long decimals, and (ii) filtering or down-weighting\n      high-unknown documents to reduce noisy supervision during seq2seq fine-tuning.\n    </div>\n  </div>\n</div>\n","metadata":{}},{"cell_type":"code","source":"\nimport re\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom collections import Counter\n\n\n# Global plotting style (soft + readable)\n\nplt.rcParams.update({\n    \"figure.dpi\": 120,\n    \"axes.titlesize\": 14,\n    \"axes.labelsize\": 12,\n    \"xtick.labelsize\": 10,\n    \"ytick.labelsize\": 10,\n    \"font.size\": 11,\n})\n\n# Soft colors (user asked for nicer colors explicitly)\nC_MAIN = \"#4C78A8\"    # soft blue\nC_ACC  = \"#59A14F\"    # soft green\nC_WARN = \"#E15759\"    # soft red\nC_NEU  = \"#9CA3AF\"    # gray\nGRID_A = 0.18\n\n\n# Normalize + tokenize\n\ndef normalize_translit(s: str) -> str:\n    if pd.isna(s):\n        return \"\"\n    s = str(s)\n\n    # literal \\n -> newline\n    s = s.replace(\"\\\\n\", \"\\n\")\n\n    # normalize dash variants\n    s = s.replace(\"–\", \"-\").replace(\"—\", \"-\")\n\n    # normalize ellipsis variants\n    s = s.replace(\"...\", \"…\")\n\n    # whitespace\n    s = re.sub(r\"\\s+\", \" \", s).strip()\n    return s\n\nTOKEN_RE = re.compile(\n    r\"\"\"\n    \\.\\.\\.|…|xxx+|x+|                # unknown / gap markers\n    \\([^)]+\\)|                       # (d) (ki) (TÚG) etc.\n    \\d+(?:\\.\\d+)?|                   # numbers / decimals\n    [\\[\\]{}<>]|                      # brackets\n    [.,;:!?/\\\\]|                     # punctuation\n    [₀-₉]+|                          # subscript digits\n    [A-Za-zÀ-žḫḪšŠṣṢṭṬ₁₂₃₄₅₆₇₈₉₀]+(?:[-][A-Za-zÀ-žḫḪšŠṣṢṭṬ₁₂₃₄₅₆₇₈₉₀]+)*|  # words with hyphen\n    [^\\s]                            # any other single char\n    \"\"\",\n    re.VERBOSE\n)\n\ndef tokenize(s: str):\n    s = normalize_translit(s)\n    if not s:\n        return []\n    return TOKEN_RE.findall(s)\n\n\n# Category rules\n\nRE_UNKNOWN = re.compile(r\"^(x+|xxx+)$\", re.IGNORECASE)\nRE_GAP     = re.compile(r\"^(…|\\.\\.\\.)$\")\nRE_PAREN   = re.compile(r\"^\\([^)]+\\)$\")\nRE_NUM     = re.compile(r\"^\\d+(\\.\\d+)?$\")\nRE_BRACK   = re.compile(r\"^[\\[\\]{}<>]$\")\nRE_PUNC    = re.compile(r\"^[.,;:!?/\\\\]$\")\nRE_SUBSCR  = re.compile(r\"^[₀-₉]+$\")\nRE_HYPHEN  = re.compile(r\"^-$\")\n\n\n# Collect stats\n\ncat_counts = Counter()\nunknown_rate_per_doc = []\ngap_rate_per_doc = []\nparen_rate_per_doc = []\nnum_rate_per_doc = []\n\nspecial_token_counter = Counter()\ndoc_token_counts = []\ndoc_unique_counts = []\n\nfor s in train_df[\"transliteration\"].fillna(\"\").tolist():\n    s_norm = normalize_translit(s)\n    if not s_norm:\n        continue\n\n    toks = tokenize(s_norm)\n    if not toks:\n        continue\n\n    n = len(toks)\n    doc_token_counts.append(n)\n    doc_unique_counts.append(len(set(toks)))\n\n    n_unk = sum(1 for t in toks if RE_UNKNOWN.match(t))\n    n_gap = sum(1 for t in toks if RE_GAP.match(t))\n    n_par = sum(1 for t in toks if RE_PAREN.match(t))\n    n_num = sum(1 for t in toks if RE_NUM.match(t))\n    n_brk = sum(1 for t in toks if RE_BRACK.match(t))\n    n_pun = sum(1 for t in toks if RE_PUNC.match(t))\n    n_sub = sum(1 for t in toks if RE_SUBSCR.match(t))\n    n_hyp = sum(1 for t in toks if RE_HYPHEN.match(t))\n\n    cat_counts[\"unknown (x/xxx)\"] += n_unk\n    cat_counts[\"gap (…/...)\"] += n_gap\n    cat_counts[\"determinatives ( ... )\"] += n_par\n    cat_counts[\"numbers\"] += n_num\n    cat_counts[\"brackets\"] += n_brk\n    cat_counts[\"punctuation\"] += n_pun\n    cat_counts[\"subscript digits\"] += n_sub\n    cat_counts[\"standalone hyphen (-)\"] += n_hyp\n\n    unknown_rate_per_doc.append(n_unk / n)\n    gap_rate_per_doc.append(n_gap / n)\n    paren_rate_per_doc.append(n_par / n)\n    num_rate_per_doc.append(n_num / n)\n\n    # collect frequent \"special tokens\" (not normal words)\n    for t in toks:\n        if (RE_UNKNOWN.match(t) or RE_GAP.match(t) or RE_PAREN.match(t) or\n            RE_BRACK.match(t) or RE_PUNC.match(t) or RE_HYPHEN.match(t)):\n            special_token_counter[t] += 1\n\nlex_div = np.array(doc_unique_counts) / np.maximum(1, np.array(doc_token_counts))\n\n\n# Nice plot helpers\n\ndef soften_axes(ax):\n    ax.grid(axis=\"y\", alpha=GRID_A)\n    ax.set_axisbelow(True)\n    ax.spines[\"top\"].set_alpha(0.15)\n    ax.spines[\"right\"].set_alpha(0.15)\n    ax.spines[\"left\"].set_alpha(0.25)\n    ax.spines[\"bottom\"].set_alpha(0.25)\n\ndef hist_clip(ax, data, bins=40, title=\"\", xlabel=\"\", color=C_MAIN, clip_q=0.995):\n    data = np.asarray(data)\n    data = data[np.isfinite(data)]\n    if len(data) == 0:\n        ax.set_title(title)\n        ax.text(0.5, 0.5, \"No data\", ha=\"center\", va=\"center\")\n        return\n\n    # clip long tails for readability (doesn't delete data; just changes x-range)\n    xmax = np.quantile(data, clip_q)\n    ax.hist(data, bins=bins, color=color, alpha=0.9, edgecolor=\"white\", linewidth=0.7)\n    ax.set_title(title)\n    ax.set_xlabel(xlabel)\n    ax.set_ylabel(\"documents\")\n    ax.set_xlim(left=0, right=max(xmax, 1e-9))\n    soften_axes(ax)\n\ndef shorten_label(s: str, max_len: int = 22) -> str:\n    s = str(s)\n    return s if len(s) <= max_len else (s[:max_len-1] + \"…\")\n\ndef plot_top_special_tokens(ax, counter, topN=18, max_label_len=22, drop_tokens_longer_than=60):\n    # filter out absurdly long tokens that destroy the plot\n    filtered = Counter({k: v for k, v in counter.items() if len(str(k)) <= drop_tokens_longer_than})\n    items = filtered.most_common(topN)\n\n    if not items:\n        ax.set_title(\"Top special tokens\")\n        ax.text(0.5, 0.5, \"No tokens after filtering\", ha=\"center\", va=\"center\")\n        return\n\n    labels = [shorten_label(t, max_label_len) for t, _ in items][::-1]\n    values = [c for _, c in items][::-1]\n\n    ax.barh(labels, values, color=C_ACC, alpha=0.95, edgecolor=\"white\", linewidth=0.7)\n    ax.set_title(f\"Top {topN} special tokens (truncated labels)\")\n    ax.set_xlabel(\"count\")\n    ax.set_ylabel(\"token\")\n    soften_axes(ax)\n\n\n# FIGURE A: 2x2 overview (clean)\n\nfig = plt.figure(figsize=(14, 8), constrained_layout=True)\ngs = fig.add_gridspec(2, 2)\n\n# A1: category counts (horizontal, readable)\nax1 = fig.add_subplot(gs[0, 0])\ncats = list(cat_counts.keys())\nvals = np.array([cat_counts[c] for c in cats])\norder = np.argsort(vals)\ncats_sorted = [cats[i] for i in order]\nvals_sorted = vals[order]\n\nax1.barh(cats_sorted, vals_sorted, color=C_MAIN, alpha=0.92, edgecolor=\"white\", linewidth=0.7)\nax1.set_title(\"Special token categories (total counts)\")\nax1.set_xlabel(\"count\")\nax1.set_ylabel(\"\")\nsoften_axes(ax1)\n\n# A2: unknown-rate\nax2 = fig.add_subplot(gs[0, 1])\nhist_clip(ax2, unknown_rate_per_doc, bins=40,\n          title=\"Unknown-marker rate per document\",\n          xlabel=\"unknown / total tokens\",\n          color=C_WARN, clip_q=0.999)\n\n# A3: determinative-rate\nax3 = fig.add_subplot(gs[1, 0])\nhist_clip(ax3, paren_rate_per_doc, bins=40,\n          title=\"Determinative rate per document (parentheses)\",\n          xlabel=\"Parenthetical determinatives ratio\",\n          color=C_ACC, clip_q=0.999)\n\n# A4: tokens per doc\nax4 = fig.add_subplot(gs[1, 1])\nhist_clip(ax4, doc_token_counts, bins=40,\n          title=\"Tokens per document\",\n          xlabel=\"#tokens\",\n          color=C_MAIN, clip_q=0.995)\n\nplt.show()\n\n# FIGURE B: 1x2 (lexical diversity + top special tokens)\n\nfig2 = plt.figure(figsize=(14, 5.2), constrained_layout=True)\ngs2 = fig2.add_gridspec(1, 2)\n\nax5 = fig2.add_subplot(gs2[0, 0])\nhist_clip(ax5, lex_div, bins=40,\n          title=\"Lexical diversity per document\",\n          xlabel=\"unique tokens / total tokens\",\n          color=C_NEU, clip_q=0.999)\n\nax6 = fig2.add_subplot(gs2[0, 1])\nplot_top_special_tokens(ax6, special_token_counter, topN=18, max_label_len=22, drop_tokens_longer_than=60)\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:50:37.054195Z","iopub.execute_input":"2025-12-23T15:50:37.054583Z","iopub.status.idle":"2025-12-23T15:50:38.986411Z","shell.execute_reply.started":"2025-12-23T15:50:37.054551Z","shell.execute_reply":"2025-12-23T15:50:38.985305Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<div style=\"padding:22px 24px; border-radius:16px; border:1px solid #e5e7eb;\n            background:#ffffff; box-shadow:0 2px 12px rgba(15,23,42,0.08);\n            margin:14px 0;\n            font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,Arial,'Noto Sans','Liberation Sans',sans-serif;\">\n  <div style=\"display:flex; align-items:flex-start; gap:12px;\">\n    <div style=\"width:12px; height:38px; border-radius:10px; background:#7c3aed; margin-top:2px;\"></div>\n    <div>\n      <div style=\"font-size:28px; font-weight:900; color:#0f172a; line-height:1.12;\">\n        Alignment quality: what the diagnostics reveal\n      </div>\n      <div style=\"margin-top:8px; font-size:16px; color:#475569; line-height:1.55;\">\n        The length-ratio and mismatch distributions highlight a small but harmful set of noisy supervision cases.\n      </div>\n    </div>\n  </div>\n</div>\n\n<div style=\"padding:18px 20px; border-radius:16px; border:1px solid #e2e8f0;\n            background:#f8fafc; margin:10px 0;\n            font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,Arial,'Noto Sans','Liberation Sans',sans-serif;\">\n  <div style=\"font-size:16px; font-weight:900; color:#0f172a;\">\n    Key finding\n  </div>\n\n  <div style=\"margin-top:10px; font-size:16px; color:#334155; line-height:1.75;\">\n    Most pairs fall in a reasonable regime where <b>English is longer than transliteration</b> (ratio &lt; 1).\n    However, a clear long tail of <b>extreme src/tgt ratios</b> is driven by targets that are not full translations,\n    e.g., <b>single-name outputs</b> (“Lullu,”) or <b>placeholder sentences</b> (“too broken for translation”).\n    These samples create <b>misleading supervision</b>: the model is encouraged to map long sources to trivial targets.\n  </div>\n\n  <div style=\"margin-top:12px; padding:12px 14px; border-radius:12px; border:1px solid #ede9fe; background:#f5f3ff;\">\n    <div style=\"font-size:15px; font-weight:900; color:#4c1d95;\">\n      Practical implication\n    </div>\n    <div style=\"margin-top:6px; font-size:15px; color:#1f2937; line-height:1.65;\">\n      Before fine-tuning, filter or down-weight <b>high-noise documents</b>:\n      (i) placeholder targets, (ii) very short targets, and/or (iii) extreme length ratios.\n      This typically reduces alignment noise and stabilizes seq2seq training.\n    </div>\n  </div>\n</div>\n","metadata":{}},{"cell_type":"code","source":"\n\n# Soft plot styling (same spirit as before)\n\nplt.rcParams.update({\n    \"figure.dpi\": 120,\n    \"axes.titlesize\": 14,\n    \"axes.labelsize\": 12,\n    \"xtick.labelsize\": 10,\n    \"ytick.labelsize\": 10,\n    \"font.size\": 11,\n})\n\nC_MAIN = \"#4C78A8\"   # soft blue\nC_ACC  = \"#59A14F\"   # soft green\nC_WARN = \"#E15759\"   # soft red\nC_NEU  = \"#9CA3AF\"   # gray\nGRID_A = 0.18\n\ndef soften_axes(ax):\n    ax.grid(axis=\"y\", alpha=GRID_A)\n    ax.set_axisbelow(True)\n    ax.spines[\"top\"].set_alpha(0.15)\n    ax.spines[\"right\"].set_alpha(0.15)\n    ax.spines[\"left\"].set_alpha(0.25)\n    ax.spines[\"bottom\"].set_alpha(0.25)\n\ndef clean_ws(x: str) -> str:\n    if pd.isna(x): \n        return \"\"\n    return re.sub(r\"\\s+\", \" \", str(x)).strip()\n\ndef split_src_lines(doc: str):\n    doc = \"\" if pd.isna(doc) else str(doc)\n    # main: newline split\n    parts = re.split(r\"\\n+\", doc)\n    parts = [clean_ws(p) for p in parts if clean_ws(p)]\n    # fallback if no newlines\n    if len(parts) <= 1:\n        parts = re.split(r\"\\s{2,}|;\\s+\", clean_ws(doc))\n        parts = [clean_ws(p) for p in parts if clean_ws(p)]\n    return parts\n\ndef split_tgt_sents(doc: str):\n    doc = clean_ws(doc)\n    if not doc:\n        return []\n    sents = re.split(r\"(?<=[.!?])\\s+\", doc)\n    sents = [clean_ws(s) for s in sents if clean_ws(s)]\n    return sents\n\ndef word_count(s: str) -> int:\n    s = clean_ws(s)\n    return 0 if not s else len(s.split())\n\n\n# Build alignment diagnostics\n\nrows = []\nfor i, r in train_df.iterrows():\n    src = r.get(\"transliteration\", \"\")\n    tgt = r.get(\"translation\", \"\")\n\n    src_lines = split_src_lines(src)\n    tgt_sents = split_tgt_sents(tgt)\n\n    src_w = word_count(src)\n    tgt_w = word_count(tgt)\n\n    # ratios\n    ratio = np.nan\n    if tgt_w > 0:\n        ratio = src_w / tgt_w\n\n    rows.append({\n        \"row_idx\": i,\n        \"src_words\": src_w,\n        \"tgt_words\": tgt_w,\n        \"src_n_lines\": len(src_lines),\n        \"tgt_n_sents\": len(tgt_sents),\n        \"abs_seg_mismatch\": abs(len(src_lines) - len(tgt_sents)),\n        \"ratio_src_over_tgt\": ratio,\n    })\n\nalign_df = pd.DataFrame(rows)\nalign_df = align_df[(align_df[\"src_words\"] > 0) & (align_df[\"tgt_words\"] > 0)].copy()\n\n# log-ratio is better behaved for symmetry (ratio<1 and ratio>1)\nalign_df[\"log_ratio\"] = np.log(align_df[\"ratio_src_over_tgt\"].clip(1e-6, 1e6))\n\n\n# FIGURE A: 2x2 overview\n\nfig = plt.figure(figsize=(14, 8), constrained_layout=True)\ngs = fig.add_gridspec(2, 2)\n\n# A1: log-ratio distribution (centered at 0)\nax1 = fig.add_subplot(gs[0, 0])\nax1.hist(align_df[\"log_ratio\"], bins=45, color=C_MAIN, alpha=0.9, edgecolor=\"white\", linewidth=0.7)\nax1.set_title(\"Source/Target length ratio (log scale)\")\nax1.set_xlabel(\"log(src_words / tgt_words)\")\nax1.set_ylabel(\"documents\")\nsoften_axes(ax1)\nax1.axvline(0.0, color=C_NEU, linewidth=1)\n\n# A2: raw ratio (clipped for readability)\nax2 = fig.add_subplot(gs[0, 1])\nr = align_df[\"ratio_src_over_tgt\"].values\nrmax = np.quantile(r, 0.995)  # clip extreme tail\nax2.hist(r, bins=45, color=C_ACC, alpha=0.9, edgecolor=\"white\", linewidth=0.7)\nax2.set_xlim(0, rmax)\nax2.set_title(\"Source/Target length ratio (raw, clipped)\")\nax2.set_xlabel(\"src_words / tgt_words\")\nax2.set_ylabel(\"documents\")\nsoften_axes(ax2)\n\n# A3: segmentation mismatch (abs difference)\nax3 = fig.add_subplot(gs[1, 0])\nm = align_df[\"abs_seg_mismatch\"].values\nmmax = np.quantile(m, 0.995)\nax3.hist(m, bins=np.arange(0, int(mmax) + 2) - 0.5, color=C_WARN, alpha=0.9,\n         edgecolor=\"white\", linewidth=0.7)\nax3.set_title(\"Segmentation mismatch\")\nax3.set_xlabel(\"| #src_lines − #tgt_sentences |\")\nax3.set_ylabel(\"documents\")\nsoften_axes(ax3)\n\n# A4: ratio vs target length (spot odd regions)\nax4 = fig.add_subplot(gs[1, 1])\nx = align_df[\"tgt_words\"].values\ny = align_df[\"ratio_src_over_tgt\"].values\ny_clip = np.minimum(y, np.quantile(y, 0.99))\nax4.scatter(x, y_clip, s=12, alpha=0.35, color=C_MAIN, edgecolors=\"none\")\nax4.set_title(\"Ratio vs target length (outlier detection)\")\nax4.set_xlabel(\"tgt_words\")\nax4.set_ylabel(\"src_words / tgt_words (clipped)\")\nsoften_axes(ax4)\n\nplt.show()\n\n\n# FIGURE B: Worst alignments (barh, no table)\n\ndef plot_worst(title, subdf, k=10):\n    # show ratios; more extreme = further from 1 => bigger |log_ratio|\n    s = subdf.sort_values(\"abs_log\", ascending=False).head(k).copy()\n    labels = [f\"row {int(i)}\" for i in s[\"row_idx\"].tolist()]\n    values = s[\"ratio_src_over_tgt\"].tolist()\n\n    plt.figure(figsize=(10, 0.38 * len(labels) + 2), constrained_layout=True)\n    plt.barh(labels[::-1], values[::-1], color=C_WARN, alpha=0.9, edgecolor=\"white\", linewidth=0.7)\n    plt.title(title)\n    plt.xlabel(\"src_words / tgt_words\")\n    plt.ylabel(\"sample\")\n    plt.grid(axis=\"x\", alpha=0.12)\n    plt.show()\n\nalign_df[\"abs_log\"] = np.abs(align_df[\"log_ratio\"])\n\n# worst by extreme ratio (far from 1)\nplot_worst(\"Worst length-ratio cases (most extreme src/tgt)\", align_df, k=12)\n\n# worst by mismatch in segment counts\ntop_m = align_df.sort_values(\"abs_seg_mismatch\", ascending=False).head(12)\nplt.figure(figsize=(10, 0.38 * len(top_m) + 2), constrained_layout=True)\nplt.barh([f\"row {int(i)}\" for i in top_m[\"row_idx\"]][::-1],\n         top_m[\"abs_seg_mismatch\"][::-1],\n         color=C_MAIN, alpha=0.9, edgecolor=\"white\", linewidth=0.7)\nplt.title(\"Largest segmentation mismatches\")\nplt.xlabel(\"| #src_lines − #tgt_sentences |\")\nplt.ylabel(\"sample\")\nplt.grid(axis=\"x\", alpha=0.12)\nplt.show()\n\n# OPTIONAL: Print examples (text) — not a table, just readable snippets\n\ndef print_examples(indices, k=5, max_chars=380):\n    print(\"\\n\" + \"=\"*80)\n    print(\"Examples (snippets)\")\n    print(\"=\"*80)\n    for ridx in indices[:k]:\n        r = train_df.iloc[int(ridx)]\n        src = clean_ws(str(r.get(\"transliteration\",\"\")))\n        tgt = clean_ws(str(r.get(\"translation\",\"\")))\n        print(f\"\\n--- row_idx={int(ridx)} ---\")\n        print(\"SRC:\", src[:max_chars] + (\"...\" if len(src) > max_chars else \"\"))\n        print(\"TGT:\", tgt[:max_chars] + (\"...\" if len(tgt) > max_chars else \"\"))\n\n# show a few worst ratio cases\nworst_ratio_idxs = align_df.sort_values(\"abs_log\", ascending=False)[\"row_idx\"].tolist()\nprint_examples(worst_ratio_idxs, k=5)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T16:20:11.136752Z","iopub.execute_input":"2025-12-23T16:20:11.137147Z","iopub.status.idle":"2025-12-23T16:20:13.333358Z","shell.execute_reply.started":"2025-12-23T16:20:11.137116Z","shell.execute_reply":"2025-12-23T16:20:13.332228Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<div style=\"padding:20px 22px; border-radius:16px; border:1px solid #e5e7eb;\nbackground:#ffffff; box-shadow:0 2px 10px rgba(15,23,42,0.08);\nmargin:14px 0;\nfont-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,Arial,'Noto Sans',sans-serif;\">\n  <div style=\"display:flex; align-items:center; gap:12px;\">\n    <div style=\"width:12px; height:34px; border-radius:8px; background:#2563eb;\"></div>\n    <div>\n      <div style=\"font-size:26px; font-weight:900; color:#0f172a; line-height:1.15;\">\n        4) Baseline Diagnostics: Typical Error Patterns (ByT5)\n      </div>\n      <div style=\"margin-top:6px; font-size:16px; color:#475569; line-height:1.45;\">\n        We group baseline failures into a small set of recurring error types to guide data cleaning,\n        alignment choices, and decoding constraints.\n      </div>\n    </div>\n  </div>\n</div>\n\n<div style=\"padding:16px 18px; border-radius:16px; border:1px solid #e2e8f0;\nbackground:#f8fafc; margin:10px 0;\nfont-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,Arial,'Noto Sans',sans-serif;\">\n  <div style=\"font-size:16px; font-weight:900; color:#0f172a;\">Key finding</div>\n  <div style=\"margin-top:10px; font-size:16px; color:#334155; line-height:1.75;\">\n    Many references are extremely long (hundreds of words), suggesting that part of the validation set still\n    reflects <b>document-level targets</b> or noisy alignments rather than sentence-like pairs.\n    This can cause <b>mode collapse</b> (very short outputs like “From.”) and unstable learning.\n  </div>\n</div>\n\n<div style=\"padding:16px 18px; border-radius:16px; border:1px solid #e2e8f0;\nbackground:#ffffff; margin:10px 0;\nfont-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,Arial,'Noto Sans',sans-serif;\">\n  <div style=\"font-size:16px; font-weight:900; color:#0f172a;\">Error categories observed</div>\n  <ul style=\"margin-top:10px; font-size:16px; color:#334155; line-height:1.75;\">\n    <li><b>Severe under-translation:</b> outputs collapse to 1–3 words (template prefixes).</li>\n    <li><b>Numbers & units drift:</b> missing or altered quantities (high impact for economic/legal texts).</li>\n    <li><b>Hallucination / off-target:</b> repeated names, formulaic phrases unrelated to the source.</li>\n    <li><b>Other / mild:</b> partial paraphrases with lost details (often improves with cleaner alignment).</li>\n  </ul>\n</div>\n","metadata":{}},{"cell_type":"code","source":"import os, re, math, random\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport torch\nfrom torch.utils.data import Dataset\nfrom transformers import (\n    AutoTokenizer, AutoModelForSeq2SeqLM,\n    DataCollatorForSeq2Seq, Seq2SeqTrainingArguments, Seq2SeqTrainer\n)\n\n\n# 0) Repro + data\n\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)\n\nTRAIN_PATH = \"/kaggle/input/deep-past-initiative-machine-translation/train.csv\"\nMODEL_PATH = \"/kaggle/input/byt5-small\"\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(\"Device:\", DEVICE)\n\ntrain_df = pd.read_csv(TRAIN_PATH)\n\n# Helpers\n\ndef clean_ws(x: str) -> str:\n    if pd.isna(x): return \"\"\n    return re.sub(r\"\\s+\", \" \", str(x)).strip()\n\ndef split_src_transliteration(doc: str):\n    doc = \"\" if pd.isna(doc) else str(doc)\n    parts = re.split(r\"\\n+\", doc)\n    parts = [clean_ws(p) for p in parts if clean_ws(p)]\n    if len(parts) <= 1:\n        parts = re.split(r\"\\s{2,}|;\\s+\", clean_ws(doc))\n        parts = [clean_ws(p) for p in parts if clean_ws(p)]\n    return parts\n\ndef split_tgt_english(doc: str):\n    doc = clean_ws(doc)\n    if not doc:\n        return []\n    sents = re.split(r\"(?<=[.!?])\\s+\", doc)\n    sents = [clean_ws(s) for s in sents if clean_ws(s)]\n    return sents\n\ndef tok_len(s: str) -> int:\n    return max(1, len(clean_ws(s).split()))\n\ndef align_monotone(src_units, tgt_units, max_src_merge=2, max_tgt_merge=2):\n    i = j = 0\n    pairs = []\n    while i < len(src_units) and j < len(tgt_units):\n        best = None\n        for si in range(1, max_src_merge+1):\n            for tj in range(1, max_tgt_merge+1):\n                if i+si <= len(src_units) and j+tj <= len(tgt_units):\n                    s_chunk = \" \".join(src_units[i:i+si])\n                    t_chunk = \" \".join(tgt_units[j:j+tj])\n                    ls, lt = tok_len(s_chunk), tok_len(t_chunk)\n                    score = abs(math.log(ls/lt))\n                    if (best is None) or (score < best[0]):\n                        best = (score, si, tj, s_chunk, t_chunk)\n        if best is None:\n            break\n        _, si, tj, s_chunk, t_chunk = best\n        pairs.append((s_chunk, t_chunk))\n        i += si\n        j += tj\n\n    if pairs:\n        if i < len(src_units):\n            pairs[-1] = (pairs[-1][0] + \" \" + \" \".join(src_units[i:]), pairs[-1][1])\n        if j < len(tgt_units):\n            pairs[-1] = (pairs[-1][0], pairs[-1][1] + \" \" + \" \".join(tgt_units[j:]))\n    return pairs\n\ndef reconstruct_pairs(df_docs: pd.DataFrame):\n    pairs = []\n    for _, row in df_docs.iterrows():\n        src_doc = row.get(\"transliteration\", \"\")\n        tgt_doc = row.get(\"translation\", \"\")\n        src_lines = split_src_transliteration(src_doc)\n        tgt_sents = split_tgt_english(tgt_doc)\n        if not src_lines or not tgt_sents:\n            continue\n        pairs.extend(align_monotone(src_lines, tgt_sents, max_src_merge=2, max_tgt_merge=2))\n    out = pd.DataFrame(pairs, columns=[\"src\",\"tgt\"])\n    out = out[(out[\"src\"].str.len() > 0) & (out[\"tgt\"].str.len() > 0)].drop_duplicates()\n    return out.reset_index(drop=True)\n\n\n# 1) Document-level split (if not already done)\n\nval_frac = 0.08\ndoc_idx = np.arange(len(train_df))\nrng = np.random.default_rng(SEED)\nrng.shuffle(doc_idx)\n\nn_val_docs = max(1, int(len(doc_idx) * val_frac))\nval_doc_idx = doc_idx[:n_val_docs]\ntrn_doc_idx = doc_idx[n_val_docs:]\n\ntrain_docs = train_df.iloc[trn_doc_idx].reset_index(drop=True)\nval_docs   = train_df.iloc[val_doc_idx].reset_index(drop=True)\n\ntrn_df = reconstruct_pairs(train_docs)\nval_df = reconstruct_pairs(val_docs)\n\nprint(\"Pairs train:\", trn_df.shape, \"Pairs val:\", val_df.shape)\n\n\n# 2) Baseline model (quick)\n\ntokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)\nmodel = AutoModelForSeq2SeqLM.from_pretrained(MODEL_PATH).to(DEVICE)\n\nMAX_SRC = 256\nMAX_TGT = 128\n\nclass MTDataset(Dataset):\n    def __init__(self, df):\n        self.src = df[\"src\"].tolist()\n        self.tgt = df[\"tgt\"].tolist()\n    def __len__(self): return len(self.src)\n    def __getitem__(self, idx):\n        x = tokenizer(self.src[idx], max_length=MAX_SRC, truncation=True)\n        y = tokenizer(text_target=self.tgt[idx], max_length=MAX_TGT, truncation=True)\n        x[\"labels\"] = y[\"input_ids\"]\n        return x\n\ntrain_ds = MTDataset(trn_df)\nval_ds   = MTDataset(val_df)\ndata_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer, model=model)\n\nargs = Seq2SeqTrainingArguments(\n    output_dir=\"sec4_byt5\",\n    eval_strategy=\"steps\",\n    eval_steps=250,\n    save_steps=250,\n    logging_steps=50,\n    learning_rate=3e-4,\n    per_device_train_batch_size=8 if DEVICE==\"cuda\" else 2,\n    per_device_eval_batch_size=8 if DEVICE==\"cuda\" else 2,\n    gradient_accumulation_steps=2 if DEVICE==\"cuda\" else 1,\n    num_train_epochs=55,         # small (diagnostic)\n    predict_with_generate=True,\n    fp16=(DEVICE==\"cuda\"),\n    save_total_limit=1,\n    report_to=\"none\",\n    remove_unused_columns=False\n)\n\ntrainer = Seq2SeqTrainer(\n    model=model,\n    args=args,\n    train_dataset=train_ds,\n    eval_dataset=val_ds,\n    data_collator=data_collator\n)\ntrainer.train()\n\n\n# 3) Predict on validation\n\ndef make_one_sentence(en: str) -> str:\n    en = clean_ws(en)\n    if not en:\n        return \"...\"\n    parts = re.split(r\"(?<=[.!?])\\s+\", en)\n    parts = [p.strip() for p in parts if p.strip()]\n    parts = [re.sub(r\"[.!?]+\\s*$\", \"\", p).strip() for p in parts]\n    parts = [p for p in parts if p]\n    out = \"; \".join(parts).strip()\n    if not out.endswith(\".\"):\n        out += \".\"\n    return out\n\ndef generate_batch(texts, num_beams=4):\n    enc = tokenizer(texts, return_tensors=\"pt\", padding=True, truncation=True, max_length=MAX_SRC).to(DEVICE)\n    with torch.no_grad():\n        out_ids = model.generate(**enc, num_beams=num_beams, max_new_tokens=MAX_TGT, early_stopping=True)\n    preds = tokenizer.batch_decode(out_ids, skip_special_tokens=True)\n    return [make_one_sentence(p) for p in preds]\n\nval_eval = val_df.copy()\nval_eval[\"ref\"] = val_eval[\"tgt\"]\n\nBATCH = 16 if DEVICE==\"cuda\" else 4\npreds = []\nmodel.eval()\nfor i in range(0, len(val_eval), BATCH):\n    preds.extend(generate_batch(val_eval[\"src\"].iloc[i:i+BATCH].tolist()))\nval_eval[\"pred\"] = preds\n\n\n# 4) Error categories (5)\n\ndef wc(s: str) -> int:\n    s = clean_ws(s)\n    return 0 if not s else len(s.split())\n\ndef normalize_for_overlap(s: str) -> str:\n    s = clean_ws(s).lower()\n    s = re.sub(r\"[^a-z0-9\\s]\", \" \", s)\n    s = re.sub(r\"\\s+\", \" \", s).strip()\n    return s\n\ndef jaccard_words(a: str, b: str) -> float:\n    A = set(normalize_for_overlap(a).split())\n    B = set(normalize_for_overlap(b).split())\n    if not A and not B: return 1.0\n    if not A or not B: return 0.0\n    return len(A & B) / max(1, len(A | B))\n\ndef extract_numbers(s: str):\n    return re.findall(r\"\\d+(?:\\.\\d+)?\", clean_ws(s))\n\nPLACEHOLDER_PAT = re.compile(r\"(too broken for translation|obverse too broken|broken for translation)\", re.IGNORECASE)\ndef is_placeholder(s: str) -> bool:\n    return bool(PLACEHOLDER_PAT.search(clean_ws(s)))\n\ndef unk_rate_src(src: str) -> float:\n    toks = clean_ws(src).split()\n    if not toks: return 0.0\n    unk = sum(1 for t in toks if t in {\"x\",\"…\",\"...\",\"[...]\"} or \"…\" in t or \"[...]\" in t)\n    return unk / len(toks)\n\nval_eval[\"src_words\"]  = val_eval[\"src\"].map(wc)\nval_eval[\"ref_words\"]  = val_eval[\"ref\"].map(wc)\nval_eval[\"pred_words\"] = val_eval[\"pred\"].map(wc)\nval_eval[\"unk_rate\"]   = val_eval[\"src\"].map(unk_rate_src)\nval_eval[\"overlap\"]    = [jaccard_words(p, r) for p, r in zip(val_eval[\"pred\"], val_eval[\"ref\"])]\n\nval_eval[\"ref_placeholder\"]  = val_eval[\"ref\"].map(is_placeholder)\nval_eval[\"pred_placeholder\"] = val_eval[\"pred\"].map(is_placeholder)\n\nval_eval[\"ref_nums\"]  = val_eval[\"ref\"].map(extract_numbers)\nval_eval[\"pred_nums\"] = val_eval[\"pred\"].map(extract_numbers)\nval_eval[\"num_mismatch\"] = [int(a != b) for a, b in zip(val_eval[\"ref_nums\"], val_eval[\"pred_nums\"])]\n\ndef categorize(row):\n    if row[\"ref_placeholder\"] or row[\"pred_placeholder\"]:\n        return \"Placeholder / non-translation\"\n    if row[\"pred_words\"] <= 3 and row[\"ref_words\"] >= 12:\n        return \"Severe under-translation\"\n    if row[\"num_mismatch\"] == 1 and (len(row[\"ref_nums\"]) + len(row[\"pred_nums\"]) >= 2):\n        return \"Numbers & units drift\"\n    if row[\"unk_rate\"] >= 0.25 and row[\"overlap\"] <= 0.15:\n        return \"Corruption sensitivity (x/… heavy)\"\n    if (row[\"pred_words\"] >= 2.2 * max(1, row[\"ref_words\"])) or (row[\"overlap\"] <= 0.08 and row[\"ref_words\"] >= 8):\n        return \"Hallucination / off-target\"\n    return \"Other / mild\"\n\nval_eval[\"error_cat\"] = val_eval.apply(categorize, axis=1)\n\n\n# 5) Figure: error distribution (barh)\n\ncounts = val_eval[\"error_cat\"].value_counts().sort_values(ascending=True)\nplt.figure(figsize=(10, 5))\nplt.barh(counts.index, counts.values)\nplt.title(\"ByT5 baseline: validation error modes (count)\")\nplt.xlabel(\"# examples\")\nplt.tight_layout()\nplt.show()\n\n# 6) Print a few examples per category\n\ndef show_examples(df, cat, k=3, max_chars=360):\n    sub = df[df[\"error_cat\"] == cat].copy()\n    if len(sub) == 0:\n        return\n    sub = sub.sort_values([\"overlap\",\"unk_rate\"], ascending=[True, False])\n    print(\"\\n\" + \"=\"*95)\n    print(f\"{cat}  (n={len(sub)})\")\n    print(\"=\"*95)\n    for _, r in sub.head(k).iterrows():\n        print(f\"\\nSRC  ({r['src_words']}w, unk={r['unk_rate']:.2f}): {clean_ws(r['src'])[:max_chars]}{'...' if len(r['src'])>max_chars else ''}\")\n        print(f\"REF  ({r['ref_words']}w): {clean_ws(r['ref'])[:max_chars]}{'...' if len(r['ref'])>max_chars else ''}\")\n        print(f\"PRED ({r['pred_words']}w, overlap={r['overlap']:.2f}): {clean_ws(r['pred'])[:max_chars]}{'...' if len(r['pred'])>max_chars else ''}\")\n        if r[\"num_mismatch\"]:\n            print(f\"NUMS ref={r['ref_nums']} pred={r['pred_nums']}\")\n\nfor cat in [\n    \"Placeholder / non-translation\",\n    \"Severe under-translation\",\n    \"Numbers & units drift\",\n    \"Corruption sensitivity (x/… heavy)\",\n    \"Hallucination / off-target\",\n    \"Other / mild\",\n]:\n    show_examples(val_eval, cat, k=3)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T16:40:31.302408Z","iopub.execute_input":"2025-12-23T16:40:31.302698Z","iopub.status.idle":"2025-12-23T16:52:49.436245Z","shell.execute_reply.started":"2025-12-23T16:40:31.302675Z","shell.execute_reply":"2025-12-23T16:52:49.435630Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# 3B) Inference on TEST + submission\n\n\nTEST_PATH  = \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\"\ntest_df = pd.read_csv(TEST_PATH)\nprint(\"test:\", test_df.shape)\n\n# Reuse make_one_sentence + generate_batch defined above\n\ndef generate_for_test(df, batch_size=16, num_beams=4):\n    model.eval()\n    preds = []\n    for i in range(0, len(df), batch_size):\n        batch_texts = df[\"transliteration\"].iloc[i:i+batch_size].tolist()\n        preds.extend(generate_batch(batch_texts, num_beams=num_beams))\n    return preds\n\nBATCH_TEST = 16 if DEVICE == \"cuda\" else 4\ntest_preds = generate_for_test(test_df, batch_size=BATCH_TEST, num_beams=4)\n\nsub = pd.DataFrame({\"id\": test_df[\"id\"], \"translation\": test_preds})\n\n# ---- Sanity checks ----\nsub[\"translation_len\"] = sub[\"translation\"].map(lambda x: len(clean_ws(x).split()))\nprint(\"Submission sanity:\")\nprint(\" - empty:\", int((sub[\"translation\"].str.strip() == \"\").sum()))\nprint(\" - min_len:\", int(sub[\"translation_len\"].min()))\nprint(\" - median_len:\", float(sub[\"translation_len\"].median()))\nprint(\" - max_len:\", int(sub[\"translation_len\"].max()))\n\n# Save\nsub[[\"id\",\"translation\"]].to_csv(\"submission.csv\", index=False)\nprint(\" Wrote submission.csv\")\nsub.head(10)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}