docs: def _tflearn_features(train_config, args):
    """Builds the tf.learn feature list.
  
    All numerical features are just given real_valued_column because all the
    preprocessing transformations are done in preprocess_input. Categoriacl
    features are processed here depending if the vocab map (from string to int)
    was applied in preprocess_input.
  
    Args:
      train_config: our train config object
      args: command line args.
  
    Returns:
      List of TF lean feature columns.
  
    Raises:
      ValueError: if wrong transforms are used for the model type.
    """
    feature_columns = []
    target_name = train_config['target_column']
    key_name = train_config['key_column']
  
    for name in train_config['numerical_columns']:
      if name != target_name and name != key_name:
        feature_columns.append(tf.contrib.layers.real_valued_column(
            name,
            dimension=1))
  
        # Supported transforms:
        # for DNN
        # 1) string -> make int -> embedding (embedding)
        # 2) string -> make int -> one_hot (one_hot, default)
        # for linear
        # 1) string -> sparse_column_with_hash_bucket (embedding)
        # 2) string -> make int -> sparse_column_with_integerized_feature (one_hot, default)
        # It is unfortunate that tf.layers has different feature transforms if the
        # model is linear or DNN. This pacakge should not expose to the user that
        # we are using tf.layers. It is crazy that DNN models support more feature
        # types (like string -> hash sparse column -> embedding)
    for name in train_config['categorical_columns']:
      if name != target_name and name != key_name:
        transform_config = train_config['transforms'].get(name, {})
        transform_name = transform_config.get('transform', None)
  
        if is_dnn_model(args.model_type):
          if transform_name == 'embedding':
            sparse = tf.contrib.layers.sparse_column_with_integerized_feature(
                name,
                bucket_size=train_config['vocab_stats'][name]['n_classes'])
            learn_feature = tf.contrib.layers.embedding_column(
                sparse,
                dimension=transform_config['embedding_dim'])
          elif transform_name == 'one_hot' or transform_name is None:
            sparse = tf.contrib.layers.sparse_column_with_integerized_feature(
                name,
                bucket_size=train_config['vocab_stats'][name]['n_classes'])
            learn_feature = tf.contrib.layers.one_hot_column(sparse)
          else:
            raise ValueError(('Unknown transform name. Only \'embedding\' '
                              'and \'one_hot\' transforms are supported. Got %s')
                             % transform_name)
        elif is_linear_model(args.model_type):
          if transform_name == 'one_hot' or transform_name is None:
            learn_feature = tf.contrib.layers.sparse_column_with_integerized_feature(
                name,
                bucket_size=train_config['vocab_stats'][name]['n_classes'])
          elif transform_name == 'embedding':
            learn_feature = tf.contrib.layers.sparse_column_with_hash_bucket(
                name,
                hash_bucket_size=transform_config['embedding_dim'])
          else:
            raise ValueError(('Unknown transform name. Only \'embedding\' '
                              'and \'one_hot\' transforms are supported. Got %s')
                             % transform_name)
  
        # Save the feature
        feature_columns.append(learn_feature)
    return feature_columns