{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Goal: end to end inference and evaluation\n",
    "\n",
    "given a csv, make predictions and evaluate predictions, then return results in a csv"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The test_dataset contains 12938 items.\n",
      "Making inference on test set\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "12938it [02:37, 82.28it/s]                    "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Inference done.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import os\n",
    "import json\n",
    "\n",
    "with open(\"mode.json\", \"r\") as json_file:\n",
    "    mode_dict = json.load(json_file)\n",
    "\n",
    "mode = mode_dict.get(\"mode\", \"none\")\n",
    "model_name = mode_dict.get(\"model\", \"none\")\n",
    "train_epochs = mode_dict.get(\"train_epochs\", \"none\")\n",
    "fold_group = mode_dict.get(\"fold_group\", \"none\")\n",
    "\n",
    "base_dir = f\"train_{fold_group}_{model_name}_{mode}_{train_epochs}\"\n",
    "checkpoints = [d for d in os.listdir(base_dir) if d.startswith(\"checkpoint-\")]\n",
    "\n",
    "model_checkpoint = os.path.join(base_dir, checkpoints[0]) if checkpoints else None\n",
    "\n",
    "data_path = f\"../../data_preprocess/dataset/{fold_group}/test.csv\"\n",
    "\n",
    "try:\n",
    "    df = pd.read_csv(data_path)\n",
    "except UnicodeDecodeError:\n",
    "    df = pd.read_csv(data_path, encoding='ISO-8859-1')\n",
    "\n",
    "df = df.dropna(subset=['tag_description']).reset_index(drop=True)\n",
    "\n",
    "df_org = df.copy()\n",
    "df[['thing', 'property', 'tag_description', 'min', 'max', 'MDM', 'pattern']] = df[['thing', 'property', 'tag_description', 'min', 'max', 'MDM', 'pattern']].astype(\"string\")\n",
    "\n",
    "from datasets import Dataset\n",
    "\n",
    "def process_df(df, mode='only_td'):\n",
    "    output_list = []\n",
    "    for _, row in df.iterrows():\n",
    "        try:\n",
    "            if mode == 'only_td':\n",
    "                input_str = f\"<TD_START>{str(row['tag_description'])}<TD_END>\"\n",
    "            elif mode == 'tn_td':\n",
    "                input_str = f\"<TN_START>{str(row['tag_name'])}<TN_END><TD_START>{str(row['tag_description'])}<TD_END>\"\n",
    "            elif mode == 'tn_td_min_max':\n",
    "                input_str = f\"<TN_START>{str(row['tag_name'])}<TN_END><TD_START>{str(row['tag_description'])}<TD_END><MIN_START>{row['min']}<MIN_END><MAX_START>{row['max']}<MAX_END>\"\n",
    "            elif mode == 'td_min_max':\n",
    "                input_str = f\"<TD_START>{str(row['tag_description'])}<TD_END><MIN_START>{row['min']}<MIN_END><MAX_START>{row['max']}<MAX_END>\"\n",
    "            elif mode == 'td_unit':\n",
    "                input_str = f\"<TD_START>{str(row['tag_description'])}<TD_END><UNIT_START>{str(row['unit'])}<UNIT_END>\"\n",
    "            elif mode == 'tn_td_unit':\n",
    "                input_str = f\"<TN_START>{str(row['tag_name'])}<TN_END><TD_START>{str(row['tag_description'])}<TD_END><UNIT_START>{str(row['unit'])}<UNIT_END>\"\n",
    "            elif mode == 'td_min_max_unit':\n",
    "                input_str = f\"<TD_START>{str(row['tag_description'])}<TD_END><MIN_START>{row['min']}<MIN_END><MAX_START>{row['max']}<MAX_END><UNIT_START>{str(row['unit'])}<UNIT_END>\"\n",
    "            else:\n",
    "                raise ValueError(\"Invalid mode specified\")\n",
    "\n",
    "            output_list.append({\n",
    "                'translation': {\n",
    "                    'ships_idx': row['ships_idx'],\n",
    "                    'input': input_str,\n",
    "                    'thing_property': f\"<THING_START>{row['thing']}<THING_END><PROPERTY_START>{row['property']}<PROPERTY_END>\",\n",
    "                    'answer_thing': row['thing'],\n",
    "                    'answer_property': row['property'],\n",
    "                    'MDM': row['MDM'],\n",
    "                }\n",
    "            })\n",
    "        except Exception as e:\n",
    "            print(f\"Error processing row: {e}\")\n",
    "    return output_list\n",
    "\n",
    "processed_data = process_df(df, mode=mode)\n",
    "test_dataset = Dataset.from_list(processed_data)\n",
    "print(f\"The test_dataset contains {len(test_dataset)} items.\")\n",
    "\n",
    "from transformers.pipelines.pt_utils import KeyDataset\n",
    "from transformers import pipeline, BartTokenizer\n",
    "from tqdm import tqdm\n",
    "\n",
    "# Use BartTokenizer for BART inference\n",
    "tokenizer = BartTokenizer.from_pretrained(model_name, return_tensors=\"pt\")\n",
    "additional_special_tokens = [\n",
    "    \"<THING_START>\", \"<THING_END>\", \"<PROPERTY_START>\", \"<PROPERTY_END>\", \n",
    "    \"<TN_START>\", \"<TN_END>\", \"<TD_START>\", \"<TD_END>\", \n",
    "    \"<MIN_START>\", \"<MIN_END>\", \"<MAX_START>\", \"<MAX_END>\", \n",
    "    \"<UNIT_START>\", \"<UNIT_END>\"\n",
    "]\n",
    "tokenizer.add_special_tokens({\"additional_special_tokens\": additional_special_tokens})\n",
    "\n",
    "# Use BART model for inference\n",
    "pipe = pipeline(\"text2text-generation\", model=model_checkpoint, tokenizer=tokenizer, return_tensors=True, max_length=128, device=0)\n",
    "\n",
    "# Check what token-ids the special tokens are\n",
    "thing_start_id = tokenizer.convert_tokens_to_ids(\"<THING_START>\")\n",
    "thing_end_id = tokenizer.convert_tokens_to_ids(\"<THING_END>\")\n",
    "property_start_id = tokenizer.convert_tokens_to_ids(\"<PROPERTY_START>\")\n",
    "property_end_id = tokenizer.convert_tokens_to_ids(\"<PROPERTY_END>\")\n",
    "\n",
    "def extract_seq(tokens, start_value, end_value):\n",
    "    if start_value in tokens and end_value in tokens:\n",
    "        return tokens[tokens.index(start_value)+1:tokens.index(end_value)]\n",
    "    return None\n",
    "\n",
    "def extract_seq_from_output(output):\n",
    "    tokens = output[0][\"generated_token_ids\"].tolist()\n",
    "    p_thing = tokenizer.decode(extract_seq(tokens, thing_start_id, thing_end_id)) if thing_start_id in tokens and thing_end_id in tokens else None\n",
    "    p_property = tokenizer.decode(extract_seq(tokens, property_start_id, property_end_id)) if property_start_id in tokens and property_end_id in tokens else None\n",
    "    return p_thing, p_property\n",
    "\n",
    "# Inference and storing predictions\n",
    "p_thing_list = []\n",
    "p_property_list = []\n",
    "print(\"Making inference on test set\")\n",
    "\n",
    "# Process the test set through the pipeline and generate predictions\n",
    "for out in tqdm(pipe(KeyDataset(test_dataset[\"translation\"], \"input\"), batch_size=256)):\n",
    "    p_thing, p_property = extract_seq_from_output(out)\n",
    "    p_thing_list.append(p_thing)\n",
    "    p_property_list.append(p_property)\n",
    "\n",
    "print(\"Inference done.\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Thing prediction accuracy: 0.9793861658268438\n",
      "Correct thing predictions: 2138, Incorrect thing predictions: 45\n",
      "Property prediction accuracy: 0.9752633989922126\n",
      "Correct property predictions: 2129, Incorrect property predictions: 10809\n",
      "total accuracy: 0.9601465872652314\n",
      "Correct total predictions: 2096, Incorrect total predictions: 87\n"
     ]
    }
   ],
   "source": [
    "answer_thing = [item['answer_thing'] for item in test_dataset[\"translation\"]]\n",
    "answer_property = [item['answer_property'] for item in test_dataset[\"translation\"]]\n",
    "mdm_list = [item['MDM'] for item in test_dataset[\"translation\"]]\n",
    "\n",
    "mdm_count = 0\n",
    "for i in range(len(mdm_list)):\n",
    "    if(mdm_list[i] == \"True\"):mdm_count = mdm_count + 1    \n",
    "\n",
    "def correctness_test(input, reference, mdm_list):\n",
    "    assert(len(input) == len(reference))\n",
    "    correctness_list = []\n",
    "    for i in range(len(input)):\n",
    "        if(mdm_list[i] == \"True\"):\n",
    "            correctness_list.append(input[i] == reference[i])\n",
    "        else:correctness_list.append(False)\n",
    "    return correctness_list\n",
    "\n",
    "# Compare with answer to evaluate correctness\n",
    "thing_correctness = correctness_test(p_thing_list, answer_thing, mdm_list)\n",
    "property_correctness = correctness_test(p_property_list, answer_property, mdm_list)\n",
    "\n",
    "correctness_mdm = []\n",
    "for i in range(len(mdm_list)):\n",
    "    if(thing_correctness[i] & property_correctness[i]):\n",
    "        correctness_mdm.append(True)\n",
    "    else:            \n",
    "        correctness_mdm.append(False)\n",
    "        \n",
    "    \n",
    "# Calculate accuracy\n",
    "thing_accuracy = sum(thing_correctness) / mdm_count\n",
    "property_accuracy = sum(property_correctness) / mdm_count\n",
    "total_accuracy = sum(correctness_mdm) / mdm_count\n",
    "\n",
    "# Count True/False values\n",
    "thing_true_count = thing_correctness.count(True)\n",
    "thing_false_count = 0\n",
    "for i in range(len(thing_correctness)):\n",
    "    if mdm_list[i] == \"True\" and thing_correctness[i] == False:\n",
    "        thing_false_count += 1\n",
    "\n",
    "property_true_count = property_correctness.count(True)\n",
    "property_false_count = property_correctness.count(False)\n",
    "total_true_count = correctness_mdm.count(True)\n",
    "total_false_count = mdm_count - correctness_mdm.count(True)\n",
    "\n",
    "# Print results\n",
    "print(\"Thing prediction accuracy:\", thing_accuracy)\n",
    "print(f\"Correct thing predictions: {thing_true_count}, Incorrect thing predictions: {thing_false_count}\")\n",
    "print(\"Property prediction accuracy:\", property_accuracy)\n",
    "print(f\"Correct property predictions: {property_true_count}, Incorrect property predictions: {property_false_count}\")\n",
    "print(\"total accuracy:\", total_accuracy)\n",
    "print(f\"Correct total predictions: {total_true_count}, Incorrect total predictions: {total_false_count}\")\n",
    "\n",
    "# Create a DataFrame with the results\n",
    "dict = {\n",
    "    'p_thing': p_thing_list,\n",
    "    'p_property': p_property_list,\n",
    "    'p_thing_correct': thing_correctness,\n",
    "    'p_property_correct': property_correctness\n",
    "}\n",
    "\n",
    "df_pred = pd.DataFrame(dict)\n",
    "\n",
    "# Read the mode from the JSON file\n",
    "with open(\"mode.json\", \"r\") as json_file:\n",
    "    mode_dict = json.load(json_file)\n",
    "\n",
    "# Add the model key to the dictionary\n",
    "mode_dict[\"model\"] = model_name\n",
    "mode_dict[\"train_epochs\"] = train_epochs\n",
    "\n",
    "# Save the updated dictionary back to the JSON file\n",
    "with open(\"mode.json\", \"w\") as json_file:\n",
    "    json.dump(mode_dict, json_file)\n",
    "\n",
    "\n",
    "# Check if the file exists and is not empty\n",
    "if os.path.exists(\"results.json\") and os.path.getsize(\"results.json\") > 0:\n",
    "    # Read the existing results.json file\n",
    "    with open(\"results.json\", \"r\") as json_file:\n",
    "        try:\n",
    "            results_dict = json.load(json_file)\n",
    "        except json.JSONDecodeError:\n",
    "            results_dict = {}\n",
    "else:\n",
    "    results_dict = {}\n",
    "\n",
    "# Add the new model_checkpoint key with the accuracy values as an object\n",
    "\n",
    "model_key = model_checkpoint \n",
    "\n",
    "results_dict[model_key] = {\n",
    "    \"thing_accuracy\": thing_accuracy,\n",
    "    \"thing_true\": thing_true_count,\n",
    "    \"thing_false\": thing_false_count,\n",
    "    \"property_accuracy\": property_accuracy,\n",
    "    \"property_true\": property_true_count,\n",
    "    \"property_false\": property_false_count,\n",
    "    \"total_accuracy\": total_accuracy,\n",
    "    \"total_true\": total_true_count,\n",
    "    \"total_false\": total_false_count    \n",
    "}\n",
    "\n",
    "# Save the updated dictionary back to the results.json file\n",
    "with open(\"results.json\", \"w\") as json_file:\n",
    "    json.dump(results_dict, json_file, indent=4)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
