Kaggle competition: Global Wheat Detection
<SOS> metric: custom metric Kaggle data source: global-wheat-detection<import_modules>
!pip install --no-deps '.. /input/timm-package/timm-0.1.26-py3-none-any.whl' > /dev/null
!pip install --no-deps '.. /input/pycocotools/pycocotools-2.0-cp37-cp37m-linux_x86_64.whl' > /dev/null
!git clone https://github.com/adambielski/GrouPy.git
sys.path.insert(0,'./GrouPy/')
<choose_model_class>
def get_valid_transforms() :
return A.Compose([
A.Resize(height=512, width=512, p=1.0),
ToTensorV2(p=1.0),
], p=1.0 )
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, in_planes, planes, stride=1):
super(BasicBlock, self ).__init__()
self.conv1 = P4MConvP4M(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
 self.bn1 = nn.BatchNorm3d(planes)
 self.conv2 = P4MConvP4M(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)
 self.bn2 = nn.BatchNorm3d(planes)
 self.shortcut = nn.Sequential()
if stride != 1 or in_planes != self.expansion*planes:
self.shortcut = nn.Sequential(
 P4MConvP4M(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm3d(self.expansion*planes)
)
 def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
 out = self.bn2(self.conv2(out))
out += self.shortcut(x)
 out = F.relu(out)
 return out
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, in_planes, planes, stride=1):
super(Bottleneck, self ).__init__()
self.conv1 = P4MConvP4M(in_planes, planes, kernel_size=1, bias=False)
 self.bn1 = nn.BatchNorm3d(planes)
 self.conv2 = P4MConvP4M(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
 self.bn2 = nn.BatchNorm3d(planes)
 self.conv3 = P4MConvP4M(planes, self.expansion*planes, kernel_size=1, bias=False)
 self.bn3 = nn.BatchNorm3d(self.expansion*planes)
 self.shortcut = nn.Sequential()
if stride != 1 or in_planes != self.expansion*planes:
self.shortcut = nn.Sequential(
 P4MConvP4M(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm3d(self.expansion*planes)
)
 def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
 out = F.relu(self.bn2(self.conv2(out)))
 out = self.bn3(self.conv3(out))
out += self.shortcut(x)
 out = F.relu(out)
 return out
class ResNet(nn.Module):
def __init__(self, block, num_blocks, num_classes=10):
super(ResNet, self ).__init__()
self.in_planes = 23
self.conv1 = P4MConvZ2(3, 23, kernel_size=3, stride=1, padding=1, bias=False)
 self.bn1 = nn.BatchNorm3d(23)
 self.layer1 = self._make_layer(block, 23, num_blocks[0], stride=1)
 self.layer2 = self._make_layer(block, 45, num_blocks[1], stride=2)
 self.layer3 = self._make_layer(block, 91, num_blocks[2], stride=2)
 self.layer4 = self._make_layer(block, 181, num_blocks[3], stride=2)
 self.linear = nn.Linear(181*8*block.expansion, num_classes)
 def _make_layer(self, block, planes, num_blocks, stride):
strides = [stride] + [1]*(num_blocks-1)
 layers = []
for stride in strides:
layers.append(block(self.in_planes, planes, stride))
self.in_planes = planes * block.expansion
return nn.Sequential(*layers)
 def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
 out = self.layer1(out)
 out = self.layer2(out)
 out = self.layer3(out)
 out = self.layer4(out)
 outs = out.size()
print(out.size() ,out.size(0))
out = out.view(outs[0], outs[1]*outs[2], outs[3], outs[4])
 print(out.size() ,out.size(0))
out = F.avg_pool2d(out, 4)
 print(out.size() ,out.size(0))
out = out.view(out.size(0), -1)
 print(out.size())
 out = self.linear(out)
 print(out.size())
 return out
def ResNet18(pretrained=False):
return ResNet(BasicBlock, [2,2,2,2])
 def ResNet34(pretrained=False):
return ResNet(BasicBlock, [3,4,6,3])
 def ResNet50(pretrained=False):
return ResNet(Bottleneck, [3,4,6,3])
 def ResNet101(pretrained=False):
return ResNet(Bottleneck, [3,4,23,3])
 def ResNet152(pretrained=False):
return ResNet(Bottleneck, [3,8,36,3] )<define_variables>
DATA_ROOT_PATH = '.. /input/global-wheat-detection/test'
class DatasetRetriever(Dataset):
def __init__(self, image_ids, transforms=None):
super().__init__()
self.image_ids = image_ids
self.transforms = transforms
def __getitem__(self, index: int):
image_id = self.image_ids[index]
image = cv2.imread(f'{DATA_ROOT_PATH}/{image_id}.jpg', cv2.IMREAD_COLOR)
 image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB ).astype(np.float32)
 image /= 255.0
if self.transforms:
sample = {'image': image}
sample = self.transforms(**sample)
 image = sample['image']
return image, image_id
def __len__(self)-> int:
return self.image_ids.shape[0]
model_path='.'
path='.. /input/'
train_folder=f'{path}train'
test_folder=f'{path}test'
train_lbl=f'{path}train_labels.csv'
ORG_SIZE=96
bs=32
num_workers=None
sz=96<load_from_csv>
dataset = DatasetRetriever(
 image_ids=np.array([path.split('/')[-1][:-4] for path in glob(f'{DATA_ROOT_PATH}/*.jpg')]),
transforms=get_valid_transforms() 
)
 def collate_fn(batch):
return tuple(zip(*batch))
data_loader = DataLoader(
 dataset,
batch_size=4,
shuffle=False,
num_workers=2,
drop_last=False,
collate_fn=collate_fn
)
df_trn=pd.read_csv(train_lbl )<feature_engineering>
def load_net(checkpoint_path):
config = get_efficientdet_config('tf_efficientdet_d5')
 net = EfficientDet(config, pretrained_backbone=False)
 config.num_classes = 1
config.image_size=512
net.class_net = HeadNet(config, num_outputs=config.num_classes, norm_kwargs=dict(eps=.001, momentum=.01))
checkpoint = torch.load(checkpoint_path)
 net.load_state_dict(checkpoint['model_state_dict'])
 del checkpoint
gc.collect()
net = DetBenchEval(net, config)
 net.eval() ;
return net.cuda()
tfms = get_transforms(do_flip=False, flip_vert=False, max_rotate=.0, max_zoom=.3,
max_lighting=0.15, max_warp=0.10 )<load_from_csv>
net = load_net('.. /input/wheat-effdet5-fold0-best-checkpoint/fold0-best-all-states.bin' )
data = ImageDataBunch.from_csv(path,csv_labels=train_lbl,folder='train',bs=bs, ds_tfms=tfms, size=sz, suffix='.tif',test=test_folder);
stats=data.batch_stats()
data.normalize(stats )<define_variables>
for images, image_ids in data_loader:
break
images = torch.stack(images ).cuda().float()
det = net(images, torch.tensor([1]*len(images)).float().cuda())
 i = 1
sample = images[i].permute(1, 2, 0 ).cpu().numpy().copy()
boxes = det[i].detach().cpu().numpy() [:,:4]
scores = det[i].detach().cpu().numpy() [:,4]
indexes = np.where(scores > 0.3)[0]
boxes = boxes[indexes]
boxes[:, 2] = boxes[:, 2] + boxes[:, 0]
boxes[:, 3] = boxes[:, 3] + boxes[:, 1]
boxes = boxes.astype(np.int32 ).clip(min=0, max=511)
 fig, ax = plt.subplots(1, 1, figsize=(16, 8))
for box in boxes:
cv2.rectangle(sample,(box[0], box[1]),(box[2], box[3]),(1, 0, 0), 1)
 ax.set_axis_off()
ax.imshow(sample)
 plt.show()
data.show_batch(rows=3, figsize=(12,9))<compute_test_metric>
def process_det(index, det, score_threshold=0.33):
boxes = det[index].detach().cpu().numpy() [:,:4]
scores = det[index].detach().cpu().numpy() [:,4]
boxes[:, 2] = boxes[:, 2] + boxes[:, 0]
boxes[:, 3] = boxes[:, 3] + boxes[:, 1]
boxes =(boxes ).clip(min=0, max=511 ).astype(int)
 indexes = np.where(scores>score_threshold)
 boxes = boxes[indexes]
scores = scores[indexes]
return boxes, scores
def auc_score(y_pred,y_true):
return roc_auc_score(to_np(y_true),to_np(F.sigmoid(y_pred)) [:,1] )<normalization>
class BaseWheatTTA:

 image_size = 512
def augment(self, image):
raise NotImplementedError
def batch_augment(self, images):
raise NotImplementedError
def deaugment_boxes(self, boxes):
raise NotImplementedError
class TTAHorizontalFlip(BaseWheatTTA):

 def augment(self, image):
return image.flip(1)
 def batch_augment(self, images):
return images.flip(2)
 def deaugment_boxes(self, boxes):
boxes[:, [1,3]] = self.image_size - boxes[:, [3,1]]
return boxes
class TTAVerticalFlip(BaseWheatTTA):

 def augment(self, image):
return image.flip(2)
 def batch_augment(self, images):
return images.flip(3)
 def deaugment_boxes(self, boxes):
boxes[:, [0,2]] = self.image_size - boxes[:, [2,0]]
return boxes
class TTARotate90(BaseWheatTTA):

 def augment(self, image):
return torch.rot90(image, 1,(1, 2))
def batch_augment(self, images):
return torch.rot90(images, 1,(2, 3))
def deaugment_boxes(self, boxes):
res_boxes = boxes.copy()
res_boxes[:, [0,2]] = self.image_size - boxes[:, [1,3]]
res_boxes[:, [1,3]] = boxes[:, [2,0]]
return res_boxes
class TTARotate180(BaseWheatTTA):

 def augment(self, image):
return torch.rot90(image, 2,(1, 2))
def batch_augment(self, images):
return torch.rot90(images, 2,(2, 3))
def deaugment_boxes(self, boxes):
boxes[:, [0,1,2,3]] = self.image_size - boxes[:, [2,3,0,1]]
return boxes
class TTARotate270(BaseWheatTTA):

 def augment(self, image):
return torch.rot90(image, 3,(1, 2))
def batch_augment(self, images):
return torch.rot90(images, 3,(2, 3))
def deaugment_boxes(self, boxes):
res_boxes = boxes.copy()
res_boxes[:, [0,2]] = boxes[:, [1,3]]
res_boxes[:, [1,3]] = self.image_size - boxes[:, [2,0]]
return res_boxes
class TTACompose(BaseWheatTTA):

 def __init__(self, transforms):
self.transforms = transforms
def augment(self, image):
for transform in self.transforms:
image = transform.augment(image)
 return image
def batch_augment(self, images):
for transform in self.transforms:
images = transform.batch_augment(images)
 return images
def prepare_boxes(self, boxes):
result_boxes = boxes.copy()
result_boxes[:,0] = np.min(boxes[:, [0,2]], axis=1)
 result_boxes[:,2] = np.max(boxes[:, [0,2]], axis=1)
 result_boxes[:,1] = np.min(boxes[:, [1,3]], axis=1)
 result_boxes[:,3] = np.max(boxes[:, [1,3]], axis=1)
 return result_boxes
def deaugment_boxes(self, boxes):
for transform in self.transforms[::-1]:
boxes = transform.deaugment_boxes(boxes)
 return self.prepare_boxes(boxes )
class AdaptiveConcatPool3d(nn.Module):
"Layer that concats `AdaptiveAvgPool3d` and `AdaptiveMaxPool3d`."
def __init__(self, sz:Optional[int]=None):
"Output will be 2*sz or 2 if sz is None"
super().__init__()
sz = sz or 1
self.ap,self.mp = nn.AdaptiveAvgPool3d(sz), nn.AdaptiveMaxPool3d(sz)
 def forward(self, x): return torch.cat([self.mp(x), self.ap(x)], 1 )<choose_model_class>
tta_transforms = []
for tta_combination in product([TTAHorizontalFlip() , None],
[TTAVerticalFlip() , None],
[TTARotate90() , TTARotate180() , TTARotate270() , None]):
tta_transforms.append(TTACompose([tta_transform for tta_transform in tta_combination if tta_transform]))
def create_head_custom(nf:int, nc:int, lin_ftrs:Optional[Collection[int]]=None, ps:Floats=0.5, bn_final:bool=False):
"Model head that takes `nf` features, runs through `lin_ftrs`, and about `nc` classes."
lin_ftrs = [nf, 512, nc] if lin_ftrs is None else [nf] + lin_ftrs + [nc]
ps = listify(ps)
 if len(ps)==1: ps = [ps[0]/2] *(len(lin_ftrs)-2)+ ps
actns = [nn.ReLU(inplace=True)] *(len(lin_ftrs)-2)+ [None]
layers = [AdaptiveConcatPool3d() , Flatten() ]
for ni,no,p,actn in zip(lin_ftrs[:-1],lin_ftrs[1:],ps,actns):
layers += bn_drop_lin(ni,no,True,p,actn)
 if bn_final: layers.append(nn.BatchNorm1d(lin_ftrs[-1], momentum=0.01))
return nn.Sequential(*layers)
<choose_model_class>
def make_tta_predictions(images, score_threshold=0.33):
with torch.no_grad() :
images = torch.stack(images ).float().cuda()
predictions = []
for tta_transform in tta_transforms:
result = []
det = net(tta_transform.batch_augment(images.clone()), torch.tensor([1]*images.shape[0] ).float().cuda())
 for i in range(images.shape[0]):
boxes = det[i].detach().cpu().numpy() [:,:4]
scores = det[i].detach().cpu().numpy() [:,4]
indexes = np.where(scores > score_threshold)[0]
boxes = boxes[indexes]
boxes[:, 2] = boxes[:, 2] + boxes[:, 0]
boxes[:, 3] = boxes[:, 3] + boxes[:, 1]
boxes = tta_transform.deaugment_boxes(boxes.copy())
 result.append({
'boxes': boxes,
'scores': scores[indexes],
})
 predictions.append(result)
 return predictions
def run_wbf(predictions, image_index, image_size=512, iou_thr=0.44, skip_box_thr=0.43, weights=None):
boxes = [(prediction[image_index]['boxes'] /(image_size-1)).tolist() for prediction in predictions]
scores = [prediction[image_index]['scores'].tolist() for prediction in predictions]
labels = [np.ones(prediction[image_index]['scores'].shape[0] ).astype(int ).tolist() for prediction in predictions]
boxes, scores, labels = ensemble_boxes.ensemble_boxes_wbf.weighted_boxes_fusion(boxes, scores, labels, weights=None, iou_thr=iou_thr, skip_box_thr=skip_box_thr)
 boxes = boxes*(image_size-1)
 return boxes, scores, labels
learn = create_cnn(
 data,
ResNet18,
path='.',
metrics=[fbeta],
ps=0.5,
pretrained=False
)<find_best_params>
def format_prediction_string(boxes, scores):
pred_strings = []
for j in zip(scores, boxes):
pred_strings.append("{0:.4f} {1} {2} {3} {4}".format(j[0], j[1][0], j[1][1], j[1][2], j[1][3]))
return " ".join(pred_strings )
learn.model[1]=create_head_custom(362,2)
 learn.model.to(learn.data.device)
 print(learn.summary())
<find_best_params>
results = []
for images, image_ids in data_loader:
predictions = make_tta_predictions(images)
 for i, image in enumerate(images):
boxes, scores, labels = run_wbf(predictions, image_index=i)
 boxes =(boxes*2 ).round().astype(np.int32 ).clip(min=0, max=1023)
 image_id = image_ids[i]
boxes[:, 2] = boxes[:, 2] - boxes[:, 0]
boxes[:, 3] = boxes[:, 3] - boxes[:, 1]
result = {
'image_id': image_id,
'PredictionString': format_prediction_string(boxes, scores)
 }
results.append(result )
learn.lr_find(num_it=500,end_lr=1000)
 learn.recorder.plot()<train_model>
test_df = pd.DataFrame(results, columns=['image_id', 'PredictionString'])
 test_df.to_csv('submission.csv', index=False)
 test_df.head(10 )