{"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":"# Train YOLOX on COTS dataset (PART 1 - TRAINING)\n\nThis notebook shows how to train custom object detection model (COTS dataset) on Kaggle. It could be good starting point for build own custom model based on YOLOX detector. Full github repository you can find here - [YOLOX](https://github.com/Megvii-BaseDetection/YOLOX)\n\n<div align = 'center'><img src='https://github.com/Megvii-BaseDetection/YOLOX/raw/main/assets/logo.png'/></div>\n\n**Steps covered in this notebook:**\n* Install YOLOX \n* Prepare COTS dataset for YOLOX object detection training\n* Download Pre-Trained Weights for YOLOX\n* Prepare configuration files\n* YOLOX training\n* Run YOLOX inference on test images\n* Export YOLOX weights for Tensorflow inference (soon)\n\nNow I created notebook for learning and prototyping in YOLOX. Next step is too create better model (play with YOLOX experimentation parameters).","metadata":{"execution":{"iopub.status.busy":"2021-11-29T13:34:31.033138Z","iopub.execute_input":"2021-11-29T13:34:31.033449Z","iopub.status.idle":"2021-11-29T13:34:33.455468Z","shell.execute_reply.started":"2021-11-29T13:34:31.033368Z","shell.execute_reply":"2021-11-29T13:34:33.454141Z"}}},{"cell_type":"markdown","source":"<div class=\"alert alert-warning\">\n<strong>I found that there is no reference custom model training YOLOX notebook on Kaggle (or I am bad in searching ... ). Since we have such an opportunity this is my contribution to this competition. Feel free to use it and enjoy!\n    I really appreciate if you upvote this notebook. Thank you! </strong>\n</div>\n\n\n<div class=\"alert alert-success\" role=\"alert\">\nThis work consists of two parts:     \n    <ul>\n        <li> PART 1 - TRAIN CUSTOM MODEL (for COTS dataset) - > YoloX full training pipeline for COTS dataset -> this notebook</li>\n        <li> PART 2 - INFERENCE PART - YOLOX on Kaggle for COTS is available -> <a href=\"https://www.kaggle.com/remekkinas/yolox-inference-on-kaggle-for-cots\">YOLOX detections submission made on COTS dataset (PART 2 - DETECTION)</a></li>\n    </ul>\n    \n</div>","metadata":{}},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings(\"ignore\")\n\nimport ast\nimport os\nimport json\nimport pandas as pd\nimport torch\nimport importlib\nimport cv2 \n\nfrom shutil import copyfile\nfrom tqdm.notebook import tqdm\ntqdm.pandas()\nfrom sklearn.model_selection import GroupKFold\nfrom PIL import Image\nfrom string import Template\nfrom IPython.display import display\n\nTRAIN_PATH = '/kaggle/input/tensorflow-great-barrier-reef'","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# check Torch and CUDA version\nprint(f\"Torch: {torch.__version__}\")\n!nvcc --version","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 1. INSTALL YOLOX","metadata":{}},{"cell_type":"code","source":"!git clone https://github.com/Megvii-BaseDetection/YOLOX -q\n\n%cd YOLOX\n!pip install -U pip && pip install -r requirements.txt\n!pip install -v -e . ","metadata":{"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install 'git+https://github.com/cocodataset/cocoapi.git#subdirectory=PythonAPI'","metadata":{"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 2. PREPARE COTS DATASET FOR YOLOX\nThis section is taken from  notebook created by Awsaf [Great-Barrier-Reef: YOLOv5 train](https://www.kaggle.com/awsaf49/great-barrier-reef-yolov5-train)\n\n## A. PREPARE DATASET AND ANNOTATIONS","metadata":{}},{"cell_type":"code","source":"def get_bbox(annots):\n    bboxes = [list(annot.values()) for annot in annots]\n    return bboxes\n\ndef get_path(row):\n    row['image_path'] = f'{TRAIN_PATH}/train_images/video_{row.video_id}/{row.video_frame}.jpg'\n    return row","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = pd.read_csv(\"/kaggle/input/tensorflow-great-barrier-reef/train.csv\")\ndf.head(5)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Taken only annotated photos\ndf[\"num_bbox\"] = df['annotations'].apply(lambda x: str.count(x, 'x'))\ndf_train = df[df[\"num_bbox\"]>0]\n\n#Annotations \ndf_train['annotations'] = df_train['annotations'].progress_apply(lambda x: ast.literal_eval(x))\ndf_train['bboxes'] = df_train.annotations.progress_apply(get_bbox)\n\n#Images resolution\ndf_train[\"width\"] = 1280\ndf_train[\"height\"] = 720\n\n#Path of images\ndf_train = df_train.progress_apply(get_path, axis=1)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"kf = GroupKFold(n_splits = 5) \ndf_train = df_train.reset_index(drop=True)\ndf_train['fold'] = -1\nfor fold, (train_idx, val_idx) in enumerate(kf.split(df_train, y = df_train.video_id.tolist(), groups=df_train.sequence)):\n    df_train.loc[val_idx, 'fold'] = fold\n\ndf_train.head(5)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"HOME_DIR = '/kaggle/working/' \nDATASET_PATH = 'dataset/images'\n\n!mkdir {HOME_DIR}dataset\n!mkdir {HOME_DIR}{DATASET_PATH}\n!mkdir {HOME_DIR}{DATASET_PATH}/train2017\n!mkdir {HOME_DIR}{DATASET_PATH}/val2017\n!mkdir {HOME_DIR}{DATASET_PATH}/annotations","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"SELECTED_FOLD = 4\n\nfor i in tqdm(range(len(df_train))):\n    row = df_train.loc[i]\n    if row.fold != SELECTED_FOLD:\n        copyfile(f'{row.image_path}', f'{HOME_DIR}{DATASET_PATH}/train2017/{row.image_id}.jpg')\n    else:\n        copyfile(f'{row.image_path}', f'{HOME_DIR}{DATASET_PATH}/val2017/{row.image_id}.jpg') ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(f'Number of training files: {len(os.listdir(f\"{HOME_DIR}{DATASET_PATH}/train2017/\"))}')\nprint(f'Number of validation files: {len(os.listdir(f\"{HOME_DIR}{DATASET_PATH}/val2017/\"))}')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## B. CREATE COCO ANNOTATION FILES","metadata":{}},{"cell_type":"code","source":"def save_annot_json(json_annotation, filename):\n    with open(filename, 'w') as f:\n        output_json = json.dumps(json_annotation)\n        f.write(output_json)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"annotion_id = 0","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def dataset2coco(df, dest_path):\n    \n    global annotion_id\n    \n    annotations_json = {\n        \"info\": [],\n        \"licenses\": [],\n        \"categories\": [],\n        \"images\": [],\n        \"annotations\": []\n    }\n    \n    info = {\n        \"year\": \"2021\",\n        \"version\": \"1\",\n        \"description\": \"COTS dataset - COCO format\",\n        \"contributor\": \"\",\n        \"url\": \"https://kaggle.com\",\n        \"date_created\": \"2021-11-30T15:01:26+00:00\"\n    }\n    annotations_json[\"info\"].append(info)\n    \n    lic = {\n            \"id\": 1,\n            \"url\": \"\",\n            \"name\": \"Unknown\"\n        }\n    annotations_json[\"licenses\"].append(lic)\n\n    classes = {\"id\": 0, \"name\": \"starfish\", \"supercategory\": \"none\"}\n\n    annotations_json[\"categories\"].append(classes)\n\n    \n    for ann_row in df.itertuples():\n            \n        images = {\n            \"id\": ann_row[0],\n            \"license\": 1,\n            \"file_name\": ann_row.image_id + '.jpg',\n            \"height\": ann_row.height,\n            \"width\": ann_row.width,\n            \"date_captured\": \"2021-11-30T15:01:26+00:00\"\n        }\n        \n        annotations_json[\"images\"].append(images)\n        \n        bbox_list = ann_row.bboxes\n        \n        for bbox in bbox_list:\n            b_width = bbox[2]\n            b_height = bbox[3]\n            \n            # some boxes in COTS are outside the image height and width\n            if (bbox[0] + bbox[2] > 1280):\n                b_width = bbox[0] - 1280 \n            if (bbox[1] + bbox[3] > 720):\n                b_height = bbox[1] - 720 \n                \n            image_annotations = {\n                \"id\": annotion_id,\n                \"image_id\": ann_row[0],\n                \"category_id\": 0,\n                \"bbox\": [bbox[0], bbox[1], b_width, b_height],\n                \"area\": bbox[2] * bbox[3],\n                \"segmentation\": [],\n                \"iscrowd\": 0\n            }\n            \n            annotion_id += 1\n            annotations_json[\"annotations\"].append(image_annotations)\n        \n        \n    print(f\"Dataset COTS annotation to COCO json format completed! Files: {len(df)}\")\n    return annotations_json","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Convert COTS dataset to JSON COCO\ntrain_annot_json = dataset2coco(df_train[df_train.fold != SELECTED_FOLD], f\"{HOME_DIR}{DATASET_PATH}/train2017/\")\nval_annot_json = dataset2coco(df_train[df_train.fold == SELECTED_FOLD], f\"{HOME_DIR}{DATASET_PATH}/val2017/\")\n\n# Save converted annotations\nsave_annot_json(train_annot_json, f\"{HOME_DIR}{DATASET_PATH}/annotations/train.json\")\nsave_annot_json(val_annot_json, f\"{HOME_DIR}{DATASET_PATH}/annotations/valid.json\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 3. PREPARE CONFIGURATION FILE\n\nConfiguration files for Yolox:\n- [YOLOX-nano](https://github.com/Megvii-BaseDetection/YOLOX/blob/main/exps/default/nano.py)\n- [YOLOX-s](https://github.com/Megvii-BaseDetection/YOLOX/blob/main/exps/default/yolox_s.py)\n- [YOLOX-m](https://github.com/Megvii-BaseDetection/YOLOX/blob/main/exps/default/yolox_m.py)\n\nBelow you can find two (yolox-s and yolox-nano) configuration files for our COTS dataset training.\n\n<div align=\"center\"><img  width=\"800\" src=\"https://github.com/Megvii-BaseDetection/YOLOX/raw/main/assets/git_fig.png\"/></div>","metadata":{}},{"cell_type":"code","source":"# Choose model for your experiments NANO or YOLOX-S (you can adapt for other model type)\n\nNANO = False","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 3A. YOLOX-S EXPERIMENT CONFIGURATION FILE\nTraining parameters could be set up in experiment config files. I created custom files for YOLOX-s and nano. You can create your own using files from oryginal github repo.","metadata":{}},{"cell_type":"markdown","source":"<div class=\"alert alert-warning\">\n<strong> For YOLOX_s I use input size 960x960 but you can change it for your experiments.</strong> \n</div>","metadata":{}},{"cell_type":"code","source":"config_file_template = '''\n\n#!/usr/bin/env python3\n# -*- coding:utf-8 -*-\n# Copyright (c) Megvii, Inc. and its affiliates.\n\nimport os\n\nfrom yolox.exp import Exp as MyExp\n\n\nclass Exp(MyExp):\n    def __init__(self):\n        super(Exp, self).__init__()\n        self.depth = 1.0   \n        self.width = 1.0\n        self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(\".\")[0]\n        \n        # Define yourself dataset path\n        self.data_dir = \"/kaggle/working/dataset/images\"\n        self.train_ann = \"train.json\"\n        self.val_ann = \"valid.json\"\n\n        self.num_classes = 1   #目标类别数 为 1\n\n        self.max_epoch =  $max_epoch\n        self.data_num_workers = 4  #几个线程\n        self.eval_interval = 5   #训练每隔几个epoch 验证一次\n        \n        #self.mosaic_prob = 1.0   # mosaic增强的概率\n        self.translate = 0.1      # 仿射变换\n        #self.mixup_prob = 0.5    # mixup增强的概率\n        #self.hsv_prob = 0.5      #色彩变换的概率\n        self.flip_prob = 0.5     #翻转的概率\n        self.no_aug_epochs = 10   #不做数据增强的轮次\n        \n        self.input_size = (960, 960)\n        self.mosaic_scale = (0.5, 1.5)\n        self.random_size = (10, 20)\n        self.test_size = (960, 960)\n'''","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 3B. YOLOX-NANO CONFIG FILE\n<div class=\"alert alert-warning\">\n<strong> For YOLOX_nano I use input size 460x460 but you can change it for your experiments.</strong> \n</div","metadata":{}},{"cell_type":"code","source":"if NANO:\n    config_file_template = '''\n\n#!/usr/bin/env python3\n# -*- coding:utf-8 -*-\n# Copyright (c) Megvii, Inc. and its affiliates.\n\nimport os\n\nimport torch.nn as nn\n\nfrom yolox.exp import Exp as MyExp\n\n\nclass Exp(MyExp):\n    def __init__(self):\n        super(Exp, self).__init__()\n        self.depth = 0.33\n        self.width = 0.25\n        self.input_size = (416, 416)\n        self.mosaic_scale = (0.5, 1.5)\n        self.random_size = (10, 20)\n        self.test_size = (416, 416)\n        self.exp_name = os.path.split(\n            os.path.realpath(__file__))[1].split(\".\")[0]\n        self.enable_mixup = False\n\n        # Define yourself dataset path\n        self.data_dir = \"/kaggle/working/dataset/images\"\n        self.train_ann = \"train.json\"\n        self.val_ann = \"valid.json\"\n\n        self.num_classes = 1\n\n        self.max_epoch = $max_epoch\n        self.data_num_workers = 2\n        self.eval_interval = 1\n\n    def get_model(self, sublinear=False):\n        def init_yolo(M):\n            for m in M.modules():\n                if isinstance(m, nn.BatchNorm2d):\n                    m.eps = 1e-3\n                    m.momentum = 0.03\n\n        if \"model\" not in self.__dict__:\n            from yolox.models import YOLOX, YOLOPAFPN, YOLOXHead\n            in_channels = [256, 512, 1024]\n            # NANO model use depthwise = True, which is main difference.\n            backbone = YOLOPAFPN(self.depth,\n                                 self.width,\n                                 in_channels=in_channels,\n                                 depthwise=True)\n            head = YOLOXHead(self.num_classes,\n                             self.width,\n                             in_channels=in_channels,\n                             depthwise=True)\n            self.model = YOLOX(backbone, head)\n\n        self.model.apply(init_yolo)\n        self.model.head.initialize_biases(1e-2)\n        return self.model\n\n'''","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div class=\"alert alert-warning\">\n<strong> I trained model for 20 EPOCHS only .... This is for DEMO purposes only.</strong> \n</div>","metadata":{}},{"cell_type":"code","source":"PIPELINE_CONFIG_PATH='cots_config.py'\n\npipeline = Template(config_file_template).substitute(max_epoch = 20)\n\nwith open(PIPELINE_CONFIG_PATH, 'w') as f:\n    f.write(pipeline)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ./yolox/data/datasets/voc_classes.py\n\nvoc_cls = '''\nVOC_CLASSES = (\n  \"starfish\",\n)\n'''\nwith open('./yolox/data/datasets/voc_classes.py', 'w') as f:\n    f.write(voc_cls)\n\n# ./yolox/data/datasets/coco_classes.py\n\ncoco_cls = '''\nCOCO_CLASSES = (\n  \"starfish\",\n)\n'''\nwith open('./yolox/data/datasets/coco_classes.py', 'w') as f:\n    f.write(coco_cls)\n\n# check if everything is ok    \n!more ./yolox/data/datasets/coco_classes.py","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 4. DOWNLOAD PRETRAINED WEIGHTS","metadata":{}},{"cell_type":"markdown","source":"List of pretrained models:\n* YOLOX-s\n* YOLOX-m\n* YOLOX-nano for inference speed (!)\n* etc.","metadata":{}},{"cell_type":"code","source":"sh = 'wget https://github.com/Megvii-BaseDetection/storage/releases/download/0.0.1/yolox_l.pth'\nMODEL_FILE = 'yolox_l.pth'\n\nif NANO:\n    sh = '''\n    wget https://github.com/Megvii-BaseDetection/storage/releases/download/0.0.1/yolox_nano.pth\n    '''\n    MODEL_FILE = 'yolox_nano.pth'\n\nwith open('script.sh', 'w') as file:\n  file.write(sh)\n\n!bash script.sh","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 5. TRAIN MODEL","metadata":{}},{"cell_type":"code","source":"!cp ./tools/train.py ./","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!python train.py \\\n    -f cots_config.py \\\n    -d 1 \\\n    -b 32 \\\n    --fp16 \\\n    -o \\\n    -c {MODEL_FILE}   # Remember to chenge this line if you take different model eg. yolo_nano.pth, yolox_s.pth or yolox_m.pth","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!cp -r YOLOX_outputs /kaggle/working","metadata":{},"execution_count":null,"outputs":[]}]}