Kaggle competition: Global Wheat Detection
def set_seed(seed = 42):

 np.random.seed(seed)
 random.seed(seed)
 torch.manual_seed(seed)
 torch.cuda.manual_seed(seed)
 torch.backends.cudnn.deterministic = True
os.environ['PYTHONHASHSEED'] = str(seed)
 set_seed(CFG.seed )<load_from_csv>
!pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext"./
df = pd.read_csv(f"{ROOT_DIR}/train.csv" )<split>
!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
skf = StratifiedKFold(n_splits=CFG.n_fold)
 for fold,(_, val_)in enumerate(skf.split(X=df, y=df.label)) :
df.loc[val_ , "kfold"] = int(fold)
 df['kfold'] = df['kfold'].astype(int )<compute_train_metric>
trained_weight_path = '.. /input/efficidentdet7fullsize/best-checkpoint-054epoch.bin'
def log_t(u, t):

 if t==1.0:
return u.log()
else:
return(u.pow(1.0 - t)- 1.0)/(1.0 - t)
 def exp_t(u, t):

 if t==1:
return u.exp()
else:
return(1.0 +(1.0-t)*u ).relu().pow(1.0 /(1.0 - t))
def compute_normalization_fixed_point(activations, t, num_iters):

 mu, _ = torch.max(activations, -1, keepdim=True)
 normalized_activations_step_0 = activations - mu
normalized_activations = normalized_activations_step_0
for _ in range(num_iters):
logt_partition = torch.sum(
 exp_t(normalized_activations, t), -1, keepdim=True)
 normalized_activations = normalized_activations_step_0 * \
logt_partition.pow(1.0-t)
 logt_partition = torch.sum(
 exp_t(normalized_activations, t), -1, keepdim=True)
 normalization_constants = - log_t(1.0 / logt_partition, t)+ mu
return normalization_constants
def compute_normalization_binary_search(activations, t, num_iters):

 mu, _ = torch.max(activations, -1, keepdim=True)
 normalized_activations = activations - mu
effective_dim = \
torch.sum(
(normalized_activations > -1.0 /(1.0-t)).to(torch.int32),
dim=-1, keepdim=True ).to(activations.dtype)
 shape_partition = activations.shape[:-1] +(1,)
 lower = torch.zeros(shape_partition, dtype=activations.dtype, device=activations.device)
 upper = -log_t(1.0/effective_dim, t)* torch.ones_like(lower)
 for _ in range(num_iters):
logt_partition =(upper + lower)/2.0
sum_probs = torch.sum(
 exp_t(normalized_activations - logt_partition, t),
dim=-1, keepdim=True)
 update =(sum_probs < 1.0 ).to(activations.dtype)
 lower = torch.reshape(
 lower * update +(1.0-update)* logt_partition,
shape_partition)
 upper = torch.reshape(
 upper *(1.0 - update)+ update * logt_partition,
shape_partition)
 logt_partition =(upper + lower)/2.0
return logt_partition + mu
class ComputeNormalization(torch.autograd.Function):

 @staticmethod
def forward(ctx, activations, t, num_iters):
if t < 1.0:
normalization_constants = compute_normalization_binary_search(activations, t, num_iters)
 else:
normalization_constants = compute_normalization_fixed_point(activations, t, num_iters)
 ctx.save_for_backward(activations, normalization_constants)
 ctx.t=t
return normalization_constants
@staticmethod
def backward(ctx, grad_output):
activations, normalization_constants = ctx.saved_tensors
t = ctx.t
normalized_activations = activations - normalization_constants
probabilities = exp_t(normalized_activations, t)
 escorts = probabilities.pow(t)
 escorts = escorts / escorts.sum(dim=-1, keepdim=True)
 grad_input = escorts * grad_output
return grad_input, None, None
def compute_normalization(activations, t, num_iters=5):

 return ComputeNormalization.apply(activations, t, num_iters)
 def tempered_sigmoid(activations, t, num_iters = 5):

 internal_activations = torch.stack([activations,
torch.zeros_like(activations)],
dim=-1)
 internal_probabilities = tempered_softmax(internal_activations, t, num_iters)
 return internal_probabilities[..., 0]
def tempered_softmax(activations, t, num_iters=5):

 if t == 1.0:
return activations.softmax(dim=-1)
 normalization_constants = compute_normalization(activations, t, num_iters)
 return exp_t(activations - normalization_constants, t)
 def bi_tempered_binary_logistic_loss(activations,
labels,
t1,
t2,
label_smoothing = 0.0,
num_iters=5,
reduction='mean'):

 internal_activations = torch.stack([activations,
torch.zeros_like(activations)],
dim=-1)
 internal_labels = torch.stack([labels.to(activations.dtype),
1.0 - labels.to(activations.dtype)],
dim=-1)
 return bi_tempered_logistic_loss(internal_activations,
internal_labels,
t1,
t2,
label_smoothing = label_smoothing,
num_iters = num_iters,
reduction = reduction)
 def bi_tempered_logistic_loss(activations,
labels,
t1,
t2,
label_smoothing=0.0,
num_iters=5,
reduction = 'mean'):

 if len(labels.shape)<len(activations.shape):
