Repository: ludwig-ai/ludwig

Issue:
[automl] Binary column type not detected for floats
For example, the titanic dataset has values like `1.0` and `0.0`. These can correctly be interpreted as binary values `1` and `0` respectively, but the auto detect system is treating this as a categorical feature.


Discussion:
@tgaddair I thinh that:
`output_features:
    -
        name: Survived
        type: binary`
and
`output_features:
    -
        name: Survived
        type: category`

are interpret similar, if you want numeric output you need of course:
`output_features:
    -
        name: Survived
        type: numeric`

but for me the question is **how to  configure sigmoid layer as  output to probaability value result?** Equivalent for:
`pred = model.predict_proba(X_test)`

Good point @PeterPirog . If you set the output to be binary, you also get the raw probabilities together with the predictions (which are basically just a transformation of probabilities > threshold, and you can set the threshold in the output feature too if you want, by default is 0.5).
Does that work for you?

We could also make it so that users can define an activation function before the final output of the numerical feature, but the issue there would be that even if you do, say, a sigmoid to squash between 0 and 1, you'll still train with MSE loss (which may be fine, there are a few papers where it is shown that performance are comparable and in few cases even better than using cross entropy).
@w4nderlust, I'm very glad to use your framework.  Can You give some suggestions how to set in ludwig metric for unballanced data because accuracy is bad option for it. I noticed some error for ROC AUC:
```
    return metrics.roc_curve(conditions, prediction_scores, pos_label,
TypeError: roc_curve() takes 2 positional arguments but 4 were given
```
Configuration is:


```
input_features:
    -
        name: Pclass
        type: category
    -
        name: Name
        type: text
        level: char
        encoder: parallel_cnn
    -
        name: Sex
        type: category
    -
        name: Age
        type: numerical
        preprocessing:
          missing_value_strategy: fill_with_mean
    -
        name: SibSp
        type: numerical
    -
        name: Parch
        type: numerical
    -
        name: Fare
        type: numerical
        preprocessing:
          missing_value_strategy: fill_with_mean

    -
        name: Embarked
        type: category

output_features:
    -
        name: Survived
        type: binary
```




Resolving patch:
diff --git a/ludwig/utils/eval_utils.py b/ludwig/utils/eval_utils.py
--- a/ludwig/utils/eval_utils.py
+++ b/ludwig/utils/eval_utils.py
@@ -262,15 +262,15 @@ def stats(self):
 
 def roc_curve(conditions, prediction_scores, pos_label=None,
               sample_weight=None):
-    return metrics.roc_curve(conditions, prediction_scores, pos_label,
-                             sample_weight)
+    return metrics.roc_curve(conditions, prediction_scores, pos_label=pos_label,
+                             sample_weight=sample_weight)
 
 
 def roc_auc_score(conditions, prediction_scores, average='micro',
                   sample_weight=None):
     try:
-        return metrics.roc_auc_score(conditions, prediction_scores, average,
-                                     sample_weight)
+        return metrics.roc_auc_score(conditions, prediction_scores, average=average,
+                                     sample_weight=sample_weight)
     except ValueError as ve:
         logger.info(ve)
 
@@ -278,7 +278,7 @@ def roc_auc_score(conditions, prediction_scores, average='micro',
 def precision_recall_curve(conditions, prediction_scores, pos_label=None,
                            sample_weight=None):
     return metrics.precision_recall_curve(conditions, prediction_scores,
-                                          pos_label, sample_weight)
+                                          pos_label=pos_label, sample_weight=sample_weight)
 
 
 def average_precision_score(conditions, prediction_scores, average='micro',
@@ -287,29 +287,3 @@ def average_precision_score(conditions, prediction_scores, average='micro',
     return metrics.average_precision_score(conditions, prediction_scores,
                                            average=average,
                                            sample_weight=sample_weight)
-
-# if __name__ == '__main__':
-#     parser = argparse.ArgumentParser(
-#         description='This script trains and tests a model.')
-#     parser.add_argument('gold_standard', help='file containing gold standars')
-#     parser.add_argument(PREDICTIONS, help='file containing predictions')
-#     parser.add_argument('output_fp', help='output file')
-#     args = parser.parse_args()
-#
-#     hdf5_data = h5py.File(args.gold_standard, 'r')
-#     split = hdf5_data[SPLIT].value
-#     column = hdf5_data['macros'].value
-#     hdf5_data.close()
-#     conditions = column[split == 2]  # ground truth
-#
-#     predictions = np.load(args.predictions)
-#
-#     confusion_matrix = ConfusionMatrix(predictions, conditions)
-#
-#     results = load_json(args.output_fp)
-#     results['confusion_matrix_stats'] = {
-#         'confusion_matrix': confusion_matrix.cm.tolist(),
-#         'overall_stats': confusion_matrix.stats(),
-#         'per_class_stats': confusion_matrix.per_class_stats()
-#     }
-#     save_json(args.output_fp, results)
diff --git a/ludwig/utils/strings_utils.py b/ludwig/utils/strings_utils.py
--- a/ludwig/utils/strings_utils.py
+++ b/ludwig/utils/strings_utils.py
@@ -37,8 +37,8 @@
 COMMA_REGEX = re.compile(r'\s*,\s*')
 UNDERSCORE_REGEX = re.compile(r'\s*_\s*')
 
-BOOL_TRUE_STRS = {'yes', 'y', 'true', 't', '1'}
-BOOL_FALSE_STRS = {'no', 'n', 'false', 'f', '0'}
+BOOL_TRUE_STRS = {'yes', 'y', 'true', 't', '1', '1.0'}
+BOOL_FALSE_STRS = {'no', 'n', 'false', 'f', '0', '0.0'}
 # Update the following if BOOL_TRUE_STRS or BOOL_FALSE_STRS changes
 MAX_DISTINCT_BOOL_PERMUTATIONS = 70
 


Test patch:
diff --git a/tests/ludwig/utils/test_strings_utils.py b/tests/ludwig/utils/test_strings_utils.py
--- a/tests/ludwig/utils/test_strings_utils.py
+++ b/tests/ludwig/utils/test_strings_utils.py
@@ -31,5 +31,6 @@ def test_are_conventional_bools():
     assert strings_utils.are_conventional_bools(['True', 'Fales']) == False
     assert strings_utils.are_conventional_bools(['0', '1']) == True
     assert strings_utils.are_conventional_bools(['0', '2']) == False
+    assert strings_utils.are_conventional_bools(['1.0', '0.0']) == True
     assert strings_utils.are_conventional_bools(['high', 'low']) == False
     assert strings_utils.are_conventional_bools(['human', 'bot']) == False