For the IceVision Bounding Boxes, we created a juypter notebook based off of IceVision's Custom Parser and Getting Started (Object Detection) notebooks.

To test which IceVision model was the most effective at identifying our data, we ran our cleaned data through four various models with five k-folds using four various k-fold cross-validation values. During this process, we used wandb to track each run so that we could compare the effectiveness of each model from run to run.

The first step in this process was to download IceVision as well as our espiownage data, which was carried out with the following code:

! [ -e /content ] &&  wget https://raw.githubusercontent.com/airctic/icevision/master/install_colab.sh
! [ -e /content ] &&  chmod +x install_colab.sh && ./install_colab.sh 
! [ -e /content ] &&  pip install git+git://github.com/airctic/icevision.git --upgrade && kill -9 -1
--2021-09-28 05:56:21--  https://raw.githubusercontent.com/airctic/icevision/master/install_colab.sh
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1244 (1.2K) [text/plain]
Saving to: ‘install_colab.sh’

install_colab.sh    100%[===================>]   1.21K  --.-KB/s    in 0s      

2021-09-28 05:56:21 (34.7 MB/s) - ‘install_colab.sh’ saved [1244/1244]

Installing icevision + dependencices for CUDA 10
Uninstalling some dependencies to prevent errors
Found existing installation: torchvision 0.10.0+cu102
Uninstalling torchvision-0.10.0+cu102:
  Successfully uninstalled torchvision-0.10.0+cu102
Found existing installation: fastai 1.0.61
Uninstalling fastai-1.0.61:
  Successfully uninstalled fastai-1.0.61
Installing some dependencies to prevent errors
     |████████████████████████████████| 721 kB 28.7 MB/s 
     |████████████████████████████████| 94 kB 2.9 MB/s 
  Building wheel for datascience (setup.py) ... done
     |█████████████████████████▎      | 311.7 MB 1.7 MB/s eta 0:00:48
ERROR: Operation cancelled by user
ERROR: Operation cancelled by user
^C
Collecting git+git://github.com/airctic/icevision.git
  Cloning git://github.com/airctic/icevision.git to /tmp/pip-req-build-1e5m2b69
  Running command git clone -q git://github.com/airctic/icevision.git /tmp/pip-req-build-1e5m2b69
  Running command git submodule update --init --recursive -q
Collecting torch<1.9,>=1.7.0
  Downloading torch-1.8.1-cp37-cp37m-manylinux1_x86_64.whl (804.1 MB)
     |████████████████████████████████| 804.1 MB 2.2 kB/s 
Collecting torchvision<0.10,>=0.8.0
  Downloading torchvision-0.9.1-cp37-cp37m-manylinux1_x86_64.whl (17.4 MB)
     |████████████████████████████████| 17.4 MB 125 kB/s 
Collecting fastcore<1.4,>=1.3.0
  Downloading fastcore-1.3.26-py3-none-any.whl (56 kB)
     |████████████████████████████████| 56 kB 4.0 MB/s 
Requirement already satisfied: tqdm<5,>=4.49.0 in /usr/local/lib/python3.7/dist-packages (from icevision==0.9.0a1) (4.62.2)
Requirement already satisfied: opencv-python<5,>=4.1.1 in /usr/local/lib/python3.7/dist-packages (from icevision==0.9.0a1) (4.1.2.30)
Collecting albumentations<1.1,>=1.0.3
  Downloading albumentations-1.0.3-py3-none-any.whl (98 kB)
     |████████████████████████████████| 98 kB 6.9 MB/s 
Requirement already satisfied: matplotlib<4,>=3.2.2 in /usr/local/lib/python3.7/dist-packages (from icevision==0.9.0a1) (3.2.2)
Requirement already satisfied: pycocotools<3,>=2.0.2 in /usr/local/lib/python3.7/dist-packages (from icevision==0.9.0a1) (2.0.2)
Requirement already satisfied: requests<3,>=2.23.0 in /usr/local/lib/python3.7/dist-packages (from icevision==0.9.0a1) (2.23.0)
Collecting loguru>=0.5.3
  Downloading loguru-0.5.3-py3-none-any.whl (57 kB)
     |████████████████████████████████| 57 kB 4.5 MB/s 
Collecting pillow>8.0.0
  Downloading Pillow-8.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.0 MB)
     |████████████████████████████████| 3.0 MB 30.8 MB/s 
