{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<div align='center'><font size=\"5\" color='#353B47'>Chest-X-ray</font></div>\n<div align='center'><font size=\"4\" color=\"#353B47\">How to deal with models averaging ?</font></div>\n<br>\n<hr>","metadata":{}},{"cell_type":"markdown","source":"The objective of this notebook is to aproach different methods for Ensembling\n\n* OR method (Affirmative): A box is considered if it’s generated by at least one of the models.\n* AND method (Unanimous): A box is considered if all of the models generate the same box (the box is considered the same if IOU > 0.5).\n* Consensus method: A box is considered if the majority of the models generate the same box (ie) if there are m models and (m/2 +1) models generate the same box, that box is considered as valid.\n* Weighted Fusion: This is a novel method which was created to replace NMS and it’s shortcomings.","metadata":{}},{"cell_type":"markdown","source":"# <div id=\"summary\">Summary</div>\n\n**<font size=\"2\"><a href=\"#chap1\">1. Load libraries and dataframes with predictions</a></font>**\n**<br><font size=\"2\"><a href=\"#chap2\">2. Helper functions</a></font>**\n**<br><font size=\"2\"><a href=\"#chap3\">3. Run ensembling with appropriate strategy</a></font>**\n**<br><font size=\"2\"><a href=\"#chap4\">4. Save results</a></font>**","metadata":{}},{"cell_type":"markdown","source":"# <div id=\"chap1\">1. Load libraries and dataframes with predictions","metadata":{}},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings('ignore')\n\nimport numpy as np\nimport pandas as pd\nimport os\nfrom tqdm import tqdm","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Import outputs of each selected models\nyolo = pd.read_csv('../input/vinbigdatastack/yolov5.csv')\ndetectron = pd.read_csv('../input/vinbigdatastack/detectron2.csv')\nfasterrcnn = pd.read_csv('../input/vinbigdatastack/fasterrcnn.csv')\n\nimage_ids = yolo.image_id.values","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <div id=\"chap2\">2. Helper functions","metadata":{}},{"cell_type":"code","source":"def getitem(dataframe, img_id):\n    \n    \"\"\"\n    Parameters\n    ----------\n    dataframe : pd.DataFrame\n    img_id : str\n        \n    Returns\n    -------\n    Dictionary of radiographic observations\n    \"\"\"\n\n    pred = list(dataframe.loc[dataframe.image_id == img_id, \"PredictionString\"])[0].split(' ')\n    nb_elm = len(pred)//6\n    output = {}\n    \n    for elm in range(nb_elm):\n        output[f'elm_{elm}'] = pred[elm*6 : (elm+1)*6]\n        \n    return output\n\n\ndef sortDictByProba(dict_):\n    \n    \"\"\"\n    Parameters\n    ----------\n    dict_ : dict, Dictionary of radiographic observations\n        \n    Returns\n    -------\n    Dictionary of radiographic observations sorted by probabilities \n    \"\"\"\n    \n    for key in dict_.keys():\n        dict_[key] = list(map(lambda x: float(x), dict_[key]))\n    \n    # item[1][1] corresponds to the second element of the value (the confidence of the class identified)\n    return {k: v for k, v in sorted(dict_.items(), key=lambda item: item[1][1], reverse = True)}\n\n\ndef getHighestProba(*list_of_dicts, n=3):\n    \n    \"\"\"\n    Parameters\n    ----------\n    list_of_dicts : list[dict], List of dictionaries containing radiographic observations\n    n : int, keep n highest elements of each list_of_dicts at most\n    \n    Returns\n    -------\n    Dict of merged top3 confidence interval in each dict of list_of_dicts\n    \"\"\"\n    \n    output = {}\n    for index, dict_ in enumerate(list_of_dicts):\n        dict_length = len(dict_)\n        for i in range(dict_length):\n            if i < n:\n                output[f\"elm_{i}_dict_{index}\"] =list(dict_.values())[i]\n                \n    return output\n\n\ndef getUnique(dict_):\n    \n    \"\"\"\n    Parameters\n    ----------\n    dict_ : dict, Dictionary of radiographic observations\n        \n    Returns\n    -------\n    List of unique class_id, list of duplicates class_id\n    \"\"\"\n    \n    dict_length = len(dict_)\n    \n    classes_non_unique = [list(dict_.values())[index][0] for index in range(dict_length)]\n    classes_unique = list(set(classes_non_unique))\n    \n    uniques, counts = np.unique(classes_non_unique, return_counts=True)\n    duplicates = uniques[counts > 1]\n    singles = np.setdiff1d(classes_unique, duplicates)\n    \n    return singles, duplicates\n\n\ndef getKeysByValue(dictOfElements, valueToFind):\n    \n    \"\"\"\n    Parameters\n    ----------\n    dictOfElements : dict, Dictionary of radiographic observations\n    valueToFind : int, corresponds to class_id\n    \n    Returns\n    -------\n    List of keys of dictOfElements that contain valueToFind\n    \"\"\"\n    \n    output = list()\n    listOfItems = dictOfElements.items()\n    \n    for item  in listOfItems:\n        if item[1][0] == valueToFind:\n            output.append(item[0])\n            \n    return  output\n\n\ndef getListKeysByValue(dictOfElements, valuesToFind):\n    \n    \"\"\"\n    Parameters\n    ----------\n    dictOfElements : dict, Dictionary of radiographic observations\n    valuesToFind : list[int], list of class_id\n    \n    Returns\n    -------\n    List of lists of keys of dictOfElements for each value in valuesToFind\n    \"\"\"\n    \n    output = []\n    \n    for value in valuesToFind:\n        output.append(getKeysByValue(dictOfElements, value))\n        \n    return output\n\n\ndef averaging(from_dict, single_keys, dupl_keys):\n    \n    \"\"\"\n    Parameters\n    ----------\n    from_dict : dict, dictionary to be filtered\n    single_keys : list[str], list of keys that should be infered\n    dupl_keys : list[str], list of class_id\n    \n    Returns\n    -------\n    A filtered dictionary with averaged probs and boxes\n    \"\"\"\n    \n    output = {}\n    \n    # Infer single keys\n    if len(np.ravel(single_keys)) != 0:\n        for single in np.ravel(single_keys):\n            output[single] = from_dict[single]\n\n    # For each duplicates, get index of all occurences and average boxing\n    if len(np.ravel(dupl_keys)) != 0:\n        for index, list_of_duplicate_class in enumerate(dupl_keys):\n            probs = [] \n            boxing1 = []\n            boxing2 = []\n            boxing3 = []\n            boxing4 = []\n            \n            for elm in list_of_duplicate_class:\n                probs.append(from_dict[elm][1])\n                boxing1.append(from_dict[elm][2])\n                boxing2.append(from_dict[elm][3])\n                boxing3.append(from_dict[elm][4])\n                boxing4.append(from_dict[elm][5])\n            \n            output[f\"elm_{index}\"] = [from_dict[list_of_duplicate_class[0]][0],\n                                      np.mean(probs),\n                                      np.mean(boxing1),\n                                      np.mean(boxing2),\n                                      np.mean(boxing3),\n                                      np.mean(boxing4)]\n            \n    return output\n\n\ndef toString(pred_list):\n    \n    \"\"\"\n    Parameters\n    ----------\n    list_final : list[int], list of all radiographic observations\n    \n    Returns\n    -------\n    A string which fits with the expected output\n    \"\"\"\n    \n    castedList = []\n    for index, elm in enumerate(pred_list):\n        if index%6 == 0:\n            castedList.append(str(int(elm)))\n        else:\n            castedList.append(str(elm))\n            \n    output = \" \".join(castedList)\n    \n    return output","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"--------\n\n**<font size=\"2\"><a href=\"#summary\">Back to summary</a></font>**","metadata":{}},{"cell_type":"markdown","source":"# <div id=\"chap3\">3. Run ensembling with appropriate strategy","metadata":{"trusted":true}},{"cell_type":"markdown","source":"My strategy here consists in averaging observations that have at least one dupplicate among all models. Some filtering about boxing areas should be added. This will come in a future release","metadata":{}},{"cell_type":"code","source":"def main():\n    \n    output = pd.DataFrame(columns = [\"image_id\", \"PredictionString\"])\n    \n    for image_id in tqdm(image_ids):\n        \n        # For each model, get PredictionString of image_id as a dict\n        fasterrcnn_pred = getitem(fasterrcnn, image_id)\n        detectron_pred = getitem(detectron, image_id)\n        yolo_pred = getitem(yolo, image_id)  \n        \n        # Sort dicts by proba\n        sorted_fasterrcnn = sortDictByProba(fasterrcnn_pred)\n        sorted_detectron = sortDictByProba(detectron_pred)\n        sorted_yolo = sortDictByProba(yolo_pred)\n\n        # Filter dicts into one dict with at most top n probs\n        highest_probs = getHighestProba(sorted_fasterrcnn, \n                                        sorted_detectron, \n                                        sorted_yolo,\n                                        n = 3)\n        \n        # Get keys of unique and duplicates values in the filtered dict\n        singles, duplicates = getUnique(highest_probs)\n        single_keys = getListKeysByValue(highest_probs, singles)\n        dupl_keys = getListKeysByValue(highest_probs, duplicates)\n        \n        # Apply averaging strategy\n        stacked_dict = averaging(highest_probs, single_keys, dupl_keys)\n        \n        # Put string in right format\n        prediction_int = np.ravel(list(stacked_dict.values()))\n        prediction_string = toString(prediction_int)\n        \n        output = output.append({\"image_id\": image_id, \n                                \"PredictionString\": prediction_string},\n                               ignore_index=True)\n        \n    return output","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Some other strategies will be tested in a future release:\n* OR method \n* AND method\n* Consensus method\n* Weighted Fusion","metadata":{}},{"cell_type":"markdown","source":"In the meantime, if you found this notebook usefull and you do have some suggestions on how this could be better implemented, do not hesitate to contribute, i'd really appreciate !","metadata":{}},{"cell_type":"markdown","source":"--------\n\n**<font size=\"2\"><a href=\"#summary\">Back to summary</a></font>**","metadata":{}},{"cell_type":"markdown","source":"# <div id=\"chap4\">4. Save results","metadata":{}},{"cell_type":"code","source":"final_sub = main()\nfinal_sub.to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# References\n\n* <a href = \"https://medium.com/inspiredbrilliance/object-detection-through-ensemble-of-models-fed015bc1ee0\">Article on object detection through ensemble of models</a>\n* detectron2 : https://www.kaggle.com/c/vinbigdata-chest-xray-abnormalities-detection/code?competitionId=24800&sortBy=scoreDescending\n* fasterrcnn : https://www.kaggle.com/awsaf49/vinbigdata-cxr-ad-yolov5-14-class-infer\n* yolov5 : https://www.kaggle.com/basu369victor/chest-x-ray-abnormalities-detection-submission","metadata":{}},{"cell_type":"markdown","source":"<hr>\n<div align='justify'><font color=\"#353B47\" size=\"4\">Thank you for taking the time to read this notebook. I hope that I was able to answer your questions or your curiosity and that it was quite understandable. <u>any constructive comments are welcome</u>. They help me progress and motivate me to share better quality content. I am above all a passionate person who tries to advance my knowledge but also that of others. If you liked it, feel free to <u>upvote and share my work.</u> </font></div>\n<br>\n<div align='center'><font color=\"#353B47\" size=\"3\">Thank you and may passion guide you.</font></div>","metadata":{}}]}