labels_onehot = torch.zeros_like(activations)
 labels_onehot.scatter_(1, labels[..., None], 1)
 else:
labels_onehot = labels
if label_smoothing > 0:
num_classes = labels_onehot.shape[-1]
labels_onehot =(1 - label_smoothing * num_classes /(num_classes - 1)) \
* labels_onehot + \
label_smoothing /(num_classes - 1)
 probabilities = tempered_softmax(activations, t2, num_iters)
 loss_values = labels_onehot * log_t(labels_onehot + 1e-10, t1)\
- labels_onehot * log_t(probabilities, t1)\
- labels_onehot.pow(2.0 - t1)/(2.0 - t1)\
+ probabilities.pow(2.0 - t1)/(2.0 - t1)
 loss_values = loss_values.sum(dim = -1)
 if reduction == 'none':
return loss_values
if reduction == 'sum':
return loss_values.sum()
if reduction == 'mean':
return loss_values.mean()<train_model>
class BaseWheatTTA:

 image_size = 1024
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 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 )
def train_model(model, optimizer, scheduler, num_epochs, dataloaders, dataset_sizes, device, fold):
start = time.time()
best_model_wts = copy.deepcopy(model.state_dict())
 best_acc = 0.0
history = defaultdict(list)
 scaler = amp.GradScaler()
for epoch in range(1,num_epochs+1):
print('Epoch {}/{}'.format(epoch, num_epochs))
print('-' * 10)
 for phase in ['train','valid']:
if(phase == 'train'):
model.train()
else:
model.eval()
running_loss = 0.0
running_corrects = 0.0
for inputs,labels in tqdm(dataloaders[phase]):
inputs = inputs.to(device)
 labels = labels.to(device)
 optimizer.zero_grad()
with torch.set_grad_enabled(phase == 'train'):
with amp.autocast() :
outputs = model(inputs)
 _, preds = torch.max(outputs,1)
 loss = bi_tempered_logistic_loss(outputs, labels, t1=CFG.t1, t2=CFG.t2, label_smoothing=CFG.smoothing)
 if phase == 'train':
scaler.scale(loss ).backward()
scaler.step(optimizer)
 scaler.update()
running_loss += loss.item() *inputs.size(0)
 running_corrects += torch.sum(preds == labels.data ).double().item()
epoch_loss = running_loss/dataset_sizes[phase]
epoch_acc = running_corrects/dataset_sizes[phase]
history[phase + ' loss'].append(epoch_loss)
 history[phase + ' acc'].append(epoch_acc)
 if phase == 'train' and scheduler != None:
scheduler.step()
print('{} Loss: {:.4f} Acc: {:.4f}'.format(
 phase, epoch_loss, epoch_acc))
if phase=='valid' and epoch_acc >= best_acc:
best_acc = epoch_acc
best_model_wts = copy.deepcopy(model.state_dict())
 PATH = f"Fold{fold}_{best_acc}_epoch{epoch}.bin"
print()
end = time.time()
time_elapsed = end - start
print('Training complete in {:.0f}h {:.0f}m {:.0f}s'.format(
 time_elapsed // 3600,(time_elapsed % 3600)// 60,(time_elapsed % 3600)% 60))
print("Best Accuracy ",best_acc)
 model.load_state_dict(best_model_wts)
 return model, history<create_dataframe>
tta_transforms = []
for tta_combination in product([TTAHorizontalFlip() , None],
[TTAVerticalFlip() , None],
[TTARotate90() , None]):
tta_transforms.append(TTACompose([tta_transform for tta_transform in tta_combination if tta_transform]))
def run_fold(model, optimizer, scheduler, device, fold, num_epochs=10):
valid_df = df[df.kfold == fold]
train_df = df[df.kfold != fold]
train_data = CassavaLeafDataset(TRAIN_DIR, train_df, transforms=data_transforms["train"])
 valid_data = CassavaLeafDataset(TRAIN_DIR, valid_df, transforms=data_transforms["valid"])
 dataset_sizes = {
'train' : len(train_data),
'valid' : len(valid_data)
 }
train_loader = DataLoader(dataset=train_data, batch_size=CFG.batch_size, num_workers=4, pin_memory=True, shuffle=True)
 valid_loader = DataLoader(dataset=valid_data, batch_size=CFG.batch_size, num_workers=4, pin_memory=True, shuffle=False)
 dataloaders = {
'train' : train_loader,
'valid' : valid_loader
}
model, history = train_model(model, optimizer, scheduler, num_epochs, dataloaders, dataset_sizes, device, fold)
 return model, history<choose_model_class>
def make_tta_predictions(images, nets, score_threshold=0.25):
all_predictions = []
images = torch.stack(images ).float().cuda()
for fold_num, net in enumerate(nets):
with torch.no_grad() :
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)
 all_predictions.append(predictions)
 return all_predictions
def run_wbf(all_predictions, image_index, image_size=1024, iou_thr=0.44, skip_box_thr=0.43, weights=None):
for i in range(len(nets)) :
predictions = all_predictions[i]
if i == 0:
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] ).tolist() for prediction in predictions]
else:
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] ).tolist() for prediction in predictions]
boxes, scores, labels = 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
model = timm.create_model(CFG.model_name, pretrained=True )<train_model>
def create_model_efficientdet_test(compound_coef:int, weight_path=None,
num_classes=1, image_size=1024, pretrained=False, model_ema=False):
config = get_efficientdet_config(f'tf_efficientdet_d{compound_coef}')
 model = EfficientDet(config, pretrained_backbone=pretrained)
 config.num_classes = num_classes