Requirement already satisfied: importlib-metadata>=1 in /usr/local/lib/python3.7/dist-packages (from icevision==0.9.0a1) (4.8.1)
Requirement already satisfied: scikit-image>=0.16.1 in /usr/local/lib/python3.7/dist-packages (from albumentations<1.1,>=1.0.3->icevision==0.9.0a1) (0.16.2)
Collecting opencv-python-headless>=4.1.1
  Downloading opencv_python_headless-4.5.3.56-cp37-cp37m-manylinux2014_x86_64.whl (37.1 MB)
     |████████████████████████████████| 37.1 MB 52 kB/s 
Requirement already satisfied: scipy in /usr/local/lib/python3.7/dist-packages (from albumentations<1.1,>=1.0.3->icevision==0.9.0a1) (1.4.1)
Requirement already satisfied: PyYAML in /usr/local/lib/python3.7/dist-packages (from albumentations<1.1,>=1.0.3->icevision==0.9.0a1) (5.4.1)
Requirement already satisfied: numpy>=1.11.1 in /usr/local/lib/python3.7/dist-packages (from albumentations<1.1,>=1.0.3->icevision==0.9.0a1) (1.19.5)
Requirement already satisfied: packaging in /usr/local/lib/python3.7/dist-packages (from fastcore<1.4,>=1.3.0->icevision==0.9.0a1) (21.0)
Requirement already satisfied: pip in /usr/local/lib/python3.7/dist-packages (from fastcore<1.4,>=1.3.0->icevision==0.9.0a1) (21.1.3)
Requirement already satisfied: typing-extensions>=3.6.4 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=1->icevision==0.9.0a1) (3.7.4.3)
Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=1->icevision==0.9.0a1) (3.5.0)
Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib<4,>=3.2.2->icevision==0.9.0a1) (2.8.2)
Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib<4,>=3.2.2->icevision==0.9.0a1) (2.4.7)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib<4,>=3.2.2->icevision==0.9.0a1) (1.3.2)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib<4,>=3.2.2->icevision==0.9.0a1) (0.10.0)
Requirement already satisfied: six in /usr/local/lib/python3.7/dist-packages (from cycler>=0.10->matplotlib<4,>=3.2.2->icevision==0.9.0a1) (1.15.0)
Requirement already satisfied: cython>=0.27.3 in /usr/local/lib/python3.7/dist-packages (from pycocotools<3,>=2.0.2->icevision==0.9.0a1) (0.29.24)
Requirement already satisfied: setuptools>=18.0 in /usr/local/lib/python3.7/dist-packages (from pycocotools<3,>=2.0.2->icevision==0.9.0a1) (57.4.0)
Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.23.0->icevision==0.9.0a1) (2.10)
Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.23.0->icevision==0.9.0a1) (1.24.3)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.23.0->icevision==0.9.0a1) (2021.5.30)
Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.23.0->icevision==0.9.0a1) (3.0.4)
Requirement already satisfied: networkx>=2.0 in /usr/local/lib/python3.7/dist-packages (from scikit-image>=0.16.1->albumentations<1.1,>=1.0.3->icevision==0.9.0a1) (2.6.3)
Requirement already satisfied: PyWavelets>=0.4.0 in /usr/local/lib/python3.7/dist-packages (from scikit-image>=0.16.1->albumentations<1.1,>=1.0.3->icevision==0.9.0a1) (1.1.1)
Requirement already satisfied: imageio>=2.3.0 in /usr/local/lib/python3.7/dist-packages (from scikit-image>=0.16.1->albumentations<1.1,>=1.0.3->icevision==0.9.0a1) (2.4.1)
Building wheels for collected packages: icevision
  Building wheel for icevision (setup.py) ... done
  Created wheel for icevision: filename=icevision-0.9.0a1-py3-none-any.whl size=250897 sha256=f3194b0d89574272c4bf00807b277c2a72d70440bc65b0d66495ef04883ca494
  Stored in directory: /tmp/pip-ephem-wheel-cache-hrhs8aw8/wheels/95/5d/e7/4eca08a29def3875927ce1b412919aaff00df5318b828916a8
