{"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":[{"sourceType":"competition","sourceId":121150,"databundleVersionId":15833391},{"sourceType":"modelInstanceVersion","sourceId":755680,"databundleVersionId":15750522,"modelInstanceId":577094,"modelId":589419},{"sourceType":"kernelVersion","sourceId":304665786},{"sourceType":"kernelVersion","sourceId":304817244},{"sourceType":"kernelVersion","sourceId":305652930},{"sourceType":"kernelVersion","sourceId":305666636},{"sourceType":"kernelVersion","sourceId":305750509},{"sourceType":"kernelVersion","sourceId":305752696},{"sourceType":"kernelVersion","sourceId":305788062}],"dockerImageVersionId":31260,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"%%writefile helsinki_final.py\n\"\"\"\nHelsinki (OPUS-MT) Akkadian — INFERENCE ONLY\n=============================================\nLoads a trained Helsinki/OPUS-MT model and runs inference on the test set.\nSaves all 4 beam search candidates per input.\n\"\"\"\nimport os\nimport pandas as pd\nimport torch\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm import tqdm\n\n# ==========================================\n# Configuration\n# ==========================================\nclass Config:\n    # Path to the trained model directory (contains config.json, pytorch_model.bin, etc.)\n    MODEL_DIR = \"/kaggle/input/notebooks/paritoshtripathi5/akkadian-helsinki-final-model/helsinki-akkadian-linelevel/\"\n\n    # Data paths\n    INPUT_DIR = \"/kaggle/input/deep-past-initiative-machine-translation/\"\n\n    # Token limits (must match training)\n    MAX_INPUT_LENGTH = 256\n    MAX_TARGET_LENGTH = 256\n\n    # Inference settings\n    BATCH_SIZE = 8\n    NUM_BEAMS = 4\n    NUM_RETURN_SEQUENCES = 4\n    EARLY_STOPPING = True\n\n\ndef main():\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Device: {device}\")\n\n    # Load model and tokenizer\n    print(f\"Loading model from {Config.MODEL_DIR} ...\")\n    tokenizer = AutoTokenizer.from_pretrained(Config.MODEL_DIR)\n    model = AutoModelForSeq2SeqLM.from_pretrained(Config.MODEL_DIR).to(device)\n    model.eval()\n    print(\"Model loaded.\")\n\n    # Load test data\n    test_df = pd.read_csv(f\"{Config.INPUT_DIR}/test.csv\")\n    print(f\"Test samples: {len(test_df)}\")\n\n    test_inputs = test_df[\"transliteration\"].astype(str).tolist()\n\n    predictions = []\n\n    with torch.no_grad():\n        for i in tqdm(range(0, len(test_inputs), Config.BATCH_SIZE), desc=\"Inference\"):\n            batch_texts = test_inputs[i : i + Config.BATCH_SIZE]\n\n            batch_enc = tokenizer(\n                batch_texts,\n                padding=True,\n                truncation=True,\n                max_length=Config.MAX_INPUT_LENGTH,\n                return_tensors=\"pt\",\n            ).to(device)\n\n            generated_ids = model.generate(\n                **batch_enc,\n                max_new_tokens=Config.MAX_TARGET_LENGTH,\n                num_beams=Config.NUM_BEAMS,\n                num_return_sequences=Config.NUM_RETURN_SEQUENCES,\n                early_stopping=Config.EARLY_STOPPING,\n            )\n\n            decoded = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)\n\n            # Group every 4 candidates together (one group per input)\n            for j in range(0, len(decoded), Config.NUM_RETURN_SEQUENCES):\n                candidates = decoded[j : j + Config.NUM_RETURN_SEQUENCES]\n                predictions.append({\n                    \"candidate_1\": candidates[0].strip() or \"broken text\",\n                    \"candidate_2\": candidates[1].strip() or \"broken text\",\n                    \"candidate_3\": candidates[2].strip() or \"broken text\",\n                    \"candidate_4\": candidates[3].strip() or \"broken text\",\n                })\n\n            del batch_enc, generated_ids\n\n    submission = pd.DataFrame(predictions)\n    submission.insert(0, \"id\", test_df[\"id\"])\n    submission.insert(1, \"transliteration\", test_df[\"transliteration\"])\n    submission.to_csv(\"submission_helsinki_final.csv\", index=False)\n\n    print(f\"Saved submission.csv ({len(submission)} rows)\")\n    print(submission.head())\n\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-23T14:28:09.887414Z","iopub.execute_input":"2026-03-23T14:28:09.888018Z","iopub.status.idle":"2026-03-23T14:28:09.897547Z","shell.execute_reply.started":"2026-03-23T14:28:09.887988Z","shell.execute_reply":"2026-03-23T14:28:09.896529Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile helsinki_mt_mul.py\n\"\"\"\nHelsinki (OPUS-MT) Akkadian — INFERENCE ONLY\n=============================================\nLoads a trained Helsinki/OPUS-MT model and runs inference on the test set.\nSaves all 4 beam search candidates per input.\n\"\"\"\nimport os\nimport pandas as pd\nimport torch\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm import tqdm\n\n# ==========================================\n# Configuration\n# ==========================================\nclass Config:\n    # Path to the trained model directory (contains config.json, pytorch_model.bin, etc.)\n    MODEL_DIR = \"/kaggle/input/notebooks/paritoshtripathi5/akkadian-helsinki-mt-mul-final-model/helsinki-akkadian-linelevel/\"\n\n    # Data paths\n    INPUT_DIR = \"/kaggle/input/deep-past-initiative-machine-translation/\"\n\n    # Token limits (must match training)\n    MAX_INPUT_LENGTH = 256\n    MAX_TARGET_LENGTH = 256\n\n    # Inference settings\n    BATCH_SIZE = 8\n    NUM_BEAMS = 4\n    NUM_RETURN_SEQUENCES = 4\n    EARLY_STOPPING = True\n\n\ndef main():\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Device: {device}\")\n\n    # Load model and tokenizer\n    print(f\"Loading model from {Config.MODEL_DIR} ...\")\n    tokenizer = AutoTokenizer.from_pretrained(Config.MODEL_DIR)\n    model = AutoModelForSeq2SeqLM.from_pretrained(Config.MODEL_DIR).to(device)\n    model.eval()\n    print(\"Model loaded.\")\n\n    # Load test data\n    test_df = pd.read_csv(f\"{Config.INPUT_DIR}/test.csv\")\n    print(f\"Test samples: {len(test_df)}\")\n\n    test_inputs = test_df[\"transliteration\"].astype(str).tolist()\n\n    predictions = []\n\n    with torch.no_grad():\n        for i in tqdm(range(0, len(test_inputs), Config.BATCH_SIZE), desc=\"Inference\"):\n            batch_texts = test_inputs[i : i + Config.BATCH_SIZE]\n\n            batch_enc = tokenizer(\n                batch_texts,\n                padding=True,\n                truncation=True,\n                max_length=Config.MAX_INPUT_LENGTH,\n                return_tensors=\"pt\",\n            ).to(device)\n\n            generated_ids = model.generate(\n                **batch_enc,\n                max_new_tokens=Config.MAX_TARGET_LENGTH,\n                num_beams=Config.NUM_BEAMS,\n                num_return_sequences=Config.NUM_RETURN_SEQUENCES,\n                early_stopping=Config.EARLY_STOPPING,\n            )\n\n            decoded = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)\n\n            # Group every 4 candidates together (one group per input)\n            for j in range(0, len(decoded), Config.NUM_RETURN_SEQUENCES):\n                candidates = decoded[j : j + Config.NUM_RETURN_SEQUENCES]\n                predictions.append({\n                    \"candidate_1\": candidates[0].strip() or \"broken text\",\n                    \"candidate_2\": candidates[1].strip() or \"broken text\",\n                    \"candidate_3\": candidates[2].strip() or \"broken text\",\n                    \"candidate_4\": candidates[3].strip() or \"broken text\",\n                })\n\n            del batch_enc, generated_ids\n\n    submission = pd.DataFrame(predictions)\n    submission.insert(0, \"id\", test_df[\"id\"])\n    submission.insert(1, \"transliteration\", test_df[\"transliteration\"])\n    submission.to_csv(\"submission_helsinki_mt_mul.csv\", index=False)\n\n    print(f\"Saved submission.csv ({len(submission)} rows)\")\n    print(submission.head())\n\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-23T14:28:09.89888Z","iopub.execute_input":"2026-03-23T14:28:09.899183Z","iopub.status.idle":"2026-03-23T14:28:09.914802Z","shell.execute_reply.started":"2026-03-23T14:28:09.899162Z","shell.execute_reply":"2026-03-23T14:28:09.91402Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile helsinki_all_ext.py\n\"\"\"\nHelsinki (OPUS-MT) Akkadian — INFERENCE ONLY\n=============================================\nLoads a trained Helsinki/OPUS-MT model and runs inference on the test set.\nSaves all 4 beam search candidates per input.\n\"\"\"\nimport os\nimport pandas as pd\nimport torch\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm import tqdm\n\n# ==========================================\n# Configuration\n# ==========================================\nclass Config:\n    # Path to the trained model directory (contains config.json, pytorch_model.bin, etc.)\n    MODEL_DIR = \"/kaggle/input/notebooks/paritoshtripathi5/akkadian-helsinki-final-model-all-ext/helsinki-akkadian-linelevel/\"\n\n    # Data paths\n    INPUT_DIR = \"/kaggle/input/deep-past-initiative-machine-translation/\"\n\n    # Token limits (must match training)\n    MAX_INPUT_LENGTH = 256\n    MAX_TARGET_LENGTH = 256\n\n    # Inference settings\n    BATCH_SIZE = 8\n    NUM_BEAMS = 4\n    NUM_RETURN_SEQUENCES = 4\n    EARLY_STOPPING = True\n\n\ndef main():\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Device: {device}\")\n\n    # Load model and tokenizer\n    print(f\"Loading model from {Config.MODEL_DIR} ...\")\n    tokenizer = AutoTokenizer.from_pretrained(Config.MODEL_DIR)\n    model = AutoModelForSeq2SeqLM.from_pretrained(Config.MODEL_DIR).to(device)\n    model.eval()\n    print(\"Model loaded.\")\n\n    # Load test data\n    test_df = pd.read_csv(f\"{Config.INPUT_DIR}/test.csv\")\n    print(f\"Test samples: {len(test_df)}\")\n\n    test_inputs = test_df[\"transliteration\"].astype(str).tolist()\n\n    predictions = []\n\n    with torch.no_grad():\n        for i in tqdm(range(0, len(test_inputs), Config.BATCH_SIZE), desc=\"Inference\"):\n            batch_texts = test_inputs[i : i + Config.BATCH_SIZE]\n\n            batch_enc = tokenizer(\n                batch_texts,\n                padding=True,\n                truncation=True,\n                max_length=Config.MAX_INPUT_LENGTH,\n                return_tensors=\"pt\",\n            ).to(device)\n\n            generated_ids = model.generate(\n                **batch_enc,\n                max_new_tokens=Config.MAX_TARGET_LENGTH,\n                num_beams=Config.NUM_BEAMS,\n                num_return_sequences=Config.NUM_RETURN_SEQUENCES,\n                early_stopping=Config.EARLY_STOPPING,\n            )\n\n            decoded = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)\n\n            # Group every 4 candidates together (one group per input)\n            for j in range(0, len(decoded), Config.NUM_RETURN_SEQUENCES):\n                candidates = decoded[j : j + Config.NUM_RETURN_SEQUENCES]\n                predictions.append({\n                    \"candidate_1\": candidates[0].strip() or \"broken text\",\n                    \"candidate_2\": candidates[1].strip() or \"broken text\",\n                    \"candidate_3\": candidates[2].strip() or \"broken text\",\n                    \"candidate_4\": candidates[3].strip() or \"broken text\",\n                })\n\n            del batch_enc, generated_ids\n\n    submission = pd.DataFrame(predictions)\n    submission.insert(0, \"id\", test_df[\"id\"])\n    submission.insert(1, \"transliteration\", test_df[\"transliteration\"])\n    submission.to_csv(\"submission_helsinki_all_ext.csv\", index=False)\n\n    print(f\"Saved submission.csv ({len(submission)} rows)\")\n    print(submission.head())\n\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-23T14:28:10.044151Z","iopub.execute_input":"2026-03-23T14:28:10.044713Z","iopub.status.idle":"2026-03-23T14:28:10.050621Z","shell.execute_reply.started":"2026-03-23T14:28:10.04469Z","shell.execute_reply":"2026-03-23T14:28:10.049713Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile helsinki_5_ep.py\n\"\"\"\nHelsinki (OPUS-MT) Akkadian — INFERENCE ONLY\n=============================================\nLoads a trained Helsinki/OPUS-MT model and runs inference on the test set.\nSaves all 4 beam search candidates per input.\n\"\"\"\nimport os\nimport pandas as pd\nimport torch\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm import tqdm\n\n# ==========================================\n# Configuration\n# ==========================================\nclass Config:\n    # Path to the trained model directory (contains config.json, pytorch_model.bin, etc.)\n    MODEL_DIR = \"/kaggle/input/notebooks/paritoshtripathi5/akkadian-helsinki-final-model-all-ext/helsinki-akkadian-linelevel/\"\n\n    # Data paths\n    INPUT_DIR = \"/kaggle/input/deep-past-initiative-machine-translation/\"\n\n    # Token limits (must match training)\n    MAX_INPUT_LENGTH = 256\n    MAX_TARGET_LENGTH = 256\n\n    # Inference settings\n    BATCH_SIZE = 8\n    NUM_BEAMS = 4\n    NUM_RETURN_SEQUENCES = 4\n    EARLY_STOPPING = True\n\n\ndef main():\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Device: {device}\")\n\n    # Load model and tokenizer\n    print(f\"Loading model from {Config.MODEL_DIR} ...\")\n    tokenizer = AutoTokenizer.from_pretrained(Config.MODEL_DIR)\n    model = AutoModelForSeq2SeqLM.from_pretrained(Config.MODEL_DIR).to(device)\n    model.eval()\n    print(\"Model loaded.\")\n\n    # Load test data\n    test_df = pd.read_csv(f\"{Config.INPUT_DIR}/test.csv\")\n    print(f\"Test samples: {len(test_df)}\")\n\n    test_inputs = test_df[\"transliteration\"].astype(str).tolist()\n\n    predictions = []\n\n    with torch.no_grad():\n        for i in tqdm(range(0, len(test_inputs), Config.BATCH_SIZE), desc=\"Inference\"):\n            batch_texts = test_inputs[i : i + Config.BATCH_SIZE]\n\n            batch_enc = tokenizer(\n                batch_texts,\n                padding=True,\n                truncation=True,\n                max_length=Config.MAX_INPUT_LENGTH,\n                return_tensors=\"pt\",\n            ).to(device)\n\n            generated_ids = model.generate(\n                **batch_enc,\n                max_new_tokens=Config.MAX_TARGET_LENGTH,\n                num_beams=Config.NUM_BEAMS,\n                num_return_sequences=Config.NUM_RETURN_SEQUENCES,\n                early_stopping=Config.EARLY_STOPPING,\n            )\n\n            decoded = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)\n\n            # Group every 4 candidates together (one group per input)\n            for j in range(0, len(decoded), Config.NUM_RETURN_SEQUENCES):\n                candidates = decoded[j : j + Config.NUM_RETURN_SEQUENCES]\n                predictions.append({\n                    \"candidate_1\": candidates[0].strip() or \"broken text\",\n                    \"candidate_2\": candidates[1].strip() or \"broken text\",\n                    \"candidate_3\": candidates[2].strip() or \"broken text\",\n                    \"candidate_4\": candidates[3].strip() or \"broken text\",\n                })\n\n            del batch_enc, generated_ids\n\n    submission = pd.DataFrame(predictions)\n    submission.insert(0, \"id\", test_df[\"id\"])\n    submission.insert(1, \"transliteration\", test_df[\"transliteration\"])\n    submission.to_csv(\"submission_helsinki_5_ep.csv\", index=False)\n\n    print(f\"Saved submission.csv ({len(submission)} rows)\")\n    print(submission.head())\n\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-23T14:28:10.052386Z","iopub.execute_input":"2026-03-23T14:28:10.052671Z","iopub.status.idle":"2026-03-23T14:28:10.067825Z","shell.execute_reply.started":"2026-03-23T14:28:10.052652Z","shell.execute_reply":"2026-03-23T14:28:10.067097Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile byt5_34_4.py\nimport os\nimport pandas as pd\nimport torch\nfrom torch.utils.data import DataLoader, Dataset\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm import tqdm\n\nclass InferenceConfig:\n    \"\"\"Configuration for ByT5 Inference\"\"\"\n    # Paths (Adjust these if your local paths differ)\n    MODEL_DIR = \"/kaggle/input/notebooks/paritoshtripathi5/akkadian-final-byt5-base-ext-data/byt5-akkadian-model/\"\n    TEST_DATA_PATH = \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\"\n    OUTPUT_CSV = \"submission_byt5_34_4.csv\"\n\n    # Inference parameters\n    MAX_LENGTH = 512\n    BATCH_SIZE = 8\n    NUM_BEAMS = 4\n    NUM_RETURN_SEQUENCES = 4\n    PREFIX = \"translate Akkadian to English: \"\n\nclass AkkadianTestDataset(Dataset):\n    def __init__(self, df, tokenizer, max_length):\n        self.texts = [InferenceConfig.PREFIX + str(t) for t in df['transliteration'].tolist()]\n        self.tokenizer = tokenizer\n        self.max_length = max_length\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=self.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\ndef run_inference():\n    print(f\"Loading test data from {InferenceConfig.TEST_DATA_PATH}...\")\n    test_df = pd.read_csv(InferenceConfig.TEST_DATA_PATH)\n\n    print(f\"Loading model and tokenizer from {InferenceConfig.MODEL_DIR}...\")\n    tokenizer = AutoTokenizer.from_pretrained(InferenceConfig.MODEL_DIR, local_files_only=True)\n    model = AutoModelForSeq2SeqLM.from_pretrained(InferenceConfig.MODEL_DIR, local_files_only=True)\n\n    # Setup Device\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Using device: {device}\")\n    model = model.to(device)\n    model.eval()\n    model.float()  # Force to FP32 to avoid ByT5 precision issues\n\n    # Setup DataLoader\n    test_dataset = AkkadianTestDataset(test_df, tokenizer, InferenceConfig.MAX_LENGTH)\n    test_loader = DataLoader(test_dataset, batch_size=InferenceConfig.BATCH_SIZE, shuffle=False)\n\n    all_predictions = []\n\n    print(\"Starting generation...\")\n    with torch.inference_mode():\n        for batch in tqdm(test_loader, desc=\"Translating\"):\n            input_ids = batch[\"input_ids\"].to(device)\n            attention_mask = batch[\"attention_mask\"].to(device)\n\n            # Generate translations with all 4 candidates\n            outputs = model.generate(\n                input_ids=input_ids,\n                attention_mask=attention_mask,\n                max_length=InferenceConfig.MAX_LENGTH,\n                num_beams=InferenceConfig.NUM_BEAMS,\n                num_return_sequences=InferenceConfig.NUM_RETURN_SEQUENCES,\n                early_stopping=True,\n            )\n\n            decoded = tokenizer.batch_decode(outputs, skip_special_tokens=True)\n\n            # Group every 4 candidates together (one group per input)\n            for j in range(0, len(decoded), InferenceConfig.NUM_RETURN_SEQUENCES):\n                candidates = decoded[j : j + InferenceConfig.NUM_RETURN_SEQUENCES]\n                all_predictions.append({\n                    \"candidate_1\": candidates[0].strip() or \"broken text\",\n                    \"candidate_2\": candidates[1].strip() or \"broken text\",\n                    \"candidate_3\": candidates[2].strip() or \"broken text\",\n                    \"candidate_4\": candidates[3].strip() or \"broken text\",\n                })\n\n    # Prepare submission dataframe\n    submission = pd.DataFrame(all_predictions)\n    submission.insert(0, \"id\", test_df[\"id\"])\n    submission.insert(1, \"transliteration\", test_df[\"transliteration\"])\n\n    # Save offline CSV\n    submission.to_csv(InferenceConfig.OUTPUT_CSV, index=False)\n    print(f\"Inference complete! Saved to {InferenceConfig.OUTPUT_CSV}\")\n    print(\"Preview of submission:\")\n    print(submission.head())\n\nif __name__ == \"__main__\":\n    run_inference()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-23T14:28:10.068918Z","iopub.execute_input":"2026-03-23T14:28:10.069216Z","iopub.status.idle":"2026-03-23T14:28:10.08985Z","shell.execute_reply.started":"2026-03-23T14:28:10.069188Z","shell.execute_reply":"2026-03-23T14:28:10.089117Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile byt5_ext_vocab.py\nimport os\nimport pandas as pd\nimport torch\nfrom torch.utils.data import DataLoader, Dataset\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm import tqdm\n\nclass InferenceConfig:\n    \"\"\"Configuration for ByT5 Inference\"\"\"\n    # Paths (Adjust these if your local paths differ)\n    MODEL_DIR = \"/kaggle/input/notebooks/paritoshtripathi5/akkadian-final-byt5-base-ext-data-vocab/byt5-akkadian-model/\"\n    TEST_DATA_PATH = \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\"\n    OUTPUT_CSV = \"submission_byt5_ext_vocab.csv\"\n\n    # Inference parameters\n    MAX_LENGTH = 512\n    BATCH_SIZE = 8\n    NUM_BEAMS = 4\n    NUM_RETURN_SEQUENCES = 4\n    PREFIX = \"translate Akkadian to English: \"\n\nclass AkkadianTestDataset(Dataset):\n    def __init__(self, df, tokenizer, max_length):\n        self.texts = [InferenceConfig.PREFIX + str(t) for t in df['transliteration'].tolist()]\n        self.tokenizer = tokenizer\n        self.max_length = max_length\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=self.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\ndef run_inference():\n    print(f\"Loading test data from {InferenceConfig.TEST_DATA_PATH}...\")\n    test_df = pd.read_csv(InferenceConfig.TEST_DATA_PATH)\n\n    print(f\"Loading model and tokenizer from {InferenceConfig.MODEL_DIR}...\")\n    tokenizer = AutoTokenizer.from_pretrained(InferenceConfig.MODEL_DIR, local_files_only=True)\n    model = AutoModelForSeq2SeqLM.from_pretrained(InferenceConfig.MODEL_DIR, local_files_only=True)\n\n    # Setup Device\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Using device: {device}\")\n    model = model.to(device)\n    model.eval()\n    model.float()  # Force to FP32 to avoid ByT5 precision issues\n\n    # Setup DataLoader\n    test_dataset = AkkadianTestDataset(test_df, tokenizer, InferenceConfig.MAX_LENGTH)\n    test_loader = DataLoader(test_dataset, batch_size=InferenceConfig.BATCH_SIZE, shuffle=False)\n\n    all_predictions = []\n\n    print(\"Starting generation...\")\n    with torch.inference_mode():\n        for batch in tqdm(test_loader, desc=\"Translating\"):\n            input_ids = batch[\"input_ids\"].to(device)\n            attention_mask = batch[\"attention_mask\"].to(device)\n\n            # Generate translations with all 4 candidates\n            outputs = model.generate(\n                input_ids=input_ids,\n                attention_mask=attention_mask,\n                max_length=InferenceConfig.MAX_LENGTH,\n                num_beams=InferenceConfig.NUM_BEAMS,\n                num_return_sequences=InferenceConfig.NUM_RETURN_SEQUENCES,\n                early_stopping=True,\n            )\n\n            decoded = tokenizer.batch_decode(outputs, skip_special_tokens=True)\n\n            # Group every 4 candidates together (one group per input)\n            for j in range(0, len(decoded), InferenceConfig.NUM_RETURN_SEQUENCES):\n                candidates = decoded[j : j + InferenceConfig.NUM_RETURN_SEQUENCES]\n                all_predictions.append({\n                    \"candidate_1\": candidates[0].strip() or \"broken text\",\n                    \"candidate_2\": candidates[1].strip() or \"broken text\",\n                    \"candidate_3\": candidates[2].strip() or \"broken text\",\n                    \"candidate_4\": candidates[3].strip() or \"broken text\",\n                })\n\n    # Prepare submission dataframe\n    submission = pd.DataFrame(all_predictions)\n    submission.insert(0, \"id\", test_df[\"id\"])\n    submission.insert(1, \"transliteration\", test_df[\"transliteration\"])\n\n    # Save offline CSV\n    submission.to_csv(InferenceConfig.OUTPUT_CSV, index=False)\n    print(f\"Inference complete! Saved to {InferenceConfig.OUTPUT_CSV}\")\n    print(\"Preview of submission:\")\n    print(submission.head())\n\nif __name__ == \"__main__\":\n    run_inference()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-23T14:28:10.090735Z","iopub.execute_input":"2026-03-23T14:28:10.091283Z","iopub.status.idle":"2026-03-23T14:28:10.107481Z","shell.execute_reply.started":"2026-03-23T14:28:10.091254Z","shell.execute_reply":"2026-03-23T14:28:10.106693Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile byt5_matia_orig.py\nimport os\nimport pandas as pd\nimport torch\nfrom torch.utils.data import DataLoader, Dataset\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm import tqdm\n\nclass InferenceConfig:\n    \"\"\"Configuration for ByT5 Inference\"\"\"\n    # Paths (Adjust these if your local paths differ)\n    MODEL_DIR = \"/kaggle/input/models/mattiaangeli/byt5-akkadian-mbr-v2/pytorch/default/1/\"\n    TEST_DATA_PATH = \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\"\n    OUTPUT_CSV = \"submission_byt5_matia_orig.csv\"\n\n    # Inference parameters\n    MAX_LENGTH = 512\n    BATCH_SIZE = 8\n    NUM_BEAMS = 4\n    NUM_RETURN_SEQUENCES = 4\n    PREFIX = \"translate Akkadian to English: \"\n\nclass AkkadianTestDataset(Dataset):\n    def __init__(self, df, tokenizer, max_length):\n        self.texts = [InferenceConfig.PREFIX + str(t) for t in df['transliteration'].tolist()]\n        self.tokenizer = tokenizer\n        self.max_length = max_length\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=self.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\ndef run_inference():\n    print(f\"Loading test data from {InferenceConfig.TEST_DATA_PATH}...\")\n    test_df = pd.read_csv(InferenceConfig.TEST_DATA_PATH)\n\n    print(f\"Loading model and tokenizer from {InferenceConfig.MODEL_DIR}...\")\n    tokenizer = AutoTokenizer.from_pretrained(InferenceConfig.MODEL_DIR, local_files_only=True)\n    model = AutoModelForSeq2SeqLM.from_pretrained(InferenceConfig.MODEL_DIR, local_files_only=True)\n\n    # Setup Device\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Using device: {device}\")\n    model = model.to(device)\n    model.eval()\n    model.float()  # Force to FP32 to avoid ByT5 precision issues\n\n    # Setup DataLoader\n    test_dataset = AkkadianTestDataset(test_df, tokenizer, InferenceConfig.MAX_LENGTH)\n    test_loader = DataLoader(test_dataset, batch_size=InferenceConfig.BATCH_SIZE, shuffle=False)\n\n    all_predictions = []\n\n    print(\"Starting generation...\")\n    with torch.inference_mode():\n        for batch in tqdm(test_loader, desc=\"Translating\"):\n            input_ids = batch[\"input_ids\"].to(device)\n            attention_mask = batch[\"attention_mask\"].to(device)\n\n            # Generate translations with all 4 candidates\n            outputs = model.generate(\n                input_ids=input_ids,\n                attention_mask=attention_mask,\n                max_length=InferenceConfig.MAX_LENGTH,\n                num_beams=InferenceConfig.NUM_BEAMS,\n                num_return_sequences=InferenceConfig.NUM_RETURN_SEQUENCES,\n                early_stopping=True,\n            )\n\n            decoded = tokenizer.batch_decode(outputs, skip_special_tokens=True)\n\n            # Group every 4 candidates together (one group per input)\n            for j in range(0, len(decoded), InferenceConfig.NUM_RETURN_SEQUENCES):\n                candidates = decoded[j : j + InferenceConfig.NUM_RETURN_SEQUENCES]\n                all_predictions.append({\n                    \"candidate_1\": candidates[0].strip() or \"broken text\",\n                    \"candidate_2\": candidates[1].strip() or \"broken text\",\n                    \"candidate_3\": candidates[2].strip() or \"broken text\",\n                    \"candidate_4\": candidates[3].strip() or \"broken text\",\n                })\n\n    # Prepare submission dataframe\n    submission = pd.DataFrame(all_predictions)\n    submission.insert(0, \"id\", test_df[\"id\"])\n    submission.insert(1, \"transliteration\", test_df[\"transliteration\"])\n\n    # Save offline CSV\n    submission.to_csv(InferenceConfig.OUTPUT_CSV, index=False)\n    print(f\"Inference complete! Saved to {InferenceConfig.OUTPUT_CSV}\")\n    print(\"Preview of submission:\")\n    print(submission.head())\n\nif __name__ == \"__main__\":\n    run_inference()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile byt5_matia_base.py\nimport os\nimport pandas as pd\nimport torch\nfrom torch.utils.data import DataLoader, Dataset\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm import tqdm\n\nclass InferenceConfig:\n    \"\"\"Configuration for ByT5 Inference\"\"\"\n    # Paths (Adjust these if your local paths differ)\n    MODEL_DIR = \"/kaggle/input/notebooks/paritoshtripathi5/akkadian-ext-data-v5-matia-base/byt5-akkadian-linelevel/\"\n    TEST_DATA_PATH = \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\"\n    OUTPUT_CSV = \"submission_byt5_matia_base.csv\"\n\n    # Inference parameters\n    MAX_LENGTH = 512\n    BATCH_SIZE = 8\n    NUM_BEAMS = 4\n    NUM_RETURN_SEQUENCES = 4\n    PREFIX = \"translate Akkadian to English: \"\n\nclass AkkadianTestDataset(Dataset):\n    def __init__(self, df, tokenizer, max_length):\n        self.texts = [InferenceConfig.PREFIX + str(t) for t in df['transliteration'].tolist()]\n        self.tokenizer = tokenizer\n        self.max_length = max_length\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=self.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\ndef run_inference():\n    print(f\"Loading test data from {InferenceConfig.TEST_DATA_PATH}...\")\n    test_df = pd.read_csv(InferenceConfig.TEST_DATA_PATH)\n\n    print(f\"Loading model and tokenizer from {InferenceConfig.MODEL_DIR}...\")\n    tokenizer = AutoTokenizer.from_pretrained(InferenceConfig.MODEL_DIR, local_files_only=True)\n    model = AutoModelForSeq2SeqLM.from_pretrained(InferenceConfig.MODEL_DIR, local_files_only=True)\n\n    # Setup Device\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Using device: {device}\")\n    model = model.to(device)\n    model.eval()\n    model.float()  # Force to FP32 to avoid ByT5 precision issues\n\n    # Setup DataLoader\n    test_dataset = AkkadianTestDataset(test_df, tokenizer, InferenceConfig.MAX_LENGTH)\n    test_loader = DataLoader(test_dataset, batch_size=InferenceConfig.BATCH_SIZE, shuffle=False)\n\n    all_predictions = []\n\n    print(\"Starting generation...\")\n    with torch.inference_mode():\n        for batch in tqdm(test_loader, desc=\"Translating\"):\n            input_ids = batch[\"input_ids\"].to(device)\n            attention_mask = batch[\"attention_mask\"].to(device)\n\n            # Generate translations with all 4 candidates\n            outputs = model.generate(\n                input_ids=input_ids,\n                attention_mask=attention_mask,\n                max_length=InferenceConfig.MAX_LENGTH,\n                num_beams=InferenceConfig.NUM_BEAMS,\n                num_return_sequences=InferenceConfig.NUM_RETURN_SEQUENCES,\n                early_stopping=True,\n            )\n\n            decoded = tokenizer.batch_decode(outputs, skip_special_tokens=True)\n\n            # Group every 4 candidates together (one group per input)\n            for j in range(0, len(decoded), InferenceConfig.NUM_RETURN_SEQUENCES):\n                candidates = decoded[j : j + InferenceConfig.NUM_RETURN_SEQUENCES]\n                all_predictions.append({\n                    \"candidate_1\": candidates[0].strip() or \"broken text\",\n                    \"candidate_2\": candidates[1].strip() or \"broken text\",\n                    \"candidate_3\": candidates[2].strip() or \"broken text\",\n                    \"candidate_4\": candidates[3].strip() or \"broken text\",\n                })\n\n    # Prepare submission dataframe\n    submission = pd.DataFrame(all_predictions)\n    submission.insert(0, \"id\", test_df[\"id\"])\n    submission.insert(1, \"transliteration\", test_df[\"transliteration\"])\n\n    # Save offline CSV\n    submission.to_csv(InferenceConfig.OUTPUT_CSV, index=False)\n    print(f\"Inference complete! Saved to {InferenceConfig.OUTPUT_CSV}\")\n    print(\"Preview of submission:\")\n    print(submission.head())\n\nif __name__ == \"__main__\":\n    run_inference()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%bash\n# Pair 1 — run in parallel\nCUDA_VISIBLE_DEVICES=0 python helsinki_final.py &\nCUDA_VISIBLE_DEVICES=1 python helsinki_5_ep.py &\nwait\n\n# Pair 2 — run in parallel after Pair 1 finishes\nCUDA_VISIBLE_DEVICES=0 python helsinki_all_ext.py &\nCUDA_VISIBLE_DEVICES=1 python helsinki_mt_mul.py &\nwait","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-23T14:28:10.109381Z","iopub.execute_input":"2026-03-23T14:28:10.109616Z","iopub.status.idle":"2026-03-23T14:28:39.55656Z","shell.execute_reply.started":"2026-03-23T14:28:10.109597Z","shell.execute_reply":"2026-03-23T14:28:39.555701Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%bash\nCUDA_VISIBLE_DEVICES=0 python byt5_matia_base.py &\nCUDA_VISIBLE_DEVICES=1 python byt5_matia_orig.py &\nwait","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%bash\nCUDA_VISIBLE_DEVICES=0 python byt5_34_4.py &\nCUDA_VISIBLE_DEVICES=1 python byt5_ext_vocab.py &\nwait","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-23T14:28:39.557725Z","iopub.execute_input":"2026-03-23T14:28:39.55832Z","iopub.status.idle":"2026-03-23T14:28:57.563977Z","shell.execute_reply.started":"2026-03-23T14:28:39.558287Z","shell.execute_reply":"2026-03-23T14:28:57.563209Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nWeighted MBR (Minimum Bayes Risk) Decoding — Optimized\n=======================================================\nEnsembles 6 models × 4 beam candidates = 24 candidates per row.\nPicks the candidate that maximises expected sentence-level\ngeometric mean of BLEU and chrF++.\n\nOptimizations for ~4000 rows:\n  - Deduplicates identical candidates per row\n  - Caches pairwise scores\n  - Multiprocessing across rows\n  - Early exit when all candidates agree\n  - Standardizes gap tokens prior to deduplication\n\nUsage:\n  python weighted_mbr.py\n\"\"\"\n\nimport math\nimport re\nimport pandas as pd\nimport sacrebleu\nimport numpy as np\nfrom multiprocessing import Pool, cpu_count\nfrom tqdm import tqdm\n\n# ==========================================\n# Gap Standardization\n# ==========================================\n\n_GAP_RE = re.compile(\n    r\"<\\s*gap\\s*>\"\n    r\"|<\\s*big[\\s_\\-]*gap\\s*>\"\n    r\"|\\bbig[\\s_\\-]*gap\\b\"\n    r\"|…+\"\n    r\"|\\.{3,}\"\n    r\"|\\[\\.+\\]\"\n    r\"|\\[\\s*x\\s*\\]|\\(\\s*x\\s*\\)\"\n    r\"|(?<!\\w)x(?!\\w)\"\n    r\"|\\(\\s*break\\s*\\)\"\n    r\"|\\(\\s*large\\s+break\\s*\\)\"\n    r\"|\\(\\s*\\d+\\s+broken\\s+lines?\\s*\\)\",\n    re.I\n)\n_MULTI_GAP_RE = re.compile(r\"(?:<gap>\\s*){2,}\")\n_WS_RE = re.compile(r\"\\s+\")\n\ndef standardize_gaps(text):\n    \"\"\"Normalizes various gap/missing text representations into a single <gap> token.\"\"\"\n    text = _GAP_RE.sub(\" <gap> \", str(text))\n    text = _MULTI_GAP_RE.sub(\"<gap>\", text)\n    return _WS_RE.sub(\" \", text).strip()\n\n\n# ==========================================\n# Configuration\n# ==========================================\n\nMODEL_FILES = {\n    \"submission_helsinki_final.csv\":    25.0,\n    \"submission_helsinki_all_ext.csv\":  27.0,\n    \"submission_helsinki_mt_mul.csv\":   25.0,\n    \"submission_helsinki_5_ep.csv\":     25.0,\n    \"submission_byt5_34_4.csv\":         35.0,\n    \"submission_byt5_ext_vocab.csv\":    27.0,\n    \"submission_byt5_matia_orig.csv\":   32.0,\n    \"submission_byt5_matia_base.csv\":   27.0\n}\n\nOUTPUT_FILE = \"submission.csv\"\nNUM_WORKERS = max(1, cpu_count() - 1)  # Leave 1 core free\n\n\n# ==========================================\n# Core scoring\n# ==========================================\n\ndef sentence_score(hypothesis: str, reference: str) -> float:\n    \"\"\"Sentence-level geometric mean of BLEU and chrF++.\"\"\"\n    bleu = sacrebleu.sentence_bleu(hypothesis, [reference]).score\n    chrf = sacrebleu.sentence_chrf(hypothesis, [reference], word_order=2).score\n    if bleu <= 0 or chrf <= 0:\n        return 0.0\n    return math.sqrt(bleu * chrf)\n\n\ndef mbr_select_single_row(row_data: dict) -> str:\n    \"\"\"\n    Process one row: deduplicate candidates, compute pairwise scores,\n    return the best candidate by weighted MBR.\n    \"\"\"\n    candidates = row_data[\"candidates\"]\n    weights = row_data[\"weights\"]\n\n    if not candidates:\n        return \"broken text\"\n\n    # --- Deduplication: merge weights for identical candidates ---\n    unique_map = {}\n    for cand, w in zip(candidates, weights):\n        unique_map[cand] = unique_map.get(cand, 0.0) + w\n\n    unique_cands = list(unique_map.keys())\n    unique_weights = np.array([unique_map[c] for c in unique_cands])\n\n    # Early exit: all candidates are the same\n    if len(unique_cands) == 1:\n        return unique_cands[0]\n\n    n = len(unique_cands)\n\n    # --- Pairwise scoring (BLEU is asymmetric, compute both directions) ---\n    scores = np.zeros((n, n))\n    for i in range(n):\n        for j in range(n):\n            if i != j:\n                scores[i][j] = sentence_score(unique_cands[i], unique_cands[j])\n\n    # --- Weighted expected utility ---\n    utilities = np.zeros(n)\n    for i in range(n):\n        ref_weights = unique_weights.copy()\n        ref_weights[i] = 0.0  # Don't score against yourself\n        denom = np.sum(ref_weights)\n        if denom > 0:\n            utilities[i] = np.dot(scores[i], ref_weights) / denom\n\n    best_idx = np.argmax(utilities)\n    return unique_cands[best_idx]\n\n\n# Top-level wrapper for multiprocessing\ndef _process_row(row_data):\n    return mbr_select_single_row(row_data)\n\n\ndef main():\n    # Load all model files\n    print(\"Loading submission files...\")\n    model_dfs = {}\n    for fname, weight in MODEL_FILES.items():\n        df = pd.read_csv(fname)\n        print(f\"  {fname}: {len(df)} rows, weight={weight}\")\n        model_dfs[fname] = df\n\n    # Verify all files have the same IDs\n    ref_ids = list(model_dfs.values())[0][\"id\"].tolist()\n    for fname, df in model_dfs.items():\n        assert df[\"id\"].tolist() == ref_ids, f\"ID mismatch in {fname}!\"\n\n    num_rows = len(ref_ids)\n    print(f\"\\nTotal rows: {num_rows}\")\n    print(f\"Models: {len(MODEL_FILES)}, Beams per model: 4\")\n    print(f\"Max candidates per row: {len(MODEL_FILES) * 4}\")\n    print(f\"Workers: {NUM_WORKERS}\")\n\n    # Build row data for parallel processing\n    print(\"\\nPreparing candidate pools...\")\n    all_row_data = []\n    total_orig = 0\n    total_uniq = 0\n\n    for row_idx in range(num_rows):\n        candidates = []\n        weights = []\n\n        for fname, weight in MODEL_FILES.items():\n            df = model_dfs[fname]\n            for c in [\"candidate_1\", \"candidate_2\", \"candidate_3\", \"candidate_4\"]:\n                text = str(df.iloc[row_idx][c]).strip()\n                if text and text != \"broken text\":\n                    # Standardize gaps BEFORE deduplication to improve overlap\n                    text = standardize_gaps(text)\n                    candidates.append(text)\n                    weights.append(weight)\n\n        total_orig += len(candidates)\n        total_uniq += len(set(candidates)) if candidates else 0\n\n        all_row_data.append({\n            \"candidates\": candidates,\n            \"weights\": weights,\n        })\n\n    avg_orig = total_orig / num_rows if num_rows > 0 else 0\n    avg_uniq = total_uniq / num_rows if num_rows > 0 else 0\n    print(f\"Avg candidates per row: {avg_orig:.1f} total, {avg_uniq:.1f} unique\")\n    print(f\"Dedup saves ~{(1 - avg_uniq / max(avg_orig, 1)) * 100:.0f}% of pairwise comparisons\")\n\n    # Estimate runtime\n    avg_pairs = avg_uniq * (avg_uniq - 1)\n    total_pairs = int(avg_pairs * num_rows)\n    print(f\"Estimated pairwise comparisons: ~{total_pairs:,}\")\n\n    # Run MBR in parallel\n    print(f\"\\nRunning weighted MBR decoding with {NUM_WORKERS} workers...\")\n    with Pool(NUM_WORKERS) as pool:\n        best_translations = list(\n            tqdm(\n                pool.imap(_process_row, all_row_data, chunksize=32),\n                total=num_rows,\n                desc=\"MBR\",\n            )\n        )\n\n    # Create submission\n    submission = pd.DataFrame({\n        \"id\": ref_ids,\n        \"translation\": best_translations,\n    })\n\n    submission[\"translation\"] = submission[\"translation\"].apply(\n        lambda x: x if isinstance(x, str) and len(x.strip()) > 0 else \"broken text\"\n    )\n\n    submission.to_csv(OUTPUT_FILE, index=False)\n    print(f\"\\nSaved {OUTPUT_FILE} ({len(submission)} rows)\")\n    print(submission.head(10))\n\n    # Stats\n    print(f\"\\nUnique translations: {submission['translation'].nunique()}\")\n    print(f\"'broken text' count: {(submission['translation'] == 'broken text').sum()}\")\n\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-23T14:42:12.650969Z","iopub.execute_input":"2026-03-23T14:42:12.651753Z","iopub.status.idle":"2026-03-23T14:42:14.129907Z","shell.execute_reply.started":"2026-03-23T14:42:12.651717Z","shell.execute_reply":"2026-03-23T14:42:14.129129Z"}},"outputs":[],"execution_count":null}]}