'Minimize using sanitized gradients.
This gets a var_list which is the list of trainable variables.
For each var in var_list, we defined a grad_accumulator variable
during init. When batches_per_lot > 1, we accumulate the gradient
update in those. At the end of each lot, we apply the update back to
the variable. This has the effect that for each lot we compute
gradients at the point at the beginning of the lot, and then apply one
update at the end of the lot. In other words, semantically, we are doing
SGD with one lot being the equivalent of one usual batch of size
batch_size * batches_per_lot.
This allows us to simulate larger batches than our memory size would permit.
The lr and the num_steps are in the lot world.
Args:
loss: the loss tensor.
global_step: the optional global step.
var_list: the optional variables.
name: the optional name.
Returns:
the operation that runs one step of DP gradient descent.'

def minimize(self, loss, global_step=None, var_list=None, name=None):

    if (var_list is None):
        var_list = tf.trainable_variables()
    for var in var_list:
        if (not isinstance(var, tf.Variable)):
            raise TypeError(('Argument    is    not    a    variable.Variable:    %s' % var))
    if (self._batches_per_lot == 1):
        sanitized_grads = self.compute_sanitized_gradients(loss, var_list=var_list)
        grads_and_vars = zip(sanitized_grads, var_list)
        self._assert_valid_dtypes([v for (g, v) in grads_and_vars if (g is not None)])
        apply_grads = self.apply_gradients(grads_and_vars, global_step=global_step, name=name)
        return apply_grads
    update_cond = tf.equal(tf.constant(0), tf.mod(self._batch_count, tf.constant(self._batches_per_lot)))
    def non_last_in_lot_op(loss, var_list):
        'Ops    to    do    for    a    typical    batch.\n\n                        For    a    batch    that    is    not    the    last    one    in    the    lot,    we    simply    compute    the\n                        sanitized    gradients    and    apply    them    to    the    grad_acc    variables.\n\n                        Args:\n                                loss:    loss    function    tensor\n                                var_list:    list    of    variables\n                        Returns:\n                                A    tensorflow    op    to    do    the    updates    to    the    gradient    accumulators\n                        '
        sanitized_grads = self.compute_sanitized_gradients(loss, var_list=var_list, add_noise=False)
        update_ops_list = []
        for (var, grad) in zip(var_list, sanitized_grads):
            grad_acc_v = self._grad_accum_dict[var.name]
            update_ops_list.append(grad_acc_v.assign_add(grad))
        update_ops_list.append(self._batch_count.assign_add(1))
        return tf.group(*update_ops_list)
    def last_in_lot_op(loss, var_list, global_step):
        'Ops    to    do    for    last    batch    in    a    lot.\n\n                        For    the    last    batch    in    the    lot,    we    first    add    the    sanitized    gradients    to\n                        the    gradient    acc    variables,    and    then    apply    these\n                        values    over    to    the    original    variables    (via    an    apply    gradient)\n\n                        Args:\n                                loss:    loss    function    tensor\n                                var_list:    list    of    variables\n                                global_step:    optional    global    step    to    be    passed    to    apply_gradients\n                        Returns:\n                                A    tensorflow    op    to    push    updates    from    shadow    vars    to    real    vars.\n                        '
        sanitized_grads = self.compute_sanitized_gradients(loss, var_list=var_list, add_noise=True)
        normalized_grads = []
        for (var, grad) in zip(var_list, sanitized_grads):
            grad_acc_v = self._grad_accum_dict[var.name]
            normalized_grad = tf.div(grad_acc_v.assign_add(grad), tf.to_float(self._batches_per_lot))
            normalized_grads.append(normalized_grad)
        with tf.control_dependencies(normalized_grads):
            grads_and_vars = zip(normalized_grads, var_list)
            self._assert_valid_dtypes([v for (g, v) in grads_and_vars if (g is not None)])
            apply_san_grads = self.apply_gradients(grads_and_vars, global_step=global_step, name='apply_grads')
        resets_list = []
        with tf.control_dependencies([apply_san_grads]):
            for (_, acc) in self._grad_accum_dict.items():
                reset = tf.assign(acc, tf.zeros_like(acc))
                resets_list.append(reset)
        resets_list.append(self._batch_count.assign_add(1))
        last_step_update = tf.group(*([apply_san_grads] + resets_list))
        return last_step_update
    update_op = tf.cond(update_cond, (lambda : last_in_lot_op(loss, var_list, global_step)), (lambda : non_last_in_lot_op(loss, var_list)))
    return tf.group(update_op)