Successfully built icevision
Installing collected packages: pillow, torch, opencv-python-headless, torchvision, loguru, fastcore, albumentations, icevision
  Attempting uninstall: pillow
    Found existing installation: Pillow 7.1.2
    Uninstalling Pillow-7.1.2:
      Successfully uninstalled Pillow-7.1.2
  Attempting uninstall: torch
    Found existing installation: torch 1.9.0+cu102
    Uninstalling torch-1.9.0+cu102:
      Successfully uninstalled torch-1.9.0+cu102
  Attempting uninstall: albumentations
    Found existing installation: albumentations 0.1.12
    Uninstalling albumentations-0.1.12:
      Successfully uninstalled albumentations-0.1.12
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
torchtext 0.10.0 requires torch==1.9.0, but you have torch 1.8.1 which is incompatible.
Successfully installed albumentations-1.0.3 fastcore-1.3.26 icevision-0.9.0a1 loguru-0.5.3 opencv-python-headless-4.5.3.56 pillow-8.3.2 torch-1.8.1 torchvision-0.9.1

The code above was used to install IceVision, detect if the user running the code was on Google Colab or not, and to also restart the notebook.

Following this, we installed the espiownage packages and defined our run parameters. At this point, we also defined a wandb project so that we could compare each run to determine which model was most effective.

dataset_name = 'cleaner'  # choose from: cleaner, preclean, spnet, cyclegan, fake
k = 4               # for k-fold cross-validation
model_choice = 0    # IceVision object detector backbone; see below
nk = 5              # number of k-folds (leave as 5)
use_wandb = True    # can set to false if no WandB login/tracking is desired
project = 'bbox_kfold' # project name for wandb

We then proceeded to import the IceVision packages as well as the pandas packages as well as a process to check if the user running the notebook was either on colab or not.

from icevision.all import *  
import pandas as pd
from mrspuff.utils import on_colab
from icevision.models.checkpoint import *

After the previous cell ran, the location of the dataset was defined and IceVision was directed to read in the training data.

Following this, the dataset was shuffled and objects within the dataset were classified as antinodes so that our k-folding process worked effectively.

df = pd.read_csv(data_dir / "bboxes/annotations.csv")
df = df.sample(frac=1).reset_index(drop=True) 
df['label'] = 'AN' 
df.head()

Afterwards, the parser for IceVision was defined.

template_record = ObjectDetectionRecord()

class BBoxParser(Parser):
    def __init__(self, template_record, data_dir):
        super().__init__(template_record=template_record)
        
        self.data_dir = data_dir
        self.df = pd.read_csv(data_dir / "bboxes/annotations.csv")
        self.df['label'] = 'AN'  
        self.df = self.df.sample(frac=1).reset_index(drop=True)  
        self.class_map = ClassMap(list(self.df['label'].unique()))
        
    def __iter__(self) -> Any:
        for o in self.df.itertuples():
            yield o
        
    def __len__(self) -> int:
        return len(self.df)
        
    def record_id(self, o) -> Hashable:
        return o.filename
        
    def parse_fields(self, o, record, is_new):
        if is_new:
            record.set_filepath(self.data_dir / 'images' / o.filename)
            record.set_img_size(ImgSize(width=o.width, height=o.height))
            record.detection.set_class_map(self.class_map)
        
        record.detection.add_bboxes([BBox.from_xyxy(o.xmin, o.ymin, o.xmax, o.ymax)])
        record.detection.add_labels([o.label])

parser = BBoxParser(template_record, data_dir)

Once the parser was defined, the dataset was split with five various k-fold values ranging from k = 0 to k = 4, one less than the number of k-folds.

kfold = True 
k = 4  # manual k-folding index -- change this yourself k = 0 to (nk-1)
    nk = 5 # number of k-folds
    print(f"\n-----\n K-fold splitting: {k+1}/{nk}")
    n = len(df)
    idmap, indlist = IDMap(list(df['filename'][0:n])), list(range(n))
    val_size = int(round(n/nk))

    if k < nk-1:
        val_list = indlist[k*val_size:(k+1)*val_size]
        train_list = indlist[0:k*val_size] + indlist[(k+1)*val_size:n]
    else:   
        val_list = indlist[k*val_size:]
        train_list = indlist[0:-len(val_list)]
    val_id_list = list([df['filename'][i] for i in val_list])
    train_id_list = list([df['filename'][i] for i in train_list])
    presplits = list([train_id_list,val_id_list])
    train_records, valid_records = parser.parse(data_splitter=FixedSplitter(presplits))

We then defined and applied transformations to the data so that IceVision did not accidentally detect the image borders and instead detected the anodes in each image. At this point, we also defined the new, split dataset.

