*This is a mashup of IceVision's "Custom Parser" example and their "Getting Started (Object Detection)" notebooks
Note: The WandB links will 404, because there is no "drscotthawley" WandB account. We just used
sed
to replace the real username in the .ipynb files. Installing IceVision and IceData
If on Colab run the following cell, else check the installation instructions
# Warning: This takes a while!
! [ -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
!pip install espiownage -Uqq
from espiownage.core import *
sysinfo()
dataset_name = 'preclean' # choose from: cleaner, preclean, spnet, cyclegan, fake
k = 1 # 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
from icevision.all import * # this one takes a while
import pandas as pd
from mrspuff.utils import on_colab
from icevision.models.checkpoint import * # as a test to make sure IV is installed properly
data_dir = get_data(dataset_name); data_dir
df = pd.read_csv(data_dir / "bboxes/annotations.csv")
# shuffle rows: if you don't do this next line then my manual/DIY k-folding will go badly
df = df.sample(frac=1).reset_index(drop=True)
df['label'] = 'AN' # all objects as one class: "antinode"
df.head()
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' # make them all the same object
# shuffle rows: if you don't do this next line then my manual/DIY k-folding will go badly
self.df = self.df.sample(frac=1).reset_index(drop=True) # shuffle rows
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)
kfold = True
if not kfold:
print("\n------\n Random splitting")
parser = BBoxParser(template_record, data_dir)
else:
# k = ^^ now defined above in 'Run Parameters' # 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: # last one might be a bit different
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))
Quick check: Let's take a look at one record of target data:
show_record(train_records[5], display_label=False, figsize=(14, 10))
# 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)
samples = [train_ds[0] for _ in range(3)]
show_samples(samples, ncols=3)
## this has been moved up to "Run Parameters" look up^^ #
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
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)
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)
# show batch - target data -- Ideally these won't all look the same! ;-)
model_type.show_batch(first(valid_dl), ncols=4)
metrics = [COCOMetric(metric_type=COCOMetricType.bbox)]
if use_wandb:
wandb.init(project=project, name=f'k={k},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)
learn.lr_find(end_lr=0.005)
epochs = 11 if kfold else 30 # really 10 or 11 is where things settle down
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)
wandb.finish()
model_type.show_results(model, valid_ds, detection_threshold=.5)
checkpoint_path = f'bbox-k{k}-m{model_choice}-{dataset_name}.pth'
save_icevision_checkpoint(model,
model_name=model_name,
backbone_name=backbone_name,
classes = parser.class_map.get_classes(),
img_size=384,
filename=checkpoint_path,
meta={'icevision_version': '0.9.1'})
checkpoint_and_model = model_from_checkpoint(checkpoint_path,
model_name=model_name,
backbone_name=backbone_name,
img_size=384)
model.to('cuda')
device=next(model.parameters()).device
device
infer_ds = valid_ds
infer_dl = model_type.infer_dl(infer_ds, batch_size=4, shuffle=False)
preds = model_type.predict_from_dl(model, infer_dl, keep_images=True)
#Do NOT use: preds = model_type.predict(model, valid_ds, keep_images=True)
show_preds(preds=preds[0:5])
len(train_ds), len(valid_ds), len(preds)
let's try to figure out how to get what we want from these predictions. hmmm
def get_bblist(pred):
my_bblist = []
bblist = pred.pred.detection.bboxes
for i in range(len(bblist)):
my_bblist.append([bblist[i].xmin, bblist[i].ymin, bblist[i].xmax, bblist[i].ymax])
return my_bblist
get_bblist(preds[1])
results = []
for i in range(len(preds)):
if (len(preds[i].pred.detection.scores) == 0): continue # sometimes you get a zero box/prediction. ??
#print(f"i = {i}, file = {str(Path(valid_ds[i].common.filepath).stem)+'.csv'}, bboxes = {get_bblist(preds[i])}, scores={preds[i].pred.detection.scores}\n")
worst_score = np.min(np.array(preds[i].pred.detection.scores))
line_list = [str(Path(valid_ds[i].common.filepath).stem)+'.csv', get_bblist(preds[i]), preds[i].pred.detection.scores, worst_score, i]
results.append(line_list)
# store as pandas dataframe
res_df = pd.DataFrame(results, columns=['filename', 'bblist','scores','worst_score','i'])
res_df = res_df.sort_values('worst_score') # order by worst score as a "top losses" kind of thing
res_df.head() # take a look
if not kfold:
res_df.to_csv('bboxes_top_losses_real.csv', index=False)
else:
res_df.to_csv(f'bboxes_top_losses_real_k{k}.csv', index=False)