def rand_crop(img: tf_compat.Tensor): with tf_compat.name_scope(name): orig_shape = tf_compat.shape(img) scale = tf_compat.random_uniform(shape=[1], minval=scale_range[0], maxval=scale_range[1])[0] ratio = tf_compat.random_uniform(shape=[1], minval=ratio_range[0], maxval=ratio_range[1])[0] height = tf_compat.minimum( tf_compat.cast( tf_compat.round( tf_compat.cast(orig_shape[0], dtype=tf_compat.float32) * scale / ratio), tf_compat.int32, ), orig_shape[0], ) width = tf_compat.minimum( tf_compat.cast( tf_compat.round( tf_compat.cast(orig_shape[1], dtype=tf_compat.float32) * scale), tf_compat.int32, ), orig_shape[1], ) img = tf_compat.random_crop(img, [height, width, orig_shape[2]]) return img
def _calc_lr(): less = tf_compat.cast( tf_compat.greater_equal(global_step, milestone_steps), tf_compat.int64) updates = tf_compat.reduce_sum(less) mult_g = tf_compat.pow(gamma, tf_compat.cast(updates, tf_compat.float32)) return tf_compat.multiply(init_lr, mult_g)
def zero_fraction(inp: tf_compat.Tensor): nonzero = tf_compat.cast( tf_compat.reduce_sum( tf_compat.cast(tf_compat.not_equal(inp, 0), tf_compat.int64)), tf_compat.float32, ) size = tf_compat.size(inp, out_type=tf_compat.float32) return 1 - tf_compat_div(nonzero, size)
def _calc_lr(): steps = tf_compat.subtract(global_step, start_step) updates = tf_compat.cond( after, lambda: max_updates, lambda: tf_compat.cast( tf_compat.floor(tf_compat.divide(steps, step_size)), tf_compat.int64, ), ) mult_g = tf_compat.pow(gamma, tf_compat.cast(updates, tf_compat.float32)) return tf_compat.multiply(init_lr, mult_g)
def _set_constant_mask(): # Assign mask tensor to be 1 for all nonzero values of op_var_tens otherwise 0 # On end step, revert mask to be all 1s with tf_compat.name_scope( PruningScope.model( op, ks_group, additional=PruningScope.OPS_UPDATE, trailing_slash=True, )): new_mask = tf_compat.cond( is_start_step, lambda: tf_compat.cast(tf_compat.not_equal(op_var_tens, 0.0), dtype=op_var_tens.dtype), lambda: tf_compat.ones(op_var_tens.shape, dtype=op_var_tens.dtype), ) weight_var = get_tensor_var(op_var_tens) return tf_compat.group( tf_compat.assign(mask, new_mask, name=PruningScope.OP_MASK_ASSIGN), tf_compat.assign(weight_var, masked, name=PruningScope.OP_WEIGHT_UPDATE), )
def cent_crop(img: tf_compat.Tensor): with tf_compat.name_scope(name): orig_shape = tf_compat.shape(img) min_size = tf_compat.cond( tf_compat.greater_equal(orig_shape[0], orig_shape[1]), lambda: orig_shape[1], lambda: orig_shape[0], ) if padding > 0: orig_shape_list = img.get_shape().as_list() resize((orig_shape_list[0] + 2 * padding, orig_shape_list[1] + 2 * padding)) padding_height = tf_compat.add( tf_compat.cast( tf_compat.round( tf_compat.div( tf_compat.cast( tf_compat.subtract(orig_shape[0], min_size), tf_compat.float32, ), 2.0, )), tf_compat.int32, ), padding, ) padding_width = tf_compat.add( tf_compat.cast( tf_compat.round( tf_compat.div( tf_compat.cast( tf_compat.subtract(orig_shape[1], min_size), tf_compat.float32, ), 2.0, )), tf_compat.int32, ), padding, ) img = tf_compat.image.crop_to_bounding_box(img, padding_height, padding_width, min_size, min_size) return img
def create_sparsity_mask( self, tensor: tf_compat.Tensor, sparsity: tf_compat.Tensor, ) -> tf_compat.Tensor: """ :param tensor: A tensor of a model layer's weights :param sparsity: the target sparsity to use for assigning the masks :return: A sparsity mask close to the set sparsity based on the values of the input tensor """ abs_var = tf_compat.abs(tensor) # Magnitudes of weights sparse_threshold_index = tf_compat.cast( tf_compat.round( tf_compat.cast(tf_compat.size(abs_var), tf_compat.float32) * sparsity), tf_compat.int32, ) sparse_threshold_index = tf_compat.minimum( tf_compat.maximum(sparse_threshold_index, 0), tf_compat.size(tensor) - 1, ) try: argsort = tf_compat.argsort except Exception: try: argsort = tf_compat.contrib.framework.argsort except Exception: raise RuntimeError( "cannot find argsort function in tensorflow_v1, " "currently unsupported") # produce tensor where each element is the index in sorted order of abs_var abs_var_flat = tf_compat.reshape(abs_var, [-1]) element_ranks_flat = tf_compat.scatter_nd( tf_compat.expand_dims(argsort(abs_var_flat), 1), tf_compat.range(abs_var_flat.get_shape()[0].value), abs_var_flat.get_shape(), ) element_ranks = tf_compat.reshape(element_ranks_flat, abs_var.get_shape()) return tf_compat.cast( tf_compat.greater(element_ranks, sparse_threshold_index), tf_compat.float32, )
def multi_step_lr_schedule( global_step: tf_compat.Tensor, start_step: int, milestone_steps: List[int], init_lr: float, gamma: float, name: str = "multi_step_lr_schedule", ): """ Create a multi step learning rate schedule in the current graph. Multiplies init_lr by gamma after each milestone has passed. Ex: lr = init_lr * (gamma ** NUM_UPDATES) :param global_step: the global step used for training :param start_step: the step to start the exponential schedule on :param milestone_steps: a list of steps to decrease the learning rate at, these are the number of steps that must pass after start_step to decrease lr :param init_lr: the learning rate to start the schedule with :param gamma: the decay weight to decrease init_lr by after every step_size interval :param name: the name scope to create the graph under :return: the calculated learning rate tensor """ with tf_compat.name_scope(name): global_step = tf_compat.cast(global_step, tf_compat.int64) milestone_steps = tf_compat.constant( [mile + start_step for mile in milestone_steps], dtype=tf_compat.int64, name="milestone_steps", ) start_step = tf_compat.constant(start_step, dtype=tf_compat.int64, name="start_step") init_lr = tf_compat.constant(init_lr, dtype=tf_compat.float32, name="init_lr") gamma = tf_compat.constant(gamma, dtype=tf_compat.float32, name="gamma") before = tf_compat.less(global_step, start_step, name="before") def _calc_lr(): less = tf_compat.cast( tf_compat.greater_equal(global_step, milestone_steps), tf_compat.int64) updates = tf_compat.reduce_sum(less) mult_g = tf_compat.pow(gamma, tf_compat.cast(updates, tf_compat.float32)) return tf_compat.multiply(init_lr, mult_g) learning_rate = tf_compat.cond(before, lambda: init_lr, _calc_lr, name="learning_rate") return learning_rate
def non_zero_mask_initializer( shape: tf_compat.TensorShape, dtype: tf_compat.DType = tf_compat.float32, partition_info: Any = None, # unsued variable for compatability ) -> tf_compat.Tensor: dtype = tf_compat.as_dtype(dtype) if not dtype.is_numpy_compatible or dtype == tf_compat.string: raise ValueError("Expected numeric or boolean dtype, got %s." % dtype) return tf_compat.cast(tf_compat.not_equal(tensor, 0.0), dtype=dtype)
def preprocess_for_eval(image: tf_compat.Tensor): """ The default preprocessing function for test set as defined in Resnet paper for Cifar datasets :param image: the image tensor :return: the preprocessed image """ with tf_compat.name_scope("test_preprocess"): image = tf_compat.cast(image, dtype=tf_compat.float32) image = tf_compat_div(image, 255.0) image = tf_compat.image.random_crop(image, [32, 32, 3]) return image
def create_ops( self, steps_per_epoch: int, global_step: tf_compat.Variable, graph: tf_compat.Graph, ) -> Tuple[List[Union[tf_compat.Tensor, tf_compat.Operation]], Dict[str, Any]]: """ Create switch case computing the learning rate at a given global step and extras created by individual LR modifiers :param steps_per_epoch: the number of steps per training epoch :param global_step: the global step used while training :param graph: the graph to be modified :return: a tuple (list of empty ops, dict of named ops/tensors for learning rate and summaries as extras) """ mod_ops, mod_extras = super().create_ops(graph, steps_per_epoch, global_step) name_scope = "{}/{}".format(NM_RECAL, self.__class__.__name__) with graph.as_default(): with tf_compat.name_scope(name_scope): pred_fn_pairs = [] global_step = tf_compat.cast(global_step, tf_compat.int64) for index, child in enumerate(self._lr_modifiers): with tf_compat.name_scope(str(index)): _, child_extras = child.create_ops( steps_per_epoch, global_step, graph ) child_lr = child_extras[EXTRAS_KEY_LEARNING_RATE] child_start_step, _ = child.start_end_steps( steps_per_epoch, after_optim=False ) child_select = tf_compat.greater_equal( global_step, tf_compat.constant(child_start_step, tf_compat.int64), name="active", ) pred_fn_pairs.append((child_select, lambda lr=child_lr: lr)) learning_rate = tf_compat.case(pred_fn_pairs) _add_lr_extras(mod_extras, learning_rate, self.log_types) return mod_ops, mod_extras
def processor(self, file_path: tf_compat.Tensor, label: tf_compat.Tensor): """ :param file_path: the path to the file to load an image from :param label: the label for the given image :return: a tuple containing the processed image and label """ with tf_compat.name_scope("img_to_tensor"): img = tf_compat.read_file(file_path) # Decode and reshape the image to 3 dimensional tensor # Note: "expand_animations" not available for TF 1.13 and prior, # hence the reshape trick below img = tf_compat.image.decode_image(img) img_shape = tf_compat.shape(img) img = tf_compat.reshape(img, [img_shape[0], img_shape[1], img_shape[2]]) img = tf_compat.cast(img, dtype=tf_compat.float32) if self.pre_resize_transforms: transforms = (self.pre_resize_transforms.train if self.train else self.pre_resize_transforms.val) if transforms: with tf_compat.name_scope("pre_resize_transforms"): for trans in transforms: img = trans(img) if self._image_size: res_callable = resize((self.image_size, self.image_size)) img = res_callable(img) if self.post_resize_transforms: transforms = (self.post_resize_transforms.train if self.train else self.post_resize_transforms.val) if transforms: with tf_compat.name_scope("post_resize_transforms"): for trans in transforms: img = trans(img) return img, label
def preprocess_for_train(image: tf_compat.Tensor): """ The default preprocessing function for train set as defined in Resnet paper for Cifar datasets :param image: the image tensor :return: the preprocessed image """ with tf_compat.name_scope("train_preprocess"): image = tf_compat.cast(image, dtype=tf_compat.float32) rand_choice = tf_compat.random_uniform(shape=[], minval=0, maxval=2, dtype=tf_compat.int32) padding = _PADDING image = tf_compat.cond( tf_compat.equal(rand_choice, 0), lambda: tf_compat.pad(image, [[padding, padding], [padding, padding], [0, 0]]), lambda: tf_compat.image.random_flip_left_right(image), ) distorted_image = tf_compat.image.random_crop(image, [32, 32, 3]) return distorted_image
def create_ks_schedule_ops( global_step: tf_compat.Variable, begin_step: int, end_step: int, update_step_freq: int, init_sparsity: float, final_sparsity: float, exponent: float, ks_group: str, ) -> Tuple[tf_compat.Tensor, tf_compat.Tensor]: """ Create a gradual schedule for model pruning (kernel sparsity). Creates a sparsity tensor that goes from init_sparsity til final_sparsity starting at begin_step and ending at end_step. Uses the global_step to map those. Additionally creates an update_ready tensor that is True if an update to the sparsity tensor should be run, False otherwise. :param global_step: the global optimizer step for the training graph :param begin_step: the global step to begin pruning at :param end_step: the global step to end pruning at :param update_step_freq: the number of global steps between each weight update :param init_sparsity: the starting value for sparsity of a weight tensor to be enforce :param final_sparsity: the end value for sparsity for a weight tensor to be enforce :param exponent: the exponent to use for interpolating between init_sparsity and final_sparsity higher values will lead to larger sparsity steps at the beginning vs the end ie: linear (1) vs cubic (3) :param ks_group: the group identifier the scope should be created under :return: a tuple containing the signal for update_ready and the target sparsity """ # create the scheduling ops first and the sparsity ops with tf_compat.name_scope( PruningScope.general(ks_group, additional=PruningScope.OPS_SCHEDULE, trailing_slash=True)): sched_before = tf_compat.less(global_step, begin_step) sched_start = tf_compat.equal(global_step, begin_step) sched_end = tf_compat.equal(global_step, end_step) sched_active = tf_compat.logical_and( tf_compat.greater(global_step, begin_step), tf_compat.less(global_step, end_step), ) sched_active_inclusive = tf_compat.logical_or( sched_active, tf_compat.logical_or(sched_start, sched_end)) sched_update = tf_compat.cond( tf_compat.less_equal(update_step_freq, 0), lambda: tf_compat.constant(True), lambda: tf_compat.equal( tf_compat.mod( (global_step - begin_step), update_step_freq), 0), ) sched_update_ready = tf_compat.logical_or( tf_compat.logical_or(sched_start, sched_end), sched_update) percentage = tf_compat.minimum( 1.0, tf_compat.maximum( 0.0, tf_compat_div( tf_compat.cast(global_step - begin_step, tf_compat.float32), end_step - begin_step, ), ), ) exp_percentage = 1 - tf_compat.pow(1 - percentage, exponent) calc_sparsity = (tf_compat.multiply(final_sparsity - init_sparsity, exp_percentage) + init_sparsity) # create the update ready tensor and sparsity tensor with tf_compat.name_scope( PruningScope.general(ks_group, trailing_slash=True)): update_ready = tf_compat.logical_and( sched_active_inclusive, sched_update_ready, name=PruningScope.OP_UPDATE_READY, ) sparsity = tf_compat.case( [ (sched_before, lambda: tf_compat.constant(0.0)), (sched_start, lambda: tf_compat.constant(init_sparsity)), (sched_active, lambda: calc_sparsity), ], default=lambda: tf_compat.constant(final_sparsity), name=PruningScope.OP_SPARSITY, ) # add return state to collections tf_compat.add_to_collection( PruningScope.collection_name(ks_group, PruningScope.OP_UPDATE_READY), update_ready, ) tf_compat.add_to_collection( PruningScope.collection_name(ks_group, PruningScope.OP_SPARSITY), sparsity) return update_ready, sparsity
def step_lr_schedule( global_step: tf_compat.Tensor, start_step: int, end_step: int, step_size: int, init_lr: float, gamma: float, name: str = "exponential_lr_schedule", ) -> tf_compat.Tensor: """ Create an exponential learning rate schedule in the current graph. Multiplies init_lr by gamma after each step_size interval has passed. Ex: lr = init_lr * (gamma ** NUM_UPDATES) :param global_step: the global step used for training :param start_step: the step to start the exponential schedule on :param end_step: the step to end the exponential schedule on, can be set to -1 and in that event will continually update the LR :param step_size: the number of steps between each gamma update to the init_lr :param init_lr: the learning rate to start the schedule with :param gamma: the decay weight to decrease init_lr by after every step_size interval :param name: the name scope to create the graph under :return: the calculated learning rate tensor """ with tf_compat.name_scope(name): global_step = tf_compat.cast(global_step, tf_compat.int64) max_updates = tf_compat.constant( (end_step - start_step) // step_size if end_step > 0 else -1, dtype=tf_compat.int64, name="max_updates", ) start_step = tf_compat.constant(start_step, dtype=tf_compat.int64, name="start_step") end_step = tf_compat.constant(end_step, dtype=tf_compat.int64, name="end_step") init_lr = tf_compat.constant(init_lr, dtype=tf_compat.float32, name="init_lr") step_size = tf_compat.constant(step_size, dtype=tf_compat.int64, name="step_size") gamma = tf_compat.constant(gamma, dtype=tf_compat.float32, name="gamma") before = tf_compat.less(global_step, start_step, name="before") after = tf_compat.logical_and( tf_compat.greater_equal(global_step, end_step, name="after"), tf_compat.not_equal(end_step, tf_compat.constant(-1, tf_compat.int64)), ) def _calc_lr(): steps = tf_compat.subtract(global_step, start_step) updates = tf_compat.cond( after, lambda: max_updates, lambda: tf_compat.cast( tf_compat.floor(tf_compat.divide(steps, step_size)), tf_compat.int64, ), ) mult_g = tf_compat.pow(gamma, tf_compat.cast(updates, tf_compat.float32)) return tf_compat.multiply(init_lr, mult_g) learning_rate = tf_compat.cond(before, lambda: init_lr, _calc_lr, name="learning_rate") return learning_rate