Kaggle competition: Quick, Draw! Doodle Recognition Challenge
<SOS> metric: MAP@{K} Kaggle data source: quick,-draw!-doodle-recognition-challenge<data_type_conversions>
warnings.filterwarnings('ignore')
 np.random.seed(60794)
 random.seed(60794)
 %matplotlib inline
%config InlineBackend.figure_format = 'retina'
def get_labels_from(data_df):
labels = data_df['label'].to_numpy(dtype=np.int32)
 return labels<prepare_x_and_y>
torch.manual_seed(60794)
 torch.backends.cudnn.deterministic = True
X = get_images_from(train_df)
 y = get_labels_from(train_df)
 print(X.shape)
 print(y.shape )<normalization>
__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101',
'resnet152']
model_urls = {
'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
}
def swish(x):
return torch.sigmoid(x)* x
def conv3x3(in_planes, out_planes, stride=1):

 return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=1, bias=False)
 def conv1x1(in_planes, out_planes, stride=1):

 return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
 class BasicBlock(nn.Module):
expansion = 1
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(BasicBlock, self ).__init__()
self.conv1 = conv3x3(inplanes, planes, stride)
 self.bn1 = nn.BatchNorm2d(planes)
 self.relu = nn.ReLU(inplace=True)
 self.conv2 = conv3x3(planes, planes)
 self.bn2 = nn.BatchNorm2d(planes)
 self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = self.conv1(x)
 out = self.bn1(out)
 out = self.relu(out)
 out = self.conv2(out)
 out = self.bn2(out)
 if self.downsample is not None:
residual = self.downsample(x)
 out += residual
out = self.relu(out)
 return out
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(Bottleneck, self ).__init__()
self.conv1 = conv1x1(inplanes, planes)
 self.bn1 = nn.BatchNorm2d(planes)
 self.conv2 = conv3x3(planes, planes, stride)
 self.bn2 = nn.BatchNorm2d(planes)
 self.conv3 = conv1x1(planes, planes * self.expansion)
 self.bn3 = nn.BatchNorm2d(planes * self.expansion)
 self.relu = nn.ReLU(inplace=True)
 self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = self.conv1(x)
 out = self.bn1(out)
 out = swish(out)
 out = self.conv2(out)
 out = self.bn2(out)
 out = swish(out)
 out = self.conv3(out)
 out = self.bn3(out)
 if self.downsample is not None:
residual = self.downsample(x)
 out += residual
out = swish(out)
 return out
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=1000):
super(ResNet, self ).__init__()
self.inplanes = 64
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
bias=False)
 self.bn1 = nn.BatchNorm2d(64)
 self.relu = nn.ReLU(inplace=True)
 self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
 self.layer1 = self._make_layer(block, 64, layers[0])
 self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
 self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
 self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
 self.avgpool = nn.AdaptiveAvgPool2d(( 1, 1))
self.fc = nn.Linear(512 * block.expansion, num_classes)
 for m in self.modules() :
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
 elif isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1)
 nn.init.constant_(m.bias, 0)
 def _make_layer(self, block, planes, blocks, stride=1):
downsample = None
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
 conv1x1(self.inplanes, planes * block.expansion, stride),
nn.BatchNorm2d(planes * block.expansion), 
)
 layers = []
layers.append(block(self.inplanes, planes, stride, downsample))
self.inplanes = planes * block.expansion
for _ in range(1, blocks):
layers.append(block(self.inplanes, planes))
return nn.Sequential(*layers)
 def forward(self, x):
x = self.conv1(x)
 x = self.bn1(x)
 x = swish(x)
 x = self.maxpool(x)
 x = self.layer1(x)
 x = self.layer2(x)
 x = self.layer3(x)
 x = self.layer4(x)
 x = self.avgpool(x)
 x = x.view(x.size(0), -1)
 x = self.fc(x)
 return x
def resnet18(pretrained=False, **kwargs):

 model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
 if pretrained:
model.load_state_dict(model_zoo.load_url(model_urls['resnet18']))
return model
RESIZE_SHAPE =(*RESIZE_SIZE, 1)
 def resize_image(orig_np):
reshape_np = np.reshape(orig_np, IMAGE_SIZE)
 orig_im = Image.fromarray(reshape_np)
 resized_im = orig_im.resize(RESIZE_SIZE, Image.LANCZOS)
 resized_np = np.asarray(resized_im, dtype=np.uint8)
 resized_reshaped_np = np.reshape(resized_np, RESIZE_SHAPE)
 return resized_reshaped_np<normalization>

 def save_checkpoint(checkpoint_path, model, optimizer):
