class_docstr: Wrap another tf optimizer with BoltOn privacy protocol.
  
  BoltOn optimizer wraps another tf optimizer to be used
  as the visible optimizer to the tf model. No matter the optimizer
  passed, "BoltOn" enables the bolt-on model to control the learning rate
  based on the strongly convex loss.
  
  To use the BoltOn method, you must:
  1. instantiate it with an instantiated tf optimizer and StrongConvexLoss.
  2. use it as a context manager around your .fit method internals.
  
  This can be accomplished by the following:
  optimizer = tf.optimizers.SGD()
  loss = privacy.bolt_on.losses.StrongConvexBinaryCrossentropy()
  bolton = BoltOn(optimizer, loss)
  with bolton(*args) as _:
    model.fit()
  The args required for the context manager can be found in the __call__
  method.
  
  For more details on the strong convexity requirements, see:
  Bolt-on Differential Privacy for Scalable Stochastic Gradient
  Descent-based Analytics by Xi Wu et. al.
docstr: Constructor.
  
  Args:
    optimizer: Optimizer_v2 or subclass to be used as the optimizer (wrapped).
    loss: StrongConvexLoss function that the model is being compiled with.
    dtype: dtype
docstr_tok: ["Constructor",".","Args",":","optimizer",":","Optimizer_v2","or","subclass","to","be","used","as","the","optimizer","(","wrapped",")",".","loss",":","StrongConvexLoss","function","that","the","model","is","being","compiled","with",".","dtype",":","dtype"]
code: def __init__(
      self,  # pylint: disable=super-init-not-called
      optimizer,
      loss,
      dtype=tf.float32,
  ):
      """Constructor.
  
      Args:
        optimizer: Optimizer_v2 or subclass to be used as the optimizer (wrapped).
        loss: StrongConvexLoss function that the model is being compiled with.
        dtype: dtype
      """
  
      if not isinstance(loss, StrongConvexMixin):
          raise ValueError(
              "loss function must be a Strongly Convex and therefore "
              "extend the StrongConvexMixin."
          )
      self._private_attributes = [
          "_internal_optimizer",
          "dtype",
          "noise_distribution",
          "epsilon",
          "loss",
          "class_weights",
          "input_dim",
          "n_samples",
          "layers",
          "batch_size",
          "_is_init",
      ]
      self._internal_optimizer = optimizer
      self.learning_rate = (
          GammaBetaDecreasingStep()
      )  # use the BoltOn Learning
      # rate scheduler, as required for privacy guarantees. This will still need
      # to get values from the loss function near the time that .fit is called
      # on the model (when this optimizer will be called as a context manager)
      self.dtype = dtype
      self.loss = loss
      self._is_init = False
  
imports: import tensorflow.compat.v1
  from tensorflow.compat.v1.python.keras.optimizer_v2 import optimizer_v2
  from tensorflow.compat.v1.python.ops import math_ops
  from tensorflow_privacy.privacy.bolt_on.losses import StrongConvexMixin
label_desc: Use image node_id 1 to create a new BoltOn object from inherited base classes: optimizer_v2 with example: obj = BoltOn(optimizer, loss, dtype)