config.image_size = image_size
model.class_net = HeadNet(config, num_outputs=config.num_classes, norm_kwargs=dict(eps=.001, momentum=.01))
checkpoint = torch.load(weight_path)
 if model_ema:
model.load_state_dict(checkpoint['state_dict_ema'])
 else:
model.load_state_dict(checkpoint['model_state_dict'])
 del checkpoint
gc.collect()
model = DetBenchEval(model, config)
 anchors.MAX_DETECTIONS_PER_IMAGE=250
model.eval() ;
return model.cuda()
nets = [create_model_efficientdet_test(compound_coef=4,
weight_path='.. /input/efficidentdet4-fullsize/best-checkpoint-058epoch.bin',
pretrained=False,
model_ema=True),
create_model_efficientdet_test(compound_coef=5,
weight_path='.. /input/efficientdet5-fullsize/best-checkpoint-052epoch.bin',
pretrained=False),
create_model_efficientdet_test(compound_coef=7,
weight_path=trained_weight_path,
pretrained=False,
model_ema=True)]
num_features = model.classifier.in_features
model.classifier = nn.Linear(num_features, CFG.num_classes)
 model.to(CFG.device);<choose_model_class>
def get_test_transforms() :
return A.Compose([
A.Resize(height=1024, width=1024, p=1.0),
ToTensorV2(p=1.0),
], p=1.0 )
optimizer = optim.Adam(model.parameters() , lr=CFG.lr, weight_decay=CFG.weight_decay, amsgrad=False )<choose_model_class>
DATA_ROOT_PATH = '.. /input/global-wheat-detection/test'
class DatasetRetrieverTest(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]
def fetch_scheduler(optimizer):
if CFG.scheduler == 'CosineAnnealingLR':
scheduler = lr_scheduler.CosineAnnealingLR(optimizer, T_max=CFG.T_max, eta_min=CFG.min_lr)
 elif CFG.scheduler == 'CosineAnnealingWarmRestarts':
scheduler = lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=CFG.T_0, T_mult=1, eta_min=CFG.min_lr)
 elif CFG.scheduler == None:
return None
return scheduler<set_options>
test_dataset = DatasetRetrieverTest(
 image_ids=np.array([path.split('/')[-1][:-4] for path in glob(f'{DATA_ROOT_PATH}/*.jpg')]),
transforms=get_test_transforms() 
)
 def collate_fn(batch):
return tuple(zip(*batch))
test_data_loader = DataLoader(
 test_dataset,
batch_size=2,
shuffle=False,
num_workers=4,
drop_last=False,
collate_fn=collate_fn
)
scheduler = fetch_scheduler(optimizer )<load_pretrained>
def predict_test(data_loader,nets):
results = []
testdf_psuedo = []
for images, image_ids in data_loader:
predictions = make_tta_predictions(images=images, nets=nets)
 for i, image in enumerate(images):
boxes, scores, labels = run_wbf(predictions, image_index=i)
 boxes = boxes.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]
for box in boxes:
result = {
'image_id': image_id,
'source': 'BULLBULL',
'width':1024,
'height':1024,
'x': box[0],
'y': box[1],
'w': box[2],
'h': box[3]
}
testdf_psuedo.append(result)
 test_df_pseudo = pd.DataFrame(testdf_psuedo, columns=['image_id', 'width', 'height', 'source', 'x', 'y', 'w', 'h'])
 return test_df_pseudo
model, history = run_fold(model, optimizer, scheduler, device=CFG.device, fold=0, num_epochs=CFG.num_epochs )<save_model>
test_df_pseudo = predict_test(test_data_loader, nets )
if(CFG.savemodel):
torch.save(model, CFG.savemodelpath )<load_from_csv>
def generate_train(test_df_pseudo, fold_=1):
print("Loading training set...")
 marking = pd.read_csv('.. /input/wheat-cleaned-dataset/clean_train_v2.csv')
 marking = marking[marking['area']> 300]
df_with_pseudo = pd.concat([marking, test_df_pseudo])
 index = list(set(marking.image_id))