state = {'state_dict': model.state_dict() ,
'optimizer' : optimizer.state_dict() }
torch.save(state, checkpoint_path)
 print('model saved to %s' % checkpoint_path)
 def load_checkpoint(checkpoint_path, model, optimizer):
state = torch.load(checkpoint_path)
 model.load_state_dict(state['state_dict'])
 optimizer.load_state_dict(state['optimizer'])
 print('model loaded from %s' % checkpoint_path )
def resize_images(orig_nps):
n_images = orig_nps.shape[0]
resized_shape =(n_images, *RESIZE_SHAPE)
 resized_nps = np.empty(resized_shape, dtype=np.uint8)
 for i in range(n_images):
if i % 100 == 0:
print('.', end='', flush=True)
 x_np = orig_nps[i]
resized_nps[i] = resize_image(x_np)
 print()
return resized_nps<normalization>
en_dict = {}
path = '.. /input/quickdraw-doodle-recognition/train_simplified/'
filenames = glob.glob(os.path.join(path, '*.csv'))
filenames = sorted(filenames)
 def encode_files() :

 counter = 0
for fn in filenames:
en_dict[fn[:-4].split('/')[-1].replace(' ', '_')] = counter
counter += 1
encode_files()
dec_dict = {v: k for k, v in en_dict.items() }
def decode_labels(label):
return dec_dict[label]
def get_label(nfile):

 return en_dict[nfile.replace(' ', '_')[:-4]]
def get_mat(rotation, shear, height_zoom, width_zoom, height_shift, width_shift):
rotation = math.pi * rotation / 180.
 shear = math.pi * shear / 180.
 c1 = tf.math.cos(rotation)
 s1 = tf.math.sin(rotation)
 one = tf.constant([1],dtype='float32')
 zero = tf.constant([0],dtype='float32')
 rotation_matrix = tf.reshape(tf.concat([c1,s1,zero, -s1,c1,zero, zero,zero,one],axis=0),[3,3])
 c2 = tf.math.cos(shear)
 s2 = tf.math.sin(shear)
 shear_matrix = tf.reshape(tf.concat([one,s2,zero, zero,c2,zero, zero,zero,one],axis=0),[3,3])
 zoom_matrix = tf.reshape(tf.concat([one/height_zoom,zero,zero, zero,one/width_zoom,zero, zero,zero,one],axis=0),[3,3])
 shift_matrix = tf.reshape(tf.concat([one,zero,height_shift, zero,one,width_shift, zero,zero,one],axis=0),[3,3])
 return K.dot(K.dot(rotation_matrix, shear_matrix), K.dot(zoom_matrix, shift_matrix))<define_search_space>
class DoodlesDataset(Dataset):

 def __init__(self, csv_file, root_dir, mode='train', nrows=1000, skiprows=None, size=256, transform=None):

 self.root_dir = root_dir
file = os.path.join(self.root_dir, csv_file)
 self.size = size
self.mode = mode
self.doodle = pd.read_csv(file, usecols=['drawing'], nrows=nrows, skiprows=skiprows)
 self.transform = transform
if self.mode == 'train':
self.label = get_label(csv_file)
 @staticmethod
def _draw(raw_strokes, size=256, lw=6, time_color=True):
BASE_SIZE = 256
img = np.zeros(( BASE_SIZE, BASE_SIZE), np.uint8)
 for t, stroke in enumerate(raw_strokes):
for i in range(len(stroke[0])- 1):
color = 255 - min(t, 10)* 13 if time_color else 255
_ = cv2.line(img,(stroke[0][i], stroke[1][i]), 
(stroke[0][i + 1], stroke[1][i + 1]), color, lw)
 if size != BASE_SIZE:
return cv2.resize(img,(size, size))
else:
return img
def __len__(self):
return len(self.doodle)
 def __getitem__(self, idx):
raw_strokes = ast.literal_eval(self.doodle.drawing[idx])
 sample = self._draw(raw_strokes, size=self.size, lw=6, time_color=True)
 if self.transform:
sample = self.transform(sample)
 if self.mode == 'train':
