<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def reshape(input_layer, shape_spec):
 """Reshapes this tensor to the given spec. This provides additional functionality over the basic `tf.reshape`. In particular, it provides the ability to specify some dimensions as unchanged (`pt.DIM_SAME`) which can greatly aid in inferring the extra dimensions (`pt.DIM_REST`) and help maintain more shape information going forward. A shape_spec can be a list or tuple of numbers specifying the new shape, but also may include the following shorthands for using values from the shape of the input: 1. `pt.DIM_SAME` ('_') will use the corresponding value from the current shape. 2. One -1 or `pt.DIM_REST` ('*') can be used to specify the remainder of the values. 3. An integer will be used as is. A compact syntax is also supported for setting shapes. If the new shape is only composed of DIM_SAME, DIM_REST/-1 and single digit integers, then a string can be passed in. Integers larger than 9 must be passed in as part of a sequence. 1. Flatten to a batch dimension (first by convention):
 [DIM_SAME, -1] or '_*'. 2. Expand a Rank 2 Tensor so that it can be used as an image: '_11*'. The primary difference between this and `tf.reshape` is that `DIM_SAME` allows more shape inference possibilities. For example: given a shape of **[None, 3, 7]** if flattening were desired then the caller would have to compute the shape and request a reshape of **[-1, 21]** to flatten. Instead of brittle or repeated code, this can be inferred if we know that the first dim is being copied. Another example that is impossible to express as a list of integers is if the starting shape were **[None, 3, None]** and we wanted to do the same flattening. While the shape cannot be inferred, this can still be expressed as '_*' (A.K.A. [DIM_SAME, DIM_REST]). Args: input_layer: The Pretty Tensor object, supplied. shape_spec: The spec for the new shape. Returns: A Pretty Tensor with the reshaped tensor. Raises: ValueError: If there are two many unknown dimensions or the shape_spec requires out of range DIM_SAME. """

  old_shape = input_layer.get_shape().as_list()

  # Extract both a tensor that sets the new shape and as much of the new
  # shape is known. This lets us merge in any extra information we have about
  # the shape.
  try:
    new_shape = _infer_unknown_dims(old_shape, shape_spec)
  except TypeError:
    # shape_spec is not iterable, it is probably a tensor or variable.
    return tf.reshape(input_layer, shape_spec)
  reshape_tensor = []

  # To avoid bloating the graph, we want to capture consecutive integers into
  # a single tf.constant. This allows us to eliminate tf.concat when we know the
  # shape.
  runner = []

  for i, s in enumerate(new_shape):
    if s is DIM_SAME:
      new_shape[i] = None
      if runner:
        reshape_tensor.append(tf.constant(runner))
        runner = []
      # Since we can't statically infer the value, compute it from the graph.
      reshape_tensor.append(tf.gather(tf.shape(input_layer), [i]))
    else:
      runner.append(s)
      if s == -1:
        new_shape[i] = None
  if runner:
    reshape_tensor.append(tf.constant(runner))

  if len(reshape_tensor) == 1:
    reshape_tensor = reshape_tensor[0]
  else:
    reshape_tensor = tf.concat(reshape_tensor, 0)
  result = tf.reshape(input_layer, reshape_tensor)
  result.set_shape(new_shape)

  return input_layer.with_tensor(result)