fold = 0
val_index = index[len(index)*fold//5:len(index)*(fold+1)//5]
train_index = index[:len(index)*fold//5] + index[len(index)*(fold+1)//5:]
train_df_pseudo_fold = test_df_pseudo['image_id'].unique()
train_fold = np.concatenate(( train_index, train_df_pseudo_fold))
print('train dataset size:', len(train_fold))
val_fold = np.array(val_index)
 print('val dataset size:', len(val_fold))
return df_with_pseudo, train_fold, val_fold
if(CFG.dosubmission):
t_df = pd.read_csv(f"{ROOT_DIR}/sample_submission.csv")
 T_DIR = TEST_DIR
t_data = CassavaLeafDataset(T_DIR, t_df, transforms=data_transforms["valid"])
 t_loader = DataLoader(dataset=t_data, batch_size=1, num_workers=4, pin_memory=True, shuffle=False)
 submit_df = pd.DataFrame(t_df ).copy(deep=True)
 for i,(inputs, label)in enumerate(t_loader):
inputs = inputs.to(CFG.device)
 outputs = model(inputs ).detach().cpu().numpy()
pred_label = np.argmax(outputs)
 submit_df.iloc[i] = [t_df.iloc[i]['image_id'], pred_label]
submit_df.to_csv("/kaggle/working/submission.csv", index=False )<import_modules>
df_with_pesudo, train_fold, val_fold = generate_train(test_df_pseudo )
!pip install livelossplot 
<define_variables>
def get_train_transforms() :
return A.Compose(
 [
A.OneOf([
A.HueSaturationValue(hue_shift_limit=0.2, sat_shift_limit= 0.2,
val_shift_limit=0.2, p=0.9),
A.RandomBrightnessContrast(brightness_limit=0.2,
contrast_limit=0.2, p=0.9),
],p=0.9),
A.ToGray(p=0.01),
A.HorizontalFlip(p=0.5),
A.VerticalFlip(p=0.5),
A.RandomRotate90(p=0.5),
A.Resize(height=1024, width=1024, p=1),
ToTensorV2(p=1.0),
],
p=1.0,
bbox_params=A.BboxParams(
 format='pascal_voc',
min_area=0,
min_visibility=0,
label_fields=['labels'] 
)
)
 def get_valid_transforms() :
return A.Compose(
 [
A.Resize(height=1024, width=1024, p=1.0),
ToTensorV2(p=1.0),
],
p=1.0,
bbox_params=A.BboxParams(
 format='pascal_voc',
min_area=0,
min_visibility=0,
label_fields=['labels'] 
)
 )
TRAINING_LOGS_FILE = "training_logs.csv"
MODEL_FILE = "histopathologic_cancer_detector.h5"
ROC_PLOT_FILE = "roc.png"
KAGGLE_SUBMISSION_FILE = "kaggle_submission.csv"
INPUT_DIR = '.. /input/'<define_variables>
TRAIN_ROOT_PATH = '.. /input/global-wheat-detection/train'
class DatasetRetriever(Dataset):
def __init__(self, marking, image_ids, transforms=None, test=False, mixup=True):
super().__init__()
self.image_ids = image_ids
self.marking = marking
self.transforms = transforms
self.test = test
self.mixup = mixup
self.mosaic_border = [-1024//2, -1024//2]
def __getitem__(self, index: int):
image_id = self.image_ids[index]
origin_image = False
if self.test or random.random() > 0.5:
image, boxes = self.load_image_and_boxes(index)
 origin_image = True
else:
image, boxes = self.load_cutmix_image_and_boxes(index)
 if(random.random() < 0.9)and(self.test == False):
image, boxes = cutout(image, boxes)
 labels = torch.ones(( boxes.shape[0],), dtype=torch.int64)
 target = {}
target['boxes'] = boxes
target['labels'] = labels
target['image_id'] = torch.tensor([index])
 if self.transforms:
for i in range(10):
sample = self.transforms(**{
'image': image,
'bboxes': target['boxes'],
'labels': labels
})
 if len(sample['bboxes'])> 0:
image = sample['image']
target['labels'] = torch.ones(( len(sample['bboxes']),), dtype=torch.int64)
 target['boxes'] = torch.stack(tuple(map(torch.tensor, zip(*sample['bboxes'])))).permute(1, 0)
 target['boxes'][:,[0,1,2,3]] = target['boxes'][:,[1,0,3,2]]
break
return image, target, image_id
def __len__(self)-> int:
return self.image_ids.shape[0]
def load_image_and_boxes(self, index):
image_id = self.image_ids[index]
if os.path.exists(f'{TRAIN_ROOT_PATH}/{image_id}.jpg'):
image = cv2.imread(f'{TRAIN_ROOT_PATH}/{image_id}.jpg', cv2.IMREAD_COLOR)
 else:
image = cv2.imread(f'{DATA_ROOT_PATH}/{image_id}.jpg', cv2.IMREAD_COLOR)
 image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB ).astype(np.float32)
 image = cv2.resize(image,(1024, 1024))
image /= 255.0
records = self.marking[self.marking['image_id'] == image_id]
boxes = records[['x', 'y', 'w', 'h']].values
boxes[:, 2] = boxes[:, 0] + boxes[:, 2]
boxes[:, 3] = boxes[:, 1] + boxes[:, 3]
return image, boxes
def load_cutmix_image_and_boxes(self, index, imsize=1024):

 w, h = imsize, imsize
s = imsize
xc, yc = [int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border]
indexes = [index] + [random.randint(0, self.image_ids.shape[0] - 1)for _ in range(3)]
result_image = np.full(( imsize*2, imsize*2, 3), 114/255, dtype=np.float32)
 result_boxes = []
for i, index in enumerate(indexes):
image, boxes = self.load_image_and_boxes(index)
 if i == 0:
x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc
x1b, y1b, x2b, y2b = w -(x2a - x1a), h -(y2a - y1a), w, h
elif i == 1:
x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc
x1b, y1b, x2b, y2b = 0, h -(y2a - y1a), min(w, x2a - x1a), h
elif i == 2:
x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h)
 x1b, y1b, x2b, y2b = w -(x2a - x1a), 0, max(xc, w), min(y2a - y1a, h)
 elif i == 3:
x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h)
 x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h)
 result_image[y1a:y2a, x1a:x2a] = image[y1b:y2b, x1b:x2b]
