def test_layer_database_with_dynamic_shape(self): """ test layer database creation with different input shapes""" # create tf.compat.v1.Session and initialize the weights and biases with zeros config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True graph = tf.Graph() with graph.as_default(): # by default, model will be constructed in default graph input_placeholder = tf.compat.v1.placeholder(tf.float32, [None, None, None, 3], 'input') x = tf.keras.layers.Conv2D(8, (2, 2), padding='SAME')(input_placeholder) x = tf.keras.layers.BatchNormalization(momentum=.3, epsilon=.65)(x) x = tf.keras.layers.Conv2D(8, (1, 1), padding='SAME', activation=tf.nn.tanh)(x) x = tf.keras.layers.BatchNormalization(momentum=.4, epsilon=.25)(x) init = tf.compat.v1.global_variables_initializer() # create session with graph sess = tf.compat.v1.Session(graph=graph, config=config) sess.run(init) layer_db = LayerDatabase(model=sess, input_shape=(1, 224, 224, 3), working_dir=None, starting_ops=['input'], ending_ops=['batch_normalization_1/cond/Merge']) conv1_layer = layer_db.find_layer_by_name('conv2d/Conv2D') conv2_layer = layer_db.find_layer_by_name('conv2d_1/Conv2D') self.assertEqual(conv1_layer.output_shape, [1, 8, 224, 224]) self.assertEqual(conv2_layer.output_shape, [1, 8, 224, 224]) layer_db.destroy() # 2) try with different input shape # create another session with graph sess = tf.compat.v1.Session(graph=graph, config=config) sess.run(init) batch_size = 32 layer_db = LayerDatabase(model=sess, input_shape=(batch_size, 28, 28, 3), working_dir=None, starting_ops=['input'], ending_ops=['batch_normalization_1/cond/Merge']) conv1_layer = layer_db.find_layer_by_name('conv2d/Conv2D') conv2_layer = layer_db.find_layer_by_name('conv2d_1/Conv2D') self.assertEqual(conv1_layer.output_shape, [32, 8, 28, 28]) self.assertEqual(conv2_layer.output_shape, [32, 8, 28, 28]) layer_db.destroy()
def test_layer_database_cpu_memory_Leak(self): # create tf.compat.v1.Session and initialize the weights and biases with zeros import psutil config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # by default, model will be constructed in default graph _ = ResNet50(weights=None) init = tf.compat.v1.global_variables_initializer() sess.run(init) layer_db = LayerDatabase(model=sess, input_shape=(1, 28, 28, 1), working_dir=None) mem_before = psutil.virtual_memory().available for i in range(5): copy_layer_db = copy.deepcopy(layer_db) copy_layer_db.model.close() mem_after = psutil.virtual_memory().available memory_consumed = (mem_before - mem_after) / (1024 * 1024) print('Memory consumed in MB:', memory_consumed, 'for iterations : ', 5) self.assertTrue(memory_consumed < 200) layer_db.model.close()
def test_layer_database_working_dir(self): # create tf.compat.v1.Session and initialize the weights and biases with zeros config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # by default, model will be constructed in default graph _ = mnist_tf_model.create_model(data_format='channels_last') sess.run(tf.compat.v1.global_variables_initializer()) meta_path = str('./temp_working_dir/') if not os.path.exists(meta_path): os.mkdir(meta_path) layer_db = LayerDatabase(model=sess, input_shape=(1, 28, 28, 1), working_dir=meta_path) copy_layer_db = copy.deepcopy(layer_db) shutil.rmtree(meta_path) tf.compat.v1.reset_default_graph() layer_db.model.close() copy_layer_db.model.close()
def test_total_model_cost(self): # create tf.compat.v1.Session and initialize the weights and biases with zeros config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # by default, model will be constructed in default graph _ = mnist_tf_model.create_model(data_format='channels_last') sess.run(tf.compat.v1.global_variables_initializer()) layer_database = LayerDatabase(model=sess, input_shape=(1, 28, 28, 1), working_dir=None) cost_calc = cc.CostCalculator() network_cost = cost_calc.compute_model_cost(layer_database) self.assertEqual(800 + 51200 + 3211264 + 10240, network_cost.memory) self.assertEqual(627200 + 10035200 + 3211264 + 10240, network_cost.mac) tf.compat.v1.reset_default_graph() sess.close() # delete temp directory shutil.rmtree(str('./temp_meta/'))
def test_prune_model_2_layers(self): """ Punning two layers with 0.5 comp-ratio in MNIST""" # create tf.compat.v1.Session and initialize the weights and biases with zeros config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # by default, model will be constructed in default graph _ = mnist_tf_model.create_model(data_format='channels_last') sess.run(tf.compat.v1.global_variables_initializer()) # Create a layer database orig_layer_db = LayerDatabase(model=sess, input_shape=(1, 28, 28, 1), working_dir=None) conv1 = orig_layer_db.find_layer_by_name('conv2d/Conv2D') conv2 = orig_layer_db.find_layer_by_name('conv2d_1/Conv2D') layer_comp_ratio_list = [ LayerCompRatioPair(conv1, Decimal(0.5)), LayerCompRatioPair(conv2, Decimal(0.5)) ] spatial_svd_pruner = SpatialSvdPruner() comp_layer_db = spatial_svd_pruner.prune_model(orig_layer_db, layer_comp_ratio_list, CostMetric.mac, trainer=None) conv1_a = comp_layer_db.find_layer_by_name('conv2d_a/Conv2D') conv1_b = comp_layer_db.find_layer_by_name('conv2d_b/Conv2D') # Weights shape [kh, kw, Nic, Noc] self.assertEqual([5, 1, 1, 2], conv1_a.module.inputs[1].get_shape().as_list()) self.assertEqual([1, 5, 2, 32], conv1_b.module.inputs[1].get_shape().as_list()) conv2_a = comp_layer_db.find_layer_by_name('conv2d_1_a/Conv2D') conv2_b = comp_layer_db.find_layer_by_name('conv2d_1_b/Conv2D') self.assertEqual([5, 1, 32, 53], conv2_a.module.inputs[1].get_shape().as_list()) self.assertEqual([1, 5, 53, 64], conv2_b.module.inputs[1].get_shape().as_list()) for layer in comp_layer_db: print("Layer: " + layer.name) print(" Module: " + str(layer.module.name)) tf.compat.v1.reset_default_graph() sess.close() # delete temp directory shutil.rmtree(str('./temp_meta/'))
def test_prune_model_tf_slim(self): """ Punning a model with tf slim api """ # create tf.compat.v1.Session and initialize the weights and biases with zeros config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # by default, model will be constructed in default graph x = tf.compat.v1.placeholder(tf.float32, [1, 32, 32, 3]) _ = tf_slim_basic_model(x) sess.run(tf.compat.v1.global_variables_initializer()) conn_graph_orig = ConnectedGraph(sess.graph, ['Placeholder'], ['tf_slim_model/Softmax']) num_ops_orig = len(conn_graph_orig.get_all_ops()) # Create a layer database orig_layer_db = LayerDatabase(model=sess, input_shape=(1, 32, 32, 3), working_dir=None) conv1 = orig_layer_db.find_layer_by_name('Conv_1/Conv2D') conv1_bias = BiasUtils.get_bias_as_numpy_data(orig_layer_db.model, conv1.module) layer_comp_ratio_list = [LayerCompRatioPair(conv1, Decimal(0.5))] spatial_svd_pruner = SpatialSvdPruner() comp_layer_db = spatial_svd_pruner.prune_model(orig_layer_db, layer_comp_ratio_list, CostMetric.mac, trainer=None) # Check that svd added these ops _ = comp_layer_db.model.graph.get_operation_by_name('Conv_1_a/Conv2D') _ = comp_layer_db.model.graph.get_operation_by_name('Conv_1_b/Conv2D') conn_graph_new = ConnectedGraph(comp_layer_db.model.graph, ['Placeholder'], ['tf_slim_model/Softmax']) num_ops_new = len(conn_graph_new.get_all_ops()) self.assertEqual(num_ops_orig + 1, num_ops_new) bias_add_op = comp_layer_db.model.graph.get_operation_by_name( 'Conv_1_b/BiasAdd') conv_1_b_op = comp_layer_db.model.graph.get_operation_by_name( 'Conv_1_b/Conv2D') self.assertEqual( conn_graph_new._module_identifier.get_op_info(bias_add_op), conn_graph_new._module_identifier.get_op_info(conv_1_b_op)) self.assertTrue( np.array_equal( conv1_bias, BiasUtils.get_bias_as_numpy_data(comp_layer_db.model, conv_1_b_op)))
def test_layer_database_with_mnist(self): config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # model will be constructed in default graph _ = mnist_tf_model.create_model(data_format='channels_last') # initialize the weights and biases with appropriate initializer sess.run(tf.compat.v1.global_variables_initializer()) layer_db = LayerDatabase(model=sess, input_shape=(1, 28, 28, 1), working_dir=None) layers = list(layer_db._compressible_layers.values()) # check output shapes self.assertEqual(layers[0].output_shape, [None, 32, 28, 28]) self.assertEqual(layers[1].output_shape, [None, 64, 14, 14]) self.assertEqual(layers[2].output_shape, [None, 1024, 1, 1]) self.assertEqual(layers[3].output_shape, [None, 10, 1, 1]) # check weight shapes # layer weight_shape is in common format [Noc, Nic, k_h, k_w] self.assertEqual(layers[0].weight_shape, [32, 1, 5, 5]) self.assertEqual(layers[1].weight_shape, [64, 32, 5, 5]) self.assertEqual(layers[2].weight_shape, [1024, 3136]) self.assertEqual(layers[3].weight_shape, [10, 1024]) # tensorflow weights are stored in [k_h, k_w, Nic, Noc] self.assertEqual(layers[0].module.inputs[1].eval(session=layer_db.model).shape, (5, 5, 1, 32)) self.assertEqual(layers[1].module.inputs[1].eval(session=layer_db.model).shape, (5, 5, 32, 64)) self.assertEqual(layers[2].module.inputs[1].eval(session=layer_db.model).shape, (3136, 1024)) self.assertEqual(layers[3].module.inputs[1].eval(session=layer_db.model).shape, (1024, 10)) tf.compat.v1.reset_default_graph() sess.close() # delete temp directory shutil.rmtree(str('./temp_meta/'))
def test_calculate_spatial_svd_cost_all_layers(self): # create tf.compat.v1.Session and initialize the weights and biases with zeros config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # by default, model will be constructed in default graph _ = mnist_tf_model.create_model(data_format='channels_last') sess.run(tf.compat.v1.global_variables_initializer()) layer_database = LayerDatabase(model=sess, input_shape=(1, 28, 28, 1), working_dir=None) # Compress all layers by 50% # Create a list of tuples of (layer, comp_ratio) layer_ratio_list = [] for layer in layer_database: if layer.module.type == 'Conv2D': layer_ratio_list.append(LayerCompRatioPair( layer, Decimal(0.5))) else: layer_ratio_list.append(LayerCompRatioPair(layer, None)) compressed_cost = cc.SpatialSvdCostCalculator.calculate_compressed_cost( layer_database, layer_ratio_list, CostMetric.mac) self.assertEqual(8466464, compressed_cost.mac) tf.compat.v1.reset_default_graph() sess.close() # delete temp directory shutil.rmtree(str('./temp_meta/'))
def test_prune_conv_no_bias(self): """ Test spatial svd on a conv layer with no bias """ # create tf.compat.v1.Session and initialize the weights and biases with zeros config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # by default, model will be constructed in default graph inputs = tf.keras.Input(shape=( 32, 32, 3, )) x = tf.keras.layers.Conv2D(32, (3, 3), use_bias=False)(inputs) _ = tf.keras.layers.Flatten()(x) sess.run(tf.compat.v1.global_variables_initializer()) # Create a layer database orig_layer_db = LayerDatabase(model=sess, input_shape=(1, 32, 32, 3), working_dir=None) conv_op = orig_layer_db.find_layer_by_name('conv2d/Conv2D') layer_comp_ratio_list = [LayerCompRatioPair(conv_op, Decimal(0.5))] spatial_svd_pruner = SpatialSvdPruner() comp_layer_db = spatial_svd_pruner.prune_model(orig_layer_db, layer_comp_ratio_list, CostMetric.mac, trainer=None) # Check that svd added these ops _ = comp_layer_db.model.graph.get_operation_by_name('conv2d_a/Conv2D') conv2d_b_op = comp_layer_db.model.graph.get_operation_by_name( 'conv2d_b/Conv2D') reshape_op = comp_layer_db.model.graph.get_operation_by_name( 'flatten/Reshape') self.assertEqual(conv2d_b_op, reshape_op.inputs[0].op)
def test_layer_database_destroy(self): # create tf.compat.v1.Session and initialize the weights and biases with zeros config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # by default, model will be constructed in default graph _ = mnist_tf_model.create_model(data_format='channels_last') init = tf.compat.v1.global_variables_initializer() sess.run(init) layer_db = LayerDatabase(model=sess, input_shape=(1, 28, 28, 1), working_dir=None) layer_db.destroy() self.assertRaises(RuntimeError, lambda: sess.run(init)) # delete temp directory shutil.rmtree(str('./temp_meta/'))
def test_per_layer_eval_scores(self): pruner = unittest.mock.MagicMock() eval_func = unittest.mock.MagicMock() # create tf.compat.v1.Session and initialize the weights and biases with zeros config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # by default, model will be constructed in default graph _ = mnist_tf_model.create_model(data_format='channels_last') sess.run(tf.compat.v1.global_variables_initializer()) # Create a layer database layer_db = LayerDatabase(model=sess, input_shape=(1, 28, 28, 1), working_dir=None) layer1 = layer_db.find_layer_by_name('conv2d/Conv2D') layer_db.mark_picked_layers([layer1]) eval_func.side_effect = [90, 80, 70, 60, 50, 40, 30, 20, 10] url, process = start_bokeh_server_session(8006) bokeh_session = BokehServerSession(url=url, session_id="compression") # Instantiate child greedy_algo = comp_ratio_select.GreedyCompRatioSelectAlgo( layer_db=layer_db, pruner=pruner, cost_calculator=SpatialSvdCostCalculator(), eval_func=eval_func, eval_iterations=20, cost_metric=CostMetric.mac, target_comp_ratio=0.5, num_candidates=10, use_monotonic_fit=True, saved_eval_scores_dict=None, comp_ratio_rounding_algo=None, use_cuda=False, bokeh_session=bokeh_session) progress_bar = ProgressBar(1, "eval scores", "green", bokeh_session=bokeh_session) data_table = DataTable(num_columns=3, num_rows=1, column_names=[ '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9' ], row_index_names=[layer1.name], bokeh_session=bokeh_session) pruner.prune_model.return_value = layer_db eval_dict = greedy_algo._compute_layerwise_eval_score_per_comp_ratio_candidate( data_table, progress_bar, layer1) self.assertEqual(90, eval_dict[Decimal('0.1')]) tf.compat.v1.reset_default_graph() sess.close() bokeh_session.server_session.close("test complete") os.killpg(os.getpgid(process.pid), signal.SIGTERM)
def test_select_per_layer_comp_ratios_with_spatial_svd_pruner(self): pruner = SpatialSvdPruner() eval_func = unittest.mock.MagicMock() rounding_algo = unittest.mock.MagicMock() eval_func.side_effect = [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 11, 21, 31, 35, 40, 45, 50, 55, 60 ] rounding_algo.round.side_effect = [ 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 ] # create tf.compat.v1.Session and initialize the weights and biases with zeros config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # by default, model will be constructed in default graph _ = mnist_tf_model.create_model(data_format='channels_last') sess.run(tf.compat.v1.global_variables_initializer()) # Create a layer database layer_db = LayerDatabase(model=sess, input_shape=(1, 28, 28, 1), working_dir=None) selected_layers = [ layer for layer in layer_db if layer.module.type == 'Conv2D' ] layer_db.mark_picked_layers(selected_layers) url, process = start_bokeh_server_session(8006) bokeh_session = BokehServerSession(url=url, session_id="compression") # Instantiate child greedy_algo = comp_ratio_select.GreedyCompRatioSelectAlgo( layer_db=layer_db, pruner=pruner, cost_calculator=SpatialSvdCostCalculator(), eval_func=eval_func, eval_iterations=20, cost_metric=CostMetric.mac, target_comp_ratio=Decimal(0.4), num_candidates=10, use_monotonic_fit=True, saved_eval_scores_dict=None, comp_ratio_rounding_algo=rounding_algo, use_cuda=False, bokeh_session=bokeh_session) layer_comp_ratio_list, stats = greedy_algo.select_per_layer_comp_ratios( ) original_cost = SpatialSvdCostCalculator.compute_model_cost(layer_db) for layer in layer_db: if layer not in selected_layers: layer_comp_ratio_list.append(LayerCompRatioPair(layer, None)) compressed_cost = SpatialSvdCostCalculator.calculate_compressed_cost( layer_db, layer_comp_ratio_list, CostMetric.mac) actual_compression_ratio = compressed_cost.mac / original_cost.mac self.assertTrue( math.isclose(Decimal(0.3), actual_compression_ratio, abs_tol=0.8)) print('\n') for pair in layer_comp_ratio_list: print(pair) tf.compat.v1.reset_default_graph() sess.close() bokeh_session.server_session.close("test complete") os.killpg(os.getpgid(process.pid), signal.SIGTERM)
def test_eval_scores_with_spatial_svd_pruner(self): pruner = SpatialSvdPruner() eval_func = unittest.mock.MagicMock() eval_func.side_effect = [ 90, 80, 70, 60, 50, 40, 30, 20, 10, 91, 81, 71, 61, 51, 41, 31, 21, 11 ] # create tf.compat.v1.Session and initialize the weights and biases with zeros config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # by default, model will be constructed in default graph _ = mnist_tf_model.create_model(data_format='channels_last') sess.run(tf.compat.v1.global_variables_initializer()) # Create a layer database layer_db = LayerDatabase(model=sess, input_shape=(1, 28, 28, 1), working_dir=None) layer1 = layer_db.find_layer_by_name('conv2d/Conv2D') layer2 = layer_db.find_layer_by_name('conv2d_1/Conv2D') layer_db.mark_picked_layers([layer1, layer2]) url, process = start_bokeh_server_session(8006) bokeh_session = BokehServerSession(url=url, session_id="compression") # Instantiate child greedy_algo = comp_ratio_select.GreedyCompRatioSelectAlgo( layer_db=layer_db, pruner=pruner, cost_calculator=SpatialSvdCostCalculator(), eval_func=eval_func, eval_iterations=20, cost_metric=CostMetric.mac, target_comp_ratio=0.5, num_candidates=10, use_monotonic_fit=True, saved_eval_scores_dict=None, comp_ratio_rounding_algo=None, use_cuda=False, bokeh_session=bokeh_session) dict = greedy_algo._compute_eval_scores_for_all_comp_ratio_candidates() print() print(dict) self.assertEqual(90, dict['conv2d/Conv2D'][Decimal('0.1')]) self.assertEqual(51, dict['conv2d_1/Conv2D'][Decimal('0.5')]) self.assertEqual(21, dict['conv2d_1/Conv2D'][Decimal('0.8')]) tf.compat.v1.reset_default_graph() sess.close() bokeh_session.server_session.close("test complete") os.killpg(os.getpgid(process.pid), signal.SIGTERM)
def test_calculate_channel_pruning_cost_all_layers(self): config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # model will be constructed in default graph _ = mnist(data_format='channels_last') # initialize the weights and biases with appropriate initializer sess.run(tf.compat.v1.global_variables_initializer()) meta_path = str('./temp_working_dir/') if not os.path.exists(meta_path): os.mkdir(meta_path) layer_db = LayerDatabase(model=sess, input_shape=(1, 28, 28, 1), working_dir=meta_path) # Compress all layers by 50% # Create a list of tuples of (layer, comp_ratio) layer_ratio_list = [] # Unfortunately in mnist we can only input channel prune conv2d_1/Conv2D for layer in layer_db: if layer.module.name == 'conv2d_1/Conv2D': layer_ratio_list.append( LayerCompRatioPair(layer, Decimal('0.5'))) else: layer_ratio_list.append(LayerCompRatioPair(layer, None)) inp_op_names = ['reshape_input'] output_op_names = ['dense_1/BiasAdd'] data_set = unittest.mock.MagicMock() batch_size = unittest.mock.MagicMock() num_reconstruction_samples = unittest.mock.MagicMock() pruner = InputChannelPruner( input_op_names=inp_op_names, output_op_names=output_op_names, data_set=data_set, batch_size=batch_size, num_reconstruction_samples=num_reconstruction_samples, allow_custom_downsample_ops=True) cost_calculator = ChannelPruningCostCalculator(pruner) compressed_cost = cost_calculator.calculate_compressed_cost( layer_db, layer_ratio_list, CostMetric.mac) self.assertEqual(8552704, compressed_cost.mac) self.assertEqual(3247504, compressed_cost.memory) # delete the meta and the checkpoint files shutil.rmtree(meta_path) layer_db.model.close()
def test_calculate_channel_pruning_cost_two_layers(self): """ test compressed model cost using two layers :return: """ config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # model will be constructed in default graph test_models.single_residual() init = tf.compat.v1.global_variables_initializer() # initialize the weights and biases with appropriate initializer sess.run(init) meta_path = str('./temp_working_dir/') if not os.path.exists(meta_path): os.mkdir(meta_path) layer_db = LayerDatabase(model=sess, input_shape=None, working_dir=meta_path) # Create a list of tuples of (layer, comp_ratio) layer_ratio_list = [] layer_names = ['conv2d_2/Conv2D', 'conv2d_3/Conv2D'] for layer in layer_db: if layer.module.name in layer_names: layer_ratio_list.append(LayerCompRatioPair(layer, 0.5)) else: layer_ratio_list.append(LayerCompRatioPair(layer, None)) input_op_names = ['input_1'] output_op_names = ['single_residual/Softmax'] data_set = unittest.mock.MagicMock() batch_size = unittest.mock.MagicMock() num_reconstruction_samples = unittest.mock.MagicMock() pruner = InputChannelPruner( input_op_names=input_op_names, output_op_names=output_op_names, data_set=data_set, batch_size=batch_size, num_reconstruction_samples=num_reconstruction_samples, allow_custom_downsample_ops=True) cost_calculator = ChannelPruningCostCalculator(pruner) compressed_cost = cost_calculator.calculate_compressed_cost( layer_db, layer_ratio_list, CostMetric.mac) self.assertEqual(108544, compressed_cost.mac) self.assertEqual(1264, compressed_cost.memory) # delete the meta and the checkpoint files shutil.rmtree(meta_path) layer_db.model.close()
def test_prune_layer(self): """ Pruning single layer with 0.5 comp-ratio in MNIST""" # create tf.compat.v1.Session and initialize the weights and biases with zeros config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # by default, model will be constructed in default graph _ = mnist_tf_model.create_model(data_format='channels_last') sess.run(tf.compat.v1.global_variables_initializer()) # Create a layer database orig_layer_db = LayerDatabase(model=sess, input_shape=(1, 28, 28, 1), working_dir=None) # Copy the db comp_layer_db = copy.deepcopy(orig_layer_db) conv1 = comp_layer_db.find_layer_by_name('conv2d/Conv2D') # before the splitting bias_op = get_succeeding_bias_op(conv1.module) for consumer in bias_op.outputs[0].consumers(): self.assertEqual(consumer.name, "conv2d/Relu") spatial_svd_pruner = SpatialSvdPruner() spatial_svd_pruner._prune_layer(orig_layer_db, comp_layer_db, conv1, 0.5, CostMetric.mac) conv2d_a_op = comp_layer_db.model.graph.get_operation_by_name( 'conv2d_a/Conv2D') conv2d_b_op = comp_layer_db.model.graph.get_operation_by_name( 'conv2d_b/Conv2D') conv2d_a_weight = WeightTensorUtils.get_tensor_as_numpy_data( comp_layer_db.model, conv2d_a_op) conv2d_b_weight = WeightTensorUtils.get_tensor_as_numpy_data( comp_layer_db.model, conv2d_b_op) conv1_a = comp_layer_db.find_layer_by_name('conv2d_a/Conv2D') conv1_b = comp_layer_db.find_layer_by_name('conv2d_b/Conv2D') # [Noc, Nic, kh, kw] self.assertEqual([2, 1, 5, 1], conv1_a.weight_shape) self.assertEqual([32, 2, 1, 5], conv1_b.weight_shape) # after the splitting bias_op = get_succeeding_bias_op(conv1_b.module) for consumer in bias_op.outputs[0].consumers(): self.assertEqual(consumer.name, "conv2d/Relu") # original layer should be not there in the database self.assertRaises( KeyError, lambda: comp_layer_db.find_layer_by_name('conv2d/Conv2D')) # check if the layer replacement is done correctly orig_conv_op = comp_layer_db.model.graph.get_operation_by_name( 'conv2d/Conv2D') bias_op = get_succeeding_bias_op(orig_conv_op) # consumers list should be empty consumers = [consumer for consumer in bias_op.outputs[0].consumers()] self.assertEqual(len(consumers), 0) # Check that weights loaded during svd pruning will stick after save and load new_sess = save_and_load_graph('./temp_meta/', comp_layer_db.model) conv2d_a_op = comp_layer_db.model.graph.get_operation_by_name( 'conv2d_a/Conv2D') conv2d_b_op = comp_layer_db.model.graph.get_operation_by_name( 'conv2d_b/Conv2D') conv2d_a_weight_after_save_load = WeightTensorUtils.get_tensor_as_numpy_data( comp_layer_db.model, conv2d_a_op) conv2d_b_weight_after_save_load = WeightTensorUtils.get_tensor_as_numpy_data( comp_layer_db.model, conv2d_b_op) self.assertTrue( np.array_equal(conv2d_a_weight, conv2d_a_weight_after_save_load)) self.assertTrue( np.array_equal(conv2d_b_weight, conv2d_b_weight_after_save_load)) tf.compat.v1.reset_default_graph() sess.close() new_sess.close() # delete temp directory shutil.rmtree(str('./temp_meta/'))
def test_sort_on_occurrence(self): """ Test sorting of ops based on occurrence """ AimetLogger.set_area_logger_level(AimetLogger.LogAreas.Winnow, logging.INFO) tf.compat.v1.reset_default_graph() orig_g = tf.Graph() with orig_g.as_default(): _ = VGG16(weights=None, input_shape=(224, 224, 3), include_top=False) orig_init = tf.compat.v1.global_variables_initializer() # create sess with graph orig_sess = tf.compat.v1.Session(graph=orig_g) orig_sess.run(orig_init) # create layer database layer_db = LayerDatabase(model=orig_sess, input_shape=(1, 224, 224, 3), working_dir=None) block1_conv2 = layer_db.model.graph.get_operation_by_name( 'block1_conv2/Conv2D') block2_conv1 = layer_db.model.graph.get_operation_by_name( 'block2_conv1/Conv2D') block2_conv2 = layer_db.model.graph.get_operation_by_name( 'block2_conv2/Conv2D') block5_conv3 = layer_db.model.graph.get_operation_by_name( 'block5_conv3/Conv2D') # output shape in NCHW format block1_conv2_output_shape = block1_conv2.outputs[0].shape block2_conv1_output_shape = block2_conv1.outputs[0].shape block2_conv2_output_shape = block2_conv2.outputs[0].shape block5_conv3_output_shape = block5_conv3.outputs[0].shape # keeping compression ratio = None for all layers layer_comp_ratio_list = [ LayerCompRatioPair( Layer(model=layer_db.model, op=block5_conv3, output_shape=block5_conv3_output_shape), None), LayerCompRatioPair( Layer(model=layer_db.model, op=block2_conv2, output_shape=block2_conv2_output_shape), None), LayerCompRatioPair( Layer(model=layer_db.model, op=block1_conv2, output_shape=block1_conv2_output_shape), None), LayerCompRatioPair( Layer(model=layer_db.model, op=block2_conv1, output_shape=block2_conv1_output_shape), None) ] input_op_names = ['input_1'] output_op_names = ['block5_pool/MaxPool'] dataset = unittest.mock.MagicMock() batch_size = unittest.mock.MagicMock() num_reconstruction_samples = unittest.mock.MagicMock() cp = InputChannelPruner( input_op_names=input_op_names, output_op_names=output_op_names, data_set=dataset, batch_size=batch_size, num_reconstruction_samples=num_reconstruction_samples, allow_custom_downsample_ops=True) sorted_layer_comp_ratio_list = cp._sort_on_occurrence( layer_db.model, layer_comp_ratio_list) self.assertEqual(sorted_layer_comp_ratio_list[0].layer.module, block1_conv2) self.assertEqual(sorted_layer_comp_ratio_list[1].layer.module, block2_conv1) self.assertEqual(sorted_layer_comp_ratio_list[2].layer.module, block2_conv2) self.assertEqual(sorted_layer_comp_ratio_list[3].layer.module, block5_conv3) self.assertEqual(len(sorted_layer_comp_ratio_list), 4) layer_db.model.close() # delete temp directory shutil.rmtree(str('./temp_meta/'))
def test_prune_model(self): """ Test end-to-end prune_model with VGG16-imagenet """ AimetLogger.set_area_logger_level(AimetLogger.LogAreas.Winnow, logging.INFO) tf.compat.v1.reset_default_graph() batch_size = 1 input_data = np.random.rand(100, 224, 224, 3) dataset = tf.data.Dataset.from_tensor_slices(input_data) dataset = dataset.batch(batch_size=batch_size) orig_g = tf.Graph() with orig_g.as_default(): _ = VGG16(weights=None, input_shape=(224, 224, 3), include_top=False) orig_init = tf.compat.v1.global_variables_initializer() input_op_names = ['input_1'] output_op_names = ['block5_pool/MaxPool'] # create sess with graph orig_sess = tf.compat.v1.Session(graph=orig_g) # initialize all the variables in VGG16 orig_sess.run(orig_init) # create layer database layer_db = LayerDatabase(model=orig_sess, input_shape=(1, 224, 224, 3), working_dir=None) block1_conv2 = layer_db.model.graph.get_operation_by_name( 'block1_conv2/Conv2D') block2_conv1 = layer_db.model.graph.get_operation_by_name( 'block2_conv1/Conv2D') block2_conv2 = layer_db.model.graph.get_operation_by_name( 'block2_conv2/Conv2D') # output shape in NCHW format block1_conv2_output_shape = block1_conv2.outputs[0].shape block2_conv1_output_shape = block2_conv1.outputs[0].shape block2_conv2_output_shape = block2_conv2.outputs[0].shape # keeping compression ratio = 0.5 for all layers layer_comp_ratio_list = [ LayerCompRatioPair( Layer(model=layer_db.model, op=block1_conv2, output_shape=block1_conv2_output_shape), 0.5), LayerCompRatioPair( Layer(model=layer_db.model, op=block2_conv1, output_shape=block2_conv1_output_shape), 0.5), LayerCompRatioPair( Layer(model=layer_db.model, op=block2_conv2, output_shape=block2_conv2_output_shape), 0.5) ] cp = InputChannelPruner(input_op_names=input_op_names, output_op_names=output_op_names, data_set=dataset, batch_size=batch_size, num_reconstruction_samples=20, allow_custom_downsample_ops=True) comp_layer_db = cp.prune_model( layer_db=layer_db, layer_comp_ratio_list=layer_comp_ratio_list, cost_metric=CostMetric.mac, trainer=None) pruned_block1_conv2 = comp_layer_db.find_layer_by_name( 'reduced_reduced_block1_conv2/Conv2D') pruned_block2_conv1 = comp_layer_db.find_layer_by_name( 'reduced_reduced_block2_conv1/Conv2D') pruned_block2_conv2 = comp_layer_db.find_layer_by_name( 'reduced_block2_conv2/Conv2D') # input channels = 64 * 0.5 = 32 # output channels = 64 * 0.5 = 32 self.assertEqual(pruned_block1_conv2.weight_shape[1], 32) self.assertEqual(pruned_block1_conv2.weight_shape[0], 32) # input channels = 64 * 0.5 = 32 # output channels = 128 * 0.5 = 64 self.assertEqual(pruned_block2_conv1.weight_shape[1], 32) self.assertEqual(pruned_block2_conv1.weight_shape[0], 64) # input channels = 128 * 0.5 = 64 # output channels = 128 self.assertEqual(pruned_block2_conv2.weight_shape[1], 64) self.assertEqual(pruned_block2_conv2.weight_shape[0], 128) layer_db.model.close() comp_layer_db.model.close() # delete temp directory shutil.rmtree(str('./temp_meta/'))
def test_datasampling_and_reconstruction(self): """ Test data sampling and reconstruction logic """ tf.compat.v1.reset_default_graph() batch_size = 1 input_data = np.random.rand(100, 224, 224, 3) dataset = tf.data.Dataset.from_tensor_slices(input_data) dataset = dataset.batch(batch_size=batch_size) orig_g = tf.Graph() with orig_g.as_default(): _ = VGG16(weights=None, input_shape=(224, 224, 3), include_top=False) orig_init = tf.compat.v1.global_variables_initializer() input_op_names = ['input_1'] output_op_names = ['block5_pool/MaxPool'] # create sess with graph orig_sess = tf.compat.v1.Session(graph=orig_g) # initialize all the variables in VGG16 orig_sess.run(orig_init) # create layer database layer_db = LayerDatabase(model=orig_sess, input_shape=(1, 224, 224, 3), working_dir=None) conv_layer = layer_db.find_layer_by_name('block1_conv1/Conv2D') comp_layer_db = copy.deepcopy(layer_db) comp_conv_layer = comp_layer_db.find_layer_by_name( 'block1_conv1/Conv2D') # get the weights before reconstruction in original model before_recon_weights_orig_model = layer_db.model.run( conv_layer.module.inputs[1]) # get the weights before reconstruction in pruned model before_recon_weights_pruned_model = comp_layer_db.model.run( comp_conv_layer.module.inputs[1]) # weight should be exactly same before reconstruction in original and pruned layer database self.assertTrue( np.array_equal(before_recon_weights_orig_model, before_recon_weights_pruned_model)) cp = InputChannelPruner(input_op_names=input_op_names, output_op_names=output_op_names, data_set=dataset, batch_size=batch_size, num_reconstruction_samples=50, allow_custom_downsample_ops=True) num_in_channels = comp_conv_layer.weight_shape[0] cp._data_subsample_and_reconstruction(orig_layer=conv_layer, pruned_layer=comp_conv_layer, output_mask=[1] * num_in_channels, orig_layer_db=layer_db, comp_layer_db=comp_layer_db) # get the weights after reconstruction after_recon_weights_pruned_model = comp_layer_db.model.run( comp_conv_layer.module.inputs[1]) # weight should not be same before and after reconstruction self.assertFalse( np.array_equal(before_recon_weights_orig_model, after_recon_weights_pruned_model)) layer_db.model.close() comp_layer_db.model.close() # delete temp directory shutil.rmtree(str('./temp_meta/'))
def create_spatial_svd_algo(cls, sess: tf.compat.v1.Session, working_dir: str, eval_callback: EvalFunction, eval_iterations, input_shape: Union[Tuple, List[Tuple]], cost_metric: CostMetric, params: SpatialSvdParameters, bokeh_session=None) -> CompressionAlgo: """ Factory method to construct SpatialSvdCompressionAlgo :param sess: Model, represented by a tf.compat.v1.Session, to compress :param working_dir: path to store temp meta and checkpoint files :param eval_callback: Evaluation callback for the model :param eval_iterations: Evaluation iterations :param input_shape: tuple or list of tuples of input shape to the model :param cost_metric: Cost metric (mac or memory) :param params: Spatial SVD compression parameters :param bokeh_session: The Bokeh Session to display plots :return: An instance of SpatialSvdCompressionAlgo """ # pylint: disable=too-many-arguments # pylint: disable=too-many-locals # Rationale: Factory functions unfortunately need to deal with a lot of parameters # Create a layer database layer_db = LayerDatabase(sess, input_shape, working_dir, starting_ops=params.input_op_names, ending_ops=params.output_op_names) use_cuda = False # Create a pruner pruner = SpatialSvdPruner() cost_calculator = SpatialSvdCostCalculator() comp_ratio_rounding_algo = RankRounder(params.multiplicity, cost_calculator) # Create a comp-ratio selection algorithm if params.mode == SpatialSvdParameters.Mode.auto: greedy_params = params.mode_params.greedy_params comp_ratio_select_algo = GreedyCompRatioSelectAlgo( layer_db, pruner, cost_calculator, eval_callback, eval_iterations, cost_metric, greedy_params.target_comp_ratio, greedy_params.num_comp_ratio_candidates, greedy_params.use_monotonic_fit, greedy_params.saved_eval_scores_dict, comp_ratio_rounding_algo, use_cuda, bokeh_session=bokeh_session) layer_selector = ConvNoDepthwiseLayerSelector() modules_to_ignore = params.mode_params.modules_to_ignore else: # Convert (module,comp-ratio) pairs to (layer,comp-ratio) pairs layer_comp_ratio_pairs = cls._get_layer_pairs( layer_db, params.mode_params.list_of_module_comp_ratio_pairs) comp_ratio_select_algo = ManualCompRatioSelectAlgo( layer_db, layer_comp_ratio_pairs, comp_ratio_rounding_algo, cost_metric=cost_metric) layer_selector = ManualLayerSelector(layer_comp_ratio_pairs) modules_to_ignore = [] # Create the overall Spatial SVD compression algorithm spatial_svd_algo = CompressionAlgo(layer_db, comp_ratio_select_algo, pruner, eval_callback, layer_selector, modules_to_ignore, cost_calculator, use_cuda) return spatial_svd_algo
def test_layer_database_deepcopy(self): # create tf.compat.v1.Session and initialize the weights and biases with zeros config = tf.compat.v1.ConfigProto() config.gpu_options.allow_growth = True # create session with graph sess = tf.compat.v1.Session(graph=tf.Graph(), config=config) with sess.graph.as_default(): # by default, model will be constructed in default graph _ = mnist_tf_model.create_model(data_format='channels_last') sess.run(tf.compat.v1.global_variables_initializer()) layer_db = LayerDatabase(model=sess, input_shape=(1, 28, 28, 1), working_dir=None) layers = list(layer_db._compressible_layers.values()) # set the picked_for_compression for first two layers layers[0].picked_for_compression = True layers[1].picked_for_compression = True layer_db_copy = copy.deepcopy(layer_db) layers_copy = list(layer_db_copy._compressible_layers.values()) # should be True self.assertTrue(layers_copy[0].picked_for_compression) self.assertTrue(layers_copy[1].picked_for_compression) # should be False self.assertFalse(layers_copy[2].picked_for_compression) self.assertFalse(layers_copy[3].picked_for_compression) # op reference should be different self.assertNotEqual(layers[0].module, layers_copy[0].module) self.assertNotEqual(layers[1].module, layers_copy[1].module) self.assertNotEqual(layers[2].module, layers_copy[2].module) self.assertNotEqual(layers[3].module, layers_copy[3].module) # layer reference should be different self.assertNotEqual(layers[0], layers_copy[0]) self.assertNotEqual(layers[1], layers_copy[1]) self.assertNotEqual(layers[2], layers_copy[2]) self.assertNotEqual(layers[3], layers_copy[3]) # op name should be same for both original and copy self.assertEqual(layers[0].name, layers_copy[0].name) self.assertEqual(layers[1].name, layers_copy[1].name) self.assertEqual(layers[2].name, layers_copy[2].name) self.assertEqual(layers[3].name, layers_copy[3].name) # session should be different self.assertNotEqual(layer_db.model, layer_db_copy.model) # check output shapes for layers in layer_db_copy self.assertEqual(layers_copy[0].output_shape, [None, 32, 28, 28]) self.assertEqual(layers_copy[1].output_shape, [None, 64, 14, 14]) self.assertEqual(layers_copy[2].output_shape, [None, 1024, 1, 1]) self.assertEqual(layers_copy[3].output_shape, [None, 10, 1, 1]) # check weight shapes for layers in layer_db_copy self.assertEqual(layers_copy[0].weight_shape, [32, 1, 5, 5]) self.assertEqual(layers_copy[1].weight_shape, [64, 32, 5, 5]) self.assertEqual(layers_copy[2].weight_shape, [1024, 3136]) self.assertEqual(layers_copy[3].weight_shape, [10, 1024]) # check the weight elements are equal in both data bases self.assertTrue(np.array_equal(layers[0].module.inputs[1].eval(session=layer_db.model), layers_copy[0].module.inputs[1].eval(session=layer_db_copy.model))) self.assertTrue(np.array_equal(layers[1].module.inputs[1].eval(session=layer_db.model), layers_copy[1].module.inputs[1].eval(session=layer_db_copy.model))) self.assertTrue(np.array_equal(layers[2].module.inputs[1].eval(session=layer_db.model), layers_copy[2].module.inputs[1].eval(session=layer_db_copy.model))) self.assertTrue(np.array_equal(layers[3].module.inputs[1].eval(session=layer_db.model), layers_copy[3].module.inputs[1].eval(session=layer_db_copy.model))) tf.compat.v1.reset_default_graph() sess.close() # delete temp directory shutil.rmtree(str('./temp_meta/'))