{"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":261126545,"sourceType":"kernelVersion"},{"sourceId":287306416,"sourceType":"kernelVersion"},{"sourceId":282751,"sourceType":"modelInstanceVersion","modelInstanceId":239470,"modelId":222398}],"dockerImageVersionId":31236,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Deep Past Initiative – Machine Translation (Inference Notebook)\n\nI noticed that the English text after translation sometimes sounded unnatural or awkward.\nTo address this issue, I have shared code that applies LLM-based post-processing to improve the quality of the translated text.\n\nThe current implementation is based on [the publicly available Best Score code](https://www.kaggle.com/code/xbar19/deep-past-challenge-byt5-base-inference), with additional post-processing using Gemma-3.","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport torch\nfrom torch.utils.data import DataLoader, Dataset\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm.auto import tqdm","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-22T06:51:37.236655Z","iopub.execute_input":"2025-12-22T06:51:37.236911Z","iopub.status.idle":"2025-12-22T06:51:42.730197Z","shell.execute_reply.started":"2025-12-22T06:51:37.236877Z","shell.execute_reply":"2025-12-22T06:51:42.729603Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"MODEL_PATH = \"/kaggle/input/deep-past-challenge-byt5-base-training/byt5-base-akkadian\".","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-22T06:51:42.731091Z","iopub.execute_input":"2025-12-22T06:51:42.731583Z","iopub.status.idle":"2025-12-22T06:51:42.735293Z","shell.execute_reply.started":"2025-12-22T06:51:42.731549Z","shell.execute_reply":"2025-12-22T06:51:42.734656Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TEST_DATA_PATH = \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\"\nBATCH_SIZE = 4\nMAX_LENGTH = 512\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\ntokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)\nmodel = AutoModelForSeq2SeqLM.from_pretrained(MODEL_PATH).to(DEVICE)\nmodel.eval()\n\ntest_df = pd.read_csv(TEST_DATA_PATH)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-22T06:51:47.526772Z","iopub.execute_input":"2025-12-22T06:51:47.527392Z","iopub.status.idle":"2025-12-22T06:51:48.361213Z","shell.execute_reply.started":"2025-12-22T06:51:47.527341Z","shell.execute_reply":"2025-12-22T06:51:48.360658Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"PREFIX = \"translate Akkadian to English: \"\n\nclass InferenceDataset(Dataset):\n    def __init__(self, df, tokenizer):\n        self.texts = df['transliteration'].astype(str).tolist()\n        self.texts = [PREFIX + i for i in self.texts]\n        self.tokenizer = tokenizer\n        \n    def __len__(self):\n        return len(self.texts)\n    \n    def __getitem__(self, idx):\n        text = self.texts[idx]\n        inputs = self.tokenizer(\n            text, \n            max_length=MAX_LENGTH, \n            padding=\"max_length\", \n            truncation=True, \n            return_tensors=\"pt\"\n        )\n        return {\n            \"input_ids\": inputs[\"input_ids\"].squeeze(0),\n            \"attention_mask\": inputs[\"attention_mask\"].squeeze(0)\n        }\n\ntest_dataset = InferenceDataset(test_df, tokenizer)\ntest_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)\n\nprint(\"Starting Inference...\")\nall_predictions = []","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-22T06:51:48.362117Z","iopub.execute_input":"2025-12-22T06:51:48.362349Z","iopub.status.idle":"2025-12-22T06:51:48.370121Z","shell.execute_reply.started":"2025-12-22T06:51:48.362325Z","shell.execute_reply":"2025-12-22T06:51:48.369546Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with torch.no_grad():\n    for batch in tqdm(test_loader):\n        input_ids = batch[\"input_ids\"].to(DEVICE)\n        attention_mask = batch[\"attention_mask\"].to(DEVICE)\n  \n        outputs = model.generate(\n            input_ids=input_ids,\n            attention_mask=attention_mask,\n            max_length=MAX_LENGTH,\n            num_beams=4,\n            early_stopping=True\n        )\n        \n        decoded = tokenizer.batch_decode(outputs, skip_special_tokens=True)\n        all_predictions.extend([d.strip() for d in decoded])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-22T06:51:48.374652Z","iopub.execute_input":"2025-12-22T06:51:48.374932Z","iopub.status.idle":"2025-12-22T06:51:53.61574Z","shell.execute_reply.started":"2025-12-22T06:51:48.374896Z","shell.execute_reply":"2025-12-22T06:51:53.614852Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Post Processing with LLM","metadata":{}},{"cell_type":"code","source":"import torch\nimport gc\nimport pandas as pd\nfrom tqdm.auto import tqdm\nfrom transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig, AutoProcessor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-22T06:51:53.616704Z","iopub.execute_input":"2025-12-22T06:51:53.616928Z","iopub.status.idle":"2025-12-22T06:51:53.642949Z","shell.execute_reply.started":"2025-12-22T06:51:53.616904Z","shell.execute_reply":"2025-12-22T06:51:53.642238Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"del model\ndel tokenizer\ndel test_loader\ndel test_dataset\n\ngc.collect()\ntorch.cuda.empty_cache()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-22T06:51:53.64395Z","iopub.execute_input":"2025-12-22T06:51:53.644636Z","iopub.status.idle":"2025-12-22T06:51:54.155273Z","shell.execute_reply.started":"2025-12-22T06:51:53.644608Z","shell.execute_reply":"2025-12-22T06:51:54.154486Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"LLM_MODEL_PATH = \"/kaggle/input/gemma-3/transformers/gemma-3-4b-it/1\"\n\n# 4bit量子化でロード (T4 GPU x2環境でも動作可能に)\n#bnb_config = BitsAndBytesConfig(\n#    load_in_4bit=True,\n#    bnb_4bit_quant_type=\"nf4\",\n#    bnb_4bit_compute_dtype=torch.float16,\n#    bnb_4bit_use_double_quant=True,\n#)\n\nprint(f\"🚀 Loading LLM from {LLM_MODEL_PATH}...\")\nllm_tokenizer = AutoTokenizer.from_pretrained(LLM_MODEL_PATH)\nllm_model = AutoModelForCausalLM.from_pretrained(\n    LLM_MODEL_PATH,\n    #quantization_config=bnb_config,\n    #device_map=\"auto\",\n    device_map={\"\": 0},\n    #torch_dtype=torch.float16,\n    torch_dtype=torch.bfloat16,\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-22T06:51:54.156196Z","iopub.execute_input":"2025-12-22T06:51:54.156443Z","iopub.status.idle":"2025-12-22T06:51:59.517706Z","shell.execute_reply.started":"2025-12-22T06:51:54.156417Z","shell.execute_reply":"2025-12-22T06:51:59.517017Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"llm_model.device","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-22T06:51:59.519506Z","iopub.execute_input":"2025-12-22T06:51:59.51981Z","iopub.status.idle":"2025-12-22T06:51:59.525191Z","shell.execute_reply.started":"2025-12-22T06:51:59.519786Z","shell.execute_reply":"2025-12-22T06:51:59.524623Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# プロンプト作成 - 通常のchat形式に修正\ndef make_gemma3_prompt(draft_text):\n    system_text = \"\"\"You are an expert editor of academic translations of ancient texts.\nRewrite the following English translation to correct grammar and improve flow.\n\nSTRICT RULES:\n1. Keep the original meaning exactly as is. Do NOT add new information.\n2. CRITICAL: Preserve all brackets [...], parentheses (...), and ellipses ... exactly as they are. Do not remove them.\n3. If the text is \"broken text\" or meaningless, output it as is.\n4. Output ONLY the rewritten text.\"\"\"\n\n    user_text = f'Original: \"{draft_text}\"'\n\n    # 通常のchat形式（VLM形式ではない）\n    return [\n        {\n            \"role\": \"user\",\n            \"content\": system_text + \"\\n\\n\" + user_text\n        }\n    ]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-22T06:51:59.52617Z","iopub.execute_input":"2025-12-22T06:51:59.526825Z","iopub.status.idle":"2025-12-22T06:51:59.538792Z","shell.execute_reply.started":"2025-12-22T06:51:59.526783Z","shell.execute_reply":"2025-12-22T06:51:59.538212Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"original_texts = all_predictions\npolished_texts = []\n\nprint(f\"✍️ Polishing {len(all_predictions)} sentences with LLM...\")\n\n# バッチ処理はパディング等の事故が起きやすいため、安全に1件ずつ処理します\n# (この規模なら1時間以内で終わります)\nfor text in tqdm(original_texts):\n    # 条件: 極端に短い文や壊れた文はLLMを通さない (時間短縮 & リスク回避)\n    if len(str(text)) < 5 or text == \"broken text\":\n        polished_texts.append(text)\n        continue\n\n    # メッセージリスト作成    \n    messages = make_gemma3_prompt(str(text))\n    \n    # tokenizerを直接使用\n    prompt_text = llm_tokenizer.apply_chat_template(\n        messages, \n        tokenize=False, \n        add_generation_prompt=True\n    )\n    \n    # tokenizerで直接トークン化\n    inputs = llm_tokenizer(prompt_text, return_tensors=\"pt\").to(llm_model.device)\n\n    # 入力の長さを取得 (出力カット用)\n    input_len = inputs[\"input_ids\"].shape[1]\n    \n    with torch.no_grad():\n        outputs = llm_model.generate(\n            **inputs,\n            max_new_tokens=512,  # 修正だけなので短めでOK\n            do_sample=False,\n            num_beams=1,\n            repetition_penalty=1.1\n        )\n\n    # ★重要変更点7: 入力プロンプト部分をカットしてデコード\n    generated_tokens = outputs[0][input_len:]\n    response = llm_tokenizer.decode(generated_tokens, skip_special_tokens=True).strip()\n    #print(f\"{response=}\")\n\n    # Gemma 3特有のアーティファクト除去\n    if \"model\\n\" in response:\n        response = response.split(\"model\\n\")[-1].strip()\n\n    # 余計な引用符の除去\n    if response.startswith('\"') and response.endswith('\"'):\n        response = response[1:-1]\n\n    # === 安全装置 ===\n    if text.count('[') != response.count('['):\n        polished_texts.append(text)\n    elif len(response) < len(text) * 0.5:\n        polished_texts.append(text)\n    else:\n        polished_texts.append(response)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-22T06:54:31.77763Z","iopub.execute_input":"2025-12-22T06:54:31.777939Z","iopub.status.idle":"2025-12-22T06:54:43.166812Z","shell.execute_reply.started":"2025-12-22T06:54:31.777909Z","shell.execute_reply":"2025-12-22T06:54:43.165991Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = pd.DataFrame({\n    \"id\": test_df[\"id\"],\n    \"translation\": polished_texts\n})\n\nsubmission[\"translation\"] = submission[\"translation\"].apply(lambda x: x if len(x) > 0 else \"broken text\")\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"Submission file saved successfully!\")\nsubmission.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-22T06:55:29.745783Z","iopub.execute_input":"2025-12-22T06:55:29.746685Z","iopub.status.idle":"2025-12-22T06:55:29.771448Z","shell.execute_reply.started":"2025-12-22T06:55:29.746639Z","shell.execute_reply":"2025-12-22T06:55:29.770736Z"}},"outputs":[],"execution_count":null}]}