padw = x1a - x1b
padh = y1a - y1b
boxes[:, 0] += padw
boxes[:, 1] += padh
boxes[:, 2] += padw
boxes[:, 3] += padh
result_boxes.append(boxes)
 result_boxes = np.concatenate(result_boxes, 0)
 np.clip(result_boxes[:, 0:], 0, 2 * s, out=result_boxes[:, 0:])
 result_boxes = result_boxes.astype(np.int32)
 result_boxes = result_boxes[np.where(( result_boxes[:, 2] - result_boxes[:, 0])*(result_boxes[:, 3] - result_boxes[:, 1])> 300.0)]
result_boxes = result_boxes[np.where(( result_boxes[:, 2] - result_boxes[:, 0])>= 10.0)]
result_boxes = result_boxes[np.where(( result_boxes[:, 3] - result_boxes[:, 1])>= 10.0)]
result_image, result_boxes = random_affine(result_image, targets=result_boxes)
 return result_image, result_boxes
SAMPLE_COUNT = 50000
TRAINING_RATIO = 0.9
IMAGE_SIZE = 96
EPOCHS = 12
BATCH_SIZE = 128
VERBOSITY = 1
TESTING_BATCH_SIZE = 1000<feature_engineering>
train_dataset = DatasetRetriever(
 image_ids=train_fold,
marking=df_with_pesudo,
transforms=get_train_transforms() ,
test=False, 
)
 validation_dataset = DatasetRetriever(
 image_ids=val_fold,
marking=df_with_pesudo,
transforms=get_valid_transforms() ,
test=True,
)
training_dir = INPUT_DIR + 'train/'
data_frame = pd.DataFrame({'path': glob(os.path.join(training_dir,'*.tif')) })
 data_frame['id'] = data_frame.path.map(lambda x: x.split('/')[3].split('.')[0])
 labels = pd.read_csv(INPUT_DIR + 'train_labels.csv')
 data_frame = data_frame.merge(labels, on = 'id')
 negatives = data_frame[data_frame.label == 0].sample(SAMPLE_COUNT)
 positives = data_frame[data_frame.label == 1].sample(SAMPLE_COUNT)
 data_frame = pd.concat([negatives, positives] ).reset_index()
data_frame = data_frame[['path', 'id', 'label']]
data_frame['image'] = data_frame['path'].map(imread)
 training_path = '.. /training1'
validation_path = '.. /validation1'
for folder in [training_path, validation_path]:
for subfolder in ['0', '1']:
path = os.path.join(folder, subfolder)
 os.makedirs(path, exist_ok=True)
 training, validation = train_test_split(data_frame, train_size=TRAINING_RATIO, stratify=data_frame['label'])
 data_frame.set_index('id', inplace=True)
 for images_and_path in [(training, training_path),(validation, validation_path)]:
images = images_and_path[0]
path = images_and_path[1]
for image in images['id'].values:
file_name = image + '.tif'
label = str(data_frame.loc[image,'label'])
 destination = os.path.join(path, label, file_name)
 if not os.path.exists(destination):
source = os.path.join(INPUT_DIR + 'train', file_name)
 shutil.copyfile(source, destination )<choose_model_class>
class AverageMeter(object):

 def __init__(self):
self.reset()
def reset(self):
self.val = 0
self.avg = 0
self.sum = 0
self.count = 0
def update(self, val, n=1):
self.val = val
self.sum += val * n
self.count += n
self.avg = self.sum / self.count
training_data_generator = ImageDataGenerator(rescale=1./255,
horizontal_flip=True,
vertical_flip=True,
rotation_range=180,
zoom_range=[1, 1.5],
width_shift_range=0.3,
height_shift_range=0.3,
shear_range=0.3,
channel_shift_range=0.3 )<train_model>
class TrainGlobalConfig:
num_workers = 2
batch_size = 2
use_amp = True
use_ema = True
n_epochs = 8
total_batch_size = 8
clip_grad = True
accumulate_steps = int(total_batch_size / batch_size)
 lr = 0.0035
folder = 'effdet7'
verbose = True
verbose_step = 1
step_scheduler = False
validation_scheduler = True
SchedulerClass = CosineLRScheduler
scheduler_params = dict(
 t_initial=n_epochs,
t_mul=1.0,
lr_min = 1e-8, 
)
training_generator = training_data_generator.flow_from_directory(training_path,
target_size=(IMAGE_SIZE,IMAGE_SIZE),
batch_size=BATCH_SIZE,
class_mode='binary')
 validation_generator = ImageDataGenerator(rescale=1./255 ).flow_from_directory(validation_path,
target_size=(IMAGE_SIZE,IMAGE_SIZE),
batch_size=BATCH_SIZE,
class_mode='binary' )<choose_model_class>
def is_parallel(model):
return type(model)in(nn.parallel.DataParallel, nn.parallel.DistributedDataParallel)
 class ModelEMA:

 def __init__(self, model, decay=0.9999, updates=0):