# size is set to 384 because EfficientDet requires its inputs to be divisible by 128
image_size = 384  
train_tfms = tfms.A.Adapter([*tfms.A.aug_tfms(size=image_=size, presize=512), tfms.A.Normalize()])
valid_tfms = tfms.A.Adapter([*tfms.A.resize_and_pad(image_size), tfms.A.Normalize()])

# Datasets
train_ds = Dataset(train_records, train_tfms)
valid_ds = Dataset(valid_records, valid_tfms)

At this point, we created a cell so that our model selection number, which was defined earlier in our run parameters, could actually set which model IceVision would use to train.

Models were defined as follows:

Retinanet = Model Selection 0

Retinanet = Model Selection 1

Efficientdet = Model Selection 2

Yolov5 = Model Selection 3

(Note:Both Efficientdet and Yolov5 require image size parameters whereas Retinanet does not.)

The following cell shows the code behind this process.

selection = model_choice
extra_args = {}

if selection == 0:
    model_type = models.mmdet.retinanet
    backbone = model_type.backbones.resnet50_fpn_1x
    model_name='mmdet.retinanet'
    backbone_name = 'resnet50_fpn_1x'

elif selection == 1:
  # The Retinanet model is also implemented in the torchvision library
    model_type = models.torchvision.retinanet
    backbone = model_type.backbones.resnet50_fpn
    model_name='torchvision.retinanet'
    backbone_name = 'resnet50_fpn'

elif selection == 2:
    model_type = models.ross.efficientdet
    backbone = model_type.backbones.tf_lite0
    # The efficientdet model requires an img_size parameter
    model_name='ross.efficientdet'
    backbone_name = 'tf_lite0'
    extra_args['img_size'] = image_size

elif selection == 3:
    model_type = models.ultralytics.yolov5
    backbone = model_type.backbones.small
    model_name='ultralytics.yolov5'
    backbone_name = 'small'
    # The yolov5 model requires an img_size parameter
    extra_args['img_size'] = image_size

model_type, backbone, extra_args

Wandb was then initialized after model selections were defined so that each run could be tracked and the model was then instantiated.

if use_wandb: 
    !pip install wandb -qqq
    import wandb
    from fastai.callback.wandb import *
    from fastai.callback.tracker import SaveModelCallback
    wandb.login()
model = model_type.model(backbone=backbone(pretrained=True), num_classes=len(parser.class_map), **extra_args) 

Then, dataloaders were defined.

train_dl = model_type.train_dl(train_ds, batch_size=8, num_workers=4, shuffle=True)
valid_dl = model_type.valid_dl(valid_ds, batch_size=8, num_workers=4, shuffle=False)

Once the dataloaders were defined, metrics for each wandb run were defined.

metrics = [COCOMetric(metric_type=COCOMetricType.bbox)]

After defining metrics, a wandb run was initalized so that the training runs could be tracked.

if use_wandb:
    wandb.init(project=project, name=f'k=4,m={model_choice},{dataset_name}')
    cbs = [WandbCallback()]
else:

    cbs = []

learn = model_type.fastai.learner(dls=[train_dl, valid_dl], model=model, metrics=metrics, 
            cbs=cbs)

The next step was to then begin training the data with 11 epochs using a learning rate of 1e-4, with two frozen epochs.

epochs = 11 if kfold else 30  
lr = 1e-4
freeze_epochs=2
print(f"Training for {epochs} epochs, starting with {freeze_epochs} frozen epochs...")
learn.fine_tune(epochs,lr, freeze_epochs=2)

Once a run was concluded, the respective wandb run was terminated so that a new run could be tracked.

wandb.finish()

This process was then repeated 19 times for each model with various k-values ranging from 0 to nk-1.

After 20 total runs of the clean data were preformed with the various models and k-values, the COCOMetric of each run was compared to the others to determine which learning model taught the neural network best. From this, it was concluded that model number = 0 (mmdet's Retinanet) taught our neural network better than any of the other models.

The results of each run can be seen below.

Screen Shot 2021-09-27 at 2.54.10 PM.png

We found that mmdet's Retinanet preformed best with an average COCOMetric of 0.68184 with a standard deviation of 0.002. Expectedly, torchvision's Retinanet preformed second best with an average COCOMetric of 0.63506 and standard deviation of 0.00928 across all runs. From these results, it was decided that mmdet's Retinanet would be used to train our neural network to fit IceVision Bounding Boxes around antinodes in our data for all data sets.