{"cells":[{"metadata":{},"cell_type":"markdown","source":"Converting tf slim resnet model into keras resnet model.\nThis works for both for res50 and res101.\nAfeter this conversion you can fine tune the baseline model.\n\n* Result for the conversion quality check :　\n\n    **-0.6%** for landmark-retrieval public/private score, not for recoginition score.\n\n    tf slim, original:  0.27154/0.24382\n\n    tf keras, converted with this script: 0.26981/0.24230\n"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"import os\nimport re\nimport tensorflow as tf\nbase_dir = \"/kaggle/input\"  \nbaseline_path = os.path.join(base_dir, \"delg-saved-models/local_and_global/variables/variables\")\n\nRESNET = '50'\n# RESNET = '101'\nif RESNET == '50':\n    backbone = tf.keras.applications.ResNet50(\n        include_top=False, weights=None, input_tensor=None, input_shape=None,\n        pooling=None\n    )\nelif RESNET == \"101\":\n    backbone = tf.keras.applications.ResNet101(\n        include_top=False, weights=None, input_tensor=None, input_shape=None,\n        pooling=None\n    )","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"# loading host baseline model weight names\ndef make_ckpt_dict(ckpt_path):\n    ck_list = tf.train.list_variables(ckpt_path)\n    ck_dict = {var[0]: var[1] for var in ck_list}\n    return ckpt_path, ck_list, ck_dict\n\nbaseline_ckpt = make_ckpt_dict(baseline_path)\n\nckpt_file, ck_list, ck_dict = baseline_ckpt\nassert len([key for key in ck_dict.keys() if key.find(f'resnet_v1_{RESNET}') > -1]) > 0   \n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"# loading keras resnet variable names\nkeras_vars = {}\nfor var in backbone.variables:\n    keras_vars[var.name] = var.shape.as_list()\n# keras_vars","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"\ndef keras_to_slim(keras_name=\"conv2_block3_1_bn/beta:0\", resnet=\"101\"):\n    # convert keras resnet variable names into correspoinding slim variable names\n    layer_dict = {'bn': 'BatchNorm/', 'conv': ''}\n    var_dict = {'beta:0': 'beta', 'gamma:0': 'gamma', 'moving_mean:0': 'moving_mean', 'moving_variance:0': 'moving_variance', 'kernel:0':'weights'}\n    keras_split = keras_name.replace(\"moving_\", \"moving-\").split(\"_\")\n    if len(keras_split) > 2:\n        conv_id, block_id, layer_id, layer_name = keras_split\n        conv_id, block_id = int(re.sub(r\"\\D\", \"\", conv_id)), int(re.sub(r\"\\D\", \"\", block_id))\n        layer_id = int(layer_id)\n        layer_name, var_type = layer_name.split(\"/\")\n        var_type = var_type.replace(\"moving-\", \"moving_\")\n        assert keras_name == f\"conv{conv_id}_block{block_id}_{layer_id}_{layer_name}/{var_type}\"\n    else:\n        conv_id, layer_name = keras_split\n        conv_id = int(re.sub(r\"\\D\", \"\", conv_id))\n        layer_name, var_type = layer_name.split(\"/\")\n        var_type = var_type.replace(\"moving-\", \"moving_\")\n        assert keras_name == f\"conv{conv_id}_{layer_name}/{var_type}\"\n        assert conv_id == 1\n\n    if conv_id > 1:\n        if layer_id > 0:\n            slim_name = f\"resnet_v1_{resnet}/block{conv_id-1}/unit_{block_id}/bottleneck_v1/conv{layer_id}/{layer_dict[layer_name]}{var_dict[var_type]}\"\n        else:\n            assert block_id == 1\n            slim_name = f\"resnet_v1_{resnet}/block{conv_id-1}/unit_{block_id}/bottleneck_v1/shortcut/{layer_dict[layer_name]}{var_dict[var_type]}\"\n    else:\n        slim_name = f\"resnet_v1_{resnet}/conv1/{layer_dict[layer_name]}{var_dict[var_type]}\"\n    assert slim_name in [var[0] for var in ck_list]\n\n    return slim_name\n\nprint(\"assigning each tf slim variable to tf keras variable by variable name\")\nfor i, keras_var in enumerate(backbone.variables):\n    keras_name = keras_var.name\n    print(f\"{i}: {keras_name} ->\")\n    if keras_name.find(\"bias:0\") > -1:\n        keras_var.assign(tf.zeros_like(keras_var))\n        print(f\"\\t\\t bias = zeros:\")\n        continue\n\n    slim_name = keras_to_slim(keras_name, resnet=RESNET)\n    slim_var = tf.train.load_variable(ckpt_file, slim_name)\n    assert keras_var.numpy().shape == slim_var.shape\n    assert keras_var.numpy().dtype == slim_var.dtype\n\n    keras_var.assign(slim_var)\n    print(f\"\\t\\t {slim_name}\")\n    ck_dict.pop(slim_name)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"class GeM(tf.keras.layers.Layer):\n    # from https://github.com/filipradenovic/cnnimageretrieval-pytorch/blob/master/cirtorch/layers/functional.py\n    def __init__(self, p=3, epsilon=1e-6, **kwargs):\n        super().__init__(**kwargs)\n        self.init_p = p\n        self.epsilon = epsilon\n\n    def build(self, input_shape):\n\n        if isinstance(input_shape, list) or len(input_shape) != 4:\n            raise ValueError('`GeM` pooling layer only allow 1 input with 4 dimensions(b, h, w, c)')\n\n        self.build_shape = input_shape\n        self.p = self.add_weight(\n            name='gem_power',\n            shape=[1,],\n            initializer=tf.keras.initializers.Constant(value=self.init_p),\n            regularizer=None,\n            trainable=self.trainable,\n            dtype=tf.float32\n            )\n        super().build(input_shape)\n\n    def call(self, x):\n        x = tf.pow(tf.clip_by_value(x, self.epsilon, tf.reduce_max(x)), self.p)\n        x = tf.keras.layers.GlobalAvgPool2D()(x)\n        x = tf.pow(x, 1.0/self.p)\n        return x\n\n    def compute_output_shape(self, input_shape):\n        return tf.TensorShape(input_shape.as_list()[:1] + input_shape.as_list()[:-1])\n\n    def get_config(self):\n        base_config = super().get_config()\n        return {**base_config, \"init_p\": self.init_p, \"epsilon\": self.epsilon}\n\nclass DELGEmbed(tf.keras.Model):\n    def __init__(self, embed_dim, backbone, **kwargs):\n        super().__init__(**kwargs)\n        self.backbone = backbone\n        self.gem = GeM(p=3, name=\"gem\", trainable=False)\n        self.embed = tf.keras.layers.Dense(embed_dim, activation=None, bias_initializer='zeros', name=\"embed\")\n\n    def call(self, inputs, training=False):\n        x = self.backbone(inputs, training=training)\n        x = self.gem(x)\n        x = self.embed(x)\n        return x\n\n\ndelg_embed = DELGEmbed(2048, backbone, name=\"delg_global\")\ndelg_embed.build((None, 224, 224, 3))\n\nprint(\"assigning remaining weights\") \nembed_w = tf.train.load_variable(ckpt_file, \"embed/weights\")\nembed_b = tf.train.load_variable(ckpt_file, \"embed/biases\")\ndelg_embed.layers[-1].variables[0].assign(embed_w)  \ndelg_embed.layers[-1].variables[1].assign(embed_b)  \n\nck_dict.pop(\"embed/weights\")\nck_dict.pop(\"embed/biases\")\n\nassert len([key for key in ck_dict.keys() if key.find(f\"resnet_v1_{RESNET}/\") > -1]) == 0\nassert len([key for key in ck_dict.keys() if key.find(\"embed/\") > -1]) == 0","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"print(\"unsued weights in the baseline model\")\nfor key, value in ck_dict.items():\n    print(f\"{key}: {value}\")\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"delg_embed.summary()\nckpt_converted_dir = \"./\"\ndelg_embed.save_weights(os.path.join(ckpt_converted_dir, f\"baseline_delg_global_res{RESNET}.h5\"))\ndelg_embed.layers[0].save_weights(os.path.join(ckpt_converted_dir, f\"baseline_backbone_res{RESNET}.h5\"))\nprint(\"saving conveted weights for both global descriptor and backbone(resnet)\")\nos.listdir(\"./\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}