self.ema = deepcopy(model.module if is_parallel(model)else model ).eval()
self.updates = updates
self.decay = lambda x: decay *(1 - math.exp(-x / 2000))
for p in self.ema.parameters() :
p.requires_grad_(False)
 def update(self, model):
with torch.no_grad() :
self.updates += 1
d = self.decay(self.updates)
 msd = model.module.state_dict() if is_parallel(model)else model.state_dict()
for k, v in self.ema.state_dict().items() :
if v.dtype.is_floating_point:
v *= d
v +=(1.- d)* msd[k].detach()
def update_attr(self, model, include=() , exclude=('process_group', 'reducer')) :
copy_attr(self.ema, model, include, exclude )
input_shape =(96, 96, 3)
 inputs = Input(input_shape)
 v16 = VGG16(include_top=False, input_shape=input_shape)
 v19 = VGG19(include_top=False, input_shape=input_shape)
 outputs = Concatenate(axis=-1 )([GlobalAveragePooling2D()(v16(inputs)) ,GlobalAveragePooling2D()(v19(inputs)) ])
 outputs = Dropout(0.5 )(outputs)
 outputs = Dense(1, activation='sigmoid' )(outputs)
 model = Model(inputs, outputs)
 model.compile(optimizer=Adam(lr=0.0001, decay=0.00001),
loss='binary_crossentropy',
metrics=['accuracy'])
 model.summary()<train_model>
warnings.filterwarnings("ignore")
 class Fitter:
def __init__(self, model, device, config):
self.config = config
self.epoch = 0
self.base_dir = f'./{config.folder}'
if not os.path.exists(self.base_dir):
os.makedirs(self.base_dir)
 self.log_path = f'{self.base_dir}/log.txt'
self.best_summary_loss = 10**5
self.model = model
self.device = device
self.use_amp = config.use_amp
self.accumulate_steps = config.accumulate_steps
self.clip_grad = config.clip_grad
self.max_norm = 10.0
self.use_ema = config.use_ema
self.model_ema = None
self.model_ema_decay = 0.999
pg0, pg1, pg2 = [], [], []
for k, v in self.model.named_parameters() :
if v.requires_grad:
if '.bias' in k:
pg2.append(v)
 elif '.weight' in k and '.bn' not in k:
pg1.append(v)
 else:
pg0.append(v)
 self.optimizer = torch.optim.SGD(pg0, lr=config.lr, momentum=0.9, nesterov=True)
 self.optimizer.add_param_group({'params': pg1, 'weight_decay': 5e-4})
 self.optimizer.add_param_group({'params': pg2})
 self.model.to(self.device)
 if self.use_amp:
self.model, self.optimizer = amp.initialize(self.model, self.optimizer, opt_level="O1",verbosity=0)
 if self.use_ema:
self.model_ema = ModelEMA(self.model, decay=self.model_ema_decay)
 self.scheduler = config.SchedulerClass(self.optimizer, **config.scheduler_params)
 self.log(f'Fitter prepared.Device is {self.device}')
 def fit(self, train_loader, validation_loader):
