diff --git a/bob/__init__.py b/bob/__init__.py index 2ca5e07cb73f0bdddcb863ef497955964087e301..edbb4090fca046b19d22d3982711084621bff3be 100644 --- a/bob/__init__.py +++ b/bob/__init__.py @@ -1,3 +1,4 @@ # see https://docs.python.org/3/library/pkgutil.html from pkgutil import extend_path -__path__ = extend_path(__path__, __name__) \ No newline at end of file + +__path__ = extend_path(__path__, __name__) diff --git a/bob/learn/__init__.py b/bob/learn/__init__.py index 2ab1e28b150f0549def9963e9e87de3fdd6b2579..edbb4090fca046b19d22d3982711084621bff3be 100644 --- a/bob/learn/__init__.py +++ b/bob/learn/__init__.py @@ -1,3 +1,4 @@ # see https://docs.python.org/3/library/pkgutil.html from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) diff --git a/bob/learn/tensorflow/__init__.py b/bob/learn/tensorflow/__init__.py index b17348644c602f4d16633e7c392b19a37c59086a..1b0e2db6061b8fcd5e328762aadf66d77a398193 100644 --- a/bob/learn/tensorflow/__init__.py +++ b/bob/learn/tensorflow/__init__.py @@ -3,4 +3,5 @@ def get_config(): Returns a string containing the configuration information. """ import bob.extension + return bob.extension.get_config(__name__) diff --git a/bob/learn/tensorflow/dataset/generator.py b/bob/learn/tensorflow/dataset/generator.py index 54d700949e5f72af67c6861a0f1161e310e97136..a8510487eda8a6f6e483a3c4cc298e9f0fc4870c 100644 --- a/bob/learn/tensorflow/dataset/generator.py +++ b/bob/learn/tensorflow/dataset/generator.py @@ -30,7 +30,14 @@ class Generator: The shapes of the returned samples. """ - def __init__(self, samples, reader, multiple_samples=False, shuffle_on_epoch_end=False, **kwargs): + def __init__( + self, + samples, + reader, + multiple_samples=False, + shuffle_on_epoch_end=False, + **kwargs + ): super().__init__(**kwargs) self.reader = reader self.samples = list(samples) diff --git a/bob/learn/tensorflow/dataset/image.py b/bob/learn/tensorflow/dataset/image.py index bc8c315e2ca8729100406e5c7f33af26d838ecb0..239d83c251e856e8e2c527e9d7782390b3a87139 100644 --- a/bob/learn/tensorflow/dataset/image.py +++ b/bob/learn/tensorflow/dataset/image.py @@ -7,22 +7,24 @@ from functools import partial from . import append_image_augmentation, from_filename_to_tensor -def shuffle_data_and_labels_image_augmentation(filenames, - labels, - data_shape, - data_type, - batch_size, - epochs=None, - buffer_size=10**3, - gray_scale=False, - output_shape=None, - random_flip=False, - random_brightness=False, - random_contrast=False, - random_saturation=False, - random_rotate=False, - per_image_normalization=True, - extension=None): +def shuffle_data_and_labels_image_augmentation( + filenames, + labels, + data_shape, + data_type, + batch_size, + epochs=None, + buffer_size=10 ** 3, + gray_scale=False, + output_shape=None, + random_flip=False, + random_brightness=False, + random_contrast=False, + random_saturation=False, + random_rotate=False, + per_image_normalization=True, + extension=None, +): """ Dump random batches from a list of image paths and labels: @@ -95,7 +97,8 @@ def shuffle_data_and_labels_image_augmentation(filenames, random_saturation=random_saturation, random_rotate=random_rotate, per_image_normalization=per_image_normalization, - extension=extension) + extension=extension, + ) dataset = dataset.shuffle(buffer_size).batch(batch_size).repeat(epochs) @@ -103,19 +106,21 @@ def shuffle_data_and_labels_image_augmentation(filenames, return data, labels -def create_dataset_from_path_augmentation(filenames, - labels, - data_shape, - data_type, - gray_scale=False, - output_shape=None, - random_flip=False, - random_brightness=False, - random_contrast=False, - random_saturation=False, - random_rotate=False, - per_image_normalization=True, - extension=None): +def create_dataset_from_path_augmentation( + filenames, + labels, + data_shape, + data_type, + gray_scale=False, + output_shape=None, + random_flip=False, + random_brightness=False, + random_contrast=False, + random_saturation=False, + random_rotate=False, + per_image_normalization=True, + extension=None, +): """ Create dataset from a list of tf-record files @@ -149,26 +154,29 @@ def create_dataset_from_path_augmentation(filenames, random_saturation=random_saturation, random_rotate=random_rotate, per_image_normalization=per_image_normalization, - extension=extension) + extension=extension, + ) dataset = tf.data.Dataset.from_tensor_slices((filenames, labels)) dataset = dataset.map(parser) return dataset -def image_augmentation_parser(filename, - label, - data_shape, - data_type, - gray_scale=False, - output_shape=None, - random_flip=False, - random_brightness=False, - random_contrast=False, - random_saturation=False, - random_rotate=False, - per_image_normalization=True, - extension=None): +def image_augmentation_parser( + filename, + label, + data_shape, + data_type, + gray_scale=False, + output_shape=None, + random_flip=False, + random_brightness=False, + random_contrast=False, + random_saturation=False, + random_rotate=False, + per_image_normalization=True, + extension=None, +): """ Parses a single tf.Example into image and label tensors. """ @@ -179,7 +187,7 @@ def image_augmentation_parser(filename, # Reshape image data into the original shape image = tf.reshape(image, data_shape) - #Applying image augmentation + # Applying image augmentation image = append_image_augmentation( image, gray_scale=gray_scale, @@ -189,12 +197,13 @@ def image_augmentation_parser(filename, random_contrast=random_contrast, random_saturation=random_saturation, random_rotate=random_rotate, - per_image_normalization=per_image_normalization) + per_image_normalization=per_image_normalization, + ) label = tf.cast(label, tf.int64) features = dict() - features['data'] = image - features['key'] = filename + features["data"] = image + features["key"] = filename return features, label diff --git a/bob/learn/tensorflow/dataset/tfrecords.py b/bob/learn/tensorflow/dataset/tfrecords.py index 0fbf69e9b88aac01f3925810ff84f9b02e65056f..32f732733eb1d5d34c1a630f0900a24c52a6309e 100644 --- a/bob/learn/tensorflow/dataset/tfrecords.py +++ b/bob/learn/tensorflow/dataset/tfrecords.py @@ -227,5 +227,3 @@ def dataset_from_tfrecord(tfrecord, num_parallel_reads=None): # key = tf.cast(features["key"], tf.string) # return image, label, key - - diff --git a/bob/learn/tensorflow/gan/losses.py b/bob/learn/tensorflow/gan/losses.py index 46be1eaa8c76b3a253968d609a1948fc08a013cd..843f795c10c077c0b3757aadccbedefd70806909 100644 --- a/bob/learn/tensorflow/gan/losses.py +++ b/bob/learn/tensorflow/gan/losses.py @@ -14,26 +14,26 @@ def relativistic_discriminator_loss( ): """Relativistic (average) loss - Args: - discriminator_real_outputs: Discriminator output on real data. - discriminator_gen_outputs: Discriminator output on generated data. Expected - to be in the range of (-inf, inf). - label_smoothing: The amount of smoothing for positive labels. This technique - is taken from `Improved Techniques for Training GANs` - (https://arxiv.org/abs/1606.03498). `0.0` means no smoothing. - real_weights: Optional `Tensor` whose rank is either 0, or the same rank as - `real_data`, and must be broadcastable to `real_data` (i.e., all - dimensions must be either `1`, or the same as the corresponding - dimension). - generated_weights: Same as `real_weights`, but for `generated_data`. - scope: The scope for the operations performed in computing the loss. - loss_collection: collection to which this loss will be added. - reduction: A `tf.compat.v1.losses.Reduction` to apply to loss. - add_summaries: Whether or not to add summaries for the loss. - - Returns: - A loss Tensor. The shape depends on `reduction`. - """ + Args: + discriminator_real_outputs: Discriminator output on real data. + discriminator_gen_outputs: Discriminator output on generated data. Expected + to be in the range of (-inf, inf). + label_smoothing: The amount of smoothing for positive labels. This technique + is taken from `Improved Techniques for Training GANs` + (https://arxiv.org/abs/1606.03498). `0.0` means no smoothing. + real_weights: Optional `Tensor` whose rank is either 0, or the same rank as + `real_data`, and must be broadcastable to `real_data` (i.e., all + dimensions must be either `1`, or the same as the corresponding + dimension). + generated_weights: Same as `real_weights`, but for `generated_data`. + scope: The scope for the operations performed in computing the loss. + loss_collection: collection to which this loss will be added. + reduction: A `tf.compat.v1.losses.Reduction` to apply to loss. + add_summaries: Whether or not to add summaries for the loss. + + Returns: + A loss Tensor. The shape depends on `reduction`. + """ with tf.compat.v1.name_scope( scope, "discriminator_relativistic_loss", @@ -75,8 +75,12 @@ def relativistic_discriminator_loss( tf.compat.v1.losses.add_loss(loss, loss_collection) if add_summaries: - tf.compat.v1.summary.scalar("discriminator_gen_relativistic_loss", loss_on_generated) - tf.compat.v1.summary.scalar("discriminator_real_relativistic_loss", loss_on_real) + tf.compat.v1.summary.scalar( + "discriminator_gen_relativistic_loss", loss_on_generated + ) + tf.compat.v1.summary.scalar( + "discriminator_real_relativistic_loss", loss_on_real + ) tf.compat.v1.summary.scalar("discriminator_relativistic_loss", loss) return loss @@ -96,26 +100,26 @@ def relativistic_generator_loss( ): """Relativistic (average) loss - Args: - discriminator_real_outputs: Discriminator output on real data. - discriminator_gen_outputs: Discriminator output on generated data. Expected - to be in the range of (-inf, inf). - label_smoothing: The amount of smoothing for positive labels. This technique - is taken from `Improved Techniques for Training GANs` - (https://arxiv.org/abs/1606.03498). `0.0` means no smoothing. - real_weights: Optional `Tensor` whose rank is either 0, or the same rank as - `real_data`, and must be broadcastable to `real_data` (i.e., all - dimensions must be either `1`, or the same as the corresponding - dimension). - generated_weights: Same as `real_weights`, but for `generated_data`. - scope: The scope for the operations performed in computing the loss. - loss_collection: collection to which this loss will be added. - reduction: A `tf.compat.v1.losses.Reduction` to apply to loss. - add_summaries: Whether or not to add summaries for the loss. - - Returns: - A loss Tensor. The shape depends on `reduction`. - """ + Args: + discriminator_real_outputs: Discriminator output on real data. + discriminator_gen_outputs: Discriminator output on generated data. Expected + to be in the range of (-inf, inf). + label_smoothing: The amount of smoothing for positive labels. This technique + is taken from `Improved Techniques for Training GANs` + (https://arxiv.org/abs/1606.03498). `0.0` means no smoothing. + real_weights: Optional `Tensor` whose rank is either 0, or the same rank as + `real_data`, and must be broadcastable to `real_data` (i.e., all + dimensions must be either `1`, or the same as the corresponding + dimension). + generated_weights: Same as `real_weights`, but for `generated_data`. + scope: The scope for the operations performed in computing the loss. + loss_collection: collection to which this loss will be added. + reduction: A `tf.compat.v1.losses.Reduction` to apply to loss. + add_summaries: Whether or not to add summaries for the loss. + + Returns: + A loss Tensor. The shape depends on `reduction`. + """ with tf.compat.v1.name_scope( scope, "generator_relativistic_loss", @@ -164,8 +168,12 @@ def relativistic_generator_loss( tf.compat.v1.losses.add_loss(loss, loss_collection) if add_summaries: - tf.compat.v1.summary.scalar("generator_gen_relativistic_loss", loss_on_generated) - tf.compat.v1.summary.scalar("generator_real_relativistic_loss", loss_on_real) + tf.compat.v1.summary.scalar( + "generator_gen_relativistic_loss", loss_on_generated + ) + tf.compat.v1.summary.scalar( + "generator_real_relativistic_loss", loss_on_real + ) tf.compat.v1.summary.scalar("generator_relativistic_loss", loss) return loss diff --git a/bob/learn/tensorflow/gan/spectral_normalization.py b/bob/learn/tensorflow/gan/spectral_normalization.py index ad2ecfaa4487892592f733858c88f0c24b569912..a2f228f07f66016ac0cc0f01a7d5f2831d4d5be4 100644 --- a/bob/learn/tensorflow/gan/spectral_normalization.py +++ b/bob/learn/tensorflow/gan/spectral_normalization.py @@ -38,279 +38,290 @@ from tensorflow.python.ops import variable_scope from tensorflow.python.platform import tf_logging as logging __all__ = [ - 'compute_spectral_norm', 'spectral_normalize', 'spectral_norm_regularizer', - 'spectral_normalization_custom_getter', 'keras_spectral_normalization' + "compute_spectral_norm", + "spectral_normalize", + "spectral_norm_regularizer", + "spectral_normalization_custom_getter", + "keras_spectral_normalization", ] # tf.bfloat16 should work, but tf.matmul converts those to tf.float32 which then # can't directly be assigned back to the tf.bfloat16 variable. _OK_DTYPES_FOR_SPECTRAL_NORM = (dtypes.float16, dtypes.float32, dtypes.float64) -_PERSISTED_U_VARIABLE_SUFFIX = 'spectral_norm_u' +_PERSISTED_U_VARIABLE_SUFFIX = "spectral_norm_u" def compute_spectral_norm(w_tensor, power_iteration_rounds=1, name=None): - """Estimates the largest singular value in the weight tensor. - - Args: - w_tensor: The weight matrix whose spectral norm should be computed. - power_iteration_rounds: The number of iterations of the power method to - perform. A higher number yields a better approximation. - name: An optional scope name. - - Returns: - The largest singular value (the spectral norm) of w. - """ - with variable_scope.variable_scope(name, 'spectral_norm'): - # The paper says to flatten convnet kernel weights from - # (C_out, C_in, KH, KW) to (C_out, C_in * KH * KW). But TensorFlow's Conv2D - # kernel weight shape is (KH, KW, C_in, C_out), so it should be reshaped to - # (KH * KW * C_in, C_out), and similarly for other layers that put output - # channels as last dimension. - # n.b. this means that w here is equivalent to w.T in the paper. - w = array_ops.reshape(w_tensor, (-1, w_tensor.get_shape()[-1])) - - # Persisted approximation of first left singular vector of matrix `w`. - u_var = variable_scope.get_variable( - _PERSISTED_U_VARIABLE_SUFFIX, - shape=(w.shape[0], 1), - dtype=w.dtype, - initializer=init_ops.random_normal_initializer(), - trainable=False) - u = u_var - - # Use power iteration method to approximate spectral norm. - for _ in range(power_iteration_rounds): - # `v` approximates the first right singular vector of matrix `w`. - v = nn.l2_normalize(math_ops.matmul(array_ops.transpose(w), u)) - u = nn.l2_normalize(math_ops.matmul(w, v)) - - # Update persisted approximation. - with ops.control_dependencies([u_var.assign(u, name='update_u')]): - u = array_ops.identity(u) - - u = array_ops.stop_gradient(u) - v = array_ops.stop_gradient(v) - - # Largest singular value of `w`. - spectral_norm = math_ops.matmul( - math_ops.matmul(array_ops.transpose(u), w), v) - spectral_norm.shape.assert_is_fully_defined() - spectral_norm.shape.assert_is_compatible_with([1, 1]) - - return spectral_norm[0][0] + """Estimates the largest singular value in the weight tensor. + + Args: + w_tensor: The weight matrix whose spectral norm should be computed. + power_iteration_rounds: The number of iterations of the power method to + perform. A higher number yields a better approximation. + name: An optional scope name. + + Returns: + The largest singular value (the spectral norm) of w. + """ + with variable_scope.variable_scope(name, "spectral_norm"): + # The paper says to flatten convnet kernel weights from + # (C_out, C_in, KH, KW) to (C_out, C_in * KH * KW). But TensorFlow's Conv2D + # kernel weight shape is (KH, KW, C_in, C_out), so it should be reshaped to + # (KH * KW * C_in, C_out), and similarly for other layers that put output + # channels as last dimension. + # n.b. this means that w here is equivalent to w.T in the paper. + w = array_ops.reshape(w_tensor, (-1, w_tensor.get_shape()[-1])) + + # Persisted approximation of first left singular vector of matrix `w`. + u_var = variable_scope.get_variable( + _PERSISTED_U_VARIABLE_SUFFIX, + shape=(w.shape[0], 1), + dtype=w.dtype, + initializer=init_ops.random_normal_initializer(), + trainable=False, + ) + u = u_var + + # Use power iteration method to approximate spectral norm. + for _ in range(power_iteration_rounds): + # `v` approximates the first right singular vector of matrix `w`. + v = nn.l2_normalize(math_ops.matmul(array_ops.transpose(w), u)) + u = nn.l2_normalize(math_ops.matmul(w, v)) + + # Update persisted approximation. + with ops.control_dependencies([u_var.assign(u, name="update_u")]): + u = array_ops.identity(u) + + u = array_ops.stop_gradient(u) + v = array_ops.stop_gradient(v) + + # Largest singular value of `w`. + spectral_norm = math_ops.matmul(math_ops.matmul(array_ops.transpose(u), w), v) + spectral_norm.shape.assert_is_fully_defined() + spectral_norm.shape.assert_is_compatible_with([1, 1]) + + return spectral_norm[0][0] def spectral_normalize(w, power_iteration_rounds=1, name=None): - """Normalizes a weight matrix by its spectral norm. + """Normalizes a weight matrix by its spectral norm. - Args: - w: The weight matrix to be normalized. - power_iteration_rounds: The number of iterations of the power method to - perform. A higher number yields a better approximation. - name: An optional scope name. + Args: + w: The weight matrix to be normalized. + power_iteration_rounds: The number of iterations of the power method to + perform. A higher number yields a better approximation. + name: An optional scope name. - Returns: - A normalized weight matrix tensor. - """ - with variable_scope.variable_scope(name, 'spectral_normalize'): - w_normalized = w / compute_spectral_norm( - w, power_iteration_rounds=power_iteration_rounds) - return array_ops.reshape(w_normalized, w.get_shape()) + Returns: + A normalized weight matrix tensor. + """ + with variable_scope.variable_scope(name, "spectral_normalize"): + w_normalized = w / compute_spectral_norm( + w, power_iteration_rounds=power_iteration_rounds + ) + return array_ops.reshape(w_normalized, w.get_shape()) def spectral_norm_regularizer(scale, power_iteration_rounds=1, scope=None): - """Returns a functions that can be used to apply spectral norm regularization. - - Small spectral norms enforce a small Lipschitz constant, which is necessary - for Wasserstein GANs. - - Args: - scale: A scalar multiplier. 0.0 disables the regularizer. - power_iteration_rounds: The number of iterations of the power method to - perform. A higher number yields a better approximation. - scope: An optional scope name. - - Returns: - A function with the signature `sn(weights)` that applies spectral norm - regularization. - - Raises: - ValueError: If scale is negative or if scale is not a float. - """ - if isinstance(scale, numbers.Integral): - raise ValueError('scale cannot be an integer: %s' % scale) - if isinstance(scale, numbers.Real): - if scale < 0.0: - raise ValueError( - 'Setting a scale less than 0 on a regularizer: %g' % scale) - if scale == 0.0: - logging.info('Scale of 0 disables regularizer.') - return lambda _: None - - def sn(weights, name=None): - """Applies spectral norm regularization to weights.""" - with ops.name_scope(scope, 'SpectralNormRegularizer', [weights]) as name: - scale_t = ops.convert_to_tensor( - scale, dtype=weights.dtype.base_dtype, name='scale') - return math_ops.multiply( - scale_t, - compute_spectral_norm( - weights, power_iteration_rounds=power_iteration_rounds), - name=name) - - return sn + """Returns a functions that can be used to apply spectral norm regularization. + + Small spectral norms enforce a small Lipschitz constant, which is necessary + for Wasserstein GANs. + + Args: + scale: A scalar multiplier. 0.0 disables the regularizer. + power_iteration_rounds: The number of iterations of the power method to + perform. A higher number yields a better approximation. + scope: An optional scope name. + + Returns: + A function with the signature `sn(weights)` that applies spectral norm + regularization. + + Raises: + ValueError: If scale is negative or if scale is not a float. + """ + if isinstance(scale, numbers.Integral): + raise ValueError("scale cannot be an integer: %s" % scale) + if isinstance(scale, numbers.Real): + if scale < 0.0: + raise ValueError("Setting a scale less than 0 on a regularizer: %g" % scale) + if scale == 0.0: + logging.info("Scale of 0 disables regularizer.") + return lambda _: None + + def sn(weights, name=None): + """Applies spectral norm regularization to weights.""" + with ops.name_scope(scope, "SpectralNormRegularizer", [weights]) as name: + scale_t = ops.convert_to_tensor( + scale, dtype=weights.dtype.base_dtype, name="scale" + ) + return math_ops.multiply( + scale_t, + compute_spectral_norm( + weights, power_iteration_rounds=power_iteration_rounds + ), + name=name, + ) + + return sn def _default_name_filter(name): - """A filter function to identify common names of weight variables. - - Args: - name: The variable name. - - Returns: - Whether `name` is a standard name for a weight/kernel variables used in the - Keras, tf.layers, tf.contrib.layers or tf.contrib.slim libraries. - """ - match = re.match(r'(.*\/)?(depthwise_|pointwise_)?(weights|kernel)$', name) - return match is not None - - -def spectral_normalization_custom_getter(name_filter=_default_name_filter, - power_iteration_rounds=1): - """Custom getter that performs Spectral Normalization on a weight tensor. - - Specifically it divides the weight tensor by its largest singular value. This - is intended to stabilize GAN training, by making the discriminator satisfy a - local 1-Lipschitz constraint. - - Based on [Spectral Normalization for Generative Adversarial Networks][sn-gan]. - - [sn-gan]: https://openreview.net/forum?id=B1QRgziT- - - To reproduce an SN-GAN, apply this custom_getter to every weight tensor of - your discriminator. The last dimension of the weight tensor must be the number - of output channels. - - Apply this to layers by supplying this as the `custom_getter` of a - `tf.compat.v1.variable_scope`. For example: - - with tf.compat.v1.variable_scope('discriminator', - custom_getter=spectral_norm_getter()): - net = discriminator_fn(net) - - IMPORTANT: Keras does not respect the custom_getter supplied by the - VariableScope, so Keras users should use `keras_spectral_normalization` - instead of (or in addition to) this approach. - - It is important to carefully select to which weights you want to apply - Spectral Normalization. In general you want to normalize the kernels of - convolution and dense layers, but you do not want to normalize biases. You - also want to avoid normalizing batch normalization (and similar) variables, - but in general such layers play poorly with Spectral Normalization, since the - gamma can cancel out the normalization in other layers. By default we supply a - filter that matches the kernel variable names of the dense and convolution - layers of the tf.layers, tf.contrib.layers, tf.keras and tf.contrib.slim - libraries. If you are using anything else you'll need a custom `name_filter`. - - This custom getter internally creates a variable used to compute the spectral - norm by power iteration. It will update every time the variable is accessed, - which means the normalized discriminator weights may change slightly whilst - training the generator. Whilst unusual, this matches how the paper's authors - implement it, and in general additional rounds of power iteration can't hurt. - - Args: - name_filter: Optionally, a method that takes a Variable name as input and - returns whether this Variable should be normalized. - power_iteration_rounds: The number of iterations of the power method to - perform per step. A higher number yields a better approximation of the - true spectral norm. - - Returns: - A custom getter function that applies Spectral Normalization to all - Variables whose names match `name_filter`. - - Raises: - ValueError: If name_filter is not callable. - """ - if not callable(name_filter): - raise ValueError('name_filter must be callable') - - def _internal_getter(getter, name, *args, **kwargs): - """A custom getter function that applies Spectral Normalization. + """A filter function to identify common names of weight variables. Args: - getter: The true getter to call. - name: Name of new/existing variable, in the same format as - tf.get_variable. - *args: Other positional arguments, in the same format as tf.get_variable. - **kwargs: Keyword arguments, in the same format as tf.get_variable. + name: The variable name. Returns: - The return value of `getter(name, *args, **kwargs)`, spectrally - normalized. + Whether `name` is a standard name for a weight/kernel variables used in the + Keras, tf.layers, tf.contrib.layers or tf.contrib.slim libraries. + """ + match = re.match(r"(.*\/)?(depthwise_|pointwise_)?(weights|kernel)$", name) + return match is not None + + +def spectral_normalization_custom_getter( + name_filter=_default_name_filter, power_iteration_rounds=1 +): + """Custom getter that performs Spectral Normalization on a weight tensor. + + Specifically it divides the weight tensor by its largest singular value. This + is intended to stabilize GAN training, by making the discriminator satisfy a + local 1-Lipschitz constraint. + + Based on [Spectral Normalization for Generative Adversarial Networks][sn-gan]. + + [sn-gan]: https://openreview.net/forum?id=B1QRgziT- + + To reproduce an SN-GAN, apply this custom_getter to every weight tensor of + your discriminator. The last dimension of the weight tensor must be the number + of output channels. + + Apply this to layers by supplying this as the `custom_getter` of a + `tf.compat.v1.variable_scope`. For example: + + with tf.compat.v1.variable_scope('discriminator', + custom_getter=spectral_norm_getter()): + net = discriminator_fn(net) + + IMPORTANT: Keras does not respect the custom_getter supplied by the + VariableScope, so Keras users should use `keras_spectral_normalization` + instead of (or in addition to) this approach. + + It is important to carefully select to which weights you want to apply + Spectral Normalization. In general you want to normalize the kernels of + convolution and dense layers, but you do not want to normalize biases. You + also want to avoid normalizing batch normalization (and similar) variables, + but in general such layers play poorly with Spectral Normalization, since the + gamma can cancel out the normalization in other layers. By default we supply a + filter that matches the kernel variable names of the dense and convolution + layers of the tf.layers, tf.contrib.layers, tf.keras and tf.contrib.slim + libraries. If you are using anything else you'll need a custom `name_filter`. + + This custom getter internally creates a variable used to compute the spectral + norm by power iteration. It will update every time the variable is accessed, + which means the normalized discriminator weights may change slightly whilst + training the generator. Whilst unusual, this matches how the paper's authors + implement it, and in general additional rounds of power iteration can't hurt. + + Args: + name_filter: Optionally, a method that takes a Variable name as input and + returns whether this Variable should be normalized. + power_iteration_rounds: The number of iterations of the power method to + perform per step. A higher number yields a better approximation of the + true spectral norm. + + Returns: + A custom getter function that applies Spectral Normalization to all + Variables whose names match `name_filter`. Raises: - ValueError: If used incorrectly, or if `dtype` is not supported. + ValueError: If name_filter is not callable. """ - if not name_filter(name): - return getter(name, *args, **kwargs) + if not callable(name_filter): + raise ValueError("name_filter must be callable") - if name.endswith(_PERSISTED_U_VARIABLE_SUFFIX): - raise ValueError( - 'Cannot apply Spectral Normalization to internal variables created ' - 'for Spectral Normalization. Tried to normalized variable [%s]' % - name) + def _internal_getter(getter, name, *args, **kwargs): + """A custom getter function that applies Spectral Normalization. - if kwargs['dtype'] not in _OK_DTYPES_FOR_SPECTRAL_NORM: - raise ValueError('Disallowed data type {}'.format(kwargs['dtype'])) + Args: + getter: The true getter to call. + name: Name of new/existing variable, in the same format as + tf.get_variable. + *args: Other positional arguments, in the same format as tf.get_variable. + **kwargs: Keyword arguments, in the same format as tf.get_variable. - # This layer's weight Variable/PartitionedVariable. - w_tensor = getter(name, *args, **kwargs) + Returns: + The return value of `getter(name, *args, **kwargs)`, spectrally + normalized. - if len(w_tensor.get_shape()) < 2: - raise ValueError( - 'Spectral norm can only be applied to multi-dimensional tensors') + Raises: + ValueError: If used incorrectly, or if `dtype` is not supported. + """ + if not name_filter(name): + return getter(name, *args, **kwargs) - return spectral_normalize( - w_tensor, - power_iteration_rounds=power_iteration_rounds, - name=(name + '/spectral_normalize')) + if name.endswith(_PERSISTED_U_VARIABLE_SUFFIX): + raise ValueError( + "Cannot apply Spectral Normalization to internal variables created " + "for Spectral Normalization. Tried to normalized variable [%s]" % name + ) - return _internal_getter + if kwargs["dtype"] not in _OK_DTYPES_FOR_SPECTRAL_NORM: + raise ValueError("Disallowed data type {}".format(kwargs["dtype"])) + # This layer's weight Variable/PartitionedVariable. + w_tensor = getter(name, *args, **kwargs) -@contextlib.contextmanager -def keras_spectral_normalization(name_filter=_default_name_filter, - power_iteration_rounds=1): - """A context manager that enables Spectral Normalization for Keras. + if len(w_tensor.get_shape()) < 2: + raise ValueError( + "Spectral norm can only be applied to multi-dimensional tensors" + ) - Keras doesn't respect the `custom_getter` in the VariableScope, so this is a - bit of a hack to make things work. + return spectral_normalize( + w_tensor, + power_iteration_rounds=power_iteration_rounds, + name=(name + "/spectral_normalize"), + ) - Usage: - with keras_spectral_normalization(): - net = discriminator_fn(net) + return _internal_getter - Args: - name_filter: Optionally, a method that takes a Variable name as input and - returns whether this Variable should be normalized. - power_iteration_rounds: The number of iterations of the power method to - perform per step. A higher number yields a better approximation of the - true spectral norm. - Yields: - A context manager that wraps the standard Keras variable creation method - with the `spectral_normalization_custom_getter`. - """ - original_make_variable = keras_base_layer_utils.make_variable - sn_getter = spectral_normalization_custom_getter( - name_filter=name_filter, power_iteration_rounds=power_iteration_rounds) +@contextlib.contextmanager +def keras_spectral_normalization( + name_filter=_default_name_filter, power_iteration_rounds=1 +): + """A context manager that enables Spectral Normalization for Keras. + + Keras doesn't respect the `custom_getter` in the VariableScope, so this is a + bit of a hack to make things work. + + Usage: + with keras_spectral_normalization(): + net = discriminator_fn(net) + + Args: + name_filter: Optionally, a method that takes a Variable name as input and + returns whether this Variable should be normalized. + power_iteration_rounds: The number of iterations of the power method to + perform per step. A higher number yields a better approximation of the + true spectral norm. + + Yields: + A context manager that wraps the standard Keras variable creation method + with the `spectral_normalization_custom_getter`. + """ + original_make_variable = keras_base_layer_utils.make_variable + sn_getter = spectral_normalization_custom_getter( + name_filter=name_filter, power_iteration_rounds=power_iteration_rounds + ) - def make_variable_wrapper(name, *args, **kwargs): - return sn_getter(original_make_variable, name, *args, **kwargs) + def make_variable_wrapper(name, *args, **kwargs): + return sn_getter(original_make_variable, name, *args, **kwargs) - keras_base_layer_utils.make_variable = make_variable_wrapper + keras_base_layer_utils.make_variable = make_variable_wrapper - yield + yield - keras_base_layer_utils.make_variable = original_make_variable + keras_base_layer_utils.make_variable = original_make_variable diff --git a/bob/learn/tensorflow/image/__init__.py b/bob/learn/tensorflow/image/__init__.py index 9a8962844d659e6b1e13f6eeeb5e1d11dc698e8d..02ce0f96ec08647cdb3effcdc6b699f8a3260752 100644 --- a/bob/learn/tensorflow/image/__init__.py +++ b/bob/learn/tensorflow/image/__init__.py @@ -4,13 +4,13 @@ from .filter import gaussian_kernel, GaussianFilter def __appropriate__(*args): """Says object was actually declared here, an not on the import module. - Parameters: + Parameters: - *args: An iterable of objects to modify + *args: An iterable of objects to modify - Resolves `Sphinx referencing issues - <https://github.com/sphinx-doc/sphinx/issues/3048>` - """ + Resolves `Sphinx referencing issues + <https://github.com/sphinx-doc/sphinx/issues/3048>` + """ for obj in args: obj.__module__ = __name__ diff --git a/bob/learn/tensorflow/loss/BaseLoss.py b/bob/learn/tensorflow/loss/BaseLoss.py index 161f653ab290e4dfe505ad0b569f5cc2176dceed..8380ec17efee5fe8c705055accd1642b05fc3f9f 100644 --- a/bob/learn/tensorflow/loss/BaseLoss.py +++ b/bob/learn/tensorflow/loss/BaseLoss.py @@ -4,6 +4,7 @@ import logging import tensorflow as tf + logger = logging.getLogger(__name__) @@ -39,12 +40,9 @@ logger = logging.getLogger(__name__) # return cross_loss -def mean_cross_entropy_center_loss(logits, - prelogits, - labels, - n_classes, - alpha=0.9, - factor=0.01): +def mean_cross_entropy_center_loss( + logits, prelogits, labels, n_classes, alpha=0.9, factor=0.01 +): """ Implementation of the CrossEntropy + Center Loss from the paper "A Discriminative Feature Learning Approach for Deep Face Recognition"(http://ydwen.github.io/papers/WenECCV16.pdf) @@ -59,44 +57,49 @@ def mean_cross_entropy_center_loss(logits, """ # Cross entropy - with tf.compat.v1.variable_scope('cross_entropy_loss'): + with tf.compat.v1.variable_scope("cross_entropy_loss"): cross_loss = tf.reduce_mean( input_tensor=tf.nn.sparse_softmax_cross_entropy_with_logits( - logits=logits, labels=labels), - name="cross_entropy_loss") + logits=logits, labels=labels + ), + name="cross_entropy_loss", + ) tf.compat.v1.add_to_collection(tf.compat.v1.GraphKeys.LOSSES, cross_loss) - tf.compat.v1.summary.scalar('loss_cross_entropy', cross_loss) + tf.compat.v1.summary.scalar("loss_cross_entropy", cross_loss) # Appending center loss - with tf.compat.v1.variable_scope('center_loss'): + with tf.compat.v1.variable_scope("center_loss"): n_features = prelogits.get_shape()[1] centers = tf.compat.v1.get_variable( - 'centers', [n_classes, n_features], + "centers", + [n_classes, n_features], dtype=tf.float32, initializer=tf.compat.v1.constant_initializer(0), - trainable=False) + trainable=False, + ) # label = tf.reshape(labels, [-1]) centers_batch = tf.gather(centers, labels) diff = (1 - alpha) * (centers_batch - prelogits) centers = tf.compat.v1.scatter_sub(centers, labels, diff) center_loss = tf.reduce_mean(input_tensor=tf.square(prelogits - centers_batch)) - tf.compat.v1.add_to_collection(tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES, - center_loss * factor) - tf.compat.v1.summary.scalar('loss_center', center_loss) + tf.compat.v1.add_to_collection( + tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES, center_loss * factor + ) + tf.compat.v1.summary.scalar("loss_center", center_loss) # Adding the regularizers in the loss - with tf.compat.v1.variable_scope('total_loss'): + with tf.compat.v1.variable_scope("total_loss"): regularization_losses = tf.compat.v1.get_collection( - tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES) - total_loss = tf.add_n( - [cross_loss] + regularization_losses, name="total_loss") + tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES + ) + total_loss = tf.add_n([cross_loss] + regularization_losses, name="total_loss") tf.compat.v1.add_to_collection(tf.compat.v1.GraphKeys.LOSSES, total_loss) - tf.compat.v1.summary.scalar('loss_total', total_loss) + tf.compat.v1.summary.scalar("loss_total", total_loss) loss = dict() - loss['loss'] = total_loss - loss['centers'] = centers + loss["loss"] = total_loss + loss["centers"] = centers return loss diff --git a/bob/learn/tensorflow/loss/ContrastiveLoss.py b/bob/learn/tensorflow/loss/ContrastiveLoss.py index d484b025bb7658403ae106df15444729016595cc..4e1a22eb1f38f10752302ed2594b8313eb5e64c6 100644 --- a/bob/learn/tensorflow/loss/ContrastiveLoss.py +++ b/bob/learn/tensorflow/loss/ContrastiveLoss.py @@ -47,16 +47,24 @@ def contrastive_loss(left_embedding, right_embedding, labels, contrastive_margin with tf.compat.v1.name_scope("within_class"): one = tf.constant(1.0) within_class = tf.multiply(one - labels, tf.square(d)) # (1-Y)*(d^2) - within_class_loss = tf.reduce_mean(input_tensor=within_class, name="within_class") - tf.compat.v1.add_to_collection(tf.compat.v1.GraphKeys.LOSSES, within_class_loss) + within_class_loss = tf.reduce_mean( + input_tensor=within_class, name="within_class" + ) + tf.compat.v1.add_to_collection( + tf.compat.v1.GraphKeys.LOSSES, within_class_loss + ) with tf.compat.v1.name_scope("between_class"): max_part = tf.square(tf.maximum(contrastive_margin - d, 0)) between_class = tf.multiply( labels, max_part ) # (Y) * max((margin - d)^2, 0) - between_class_loss = tf.reduce_mean(input_tensor=between_class, name="between_class") - tf.compat.v1.add_to_collection(tf.compat.v1.GraphKeys.LOSSES, between_class_loss) + between_class_loss = tf.reduce_mean( + input_tensor=between_class, name="between_class" + ) + tf.compat.v1.add_to_collection( + tf.compat.v1.GraphKeys.LOSSES, between_class_loss + ) with tf.compat.v1.name_scope("total_loss"): loss = 0.5 * (within_class + between_class) diff --git a/bob/learn/tensorflow/loss/StyleLoss.py b/bob/learn/tensorflow/loss/StyleLoss.py index 93d632777a04192a7f4e9697482c53b96019114b..75054a5aced8c124b60cfcb889ef4d5499f58c56 100644 --- a/bob/learn/tensorflow/loss/StyleLoss.py +++ b/bob/learn/tensorflow/loss/StyleLoss.py @@ -5,6 +5,7 @@ import logging import tensorflow as tf import functools + logger = logging.getLogger(__name__) @@ -32,7 +33,7 @@ def content_loss(noises, content_features): """ content_losses = [] - for n,c in zip(noises, content_features): + for n, c in zip(noises, content_features): content_losses.append((2 * tf.nn.l2_loss(n - c) / c.size)) return functools.reduce(tf.add, content_losses) @@ -61,13 +62,12 @@ def linear_gram_style_loss(noises, gram_style_features): """ style_losses = [] - for n,s in zip(noises, gram_style_features): + for n, s in zip(noises, gram_style_features): style_losses.append((2 * tf.nn.l2_loss(n - s)) / s.size) return functools.reduce(tf.add, style_losses) - def denoising_loss(noise): """ Computes the denoising loss as in: @@ -81,16 +81,25 @@ def denoising_loss(noise): Input noise """ + def _tensor_size(tensor): from operator import mul + return functools.reduce(mul, (d.value for d in tensor.get_shape()), 1) shape = noise.get_shape().as_list() - noise_y_size = _tensor_size(noise[:,1:,:,:]) - noise_x_size = _tensor_size(noise[:,:,1:,:]) - denoise_loss = 2 * ( (tf.nn.l2_loss(noise[:,1:,:,:] - noise[:,:shape[1]-1,:,:]) / noise_y_size) + - (tf.nn.l2_loss(noise[:,:,1:,:] - noise[:,:,:shape[2]-1,:]) / noise_x_size)) + noise_y_size = _tensor_size(noise[:, 1:, :, :]) + noise_x_size = _tensor_size(noise[:, :, 1:, :]) + denoise_loss = 2 * ( + ( + tf.nn.l2_loss(noise[:, 1:, :, :] - noise[:, : shape[1] - 1, :, :]) + / noise_y_size + ) + + ( + tf.nn.l2_loss(noise[:, :, 1:, :] - noise[:, :, : shape[2] - 1, :]) + / noise_x_size + ) + ) return denoise_loss - diff --git a/bob/learn/tensorflow/loss/TripletLoss.py b/bob/learn/tensorflow/loss/TripletLoss.py index 14dc4e98e0e16db2f4d67d862df7bc89993dab39..fb4b469b821f7730296a13d4516e8b88c60191d9 100644 --- a/bob/learn/tensorflow/loss/TripletLoss.py +++ b/bob/learn/tensorflow/loss/TripletLoss.py @@ -3,16 +3,14 @@ # @author: Tiago de Freitas Pereira <tiago.pereira@idiap.ch> import logging + logger = logging.getLogger(__name__) import tensorflow as tf from bob.learn.tensorflow.utils import compute_euclidean_distance -def triplet_loss(anchor_embedding, - positive_embedding, - negative_embedding, - margin=5.0): +def triplet_loss(anchor_embedding, positive_embedding, negative_embedding, margin=5.0): """ Compute the triplet loss as in @@ -40,55 +38,70 @@ def triplet_loss(anchor_embedding, with tf.compat.v1.name_scope("triplet_loss"): # Normalize - anchor_embedding = tf.nn.l2_normalize( - anchor_embedding, 1, 1e-10, name="anchor") + anchor_embedding = tf.nn.l2_normalize(anchor_embedding, 1, 1e-10, name="anchor") positive_embedding = tf.nn.l2_normalize( - positive_embedding, 1, 1e-10, name="positive") + positive_embedding, 1, 1e-10, name="positive" + ) negative_embedding = tf.nn.l2_normalize( - negative_embedding, 1, 1e-10, name="negative") + negative_embedding, 1, 1e-10, name="negative" + ) d_positive = tf.reduce_sum( - input_tensor=tf.square(tf.subtract(anchor_embedding, positive_embedding)), axis=1) + input_tensor=tf.square(tf.subtract(anchor_embedding, positive_embedding)), + axis=1, + ) d_negative = tf.reduce_sum( - input_tensor=tf.square(tf.subtract(anchor_embedding, negative_embedding)), axis=1) + input_tensor=tf.square(tf.subtract(anchor_embedding, negative_embedding)), + axis=1, + ) basic_loss = tf.add(tf.subtract(d_positive, d_negative), margin) with tf.compat.v1.name_scope("TripletLoss"): # Between between_class_loss = tf.reduce_mean(input_tensor=d_negative) - tf.compat.v1.summary.scalar('loss_between_class', between_class_loss) - tf.compat.v1.add_to_collection(tf.compat.v1.GraphKeys.LOSSES, between_class_loss) + tf.compat.v1.summary.scalar("loss_between_class", between_class_loss) + tf.compat.v1.add_to_collection( + tf.compat.v1.GraphKeys.LOSSES, between_class_loss + ) # Within within_class_loss = tf.reduce_mean(input_tensor=d_positive) - tf.compat.v1.summary.scalar('loss_within_class', within_class_loss) - tf.compat.v1.add_to_collection(tf.compat.v1.GraphKeys.LOSSES, within_class_loss) + tf.compat.v1.summary.scalar("loss_within_class", within_class_loss) + tf.compat.v1.add_to_collection( + tf.compat.v1.GraphKeys.LOSSES, within_class_loss + ) # Total loss loss = tf.reduce_mean( - input_tensor=tf.maximum(basic_loss, 0.0), axis=0, name="total_loss") + input_tensor=tf.maximum(basic_loss, 0.0), axis=0, name="total_loss" + ) tf.compat.v1.add_to_collection(tf.compat.v1.GraphKeys.LOSSES, loss) - tf.compat.v1.summary.scalar('loss_triplet', loss) + tf.compat.v1.summary.scalar("loss_triplet", loss) return loss -def triplet_fisher_loss(anchor_embedding, positive_embedding, - negative_embedding): +def triplet_fisher_loss(anchor_embedding, positive_embedding, negative_embedding): with tf.compat.v1.name_scope("triplet_loss"): # Normalize - anchor_embedding = tf.nn.l2_normalize( - anchor_embedding, 1, 1e-10, name="anchor") + anchor_embedding = tf.nn.l2_normalize(anchor_embedding, 1, 1e-10, name="anchor") positive_embedding = tf.nn.l2_normalize( - positive_embedding, 1, 1e-10, name="positive") + positive_embedding, 1, 1e-10, name="positive" + ) negative_embedding = tf.nn.l2_normalize( - negative_embedding, 1, 1e-10, name="negative") + negative_embedding, 1, 1e-10, name="negative" + ) average_class = tf.reduce_mean(input_tensor=anchor_embedding, axis=0) - average_total = tf.compat.v1.div(tf.add(tf.reduce_mean(input_tensor=anchor_embedding, axis=0),\ - tf.reduce_mean(input_tensor=negative_embedding, axis=0)), 2) + average_total = tf.compat.v1.div( + tf.add( + tf.reduce_mean(input_tensor=anchor_embedding, axis=0), + tf.reduce_mean(input_tensor=negative_embedding, axis=0), + ), + 2, + ) length = anchor_embedding.get_shape().as_list()[0] dim = anchor_embedding.get_shape().as_list()[1] @@ -101,15 +114,11 @@ def triplet_fisher_loss(anchor_embedding, positive_embedding, positive = s[0] negative = s[1] - buffer_sw = tf.reshape( - tf.subtract(positive, average_class), shape=(dim, 1)) - buffer_sw = tf.matmul(buffer_sw, - tf.reshape(buffer_sw, shape=(1, dim))) + buffer_sw = tf.reshape(tf.subtract(positive, average_class), shape=(dim, 1)) + buffer_sw = tf.matmul(buffer_sw, tf.reshape(buffer_sw, shape=(1, dim))) - buffer_sb = tf.reshape( - tf.subtract(negative, average_total), shape=(dim, 1)) - buffer_sb = tf.matmul(buffer_sb, - tf.reshape(buffer_sb, shape=(1, dim))) + buffer_sb = tf.reshape(tf.subtract(negative, average_total), shape=(dim, 1)) + buffer_sb = tf.matmul(buffer_sb, tf.reshape(buffer_sb, shape=(1, dim))) if Sw is None: Sw = buffer_sw @@ -120,16 +129,17 @@ def triplet_fisher_loss(anchor_embedding, positive_embedding, # Sw = tf.trace(Sw) # Sb = tf.trace(Sb) - #loss = tf.trace(tf.div(Sb, Sw)) - loss = tf.linalg.trace(tf.compat.v1.div(Sw, Sb), name=tf.compat.v1.GraphKeys.LOSSES) + # loss = tf.trace(tf.div(Sb, Sw)) + loss = tf.linalg.trace( + tf.compat.v1.div(Sw, Sb), name=tf.compat.v1.GraphKeys.LOSSES + ) return loss, tf.linalg.trace(Sb), tf.linalg.trace(Sw) -def triplet_average_loss(anchor_embedding, - positive_embedding, - negative_embedding, - margin=5.0): +def triplet_average_loss( + anchor_embedding, positive_embedding, negative_embedding, margin=5.0 +): """ Compute the triplet loss as in @@ -157,24 +167,32 @@ def triplet_average_loss(anchor_embedding, with tf.compat.v1.name_scope("triplet_loss"): # Normalize - anchor_embedding = tf.nn.l2_normalize( - anchor_embedding, 1, 1e-10, name="anchor") + anchor_embedding = tf.nn.l2_normalize(anchor_embedding, 1, 1e-10, name="anchor") positive_embedding = tf.nn.l2_normalize( - positive_embedding, 1, 1e-10, name="positive") + positive_embedding, 1, 1e-10, name="positive" + ) negative_embedding = tf.nn.l2_normalize( - negative_embedding, 1, 1e-10, name="negative") + negative_embedding, 1, 1e-10, name="negative" + ) anchor_mean = tf.reduce_mean(input_tensor=anchor_embedding, axis=0) d_positive = tf.reduce_sum( - input_tensor=tf.square(tf.subtract(anchor_mean, positive_embedding)), axis=1) + input_tensor=tf.square(tf.subtract(anchor_mean, positive_embedding)), axis=1 + ) d_negative = tf.reduce_sum( - input_tensor=tf.square(tf.subtract(anchor_mean, negative_embedding)), axis=1) + input_tensor=tf.square(tf.subtract(anchor_mean, negative_embedding)), axis=1 + ) basic_loss = tf.add(tf.subtract(d_positive, d_negative), margin) loss = tf.reduce_mean( - input_tensor=tf.maximum(basic_loss, 0.0), axis=0, name=tf.compat.v1.GraphKeys.LOSSES) - - return loss, tf.reduce_mean(input_tensor=d_negative), tf.reduce_mean(input_tensor=d_positive) - - + input_tensor=tf.maximum(basic_loss, 0.0), + axis=0, + name=tf.compat.v1.GraphKeys.LOSSES, + ) + + return ( + loss, + tf.reduce_mean(input_tensor=d_negative), + tf.reduce_mean(input_tensor=d_positive), + ) diff --git a/bob/learn/tensorflow/loss/__init__.py b/bob/learn/tensorflow/loss/__init__.py index 6d93dbe48bcc959b19fac29088a8e81f43e3f875..215237f0145e141c39c5ae84e09f5ea6a0cc5031 100644 --- a/bob/learn/tensorflow/loss/__init__.py +++ b/bob/learn/tensorflow/loss/__init__.py @@ -14,13 +14,13 @@ from .utils import * def __appropriate__(*args): """Says object was actually declared here, an not on the import module. - Parameters: + Parameters: - *args: An iterable of objects to modify + *args: An iterable of objects to modify - Resolves `Sphinx referencing issues - <https://github.com/sphinx-doc/sphinx/issues/3048>` - """ + Resolves `Sphinx referencing issues + <https://github.com/sphinx-doc/sphinx/issues/3048>` + """ for obj in args: obj.__module__ = __name__ diff --git a/bob/learn/tensorflow/loss/center_loss.py b/bob/learn/tensorflow/loss/center_loss.py index 553f01e95926e28a9c44935ee56200eaa0a915d9..d419a20f6ddb999878d479c744eea30cfc854fc1 100644 --- a/bob/learn/tensorflow/loss/center_loss.py +++ b/bob/learn/tensorflow/loss/center_loss.py @@ -18,7 +18,7 @@ class CenterLoss: "centers", [n_classes, n_features], dtype=tf.float32, - initializer=tf.compat.v1.constant_initializer(0.), + initializer=tf.compat.v1.constant_initializer(0.0), trainable=False, ) @@ -26,8 +26,12 @@ class CenterLoss: with tf.compat.v1.name_scope(self.name): centers_batch = tf.gather(self.centers, sparse_labels) diff = (1 - self.alpha) * (centers_batch - prelogits) - self.centers_update_op = tf.compat.v1.scatter_sub(self.centers, sparse_labels, diff) - center_loss = tf.reduce_mean(input_tensor=tf.square(prelogits - centers_batch)) + self.centers_update_op = tf.compat.v1.scatter_sub( + self.centers, sparse_labels, diff + ) + center_loss = tf.reduce_mean( + input_tensor=tf.square(prelogits - centers_batch) + ) tf.compat.v1.summary.scalar("loss_center", center_loss) # Add histogram for all centers for i in range(self.n_classes): diff --git a/bob/learn/tensorflow/loss/mmd.py b/bob/learn/tensorflow/loss/mmd.py index 2a0efff5fcb68cbda78d8c5e8b1a6d5c4159bf0f..a48aaba224088d0af63a5c3b5555ed16cae826c7 100644 --- a/bob/learn/tensorflow/loss/mmd.py +++ b/bob/learn/tensorflow/loss/mmd.py @@ -2,8 +2,7 @@ import tensorflow as tf def compute_kernel(x, y): - """Gaussian kernel. - """ + """Gaussian kernel.""" x_size = tf.shape(input=x)[0] y_size = tf.shape(input=y)[0] dim = tf.shape(input=x)[1] @@ -14,7 +13,8 @@ def compute_kernel(x, y): tf.reshape(y, tf.stack([1, y_size, dim])), tf.stack([x_size, 1, 1]) ) return tf.exp( - -tf.reduce_mean(input_tensor=tf.square(tiled_x - tiled_y), axis=2) / tf.cast(dim, tf.float32) + -tf.reduce_mean(input_tensor=tf.square(tiled_x - tiled_y), axis=2) + / tf.cast(dim, tf.float32) ) diff --git a/bob/learn/tensorflow/loss/pairwise_confusion.py b/bob/learn/tensorflow/loss/pairwise_confusion.py index b4c319711733e0631cece2bf25a7e75dcbac687e..f5309bfeed2fc240f4f6733c588c7dca087a304c 100644 --- a/bob/learn/tensorflow/loss/pairwise_confusion.py +++ b/bob/learn/tensorflow/loss/pairwise_confusion.py @@ -1,11 +1,12 @@ import tensorflow as tf from ..utils import pdist_safe, upper_triangle + def total_pairwise_confusion(prelogits, name=None): """Total Pairwise Confusion Loss - [1]X. Tu et al., “Learning Generalizable and Identity-Discriminative - Representations for Face Anti-Spoofing,” arXiv preprint arXiv:1901.05602, 2019. + [1]X. Tu et al., “Learning Generalizable and Identity-Discriminative + Representations for Face Anti-Spoofing,” arXiv preprint arXiv:1901.05602, 2019. """ # compute L2 norm between all prelogits and sum them. with tf.compat.v1.name_scope(name, default_name="total_pairwise_confusion"): diff --git a/bob/learn/tensorflow/loss/utils.py b/bob/learn/tensorflow/loss/utils.py index aad477ed5747cfe102015e77742ce93220073cf4..013de65aac1136d903f6714ad0d2dfe5e7291f51 100644 --- a/bob/learn/tensorflow/loss/utils.py +++ b/bob/learn/tensorflow/loss/utils.py @@ -135,7 +135,7 @@ def balanced_sigmoid_cross_entropy_loss_weights(labels, dtype="float32"): >>> #weights = balanced_sigmoid_cross_entropy_loss_weights(labels, dtype=logits.dtype) >>> #loss = tf.losses.sigmoid_cross_entropy(logits=logits, labels=labels, weights=weights) """ - labels = tf.cast(labels, dtype='int32') + labels = tf.cast(labels, dtype="int32") batch_size = tf.cast(tf.shape(input=labels)[0], dtype=dtype) weights = tf.cast(tf.reduce_sum(input_tensor=labels), dtype=dtype) weights = tf.convert_to_tensor(value=[batch_size - weights, weights]) diff --git a/bob/learn/tensorflow/loss/vat.py b/bob/learn/tensorflow/loss/vat.py index d77ecf605bce000841734b2d5dae8e15da7febf4..e51c24f441a4ee7f5a24834ef7d48ae222ceb6b9 100644 --- a/bob/learn/tensorflow/loss/vat.py +++ b/bob/learn/tensorflow/loss/vat.py @@ -28,27 +28,44 @@ from functools import partial def get_normalized_vector(d): - d /= (1e-12 + tf.reduce_max(input_tensor=tf.abs(d), axis=list(range(1, len(d.get_shape()))), keepdims=True)) - d /= tf.sqrt(1e-6 + tf.reduce_sum(input_tensor=tf.pow(d, 2.0), axis=list(range(1, len(d.get_shape()))), keepdims=True)) + d /= 1e-12 + tf.reduce_max( + input_tensor=tf.abs(d), axis=list(range(1, len(d.get_shape()))), keepdims=True + ) + d /= tf.sqrt( + 1e-6 + + tf.reduce_sum( + input_tensor=tf.pow(d, 2.0), + axis=list(range(1, len(d.get_shape()))), + keepdims=True, + ) + ) return d def logsoftmax(x): xdev = x - tf.reduce_max(input_tensor=x, axis=1, keepdims=True) - lsm = xdev - tf.math.log(tf.reduce_sum(input_tensor=tf.exp(xdev), axis=1, keepdims=True)) + lsm = xdev - tf.math.log( + tf.reduce_sum(input_tensor=tf.exp(xdev), axis=1, keepdims=True) + ) return lsm def kl_divergence_with_logit(q_logit, p_logit): q = tf.nn.softmax(q_logit) - qlogq = tf.reduce_mean(input_tensor=tf.reduce_sum(input_tensor=q * logsoftmax(q_logit), axis=1)) - qlogp = tf.reduce_mean(input_tensor=tf.reduce_sum(input_tensor=q * logsoftmax(p_logit), axis=1)) + qlogq = tf.reduce_mean( + input_tensor=tf.reduce_sum(input_tensor=q * logsoftmax(q_logit), axis=1) + ) + qlogp = tf.reduce_mean( + input_tensor=tf.reduce_sum(input_tensor=q * logsoftmax(p_logit), axis=1) + ) return qlogq - qlogp def entropy_y_x(logit): p = tf.nn.softmax(logit) - return -tf.reduce_mean(input_tensor=tf.reduce_sum(input_tensor=p * logsoftmax(logit), axis=1)) + return -tf.reduce_mean( + input_tensor=tf.reduce_sum(input_tensor=p * logsoftmax(logit), axis=1) + ) class VATLoss: @@ -68,7 +85,9 @@ class VATLoss: small constant for finite difference """ - def __init__(self, epsilon=8.0, xi=1e-6, num_power_iterations=1, method='vatent', **kwargs): + def __init__( + self, epsilon=8.0, xi=1e-6, num_power_iterations=1, method="vatent", **kwargs + ): super(VATLoss, self).__init__(**kwargs) self.epsilon = epsilon self.xi = xi @@ -104,15 +123,17 @@ class VATLoss: If self.method is not ``vat`` or ``vatent``. """ if mode != tf.estimator.ModeKeys.TRAIN: - return 0. + return 0.0 architecture = partial(architecture, reuse=True) with tf.compat.v1.variable_scope(tf.compat.v1.get_variable_scope(), reuse=True): - vat_loss = self.virtual_adversarial_loss(features, logits, architecture, mode) + vat_loss = self.virtual_adversarial_loss( + features, logits, architecture, mode + ) tf.compat.v1.summary.scalar("loss_VAT", vat_loss) tf.compat.v1.add_to_collection(tf.compat.v1.GraphKeys.LOSSES, vat_loss) - if self.method == 'vat': + if self.method == "vat": loss = vat_loss - elif self.method == 'vatent': + elif self.method == "vatent": ent_loss = entropy_y_x(logits) tf.compat.v1.summary.scalar("loss_entropy", ent_loss) tf.compat.v1.add_to_collection(tf.compat.v1.GraphKeys.LOSSES, ent_loss) @@ -121,8 +142,12 @@ class VATLoss: raise ValueError return loss - def virtual_adversarial_loss(self, features, logits, architecture, mode, name="vat_loss_op"): - r_vadv = self.generate_virtual_adversarial_perturbation(features, logits, architecture, mode) + def virtual_adversarial_loss( + self, features, logits, architecture, mode, name="vat_loss_op" + ): + r_vadv = self.generate_virtual_adversarial_perturbation( + features, logits, architecture, mode + ) logit_p = tf.stop_gradient(logits) adversarial_input = features + r_vadv tf.compat.v1.summary.image("Adversarial_Image", adversarial_input) @@ -130,7 +155,9 @@ class VATLoss: loss = kl_divergence_with_logit(logit_p, logit_m) return tf.identity(loss, name=name) - def generate_virtual_adversarial_perturbation(self, features, logits, architecture, mode): + def generate_virtual_adversarial_perturbation( + self, features, logits, architecture, mode + ): d = tf.random.normal(shape=tf.shape(input=features)) for _ in range(self.num_power_iterations): diff --git a/bob/learn/tensorflow/models/alexnet.py b/bob/learn/tensorflow/models/alexnet.py index 202348034fdbbd299b1fb2f64c09e439e822d682..3428aa39635402f31b87b630c994603cd81bc8fd 100644 --- a/bob/learn/tensorflow/models/alexnet.py +++ b/bob/learn/tensorflow/models/alexnet.py @@ -9,13 +9,43 @@ def AlexNet_simplified(name="AlexNet", **kwargs): model = tf.keras.Sequential( [ tf.keras.Input(shape=(227, 227, 3)), - tf.keras.layers.Conv2D(filters=96, kernel_size=11, strides=4, name="C1", activation="relu"), + tf.keras.layers.Conv2D( + filters=96, kernel_size=11, strides=4, name="C1", activation="relu" + ), tf.keras.layers.MaxPool2D(pool_size=3, strides=2, name="P1"), - tf.keras.layers.Conv2D(filters=256, kernel_size=5, strides=1, name="C2", activation="relu", padding="same"), + tf.keras.layers.Conv2D( + filters=256, + kernel_size=5, + strides=1, + name="C2", + activation="relu", + padding="same", + ), tf.keras.layers.MaxPool2D(pool_size=3, strides=2, name="P2"), - tf.keras.layers.Conv2D(filters=384, kernel_size=3, strides=1, name="C3", activation="relu", padding="same"), - tf.keras.layers.Conv2D(filters=384, kernel_size=3, strides=1, name="C4", activation="relu", padding="same"), - tf.keras.layers.Conv2D(filters=256, kernel_size=3, strides=1, name="C5", activation="relu", padding="same"), + tf.keras.layers.Conv2D( + filters=384, + kernel_size=3, + strides=1, + name="C3", + activation="relu", + padding="same", + ), + tf.keras.layers.Conv2D( + filters=384, + kernel_size=3, + strides=1, + name="C4", + activation="relu", + padding="same", + ), + tf.keras.layers.Conv2D( + filters=256, + kernel_size=3, + strides=1, + name="C5", + activation="relu", + padding="same", + ), tf.keras.layers.MaxPool2D(pool_size=3, strides=2, name="P5"), tf.keras.layers.Flatten(name="FLATTEN"), tf.keras.layers.Dropout(rate=0.5, name="D6"), diff --git a/bob/learn/tensorflow/models/inception.py b/bob/learn/tensorflow/models/inception.py index 3e25a59fa4ae9c6bb9d823ea7d6c20c311f37ca4..a4666b1b331fb8f23532b0d43342483437926175 100644 --- a/bob/learn/tensorflow/models/inception.py +++ b/bob/learn/tensorflow/models/inception.py @@ -2,8 +2,7 @@ import tensorflow as tf class LRN(tf.keras.layers.Lambda): - """local response normalization with default parameters for GoogLeNet - """ + """local response normalization with default parameters for GoogLeNet""" def __init__(self, alpha=0.0001, beta=0.75, depth_radius=5, **kwargs): self.alpha = alpha @@ -21,8 +20,8 @@ class LRN(tf.keras.layers.Lambda): class InceptionModule(tf.keras.Model): """The inception module as it was introduced in: - C. Szegedy et al., “Going deeper with convolutions,” in Proceedings of the IEEE - Conference on Computer Vision and Pattern Recognition, 2015, pp. 1–9. + C. Szegedy et al., “Going deeper with convolutions,” in Proceedings of the IEEE + Conference on Computer Vision and Pattern Recognition, 2015, pp. 1–9. """ def __init__( @@ -112,7 +111,9 @@ def GoogLeNet(*, num_classes=1000, name="GoogLeNet", **kwargs): ), tf.keras.layers.MaxPool2D(3, 2, padding="same", name="pool1/3x3_s2"), LRN(name="pool1/norm1"), - tf.keras.layers.Conv2D(64, 1, padding="same", activation="relu", name="conv2/3x3_reduce"), + tf.keras.layers.Conv2D( + 64, 1, padding="same", activation="relu", name="conv2/3x3_reduce" + ), tf.keras.layers.Conv2D( 192, 3, padding="same", activation="relu", name="conv2/3x3" ), diff --git a/bob/learn/tensorflow/models/inception_resnet_v2.py b/bob/learn/tensorflow/models/inception_resnet_v2.py index e5711e27ff6e075b271e7f97d7239ea75ac4809b..8f15d0b2cb089c721856a9657f45bf56301187e7 100644 --- a/bob/learn/tensorflow/models/inception_resnet_v2.py +++ b/bob/learn/tensorflow/models/inception_resnet_v2.py @@ -98,6 +98,7 @@ class Conv2D_BN(tf.keras.Sequential): class ScaledResidual(tf.keras.Model): """A scaled residual connection layer""" + def __init__(self, scale, name="scaled_residual", **kwargs): super().__init__(name=name, **kwargs) self.scale = scale @@ -174,22 +175,14 @@ class InceptionResnetBlock(tf.keras.Model): elif block_type == "block17": branch_0 = [Conv2D_BN(192 // n, 1, name="branch0_conv1")] branch_1 = [Conv2D_BN(128 // n, 1, name="branch1_conv1")] - branch_1 += [ - Conv2D_BN(160 // n, (1, 7), name="branch1_conv2") - ] - branch_1 += [ - Conv2D_BN(192 // n, (7, 1), name="branch1_conv3") - ] + branch_1 += [Conv2D_BN(160 // n, (1, 7), name="branch1_conv2")] + branch_1 += [Conv2D_BN(192 // n, (7, 1), name="branch1_conv3")] branches = [branch_0, branch_1] elif block_type == "block8": branch_0 = [Conv2D_BN(192 // n, 1, name="branch0_conv1")] branch_1 = [Conv2D_BN(192 // n, 1, name="branch1_conv1")] - branch_1 += [ - Conv2D_BN(224 // n, (1, 3), name="branch1_conv2") - ] - branch_1 += [ - Conv2D_BN(256 // n, (3, 1), name="branch1_conv3") - ] + branch_1 += [Conv2D_BN(224 // n, (1, 3), name="branch1_conv2")] + branch_1 += [Conv2D_BN(256 // n, (3, 1), name="branch1_conv3")] branches = [branch_0, branch_1] else: raise ValueError( @@ -335,31 +328,21 @@ class ReductionB(tf.keras.Model): branch_1 = [ Conv2D_BN(n, 1, name="branch1_conv1"), - Conv2D_BN( - no, 3, strides=2, padding=padding, name="branch1_conv2" - ), + Conv2D_BN(no, 3, strides=2, padding=padding, name="branch1_conv2"), ] branch_2 = [ Conv2D_BN(p, 1, name="branch2_conv1"), - Conv2D_BN( - pq, 3, strides=2, padding=padding, name="branch2_conv2" - ), + Conv2D_BN(pq, 3, strides=2, padding=padding, name="branch2_conv2"), ] branch_3 = [ Conv2D_BN(k, 1, name="branch3_conv1"), Conv2D_BN(kl, 3, name="branch3_conv2"), - Conv2D_BN( - km, 3, strides=2, padding=padding, name="branch3_conv3" - ), + Conv2D_BN(km, 3, strides=2, padding=padding, name="branch3_conv3"), ] - branch_pool = [ - MaxPool2D( - 3, strides=2, padding=padding, name=f"branch4_pool1" - ) - ] + branch_pool = [MaxPool2D(3, strides=2, padding=padding, name=f"branch4_pool1")] self.branches = [branch_1, branch_2, branch_3, branch_pool] channel_axis = 1 if K.image_data_format() == "channels_first" else 3 self.concat = Concatenate(axis=channel_axis, name=f"{name}/mixed") @@ -383,17 +366,33 @@ class InceptionA(tf.keras.Model): super().__init__(name=name, **kwargs) self.pool_filters = pool_filters - self.branch1x1 = Conv2D_BN(96, kernel_size=1, padding="same", name="branch1_conv1") + self.branch1x1 = Conv2D_BN( + 96, kernel_size=1, padding="same", name="branch1_conv1" + ) - self.branch3x3dbl_1 = Conv2D_BN(64, kernel_size=1, padding="same", name="branch2_conv1") - self.branch3x3dbl_2 = Conv2D_BN(96, kernel_size=3, padding="same", name="branch2_conv2") - self.branch3x3dbl_3 = Conv2D_BN(96, kernel_size=3, padding="same", name="branch2_conv3") + self.branch3x3dbl_1 = Conv2D_BN( + 64, kernel_size=1, padding="same", name="branch2_conv1" + ) + self.branch3x3dbl_2 = Conv2D_BN( + 96, kernel_size=3, padding="same", name="branch2_conv2" + ) + self.branch3x3dbl_3 = Conv2D_BN( + 96, kernel_size=3, padding="same", name="branch2_conv3" + ) - self.branch5x5_1 = Conv2D_BN(48, kernel_size=1, padding="same", name="branch3_conv1") - self.branch5x5_2 = Conv2D_BN(64, kernel_size=5, padding="same", name="branch3_conv2") + self.branch5x5_1 = Conv2D_BN( + 48, kernel_size=1, padding="same", name="branch3_conv1" + ) + self.branch5x5_2 = Conv2D_BN( + 64, kernel_size=5, padding="same", name="branch3_conv2" + ) - self.branch_pool_1 = AvgPool2D(pool_size=3, strides=1, padding="same", name="branch4_pool1") - self.branch_pool_2 = Conv2D_BN(pool_filters, kernel_size=1, padding="same", name="branch4_conv1") + self.branch_pool_1 = AvgPool2D( + pool_size=3, strides=1, padding="same", name="branch4_pool1" + ) + self.branch_pool_2 = Conv2D_BN( + pool_filters, kernel_size=1, padding="same", name="branch4_conv1" + ) channel_axis = 1 if K.image_data_format() == "channels_first" else 3 self.concat = Concatenate(axis=channel_axis) @@ -495,7 +494,10 @@ def InceptionResNetV2( # 10x block35 (Inception-ResNet-A block): 35 x 35 x 320 for block_idx in range(1, 11): x = InceptionResnetBlock( - n_channels=320, scale=0.17, block_type="block35", block_idx=block_idx, + n_channels=320, + scale=0.17, + block_type="block35", + block_idx=block_idx, name=f"block35_{block_idx}", )(x) @@ -505,7 +507,10 @@ def InceptionResNetV2( # 20x block17 (Inception-ResNet-B block): 17 x 17 x 1088 for block_idx in range(1, 21): x = InceptionResnetBlock( - n_channels=1088, scale=0.1, block_type="block17", block_idx=block_idx, + n_channels=1088, + scale=0.1, + block_type="block17", + block_idx=block_idx, name=f"block17_{block_idx}", )(x) @@ -517,11 +522,18 @@ def InceptionResNetV2( # 10x block8 (Inception-ResNet-C block): 8 x 8 x 2080 for block_idx in range(1, 10): x = InceptionResnetBlock( - n_channels=2080, scale=0.2, block_type="block8", block_idx=block_idx, + n_channels=2080, + scale=0.2, + block_type="block8", + block_idx=block_idx, name=f"block8_{block_idx}", )(x) x = InceptionResnetBlock( - n_channels=2080, scale=1.0, activation=None, block_type="block8", block_idx=10, + n_channels=2080, + scale=1.0, + activation=None, + block_type="block8", + block_idx=10, name=f"block8_{block_idx+1}", )(x) diff --git a/bob/learn/tensorflow/models/mcae.py b/bob/learn/tensorflow/models/mcae.py index 07cbb529be878551cfd2efcf2e438f802c52557f..e4241c409f747464e9f0ab8408acfe591658107c 100644 --- a/bob/learn/tensorflow/models/mcae.py +++ b/bob/learn/tensorflow/models/mcae.py @@ -67,7 +67,7 @@ class ConvDecoder(tf.keras.Model): name="Decoder", **kwargs, ): - super().__init__(name=name, ** kwargs) + super().__init__(name=name, **kwargs) self.data_format = data_format l2_kw = get_l2_kw(weight_decay) layers = [] diff --git a/bob/learn/tensorflow/models/mlp.py b/bob/learn/tensorflow/models/mlp.py index 3804c4e3222b2b0616e59f243bf8498f4f8f151f..71076818853e976063e64be108a3f27e3bd1a3d8 100644 --- a/bob/learn/tensorflow/models/mlp.py +++ b/bob/learn/tensorflow/models/mlp.py @@ -22,7 +22,9 @@ class MLP(tf.keras.Model): for i, n in enumerate(hidden_layers, start=1): sequential_layers.extend( [ - tf.keras.layers.Dense(n, use_bias=False, name=f"dense_{i}", **dense_kw), + tf.keras.layers.Dense( + n, use_bias=False, name=f"dense_{i}", **dense_kw + ), tf.keras.layers.BatchNormalization(scale=False, name=f"bn_{i}"), tf.keras.layers.Activation("relu", name=f"relu_{i}"), ] @@ -77,7 +79,9 @@ class MLPDropout(tf.keras.Model): for i, n in enumerate(hidden_layers, start=1): sequential_layers.extend( [ - tf.keras.layers.Dense(n, use_bias=False, name=f"dense_{i}", **dense_kw), + tf.keras.layers.Dense( + n, use_bias=False, name=f"dense_{i}", **dense_kw + ), tf.keras.layers.Activation("relu", name=f"relu_{i}"), tf.keras.layers.Dropout(rate=drop_rate, name=f"drop_{i}"), ] diff --git a/bob/learn/tensorflow/models/msu_patch.py b/bob/learn/tensorflow/models/msu_patch.py index af1ad1da9a287d3cd55b2e616385a0145acc50d5..3cd63ff95facae61a62b83bc7945a93726cad615 100644 --- a/bob/learn/tensorflow/models/msu_patch.py +++ b/bob/learn/tensorflow/models/msu_patch.py @@ -13,24 +13,39 @@ def MSUPatch(name="MSUPatch", **kwargs): return tf.keras.Sequential( [ tf.keras.layers.Conv2D( - 50, (5, 5), padding="same", use_bias=False, name="Conv-1", input_shape=(96, 96, 3) + 50, + (5, 5), + padding="same", + use_bias=False, + name="Conv-1", + input_shape=(96, 96, 3), ), tf.keras.layers.BatchNormalization(scale=False, name="BN-1"), tf.keras.layers.Activation("relu", name="ReLU-1"), tf.keras.layers.MaxPool2D(padding="same", name="MaxPool-1"), - tf.keras.layers.Conv2D(100, (3, 3), padding="same", use_bias=False, name="Conv-2"), + tf.keras.layers.Conv2D( + 100, (3, 3), padding="same", use_bias=False, name="Conv-2" + ), tf.keras.layers.BatchNormalization(scale=False, name="BN-2"), tf.keras.layers.Activation("relu", name="ReLU-2"), tf.keras.layers.MaxPool2D(padding="same", name="MaxPool-2"), - tf.keras.layers.Conv2D(150, (3, 3), padding="same", use_bias=False, name="Conv-3"), + tf.keras.layers.Conv2D( + 150, (3, 3), padding="same", use_bias=False, name="Conv-3" + ), tf.keras.layers.BatchNormalization(scale=False, name="BN-3"), tf.keras.layers.Activation("relu", name="ReLU-3"), - tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding="same", name="MaxPool-3"), - tf.keras.layers.Conv2D(200, (3, 3), padding="same", use_bias=False, name="Conv-4"), + tf.keras.layers.MaxPool2D( + pool_size=3, strides=2, padding="same", name="MaxPool-3" + ), + tf.keras.layers.Conv2D( + 200, (3, 3), padding="same", use_bias=False, name="Conv-4" + ), tf.keras.layers.BatchNormalization(scale=False, name="BN-4"), tf.keras.layers.Activation("relu", name="ReLU-4"), tf.keras.layers.MaxPool2D(padding="same", name="MaxPool-4"), - tf.keras.layers.Conv2D(250, (3, 3), padding="same", use_bias=False, name="Conv-5"), + tf.keras.layers.Conv2D( + 250, (3, 3), padding="same", use_bias=False, name="Conv-5" + ), tf.keras.layers.BatchNormalization(scale=False, name="BN-5"), tf.keras.layers.Activation("relu", name="ReLU-5"), tf.keras.layers.MaxPool2D(padding="same", name="MaxPool-5"), diff --git a/bob/learn/tensorflow/script/__init__.py b/bob/learn/tensorflow/script/__init__.py index b156cdd2398fd5af2b88db8279fd84c85b767b36..435eb712f6b140c26c5402f125616b558e7e223d 100644 --- a/bob/learn/tensorflow/script/__init__.py +++ b/bob/learn/tensorflow/script/__init__.py @@ -1,2 +1,2 @@ # gets sphinx autodoc done right - don't remove it -__all__ = [_ for _ in dir() if not _.startswith('_')] +__all__ = [_ for _ in dir() if not _.startswith("_")] diff --git a/bob/learn/tensorflow/script/cache_dataset.py b/bob/learn/tensorflow/script/cache_dataset.py index 3fe31750cf6d8abaf47558d36472eead700d7efd..5ec99916e62c1df2feb337f14ef24a618008aa98 100644 --- a/bob/learn/tensorflow/script/cache_dataset.py +++ b/bob/learn/tensorflow/script/cache_dataset.py @@ -8,41 +8,50 @@ import logging import click import tensorflow as tf from bob.extension.scripts.click_helper import ( - verbosity_option, ConfigCommand, ResourceOption, log_parameters) + verbosity_option, + ConfigCommand, + ResourceOption, + log_parameters, +) from ..utils import is_argument_available logger = logging.getLogger(__name__) -@click.command( - entry_point_group='bob.learn.tensorflow.config', cls=ConfigCommand) +@click.command(entry_point_group="bob.learn.tensorflow.config", cls=ConfigCommand) @click.option( - '--input-fn', - '-i', + "--input-fn", + "-i", required=True, cls=ResourceOption, - entry_point_group='bob.learn.tensorflow.input_fn', - help='The ``input_fn`` that will return the features and labels. ' - 'You should call the dataset.cache(...) yourself in the input ' - 'function. If the ``input_fn`` accepts a ``cache_only`` argument, ' - 'it will be given as True.') + entry_point_group="bob.learn.tensorflow.input_fn", + help="The ``input_fn`` that will return the features and labels. " + "You should call the dataset.cache(...) yourself in the input " + "function. If the ``input_fn`` accepts a ``cache_only`` argument, " + "it will be given as True.", +) @click.option( - '--mode', + "--mode", cls=ResourceOption, default=tf.estimator.ModeKeys.TRAIN, show_default=True, - type=click.Choice((tf.estimator.ModeKeys.TRAIN, - tf.estimator.ModeKeys.EVAL, - tf.estimator.ModeKeys.PREDICT)), - help='mode value to be given to the input_fn.') + type=click.Choice( + ( + tf.estimator.ModeKeys.TRAIN, + tf.estimator.ModeKeys.EVAL, + tf.estimator.ModeKeys.PREDICT, + ) + ), + help="mode value to be given to the input_fn.", +) @verbosity_option(cls=ResourceOption) def cache_dataset(input_fn, mode, **kwargs): """Trains networks using Tensorflow estimators.""" log_parameters(logger) kwargs = {} - if is_argument_available('cache_only', input_fn): - kwargs['cache_only'] = True + if is_argument_available("cache_only", input_fn): + kwargs["cache_only"] = True logger.info("cache_only as True will be passed to input_fn.") # call the input function manually diff --git a/bob/learn/tensorflow/script/compute_statistics.py b/bob/learn/tensorflow/script/compute_statistics.py index b876c5e4ae5be9f3df6f4f18c34aaaf50ef2395e..3638dee11beb94a7a24f24536bfd2c170db2b97c 100644 --- a/bob/learn/tensorflow/script/compute_statistics.py +++ b/bob/learn/tensorflow/script/compute_statistics.py @@ -8,75 +8,80 @@ import logging import click import numpy as np from bob.extension.scripts.click_helper import ( - verbosity_option, ConfigCommand, ResourceOption, log_parameters) + verbosity_option, + ConfigCommand, + ResourceOption, + log_parameters, +) from bob.learn.tensorflow.dataset.bio import BioGenerator logger = logging.getLogger(__name__) @click.command( - entry_point_group='bob.learn.tensorflow.config', cls=ConfigCommand, + entry_point_group="bob.learn.tensorflow.config", + cls=ConfigCommand, epilog="""\b An example configuration could be:: # define the database: from bob.bio.base.test.dummy.database import database groups = ['dev'] biofiles = database.all_files(groups) -""" +""", ) @click.option( - '--database', - '-d', + "--database", + "-d", required=True, cls=ResourceOption, - entry_point_group='bob.bio.database', - help='A bio database. Its original_directory must point to the correct ' - 'path.') + entry_point_group="bob.bio.database", + help="A bio database. Its original_directory must point to the correct " "path.", +) @click.option( - '--biofiles', + "--biofiles", required=True, cls=ResourceOption, - help='The list of the bio files. You can only provide this through ' - 'config files.') + help="The list of the bio files. You can only provide this through " + "config files.", +) @click.option( - '--load-data', + "--load-data", cls=ResourceOption, - entry_point_group='bob.learn.tensorflow.load_data', - help='A callable with the signature of ' - '``data = load_data(database, biofile)``. ' - ':any:`bob.bio.base.read_original_data` is used by default.') + entry_point_group="bob.learn.tensorflow.load_data", + help="A callable with the signature of " + "``data = load_data(database, biofile)``. " + ":any:`bob.bio.base.read_original_data` is used by default.", +) @click.option( - '--multiple-samples', + "--multiple-samples", is_flag=True, cls=ResourceOption, - help='If provided, it means that the data provided by reader contains ' - 'multiple samples with same label and path.') + help="If provided, it means that the data provided by reader contains " + "multiple samples with same label and path.", +) @verbosity_option(cls=ResourceOption) -def compute_statistics(database, biofiles, load_data, multiple_samples, - **kwargs): +def compute_statistics(database, biofiles, load_data, multiple_samples, **kwargs): """Computes statistics on a BioGenerator. This script works with bob.bio.base databases. It will load all the samples and print their mean. """ - log_parameters(logger, ignore=('biofiles', )) + log_parameters(logger, ignore=("biofiles",)) logger.debug("len(biofiles): %d", len(biofiles)) assert len(biofiles), "biofiles are empty!" - logger.info('Calculating the mean for %d files', len(biofiles)) + logger.info("Calculating the mean for %d files", len(biofiles)) generator = BioGenerator( - database, - biofiles, - load_data=load_data, - multiple_samples=multiple_samples) + database, biofiles, load_data=load_data, multiple_samples=multiple_samples + ) for i, (data, _, _) in enumerate(generator()): if i == 0: - mean = np.cast['float'](data) + mean = np.cast["float"](data) else: mean += data mean = mean.reshape(mean.shape[0], -1) mean = np.mean(mean, axis=1) - click.echo(mean / (i + 1.)) + click.echo(mean / (i + 1.0)) diff --git a/bob/learn/tensorflow/script/eval.py b/bob/learn/tensorflow/script/eval.py index 0ff48972f05cf70aa80c849ac7f6e85f0c9508b1..003f72f3c6e8235108206f11d801a4bb10cc8dd5 100644 --- a/bob/learn/tensorflow/script/eval.py +++ b/bob/learn/tensorflow/script/eval.py @@ -15,14 +15,18 @@ from glob import glob from collections import defaultdict, OrderedDict from ..utils.eval import get_global_step from bob.extension.scripts.click_helper import ( - verbosity_option, ConfigCommand, ResourceOption, log_parameters) + verbosity_option, + ConfigCommand, + ResourceOption, + log_parameters, +) from bob.io.base import create_directories_safe logger = logging.getLogger(__name__) def copy_one_step(train_dir, global_step, save_dir, fail_on_error=False): - for path in glob('{}/model.ckpt-{}.*'.format(train_dir, global_step)): + for path in glob("{}/model.ckpt-{}.*".format(train_dir, global_step)): dst = os.path.join(save_dir, os.path.basename(path)) if os.path.isfile(dst): continue @@ -30,38 +34,41 @@ def copy_one_step(train_dir, global_step, save_dir, fail_on_error=False): shutil.copy(path, dst) logger.info("Copied `%s' over to `%s'", path, dst) except OSError: - logger.warning( - "Failed to copy `%s' over to `%s'", path, dst, - exc_info=True) + logger.warning("Failed to copy `%s' over to `%s'", path, dst, exc_info=True) if fail_on_error: raise -def save_n_best_models(train_dir, save_dir, evaluated_file, - keep_n_best_models, sort_by, exceptions=tuple()): +def save_n_best_models( + train_dir, save_dir, evaluated_file, keep_n_best_models, sort_by, exceptions=tuple() +): logger.debug( "save_n_best_models was called with %s, %s, %s, %s, %s, %s", - train_dir, save_dir, evaluated_file, keep_n_best_models, sort_by, - exceptions) + train_dir, + save_dir, + evaluated_file, + keep_n_best_models, + sort_by, + exceptions, + ) create_directories_safe(save_dir) evaluated = read_evaluated_file(evaluated_file) def _key(x): x = x[1][sort_by] - if 'loss' in sort_by: + if "loss" in sort_by: return x else: return -x - best_models = OrderedDict( - sorted(evaluated.items(), key=_key)[:keep_n_best_models]) + best_models = OrderedDict(sorted(evaluated.items(), key=_key)[:keep_n_best_models]) logger.info("Best models: %s", best_models) # delete the old saved models that are not in top N best anymore saved_models = defaultdict(list) - for path in glob('{}/model.ckpt-*'.format(save_dir)): - global_step = path.split('model.ckpt-')[1].split('.')[0] + for path in glob("{}/model.ckpt-*".format(save_dir)): + global_step = path.split("model.ckpt-")[1].split(".")[0] saved_models[global_step].append(path) for global_step, paths in saved_models.items(): @@ -77,37 +84,35 @@ def save_n_best_models(train_dir, save_dir, evaluated_file, # create a checkpoint file indicating to the best existing model: # 1. filter non-existing models first def _filter(x): - return len(glob('{}/model.ckpt-{}.*'.format(save_dir, x[0]))) > 0 + return len(glob("{}/model.ckpt-{}.*".format(save_dir, x[0]))) > 0 best_models = OrderedDict(filter(_filter, best_models.items())) # 2. create the checkpoint file - with open(os.path.join(save_dir, 'checkpoint'), 'wt') as f: + with open(os.path.join(save_dir, "checkpoint"), "wt") as f: if not len(best_models): return the_best_global_step = list(best_models)[0] - f.write('model_checkpoint_path: "model.ckpt-{}"\n'.format( - the_best_global_step)) + f.write('model_checkpoint_path: "model.ckpt-{}"\n'.format(the_best_global_step)) # reverse the models before saving since the last ones in checkpoints # are usually more important. This aligns with the bob tf trim script. for i, global_step in enumerate(reversed(best_models)): - f.write('all_model_checkpoint_paths: "model.ckpt-{}"\n'.format( - global_step)) + f.write('all_model_checkpoint_paths: "model.ckpt-{}"\n'.format(global_step)) def read_evaluated_file(path): evaluated = {} with open(path) as f: for line in f: - global_step, line = line.split(' ', 1) + global_step, line = line.split(" ", 1) temp = {} - for k_v in line.strip().split(', '): - k, v = k_v.split(' = ') + for k_v in line.strip().split(", "): + k, v = k_v.split(" = ") try: v = float(v) except ValueError: # not all values could be floats pass - if 'global_step' in k: + if "global_step" in k: v = int(v) temp[k] = v evaluated[global_step] = temp @@ -115,90 +120,105 @@ def read_evaluated_file(path): def append_evaluated_file(path, evaluations): - str_evaluations = ', '.join( - '%s = %s' % (k, v) for k, v in sorted(evaluations.items())) - with open(path, 'a') as f: - f.write('{} {}\n'.format(evaluations['global_step'], str_evaluations)) + str_evaluations = ", ".join( + "%s = %s" % (k, v) for k, v in sorted(evaluations.items()) + ) + with open(path, "a") as f: + f.write("{} {}\n".format(evaluations["global_step"], str_evaluations)) return str_evaluations -@click.command( - entry_point_group='bob.learn.tensorflow.config', cls=ConfigCommand) +@click.command(entry_point_group="bob.learn.tensorflow.config", cls=ConfigCommand) @click.option( - '--estimator', - '-e', + "--estimator", + "-e", required=True, cls=ResourceOption, - entry_point_group='bob.learn.tensorflow.estimator', - help='The estimator that will be evaluated.') + entry_point_group="bob.learn.tensorflow.estimator", + help="The estimator that will be evaluated.", +) @click.option( - '--eval-input-fn', - '-i', + "--eval-input-fn", + "-i", required=True, cls=ResourceOption, - entry_point_group='bob.learn.tensorflow.input_fn', - help='The ``input_fn`` that will be given to ' - ':any:`tf.estimator.Estimator.eval`.') + entry_point_group="bob.learn.tensorflow.input_fn", + help="The ``input_fn`` that will be given to " + ":any:`tf.estimator.Estimator.eval`.", +) @click.option( - '--hooks', + "--hooks", cls=ResourceOption, multiple=True, - entry_point_group='bob.learn.tensorflow.hook', - help='List of SessionRunHook subclass instances. Used for callbacks ' - 'inside the evaluation loop.') + entry_point_group="bob.learn.tensorflow.hook", + help="List of SessionRunHook subclass instances. Used for callbacks " + "inside the evaluation loop.", +) @click.option( - '--run-once', + "--run-once", cls=ResourceOption, default=False, show_default=True, is_flag=True, - help='If given, the model will be evaluated only once.') + help="If given, the model will be evaluated only once.", +) @click.option( - '--eval-interval-secs', + "--eval-interval-secs", cls=ResourceOption, type=click.INT, default=60, show_default=True, - help='The seconds to wait for the next evaluation.') -@click.option('--name', cls=ResourceOption, help='Name of the evaluation') + help="The seconds to wait for the next evaluation.", +) +@click.option("--name", cls=ResourceOption, help="Name of the evaluation") @click.option( - '--keep-n-best-models', - '-K', + "--keep-n-best-models", + "-K", type=click.INT, cls=ResourceOption, default=1, show_default=True, - help='If more than 0, will keep the best N models in the evaluation folder' + help="If more than 0, will keep the best N models in the evaluation folder", ) @click.option( - '--sort-by', + "--sort-by", cls=ResourceOption, default="loss", show_default=True, - help='The metric for sorting the N best models.') + help="The metric for sorting the N best models.", +) @click.option( - '--max-wait-intervals', + "--max-wait-intervals", cls=ResourceOption, type=click.INT, default=-1, show_default=True, - help='If given, the maximum number of intervals waiting for new training checkpoint.') + help="If given, the maximum number of intervals waiting for new training checkpoint.", +) @click.option( - '--force-re-run', - is_flag=True, - default=False, - help='A debugging flag. Do not use!') + "--force-re-run", is_flag=True, default=False, help="A debugging flag. Do not use!" +) @verbosity_option(cls=ResourceOption) -def eval(estimator, eval_input_fn, hooks, run_once, eval_interval_secs, name, - keep_n_best_models, sort_by, max_wait_intervals, force_re_run, - **kwargs): +def eval( + estimator, + eval_input_fn, + hooks, + run_once, + eval_interval_secs, + name, + keep_n_best_models, + sort_by, + max_wait_intervals, + force_re_run, + **kwargs +): """Evaluates networks using Tensorflow estimators.""" log_parameters(logger) - real_name = 'eval_' + name if name else 'eval' + real_name = "eval_" + name if name else "eval" eval_dir = os.path.join(estimator.model_dir, real_name) os.makedirs(eval_dir, exist_ok=True) - evaluated_file = os.path.join(eval_dir, 'evaluated') + evaluated_file = os.path.join(eval_dir, "evaluated") wait_interval_count = 0 evaluated_steps_count = 0 while True: @@ -233,8 +253,14 @@ def eval(estimator, eval_input_fn, hooks, run_once, eval_interval_secs, name, wait_interval_count = 0 # Save the best N models into the eval directory - save_n_best_models(estimator.model_dir, eval_dir, evaluated_file, - keep_n_best_models, sort_by, exceptions) + save_n_best_models( + estimator.model_dir, + eval_dir, + evaluated_file, + keep_n_best_models, + sort_by, + exceptions, + ) if (not ckpt) or (not ckpt.model_checkpoint_path): if max_wait_intervals > 0: @@ -249,8 +275,10 @@ def eval(estimator, eval_input_fn, hooks, run_once, eval_interval_secs, name, global_step = str(get_global_step(checkpoint_path)) except Exception: logger.warning( - 'Failed to find global_step for checkpoint_path {}, ' - 'skipping ...'.format(checkpoint_path), exc_info=True) + "Failed to find global_step for checkpoint_path {}, " + "skipping ...".format(checkpoint_path), + exc_info=True, + ) continue if global_step in evaluated_steps and not force_re_run: continue @@ -258,13 +286,17 @@ def eval(estimator, eval_input_fn, hooks, run_once, eval_interval_secs, name, # copy over the checkpoint before evaluating since it might # disappear after evaluation. try: - copy_one_step(estimator.model_dir, global_step, eval_dir, fail_on_error=True) + copy_one_step( + estimator.model_dir, global_step, eval_dir, fail_on_error=True + ) except Exception: # skip testing this checkpoint continue # evaluate based on the just copied checkpoint_path - checkpoint_path = checkpoint_path.replace(estimator.model_dir, eval_dir + os.sep) + checkpoint_path = checkpoint_path.replace( + estimator.model_dir, eval_dir + os.sep + ) checkpoint_path = os.path.abspath(checkpoint_path) logger.debug("Evaluating the model from %s", checkpoint_path) @@ -281,14 +313,18 @@ def eval(estimator, eval_input_fn, hooks, run_once, eval_interval_secs, name, logger.info("Something went wrong in evaluation.") raise - str_evaluations = append_evaluated_file(evaluated_file, - evaluations) + str_evaluations = append_evaluated_file(evaluated_file, evaluations) click.echo(str_evaluations) sys.stdout.flush() # Save the best N models into the eval directory - save_n_best_models(estimator.model_dir, eval_dir, evaluated_file, - keep_n_best_models, sort_by) + save_n_best_models( + estimator.model_dir, + eval_dir, + evaluated_file, + keep_n_best_models, + sort_by, + ) if run_once: break diff --git a/bob/learn/tensorflow/script/fit.py b/bob/learn/tensorflow/script/fit.py index f19776c5da551fa9dd611d0279eab99d0eee6469..177d2761bad0c483216db353ff47216a76594e8b 100644 --- a/bob/learn/tensorflow/script/fit.py +++ b/bob/learn/tensorflow/script/fit.py @@ -10,82 +10,101 @@ import logging import os import tensorflow as tf from bob.extension.scripts.click_helper import ( - verbosity_option, ConfigCommand, ResourceOption, log_parameters) + verbosity_option, + ConfigCommand, + ResourceOption, + log_parameters, +) logger = logging.getLogger(__name__) -@click.command( - entry_point_group='bob.learn.tensorflow.config', cls=ConfigCommand) +@click.command(entry_point_group="bob.learn.tensorflow.config", cls=ConfigCommand) @click.option( - '--model', - '-m', + "--model", + "-m", required=True, cls=ResourceOption, - entry_point_group='bob.learn.tensorflow.model', - help='The keras model that will be trained.') + entry_point_group="bob.learn.tensorflow.model", + help="The keras model that will be trained.", +) @click.option( - '--train-input-fn', - '-i', + "--train-input-fn", + "-i", required=True, cls=ResourceOption, - entry_point_group='bob.learn.tensorflow.input_fn', - help='A function that will return the training data as a tf.data.Dataset ' - 'or tf.data.Iterator. This will be given as `x` to ' - 'tf.keras.Model.fit.') + entry_point_group="bob.learn.tensorflow.input_fn", + help="A function that will return the training data as a tf.data.Dataset " + "or tf.data.Iterator. This will be given as `x` to " + "tf.keras.Model.fit.", +) @click.option( - '--epochs', - '-e', + "--epochs", + "-e", default=1, type=click.types.INT, cls=ResourceOption, - help='Number of epochs to train model. See ' - 'tf.keras.Model.fit.') + help="Number of epochs to train model. See " "tf.keras.Model.fit.", +) @click.option( - '--callbacks', + "--callbacks", cls=ResourceOption, multiple=True, - entry_point_group='bob.learn.tensorflow.callback', - help='List of tf.keras.callbacks. Used for callbacks ' - 'inside the training loop.') + entry_point_group="bob.learn.tensorflow.callback", + help="List of tf.keras.callbacks. Used for callbacks " "inside the training loop.", +) @click.option( - '--eval-input-fn', - '-i', + "--eval-input-fn", + "-i", cls=ResourceOption, - entry_point_group='bob.learn.tensorflow.input_fn', - help='A function that will return the validation data as a tf.data.Dataset' - ' or tf.data.Iterator. This will be given as `validation_data` to ' - 'tf.keras.Model.fit.') + entry_point_group="bob.learn.tensorflow.input_fn", + help="A function that will return the validation data as a tf.data.Dataset" + " or tf.data.Iterator. This will be given as `validation_data` to " + "tf.keras.Model.fit.", +) @click.option( - '--class-weight', - '-c', - cls=ResourceOption, - help='See tf.keras.Model.fit.') + "--class-weight", "-c", cls=ResourceOption, help="See tf.keras.Model.fit." +) @click.option( - '--initial-epoch', + "--initial-epoch", default=0, type=click.types.INT, cls=ResourceOption, - help='See tf.keras.Model.fit.') + help="See tf.keras.Model.fit.", +) @click.option( - '--steps-per-epoch', + "--steps-per-epoch", type=click.types.INT, cls=ResourceOption, - help='See tf.keras.Model.fit.') + help="See tf.keras.Model.fit.", +) @click.option( - '--validation-steps', + "--validation-steps", type=click.types.INT, cls=ResourceOption, - help='See tf.keras.Model.fit.') + help="See tf.keras.Model.fit.", +) @verbosity_option(cls=ResourceOption) -def fit(model, train_input_fn, epochs, verbose, callbacks, eval_input_fn, - class_weight, initial_epoch, steps_per_epoch, validation_steps, - **kwargs): +def fit( + model, + train_input_fn, + epochs, + verbose, + callbacks, + eval_input_fn, + class_weight, + initial_epoch, + steps_per_epoch, + validation_steps, + **kwargs +): """Trains networks using Keras models.""" log_parameters(logger) # Train - save_callback = [c for c in callbacks if isinstance(c, tf.keras.callbacks.ModelCheckpoint)] + save_callback = [ + c for c in callbacks if isinstance(c, tf.keras.callbacks.ModelCheckpoint) + ] model_dir = None if save_callback: model_dir = save_callback[0].filepath @@ -103,5 +122,5 @@ def fit(model, train_input_fn, epochs, verbose, callbacks, eval_input_fn, ) click.echo(history.history) if model_dir is not None: - with open(os.path.join(model_dir, 'keras_fit_history.json'), 'w') as f: + with open(os.path.join(model_dir, "keras_fit_history.json"), "w") as f: json.dump(history.history, f) diff --git a/bob/learn/tensorflow/script/keras.py b/bob/learn/tensorflow/script/keras.py index 9f6ee9c24b09f174ceba366b98e4d17cf0ebbdd0..794f4d672836c5b0143e02435073ea92108127e2 100644 --- a/bob/learn/tensorflow/script/keras.py +++ b/bob/learn/tensorflow/script/keras.py @@ -7,7 +7,7 @@ from bob.extension.scripts.click_helper import AliasedGroup from .utils import eager_execution_option -@with_plugins(pkg_resources.iter_entry_points('bob.learn.tensorflow.keras_cli')) +@with_plugins(pkg_resources.iter_entry_points("bob.learn.tensorflow.keras_cli")) @click.group(cls=AliasedGroup) @eager_execution_option() def keras(): diff --git a/bob/learn/tensorflow/script/predict_bio.py b/bob/learn/tensorflow/script/predict_bio.py index 54c5bb2c12cacac2c87416f4ded0df7a4140cfdf..31a4d05c94445031c3918536d4b5ecc7c2a04eda 100644 --- a/bob/learn/tensorflow/script/predict_bio.py +++ b/bob/learn/tensorflow/script/predict_bio.py @@ -435,7 +435,9 @@ def generic_predict( if last_key == key: continue else: - save_predictions(output_dir, last_key, pred_buffer, video_container, remove_nan) + save_predictions( + output_dir, last_key, pred_buffer, video_container, remove_nan + ) # delete saved data so we don't run out of RAM del pred_buffer[last_key] # start saving this new key diff --git a/bob/learn/tensorflow/script/tf.py b/bob/learn/tensorflow/script/tf.py index f460df4333a100a9809b89d84012dec9ba0e3eb3..859efe27ba390cdf4a8ebb8fe2f1ac099deddf67 100644 --- a/bob/learn/tensorflow/script/tf.py +++ b/bob/learn/tensorflow/script/tf.py @@ -7,7 +7,7 @@ from bob.extension.scripts.click_helper import AliasedGroup from .utils import eager_execution_option -@with_plugins(pkg_resources.iter_entry_points('bob.learn.tensorflow.cli')) +@with_plugins(pkg_resources.iter_entry_points("bob.learn.tensorflow.cli")) @click.group(cls=AliasedGroup) @eager_execution_option() def tf(): diff --git a/bob/learn/tensorflow/script/train.py b/bob/learn/tensorflow/script/train.py index d33d5767abb82a3c012805df4117cce227df3bf5..6f7a0c0d5036aca5a4835b5a98e9f6684deceda4 100644 --- a/bob/learn/tensorflow/script/train.py +++ b/bob/learn/tensorflow/script/train.py @@ -7,49 +7,57 @@ from __future__ import print_function import logging import click from bob.extension.scripts.click_helper import ( - verbosity_option, ConfigCommand, ResourceOption, log_parameters) + verbosity_option, + ConfigCommand, + ResourceOption, + log_parameters, +) logger = logging.getLogger(__name__) -@click.command( - entry_point_group='bob.learn.tensorflow.config', cls=ConfigCommand) +@click.command(entry_point_group="bob.learn.tensorflow.config", cls=ConfigCommand) @click.option( - '--estimator', - '-e', + "--estimator", + "-e", required=True, cls=ResourceOption, - entry_point_group='bob.learn.tensorflow.estimator', - help='The estimator that will be trained.') + entry_point_group="bob.learn.tensorflow.estimator", + help="The estimator that will be trained.", +) @click.option( - '--train-input-fn', - '-i', + "--train-input-fn", + "-i", required=True, cls=ResourceOption, - entry_point_group='bob.learn.tensorflow.input_fn', - help='The ``input_fn`` that will be given to ' - ':any:`tf.estimator.Estimator.train`.') + entry_point_group="bob.learn.tensorflow.input_fn", + help="The ``input_fn`` that will be given to " + ":any:`tf.estimator.Estimator.train`.", +) @click.option( - '--hooks', + "--hooks", cls=ResourceOption, multiple=True, - entry_point_group='bob.learn.tensorflow.hook', - help='List of SessionRunHook subclass instances. Used for callbacks ' - 'inside the training loop.') + entry_point_group="bob.learn.tensorflow.hook", + help="List of SessionRunHook subclass instances. Used for callbacks " + "inside the training loop.", +) @click.option( - '--steps', - '-s', + "--steps", + "-s", cls=ResourceOption, type=click.types.INT, - help='Number of steps for which to train model. See ' - ':any:`tf.estimator.Estimator.train`.') + help="Number of steps for which to train model. See " + ":any:`tf.estimator.Estimator.train`.", +) @click.option( - '--max-steps', - '-m', + "--max-steps", + "-m", cls=ResourceOption, type=click.types.INT, - help='Number of total steps for which to train model. See ' - ':any:`tf.estimator.Estimator.train`.') + help="Number of total steps for which to train model. See " + ":any:`tf.estimator.Estimator.train`.", +) @verbosity_option(cls=ResourceOption) def train(estimator, train_input_fn, hooks, steps, max_steps, **kwargs): """Trains networks using Tensorflow estimators.""" @@ -58,4 +66,5 @@ def train(estimator, train_input_fn, hooks, steps, max_steps, **kwargs): # Train logger.info("Training a model in %s", estimator.model_dir) estimator.train( - input_fn=train_input_fn, hooks=hooks, steps=steps, max_steps=max_steps) + input_fn=train_input_fn, hooks=hooks, steps=steps, max_steps=max_steps + ) diff --git a/bob/learn/tensorflow/script/train_and_evaluate.py b/bob/learn/tensorflow/script/train_and_evaluate.py index 3ba4ebde0c874750ef97ea6ec8a942c1a3d5bc85..1a6cdc840ec0c33b1b33a408a16099c385a15cb0 100644 --- a/bob/learn/tensorflow/script/train_and_evaluate.py +++ b/bob/learn/tensorflow/script/train_and_evaluate.py @@ -8,47 +8,52 @@ import tensorflow as tf from bob.learn.tensorflow.utils.hooks import EarlyStopException import logging import click -from bob.extension.scripts.click_helper import (verbosity_option, - ConfigCommand, ResourceOption) +from bob.extension.scripts.click_helper import ( + verbosity_option, + ConfigCommand, + ResourceOption, +) logger = logging.getLogger(__name__) -@click.command( - entry_point_group='bob.learn.tensorflow.config', cls=ConfigCommand) +@click.command(entry_point_group="bob.learn.tensorflow.config", cls=ConfigCommand) @click.option( - '--estimator', - '-e', + "--estimator", + "-e", required=True, cls=ResourceOption, - entry_point_group='bob.learn.tensorflow.estimator', - help='The estimator that will be trained and evaluated.') + entry_point_group="bob.learn.tensorflow.estimator", + help="The estimator that will be trained and evaluated.", +) @click.option( - '--train-spec', - '-it', + "--train-spec", + "-it", required=True, cls=ResourceOption, - entry_point_group='bob.learn.tensorflow.trainspec', - help='See :any:`tf.estimator.Estimator.train_and_evaluate`.') + entry_point_group="bob.learn.tensorflow.trainspec", + help="See :any:`tf.estimator.Estimator.train_and_evaluate`.", +) @click.option( - '--eval-spec', - '-ie', + "--eval-spec", + "-ie", required=True, cls=ResourceOption, - entry_point_group='bob.learn.tensorflow.evalspec', - help='See :any:`tf.estimator.Estimator.train_and_evaluate`.') + entry_point_group="bob.learn.tensorflow.evalspec", + help="See :any:`tf.estimator.Estimator.train_and_evaluate`.", +) @click.option( - '--exit-ok-exceptions', + "--exit-ok-exceptions", cls=ResourceOption, multiple=True, - default=(EarlyStopException, ), + default=(EarlyStopException,), show_default=True, - entry_point_group='bob.learn.tensorflow.exception', - help='A list of exceptions to exit properly if they occur. If nothing is ' - 'provided, the EarlyStopException is handled by default.') + entry_point_group="bob.learn.tensorflow.exception", + help="A list of exceptions to exit properly if they occur. If nothing is " + "provided, the EarlyStopException is handled by default.", +) @verbosity_option(cls=ResourceOption) -def train_and_evaluate(estimator, train_spec, eval_spec, exit_ok_exceptions, - **kwargs): +def train_and_evaluate(estimator, train_spec, eval_spec, exit_ok_exceptions, **kwargs): """Trains and evaluates a network using Tensorflow estimators. This script calls the estimator.train_and_evaluate function. Please see: @@ -57,11 +62,11 @@ def train_and_evaluate(estimator, train_spec, eval_spec, exit_ok_exceptions, https://www.tensorflow.org/api_docs/python/tf/estimator/EvalSpec for more details. """ - logger.debug('estimator: %s', estimator) - logger.debug('train_spec: %s', train_spec) - logger.debug('eval_spec: %s', eval_spec) - logger.debug('exit_ok_exceptions: %s', exit_ok_exceptions) - logger.debug('kwargs: %s', kwargs) + logger.debug("estimator: %s", estimator) + logger.debug("train_spec: %s", train_spec) + logger.debug("eval_spec: %s", eval_spec) + logger.debug("exit_ok_exceptions: %s", exit_ok_exceptions) + logger.debug("kwargs: %s", kwargs) # Train and evaluate try: diff --git a/bob/learn/tensorflow/script/trim.py b/bob/learn/tensorflow/script/trim.py index ed29b4c28847499a283d9f9908ba427f8604d8b6..7f58ce47ab11ebac534ed294c8fb0f1058773467 100644 --- a/bob/learn/tensorflow/script/trim.py +++ b/bob/learn/tensorflow/script/trim.py @@ -20,59 +20,65 @@ def delete_extra_checkpoints(directory, keep_last_n, dry_run): except Exception: return if (not ckpt) or (not ckpt.model_checkpoint_path): - logger.debug('Could not find a checkpoint in %s', directory) + logger.debug("Could not find a checkpoint in %s", directory) return for checkpoint_path in ckpt.all_model_checkpoint_paths[:-keep_last_n]: if checkpoint_path == ckpt.model_checkpoint_path: continue if dry_run: - click.echo('Would delete {}.*'.format(checkpoint_path)) + click.echo("Would delete {}.*".format(checkpoint_path)) else: - logger.info('Deleting %s.*', checkpoint_path) - for path in glob.glob('{}.*'.format(checkpoint_path)): + logger.info("Deleting %s.*", checkpoint_path) + for path in glob.glob("{}.*".format(checkpoint_path)): os.remove(path) def _existing(x): - return glob.glob('{}.*'.format(x)) + return glob.glob("{}.*".format(x)) # update the checkpoint file all_paths = filter(_existing, ckpt.all_model_checkpoint_paths) all_paths = list(map(os.path.basename, all_paths)) model_checkpoint_path = os.path.basename(ckpt.model_checkpoint_path) tf.compat.v1.train.update_checkpoint_state( - directory, model_checkpoint_path, all_paths) + directory, model_checkpoint_path, all_paths + ) -@click.command(epilog='''\b +@click.command( + epilog="""\b Examples: $ bob tf trim -vv ~/my_models/model_dir $ bob tf trim -vv ~/my_models/model_dir1 ~/my_models/model_dir2 $ bob tf trim -vvr ~/my_models $ bob tf trim -vvrn ~/my_models $ bob tf trim -vvrK 2 ~/my_models -''') +""" +) @click.argument( - 'root_dirs', + "root_dirs", nargs=-1, type=click.Path(exists=True, file_okay=False, dir_okay=True), ) @click.option( - '--keep-last-n-models', - '-K', + "--keep-last-n-models", + "-K", type=click.INT, default=1, show_default=True, - help='The number of recent checkpoints to keep.') + help="The number of recent checkpoints to keep.", +) @click.option( - '--recurse', - '-r', + "--recurse", + "-r", is_flag=True, - help='If given, it will delete checkpoints recursively.') + help="If given, it will delete checkpoints recursively.", +) @click.option( - '--dry-run', - '-n', + "--dry-run", + "-n", is_flag=True, - help='If given, will only print what will be deleted.') + help="If given, will only print what will be deleted.", +) @verbosity_option() def trim(root_dirs, keep_last_n_models, recurse, dry_run, **kwargs): """Deletes extra tensorflow checkpoints.""" @@ -81,7 +87,6 @@ def trim(root_dirs, keep_last_n_models, recurse, dry_run, **kwargs): for root_dir in root_dirs: if recurse: for directory, _, _ in os.walk(root_dir): - delete_extra_checkpoints(directory, keep_last_n_models, - dry_run) + delete_extra_checkpoints(directory, keep_last_n_models, dry_run) else: delete_extra_checkpoints(root_dir, keep_last_n_models, dry_run) diff --git a/bob/learn/tensorflow/script/utils.py b/bob/learn/tensorflow/script/utils.py index ca5ba4ef219b093b4706729a37bbc55b71bba508..db6efdb4594f8f4e62227dd94c8a4df63906feb8 100644 --- a/bob/learn/tensorflow/script/utils.py +++ b/bob/learn/tensorflow/script/utils.py @@ -9,19 +9,29 @@ def eager_execution_option(**kwargs): callable A decorator to be used for adding this option to click commands """ + def custom_eager_execution_option(func): def callback(ctx, param, value): if not value or ctx.resilient_parsing: return import tensorflow as tf + tf.compat.v1.enable_eager_execution() if not tf.executing_eagerly(): raise click.ClickException( - "Could not enable tensorflow eager execution mode!") + "Could not enable tensorflow eager execution mode!" + ) else: click.echo("Executing tensorflow operations eagerly!") return click.option( - '-e', '--eager', is_flag=True, callback=callback, - expose_value=False, is_eager=True, **kwargs)(func) + "-e", + "--eager", + is_flag=True, + callback=callback, + expose_value=False, + is_eager=True, + **kwargs + )(func) + return custom_eager_execution_option diff --git a/bob/learn/tensorflow/test/data/input_biogenerator_config.py b/bob/learn/tensorflow/test/data/input_biogenerator_config.py index 2aca7ccd4cda182bc882f1ca512b65b837a18810..2d03e4b19efe5ede90a5e9bffd4018f036ad3941 100644 --- a/bob/learn/tensorflow/test/data/input_biogenerator_config.py +++ b/bob/learn/tensorflow/test/data/input_biogenerator_config.py @@ -10,9 +10,9 @@ def input_fn(mode): from bob.bio.base.test.dummy.database import database as db if mode == tf.estimator.ModeKeys.TRAIN: - groups = 'world' + groups = "world" elif mode == tf.estimator.ModeKeys.EVAL: - groups = 'dev' + groups = "dev" files = db.objects(groups=groups) @@ -33,7 +33,8 @@ def input_fn(mode): generator = BioGenerator(db, files, load_data, biofile_to_label) dataset = tf.data.Dataset.from_generator( - generator, generator.output_types, generator.output_shapes) + generator, generator.output_types, generator.output_shapes + ) def transform(image, label, key): # convert to channels last @@ -51,7 +52,7 @@ def input_fn(mode): dataset = dataset.batch(batch_size) data, label, key = tf.compat.v1.data.make_one_shot_iterator(dataset).get_next() - return {'data': data, 'key': key}, label + return {"data": data, "key": key}, label def train_input_fn(): diff --git a/bob/learn/tensorflow/test/data/input_predict_bio_config.py b/bob/learn/tensorflow/test/data/input_predict_bio_config.py index d355768b5a84064730878326f879d8f02dd77f2d..cf4d862ac27da74c2fb001c6d8d2ca4111022e3a 100644 --- a/bob/learn/tensorflow/test/data/input_predict_bio_config.py +++ b/bob/learn/tensorflow/test/data/input_predict_bio_config.py @@ -1,17 +1,20 @@ import tensorflow as tf from bob.bio.base.test.dummy.database import database -biofiles = database.all_files(['dev']) + +biofiles = database.all_files(["dev"]) def bio_predict_input_fn(generator, output_types, output_shapes): def input_fn(): - dataset = tf.data.Dataset.from_generator( - generator, output_types, output_shapes) + dataset = tf.data.Dataset.from_generator(generator, output_types, output_shapes) # apply all kinds of transformations here, process the data # even further if you want. dataset = dataset.prefetch(1) - dataset = dataset.batch(10**3) - images, labels, keys = tf.compat.v1.data.make_one_shot_iterator(dataset).get_next() + dataset = dataset.batch(10 ** 3) + images, labels, keys = tf.compat.v1.data.make_one_shot_iterator( + dataset + ).get_next() + + return {"data": images, "key": keys}, labels - return {'data': images, 'key': keys}, labels return input_fn diff --git a/bob/learn/tensorflow/test/data/input_tfrecords_config.py b/bob/learn/tensorflow/test/data/input_tfrecords_config.py index 2c5ec8f02e8f547bc31de2e062f2240ab044d0e5..f6df06c6c9af58d9e376c2d320724c079fe4d064 100644 --- a/bob/learn/tensorflow/test/data/input_tfrecords_config.py +++ b/bob/learn/tensorflow/test/data/input_tfrecords_config.py @@ -1,8 +1,10 @@ import tensorflow as tf -from bob.learn.tensorflow.dataset.tfrecords import shuffle_data_and_labels, \ - batch_data_and_labels +from bob.learn.tensorflow.dataset.tfrecords import ( + shuffle_data_and_labels, + batch_data_and_labels, +) -tfrecord_filenames = ['%(tfrecord_filenames)s'] +tfrecord_filenames = ["%(tfrecord_filenames)s"] data_shape = (1, 112, 92) # size of atnt images data_type = tf.uint8 batch_size = 2 @@ -10,13 +12,15 @@ epochs = 2 def train_input_fn(): - return shuffle_data_and_labels(tfrecord_filenames, data_shape, data_type, - batch_size, epochs=epochs) + return shuffle_data_and_labels( + tfrecord_filenames, data_shape, data_type, batch_size, epochs=epochs + ) def eval_input_fn(): - return batch_data_and_labels(tfrecord_filenames, data_shape, data_type, - batch_size, epochs=1) + return batch_data_and_labels( + tfrecord_filenames, data_shape, data_type, batch_size, epochs=1 + ) # config for train_and_evaluate diff --git a/bob/learn/tensorflow/test/data/mnist_estimator.py b/bob/learn/tensorflow/test/data/mnist_estimator.py index 9957657681b9c46594647c68302eb592ceadadb4..378b52628da8c6f1a1bbbdf29ff8eaacd508216b 100644 --- a/bob/learn/tensorflow/test/data/mnist_estimator.py +++ b/bob/learn/tensorflow/test/data/mnist_estimator.py @@ -1,3 +1,6 @@ import tensorflow as tf -data = tf.feature_column.numeric_column('data', shape=[784]) -estimator = tf.estimator.LinearClassifier(feature_columns=[data], n_classes=10, loss_reduction=tf.keras.losses.Reduction.SUM) + +data = tf.feature_column.numeric_column("data", shape=[784]) +estimator = tf.estimator.LinearClassifier( + feature_columns=[data], n_classes=10, loss_reduction=tf.keras.losses.Reduction.SUM +) diff --git a/bob/learn/tensorflow/test/data/mnist_input_fn.py b/bob/learn/tensorflow/test/data/mnist_input_fn.py index 0274f5075d5dede7e5d7a98538e05613089733bc..df1d81e7ff3c8195206ddc26d9ca05d0329d6ab3 100644 --- a/bob/learn/tensorflow/test/data/mnist_input_fn.py +++ b/bob/learn/tensorflow/test/data/mnist_input_fn.py @@ -6,23 +6,21 @@ database = Database() def input_fn(mode): if mode == tf.estimator.ModeKeys.TRAIN: - groups = 'train' + groups = "train" num_epochs = None shuffle = True else: - groups = 'test' + groups = "test" num_epochs = 1 shuffle = True data, labels = database.data(groups=groups) return tf.compat.v1.estimator.inputs.numpy_input_fn( - x={ - "data": data.astype('float32'), - 'key': labels.astype('float32') - }, - y=labels.astype('int32'), + x={"data": data.astype("float32"), "key": labels.astype("float32")}, + y=labels.astype("int32"), batch_size=128, num_epochs=num_epochs, - shuffle=shuffle) + shuffle=shuffle, + ) train_input_fn = input_fn(tf.estimator.ModeKeys.TRAIN) diff --git a/bob/learn/tensorflow/test/test_db_to_tfrecords.py b/bob/learn/tensorflow/test/test_db_to_tfrecords.py index 790fceea2980a7ed5d260f3d6763d6bdca051fb4..be07bde07a73b776f3bd2bff3d3fa8beb42c79cb 100644 --- a/bob/learn/tensorflow/test/test_db_to_tfrecords.py +++ b/bob/learn/tensorflow/test/test_db_to_tfrecords.py @@ -6,8 +6,7 @@ import tensorflow as tf import numpy as np from click.testing import CliRunner from bob.io.base import create_directories_safe -from bob.learn.tensorflow.script.db_to_tfrecords import ( - datasets_to_tfrecords) +from bob.learn.tensorflow.script.db_to_tfrecords import datasets_to_tfrecords from bob.learn.tensorflow.utils import load_mnist, create_mnist_tfrecord from bob.extension.scripts.click_helper import assert_click_runner_result from bob.extension.config import load @@ -16,7 +15,8 @@ from bob.learn.tensorflow.dataset.tfrecords import dataset_from_tfrecord regenerate_reference = False dummy_config = pkg_resources.resource_filename( - 'bob.learn.tensorflow', 'test/data/db_to_tfrecords_config.py') + "bob.learn.tensorflow", "test/data/db_to_tfrecords_config.py" +) def compare_datasets(ds1, ds2, sess=None): @@ -44,19 +44,17 @@ def compare_datasets(ds1, ds2, sess=None): return True - - def test_datasets_to_tfrecords(): runner = CliRunner() with runner.isolated_filesystem(): - output_path = './test' - args = (dummy_config, '--output', output_path) - result = runner.invoke( - datasets_to_tfrecords, args=args, standalone_mode=False) + output_path = "./test" + args = (dummy_config, "--output", output_path) + result = runner.invoke(datasets_to_tfrecords, args=args, standalone_mode=False) assert_click_runner_result(result) # read back the tfrecod with tf.compat.v1.Session() as sess: dataset2 = dataset_from_tfrecord(output_path) dataset1 = load( - [dummy_config], attribute_name='dataset', entry_point_group='bob') + [dummy_config], attribute_name="dataset", entry_point_group="bob" + ) assert compare_datasets(dataset1, dataset2, sess) diff --git a/bob/learn/tensorflow/test/test_loss.py b/bob/learn/tensorflow/test/test_loss.py index f54d49e700129856d2517516d5758f6f45b27a70..cccdf600229bcac48a7526827174e465450e15c3 100644 --- a/bob/learn/tensorflow/test/test_loss.py +++ b/bob/learn/tensorflow/test/test_loss.py @@ -4,73 +4,175 @@ import tensorflow as tf import numpy -from bob.learn.tensorflow.loss import balanced_softmax_cross_entropy_loss_weights,\ - balanced_sigmoid_cross_entropy_loss_weights +from bob.learn.tensorflow.loss import ( + balanced_softmax_cross_entropy_loss_weights, + balanced_sigmoid_cross_entropy_loss_weights, +) def test_balanced_softmax_cross_entropy_loss_weights(): - labels = numpy.array([[1, 0, 0], - [1, 0, 0], - [0, 0, 1], - [0, 1, 0], - [0, 0, 1], - [1, 0, 0], - [1, 0, 0], - [0, 0, 1], - [1, 0, 0], - [1, 0, 0], - [1, 0, 0], - [1, 0, 0], - [1, 0, 0], - [1, 0, 0], - [0, 1, 0], - [1, 0, 0], - [0, 1, 0], - [1, 0, 0], - [0, 0, 1], - [0, 0, 1], - [1, 0, 0], - [0, 0, 1], - [1, 0, 0], - [1, 0, 0], - [0, 1, 0], - [1, 0, 0], - [1, 0, 0], - [1, 0, 0], - [0, 1, 0], - [1, 0, 0], - [0, 0, 1], - [1, 0, 0]], dtype="int32") + labels = numpy.array( + [ + [1, 0, 0], + [1, 0, 0], + [0, 0, 1], + [0, 1, 0], + [0, 0, 1], + [1, 0, 0], + [1, 0, 0], + [0, 0, 1], + [1, 0, 0], + [1, 0, 0], + [1, 0, 0], + [1, 0, 0], + [1, 0, 0], + [1, 0, 0], + [0, 1, 0], + [1, 0, 0], + [0, 1, 0], + [1, 0, 0], + [0, 0, 1], + [0, 0, 1], + [1, 0, 0], + [0, 0, 1], + [1, 0, 0], + [1, 0, 0], + [0, 1, 0], + [1, 0, 0], + [1, 0, 0], + [1, 0, 0], + [0, 1, 0], + [1, 0, 0], + [0, 0, 1], + [1, 0, 0], + ], + dtype="int32", + ) with tf.compat.v1.Session() as session: weights = session.run(balanced_softmax_cross_entropy_loss_weights(labels)) - - expected_weights = numpy.array([0.53333336, 0.53333336, 1.5238096 , 2.1333334,\ - 1.5238096 , 0.53333336, 0.53333336, 1.5238096,\ - 0.53333336, 0.53333336, 0.53333336, 0.53333336,\ - 0.53333336, 0.53333336, 2.1333334 , 0.53333336,\ - 2.1333334 , 0.53333336, 1.5238096 , 1.5238096 ,\ - 0.53333336, 1.5238096 , 0.53333336, 0.53333336,\ - 2.1333334 , 0.53333336, 0.53333336, 0.53333336,\ - 2.1333334 , 0.53333336, 1.5238096 , 0.53333336],\ - dtype="float32") + + expected_weights = numpy.array( + [ + 0.53333336, + 0.53333336, + 1.5238096, + 2.1333334, + 1.5238096, + 0.53333336, + 0.53333336, + 1.5238096, + 0.53333336, + 0.53333336, + 0.53333336, + 0.53333336, + 0.53333336, + 0.53333336, + 2.1333334, + 0.53333336, + 2.1333334, + 0.53333336, + 1.5238096, + 1.5238096, + 0.53333336, + 1.5238096, + 0.53333336, + 0.53333336, + 2.1333334, + 0.53333336, + 0.53333336, + 0.53333336, + 2.1333334, + 0.53333336, + 1.5238096, + 0.53333336, + ], + dtype="float32", + ) assert numpy.allclose(weights, expected_weights) def test_balanced_sigmoid_cross_entropy_loss_weights(): - labels = numpy.array([1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, - 1, 1, 0, 1, 1, 1, 0, 1, 0, 1], dtype="int32") - + labels = numpy.array( + [ + 1, + 1, + 0, + 0, + 0, + 1, + 1, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 1, + 0, + 1, + 0, + 0, + 1, + 0, + 1, + 1, + 0, + 1, + 1, + 1, + 0, + 1, + 0, + 1, + ], + dtype="int32", + ) + with tf.compat.v1.Session() as session: - weights = session.run(balanced_sigmoid_cross_entropy_loss_weights(labels, dtype='float32')) - - expected_weights = numpy.array([0.8, 0.8, 1.3333334, 1.3333334, 1.3333334, 0.8, - 0.8, 1.3333334, 0.8, 0.8, 0.8, 0.8, - 0.8, 0.8, 1.3333334, 0.8, 1.3333334, 0.8, - 1.3333334, 1.3333334, 0.8, 1.3333334, 0.8, 0.8, - 1.3333334, 0.8, 0.8, 0.8, 1.3333334, 0.8, - 1.3333334, 0.8], dtype="float32") + weights = session.run( + balanced_sigmoid_cross_entropy_loss_weights(labels, dtype="float32") + ) - assert numpy.allclose(weights, expected_weights) + expected_weights = numpy.array( + [ + 0.8, + 0.8, + 1.3333334, + 1.3333334, + 1.3333334, + 0.8, + 0.8, + 1.3333334, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 1.3333334, + 0.8, + 1.3333334, + 0.8, + 1.3333334, + 1.3333334, + 0.8, + 1.3333334, + 0.8, + 0.8, + 1.3333334, + 0.8, + 0.8, + 0.8, + 1.3333334, + 0.8, + 1.3333334, + 0.8, + ], + dtype="float32", + ) + assert numpy.allclose(weights, expected_weights) diff --git a/bob/learn/tensorflow/test/test_utils.py b/bob/learn/tensorflow/test/test_utils.py index 6edfe4519f74b02b160b343f52943e0d2e608670..29d9f276ae27a6aa7135487f46c562cc10cbf284 100644 --- a/bob/learn/tensorflow/test/test_utils.py +++ b/bob/learn/tensorflow/test/test_utils.py @@ -3,10 +3,13 @@ # @author: Tiago de Freitas Pereira <tiago.pereira@idiap.ch> import numpy -from bob.learn.tensorflow.utils import compute_embedding_accuracy, \ - compute_embedding_accuracy_tensors +from bob.learn.tensorflow.utils import ( + compute_embedding_accuracy, + compute_embedding_accuracy_tensors, +) import tensorflow as tf + """ Some unit tests for the datashuffler """ @@ -17,18 +20,16 @@ def test_embedding_accuracy(): numpy.random.seed(10) samples_per_class = 5 - class_a = numpy.random.normal( - loc=0, scale=0.1, size=(samples_per_class, 2)) + class_a = numpy.random.normal(loc=0, scale=0.1, size=(samples_per_class, 2)) labels_a = numpy.zeros(samples_per_class) - class_b = numpy.random.normal( - loc=10, scale=0.1, size=(samples_per_class, 2)) + class_b = numpy.random.normal(loc=10, scale=0.1, size=(samples_per_class, 2)) labels_b = numpy.ones(samples_per_class) data = numpy.vstack((class_a, class_b)) labels = numpy.concatenate((labels_a, labels_b)) - assert compute_embedding_accuracy(data, labels) == 1. + assert compute_embedding_accuracy(data, labels) == 1.0 # Adding noise noise = numpy.random.normal(loc=0, scale=0.1, size=(samples_per_class, 2)) @@ -37,7 +38,7 @@ def test_embedding_accuracy(): data = numpy.vstack((data, noise)) labels = numpy.concatenate((labels, noise_labels)) - assert compute_embedding_accuracy(data, labels) == 10 / 15. + assert compute_embedding_accuracy(data, labels) == 10 / 15.0 def test_embedding_accuracy_tensors(): @@ -45,12 +46,10 @@ def test_embedding_accuracy_tensors(): numpy.random.seed(10) samples_per_class = 5 - class_a = numpy.random.normal( - loc=0, scale=0.1, size=(samples_per_class, 2)) + class_a = numpy.random.normal(loc=0, scale=0.1, size=(samples_per_class, 2)) labels_a = numpy.zeros(samples_per_class) - class_b = numpy.random.normal( - loc=10, scale=0.1, size=(samples_per_class, 2)) + class_b = numpy.random.normal(loc=10, scale=0.1, size=(samples_per_class, 2)) labels_b = numpy.ones(samples_per_class) data = numpy.vstack((class_a, class_b)) @@ -60,4 +59,4 @@ def test_embedding_accuracy_tensors(): labels = tf.convert_to_tensor(value=labels.astype("int64")) accuracy = compute_embedding_accuracy_tensors(data, labels) - assert accuracy == 1. + assert accuracy == 1.0 diff --git a/bob/learn/tensorflow/utils/graph.py b/bob/learn/tensorflow/utils/graph.py index 3f551dd7d230d12856773ec451812eda1ded52a2..0a5fd5904180659bd92bf6e71ca5a7ddefd213f9 100644 --- a/bob/learn/tensorflow/utils/graph.py +++ b/bob/learn/tensorflow/utils/graph.py @@ -2,12 +2,7 @@ import tensorflow as tf def call_on_frozen_graph( - graph_def_path, - input, - return_elements, - input_name, - name=None, - **kwargs + graph_def_path, input, return_elements, input_name, name=None, **kwargs ): """Loads a frozen graph def file (.pb) and replaces its input with the given input and return the requested output tensors. diff --git a/bob/learn/tensorflow/utils/sequences.py b/bob/learn/tensorflow/utils/sequences.py index c2de433a50c65e13d3cb7353bd1f036a8055ddc6..99cdf5aa47982570d0862a08743e8998970ab0e6 100644 --- a/bob/learn/tensorflow/utils/sequences.py +++ b/bob/learn/tensorflow/utils/sequences.py @@ -1,6 +1,7 @@ from __future__ import division import numpy from keras.utils import Sequence + # documentation imports from bob.dap.base.database import PadDatabase, PadFile from bob.bio.base.preprocessor import Preprocessor @@ -22,8 +23,15 @@ class PadSequence(Sequence): The preprocessor to be used to load and process the data. """ - def __init__(self, files, labels, batch_size, preprocessor, - original_directory, original_extension): + def __init__( + self, + files, + labels, + batch_size, + preprocessor, + original_directory, + original_extension, + ): super(PadSequence, self).__init__() self.files = files self.labels = labels @@ -43,8 +51,8 @@ class PadSequence(Sequence): return int(numpy.ceil(len(self.files) / self.batch_size)) def __getitem__(self, idx): - files = self.files[idx * self.batch_size:(idx + 1) * self.batch_size] - labels = self.labels[idx * self.batch_size:(idx + 1) * self.batch_size] + files = self.files[idx * self.batch_size : (idx + 1) * self.batch_size] + labels = self.labels[idx * self.batch_size : (idx + 1) * self.batch_size] return self.load_batch(files, labels) def load_batch(self, files, labels): @@ -65,7 +73,8 @@ class PadSequence(Sequence): data, targets = [], [] for file_object, target in zip(files, labels): loaded_data = self.preprocessor.read_original_data( - file_object, self.original_directory, self.original_extension) + file_object, self.original_directory, self.original_extension + ) preprocessed_data = self.preprocessor(loaded_data) data.append(preprocessed_data) targets.append(target) @@ -104,12 +113,14 @@ def get_pad_files_labels(database, groups): return files, labels -def get_pad_sequences(database, - preprocessor, - batch_size, - groups=('world', 'dev', 'eval'), - shuffle=False, - limit=None): +def get_pad_sequences( + database, + preprocessor, + batch_size, + groups=("world", "dev", "eval"), + shuffle=False, + limit=None, +): """Returns a list of :any:`Sequence` objects for the database. Parameters @@ -138,7 +149,13 @@ def get_pad_sequences(database, if limit is not None: files, labels = files[:limit], labels[:limit] seqs.append( - PadSequence(files, labels, batch_size, preprocessor, - database.original_directory, - database.original_extension)) + PadSequence( + files, + labels, + batch_size, + preprocessor, + database.original_directory, + database.original_extension, + ) + ) return seqs diff --git a/bob/learn/tensorflow/utils/session.py b/bob/learn/tensorflow/utils/session.py index 67f4856d3047c713d98b4af8ee38c853e914fc7e..34022df16d0f2f834c0fd268d58eb255bba94a71 100644 --- a/bob/learn/tensorflow/utils/session.py +++ b/bob/learn/tensorflow/utils/session.py @@ -18,7 +18,8 @@ class Session(object): config = tf.compat.v1.ConfigProto( log_device_placement=False, allow_soft_placement=True, - gpu_options=tf.compat.v1.GPUOptions(per_process_gpu_memory_fraction=0.5)) + gpu_options=tf.compat.v1.GPUOptions(per_process_gpu_memory_fraction=0.5), + ) config.gpu_options.allow_growth = True self.session = tf.compat.v1.Session() if debug: diff --git a/bob/learn/tensorflow/utils/singleton.py b/bob/learn/tensorflow/utils/singleton.py index d91f3c3ab5cfc339936066e3642c4f728edd20da..4e7769d02134cee901c4d49ab29764bdf291fa0b 100644 --- a/bob/learn/tensorflow/utils/singleton.py +++ b/bob/learn/tensorflow/utils/singleton.py @@ -45,8 +45,7 @@ class Singleton(object): return self._instance def __call__(self): - raise TypeError( - 'Singletons must be accessed through the `instance()` method.') + raise TypeError("Singletons must be accessed through the `instance()` method.") def __instancecheck__(self, inst): return isinstance(inst, self._decorated) diff --git a/bob/learn/tensorflow/utils/util.py b/bob/learn/tensorflow/utils/util.py index 4710d026c8a87cd1014cb180dcef813473f0e116..a7cd02d39cfd87adc1625d418e8488072e56660a 100644 --- a/bob/learn/tensorflow/utils/util.py +++ b/bob/learn/tensorflow/utils/util.py @@ -51,7 +51,8 @@ def cdist(A, B, metric="sqeuclidean"): M1, M2 = tf.shape(input=A)[0], tf.shape(input=B)[0] # code from https://stackoverflow.com/a/43839605/1286165 p1 = tf.matmul( - tf.expand_dims(tf.reduce_sum(input_tensor=tf.square(A), axis=1), 1), tf.ones(shape=(1, M2)) + tf.expand_dims(tf.reduce_sum(input_tensor=tf.square(A), axis=1), 1), + tf.ones(shape=(1, M2)), ) p2 = tf.transpose( a=tf.matmul( @@ -187,8 +188,7 @@ def compute_accuracy( def debug_embbeding(image, architecture, embbeding_dim=2, feature_layer="fc3"): - """ - """ + """""" import tensorflow as tf from bob.learn.tensorflow.utils.session import Session @@ -216,12 +216,16 @@ def pdist(A): """ with tf.compat.v1.name_scope("Pairwisedistance"): ones_1 = tf.reshape(tf.cast(tf.ones_like(A), tf.float32)[:, 0], [1, -1]) - p1 = tf.matmul(tf.expand_dims(tf.reduce_sum(input_tensor=tf.square(A), axis=1), 1), ones_1) + p1 = tf.matmul( + tf.expand_dims(tf.reduce_sum(input_tensor=tf.square(A), axis=1), 1), ones_1 + ) ones_2 = tf.reshape(tf.cast(tf.ones_like(A), tf.float32)[:, 0], [-1, 1]) p2 = tf.transpose( a=tf.matmul( - tf.reshape(tf.reduce_sum(input_tensor=tf.square(A), axis=1), shape=[-1, 1]), + tf.reshape( + tf.reduce_sum(input_tensor=tf.square(A), axis=1), shape=[-1, 1] + ), ones_2, transpose_b=True, ) @@ -467,6 +471,7 @@ def random_choice_no_replacement(one_dim_input, num_indices_to_drop=3, sort=Fals result = tf.gather(one_dim_input, sorted_indices_to_keep) return result + def is_argument_available(argument, method): """ Check if an argument (or keyword argument) is available in a method diff --git a/doc/conf.py b/doc/conf.py index efb56b77423f80b23671cedadca42b3c3e2ef65d..7959b3ebb4d3be85efe68a80b767051c67e89d89 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -10,24 +10,24 @@ import pkg_resources # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.3' +needs_sphinx = "1.3" # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.ifconfig', - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.doctest', - 'sphinx.ext.graphviz', - 'sphinx.ext.intersphinx', - 'sphinx.ext.napoleon', - 'sphinx.ext.viewcode', - 'sphinx.ext.mathjax', - 'matplotlib.sphinxext.plot_directive' - ] + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.ifconfig", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.doctest", + "sphinx.ext.graphviz", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx.ext.mathjax", + "matplotlib.sphinxext.plot_directive", +] # Be picky about warnings nitpicky = True @@ -36,13 +36,13 @@ nitpicky = True nitpick_ignore = [] # Allows the user to override warnings from a separate file -if os.path.exists('nitpick-exceptions.txt'): - for line in open('nitpick-exceptions.txt'): +if os.path.exists("nitpick-exceptions.txt"): + for line in open("nitpick-exceptions.txt"): if line.strip() == "" or line.startswith("#"): continue dtype, target = line.split(None, 1) target = target.strip() - try: # python 2.x + try: # python 2.x target = unicode(target) except NameError: pass @@ -58,25 +58,27 @@ autosummary_generate = True numfig = True # If we are on OSX, the 'dvipng' path maybe different -dvipng_osx = '/opt/local/libexec/texlive/binaries/dvipng' -if os.path.exists(dvipng_osx): pngmath_dvipng = dvipng_osx +dvipng_osx = "/opt/local/libexec/texlive/binaries/dvipng" +if os.path.exists(dvipng_osx): + pngmath_dvipng = dvipng_osx # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'bob.learn.tensorflow' +project = u"bob.learn.tensorflow" import time -copyright = u'%s, Idiap Research Institute' % time.strftime('%Y') + +copyright = u"%s, Idiap Research Institute" % time.strftime("%Y") # Grab the setup entry distribution = pkg_resources.require(project)[0] @@ -92,42 +94,42 @@ release = distribution.version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['links.rst'] +exclude_patterns = ["links.rst"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # Some variables which are useful for generated material -project_variable = project.replace('.', '_') -short_description = u'bob.learn.tensorflow API' -owner = [u'Idiap Research Institute'] +project_variable = project.replace(".", "_") +short_description = u"bob.learn.tensorflow API" +owner = [u"Idiap Research Institute"] # -- Options for HTML output --------------------------------------------------- @@ -135,80 +137,81 @@ owner = [u'Idiap Research Institute'] # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. import sphinx_rtd_theme -html_theme = 'sphinx_rtd_theme' + +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The name for this set of Sphinx documents. If None, it defaults to # "<project> v<release> documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = project_variable +# html_short_title = project_variable # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = 'img/logo.png' +html_logo = "img/logo.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -html_favicon = 'img/favicon.ico' +html_favicon = "img/favicon.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] +# html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a <link> tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = project_variable + u'_doc' +htmlhelp_basename = project_variable + u"_doc" # -- Post configuration -------------------------------------------------------- @@ -218,46 +221,49 @@ rst_epilog = """ .. |project| replace:: Bob .. |version| replace:: %s .. |current-year| date:: %%Y -""" % (version,) +""" % ( + version, +) # Default processing flags for sphinx -autoclass_content = 'class' -autodoc_member_order = 'bysource' +autoclass_content = "class" +autodoc_member_order = "bysource" autodoc_default_flags = [ - 'members', - 'undoc-members', - 'show-inheritance', - ] + "members", + "undoc-members", + "show-inheritance", +] # For inter-documentation mapping: from bob.extension.utils import link_documentation, load_requirements + sphinx_requirements = "extra-intersphinx.txt" if os.path.exists(sphinx_requirements): - intersphinx_mapping = link_documentation( - additional_packages=['python','numpy'] + \ - load_requirements(sphinx_requirements) - ) + intersphinx_mapping = link_documentation( + additional_packages=["python", "numpy"] + load_requirements(sphinx_requirements) + ) else: - intersphinx_mapping = link_documentation() + intersphinx_mapping = link_documentation() # We want to remove all private (i.e. _. or __.__) members # that are not in the list of accepted functions -accepted_private_functions = ['__array__'] +accepted_private_functions = ["__array__"] + def member_function_test(app, what, name, obj, skip, options): - # test if we have a private function - if len(name) > 1 and name[0] == '_': - # test if this private function should be allowed - if name not in accepted_private_functions: - # omit privat functions that are not in the list of accepted private functions - return skip - else: - # test if the method is documented - if not hasattr(obj, '__doc__') or not obj.__doc__: - return skip - return False + # test if we have a private function + if len(name) > 1 and name[0] == "_": + # test if this private function should be allowed + if name not in accepted_private_functions: + # omit privat functions that are not in the list of accepted private functions + return skip + else: + # test if the method is documented + if not hasattr(obj, "__doc__") or not obj.__doc__: + return skip + return False -def setup(app): - app.connect('autodoc-skip-member', member_function_test) +def setup(app): + app.connect("autodoc-skip-member", member_function_test) diff --git a/setup.py b/setup.py index 2b1997fbdcb0bf7d8bbe19fa0280a3f20d01f0e1..c53aa82aa7f5f9cfe2aaacb565bedbceb0384597 100644 --- a/setup.py +++ b/setup.py @@ -6,33 +6,31 @@ from setuptools import setup from setuptools import setup, dist -dist.Distribution(dict(setup_requires=['bob.extension'])) + +dist.Distribution(dict(setup_requires=["bob.extension"])) from bob.extension.utils import load_requirements, find_packages + install_requires = load_requirements() # The only thing we do in this file is to call the setup() function with all # parameters that define our package. setup( - # This is the basic information about your project. Modify all this # information before releasing code publicly. - name='bob.learn.tensorflow', + name="bob.learn.tensorflow", version=open("version.txt").read().rstrip(), - description='Bob bindings for tensorflow', - url='', - license='BSD', - author='Tiago de Freitas Pereira', - author_email='tiago.pereira@idiap.ch', - keywords='tensorflow', - + description="Bob bindings for tensorflow", + url="", + license="BSD", + author="Tiago de Freitas Pereira", + author_email="tiago.pereira@idiap.ch", + keywords="tensorflow", # If you have a better, long description of your package, place it on the # 'doc' directory and then hook it here - long_description=open('README.rst').read(), - + long_description=open("README.rst").read(), # This line is required for any distutils based packaging. include_package_data=True, - # This line defines which packages should be installed when you "install" # this package. All packages that are mentioned here, but are not installed # on the current system will be installed locally and only visible to the @@ -42,46 +40,43 @@ setup( packages=find_packages(), zip_safe=False, entry_points={ - # main entry for bob tf cli - 'bob.cli': [ - 'tf = bob.learn.tensorflow.script.tf:tf', - 'keras = bob.learn.tensorflow.script.keras:keras', + "bob.cli": [ + "tf = bob.learn.tensorflow.script.tf:tf", + "keras = bob.learn.tensorflow.script.keras:keras", ], - # bob tf scripts - 'bob.learn.tensorflow.cli': [ - 'cache-dataset = bob.learn.tensorflow.script.cache_dataset:cache_dataset', - 'compute-statistics = bob.learn.tensorflow.script.compute_statistics:compute_statistics', - 'dataset-to-hdf5 = bob.learn.tensorflow.script.db_to_tfrecords:dataset_to_hdf5', - 'datasets-to-tfrecords = bob.learn.tensorflow.script.db_to_tfrecords:datasets_to_tfrecords', - 'db-to-tfrecords = bob.learn.tensorflow.script.db_to_tfrecords:db_to_tfrecords', - 'describe-tfrecord = bob.learn.tensorflow.script.db_to_tfrecords:describe_tfrecord', - 'distance-matrix = bob.learn.tensorflow.script.cgm:distance_matrix', - 'eval = bob.learn.tensorflow.script.eval:eval', - 'predict = bob.learn.tensorflow.script.predict_bio:predict', - 'predict-bio = bob.learn.tensorflow.script.predict_bio:predict_bio', - 'style-transfer = bob.learn.tensorflow.script.style_transfer:style_transfer', - 'train = bob.learn.tensorflow.script.train:train', - 'train-and-evaluate = bob.learn.tensorflow.script.train_and_evaluate:train_and_evaluate', - 'trim = bob.learn.tensorflow.script.trim:trim', + "bob.learn.tensorflow.cli": [ + "cache-dataset = bob.learn.tensorflow.script.cache_dataset:cache_dataset", + "compute-statistics = bob.learn.tensorflow.script.compute_statistics:compute_statistics", + "dataset-to-hdf5 = bob.learn.tensorflow.script.db_to_tfrecords:dataset_to_hdf5", + "datasets-to-tfrecords = bob.learn.tensorflow.script.db_to_tfrecords:datasets_to_tfrecords", + "db-to-tfrecords = bob.learn.tensorflow.script.db_to_tfrecords:db_to_tfrecords", + "describe-tfrecord = bob.learn.tensorflow.script.db_to_tfrecords:describe_tfrecord", + "distance-matrix = bob.learn.tensorflow.script.cgm:distance_matrix", + "eval = bob.learn.tensorflow.script.eval:eval", + "predict = bob.learn.tensorflow.script.predict_bio:predict", + "predict-bio = bob.learn.tensorflow.script.predict_bio:predict_bio", + "style-transfer = bob.learn.tensorflow.script.style_transfer:style_transfer", + "train = bob.learn.tensorflow.script.train:train", + "train-and-evaluate = bob.learn.tensorflow.script.train_and_evaluate:train_and_evaluate", + "trim = bob.learn.tensorflow.script.trim:trim", ], # bob keras scripts - 'bob.learn.tensorflow.keras_cli': [ - 'fit = bob.learn.tensorflow.script.fit:fit', + "bob.learn.tensorflow.keras_cli": [ + "fit = bob.learn.tensorflow.script.fit:fit", ], }, - # Classifiers are important if you plan to distribute this package through # PyPI. You can find the complete list of classifiers that are valid and # useful here (http://pypi.python.org/pypi?%3Aaction=list_classifiers). classifiers=[ - 'Framework :: Bob', - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Natural Language :: English', - 'Programming Language :: Python', - 'Topic :: Scientific/Engineering :: Artificial Intelligence', + "Framework :: Bob", + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Natural Language :: English", + "Programming Language :: Python", + "Topic :: Scientific/Engineering :: Artificial Intelligence", ], )