import os
import pickle
import random
import sklearn
import numpy as np
import pandas as pd
import tensorflow as tf
import matplotlib.pyplot as plt
from tqdm.notebook import *
from sklearn.neighbors import *
from sklearn.decomposition import *
from sklearn.model_selection import *
try:
import farmhash
import keras_cv_attention_models
import tensorflow_addons as tfa
except:
!pip install -qq cityhash
!pip install -qq tensorflow-addons
!pip install -qq keras-cv-attention-models
import farmhash
import keras_cv_attention_models
import tensorflow_addons as tfa
try:
tpu = tf.distribute.cluster_resolver.TPUClusterResolver() # TPU detection
except ValueError:
tpu = None
gpus = tf.config.experimental.list_logical_devices("GPU")
if tpu:
tf.config.experimental_connect_to_cluster(tpu)
tf.tpu.experimental.initialize_tpu_system(tpu)
strategy = tf.distribute.experimental.TPUStrategy(tpu)
tf.config.set_soft_device_placement(True)
print('Running on TPU ', tpu.master())
elif len(gpus) > 0:
strategy = tf.distribute.MirroredStrategy(gpus)
print('Running on ', len(gpus), ' GPU(s) ')
else:
strategy = tf.distribute.get_strategy()
print('Running on CPU')
print("Number of accelerators: ", strategy.num_replicas_in_sync)
AUTO = tf.data.experimental.AUTOTUNE
config = {
"seed": 1213,
"lr": 1e-5,
"epochs": 10,
"batch_size": 32 * strategy.num_replicas_in_sync,
"n_classes": 1000,
"image_size": [224, 224, 3],
"hashLength": 2048,
"data_paths": ['gs://kds-3f5ce2e56559d17c577c6d9f0c1acd2179da3bc0c1f87ae24d66f478', 'gs://kds-6d0cfb649153417b33f05a9913c59a358a3c03706a8fd6c5f523b8fc', 'gs://kds-0a7e8b42af254bd7711b5670f5aeb0eeda8d169841722ea59680d3d4', 'gs://kds-d27f1d2f32cff13b0ce97a6b912eb44a68e8ee3128504dcb52e23a3e', 'gs://kds-7a36360336e83a60d74c0196158fed9d6fa70cd342dc96383b7f6e2a', 'gs://kds-f99762a40174159b2d5b1caedff9d3e42758db9134c67405c68ce52d', 'gs://kds-af8ef10c715e13cb364f2655d5188aedfbb874b71e250d2773dade89', 'gs://kds-b0b80433ab5beabf70aca68659672322018f637039ae27fbd9ee9bf1'],
"save_path": "./",
"backbones": ["EfficientNetV2S", "beit.BeitV2BasePatch16"] #["EfficientNetV2M", "beit.BeitV2BasePatch16", "davit.DaViT_S"]
}
def seed_everything(seed):
random.seed(seed)
np.random.seed(seed)
tf.random.set_seed(seed)
seed_everything(config["seed"])
def train_transform(image, label):
image = tf.expand_dims(image, axis=0)
if (tf.random.uniform([1]) < 0.5):
image = tf.image.random_flip_left_right(image)
if (tf.random.uniform([1]) < 0.2):
image = tf.image.random_brightness(image, 0.08)
if (tf.random.uniform([1]) < 0.3):
image = tfa.image.rotate(image, 1.0*tf.random.uniform([1]) - 0.5)
return tf.squeeze(image, axis=0), label
def margin_format(image, label):
return {'image': image, 'label': label}, label
def onehot(data, label):
label = tf.one_hot(label, tf.constant(config["n_classes"]), axis =-1, dtype=tf.float32)
return data, label
def decode_image(image_data):
image = tf.image.decode_jpeg(image_data, channels = 3)
image = tf.image.resize_with_pad(image, target_width = config["image_size"][0], target_height = config["image_size"][1])
return image
def read_tfrecord(example):
TFREC_FORMAT = {
"image": tf.io.FixedLenFeature([], tf.string),
'label': tf.io.FixedLenFeature([], tf.int64),
}
example = tf.io.parse_single_example(example, TFREC_FORMAT)
image = decode_image(example['image'])
label = tf.cast(example['label'], dtype = tf.int64)
return image, label
def load_dataset(filenames, ordered):
ignore_order = tf.data.Options()
if not ordered:
ignore_order.experimental_deterministic = False
dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads = AUTO)
dataset = dataset.with_options(ignore_order)
dataset = dataset.map(read_tfrecord, num_parallel_calls = AUTO)
return dataset
def get_train_dataset(filenames):
dataset = load_dataset(filenames, ordered = False).repeat().shuffle(config["seed"])
dataset = dataset.map(train_transform, num_parallel_calls = AUTO)
dataset = dataset.map(onehot, num_parallel_calls = AUTO)
dataset = dataset.map(margin_format, num_parallel_calls = AUTO)
dataset = dataset.batch(config["batch_size"])
dataset = dataset.prefetch(AUTO)
return dataset
def get_valid_dataset(filenames):
dataset = load_dataset(filenames, ordered = True)
dataset = dataset.map(onehot, num_parallel_calls = AUTO)
dataset = dataset.map(margin_format, num_parallel_calls = AUTO)
dataset = dataset.batch(config["batch_size"])
dataset = dataset.prefetch(AUTO)
return dataset
def get_test_dataset(filenames):
dataset = load_dataset(filenames, ordered = True)
dataset = dataset.map(margin_format, num_parallel_calls = AUTO)
dataset = dataset.batch(config["batch_size"])
dataset = dataset.prefetch(AUTO)
return dataset
class Margin(tf.keras.layers.Layer):
def __init__(self, num_classes, margin = 0.1, scale=32, **kwargs):
super().__init__(**kwargs)
self.scale = scale
self.margin = margin
self.num_classes = num_classes
def build(self, input_shape):
self.W = self.add_weight(shape=(self.num_classes, input_shape[0][-1]), initializer='glorot_uniform', trainable=True)
def build_hash(self, x, scale = 10.0):
return tf.nn.sigmoid(scale*tf.nn.l2_normalize(x, axis = 1))
def hamming_distance(self, feature):
x = self.build_hash(feature, self.scale)
w = self.build_hash(self.W, self.scale)
x = tf.tile(tf.expand_dims(feature, 2), [1, 1, self.W.shape[0]])
w = tf.transpose(self.W)
return tf.clip_by_value(tf.reduce_sum(tf.math.abs(x - w), axis = 1), 1e-4, 48)
def logits_hamming(self, feature, labels):
distance = self.hamming_distance(feature)
mr = tf.random.normal(shape = tf.shape(distance), mean = self.margin, stddev = 0.1*self.margin)
distance_add = distance + mr
mask = tf.cast(labels, dtype=distance.dtype)
logits = self.scale/(mask*distance_add + (1-mask)*distance)
return logits
def distance(self, feature):
x = tf.nn.l2_normalize(feature, axis=1)
w = tf.nn.l2_normalize(self.W, axis=1)
x = tf.tile(tf.expand_dims(x, 2), [1, 1, self.W.shape[0]])
w = tf.transpose(w)
return tf.reduce_sum( tf.math.pow( x - w, 2 ), axis=1)
def logits_distance(self, feature, labels):
distance = self.distance(feature)
mr = tf.random.normal(shape = tf.shape(distance), mean = self.margin, stddev = 0.1*self.margin)
distance_add = distance + mr
mask = tf.cast(labels, dtype=distance.dtype)
logits = self.scale/(mask*distance_add + (1-mask)*distance)
return logits
def cosine(self, feature):
x = tf.nn.l2_normalize(feature, axis=1)
w = tf.nn.l2_normalize(self.W, axis=1)
cos = tf.matmul(x, tf.transpose(w))
return cos
def logits_cosine(self, feature, labels):
cosine = self.cosine(feature)
mr = tf.random.normal(shape = tf.shape(cosine), mean = self.margin, stddev = 0.1*self.margin)
theta = tf.acos(tf.clip_by_value(cosine, -1, 1))
cosine_add = tf.math.cos(theta + mr)
mask = tf.cast(labels, dtype=cosine.dtype)
logits = mask*cosine_add + (1-mask)*cosine
return logits*self.scale
def call(self, inputs, training):
feature, labels = inputs
if training:
logits = self.logits_cosine(feature, labels)
else:
logits = self.cosine(feature)
return logits
def get_config(self):
config = super().get_config().copy()
config.update({
'scale': self.scale,
'margin': self.margin,
'num_classes': self.num_classes,
})
return config
def get_backbone(backbone_name, x):
if hasattr(tf.keras.applications, backbone_name):
headModel = tf.keras.layers.Lambda(lambda data: tf.keras.applications.imagenet_utils.preprocess_input(tf.cast(data, tf.float32), mode="tf"))(x)
return tf.keras.layers.GlobalAveragePooling2D()(getattr(tf.keras.applications, backbone_name)(weights = "imagenet", include_top = False)(headModel))
else:
backbone = getattr(getattr(keras_cv_attention_models, backbone_name.split(".")[0]), backbone_name.split(".")[1])(num_classes=0)
headModel = tf.keras.layers.Lambda(lambda data: tf.keras.applications.imagenet_utils.preprocess_input(tf.cast(data, tf.float32), mode="torch"))(x)
backbone.trainable = True
if "beit" in backbone_name:
return backbone(headModel)
return tf.keras.layers.GlobalAveragePooling2D()(backbone(headModel))
def model_factory(backbones, n_classes):
image = tf.keras.layers.Input(shape = (None, None, 3), dtype=tf.uint8, name = 'image')
label = tf.keras.layers.Input(shape = (), name = 'label', dtype = tf.int64)
features = [get_backbone(backbone, image) for backbone in backbones]
headModel = tf.keras.layers.Concatenate(name = "concat")(features)
headModel = tf.keras.layers.Dense(config["hashLength"], activation = "linear", name = "feature")(headModel)
margin = Margin(num_classes = n_classes, name = "margin")([headModel, label])
output = tf.keras.layers.Softmax(dtype=tf.float32, name = "output")(margin)
model = tf.keras.models.Model(inputs = [image, label], outputs = [output])
return model
%%writefile generate_submit_csv.py
import argparse
import numpy as np
parser = argparse.ArgumentParser()
parser.add_argument('--query', type=str, default='submit_query.csv', help='48-bit hash code file for query set.')
parser.add_argument('--gallery', type=str, default='submit_gallery.csv', help='48-bit hash code file for gallery set.')
parser.add_argument('--submit', type=str, default='submit.csv', help='Final submit csv file.')
parser.add_argument('--k', type=int, default=100, help='Topk == 100.')
args = parser.parse_args()
k = args.k
query_code_path = args.query
gallery_code_path = args.gallery
submit_path = args.submit
query_code = []
query_images = []
with open(query_code_path, 'r') as f:
f.readline()
for line in f:
image, code = line[:-1].split(',')
code = [-1. if i == 0 else 1. for i in list(map(float, list(code[1:-1])))]
query_code.append(code)
query_images.append(image)
query_code = np.array(query_code)
gallery_code = []
gallery_images = []
with open(gallery_code_path, 'r') as f:
f.readline()
for line in f:
image, code = line[:-1].split(',')
code = [-1. if i == 0 else 1. for i in list(map(float, list(code[1:-1])))]
gallery_code.append(code)
gallery_images.append(image[:-4])
gallery_code = np.array(gallery_code)
with open(submit_path, 'w') as f:
f.write('Id,Predicted\n')
for i, q in enumerate(query_code):
hamming_dist = q.shape[0] - np.dot(q, gallery_code.T)
index = np.argsort(hamming_dist)[:k]
retrieval_images = [gallery_images[j] for j in index]
f.write(query_images[i] + ',')
f.write(' '.join(retrieval_images) + '\n')
print(f'\r writing {i + 1}/{query_code.shape[0]}', end='')
class Evaluation(tf.keras.callbacks.Callback):
def __init__(self, g_data, q_data, v_g_data):
self.g_data = g_data
self.q_data = q_data
self.v_g_data = v_g_data
def bitdigest(self, digest):
return [str(x) for i, x in enumerate(digest.tolist())]
def to_id(self, ids):
return [str(x) + ".jpg" for x in ids]
def process_vgallery(self, model, epoch, steps = None):
feature, vg_id = model.predict(self.v_g_data, verbose = 1, steps = steps)
feature = np.array(tf.nn.l2_normalize(feature, axis=1)).astype(np.float16)
return feature, vg_id
# vg_id.tofile(f"{config['save_path']}VGid_{epoch}")
# np.array(feature).astype(np.float16).tofile(f"{config['save_path']}VGfeature_{epoch}")
def process_gallery(self, model, epoch, steps = None):
feature, g_id = model.predict(self.g_data, verbose = 1, steps = steps)
feature = np.array(tf.nn.l2_normalize(feature, axis=1)).astype(np.float16)
return feature, g_id
# g_id.tofile(f"{config['save_path']}Gid_{epoch}")
# np.array(feature).astype(np.float16).tofile(f"{config['save_path']}Gfeature_{epoch}")
def process_query(self, model, epoch, steps = None):
feature, q_id = model.predict(self.q_data, verbose = 1, steps = steps)
feature = np.array(tf.nn.l2_normalize(feature, axis=1)).astype(np.float16)
return feature, q_id
# q_id.tofile(f"{config['save_path']}Qid_{epoch}")
# np.array(feature).astype(np.float16).tofile(f"{config['save_path']}Qfeature_{epoch}")
def gen_sub(self, epoch):
return os.popen(f"python3 generate_submit_csv.py --gallery {config['save_path']}G_{epoch}.csv --query {config['save_path']}Q_{epoch}.csv --submit {config['save_path']}S_{epoch}.csv").read()
def on_epoch_end(self, epoch, logs={}):
model = tf.keras.models.Model(inputs = self.model.inputs,
outputs = [self.model.get_layer('feature').output, self.model.inputs[1]])
with open(f"{config['save_path']}data_{epoch}", 'wb') as handle:
pickle.dump([self.process_query(model, epoch), self.process_gallery(model, epoch), self.process_vgallery(model, epoch)], handle, protocol=pickle.HIGHEST_PROTOCOL)
# self.gen_sub(epoch)
class SaveModel(tf.keras.callbacks.Callback):
def __init__(self, path):
self.path = path
def on_epoch_end(self, epoch, logs={}):
pass
# self.model.save(self.path + "model.keras")
DATA_FILENAMES = []
TEST_G_FILENAMES = []
TEST_Q_FILENAMES = []
for gcs_path in config["data_paths"]:
DATA_FILENAMES += tf.io.gfile.glob(gcs_path + '/*BIO*.tfrec')
TEST_G_FILENAMES += tf.io.gfile.glob(gcs_path + '/FGVC11*Test_G*.tfrec')
TEST_Q_FILENAMES += tf.io.gfile.glob(gcs_path + '/FGVC11*Test_Q*.tfrec')
TRAINING_FILENAMES, VALIDATION_FILENAMES = train_test_split(DATA_FILENAMES, test_size=0.02, random_state = config["seed"])
sample_dataset = get_test_dataset(random.choices(DATA_FILENAMES, k = 1))
train_dataset = get_train_dataset(TRAINING_FILENAMES)
valid_dataset = get_valid_dataset(VALIDATION_FILENAMES)
valid_G_dataset = get_test_dataset(VALIDATION_FILENAMES)
test_G_dataset = get_test_dataset(sorted(TEST_G_FILENAMES))
test_Q_dataset = get_test_dataset(sorted(TEST_Q_FILENAMES))
evaluation = Evaluation(test_G_dataset, test_Q_dataset, valid_G_dataset)
counter = 0
plt.figure()
f, axarr = plt.subplots(4,4)
for data in train_dataset:
for image in data[0]['image']:
axarr[counter//4, counter%4].imshow(image/256)
axarr[counter//4, counter%4].axis('off')
counter += 1
if (counter == 16):
break
if (counter == 16):
break
plt.show()
with strategy.scope():
model = model_factory(backbones = config["backbones"],
n_classes = config["n_classes"])
optimizer = tf.keras.optimizers.Adam(learning_rate = config["lr"])
model.compile(optimizer = optimizer,
loss = [tf.keras.losses.CategoricalCrossentropy()],
metrics = [tf.keras.metrics.CategoricalAccuracy(name = "ACC@1"),
tf.keras.metrics.TopKCategoricalAccuracy(k = 10, name = "ACC@10"),
tf.keras.metrics.TopKCategoricalAccuracy(k = 50, name = "ACC@50"),
])
savemodel = SaveModel(path = config['save_path'])
# if os.path.isfile("model.keras"):
# model = tf.keras.models.load_model("model.keras", safe_mode=False, custom_objects={"Hash": Hash, "Margin": Margin})
H = model.fit(train_dataset, verbose = 1,
validation_data = valid_dataset,
callbacks = [savemodel, evaluation],
steps_per_epoch = 1000,
epochs = config["epochs"])