best_epoch = 0
for e in range(self.config.n_epochs):
if self.config.verbose:
lr = self.optimizer.param_groups[0]['lr']
timestamp = datetime.utcnow().isoformat()
self.log(f'
{timestamp}
LR: {lr}')
 t = time.time()
summary_loss = self.train_one_epoch(train_loader)
 self.log(f'[RESULT]: Train.Epoch: {self.epoch}, summary_loss: {summary_loss.avg:.5f}, time: {(time.time() - t):.5f}')
 self.save(f'{self.base_dir}/last-checkpoint.bin')
 t = time.time()
if self.use_ema:
summary_loss = self.validation(self.model_ema.ema.module if hasattr(self.model_ema.ema, 'module')else self.model_ema.ema,
validation_loader)
 else:
summary_loss = self.validation(self.model, validation_loader)
 self.log(f'[RESULT]: Val.Epoch: {self.epoch}, summary_loss: {summary_loss.avg:.5f}, time: {(time.time() - t):.5f}')
 if summary_loss.avg < self.best_summary_loss:
self.best_summary_loss = summary_loss.avg
self.model.eval()
self.save(f'{self.base_dir}/best-checkpoint-{str(self.epoch ).zfill(3)}epoch.bin')
 for path in sorted(glob(f'{self.base_dir}/best-checkpoint-*epoch.bin')) [:-3]:
os.remove(path)
 best_epoch = e
if self.config.validation_scheduler:
self.scheduler.step(e+1)
 self.epoch += 1
return best_epoch
def validation(self, model, val_loader):
model.eval()
summary_loss = AverageMeter()
t = time.time()
for step,(images, targets, image_ids)in enumerate(val_loader):
if self.config.verbose:
if step % self.config.verbose_step == 0:
print(
 f'Val Step {step}/{len(val_loader)}, ' + \
f'summary_loss: {summary_loss.avg:.5f}, ' + \
f'time: {(time.time() - t):.5f}', end='\r' 
)
 with torch.no_grad() :
images = torch.stack(images ).to(self.device ).float()
batch_size = images.shape[0]
target_input = {'bbox':[target['boxes'].to(self.device ).float() for target in targets],
'cls': [target['labels'].to(self.device ).float() for target in targets]}
loss = model(images, target_input['bbox'], target_input['cls'])
 loss, class_loss, box_loss = loss
summary_loss.update(loss.detach().item() , batch_size)
 return summary_loss
def train_one_epoch(self, train_loader):
self.model.train()
summary_loss = AverageMeter()
t = time.time()
self.optimizer.zero_grad()
for step,(images, targets, image_ids)in enumerate(train_loader):
if self.config.verbose:
if step % self.config.verbose_step == 0:
print(
 f'Train Step {step}/{len(train_loader)}, ' + \
f'summary_loss: {summary_loss.avg:.5f}, ' + \
f'time: {(time.time() - t):.5f}', end='\r' 
)
 images = torch.stack(images ).to(self.device ).float()
batch_size = images.shape[0]
target_input = {'bbox':[target['boxes'].to(self.device ).float() for target in targets],
'cls': [target['labels'].to(self.device ).float() for target in targets]}
loss = self.model(images, target_input['bbox'], target_input['cls'])
 loss, class_loss, box_loss = loss
if self.use_amp:
with amp.scale_loss(loss, self.optimizer)as scaled_loss:
scaled_loss.backward()
else:
loss.backward()
if(step+1)% self.accumulate_steps == 0:
if self.clip_grad:
if self.use_amp:
torch.nn.utils.clip_grad_norm_(amp.master_params(self.optimizer), self.max_norm)
 else:
torch.nn.utils.clip_grad_norm_(self.model.parameters() , self.max_norm)
 self.optimizer.step()
self.optimizer.zero_grad()
if self.model_ema is not None:
self.model_ema.update(self.model)
 summary_loss.update(loss.detach().item() , batch_size)
 if self.config.step_scheduler:
self.scheduler.step()
return summary_loss
def save(self, path):
self.model.eval()
if self.use_ema:
torch.save({
'model_state_dict': self.model.model.state_dict() ,
'state_dict_ema': self.model_ema.ema.module.state_dict() if hasattr(self.model_ema.ema, 'module')else self.model_ema.ema.model.state_dict() ,
'optimizer_state_dict': self.optimizer.state_dict() ,
'scheduler_state_dict': self.scheduler.state_dict() ,
'best_summary_loss': self.best_summary_loss,
'epoch': self.epoch,
}, path)
 else:
torch.save({
'model_state_dict': self.model.model.state_dict() ,
'optimizer_state_dict': self.optimizer.state_dict() ,
'scheduler_state_dict': self.scheduler.state_dict() ,
'best_summary_loss': self.best_summary_loss,
'epoch': self.epoch,
}, path)
 def load(self, path):
checkpoint = torch.load(path)
 self.model.model.load_state_dict(checkpoint['model_state_dict'])
 self.optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
 self.scheduler.load_state_dict(checkpoint['scheduler_state_dict'])
 self.best_summary_loss = checkpoint['best_summary_loss']
self.epoch = checkpoint['epoch'] + 1
def log(self, message):
if self.config.verbose:
print(message)
 with open(self.log_path, 'a+')as logger:
logger.write(f'{message}
' )
history = model.fit_generator(training_generator,
steps_per_epoch=len(training_generator),
validation_data=validation_generator,
validation_steps=len(validation_generator),
epochs=EPOCHS,
verbose=VERBOSITY,shuffle=True,
callbacks=[PlotLossesKeras() ,
ModelCheckpoint(MODEL_FILE,
monitor='val_acc',
verbose=VERBOSITY,
save_best_only=True,
mode='max'),
CSVLogger(TRAINING_LOGS_FILE,
append=False,
separator=';')])
 model.load_weights(MODEL_FILE )<define_variables>
def collate_fn(batch):
return tuple(zip(*batch))
def run_training() :
device = torch.device('cuda:0')
 net.to(device)
 train_loader = torch.utils.data.DataLoader(
 train_dataset,
batch_size=TrainGlobalConfig.batch_size,
sampler=RandomSampler(train_dataset),
pin_memory=False,
drop_last=True,
num_workers=TrainGlobalConfig.num_workers,
collate_fn=collate_fn, 
)
 val_loader = torch.utils.data.DataLoader(
 validation_dataset,
batch_size=TrainGlobalConfig.batch_size,
num_workers=TrainGlobalConfig.num_workers,
shuffle=False,
sampler=SequentialSampler(validation_dataset),
pin_memory=False,
collate_fn=collate_fn, 
)
 fitter = Fitter(model=net, device=device, config=TrainGlobalConfig)
 best_epoch = fitter.fit(train_loader, val_loader)
 return best_epoch
roc_validation_generator = ImageDataGenerator(rescale=1./255,shear_range=0.3,zoom_range=[1,1.5],
horizontal_flip=True,
rotation_range=10.,
width_shift_range = 0.3,
height_shift_range = 0.3 ).flow_from_directory(validation_path,target_size=(IMAGE_SIZE,IMAGE_SIZE),
batch_size=BATCH_SIZE,
class_mode='binary',
shuffle=False)
 predictions = model.predict_generator(roc_validation_generator, steps=len(roc_validation_generator), verbose=VERBOSITY)
 false_positive_rate, true_positive_rate, threshold = roc_curve(roc_validation_generator.classes, predictions)
 area_under_curve = auc(false_positive_rate, true_positive_rate)
 plt.plot([0, 1], [0, 1], 'k--')
 plt.plot(false_positive_rate, true_positive_rate, label='AUC = {:.3f}'.format(area_under_curve))
plt.xlabel('False positive rate')
 plt.ylabel('True positive rate')
 plt.title('ROC curve')
 plt.legend(loc='best')
 plt.savefig(ROC_PLOT_FILE, bbox_inches='tight')
 plt.show()<define_variables>
def get_net() :
config = get_efficientdet_config('tf_efficientdet_d7')
 net = EfficientDet(config, pretrained_backbone=False)
 config.num_classes = 1
config.image_size = 1024
net.class_net = HeadNet(config, num_outputs=config.num_classes, norm_kwargs=dict(eps=.001, momentum=.01))
checkpoint = torch.load(trained_weight_path)
 net.load_state_dict(checkpoint['state_dict_ema'])
 return DetBenchTrain(net, config)
 net = get_net()
tta_steps = 5
test_datagen = ImageDataGenerator(
 shear_range=0.1,
zoom_range=[1,1.5],
horizontal_flip=True,
rotation_range=10.,
fill_mode='reflect',
width_shift_range = 0.1,
height_shift_range = 0.1)
 testing_files = glob(os.path.join(INPUT_DIR+'test/','*.tif'))
submission = pd.DataFrame()
for index in range(0, len(testing_files), TESTING_BATCH_SIZE):
predicted_labels=[]
data_frame = pd.DataFrame({'path': testing_files[index:index+TESTING_BATCH_SIZE]})
 data_frame['id'] = data_frame.path.map(lambda x: x.split('/')[3].split(".")[0])
 data_frame['image'] = data_frame['path'].map(imread)
 if(index==57000):
TESTING_BATCH_SIZE=548;
images = np.stack(data_frame.image, axis=0)
 for i in tqdm(range(tta_steps)) :
preds = model.predict_generator(test_datagen.flow(images/255, batch_size=TESTING_BATCH_SIZE, shuffle=False), steps = len(images)/TESTING_BATCH_SIZE)
 predicted_labels.append(preds)
 predictions = np.max(predicted_labels,axis=0)
 data_frame['label'] = predictions
submission = pd.concat([submission, data_frame[["id", "label"]]])
 submission.to_csv(KAGGLE_SUBMISSION_FILE, index=False, header=True )<install_modules>
best_epoch = None
if len(os.listdir(".. /input/global-wheat-detection/test/")) > 11:
best_epoch = run_training()
! pip install albumentations
! pip install pytorchcv<set_options>
if best_epoch is not None:
print("load retrained model {}".format(best_epoch))
net = create_model_efficientdet_test(compound_coef=7,
weight_path=f'./effdet7/best-checkpoint-{str(best_epoch ).zfill(3)}epoch.bin',
image_size=1024,
model_ema=True)
 else:
net = create_model_efficientdet_test(compound_coef=7,
weight_path=trained_weight_path,
image_size=1024,
model_ema=True )
%matplotlib inline
train_on_gpu = True
cudnn.benchmark = True<feature_engineering>
class BaseWheatTTA:

 image_size = 1024
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 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)
 tta_transforms = []
for tta_combination in product([TTAHorizontalFlip() , None],
[TTAVerticalFlip() , None],
[TTARotate90() , None]):
tta_transforms.append(TTACompose([tta_transform for tta_transform in tta_combination if tta_transform]))
def make_tta_predictions(images, net, score_threshold=0.25):
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=1024, 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_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
SEED = 323
base_dir = '.. /input/'
def seed_everything(seed=SEED):
random.seed(seed)
 os.environ['PYHTONHASHSEED'] = str(seed)
 np.random.seed(seed)
 torch.manual_seed(seed)
 torch.cuda.manual_seed(seed)
 torch.backends.cudnn.deterministic = True
seed_everything(SEED )<load_from_csv>
def get_test_transforms() :
return A.Compose([
A.Resize(height=1024, width=1024, p=1.0),
ToTensorV2(p=1.0),
], p=1.0 )
labels = pd.read_csv(base_dir+'train_labels.csv' )<count_values>
test_dataset = DatasetRetrieverTest(
 image_ids=np.array([path.split('/')[-1][:-4] for path in glob(f'{DATA_ROOT_PATH}/*.jpg')]),
transforms=get_test_transforms() 
)
 def collate_fn(batch):
return tuple(zip(*batch))
test_data_loader = DataLoader(
 test_dataset,
batch_size=2,
shuffle=False,
num_workers=4,
drop_last=False,
collate_fn=collate_fn
)
labels.label.value_counts()<split>
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)
 results = []
for images, image_ids in test_data_loader:
predictions = make_tta_predictions(images, net=net)
 for i, image in enumerate(images):
boxes, scores, labels = run_wbf(predictions, image_index=i)
 boxes = boxes.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 )
<define_variables><EOS> 
test_df = pd.DataFrame(results, columns=['image_id', 'PredictionString'])
 test_df.to_csv('submission.csv', index=False)
 test_df.head()