text: # Working example for my blog post at:
  # http://danijar.com/variable-sequence-lengths-in-tensorflow/
  import functools
  import tensorflow as tf
  from tensorflow import nn
  
  import sets
  
  # import sets
  # from . import sets
  
  
  def lazy_property(function):
      attribute = '_' + function.__name__
  
      @property
      @functools.wraps(function)
      def wrapper(self):
          if not hasattr(self, attribute):
              setattr(self, attribute, function(self))
          return getattr(self, attribute)
      return wrapper
  
  
  class VariableSequenceClassification:
  
      def __init__(self, data, target, num_hidden=200, num_layers=2):
          self.data = data
          self.target = target
          self._num_hidden = num_hidden
          self._num_layers = num_layers
          self.prediction
          self.error
          self.optimize
  
      @lazy_property
      def length(self):
          used = tf.sign(tf.reduce_max(input_tensor=tf.abs(self.data), axis=2))
          length = tf.reduce_sum(input_tensor=used, axis=1)
          length = tf.cast(length, tf.int32)
          return length
  
      @lazy_property
      def prediction(self):
          # Recurrent network.
          output, _ = nn.dynamic_rnn(
              tf.compat.v1.nn.rnn_cell.GRUCell(self._num_hidden),
              self.data,
              dtype=tf.float32,
              sequence_length=self.length,
          )
          last = self._last_relevant(output, self.length)
          # Softmax layer.
          weight, bias = self._weight_and_bias(
              self._num_hidden, int(self.target.get_shape()[1]))
          prediction = tf.nn.softmax(tf.matmul(last, weight) + bias)
          return prediction
  
      @lazy_property
      def cost(self):
          cross_entropy = -tf.reduce_sum(input_tensor=self.target * tf.math.log(self.prediction))
          return cross_entropy
  
      @lazy_property
      def optimize(self):
          learning_rate = 0.003
          optimizer = tf.compat.v1.train.RMSPropOptimizer(learning_rate)
          return optimizer.minimize(self.cost)
  
      @lazy_property
      def error(self):
          mistakes = tf.not_equal(
              tf.argmax(input=self.target, axis=1), tf.argmax(input=self.prediction, axis=1))
          return tf.reduce_mean(input_tensor=tf.cast(mistakes, tf.float32))
  
      @staticmethod
      def _weight_and_bias(in_size, out_size):
          weight = tf.random.truncated_normal([in_size, out_size], stddev=0.01)
          bias = tf.constant(0.1, shape=[out_size])
          return tf.Variable(weight), tf.Variable(bias)
  
      @staticmethod
      def _last_relevant(output, length):
          batch_size = tf.shape(input=output)[0]
          max_length = int(output.get_shape()[1])
          output_size = int(output.get_shape()[2])
          index = tf.range(0, batch_size) * max_length + (length - 1)
          flat = tf.reshape(output, [-1, output_size])
          relevant = tf.gather(flat, index)
          return relevant
  
  
  if __name__ == '__main__':
      # We treat images as sequences of pixel rows.
      train, test = sets.Mnist()
      _, rows, row_size = train.data.shape
      num_classes = train.target.shape[1]
      data = tf.compat.v1.placeholder(tf.float32, [None, rows, row_size])
      target = tf.compat.v1.placeholder(tf.float32, [None, num_classes])
      model = VariableSequenceClassification(data, target)
      sess = tf.compat.v1.Session()
      sess.run(tf.compat.v1.initialize_all_variables())
      for epoch in range(10):
          for _ in range(100):
              batch = train.sample(10)
              sess.run(model.optimize, {data: batch.data, target: batch.target})
          error = sess.run(model.error, {data: test.data, target: test.target})
          print('Epoch {:2d} error {:3.1f}%'.format(epoch + 1, 100 * error))