'Loss function for char sequence.
Depending on values of hyper parameters it applies label smoothing and can
also ignore all null chars after the first one.
Args:
chars_logits: logits for predicted characters,
shape=[batch_size, seq_length, num_char_classes];
chars_labels: ground truth ids of characters,
shape=[batch_size, seq_length];
mparams: method hyper parameters.
Returns:
A Tensor with shape [batch_size] - the log-perplexity for each sequence.'

def sequence_loss_fn(self, chars_logits, chars_labels):

    mparams = self._mparams['sequence_loss_fn']
    with tf.variable_scope('sequence_loss_fn/SLF'):
        if (mparams.label_smoothing > 0):
            smoothed_one_hot_labels = self.label_smoothing_regularization(chars_labels, mparams.label_smoothing)
            labels_list = tf.unstack(smoothed_one_hot_labels, axis=1)
        else:
            labels_list = tf.unstack(chars_labels, axis=1)
        (batch_size, seq_length, _) = chars_logits.shape.as_list()
        if mparams.ignore_nulls:
            weights = tf.ones((batch_size, seq_length), dtype=tf.float32)
        else:
            reject_char = tf.constant((self._params.num_char_classes - 1), shape=(batch_size, seq_length), dtype=tf.int64)
            known_char = tf.not_equal(chars_labels, reject_char)
            weights = tf.to_float(known_char)
        logits_list = tf.unstack(chars_logits, axis=1)
        weights_list = tf.unstack(weights, axis=1)
        loss = tf.contrib.legacy_seq2seq.sequence_loss(logits_list, labels_list, weights_list, softmax_loss_function=get_softmax_loss_fn(mparams.label_smoothing), average_across_timesteps=mparams.average_across_timesteps)
        tf.losses.add_loss(loss)
        return loss
