├── .gitignore ├── LICENSE ├── cat.png ├── extract_weights.py ├── frozen_batchnorm.py ├── hub_weight_loading.py ├── imagenet_utils.py ├── load_hub_weights.py ├── load_weights_multiple.py ├── mobilenetv2.py ├── models_to_load.py ├── panda.jpg ├── readme.md ├── test_mobilenet.py └── test_results.p /.gitignore: -------------------------------------------------------------------------------- 1 | /models/ 2 | /old_models/ 3 | *.npy 4 | /weights/ 5 | *.jpeg 6 | *.pyc 7 | *.txt 8 | *.jpg 9 | __pycache__ 10 | /__pycache__/ 11 | *.h5 12 | .vscode 13 | /.vscode/ 14 | layer_*.p 15 | ol_layer_*.p 16 | 17 | *.p 18 | eval_test_results.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jonathan Mitchell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonathanCMitchell/mobilenet_v2_keras/2474973db694f444a8472f6d2e369c71e104cd1c/cat.png -------------------------------------------------------------------------------- /extract_weights.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import numpy as np 3 | import urllib 4 | from nets.mobilenet import mobilenet_v2 5 | import tensorflow as tf 6 | import gzip 7 | import tarfile 8 | from test_mobilenet import get_tf_mobilenet_v2_items 9 | import sys 10 | import os 11 | import pickle 12 | from models_to_load import models_to_load 13 | # PYTHONPATH should contain the research/slim/ directory in the tensorflow/models repo. 14 | 15 | ROOT_DIR = os.getcwd() 16 | MODEL_DIR = os.path.join(ROOT_DIR, 'models') 17 | 18 | def extract_weights(models = []): 19 | 20 | for alpha, rows in models: 21 | 22 | SLIM_CKPT_base_path = get_tf_mobilenet_v2_items(alpha=alpha, rows=rows) 23 | 24 | checkpoint = SLIM_CKPT_base_path + '.ckpt' 25 | 26 | reader = tf.train.NewCheckpointReader(SLIM_CKPT_base_path + '.ckpt') 27 | 28 | # Get checkpoint and then do the rest 29 | 30 | # Obtain expanded keys and not_expanded keys 31 | tensor_count = 0 32 | weights_count = 0 33 | project_count = 0 34 | expand_count = 0 35 | depthwise_count = 0 36 | key_not_exp = 0 37 | layer_kind_count = {} 38 | expanded_keys = [] 39 | not_expanded_keys = [] 40 | for key in reader.get_variable_to_shape_map(): 41 | if key.split('/')[-1] == 'ExponentialMovingAverage': 42 | continue 43 | if key.split('/')[-1] == 'RMSProp' or key.split('/')[-1] == 'RMPSProp_1': 44 | continue 45 | if key == 'global_step': 46 | continue 47 | 48 | if 'expanded' not in key.split('/')[1]: 49 | key_not_exp += 1 50 | not_expanded_keys.append(key) 51 | else: 52 | expanded_keys.append(key) 53 | 54 | base = key.split('/')[0] 55 | block_id = key.split('/')[1] 56 | layer_kind = key.split('/')[2] 57 | T = reader.get_tensor(key) 58 | 59 | tensor_count += 1 60 | 61 | # Handle not_expanded keys: 62 | # add shapes and filter out RMSProp to non expanded keys 63 | not_expanded_layers = [] 64 | for key in not_expanded_keys: 65 | if key.split('/')[-1] == 'RMSProp_1': 66 | continue 67 | if len(key.split('/')) == 4: 68 | _, layer, kind, meta = key.split('/') 69 | elif len(key.split('/')) == 3: 70 | _, layer, meta = key.split('/') 71 | 72 | if layer == 'Conv': 73 | block_id = -1 74 | layer = 'Conv1' 75 | if meta in ['gamma', 'moving_mean', 'moving_variance', 'beta']: 76 | layer = 'bn_Conv1' 77 | 78 | elif layer == 'Conv_1': 79 | block_id = 17 80 | if meta in ['gamma', 'moving_mean', 'moving_variance', 'beta']: 81 | layer = 'Conv_1_bn' 82 | elif layer == 'Logits': 83 | block_id = 19 84 | else: 85 | print('something odd encountered') 86 | continue 87 | 88 | not_expanded_layers.append({ 89 | 'key': key, 90 | 'block_id': block_id, 91 | 'layer': layer, 92 | 'mod': '', 93 | 'meta': meta, 94 | 'shape': reader.get_tensor(key).shape, 95 | }) 96 | 97 | # Perform analysis on expanded keys 98 | expanded_weights_keys = [] 99 | expanded_bn_keys = [] 100 | for key in expanded_keys: 101 | # if it's length = 5 then it should be a batch norm 102 | # if it's len = 4 then its a conv 103 | if len(key.split('/')) == 4: 104 | # print('weights keys: ', key) 105 | T = reader.get_tensor(key) 106 | expanded_weights_keys.append((key, T.shape)) 107 | elif len(key.split('/')) == 5: 108 | # print('batchnorm keys: ', key) 109 | T = reader.get_tensor(key) 110 | expanded_bn_keys.append((key, T.shape)) 111 | else: 112 | # otherwise it's a gamma/RMSProp key 113 | continue 114 | 115 | 116 | print('len of expanded_weights keys: ', len(expanded_weights_keys)) 117 | print('len of expanded_bn_keys: ', len(expanded_bn_keys)) 118 | 119 | 120 | # Layer will be 121 | # Block_id = -1 layer => 'Conv' 'bn_Conv_1' 122 | # Block_id = 17 layer => 'Conv_1' 'Conv_1_bn' 123 | # Block_id = 19 layer => 'logits', this is a Dense layer 124 | 125 | # Loop through expanded weights keys and create guide 126 | bn_layer_guide = [] 127 | count = 0 128 | for bnkey, bnshape in expanded_bn_keys: 129 | 130 | # # save the file 131 | _, layer, mod, kind, meta = bnkey.split('/') 132 | if kind == 'BatchNorm': 133 | 134 | if layer == 'expanded_conv': 135 | # then layer is depthwise 136 | bn_layer_guide.append({ 137 | "key": bnkey, 138 | 'block_id': 0, 139 | 'layer': kind, 140 | 'mod': mod, 141 | 'meta': meta, 142 | 'shape': bnshape 143 | }) 144 | else: 145 | num = layer.split('_')[-1] 146 | # then layer is depthwise 147 | bn_layer_guide.append({ 148 | "key": bnkey, 149 | 'block_id': num, 150 | 'layer': kind, 151 | 'mod': mod, 152 | 'meta': meta, 153 | 'shape': bnshape 154 | }) 155 | 156 | # Loop through expanded weights keys and create guide 157 | w_layer_guide = [] 158 | for wkey, wshape in expanded_weights_keys: 159 | 160 | # save the file 161 | _, layer, mod, meta = wkey.split('/') 162 | if len(layer.split('_')) == 2: 163 | # This is expanded_conv_0 164 | 165 | if mod == 'depthwise': 166 | kind = 'DepthwiseConv2D' 167 | elif mod == 'expand' or mod == 'project': 168 | kind = 'Conv2D' 169 | 170 | # then layer is depthwise 171 | w_layer_guide.append({ 172 | "key": wkey, 173 | 'block_id': 0, 174 | 'layer': kind, 175 | 'mod': mod, 176 | 'meta': meta, 177 | 'shape': wshape 178 | }) 179 | else: 180 | num = layer.split('_')[-1] 181 | if mod == 'depthwise': 182 | kind = 'DepthwiseConv2D' 183 | elif mod == 'expand' or mod == 'project': 184 | kind = 'Conv2D' 185 | 186 | # then layer is depthwise 187 | w_layer_guide.append({ 188 | "key": wkey, 189 | 'block_id': num, 190 | 'layer': kind, 191 | 'mod': mod, 192 | 'meta': meta, 193 | 'shape': wshape 194 | }) 195 | 196 | # Merge layer guides together 197 | layer_guide = bn_layer_guide + w_layer_guide + not_expanded_layers 198 | 199 | # Sort the layer guide by block_ids 200 | layer_guide = sorted(layer_guide, key=lambda x: int(x['block_id'])) 201 | 202 | 203 | # Save layer guide to np arrays 204 | for layer in layer_guide: 205 | T = reader.get_tensor(layer['key']) 206 | 207 | filename = layer['key'].replace('/', '_') 208 | filename = filename.replace( 209 | 'MobilenetV2_', './weights'+str(alpha)+str(rows)+'/') + '.npy' 210 | if not os.path.isdir('./weights' + str(alpha)+str(rows)): 211 | os.makedirs('./weights' + str(alpha)+str(rows)) 212 | layer['file'] = filename 213 | np.save(filename, T) 214 | 215 | print('len of layer_guide: ', len(layer_guide)) 216 | 217 | 218 | # Save to dir 219 | extraction_repo_path = '/home/jon/Documents/keras_mobilenetV2/' 220 | with open(extraction_repo_path + 'layer_guide' + str(alpha) + str(rows) + '.p', 'wb') as pickle_file: 221 | pickle.dump(layer_guide, pickle_file) 222 | 223 | print('created for : ', alpha, 'rows: ', rows) 224 | 225 | 226 | 227 | if __name__ == "__main__": 228 | # models = [(1.0, 128)] 229 | extract_weights(models = models_to_load) 230 | print('weights extracted') 231 | -------------------------------------------------------------------------------- /frozen_batchnorm.py: -------------------------------------------------------------------------------- 1 | from keras import layers as KL 2 | from keras.engine import InputSpec 3 | 4 | class FrozenBatchNorm(KL.BatchNormalization): 5 | """Batch Normalization class. Subclasses the Keras BN class and 6 | hardcodes training=False so the BN layer doesn't update 7 | during training. 8 | Batch normalization has a negative effect on training if batches are small 9 | so we disable it here. 10 | """ 11 | 12 | def build(self, input_shape): 13 | dim = input_shape[self.axis] 14 | if dim is None: 15 | raise ValueError('Axis ' + str(self.axis) + ' of ' 16 | 'input tensor should have a defined dimension ' 17 | 'but the layer received an input with shape ' + 18 | str(input_shape) + '.') 19 | self.input_spec = InputSpec(ndim=len(input_shape), 20 | axes={self.axis: dim}) 21 | shape = (dim,) 22 | 23 | if self.scale: 24 | self.gamma = self.add_weight(shape=shape, 25 | name='gamma', 26 | initializer=self.gamma_initializer, 27 | regularizer=self.gamma_regularizer, 28 | constraint=self.gamma_constraint, 29 | trainable=False) 30 | else: 31 | self.gamma = None 32 | if self.center: 33 | self.beta = self.add_weight(shape=shape, 34 | name='beta', 35 | initializer=self.beta_initializer, 36 | regularizer=self.beta_regularizer, 37 | constraint=self.beta_constraint, 38 | trainable=False) 39 | else: 40 | self.beta = None 41 | self.moving_mean = self.add_weight( 42 | shape=shape, 43 | name='moving_mean', 44 | initializer=self.moving_mean_initializer, 45 | trainable=False) 46 | self.moving_variance = self.add_weight( 47 | shape=shape, 48 | name='moving_variance', 49 | initializer=self.moving_variance_initializer, 50 | trainable=False) 51 | self.built = True 52 | 53 | def call(self, inputs, training=None): 54 | return super(self.__class__, self).call(inputs, training=False) 55 | -------------------------------------------------------------------------------- /hub_weight_loading.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tensorflow as tf 3 | import tensornets as nets 4 | import tensorflow_hub as hub 5 | # from mobilenetv2 import MobileNetV2 6 | from keras.models import Model 7 | from keras.applications.mobilenetv2 import MobileNetV2 8 | # from mobilenetv2 import MobileNetV2 9 | 10 | 11 | def map_alpha_to_slim(alpha): 12 | alpha_map = { 13 | 1.4: '140', 14 | 1.3: '130', 15 | 1.0: '100', 16 | 0.75: '075', 17 | 0.5: '050', 18 | 0.35: '035' 19 | } 20 | return alpha_map[alpha] 21 | 22 | 23 | alpha = 0.35 24 | rows = 192 25 | # rows = 224 26 | # rows = 160 27 | # rows = 128 28 | # rows = 96 29 | 30 | print('ALPHA: ', alpha) 31 | print('rows:', rows) 32 | 33 | WEIGHTS_SAVE_PATH_INCLUDE_TOP = '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' + \ 34 | str(alpha) + '_' + str(rows) + '.h5' 35 | 36 | WEIGHTS_SAVE_PATH_NO_TOP = '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' + \ 37 | str(alpha) + '_' + str(rows) + '_no_top' + '.h5' 38 | 39 | img = nets.utils.load_img('cat.png', target_size=256, crop_size=rows) 40 | img = (img / 128.0) - 1.0 41 | 42 | inputs = tf.placeholder(tf.float32, [None, rows, rows, 3]) 43 | model = hub.Module( 44 | "https://tfhub.dev/google/imagenet/mobilenet_v2_" + map_alpha_to_slim(alpha) + "_" + str(rows) + "/classification/1") 45 | features = model(inputs, signature="image_classification", as_dict=True) 46 | probs = tf.nn.softmax(features['default']) 47 | 48 | # with tf.variable_scope('keras'): 49 | print('for ALPHA: ', alpha) 50 | 51 | model2 = MobileNetV2(weights='imagenet', alpha = alpha, input_shape = (rows, rows, 3)) 52 | 53 | 54 | with tf.Session() as sess: 55 | sess.run(tf.global_variables_initializer()) 56 | preds = sess.run(probs, {inputs: img}) 57 | 58 | preds2 = model2.predict(img) 59 | 60 | print('TFHUB: ', nets.utils.decode_predictions(preds[:, 1:])) 61 | print('MOBLV2 local bn new: ',nets.utils.decode_predictions(preds2)) 62 | 63 | 64 | with tf.Session() as sess: 65 | sess.run(tf.global_variables_initializer()) 66 | weights = tf.get_collection( 67 | tf.GraphKeys.GLOBAL_VARIABLES, scope='module/MobilenetV2') 68 | values = sess.run(weights) 69 | values[-2] = np.delete(np.squeeze(values[-2]), 0, axis=-1) 70 | values[-1] = np.delete(values[-1], 0, axis=-1) 71 | 72 | model2.set_weights(values) 73 | 74 | # Save weights no top and model 75 | model2.save_weights(WEIGHTS_SAVE_PATH_INCLUDE_TOP) 76 | model2_no_top = Model(input=model2.input, 77 | output=model2.get_layer('out_relu').output) 78 | model2_no_top.save_weights(WEIGHTS_SAVE_PATH_NO_TOP) 79 | 80 | 81 | preds3 = model2.predict(img) 82 | 83 | 84 | 85 | print('MOBLV2 local bn new weights new: ', nets.utils.decode_predictions(preds3)) 86 | 87 | 88 | # Now try to load new model locally and get the same weight score. 89 | 90 | -------------------------------------------------------------------------------- /imagenet_utils.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | 3 | 4 | def create_readable_names_for_imagenet_labels(): 5 | """Create a dict mapping label id to human readable string. 6 | 7 | Returns: 8 | labels_to_names: dictionary where keys are integers from to 1000 9 | and values are human-readable names. 10 | 11 | We retrieve a synset file, which contains a list of valid synset labels used 12 | by ILSVRC competition. There is one synset one per line, eg. 13 | # n01440764 14 | # n01443537 15 | We also retrieve a synset_to_human_file, which contains a mapping from synsets 16 | to human-readable names for every synset in Imagenet. These are stored in a 17 | tsv format, as follows: 18 | # n02119247 black fox 19 | # n02119359 silver fox 20 | We assign each synset (in alphabetical order) an integer, starting from 1 21 | (since 0 is reserved for the background class). 22 | 23 | Code is based on 24 | https://github.com/tensorflow/models/blob/master/research/inception/inception/data/build_imagenet_data.py#L463 25 | """ 26 | 27 | # pylint: disable=g-line-too-long 28 | base_url = 'https://raw.githubusercontent.com/tensorflow/models/master/research/inception/inception/data/' 29 | synset_url = '{}/imagenet_lsvrc_2015_synsets.txt'.format(base_url) 30 | synset_to_human_url = '{}/imagenet_metadata.txt'.format(base_url) 31 | 32 | filename, _ = urllib.request.urlretrieve(synset_url) 33 | synset_list = [s.strip() for s in open(filename).readlines()] 34 | num_synsets_in_ilsvrc = len(synset_list) 35 | assert num_synsets_in_ilsvrc == 1000 36 | 37 | filename, _ = urllib.request.urlretrieve(synset_to_human_url) 38 | synset_to_human_list = open(filename).readlines() 39 | num_synsets_in_all_imagenet = len(synset_to_human_list) 40 | assert num_synsets_in_all_imagenet == 21842 41 | 42 | synset_to_human = {} 43 | for s in synset_to_human_list: 44 | parts = s.strip().split('\t') 45 | assert len(parts) == 2 46 | synset = parts[0] 47 | human = parts[1] 48 | synset_to_human[synset] = human 49 | 50 | label_index = 1 51 | labels_to_names = {0: 'background'} 52 | for synset in synset_list: 53 | name = synset_to_human[synset] 54 | labels_to_names[label_index] = name 55 | label_index += 1 56 | 57 | return labels_to_names 58 | -------------------------------------------------------------------------------- /load_hub_weights.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tensorflow as tf 3 | import tensornets as nets 4 | import tensorflow_hub as hub 5 | from mobilenetv2 import MobileNetV2 6 | from keras.models import Model 7 | from keras.layers import Input 8 | from models_to_load import models_to_load 9 | 10 | 11 | def map_alpha_to_slim(alpha): 12 | alpha_map = { 13 | 1.4: '140', 14 | 1.3: '130', 15 | 1.0: '100', 16 | 0.75: '075', 17 | 0.5: '050', 18 | 0.35: '035' 19 | } 20 | 21 | return alpha_map[alpha] 22 | 23 | def load_hub_weights(models): 24 | for alpha, rows in models: 25 | 26 | tf.reset_default_graph() 27 | print('alpha: ', alpha, 'rows: ', rows) 28 | 29 | WEIGHTS_SAVE_PATH_INCLUDE_TOP = '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' + str(alpha) + '_' + str(rows) + '.h5' 30 | 31 | WEIGHTS_SAVE_PATH_NO_TOP = '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' + \ 32 | str(alpha) + '_' + str(rows) + '_no_top' + '.h5' 33 | 34 | # Load tf stuff 35 | img = nets.utils.load_img('cat.png', target_size=256, crop_size=rows) 36 | img = (img / 128.0) - 1.0 37 | inputs = tf.placeholder(tf.float32, [None, rows, rows, 3]) 38 | 39 | model = hub.Module( 40 | "https://tfhub.dev/google/imagenet/mobilenet_v2_" 41 | + map_alpha_to_slim(alpha) + "_" 42 | + str(rows) + "/classification/1") 43 | 44 | h, w = hub.get_expected_image_size(model) 45 | 46 | features = model(inputs, signature="image_classification", as_dict=True) 47 | probs = tf.nn.softmax(features['default']) 48 | 49 | # Load local model 50 | with tf.variable_scope('keras'): 51 | model2 = MobileNetV2(weights=None, 52 | alpha = alpha, 53 | input_shape=(rows, rows, 3)) 54 | model2.load_weights('./old_weights_nonhub/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' + str(alpha) + '_' +str(rows) + '.h5') 55 | 56 | preds1 = model2.predict(img) 57 | print('preds1: (remote weights) new BN no set w:: ', 58 | nets.utils.decode_predictions(preds1)) 59 | 60 | with tf.Session() as sess: 61 | sess.run(tf.global_variables_initializer()) 62 | weights = tf.get_collection( 63 | tf.GraphKeys.GLOBAL_VARIABLES, scope='module/MobilenetV2') 64 | values = sess.run(weights) 65 | values[-2] = np.delete(np.squeeze(values[-2]), 0, axis=-1) 66 | values[-1] = np.delete(values[-1], 0, axis=-1) 67 | sess.close() 68 | 69 | # Save weights no top and model 70 | model2.set_weights(values) 71 | model2.save_weights(WEIGHTS_SAVE_PATH_INCLUDE_TOP) 72 | model2_no_top = Model(input = model2.input, output = model2.get_layer('out_relu').output) 73 | model2_no_top.save_weights(WEIGHTS_SAVE_PATH_NO_TOP) 74 | 75 | # Predictions with new BN, new weights 76 | preds2 = model2.predict(img) 77 | 78 | print('preds2: (after set weights) ', 79 | nets.utils.decode_predictions(preds2)) 80 | 81 | 82 | if __name__ == "__main__": 83 | # Note: if you want to load and save all the models, you have to reset the tf graph and tf session 84 | load_hub_weights(models=[(1.0, 224)]]) 85 | -------------------------------------------------------------------------------- /load_weights_multiple.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import numpy as np 4 | import pickle 5 | 6 | from keras.layers import Input, Conv2D, Dense 7 | from mobilenetv2 import MobileNetV2, BatchNorm, DepthwiseConv2D 8 | from models_to_load import models_to_load 9 | 10 | 11 | def load_weights(models): 12 | 13 | for alpha, rows in models: 14 | 15 | WEIGHTS_SAVE_PATH_INCLUDE_TOP = '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' + str(alpha) + '_' + str(rows) + '.h5' 16 | WEIGHTS_SAVE_PATH_NO_TOP = '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' + str(alpha) + '_' + str(rows) + '_no_top' + '.h5' 17 | 18 | 19 | with open('layer_guide'+str(alpha) + str(rows) + '.p', 'rb') as pickle_file: 20 | layer_guide = pickle.load(pickle_file) 21 | 22 | # layers 0-4 are first conv 23 | # layers 4 - 16 are expanded 24 | 25 | # load mobilenet 26 | input_tensor = Input(shape = (rows, rows, 3)) 27 | model = MobileNetV2(input_tensor = input_tensor, include_top=True, alpha = alpha, weights = None) 28 | 29 | 30 | not_expanded_layers = ['bn_Conv1', 31 | 'Conv_1_bn', 32 | 'Conv1', 33 | 'Logits', 34 | 'bn_Conv1', 35 | 'Logits', 36 | 'Conv_1', 37 | 'Conv_1_bn', 38 | 'Conv_1_bn', 39 | 'bn_Conv1', 40 | 'bn_Conv1', 41 | 'Conv_1_bn'] 42 | 43 | 44 | def find_not_exp_conv_layer_match(keras_layer, kind=None): 45 | """ 46 | This function takes in layer parameters and finds the associated conv layer from the weights layer guide 47 | Returns the weights layer guide object , found boolean 48 | """ 49 | for lobj in layer_guide: 50 | if keras_layer.name == lobj['layer']: 51 | if keras_layer.weights[0]._keras_shape == lobj['shape']: 52 | return lobj, True 53 | else: 54 | return {}, False 55 | 56 | 57 | def find_not_exp_bn_layers_match(keras_layer, kind=None): 58 | """ 59 | This function takes in layer parameters and finds the associated conv layer from the weights layer guide 60 | Returns the weights layer guide object , found boolean 61 | """ 62 | meta_full = 4 63 | metas_found = 0 64 | 65 | for lobj in layer_guide: 66 | if keras_layer.name == lobj['layer']: 67 | if lobj['meta'] == 'gamma' and keras_layer.weights[0]._keras_shape == lobj['shape']: 68 | gamma = lobj 69 | metas_found += 1 70 | elif lobj['meta'] == 'beta' and keras_layer.weights[1]._keras_shape == lobj['shape']: 71 | beta = lobj 72 | metas_found += 1 73 | elif lobj['meta'] == 'moving_mean' and keras_layer.weights[2]._keras_shape == lobj['shape']: 74 | moving_mean = lobj 75 | metas_found += 1 76 | elif lobj['meta'] == 'moving_variance' and keras_layer.weights[3]._keras_shape == lobj['shape']: 77 | moving_variance = lobj 78 | metas_found += 1 79 | 80 | if metas_found == meta_full: 81 | return [gamma, beta, moving_mean, moving_variance], True 82 | else: 83 | return [], False 84 | 85 | 86 | def find_not_exp_dense_layers_match(keras_layer, kind=None): 87 | meta_full = 2 88 | metas_found = 0 89 | for lobj in layer_guide: 90 | if keras_layer.name == lobj['layer']: 91 | if lobj['meta'] == 'weights': 92 | weights = lobj 93 | metas_found += 1 94 | elif lobj['meta'] == 'biases': 95 | bias = lobj 96 | metas_found += 1 97 | if metas_found == meta_full: 98 | return [weights, bias], True 99 | else: 100 | return [], False 101 | 102 | 103 | 104 | def find_conv_layer_match(keras_layer, block_id = None, mod = None, kind = None): 105 | """ 106 | This function takes in layer parameters and finds the associated conv layer from the weights layer guide 107 | Returns the weights layer guide object , found boolean 108 | """ 109 | for lobj in layer_guide: 110 | if int(lobj['block_id']) == int(block_id): 111 | if lobj['layer'] == kind: 112 | if lobj['mod'] == mod: 113 | if keras_layer.weights[0]._keras_shape == lobj['shape']: 114 | return lobj, True 115 | else: 116 | return {}, False 117 | 118 | def find_bn_layers_match(keras_layer, block_id = None, mod = None, kind = None): 119 | """ 120 | This function takes in layer parameters and returns a list of the four batch norm parameters as well as a boolean indicating success 121 | """ 122 | meta_full = 4 123 | metas_found = 0 124 | 125 | for lobj in layer_guide: 126 | if int(lobj['block_id']) == int(block_id): 127 | if lobj['layer'] == kind: 128 | if lobj['mod'] == mod: 129 | if lobj['meta'] == 'gamma' and keras_layer.weights[0]._keras_shape == lobj['shape']: 130 | gamma = lobj 131 | metas_found += 1 132 | elif lobj['meta'] == 'beta' and keras_layer.weights[1]._keras_shape == lobj['shape']: 133 | beta = lobj 134 | metas_found += 1 135 | elif lobj['meta'] == 'moving_mean' and keras_layer.weights[2]._keras_shape == lobj['shape']: 136 | moving_mean = lobj 137 | metas_found += 1 138 | elif lobj['meta'] == 'moving_variance' and keras_layer.weights[3]._keras_shape == lobj['shape']: 139 | moving_variance = lobj 140 | metas_found += 1 141 | if metas_found == meta_full: 142 | return [gamma, beta, moving_mean, moving_variance], True 143 | else: 144 | return [], False 145 | 146 | # Calculate loaded number 147 | set_weights = 0 148 | for keras_layer in model.layers: 149 | name = keras_layer.name.split('_') 150 | # If it not an expandable layer 151 | if keras_layer.name in not_expanded_layers: 152 | print('keras_layer : ', keras_layer.name, ' Is not expandable') 153 | if isinstance(keras_layer, BatchNorm): 154 | bn_layers, isfound = find_not_exp_bn_layers_match(keras_layer = keras_layer, kind='BatchNorm') 155 | if isfound: 156 | arrs = [np.load(lobj['file']) for lobj in bn_layers] 157 | keras_layer.set_weights(arrs) 158 | set_weights += 1 # can add () 159 | else: 160 | print('possible error not match found if isinstance BatchNorm and not expandable') 161 | 162 | elif isinstance(keras_layer, DepthwiseConv2D): 163 | lobj, isfound = find_not_exp_conv_layer_match( 164 | keras_layer=keras_layer, kind='DepthwiseConv2D') 165 | if isfound: 166 | if lobj['meta'] == 'weights': 167 | arr = np.load(lobj['file']) 168 | keras_layer.set_weights([arr]) 169 | set_weights += 1 170 | else: 171 | print(' You probably wont see this but just in case possible error finding weights for not expandable DepthwiseConv2D: ') 172 | elif isinstance(keras_layer, Conv2D): 173 | # get mods 174 | 175 | lobj, isfound = find_not_exp_conv_layer_match(keras_layer=keras_layer, kind='Conv2D') 176 | if isfound: 177 | if lobj['meta'] == 'weights': 178 | arr = np.load(lobj['file']) 179 | keras_layer.set_weights([arr]) 180 | set_weights += 1 181 | else: 182 | print('possible error finding weights for not expandable Conv2D: ') 183 | # if layer['meta'] == 'biases' 184 | elif isinstance(keras_layer, Dense): 185 | dense_layers, isfound = find_not_exp_dense_layers_match(keras_layer=keras_layer, kind='Dense') 186 | if isfound: 187 | weights = np.load(dense_layers[0]['file']) 188 | bias = np.load(dense_layers[1]['file']) 189 | 190 | # squeeze 191 | weights = np.squeeze(weights) 192 | 193 | # Remove background class 194 | weights = weights[:, 1:] 195 | bias = bias[1:] 196 | 197 | keras_layer.set_weights([weights, bias]) 198 | set_weights += 1 199 | else: 200 | print('possible error with dense layer in not expandable') 201 | 202 | else: 203 | if isinstance(keras_layer, BatchNorm): 204 | _, _, num, _, mod = name 205 | bn_layers, isfound = find_bn_layers_match(keras_layer=keras_layer, block_id=num, mod=mod, kind='BatchNorm') 206 | # note: BatchNorm layers have 4 weights 207 | # This will return a list of the 4 objects corresponding to the 4 weights in the right order 208 | # Then go through the list and grab each numpy array, make sure it is wrapped inside a standard list 209 | if isfound: 210 | arrs = [np.load(lobj['file']) for lobj in bn_layers] 211 | keras_layer.set_weights(arrs) 212 | set_weights += 1 213 | else: 214 | print('possible error not match found on expandable batchNorm layers') 215 | print('=== dump: ') 216 | print('keras_layer: ', keras_layer.name) 217 | 218 | 219 | # Check for DepthwiseConv2D 220 | elif isinstance(keras_layer, DepthwiseConv2D): 221 | _, _, num, mod = name 222 | # check layer meta to be depthwise_weights 223 | layer, isfound = find_conv_layer_match(keras_layer=keras_layer, block_id=num, mod=mod, kind='DepthwiseConv2D') 224 | if isfound: 225 | if layer['meta'] == 'depthwise_weights': 226 | arr = np.load(layer['file']) 227 | keras_layer.set_weights([arr]) 228 | set_weights += 1 229 | else: 230 | print('possible error not match found for DepthwiseConv2D in not expandable') 231 | 232 | # Check for Conv2D 233 | elif isinstance(keras_layer, Conv2D): 234 | _, _, num, mod = name 235 | layer, isfound = find_conv_layer_match(keras_layer=keras_layer, block_id=num, mod=mod, kind='Conv2D') 236 | if isfound: 237 | if layer['meta'] == 'weights': 238 | arr = np.load(layer['file']) 239 | keras_layer.set_weights([arr]) 240 | else: 241 | print('possible error not match found') 242 | 243 | # Set first block 244 | 245 | # Organize batch norm layers into [gamma, beta, moving_mean, moving_std] 246 | # Organize expand conv block layers into [expand, depthwise, project] 247 | print('set_weights: ', set_weights) 248 | model.save_weights(WEIGHTS_SAVE_PATH_INCLUDE_TOP) 249 | 250 | # Save no top model 251 | out_relu = model.get_layer('out_relu').output 252 | from keras.models import Model 253 | model_no_top = Model(input = input_tensor, output = out_relu) 254 | model_no_top.save(WEIGHTS_SAVE_PATH_NO_TOP) 255 | 256 | 257 | trainable_layers = [l.weights for l in model.layers] 258 | print('len trainable: ', len(trainable_layers)) 259 | 260 | # TODO test predict with detect api 261 | print('model: ', model) 262 | print('set_weights: ', set_weights) 263 | assert(set_weights == 72) 264 | print('alpha: ', alpha) 265 | print('rows: ', rows) 266 | 267 | 268 | # alpha = 1.3 269 | # rows = 224 270 | # models = [(1.4, 224), (1.3, 224), (1.0, 224), (0.5, 224)] 271 | load_weights(models=models_to_load) 272 | 273 | 274 | 275 | -------------------------------------------------------------------------------- /mobilenetv2.py: -------------------------------------------------------------------------------- 1 | """MobileNet v2 models for Keras. 2 | 3 | MobileNetV2 is a general architecture and can be used for multiple use cases. 4 | Depending on the use case, it can use different input layer size and 5 | different width factors. This allows different width models to reduce 6 | the number of multiply-adds and thereby 7 | reduce inference cost on mobile devices. 8 | 9 | MobileNetV2 is very similar to the original MobileNet, 10 | except that it uses inverted residual blocks with 11 | bottlenecking features. It has a drastically lower 12 | parameter count than the original MobileNet. 13 | MobileNets support any input size greater 14 | than 32 x 32, with larger image sizes 15 | offering better performance. 16 | 17 | The number of parameters and number of multiply-adds 18 | can be modified by using the `alpha` parameter, 19 | which increases/decreases the number of filters in each layer. 20 | By altering the image size and `alpha` parameter, 21 | all 22 models from the paper can be built, with ImageNet weights provided. 22 | 23 | The paper demonstrates the performance of MobileNets using `alpha` values of 24 | 1.0 (also called 100 % MobileNet), 0.35, 0.5, 0.75, 1.0, 1.3, and 1.4 25 | 26 | For each of these `alpha` values, weights for 5 different input image sizes 27 | are provided (224, 192, 160, 128, and 96). 28 | 29 | 30 | The following table describes the performance of 31 | MobileNet on various input sizes: 32 | ------------------------------------------------------------------------ 33 | MACs stands for Multiply Adds 34 | 35 | Classification Checkpoint| MACs (M) | Parameters (M)| Top 1 Accuracy| Top 5 Accuracy 36 | --------------------------|------------|---------------|---------|----|------------- 37 | | [mobilenet_v2_1.4_224] | 582 | 6.06 | 75.0 | 92.5 | 38 | | [mobilenet_v2_1.3_224] | 509 | 5.34 | 74.4 | 92.1 | 39 | | [mobilenet_v2_1.0_224] | 300 | 3.47 | 71.8 | 91.0 | 40 | | [mobilenet_v2_1.0_192] | 221 | 3.47 | 70.7 | 90.1 | 41 | | [mobilenet_v2_1.0_160] | 154 | 3.47 | 68.8 | 89.0 | 42 | | [mobilenet_v2_1.0_128] | 99 | 3.47 | 65.3 | 86.9 | 43 | | [mobilenet_v2_1.0_96] | 56 | 3.47 | 60.3 | 83.2 | 44 | | [mobilenet_v2_0.75_224] | 209 | 2.61 | 69.8 | 89.6 | 45 | | [mobilenet_v2_0.75_192] | 153 | 2.61 | 68.7 | 88.9 | 46 | | [mobilenet_v2_0.75_160] | 107 | 2.61 | 66.4 | 87.3 | 47 | | [mobilenet_v2_0.75_128] | 69 | 2.61 | 63.2 | 85.3 | 48 | | [mobilenet_v2_0.75_96] | 39 | 2.61 | 58.8 | 81.6 | 49 | | [mobilenet_v2_0.5_224] | 97 | 1.95 | 65.4 | 86.4 | 50 | | [mobilenet_v2_0.5_192] | 71 | 1.95 | 63.9 | 85.4 | 51 | | [mobilenet_v2_0.5_160] | 50 | 1.95 | 61.0 | 83.2 | 52 | | [mobilenet_v2_0.5_128] | 32 | 1.95 | 57.7 | 80.8 | 53 | | [mobilenet_v2_0.5_96] | 18 | 1.95 | 51.2 | 75.8 | 54 | | [mobilenet_v2_0.35_224] | 59 | 1.66 | 60.3 | 82.9 | 55 | | [mobilenet_v2_0.35_192] | 43 | 1.66 | 58.2 | 81.2 | 56 | | [mobilenet_v2_0.35_160] | 30 | 1.66 | 55.7 | 79.1 | 57 | | [mobilenet_v2_0.35_128] | 20 | 1.66 | 50.8 | 75.0 | 58 | | [mobilenet_v2_0.35_96] | 11 | 1.66 | 45.5 | 70.4 | 59 | 60 | The weights for all 16 models are obtained and translated from the Tensorflow checkpoints 61 | from TensorFlow checkpoints found at 62 | https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/README.md 63 | 64 | # Reference 65 | This file contains building code for MobileNetV2, based on 66 | [MobileNetV2: Inverted Residuals and Linear Bottlenecks](https://arxiv.org/abs/1801.04381) 67 | 68 | Tests comparing this model to the existing Tensorflow model can be 69 | found at [mobilenet_v2_keras](https://github.com/JonathanCMitchell/mobilenet_v2_keras) 70 | """ 71 | from __future__ import print_function 72 | from __future__ import absolute_import 73 | from __future__ import division 74 | 75 | import os 76 | import warnings 77 | import h5py 78 | import numpy as np 79 | 80 | from keras.models import Model 81 | from keras.layers import Input 82 | from keras.layers import Activation 83 | from keras.layers import Dropout 84 | from keras.layers import Reshape 85 | from keras.layers import BatchNormalization 86 | from keras.layers import GlobalAveragePooling2D 87 | from keras.layers import GlobalMaxPooling2D 88 | from keras.layers import Conv2D 89 | from keras.layers import AveragePooling2D 90 | from keras.layers import Flatten 91 | from keras.layers import Add 92 | from keras.layers import Dense 93 | from keras.layers import DepthwiseConv2D 94 | from keras import initializers 95 | from keras import regularizers 96 | from keras import constraints 97 | from keras.utils import conv_utils 98 | from keras.utils.data_utils import get_file 99 | from keras.engine.topology import get_source_inputs 100 | from keras.engine import InputSpec 101 | from keras.applications import imagenet_utils 102 | from keras.applications.imagenet_utils import _obtain_input_shape 103 | from keras.applications.imagenet_utils import decode_predictions 104 | from keras import backend as K 105 | 106 | # Load remote 107 | BASE_WEIGHT_PATH = 'https://github.com/JonathanCMitchell/mobilenet_v2_keras/releases/download/v1.1/' 108 | 109 | 110 | def relu6(x): 111 | return K.relu(x, max_value=6) 112 | 113 | # DepthwiseConv2D is included in case you have an older version of keras which does not contain DepthwiseConv2D 114 | class DepthwiseConv2D(Conv2D): 115 | """Depthwise separable 2D convolution. 116 | 117 | Depthwise Separable convolutions consists in performing 118 | just the first step in a depthwise spatial convolution 119 | (which acts on each input channel separately). 120 | The `depth_multiplier` argument controls how many 121 | output channels are generated per input channel in the depthwise step. 122 | 123 | # Arguments 124 | kernel_size: An integer or tuple/list of 2 integers, specifying the 125 | width and height of the 2D convolution window. 126 | Can be a single integer to specify the same value for 127 | all spatial dimensions. 128 | strides: An integer or tuple/list of 2 integers, 129 | specifying the strides of the convolution along the width and height. 130 | Can be a single integer to specify the same value for 131 | all spatial dimensions. 132 | Specifying any stride value != 1 is incompatible with specifying 133 | any `dilation_rate` value != 1. 134 | padding: one of `'valid'` or `'same'` (case-insensitive). 135 | depth_multiplier: The number of depthwise convolution output channels 136 | for each input channel. 137 | The total number of depthwise convolution output 138 | channels will be equal to `filters_in * depth_multiplier`. 139 | data_format: A string, 140 | one of `channels_last` (default) or `channels_first`. 141 | The ordering of the dimensions in the inputs. 142 | `channels_last` corresponds to inputs with shape 143 | `(batch, height, width, channels)` while `channels_first` 144 | corresponds to inputs with shape 145 | `(batch, channels, height, width)`. 146 | It defaults to the `image_data_format` value found in your 147 | Keras config file at `~/.keras/keras.json`. 148 | If you never set it, then it will be 'channels_last'. 149 | activation: Activation function to use 150 | (see [activations](../activations.md)). 151 | If you don't specify anything, no activation is applied 152 | (ie. 'linear' activation: `a(x) = x`). 153 | use_bias: Boolean, whether the layer uses a bias vector. 154 | depthwise_initializer: Initializer for the depthwise kernel matrix 155 | (see [initializers](../initializers.md)). 156 | bias_initializer: Initializer for the bias vector 157 | (see [initializers](../initializers.md)). 158 | depthwise_regularizer: Regularizer function applied to 159 | the depthwise kernel matrix 160 | (see [regularizer](../regularizers.md)). 161 | bias_regularizer: Regularizer function applied to the bias vector 162 | (see [regularizer](../regularizers.md)). 163 | activity_regularizer: Regularizer function applied to 164 | the output of the layer (its 'activation'). 165 | (see [regularizer](../regularizers.md)). 166 | depthwise_constraint: Constraint function applied to 167 | the depthwise kernel matrix 168 | (see [constraints](../constraints.md)). 169 | bias_constraint: Constraint function applied to the bias vector 170 | (see [constraints](../constraints.md)). 171 | 172 | # Input shape 173 | 4D tensor with shape: 174 | `[batch, channels, rows, cols]` if data_format='channels_first' 175 | or 4D tensor with shape: 176 | `[batch, rows, cols, channels]` if data_format='channels_last'. 177 | 178 | # Output shape 179 | 4D tensor with shape: 180 | `[batch, filters, new_rows, new_cols]` if data_format='channels_first' 181 | or 4D tensor with shape: 182 | `[batch, new_rows, new_cols, filters]` if data_format='channels_last'. 183 | `rows` and `cols` values might have changed due to padding. 184 | """ 185 | 186 | def __init__(self, 187 | kernel_size, 188 | strides=(1, 1), 189 | padding='valid', 190 | depth_multiplier=1, 191 | data_format=None, 192 | activation=None, 193 | use_bias=True, 194 | depthwise_initializer='glorot_uniform', 195 | bias_initializer='zeros', 196 | depthwise_regularizer=None, 197 | bias_regularizer=None, 198 | activity_regularizer=None, 199 | depthwise_constraint=None, 200 | bias_constraint=None, 201 | **kwargs): 202 | super(DepthwiseConv2D, self).__init__( 203 | filters=None, 204 | kernel_size=kernel_size, 205 | strides=strides, 206 | padding=padding, 207 | data_format=data_format, 208 | activation=activation, 209 | use_bias=use_bias, 210 | bias_regularizer=bias_regularizer, 211 | activity_regularizer=activity_regularizer, 212 | bias_constraint=bias_constraint, 213 | **kwargs) 214 | self.depth_multiplier = depth_multiplier 215 | self.depthwise_initializer = initializers.get(depthwise_initializer) 216 | self.depthwise_regularizer = regularizers.get(depthwise_regularizer) 217 | self.depthwise_constraint = constraints.get(depthwise_constraint) 218 | self.bias_initializer = initializers.get(bias_initializer) 219 | 220 | def build(self, input_shape): 221 | if len(input_shape) < 4: 222 | raise ValueError('Inputs to `DepthwiseConv2D` should have rank 4. ' 223 | 'Received input shape:', str(input_shape)) 224 | if self.data_format == 'channels_first': 225 | channel_axis = 1 226 | else: 227 | channel_axis = 3 228 | if input_shape[channel_axis] is None: 229 | raise ValueError('The channel dimension of the inputs to ' 230 | '`DepthwiseConv2D` ' 231 | 'should be defined. Found `None`.') 232 | input_dim = int(input_shape[channel_axis]) 233 | depthwise_kernel_shape = (self.kernel_size[0], 234 | self.kernel_size[1], 235 | input_dim, 236 | self.depth_multiplier) 237 | 238 | self.depthwise_kernel = self.add_weight( 239 | shape=depthwise_kernel_shape, 240 | initializer=self.depthwise_initializer, 241 | name='depthwise_kernel', 242 | regularizer=self.depthwise_regularizer, 243 | constraint=self.depthwise_constraint) 244 | 245 | if self.use_bias: 246 | self.bias = self.add_weight(shape=(input_dim * self.depth_multiplier,), 247 | initializer=self.bias_initializer, 248 | name='bias', 249 | regularizer=self.bias_regularizer, 250 | constraint=self.bias_constraint) 251 | else: 252 | self.bias = None 253 | # Set input spec. 254 | self.input_spec = InputSpec(ndim=4, axes={channel_axis: input_dim}) 255 | self.built = True 256 | 257 | def call(self, inputs, training=None): 258 | outputs = K.depthwise_conv2d( 259 | inputs, 260 | self.depthwise_kernel, 261 | strides=self.strides, 262 | padding=self.padding, 263 | dilation_rate=self.dilation_rate, 264 | data_format=self.data_format) 265 | 266 | if self.bias: 267 | outputs = K.bias_add( 268 | outputs, 269 | self.bias, 270 | data_format=self.data_format) 271 | 272 | if self.activation is not None: 273 | return self.activation(outputs) 274 | 275 | return outputs 276 | 277 | def compute_output_shape(self, input_shape): 278 | if self.data_format == 'channels_first': 279 | rows = input_shape[2] 280 | cols = input_shape[3] 281 | out_filters = input_shape[1] * self.depth_multiplier 282 | elif self.data_format == 'channels_last': 283 | rows = input_shape[1] 284 | cols = input_shape[2] 285 | out_filters = input_shape[3] * self.depth_multiplier 286 | 287 | rows = conv_utils.conv_output_length(rows, self.kernel_size[0], 288 | self.padding, 289 | self.strides[0]) 290 | cols = conv_utils.conv_output_length(cols, self.kernel_size[1], 291 | self.padding, 292 | self.strides[1]) 293 | 294 | if self.data_format == 'channels_first': 295 | return (input_shape[0], out_filters, rows, cols) 296 | elif self.data_format == 'channels_last': 297 | return (input_shape[0], rows, cols, out_filters) 298 | 299 | def get_config(self): 300 | config = super(DepthwiseConv2D, self).get_config() 301 | config.pop('filters') 302 | config.pop('kernel_initializer') 303 | config.pop('kernel_regularizer') 304 | config.pop('kernel_constraint') 305 | config['depth_multiplier'] = self.depth_multiplier 306 | config['depthwise_initializer'] = initializers.serialize( 307 | self.depthwise_initializer) 308 | config['depthwise_regularizer'] = regularizers.serialize( 309 | self.depthwise_regularizer) 310 | config['depthwise_constraint'] = constraints.serialize( 311 | self.depthwise_constraint) 312 | return config 313 | 314 | 315 | def preprocess_input(x): 316 | """Preprocesses a numpy array encoding a batch of images. 317 | 318 | This function applies the "Inception" preprocessing which converts 319 | the RGB values from [0, 255] to [-1, 1]. Note that this preprocessing 320 | function is different from `imagenet_utils.preprocess_input()`. 321 | 322 | # Arguments 323 | x: a 4D numpy array consists of RGB values within [0, 255]. 324 | 325 | # Returns 326 | Preprocessed array. 327 | """ 328 | x /= 128. 329 | x -= 1. 330 | return x.astype(np.float32) 331 | 332 | 333 | def unprocess_input(x): 334 | """Unprocesses a numpy array encoding a batch of images. 335 | This function undoes the preprocessing which converts 336 | the RGB values from [0, 255] to [-1, 1]. 337 | 338 | # Arguments 339 | x: a 4D numpy array consists of RGB values within [0, 255]. 340 | 341 | # Returns 342 | Preprocessed array. 343 | """ 344 | x += 1. 345 | x *= 128. 346 | return x.astype(np.uint8) 347 | 348 | # This function is taken from the original tf repo. It ensures that all layers have a channel number that is divisible by 8 349 | # It can be seen here https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py 350 | 351 | 352 | def _make_divisible(v, divisor, min_value=None): 353 | if min_value is None: 354 | min_value = divisor 355 | new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) 356 | # Make sure that round down does not go down by more than 10%. 357 | if new_v < 0.9 * v: 358 | new_v += divisor 359 | return new_v 360 | 361 | 362 | def MobileNetV2(input_shape=None, 363 | alpha=1.0, 364 | depth_multiplier=1, 365 | include_top=True, 366 | weights='imagenet', 367 | input_tensor=None, 368 | classes=1000): 369 | """Instantiates the MobileNetV2 architecture. 370 | 371 | To load a MobileNetV2 model via `load_model`, import the custom 372 | objects `relu6` and pass them to the `custom_objects` parameter. 373 | E.g. 374 | model = load_model('mobilenet.h5', custom_objects={ 375 | 'relu6': mobilenet.relu6}) 376 | 377 | # Arguments 378 | input_shape: optional shape tuple, to be specified if you would 379 | like to use a model with an input img resolution that is not 380 | (224, 224, 3). 381 | It should have exactly 3 inputs channels (224, 224, 3). 382 | You can also omit this option if you would like 383 | to infer input_shape from an input_tensor. 384 | If you choose to include both input_tensor and input_shape then 385 | input_shape will be used if they match, if the shapes 386 | do not match then we will throw an error. 387 | E.g. `(160, 160, 3)` would be one valid value. 388 | alpha: controls the width of the network. This is known as the 389 | width multiplier in the MobileNetV2 paper. 390 | - If `alpha` < 1.0, proportionally decreases the number 391 | of filters in each layer. 392 | - If `alpha` > 1.0, proportionally increases the number 393 | of filters in each layer. 394 | - If `alpha` = 1, default number of filters from the paper 395 | are used at each layer. 396 | depth_multiplier: depth multiplier for depthwise convolution 397 | (also called the resolution multiplier) 398 | include_top: whether to include the fully-connected 399 | layer at the top of the network. 400 | weights: one of `None` (random initialization), 401 | 'imagenet' (pre-training on ImageNet), 402 | or the path to the weights file to be loaded. 403 | input_tensor: optional Keras tensor (i.e. output of 404 | `layers.Input()`) 405 | to use as image input for the model. 406 | classes: optional number of classes to classify images 407 | into, only to be specified if `include_top` is True, and 408 | if no `weights` argument is specified. 409 | 410 | # Returns 411 | A Keras model instance. 412 | 413 | # Raises 414 | ValueError: in case of invalid argument for `weights`, 415 | or invalid input shape or invalid depth_multiplier, alpha, 416 | rows when weights='imagenet' 417 | """ 418 | 419 | if not (weights in {'imagenet', None} or os.path.exists(weights)): 420 | raise ValueError('The `weights` argument should be either ' 421 | '`None` (random initialization), `imagenet` ' 422 | '(pre-training on ImageNet), ' 423 | 'or the path to the weights file to be loaded.') 424 | 425 | if weights == 'imagenet' and include_top and classes != 1000: 426 | raise ValueError('If using `weights` as ImageNet with `include_top` ' 427 | 'as true, `classes` should be 1000') 428 | 429 | # Determine proper input shape and default size. 430 | # If both input_shape and input_tensor are used, they should match 431 | if input_shape is not None and input_tensor is not None: 432 | try: 433 | is_input_t_tensor = K.is_keras_tensor(input_tensor) 434 | except ValueError: 435 | try: 436 | is_input_t_tensor = K.is_keras_tensor( 437 | get_source_inputs(input_tensor)) 438 | except ValueError: 439 | raise ValueError('input_tensor: ', input_tensor, 440 | 'is not type input_tensor') 441 | if is_input_t_tensor: 442 | if K.image_data_format == 'channels_first': 443 | if input_tensor._keras_shape[1] != input_shape[1]: 444 | raise ValueError('input_shape: ', input_shape, 445 | 'and input_tensor: ', input_tensor, 446 | 'do not meet the same shape requirements') 447 | else: 448 | if input_tensor._keras_shape[2] != input_shape[1]: 449 | raise ValueError('input_shape: ', input_shape, 450 | 'and input_tensor: ', input_tensor, 451 | 'do not meet the same shape requirements') 452 | else: 453 | raise ValueError('input_tensor specified: ', input_tensor, 454 | 'is not a keras tensor') 455 | 456 | # If input_shape is None, infer shape from input_tensor 457 | if input_shape is None and input_tensor is not None: 458 | 459 | try: 460 | K.is_keras_tensor(input_tensor) 461 | except ValueError: 462 | raise ValueError('input_tensor: ', input_tensor, 463 | 'is type: ', type(input_tensor), 464 | 'which is not a valid type') 465 | 466 | if input_shape is None and not K.is_keras_tensor(input_tensor): 467 | default_size = 224 468 | elif input_shape is None and K.is_keras_tensor(input_tensor): 469 | if K.image_data_format() == 'channels_first': 470 | rows = input_tensor._keras_shape[2] 471 | cols = input_tensor._keras_shape[3] 472 | else: 473 | rows = input_tensor._keras_shape[1] 474 | cols = input_tensor._keras_shape[2] 475 | 476 | if rows == cols and rows in [96, 128, 160, 192, 224]: 477 | default_size = rows 478 | else: 479 | default_size = 224 480 | 481 | # If input_shape is None and no input_tensor 482 | elif input_shape is None: 483 | default_size = 224 484 | 485 | # If input_shape is not None, assume default size 486 | else: 487 | if K.image_data_format() == 'channels_first': 488 | rows = input_shape[1] 489 | cols = input_shape[2] 490 | else: 491 | rows = input_shape[0] 492 | cols = input_shape[1] 493 | 494 | if rows == cols and rows in [96, 128, 160, 192, 224]: 495 | default_size = rows 496 | else: 497 | default_size = 224 498 | 499 | input_shape = _obtain_input_shape(input_shape, 500 | default_size=default_size, 501 | min_size=32, 502 | data_format=K.image_data_format(), 503 | require_flatten=include_top, 504 | weights=weights) 505 | 506 | if K.image_data_format() == 'channels_last': 507 | row_axis, col_axis = (0, 1) 508 | else: 509 | row_axis, col_axis = (1, 2) 510 | rows = input_shape[row_axis] 511 | cols = input_shape[col_axis] 512 | 513 | if weights == 'imagenet': 514 | if depth_multiplier != 1: 515 | raise ValueError('If imagenet weights are being loaded, ' 516 | 'depth multiplier must be 1') 517 | 518 | if alpha not in [0.35, 0.50, 0.75, 1.0, 1.3, 1.4]: 519 | raise ValueError('If imagenet weights are being loaded, ' 520 | 'alpha can be one of' 521 | '`0.25`, `0.50`, `0.75` or `1.0` only.') 522 | 523 | if rows != cols or rows not in [96, 128, 160, 192, 224]: 524 | if rows is None: 525 | rows = 224 526 | warnings.warn('MobileNet shape is undefined.' 527 | ' Weights for input shape' 528 | '(224, 224) will be loaded.') 529 | else: 530 | raise ValueError('If imagenet weights are being loaded, ' 531 | 'input must have a static square shape' 532 | '(one of (96, 96), (128, 128), (160, 160),' 533 | '(192, 192), or (224, 224)).' 534 | 'Input shape provided = %s' % (input_shape,)) 535 | 536 | if K.image_data_format() != 'channels_last': 537 | warnings.warn('The MobileNet family of models is only available ' 538 | 'for the input data format "channels_last" ' 539 | '(width, height, channels). ' 540 | 'However your settings specify the default ' 541 | 'data format "channels_first" (channels, width, height).' 542 | ' You should set `image_data_format="channels_last"` ' 543 | 'in your Keras config located at ~/.keras/keras.json. ' 544 | 'The model being returned right now will expect inputs ' 545 | 'to follow the "channels_last" data format.') 546 | K.set_image_data_format('channels_last') 547 | old_data_format = 'channels_first' 548 | else: 549 | old_data_format = None 550 | 551 | if input_tensor is None: 552 | img_input = Input(shape=input_shape) 553 | else: 554 | if not K.is_keras_tensor(input_tensor): 555 | img_input = Input(tensor=input_tensor, shape=input_shape) 556 | else: 557 | img_input = input_tensor 558 | 559 | first_block_filters = _make_divisible(32 * alpha, 8) 560 | x = Conv2D(first_block_filters, 561 | kernel_size=3, 562 | strides=(2, 2), padding='same', 563 | use_bias=False, name='Conv1')(img_input) 564 | x = BatchNormalization(epsilon=1e-3, momentum=0.999, name='bn_Conv1')(x) 565 | x = Activation(relu6, name='Conv1_relu')(x) 566 | 567 | x = _first_inverted_res_block(x, 568 | filters=16, 569 | alpha=alpha, 570 | stride=1, 571 | expansion=1, 572 | block_id=0) 573 | 574 | x = _inverted_res_block(x, filters=24, alpha=alpha, stride=2, 575 | expansion=6, block_id=1) 576 | x = _inverted_res_block(x, filters=24, alpha=alpha, stride=1, 577 | expansion=6, block_id=2) 578 | 579 | x = _inverted_res_block(x, filters=32, alpha=alpha, stride=2, 580 | expansion=6, block_id=3) 581 | x = _inverted_res_block(x, filters=32, alpha=alpha, stride=1, 582 | expansion=6, block_id=4) 583 | x = _inverted_res_block(x, filters=32, alpha=alpha, stride=1, 584 | expansion=6, block_id=5) 585 | 586 | x = _inverted_res_block(x, filters=64, alpha=alpha, stride=2, 587 | expansion=6, block_id=6) 588 | x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1, 589 | expansion=6, block_id=7) 590 | x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1, 591 | expansion=6, block_id=8) 592 | x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1, 593 | expansion=6, block_id=9) 594 | 595 | x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1, 596 | expansion=6, block_id=10) 597 | x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1, 598 | expansion=6, block_id=11) 599 | x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1, 600 | expansion=6, block_id=12) 601 | 602 | x = _inverted_res_block(x, filters=160, alpha=alpha, stride=2, 603 | expansion=6, block_id=13) 604 | x = _inverted_res_block(x, filters=160, alpha=alpha, stride=1, 605 | expansion=6, block_id=14) 606 | x = _inverted_res_block(x, filters=160, alpha=alpha, stride=1, 607 | expansion=6, block_id=15) 608 | 609 | x = _inverted_res_block(x, filters=320, alpha=alpha, stride=1, 610 | expansion=6, block_id=16) 611 | 612 | # no alpha applied to last conv as stated in the paper: 613 | # if the width multiplier is greater than 1 we 614 | # increase the number of output channels 615 | if alpha > 1.0: 616 | last_block_filters = _make_divisible(1280 * alpha, 8) 617 | else: 618 | last_block_filters = 1280 619 | 620 | x = Conv2D(last_block_filters, 621 | kernel_size=1, 622 | use_bias=False, 623 | name='Conv_1')(x) 624 | x = BatchNormalization(epsilon=1e-3, momentum=0.999, name='Conv_1_bn')(x) 625 | x = Activation(relu6, name='out_relu')(x) 626 | 627 | if include_top: 628 | x = GlobalAveragePooling2D()(x) 629 | x = Dense(classes, activation='softmax', 630 | use_bias=True, name='Logits')(x) 631 | 632 | # Ensure that the model takes into account 633 | # any potential predecessors of `input_tensor`. 634 | if input_tensor is not None: 635 | inputs = get_source_inputs(input_tensor) 636 | else: 637 | inputs = img_input 638 | 639 | # Create model. 640 | model = Model(inputs, x, name='mobilenetv2_%0.2f_%s' % (alpha, rows)) 641 | 642 | # load weights 643 | if weights == 'imagenet': 644 | if K.image_data_format() == 'channels_first': 645 | raise ValueError('Weights for "channels_first" format ' 646 | 'are not available.') 647 | 648 | if include_top: 649 | model_name = 'mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' + \ 650 | str(alpha) + '_' + str(rows) + '.h5' 651 | weigh_path = BASE_WEIGHT_PATH + model_name 652 | weights_path = get_file(model_name, weigh_path, 653 | cache_subdir='models') 654 | else: 655 | model_name = 'mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' + \ 656 | str(alpha) + '_' + str(rows) + '_no_top' + '.h5' 657 | weigh_path = BASE_WEIGHT_PATH + model_name 658 | weights_path = get_file(model_name, weigh_path, 659 | cache_subdir='models') 660 | model.load_weights(weights_path) 661 | elif weights is not None: 662 | model.load_weights(weights) 663 | 664 | if old_data_format: 665 | K.set_image_data_format(old_data_format) 666 | return model 667 | 668 | 669 | def _inverted_res_block(inputs, expansion, stride, alpha, filters, block_id): 670 | in_channels = inputs._keras_shape[-1] 671 | prefix = 'features.' + str(block_id) + '.conv.' 672 | pointwise_conv_filters = int(filters * alpha) 673 | pointwise_filters = _make_divisible(pointwise_conv_filters, 8) 674 | # Expand 675 | 676 | x = Conv2D(expansion * in_channels, kernel_size=1, padding='same', 677 | use_bias=False, activation=None, 678 | name='mobl%d_conv_expand' % block_id)(inputs) 679 | x = BatchNormalization(epsilon=1e-3, momentum=0.999, 680 | name='bn%d_conv_bn_expand' % 681 | block_id)(x) 682 | x = Activation(relu6, name='conv_%d_relu' % block_id)(x) 683 | 684 | # Depthwise 685 | x = DepthwiseConv2D(kernel_size=3, strides=stride, activation=None, 686 | use_bias=False, padding='same', 687 | name='mobl%d_conv_depthwise' % block_id)(x) 688 | x = BatchNormalization(epsilon=1e-3, momentum=0.999, 689 | name='bn%d_conv_depthwise' % block_id)(x) 690 | 691 | x = Activation(relu6, name='conv_dw_%d_relu' % block_id)(x) 692 | 693 | # Project 694 | x = Conv2D(pointwise_filters, 695 | kernel_size=1, padding='same', use_bias=False, activation=None, 696 | name='mobl%d_conv_project' % block_id)(x) 697 | x = BatchNormalization(epsilon=1e-3, momentum=0.999, 698 | name='bn%d_conv_bn_project' % block_id)(x) 699 | 700 | if in_channels == pointwise_filters and stride == 1: 701 | return Add(name='res_connect_' + str(block_id))([inputs, x]) 702 | 703 | return x 704 | 705 | 706 | def _first_inverted_res_block(inputs, 707 | expansion, stride, 708 | alpha, filters, block_id): 709 | in_channels = inputs._keras_shape[-1] 710 | prefix = 'features.' + str(block_id) + '.conv.' 711 | pointwise_conv_filters = int(filters * alpha) 712 | pointwise_filters = _make_divisible(pointwise_conv_filters, 8) 713 | 714 | # Depthwise 715 | x = DepthwiseConv2D(kernel_size=3, 716 | strides=stride, activation=None, 717 | use_bias=False, padding='same', 718 | name='mobl%d_conv_depthwise' % 719 | block_id)(inputs) 720 | x = BatchNormalization(epsilon=1e-3, momentum=0.999, 721 | name='bn%d_conv_depthwise' % 722 | block_id)(x) 723 | x = Activation(relu6, name='conv_dw_%d_relu' % block_id)(x) 724 | 725 | # Project 726 | x = Conv2D(pointwise_filters, 727 | kernel_size=1, 728 | padding='same', 729 | use_bias=False, 730 | activation=None, 731 | name='mobl%d_conv_project' % 732 | block_id)(x) 733 | x = BatchNormalization(epsilon=1e-3, momentum=0.999, 734 | name='bn%d_conv_project' % 735 | block_id)(x) 736 | 737 | if in_channels == pointwise_filters and stride == 1: 738 | return Add(name='res_connect_' + str(block_id))([inputs, x]) 739 | 740 | return x 741 | -------------------------------------------------------------------------------- /models_to_load.py: -------------------------------------------------------------------------------- 1 | models_to_load = [ 2 | (1.4, 224), 3 | (1.3, 224), 4 | (1.0, 224), 5 | (1.0, 192), 6 | (1.0, 160), 7 | (1.0, 128), 8 | (1.0, 96), 9 | (0.75, 224), 10 | (0.75, 192), 11 | (0.75, 160), 12 | (0.75, 128), 13 | (0.75, 96), 14 | (0.5, 224), 15 | (0.5, 192), 16 | (0.5, 160), 17 | (0.5, 128), 18 | (0.5, 96), 19 | (0.35, 224), 20 | (0.35, 192), 21 | (0.35, 160), 22 | (0.35, 128), 23 | (0.35, 96), 24 | ] 25 | -------------------------------------------------------------------------------- /panda.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonathanCMitchell/mobilenet_v2_keras/2474973db694f444a8472f6d2e369c71e104cd1c/panda.jpg -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # MobileNetV2 2 | This folder contains building code for MobileNetV2, based on 3 | [MobileNetV2: Inverted Residuals and Linear Bottlenecks](https://arxiv.org/abs/1801.04381) 4 | 5 | 6 | This model file has been pushed to my keras fork which you can see [here](https://github.com/JonathanCMitchell/keras). 7 | You can also view the active pull request to keras [here](https://github.com/keras-team/keras/pull/10047) 8 | A lot of the techniques and strategies developed for weight extraction in this repository was taken from [here](https://github.com/kentsommer/keras-inception-resnetV2) 9 | 10 | # Performance 11 | ## Latency 12 | This is the timing of [MobileNetV1](../mobilenet_v1.md) vs MobileNetV2 using 13 | TF-Lite on the large core of Pixel 1 phone. 14 | 15 | * ![mnet_v1_vs_v2_pixel1_latency](https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mnet_v1_vs_v2_pixel1_latency.png) 16 | 17 | # This model checkpoint was downloaded from the following source: 18 | | [mobilenet_v2_1.0_224](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.0_224.tgz) | 300 | 3.47 | 71.8 | 91.0 | 73.8 19 | 20 | 21 | First, I chose to extract all the weights from Tensorflows [repo](https://github.com/tensorflow/models/tree/master/research/slim/nets/mobilenet) and save the depth_multiplier values and input resolutions to a file `models_to_load.py`. 22 | The for each model in `models_to_load.py`, I extracted the weights using file `extract_weights.py`, utilizing the checkpoints provided, and saved the weights to a directory called 'weights'. Then I used the file `load_weights_multiple.py` to set the weights of the corresponding keras model using keras's built in `set_weights` function. I used a pickle file that was generated using `extract_weights.py` to serve as a guide and provide meta data about each layer so that I could align them. Each weight is checked for: 23 | Shape, mod (expand, depthwise, or project), meta: (weights or batch norm parameters), and size. 24 | ### The model is then tested inside `test_mobilenet.py`. This model is tested against the tensorflow slim model that can be found [here](https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.md) 25 | 26 | to use this model: 27 | ``` 28 | from keras.applications.mobilenetv2 import MobileNetV2 29 | from keras.layers import Input 30 | input_tensor = Input(shape=(224,224, 3)) # or you could put (None, None, 3) for shape 31 | model = MobileNetV2(input_tensor = input_tensor, alpha = 1.0, include_top = True, weights=’imagenet’) 32 | 33 | # Now you have a fully loaded model. 34 | ``` 35 | 36 | The model only works with depth_multiplier = 1, although the alpha parameter is able to specify width_multipliers if they are included in [0.35, 0.50, 0.75, 1.0] 37 | Additionally, only square input sizes included in [96, 128, 160, 182, 224] can be used. 38 | 39 | The `include_top` parameter can be used to grab the full network, if you set it to false, you will grab the base network before the pooling operation and fully connected layer. 40 | 41 | 42 | # Pretrained models 43 | Models can be found [here](https://github.com/JonathanCMitchell/mobilenet_v2_keras/releases) 44 | ## Imagenet Checkpoints 45 | 46 | * These results are taken from [tfmobilenet](https://github.com/tensorflow/models/tree/master/research/slim/nets/mobilenet) but I estimate ours are similar in performance. Except for the Pixel 1 inference time. 47 | 48 | 49 | ## Inference results in (test_mobilenet.py) 50 | You can grab and load up the pickle file `test_results.p` or you can read the results below: Please note that there are subtle differences between the TF models and the Keras models in the testing procedure, these are due to 51 | the differences in how Keras performs softmax, and the normalization that occurs after we pop out the first tensorflow logit (that is the background class) and re-normalize. 52 | 53 | 54 | For questions, comments, and concerns please reach me at jmitchell1991@gmail.com. 55 | 56 | ## Test results (1001 class) 57 | ``` 58 | test_results: [{ 59 | 'rows': 224, 60 | 'vector_difference': array([ 61 | [3.06442744e-05, 1.03940765e-05, 6.52904509e-06, ..., 62 | 1.86086560e-04, 1.36749877e-05, 2.29768884e-05 63 | ] 64 | ], dtype = float32), 65 | 'pred_keras_score': 389, 66 | 'alpha': 1.4, 67 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 68 | 'pred_tf_score': 389, 69 | 'inference_time_tf': 0.584200382232666, 70 | 'inference_time_keras': 2.468465566635132, 71 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 72 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.4_224.h5', 73 | 'max_vector_difference': 0.0442425, 74 | 'preds_agree': True 75 | }, { 76 | 'rows': 224, 77 | 'vector_difference': array([ 78 | [1.2600726e-04, 1.9243037e-04, 8.6632121e-05, ..., 1.7771832e-05, 79 | 1.2540509e-04, 8.6921602e-05 80 | ] 81 | ], dtype = float32), 82 | 'pred_keras_score': 389, 83 | 'alpha': 1.3, 84 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 85 | 'pred_tf_score': 389, 86 | 'inference_time_tf': 0.7864320278167725, 87 | 'inference_time_keras': 0.48574304580688477, 88 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 89 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.3_224.h5', 90 | 'max_vector_difference': 0.1639086, 91 | 'preds_agree': True 92 | }, { 93 | 'rows': 224, 94 | 'vector_difference': array([ 95 | [2.6156631e-06, 8.9272799e-06, 9.9282261e-07, ..., 2.7298967e-05, 96 | 7.0550705e-06, 1.7008846e-05 97 | ] 98 | ], dtype = float32), 99 | 'pred_keras_score': 389, 100 | 'alpha': 1.0, 101 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 102 | 'pred_tf_score': 389, 103 | 'inference_time_tf': 0.9191036224365234, 104 | 'inference_time_keras': 0.5298285484313965, 105 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 106 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224.h5', 107 | 'max_vector_difference': 0.008201845, 108 | 'preds_agree': True 109 | }, { 110 | 'rows': 192, 111 | 'vector_difference': array([ 112 | [2.9478622e-05, 3.1182157e-05, 1.6525744e-05, ..., 3.2548003e-05, 113 | 2.3264165e-05, 1.2547210e-04 114 | ] 115 | ], dtype = float32), 116 | 'pred_keras_score': 389, 117 | 'alpha': 1.0, 118 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 119 | 'pred_tf_score': 389, 120 | 'inference_time_tf': 1.0997955799102783, 121 | 'inference_time_keras': 0.609818696975708, 122 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 123 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_192.h5', 124 | 'max_vector_difference': 0.03410864, 125 | 'preds_agree': True 126 | }, { 127 | 'rows': 160, 128 | 'vector_difference': array([ 129 | [1.9924228e-06, 1.1813272e-05, 2.6646510e-05, ..., 2.1615942e-06, 130 | 7.3942429e-06, 2.8581535e-06 131 | ] 132 | ], dtype = float32), 133 | 'pred_keras_score': 389, 134 | 'alpha': 1.0, 135 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 136 | 'pred_tf_score': 389, 137 | 'inference_time_tf': 1.3430328369140625, 138 | 'inference_time_keras': 0.6801567077636719, 139 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 140 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_160.h5', 141 | 'max_vector_difference': 0.0024641072, 142 | 'preds_agree': True 143 | }, { 144 | 'rows': 128, 145 | 'vector_difference': array([ 146 | [1.9651561e-05, 7.6118726e-05, 1.1588483e-05, ..., 2.3098060e-05, 147 | 5.0026181e-05, 2.3178840e-05 148 | ] 149 | ], dtype = float32), 150 | 'pred_keras_score': 389, 151 | 'alpha': 1.0, 152 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 153 | 'pred_tf_score': 389, 154 | 'inference_time_tf': 1.6118056774139404, 155 | 'inference_time_keras': 0.7277970314025879, 156 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 157 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_128.h5', 158 | 'max_vector_difference': 0.022542655, 159 | 'preds_agree': True 160 | }, { 161 | 'rows': 96, 162 | 'vector_difference': array([ 163 | [4.1969906e-07, 6.2867985e-06, 7.6682009e-06, ..., 9.5812502e-06, 164 | 5.6552781e-06, 2.0793846e-04 165 | ] 166 | ], dtype = float32), 167 | 'pred_keras_score': 389, 168 | 'alpha': 1.0, 169 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 170 | 'pred_tf_score': 389, 171 | 'inference_time_tf': 1.759774923324585, 172 | 'inference_time_keras': 0.8093435764312744, 173 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 174 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_96.h5', 175 | 'max_vector_difference': 0.009057283, 176 | 'preds_agree': True 177 | }, { 178 | 'rows': 224, 179 | 'vector_difference': array([ 180 | [1.9891046e-05, 3.5158137e-05, 7.4801164e-06, ..., 3.1968251e-05, 181 | 1.8171089e-05, 2.4247890e-04 182 | ] 183 | ], dtype = float32), 184 | 'pred_keras_score': 389, 185 | 'alpha': 0.75, 186 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 187 | 'pred_tf_score': 389, 188 | 'inference_time_tf': 1.988135576248169, 189 | 'inference_time_keras': 0.907604455947876, 190 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 191 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.75_224.h5', 192 | 'max_vector_difference': 0.027664006, 193 | 'preds_agree': True 194 | }, { 195 | 'rows': 192, 196 | 'vector_difference': array([ 197 | [2.4571000e-06, 2.3607154e-06, 1.0745002e-06, ..., 7.6524229e-06, 198 | 2.5452073e-07, 6.0848397e-06 199 | ] 200 | ], dtype = float32), 201 | 'pred_keras_score': 389, 202 | 'alpha': 0.75, 203 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 204 | 'pred_tf_score': 389, 205 | 'inference_time_tf': 2.1681172847747803, 206 | 'inference_time_keras': 0.9792499542236328, 207 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 208 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.75_192.h5', 209 | 'max_vector_difference': 0.0048509836, 210 | 'preds_agree': True 211 | }, { 212 | 'rows': 160, 213 | 'vector_difference': array([ 214 | [3.3421951e-05, 2.8039271e-05, 2.6024140e-05, ..., 1.8016202e-05, 215 | 2.8351524e-06, 3.2267882e-05 216 | ] 217 | ], dtype = float32), 218 | 'pred_keras_score': 389, 219 | 'alpha': 0.75, 220 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 221 | 'pred_tf_score': 389, 222 | 'inference_time_tf': 2.4570868015289307, 223 | 'inference_time_keras': 1.0636296272277832, 224 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 225 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.75_160.h5', 226 | 'max_vector_difference': 0.04817468, 227 | 'preds_agree': True 228 | }, { 229 | 'rows': 128, 230 | 'vector_difference': array([ 231 | [5.7490397e-05, 2.3515218e-05, 6.2765699e-05, ..., 6.3877407e-05, 232 | 2.4530049e-05, 1.9020826e-04 233 | ] 234 | ], dtype = float32), 235 | 'pred_keras_score': 389, 236 | 'alpha': 0.75, 237 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 238 | 'pred_tf_score': 389, 239 | 'inference_time_tf': 2.712172746658325, 240 | 'inference_time_keras': 1.1841137409210205, 241 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 242 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.75_128.h5', 243 | 'max_vector_difference': 0.10443801, 244 | 'preds_agree': True 245 | }, { 246 | 'rows': 96, 247 | 'vector_difference': array([ 248 | [1.91572035e-05, 1.15800685e-05, 4.44647230e-06, ..., 249 | 9.32329567e-06, 4.12614900e-05, 3.88475746e-06 250 | ] 251 | ], dtype = float32), 252 | 'pred_keras_score': 389, 253 | 'alpha': 0.75, 254 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 255 | 'pred_tf_score': 389, 256 | 'inference_time_tf': 2.947575807571411, 257 | 'inference_time_keras': 1.2835781574249268, 258 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 259 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.75_96.h5', 260 | 'max_vector_difference': 0.02189435, 261 | 'preds_agree': True 262 | }, { 263 | 'rows': 224, 264 | 'vector_difference': array([ 265 | [2.7594273e-05, 1.8192208e-05, 5.0051967e-06, ..., 2.2260952e-05, 266 | 1.0851298e-05, 2.0575267e-04 267 | ] 268 | ], dtype = float32), 269 | 'pred_keras_score': 389, 270 | 'alpha': 0.5, 271 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 272 | 'pred_tf_score': 389, 273 | 'inference_time_tf': 3.234971761703491, 274 | 'inference_time_keras': 1.4089748859405518, 275 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 276 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.5_224.h5', 277 | 'max_vector_difference': 0.03057003, 278 | 'preds_agree': True 279 | }, { 280 | 'rows': 192, 281 | 'vector_difference': array([ 282 | [6.4921463e-05, 1.8974228e-05, 9.5047453e-06, ..., 2.7531139e-05, 283 | 1.5725660e-05, 1.3637640e-04 284 | ] 285 | ], dtype = float32), 286 | 'pred_keras_score': 389, 287 | 'alpha': 0.5, 288 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 289 | 'pred_tf_score': 389, 290 | 'inference_time_tf': 3.471426010131836, 291 | 'inference_time_keras': 1.5226759910583496, 292 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 293 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.5_192.h5', 294 | 'max_vector_difference': 0.10459131, 295 | 'preds_agree': True 296 | }, { 297 | 'rows': 160, 298 | 'vector_difference': array([ 299 | [2.1799133e-05, 2.6465546e-05, 9.7673910e-07, ..., 4.9333670e-05, 300 | 1.2139077e-05, 3.4930854e-05 301 | ] 302 | ], dtype = float32), 303 | 'pred_keras_score': 389, 304 | 'alpha': 0.5, 305 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 306 | 'pred_tf_score': 389, 307 | 'inference_time_tf': 3.7173619270324707, 308 | 'inference_time_keras': 1.6358251571655273, 309 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 310 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.5_160.h5', 311 | 'max_vector_difference': 0.041921377, 312 | 'preds_agree': True 313 | }, { 314 | 'rows': 128, 315 | 'vector_difference': array([ 316 | [1.6039543e-05, 3.6521582e-05, 2.0016232e-06, ..., 7.7442382e-06, 317 | 1.3480414e-05, 1.9661791e-05 318 | ] 319 | ], dtype = float32), 320 | 'pred_keras_score': 389, 321 | 'alpha': 0.5, 322 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 323 | 'pred_tf_score': 389, 324 | 'inference_time_tf': 3.96859073638916, 325 | 'inference_time_keras': 1.7203476428985596, 326 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 327 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.5_128.h5', 328 | 'max_vector_difference': 0.015232503, 329 | 'preds_agree': True 330 | }, { 331 | 'rows': 96, 332 | 'vector_difference': array([ 333 | [5.3964366e-05, 4.3542765e-05, 1.9309173e-05, ..., 1.8680606e-05, 334 | 1.7692482e-05, 1.0907562e-03 335 | ] 336 | ], dtype = float32), 337 | 'pred_keras_score': 389, 338 | 'alpha': 0.5, 339 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 340 | 'pred_tf_score': 389, 341 | 'inference_time_tf': 4.313321590423584, 342 | 'inference_time_keras': 1.8665125370025635, 343 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 344 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.5_96.h5', 345 | 'max_vector_difference': 0.10974246, 346 | 'preds_agree': True 347 | }, { 348 | 'rows': 224, 349 | 'vector_difference': array([ 350 | [1.8852079e-05, 9.4596544e-07, 2.4118672e-06, ..., 3.3465330e-06, 351 | 3.7501377e-07, 2.0572159e-05 352 | ] 353 | ], dtype = float32), 354 | 'pred_keras_score': 389, 355 | 'alpha': 0.35, 356 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 357 | 'pred_tf_score': 389, 358 | 'inference_time_tf': 4.690372705459595, 359 | 'inference_time_keras': 1.9682881832122803, 360 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 361 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.35_224.h5', 362 | 'max_vector_difference': 0.018127322, 363 | 'preds_agree': True 364 | }, { 365 | 'rows': 192, 366 | 'vector_difference': array([ 367 | [5.89300471e-05, 1.10333494e-05, 2.08540587e-06, ..., 368 | 1.03199854e-04, 1.02247395e-05, 3.36650060e-04 369 | ] 370 | ], dtype = float32), 371 | 'pred_keras_score': 389, 372 | 'alpha': 0.35, 373 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 374 | 'pred_tf_score': 389, 375 | 'inference_time_tf': 4.746210336685181, 376 | 'inference_time_keras': 2.11893892288208, 377 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 378 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.35_192.h5', 379 | 'max_vector_difference': 0.031285435, 380 | 'preds_agree': True 381 | }, { 382 | 'rows': 160, 383 | 'vector_difference': array([ 384 | [4.2083520e-06, 5.5578494e-07, 3.2600292e-07, ..., 4.2584943e-06, 385 | 5.7042635e-06, 4.6083354e-05 386 | ] 387 | ], dtype = float32), 388 | 'pred_keras_score': 389, 389 | 'alpha': 0.35, 390 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 391 | 'pred_tf_score': 389, 392 | 'inference_time_tf': 5.078217029571533, 393 | 'inference_time_keras': 2.330106735229492, 394 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 395 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.35_160.h5', 396 | 'max_vector_difference': 0.008356452, 397 | 'preds_agree': True 398 | }, { 399 | 'rows': 128, 400 | 'vector_difference': array([ 401 | [1.0704320e-05, 5.3489948e-06, 7.2533185e-06, ..., 1.6965925e-05, 402 | 2.3451194e-06, 2.8069786e-05 403 | ] 404 | ], dtype = float32), 405 | 'pred_keras_score': 389, 406 | 'alpha': 0.35, 407 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 408 | 'pred_tf_score': 389, 409 | 'inference_time_tf': 5.38210391998291, 410 | 'inference_time_keras': 2.5224623680114746, 411 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 412 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.35_128.h5', 413 | 'max_vector_difference': 0.012936056, 414 | 'preds_agree': True 415 | }, { 416 | 'rows': 96, 417 | 'vector_difference': array([ 418 | [6.1693572e-06, 3.9814022e-06, 3.0157253e-07, ..., 4.4240751e-06, 419 | 2.7236201e-06, 7.0944225e-06 420 | ] 421 | ], dtype = float32), 422 | 'pred_keras_score': 389, 423 | 'alpha': 0.35, 424 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 425 | 'pred_tf_score': 389, 426 | 'inference_time_tf': 5.456079721450806, 427 | 'inference_time_keras': 2.454572916030884, 428 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 429 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.35_96.h5', 430 | 'max_vector_difference': 0.018446982, 431 | 'preds_agree': True 432 | }] 433 | ``` 434 | 435 | ## Test results: 1000 classes 436 | ``` 437 | [{ 438 | 'rows': 224, 439 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 440 | 'pred_keras_score': 389, 441 | 'pred_tf_score': 389, 442 | 'preds_agree': True, 443 | 'alpha': 1.4, 444 | 'inference_time_keras': 0.2321312427520752, 445 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropodamelanoleuca', 446 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.4_224.h5', 447 | 'max_vector_difference': 0.042831063, 448 | 'inference_time_tf': 0.5365610122680664 449 | }, { 450 | 'rows': 224, 451 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 452 | 'pred_keras_score': 389, 453 | 'pred_tf_score': 389, 454 | 'preds_agree': True, 455 | 'alpha': 1.3, 456 | 'inference_time_keras': 0.309173583984375, 457 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 458 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.3_224.h5', 459 | 'max_vector_difference': 0.1633901, 460 | 'inference_time_tf': 0.6658704280853271 461 | }, { 462 | 'rows': 224, 463 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 464 | 'pred_keras_score': 389, 465 | 'pred_tf_score': 389, 466 | 'preds_agree': True, 467 | 'alpha': 1.0, 468 | 'inference_time_keras': 0.36829209327697754, 469 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 470 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224.h5', 471 | 'max_vector_difference': 0.00815512, 472 | 'inference_time_tf': 0.8123471736907959 473 | }, { 474 | 'rows': 192, 475 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 476 | 'pred_keras_score': 389, 477 | 'pred_tf_score': 389, 478 | 'preds_agree': True, 479 | 'alpha': 1.0, 480 | 'inference_time_keras': 0.43697261810302734, 481 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 482 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_192.h5', 483 | 'max_vector_difference': 0.03405291, 484 | 'inference_time_tf': 1.0188896656036377 485 | }, { 486 | 'rows': 160, 487 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 488 | 'pred_keras_score': 389, 489 | 'pred_tf_score': 389, 490 | 'preds_agree': True, 491 | 'alpha': 1.0, 492 | 'inference_time_keras': 0.5213620662689209, 493 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 494 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_160.h5', 495 | 'max_vector_difference': 0.0024634155, 496 | 'inference_time_tf': 1.231605052947998 497 | }, { 498 | 'rows': 128, 499 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 500 | 'pred_keras_score': 389, 501 | 'pred_tf_score': 389, 502 | 'preds_agree': True, 503 | 'alpha': 1.0, 504 | 'inference_time_keras': 0.590782642364502, 505 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 506 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_128.h5', 507 | 'max_vector_difference': 0.02254337, 508 | 'inference_time_tf': 1.4477722644805908 509 | }, { 510 | 'rows': 96, 511 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 512 | 'pred_keras_score': 389, 513 | 'pred_tf_score': 389, 514 | 'preds_agree': True, 515 | 'alpha': 1.0, 516 | 'inference_time_keras': 0.6745755672454834, 517 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 518 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_96.h5', 519 | 'max_vector_difference': 0.009703398, 520 | 'inference_time_tf': 1.725059986114502 521 | }, { 522 | 'rows': 224, 523 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 524 | 'pred_keras_score': 389, 525 | 'pred_tf_score': 389, 526 | 'preds_agree': True, 527 | 'alpha': 0.75, 528 | 'inference_time_keras': 0.7602894306182861, 529 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 530 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.75_224.h5', 531 | 'max_vector_difference': 0.03044498, 532 | 'inference_time_tf': 1.8605682849884033 533 | }, { 534 | 'rows': 192, 535 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 536 | 'pred_keras_score': 389, 537 | 'pred_tf_score': 389, 538 | 'preds_agree': True, 539 | 'alpha': 0.75, 540 | 'inference_time_keras': 0.8398780822753906, 541 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 542 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.75_192.h5', 543 | 'max_vector_difference': 0.0047159195, 544 | 'inference_time_tf': 2.006946086883545 545 | }, { 546 | 'rows': 160, 547 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 548 | 'pred_keras_score': 389, 549 | 'pred_tf_score': 389, 550 | 'preds_agree': True, 551 | 'alpha': 0.75, 552 | 'inference_time_keras': 0.9440453052520752, 553 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 554 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.75_160.h5', 555 | 'max_vector_difference': 0.048369706, 556 | 'inference_time_tf': 2.309004545211792 557 | }, { 558 | 'rows': 128, 559 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 560 | 'pred_keras_score': 389, 561 | 'pred_tf_score': 389, 562 | 'preds_agree': True, 563 | 'alpha': 0.75, 564 | 'inference_time_keras': 1.0247721672058105, 565 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 566 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.75_128.h5', 567 | 'max_vector_difference': 0.10489011, 568 | 'inference_time_tf': 2.551898717880249 569 | }, { 570 | 'rows': 96, 571 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 572 | 'pred_keras_score': 389, 573 | 'pred_tf_score': 389, 574 | 'preds_agree': True, 575 | 'alpha': 0.75, 576 | 'inference_time_keras': 1.1196868419647217, 577 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 578 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.75_96.h5', 579 | 'max_vector_difference': 0.020857051, 580 | 'inference_time_tf': 2.7527146339416504 581 | }, { 582 | 'rows': 224, 583 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 584 | 'pred_keras_score': 389, 585 | 'pred_tf_score': 389, 586 | 'preds_agree': True, 587 | 'alpha': 0.5, 588 | 'inference_time_keras': 1.230698823928833, 589 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 590 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.5_224.h5', 591 | 'max_vector_difference': 0.031421363, 592 | 'inference_time_tf': 2.9628419876098633 593 | }, { 594 | 'rows': 192, 595 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 596 | 'pred_keras_score': 389, 597 | 'pred_tf_score': 389, 598 | 'preds_agree': True, 599 | 'alpha': 0.5, 600 | 'inference_time_keras': 1.331193447113037, 601 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 602 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.5_192.h5', 603 | 'max_vector_difference': 0.10464072, 604 | 'inference_time_tf': 3.2052600383758545 605 | }, { 606 | 'rows': 160, 607 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 608 | 'pred_keras_score': 389, 609 | 'pred_tf_score': 389, 610 | 'preds_agree': True, 611 | 'alpha': 0.5, 612 | 'inference_time_keras': 1.4506702423095703, 613 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 614 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.5_160.h5', 615 | 'max_vector_difference': 0.04293281, 616 | 'inference_time_tf': 3.4141736030578613 617 | }, { 618 | 'rows': 128, 619 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 620 | 'pred_keras_score': 389, 621 | 'pred_tf_score': 389, 622 | 'preds_agree': True, 623 | 'alpha': 0.5, 624 | 'inference_time_keras': 1.546645164489746, 625 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 626 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.5_128.h5', 627 | 'max_vector_difference': 0.012662649, 628 | 'inference_time_tf': 3.6236307621002197 629 | }, { 630 | 'rows': 96, 631 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 632 | 'pred_keras_score': 389, 633 | 'pred_tf_score': 389, 634 | 'preds_agree': True, 635 | 'alpha': 0.5, 636 | 'inference_time_keras': 1.6599667072296143, 637 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 638 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.5_96.h5', 639 | 'max_vector_difference': 0.11007625, 640 | 'inference_time_tf': 3.9398250579833984 641 | }, { 642 | 'rows': 224, 643 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 644 | 'pred_keras_score': 389, 645 | 'pred_tf_score': 389, 646 | 'preds_agree': True, 647 | 'alpha': 0.35, 648 | 'inference_time_keras': 1.7787129878997803, 649 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 650 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.35_224.h5', 651 | 'max_vector_difference': 0.018119395, 652 | 'inference_time_tf': 4.164514064788818 653 | }, { 654 | 'rows': 192, 655 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 656 | 'pred_keras_score': 389, 657 | 'pred_tf_score': 389, 658 | 'preds_agree': True, 659 | 'alpha': 0.35, 660 | 'inference_time_keras': 1.8967313766479492, 661 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 662 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.35_192.h5', 663 | 'max_vector_difference': 0.031832576, 664 | 'inference_time_tf': 4.384629249572754 665 | }, { 666 | 'rows': 160, 667 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 668 | 'pred_keras_score': 389, 669 | 'pred_tf_score': 389, 670 | 'preds_agree': True, 671 | 'alpha': 0.35, 672 | 'inference_time_keras': 2.034467935562134, 673 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 674 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.35_160.h5', 675 | 'max_vector_difference': 0.008257747, 676 | 'inference_time_tf': 4.6021528244018555 677 | }, { 678 | 'rows': 128, 679 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 680 | 'pred_keras_score': 389, 681 | 'pred_tf_score': 389, 682 | 'preds_agree': True, 683 | 'alpha': 0.35, 684 | 'inference_time_keras': 2.175736427307129, 685 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 686 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.35_128.h5', 687 | 'max_vector_difference': 0.012835741, 688 | 'inference_time_tf': 4.958279609680176 689 | }, { 690 | 'rows': 96, 691 | 'pred_tf_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 692 | 'pred_keras_score': 389, 693 | 'pred_tf_score': 389, 694 | 'preds_agree': True, 695 | 'alpha': 0.35, 696 | 'inference_time_keras': 2.609691619873047, 697 | 'pred_keras_label': 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 698 | 'model': '/home/jon/Documents/keras_mobilenetV2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_0.35_96.h5', 699 | 'max_vector_difference': 0.019455373, 700 | 'inference_time_tf': 6.363346576690674 701 | }] 702 | ``` 703 | 704 | ## Test results remote (1000) class 705 | -------------------------------------------------------------------------------- /test_mobilenet.py: -------------------------------------------------------------------------------- 1 | # Test script to show that the mobilenetV2 works as expected 2 | from __future__ import print_function 3 | from keras.layers import Input 4 | from keras.utils import get_file 5 | import numpy as np 6 | from mobilenetv2 import MobileNetV2 7 | import urllib 8 | import json 9 | import PIL 10 | import time 11 | from imagenet_utils import create_readable_names_for_imagenet_labels 12 | import tensorflow as tf 13 | import gzip 14 | import tarfile 15 | import os 16 | import sys 17 | # PYTHONPATH should contain the research/slim/ directory in the tensorflow/models repo. 18 | from nets.mobilenet import mobilenet_v2 19 | from models_to_load import models_to_load 20 | from keras.models import Model 21 | import pickle 22 | 23 | ROOT_DIR = os.getcwd() 24 | MODEL_DIR = os.path.join(ROOT_DIR, 'models') 25 | 26 | 27 | def predict_keras(img, alpha, rows): 28 | """ 29 | params: img: an input image with shape (1, 224, 224, 3) 30 | note: Image has been preprocessed (x /= 127.5 - 1) 31 | Runs forward pass on network and returns logits and the inference time 32 | """ 33 | input_tensor = Input(shape=(rows, rows, 3)) 34 | 35 | # Note: you could also provide an input_shape 36 | model = MobileNetV2(input_tensor=input_tensor, 37 | include_top=True, 38 | weights='imagenet', 39 | alpha=alpha) 40 | 41 | tic = time.time() 42 | y_pred = model.predict(img.astype(np.float32)) 43 | y_pred = y_pred[0].ravel() 44 | toc = time.time() 45 | return y_pred, toc-tic 46 | 47 | 48 | def get_tf_mobilenet_v2_items(alpha, rows): 49 | model_path = os.path.join(MODEL_DIR, 'mobilenet_v2_' + str(alpha) + '_' + str(rows)) 50 | base_name = 'mobilenet_v2_' + str(alpha) + '_' + str(rows) 51 | base_path = os.path.join(model_path, base_name) 52 | 53 | url = 'https://storage.googleapis.com/mobilenet_v2/checkpoints/' + base_name + '.tgz' 54 | print('Downloading from ', url) 55 | 56 | urllib.request.urlretrieve(url, model_path + '.tgz') 57 | tar = tarfile.open(model_path + '.tgz', "r:gz") 58 | tar.extractall(model_path) 59 | tar.close() 60 | 61 | return base_path 62 | 63 | 64 | def predict_slim(img, checkpoint, rows): 65 | """ 66 | params: img: a preprocessed image with shape (1, 224, 224, 3) 67 | checkpoint: the path to the frozen.pb checkpoint 68 | Runs a forward pass of the tensorflow slim mobilenetV2 model which has been frozen for inference 69 | returns: numpy array x, which are the logits, and the inference time 70 | """ 71 | gd = tf.GraphDef.FromString(open(checkpoint + '_frozen.pb', 'rb').read()) 72 | inp, predictions = tf.import_graph_def(gd, return_elements=['input:0', 'MobilenetV2/Predictions/Reshape_1:0']) 73 | 74 | # maybe reshaping image twice img.reshape(1, rows,rows, 3) 75 | with tf.Session(graph=inp.graph): 76 | tic = time.time() 77 | y_pred = predictions.eval(feed_dict={inp: img}) 78 | y_pred = y_pred[0].ravel() 79 | 80 | # remove background class and renormalize 81 | y_pred = y_pred[1:] / y_pred[1:].sum() 82 | 83 | toc = time.time() 84 | return y_pred, toc-tic 85 | 86 | 87 | def test_keras_and_tf(models=[]): 88 | # This test runs through all the models included below and tests them 89 | results = [] 90 | for alpha, rows in models: 91 | print('alpha: ', alpha) 92 | print('rows: ', rows) 93 | 94 | # Get tensorflow checkpoint path and download required items 95 | SLIM_CKPT_base_path = get_tf_mobilenet_v2_items(alpha=alpha, rows=rows) 96 | 97 | # To test Panda image 98 | url = 'https://upload.wikimedia.org/wikipedia/commons/f/fe/Giant_Panda_in_Beijing_Zoo_1.JPG' 99 | img_filename = 'panda.jpg' 100 | 101 | # To test Monkey image 102 | # url = 'https://upload.wikimedia.org/wikipedia/commons/a/a9/Macaca_sinica_-_01.jpg' 103 | # img_filename = 'monkey.jpg' 104 | 105 | # Grab test image 106 | urllib.request.urlretrieve(url, img_filename) 107 | 108 | # Preprocess 109 | img = np.array(PIL.Image.open(img_filename).resize( 110 | (rows, rows))).astype(np.float32) / 128. - 1. 111 | img = np.expand_dims(img, axis=0) 112 | 113 | # Keras model test 114 | output_logits_keras, tk = predict_keras(img, alpha=alpha, rows=rows) 115 | 116 | # Tensorflow SLIM 117 | output_logits_tf, tt = predict_slim(img, SLIM_CKPT_base_path, rows) 118 | 119 | label_map = create_readable_names_for_imagenet_labels() 120 | pred_keras = output_logits_keras.argmax() + 1 # shift over one cell for background 121 | pred_tf = output_logits_tf.argmax() + 1 122 | 123 | print('for Model: alpha: ', alpha, "rows: ", rows) 124 | print("Prediction keras: ", pred_keras, label_map[pred_keras], "score: ", output_logits_keras.max()) 125 | print("Prediction tf: ", pred_tf, label_map[pred_tf], "score: ", output_logits_tf.max()) 126 | print("output logit keras: ", output_logits_keras.max()) 127 | print("output logit tf: ", output_logits_tf.max()) 128 | print("Inference time keras: ", tk) 129 | print("Inference time tf: ", tt) 130 | print('Do output logits deviate > 0.5 thresh? : ', np.allclose(output_logits_keras, output_logits_tf, 0.5)) 131 | print("max_vector_difference", np.abs(output_logits_keras - output_logits_tf).max() ) 132 | assert(pred_tf == pred_keras) 133 | result = { 134 | "alpha": alpha, 135 | "rows": rows, 136 | "pred_keras_score": pred_keras, 137 | "pred_keras_label": label_map[pred_keras], 138 | "inference_time_keras": tk, 139 | "pred_tf_score": pred_tf, 140 | "pred_tf_label": label_map[pred_tf], 141 | "inference_time_tf": tt, 142 | "preds_agree": pred_keras == pred_tf, 143 | "max_vector_difference": np.abs(output_logits_keras - output_logits_tf).max() 144 | } 145 | results.append(result) 146 | 147 | return results 148 | 149 | 150 | if __name__ == "__main__": 151 | 152 | if not os.path.isdir(MODEL_DIR): 153 | os.makedirs(MODEL_DIR) 154 | 155 | test_results = test_keras_and_tf(models=[(1.0, 224)]) 156 | 157 | # Uncomment to test results 158 | with open('test_results.p', 'wb') as pickle_file: 159 | pickle.dump(test_results, pickle_file) 160 | -------------------------------------------------------------------------------- /test_results.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonathanCMitchell/mobilenet_v2_keras/2474973db694f444a8472f6d2e369c71e104cd1c/test_results.p --------------------------------------------------------------------------------