docs: def make_preprocessing_fn(output_dir, features, keep_target):
    """Makes a preprocessing function.
  
    Args:
      output_dir: folder path that contains the vocab and stats files.
      features: the features dict
  
    Returns:
      a function that takes a dict of input tensors
    """
    def preprocessing_fn(inputs):
      """Preprocessing function.
  
      Args:
        inputs: dictionary of raw input tensors
  
      Returns:
        A dictionary of transformed tensors
      """
      stats = json.loads(
        file_io.read_file_to_string(
            os.path.join(output_dir, STATS_FILE)).decode())
  
      result = {}
      for name, transform in six.iteritems(features):
        transform_name = transform['transform']
        source_column = transform['source_column']
  
        if transform_name == KEY_TRANSFORM:
          transform_name = 'identity'
        elif transform_name == TARGET_TRANSFORM:
          if not keep_target:
            continue
          if file_io.file_exists(os.path.join(output_dir, VOCAB_ANALYSIS_FILE % source_column)):
            transform_name = 'one_hot'
          else:
            transform_name = 'identity'
  
        if transform_name == 'identity':
          result[name] = inputs[source_column]
        elif transform_name == 'scale':
          result[name] = _scale(
              inputs[name],
              min_x_value=stats['column_stats'][source_column]['min'],
              max_x_value=stats['column_stats'][source_column]['max'],
              output_min=transform.get('value', 1) * (-1),
              output_max=transform.get('value', 1))
        elif transform_name in [ONE_HOT_TRANSFORM, EMBEDDING_TRANSFROM, MULTI_HOT_TRANSFORM,
                                TFIDF_TRANSFORM, BOW_TRANSFORM]:
          vocab, ex_count = read_vocab_file(
              os.path.join(output_dir, VOCAB_ANALYSIS_FILE % source_column))
  
          if transform_name == TFIDF_TRANSFORM:
            separator = transform.get('separator', ' ')
            tokens = tf.string_split(inputs[source_column], separator)
            ids = _string_to_int(tokens, vocab)
            weights = _tfidf(
                x=ids,
                reduced_term_freq=ex_count + [0],
                vocab_size=len(vocab) + 1,
                corpus_size=stats['num_examples'])
  
            result[name + '_ids'] = ids
            result[name + '_weights'] = weights
          elif transform_name == BOW_TRANSFORM:
            separator = transform.get('separator', ' ')
            tokens = tf.string_split(inputs[source_column], separator)
            ids = _string_to_int(tokens, vocab)
            weights = _bag_of_words(x=ids)
  
            result[name + '_ids'] = ids
            result[name + '_weights'] = weights
          elif transform_name == MULTI_HOT_TRANSFORM:
            separator = transform.get('separator', ' ')
            tokens = tf.string_split(inputs[source_column], separator)
            result[name] = _string_to_int(tokens, vocab)
          else:
            # ONE_HOT_TRANSFORM: making a dense vector is done at training
            # EMBEDDING_TRANSFROM: embedding vectors have to be done at training
            result[name] = _string_to_int(inputs[source_column], vocab)
        elif transform_name == IMAGE_TRANSFORM:
          make_image_to_vec_fn = _make_image_to_vec_tito(
              name, checkpoint=transform.get('checkpoint', None))
          result[name] = make_image_to_vec_fn(inputs[source_column])
        else:
          raise ValueError('unknown transform %s' % transform_name)
      return result
  
    return preprocessing_fn