return(sample[None]/255 ).astype('float32'), self.label
else:
return(sample[None]/255 ).astype('float32' )
def do_transform(image,label):
DIM = RESIZE_SIZE[0]
XDIM = DIM%2
rot = 10.* tf.random.normal([1],dtype='float32')
 shr = 5.* tf.random.normal([1],dtype='float32')
 h_zoom = 1.0 + tf.random.normal([1],dtype='float32')/10.
 w_zoom = 1.0 + tf.random.normal([1],dtype='float32')/10.
 h_shift = DIM * 0.05 * tf.random.normal([1],dtype='float32')
 w_shift = DIM * 0.05 * tf.random.normal([1],dtype='float32')
 m = get_mat(rot,shr,h_zoom,w_zoom,h_shift,w_shift)
 x = tf.repeat(tf.range(DIM//2,-DIM//2,-1), DIM)
 y = tf.tile(tf.range(-DIM//2,DIM//2),[DIM])
 z = tf.ones([DIM*DIM],dtype='int32')
 idx = tf.stack([x,y,z])
 idx2 = K.dot(m,tf.cast(idx,dtype='float32'))
idx2 = K.cast(idx2,dtype='int32')
 idx2 = K.clip(idx2,-DIM//2+XDIM+1,DIM//2)
 idx3 = tf.stack([DIM//2-idx2[0,], DIM//2-1+idx2[1,]])
 d = tf.gather_nd(image,tf.transpose(idx3))
return tf.reshape(d,[DIM,DIM,1]),label<define_search_space>
SIZE = 224
select_nrows = 8000
doodle_all = ConcatDataset([DoodlesDataset(fn.split('/')[-1], path,
skiprows=list(range(1,78000)) ,
nrows=select_nrows, size=SIZE)for fn in filenames] )
def randints(shape, minval, maxval):
return tf.random.uniform(
 shape=shape, minval=minval, maxval=maxval+1, dtype=tf.int32 )<categorify>
print('Train set:', len(doodle_all))
loader = DataLoader(doodle_all, batch_size=128, shuffle=True, num_workers=0)
def make_range_mask(size, start, end):
indice = tf.range(size, dtype=tf.int32)
 start_mask =(start <= indice)
 end_mask =(indice <= end)
 range_mask = start_mask & end_mask
return range_mask
def make_region_mask(image_height, image_width, top, left, bottom, right):
row_mask = make_range_mask(image_height, top, bottom)
 col_mask = make_range_mask(image_width, left, right)
 region_mask = row_mask[..., tf.newaxis] & col_mask[ tf.newaxis,...]
reshaped_region_mask = region_mask[..., tf.newaxis]
return reshaped_region_mask<categorify>
def validation(lossf, scoref):
model.eval()
loss, score = 0, 0
vlen = len(valid_loader)
 for x, y in valid_loader:
x, y = x.to(device), y.to(device)
 output = model(x)
 loss += lossf(output, y ).item()
score += scoref(output, y)[0].item()
model.train()
return loss/vlen, score/vlen
def do_cutout(orig_image, label):
mask_ratio = 0.5
image_shape = tf.shape(orig_image)
 image_height = image_shape[0]
image_width = image_shape[1]
mask_h = tf.cast(tf.cast(image_height, tf.float32)* mask_ratio, tf.int32)
 mask_w = tf.cast(tf.cast(image_width, tf.float32)* mask_ratio, tf.int32)
 mask_value = 0.0
top = randints([], -mask_h // 2, image_height - mask_h // 2)
 left = randints([], -mask_w // 2, image_width - mask_w // 2)
 bottom = top + mask_h
right = left + mask_w
cut_region = make_region_mask(
 image_height, image_width, top, left, bottom, right)
 cutout_image = tf.where(cut_region, mask_value, orig_image)
 return cutout_image, label<count_unique_values>
def accuracy(output, target, topk=(3,)) :

 with torch.no_grad() :
maxk = max(topk)
 batch_size = target.size(0)
 _, pred = output.topk(maxk, 1, True, True)
 pred = pred.t()
correct = pred.eq(target.view(1, -1 ).expand_as(pred))
res = []
for k in topk:
correct_k = correct[:k].view(-1 ).float().sum(0, keepdim=True)
 res.append(correct_k.mul_(100.0 / batch_size))
return res
unique_y = np.unique(y)
 label_count = len(unique_y)
 print(unique_y)
 print(label_count )<categorify>
def mapk(output, target, k=3):

 with torch.no_grad() :
batch_size = target.size(0)
 _, pred = output.topk(k, 1, True, True)
 pred = pred.t()
correct = pred.eq(target.view(1, -1 ).expand_as(pred))
for i in range(k):
correct[i] = correct[i]*(k-i)
 score = correct[:k].view(-1 ).float().sum(0, keepdim=True)
 score.mul_(1.0 /(k * batch_size))
return score
AUTO = tf.data.experimental.AUTOTUNE
def make_dataset(
 X_np, y_np,
transform=False, cutout=False, repeat=False, shuffle=False):
def _cast_to_float(x, y):
return tf.cast(x, tf.float32), y
ds = tf.data.Dataset.from_tensor_slices(( X_np, y_np))
ds = ds.map(_cast_to_float, num_parallel_calls=AUTO)
 if shuffle:
ds = ds.shuffle(2048)
 if transform:
ds = ds.map(do_transform, num_parallel_calls=AUTO)
 if cutout:
ds = ds.map(do_cutout, num_parallel_calls=AUTO)
 ds = ds.batch(BATCH_SIZE)
 if repeat:
ds = ds.repeat()
return ds<prepare_x_and_y>
model = resnet18(pretrained=True)
def make_train_ds(X_np, y_np):
ds = make_dataset(
 X_np, y_np, transform=True, cutout=True, repeat=True, shuffle=True)
 return ds
def make_val_ds(X_np, y_np):
ds = make_dataset(
 X_np, y_np, transform=False, cutout=False, repeat=False, shuffle=False)
 return ds
def make_test_ds(X_np):
y_np = np.zeros(X_np.shape[0], dtype=np.int32)
 ds = make_dataset(
 X_np, y_np, transform=True, cutout=False, repeat=False, shuffle=False)
 return ds<split>
def squeeze_weights(m):
m.weight.data = m.weight.data.sum(dim=1)[:,None]
m.in_channels = 1
model.conv1.apply(squeeze_weights);
num_classes = 340
model.fc = nn.Linear(in_features=512, out_features=num_classes, bias=True )
NFOLD = 5
k_fold = StratifiedKFold(n_splits=NFOLD)
 fold_index_list = []
for train_index, val_index in k_fold.split(X_resized, y):
fold_index_list.append(( train_index, val_index))
def get_fold(fold_i):
train_index, val_index = fold_index_list[fold_i]
X_train, y_train = X_resized[train_index], y[train_index]
X_val, y_val = X_resized[val_index], y[val_index]
train_ds = make_train_ds(X_train, y_train)
 val_ds = make_val_ds(X_val, y_val)
 return train_ds, val_ds<define_variables>
%%time
model(torch.randn(12,1,224,224)).size()
def calc_steps_per_epoch(fold_i):
train_index, val_index = fold_index_list[fold_i]
steps_per_epoch =(len(train_index)+ BATCH_SIZE - 1)// BATCH_SIZE
return steps_per_epoch<choose_model_class>
device = 'cuda'
model.to(device);
MODEL_INPUT_SHAPE =(*RESIZE_SIZE, 3)
 def make_model(name):
with strategy.scope() :
inputs = L.Input(shape=RESIZE_SHAPE, name="input")
 scaled = L.Lambda(lambda v: v / 255.0, name='scaling' )(inputs)
 img_input = L.Concatenate(name='concat' )([scaled, scaled, scaled])
 x = ResNet50(
 include_top=False, weights='imagenet',
input_shape=MODEL_INPUT_SHAPE, pooling='avg' )(img_input)
 x = L.Dropout(DROP_RATE, name="dropout" )(x)
 outputs = L.Dense(label_count, activation='sigmoid', name='classify' )(x)
 model = M.Model(inputs=inputs, outputs=outputs, name=name)
 model.compile(
 optimizer='adam',
loss="sparse_categorical_crossentropy",
metrics=['accuracy'])
 return model<init_hyperparams>
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters() , lr=0.001, amsgrad=True )
LR_START = 0.00001
LR_MAX = 0.00005 * strategy.num_replicas_in_sync
LR_MIN = 0.00001
LR_RAMPUP_EPOCHS = min(5, EPOCHS // 5)
 LR_SUSTAIN_EPOCHS = 0
def lrfn(epoch):
if epoch < LR_RAMPUP_EPOCHS:
lr =(LR_MAX - LR_START)/ LR_RAMPUP_EPOCHS * epoch + LR_START
elif epoch < LR_RAMPUP_EPOCHS + LR_SUSTAIN_EPOCHS:
lr = LR_MAX
else:
decay_total_epochs = EPOCHS - LR_RAMPUP_EPOCHS - LR_SUSTAIN_EPOCHS
decay_epoch_index = epoch - LR_RAMPUP_EPOCHS - LR_SUSTAIN_EPOCHS
phase = math.pi * decay_epoch_index / decay_total_epochs
cosine_decay = 0.5 *(1 + math.cos(phase))
lr =(LR_MAX - LR_MIN)* cosine_decay + LR_MIN
return lr
def make_lr_callback() :
lr_callback = tf.keras.callbacks.LearningRateScheduler(lrfn, verbose = False)
 return lr_callback
rng = [i for i in range(EPOCHS)]
lr_y = [lrfn(x)for x in rng]
plt.figure(figsize=(10, 4))
plt.plot(rng, lr_y)
 print("Learning rate schedule: {:.3g} to {:.3g} to {:.3g}".\
format(lr_y[0], max(lr_y), lr_y[-1]))<define_variables>
load_checkpoint(".. /input/fold-two-doodler5/doodle-fold-two-2.pth", model, optimizer )
def make_best_model_file_path(fold_i):
file_name = "best_model_{0}_{1}.hdf5".format(MODEL_VERSION, fold_i)
 return file_name<find_best_params>
scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[5000,12000,18000], gamma=0.5 )
def make_model_check_point(fold_i):
best_model_file_path = make_best_model_file_path(fold_i)
 return ModelCheckpoint(
 best_model_file_path, monitor='val_accuracy', mode='max',
verbose=0, save_best_only=True, save_weights_only=False, period=1 )<load_pretrained>
%%time
epochs = 1
lsize = len(loader)
 itr = 1
p_itr = 1000
model.train()
tloss, score = 0, 0
for epoch in range(epochs):
for x, y in tqdm.tqdm(loader):
x, y = x.to(device), y.to(device)
 optimizer.zero_grad()
output = model(x)
 loss = criterion(output, y)
 loss.backward()
optimizer.step()
tloss += loss.item()
score += mapk(output, y)[0].item()
scheduler.step()
if itr%p_itr==0:
print('Iteration {} -> Train Loss: {:.4f}, MAP@3: {:.3f}'.format(itr, tloss/p_itr, score/p_itr))
tloss, score = 0, 0
itr +=1
itr = itr%p_itr
print('Final -> Loss: {:.4f}, MAP@3: {:.3f}'.format(loss/itr, score/itr))
save_checkpoint('doodle-fold-two-%i.pth' % epochs, model, optimizer )
initial_weights_file_path = "initial_weights.hdf5"
model.save_weights(initial_weights_file_path )<train_model>
testset = DoodlesDataset('test_simplified.csv', '.. /input/quickdraw-doodle-recognition', mode='test', nrows=None, size=SIZE)
 testloader = DataLoader(testset, batch_size=128, shuffle=False, num_workers=0 )
history_list = []
for fold_i in range(NFOLD):
print("
print("
train_ds, val_ds = get_fold(fold_i)
 steps_per_epoch = calc_steps_per_epoch(fold_i)
 lr_callback = make_lr_callback()
check_point = make_model_check_point(fold_i)
 model.load_weights(initial_weights_file_path)
 history = model.fit(
 train_ds, epochs=EPOCHS, steps_per_epoch=steps_per_epoch,
validation_data=val_ds,
callbacks=[lr_callback, check_point], verbose=0)
 history_list.append(history)
 best_acc = max(history.history['accuracy'])
 best_val_acc = max(history.history['val_accuracy'])
 print(
 "Fold {0}: accuracy={1:.5f}, val_accuracy={2:.5f}".format(
 fold_i, best_acc, best_val_acc))<load_from_csv>
model.eval()
labels = np.empty(( 0,3))
for x in tqdm.tqdm(testloader):
x = x.to(device)
 output = model(x)
 _, pred = output.topk(3, 1, True, True)
 labels = np.concatenate([labels, pred], axis = 0 )
test_df = read_df('test.csv')
 test_df<define_variables>
%%time
submission = pd.read_csv('.. /input/quickdraw-doodle-recognition/test_simplified.csv', index_col='key_id')
 submission.drop(['countrycode', 'drawing'], axis=1, inplace=True)
 submission['word'] = ''
for i, label in enumerate(labels):
submission.word.iloc[i] = " ".join([decode_labels(l)for l in label] )
def make_val_pred_file_path(fold_i):
file_name = "val_pred_{0}_{1}".format(MODEL_VERSION, fold_i)
 return file_name<load_pretrained>
submission.to_csv('preds_resnet18_foldone.csv' )