{"cells": [{"cell_type": "markdown", "metadata": {}, "source": "# Deep Past: Akkadian \u2192 English Translation\n## V1: TF-IDF Retrieval Baseline\n\nStrategy: For each test transliteration, find the most similar training sample via TF-IDF cosine similarity, then return that sample's translation. Simple but surprisingly effective for narrow-domain ancient texts."}, {"cell_type": "code", "source": "import os, pandas as pd, numpy as np, re\nfrom sklearn.feature_extraction.text import TfidfVectorizer\nfrom sklearn.metrics.pairwise import cosine_similarity\n\n# Competition data path (confirmed via diagnostic)\nBASE     = '/kaggle/input/competitions/deep-past-initiative-machine-translation'\nOUT_PATH = '/kaggle/working/submission.csv'\n\ntrain = pd.read_csv(f'{BASE}/train.csv').dropna(subset=['transliteration','translation'])\ntest  = pd.read_csv(f'{BASE}/test.csv')\nsent  = pd.read_csv(f'{BASE}/Sentences_Oare_FirstWord_LinNum.csv')\nprint(f'Train: {len(train)} | Test: {len(test)}')", "metadata": {}, "execution_count": null, "outputs": []}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# \u2500\u2500 Text normalizer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nimport re\n\ndef normalize(text):\n    if pd.isna(text):\n        return ''\n    text = str(text).lower()\n    text = re.sub(r'[_{}\\[\\]\u2026]+', ' ', text)\n    text = re.sub(r'\\s+', ' ', text).strip()\n    return text\n\ntrain_texts = train['transliteration'].apply(normalize).tolist()\ntest_texts  = test['transliteration'].apply(normalize).tolist()\n\nprint('Sample normalized train:', train_texts[0][:120])\nprint('Sample normalized test: ', test_texts[0][:120])"}, {"cell_type": "code", "source": "# \u2500\u2500 Build TF-IDF index \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfrom sklearn.feature_extraction.text import TfidfVectorizer\nfrom sklearn.metrics.pairwise import cosine_similarity\n\nprint('Building TF-IDF index...')\nvectorizer = TfidfVectorizer(\n    analyzer='char_wb',\n    ngram_range=(2, 4),\n    min_df=1,\n    max_features=100_000,\n    sublinear_tf=True,\n)\n\ntrain_vecs = vectorizer.fit_transform(train_texts)\ntest_vecs  = vectorizer.transform(test_texts)\n\nprint(f'Vocabulary size: {len(vectorizer.vocabulary_):,}')\nprint(f'Train matrix: {train_vecs.shape}')", "metadata": {}, "execution_count": null, "outputs": []}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# \u2500\u2500 Retrieve top-K matches for each test sample \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nK = 5  # show top-5, use top-1 as prediction\n\nsims = cosine_similarity(test_vecs, train_vecs)  # (4, 1561)\n\nresults = []\nfor i, row in test.iterrows():\n    sim_row = sims[i]\n    top_k_idx = sim_row.argsort()[::-1][:K]\n    \n    print(f\"\\n{'='*70}\")\n    print(f\"Test ID {row['id']} | text_id={row['text_id']} lines {row['line_start']}-{row['line_end']}\")\n    print(f\"Query: {row['transliteration'][:120]}\")\n    print()\n    \n    for rank, idx in enumerate(top_k_idx):\n        score = sim_row[idx]\n        print(f\"  Rank {rank+1} (sim={score:.4f}):\")\n        print(f\"    SRC: {train.iloc[idx]['transliteration'][:120]}\")\n        print(f\"    TGT: {train.iloc[idx]['translation'][:120]}\")\n    \n    # Best prediction = top-1 match's translation\n    best_idx = top_k_idx[0]\n    prediction = train.iloc[best_idx]['translation']\n    results.append({'id': row['id'], 'translation': prediction})\n\nprint(f\"\\nGenerated {len(results)} predictions.\")"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# \u2500\u2500 Also try word-level TF-IDF as ensemble backup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nprint('Word-level TF-IDF for comparison...')\nword_vec = TfidfVectorizer(\n    analyzer='word',\n    ngram_range=(1, 2),\n    min_df=1,\n    sublinear_tf=True,\n)\ntrain_wv = word_vec.fit_transform(train_texts)\ntest_wv  = word_vec.transform(test_texts)\n\nsims_word = cosine_similarity(test_wv, train_wv)\n\n# Ensemble: average char-ngram + word sims\nsims_ensemble = 0.6 * sims + 0.4 * sims_word\n\nresults_ensemble = []\nprint('\\nEnsemble predictions:')\nfor i, row in test.iterrows():\n    best_idx = sims_ensemble[i].argmax()\n    score = sims_ensemble[i, best_idx]\n    prediction = train.iloc[best_idx]['translation']\n    results_ensemble.append({'id': row['id'], 'translation': prediction})\n    print(f\"  ID {row['id']}: sim={score:.4f} \u2192 {prediction[:80]}\")"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# \u2500\u2500 Context expansion: use Sentences file to find neighboring lines \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Test rows have text_id, line_start, line_end\n# Try to pull adjacent lines from train to get more context\n\nprint('Attempting context-expanded retrieval...')\nprint('Sentences columns:', sent.columns.tolist())\nprint(sent.head(2))"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# \u2500\u2500 Check if test text_ids appear in train \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# train has 'oare_id' column, test has 'text_id'\n# Sentences has 'text_uuid' and 'sentence_uuid'\n\ntest_text_ids = set(test['text_id'].tolist())\nprint('Test text_ids:', test_text_ids)\n\n# Check overlap with Sentences\nsent_text_ids = set(sent['text_uuid'].tolist())\noverlap = test_text_ids & sent_text_ids\nprint(f'Overlap with Sentences file: {len(overlap)} of {len(test_text_ids)}')\n\n# Show matching sentences for test text_ids\nfor tid in test_text_ids:\n    matches = sent[sent['text_uuid'] == tid]\n    if len(matches) > 0:\n        print(f\"\\ntext_id {tid}: {len(matches)} sentences\")\n        print(matches[['sentence_obj_in_text','translation','first_word_transcription','line_number']].head(10).to_string())"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# \u2500\u2500 Generate final submission \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Use ensemble as primary submission\nsubmission = pd.DataFrame(results_ensemble)\nsubmission = submission.sort_values('id').reset_index(drop=True)\n\nprint('Final submission:')\nprint(submission.to_string())\n\nsubmission.to_csv(OUT_PATH, index=False)\nprint(f'\\nSaved to {OUT_PATH}')"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# \u2500\u2500 Self-evaluation: estimate BLEU on train split \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Leave-one-out BLEU on a small sample to sanity-check\nfrom nltk.translate.bleu_score import sentence_bleu, SmoothingFunction\nimport nltk\nnltk.download('punkt', quiet=True)\n\nsmoother = SmoothingFunction().method1\n\n# Sample 100 train rows, retrieve best match excluding self\nsample_idx = np.random.choice(len(train), size=min(100, len(train)), replace=False)\nbleu_scores = []\n\nfor idx in sample_idx:\n    query_vec = train_vecs[idx]\n    sims_row = cosine_similarity(query_vec, train_vecs)[0]\n    sims_row[idx] = -1  # exclude self\n    best = sims_row.argmax()\n    \n    ref = train.iloc[idx]['translation'].lower().split()\n    hyp = train.iloc[best]['translation'].lower().split()\n    \n    if len(ref) > 0 and len(hyp) > 0:\n        score = sentence_bleu([ref], hyp, smoothing_function=smoother)\n        bleu_scores.append(score)\n\navg_bleu = np.mean(bleu_scores) * 100\nprint(f'Leave-one-out BLEU (100 samples): {avg_bleu:.2f}')\nprint(f'(This is a lower bound \u2014 test queries may match better due to overlapping texts)')"}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"name": "python", "version": "3.10.0"}}, "nbformat": 4, "nbformat_minor": 4}