diff --git a/openml-h2o/pom.xml b/openml-h2o/pom.xml index dc7a70f8..16eeeae7 100644 --- a/openml-h2o/pom.xml +++ b/openml-h2o/pom.xml @@ -30,7 +30,7 @@ Contains classes and logic related with the import of H2O models. - 3.36.0.3 + 3.46.0.11 diff --git a/openml-h2o/src/main/java/com/feedzai/openml/h2o/H2OAlgorithm.java b/openml-h2o/src/main/java/com/feedzai/openml/h2o/H2OAlgorithm.java index 61e75053..897af1ec 100644 --- a/openml-h2o/src/main/java/com/feedzai/openml/h2o/H2OAlgorithm.java +++ b/openml-h2o/src/main/java/com/feedzai/openml/h2o/H2OAlgorithm.java @@ -95,8 +95,6 @@ public enum H2OAlgorithm implements MLAlgorithmEnum { /** * Generalized Linear Models (GLM) estimate regression models for outcomes following exponential distributions. - *

- * We enforce it to always have a family parameter Binomial that causes it to be a classification algorithm. */ GENERALIZED_LINEAR_MODEL(createDescriptor( "Generalized Linear Modeling", diff --git a/openml-h2o/src/main/java/com/feedzai/openml/h2o/algos/H2OGeneralizedLinearModelUtils.java b/openml-h2o/src/main/java/com/feedzai/openml/h2o/algos/H2OGeneralizedLinearModelUtils.java index 94430ad8..a1cd1661 100644 --- a/openml-h2o/src/main/java/com/feedzai/openml/h2o/algos/H2OGeneralizedLinearModelUtils.java +++ b/openml-h2o/src/main/java/com/feedzai/openml/h2o/algos/H2OGeneralizedLinearModelUtils.java @@ -65,7 +65,8 @@ public final class H2OGeneralizedLinearModelUtils extends AbstractSupervisedH2OA // H2O UI excludes these in a hard-coded way...so do we. .filter(modelParameter -> !"tweedie_link_power".equals(modelParameter.getName()) && !"tweedie_variance_power".equals(modelParameter.getName()) && - !"nlambdas".equals(modelParameter.getName()) && !"early_stopping".equals(modelParameter.getName()) + !"nlambdas".equals(modelParameter.getName()) && !"early_stopping".equals(modelParameter.getName()) && + !"influence".equals(modelParameter.getName()) ) .collect(Collectors.toSet()); diff --git a/openml-h2o/src/main/java/com/feedzai/openml/h2o/params/ParametersBuilderUtil.java b/openml-h2o/src/main/java/com/feedzai/openml/h2o/params/ParametersBuilderUtil.java index 0ac1d95d..ca900d05 100644 --- a/openml-h2o/src/main/java/com/feedzai/openml/h2o/params/ParametersBuilderUtil.java +++ b/openml-h2o/src/main/java/com/feedzai/openml/h2o/params/ParametersBuilderUtil.java @@ -243,7 +243,29 @@ private static ModelParameter getModelParameter(final Class 0) { final Set possibleValues = Arrays.stream(apiAnnot.values()).collect(Collectors.toSet()); final Enum defaultValue = getDefaultChoiceValue(paramsClass, fieldName); - paramType = new ChoiceFieldType(possibleValues, defaultValue.name()); + String defaultValueName = defaultValue != null ? defaultValue.name() : ""; + if (!possibleValues.contains(defaultValueName)) { + final String finalDefaultName = defaultValueName; + + // Check if there is a match + final Optional match = possibleValues.stream() + .filter(val -> val.equalsIgnoreCase(finalDefaultName)) + .findFirst(); + + if (match.isPresent()) { + defaultValueName = match.get(); + } else if (!possibleValues.isEmpty()) { + // Fallback to the first allowed choice if the default is missing or null + defaultValueName = possibleValues.iterator().next(); + logger.warn( + "Default value name `{}` is not present on possible values set `{}`. Falling back to `{}`", + finalDefaultName, + possibleValues, + defaultValueName + ); + } + } + paramType = new ChoiceFieldType(possibleValues, defaultValueName); } else if (fieldType.equals(String.class)) { final String defaultValue = getDefaultStringValue(paramsClass, fieldName); diff --git a/openml-h2o/src/test/java/com/feedzai/openml/h2o/H2OAlgorithmTestParams.java b/openml-h2o/src/test/java/com/feedzai/openml/h2o/H2OAlgorithmTestParams.java index b01ea281..552f98e5 100644 --- a/openml-h2o/src/test/java/com/feedzai/openml/h2o/H2OAlgorithmTestParams.java +++ b/openml-h2o/src/test/java/com/feedzai/openml/h2o/H2OAlgorithmTestParams.java @@ -133,7 +133,9 @@ public static Map getGlm() { .put("lambda", "1") .put("lambda_search", "true") .put("standardize", "true") - .put("non_negative", "true") + // non_negative does not work with multinomial/ordinal families and GLM + // fails on init if set to true (see https://github.com/h2oai/h2o-3/issues/7055) + .put("non_negative", "false") .put("obj_reg", "-1") .put("theta", "1e-10") // default value .put("HGLM", "false") // default value diff --git a/openml-h2o/src/test/java/com/feedzai/openml/h2o/algos/ParametersTest.java b/openml-h2o/src/test/java/com/feedzai/openml/h2o/algos/ParametersTest.java index 39a743d2..99f16fe4 100644 --- a/openml-h2o/src/test/java/com/feedzai/openml/h2o/algos/ParametersTest.java +++ b/openml-h2o/src/test/java/com/feedzai/openml/h2o/algos/ParametersTest.java @@ -32,6 +32,8 @@ import com.feedzai.openml.h2o.algos.mocks.RegularParameters; import com.feedzai.openml.h2o.params.ParametersBuilderUtil; import com.feedzai.openml.provider.descriptor.ModelParameter; +import com.feedzai.openml.provider.descriptor.fieldtype.ChoiceFieldType; + import org.junit.Test; import org.slf4j.LoggerFactory; @@ -177,4 +179,96 @@ private ListAppender appendLogger(final Class clazz) { classLogger.addAppender(listAppender); return listAppender; } + + /** + * Tests that a case-mismatched enum name is still resolved. + */ + @Test + public void choiceFieldCaseMismatchCorrection() { + final Set parameters = ParametersBuilderUtil.getParametersFor( + MockCaseMismatchSchema.class, + MockCaseMismatchBinding.class + ); + + assertThat(parameters).as("The choice parameter descriptor should be resolved successfully").hasSize(1); + + final ModelParameter parameter = parameters.iterator().next(); + + assertThat(parameter.getName()).as("The parameter name should match the target field").isEqualTo("dummyField"); + + final ChoiceFieldType fieldType = (ChoiceFieldType) parameter.getFieldType(); + + assertThat(fieldType.getDefaultValue()).as("The enum 'VALUE_ONE' should match the schema's 'value_one' choice") + .isEqualTo("value_one"); + } + + /** + * Tests that when an enum field evaluates to null or does not match any valid choices, we fall back to the first + * available choice. + */ + @Test + public void choiceFieldNullOrMissingFallback() { + final ListAppender listAppender = appendLogger(ParametersBuilderUtil.class); + + final Set parameters = ParametersBuilderUtil.getParametersFor( + MockFallbackSchema.class, + MockFallbackBinding.class + ); + + assertThat(parameters).as("The parameter descriptor should be built using a fallback strategy").hasSize(1); + + final ModelParameter parameter = parameters.iterator().next(); + + assertThat(parameter.getName()).as("The parameter name should match the target field").isEqualTo("dummyField"); + + final ChoiceFieldType fieldType = (ChoiceFieldType) parameter.getFieldType(); + + assertThat(fieldType.getDefaultValue()).as( + "A null value should fall back safely to the first choice in the schema").isEqualTo("value_one"); + + final List loggingList = listAppender.list; + assertThat(loggingList).as("A fallback must log an explicit warning").isNotEmpty(); + + final ILoggingEvent warningEvent = loggingList.get(0); + assertThat(warningEvent.getLevel()).isEqualTo(Level.WARN); + assertThat(warningEvent.getMessage()).as("The warning message should detail the fallback replacement") + .containsIgnoringCase("is not present on possible values set"); + } + + /** + * Dummy enum to simulate parameter choices. + */ + public enum DummyEnum { + VALUE_ONE, VALUE_TWO; + } + + /** + * Schema where the annotation choices are lowercase. + */ + public static class MockCaseMismatchSchema extends water.api.schemas3.ModelParametersSchemaV3 { + @water.api.API(help = "Case mismatch", values = {"value_one", "value_two"}) + public DummyEnum dummyField; + } + + /** + * Fallback schema to be used when client binding POJO is null. + */ + public static class MockFallbackSchema extends water.api.schemas3.ModelParametersSchemaV3 { + @water.api.API(help = "Fallback", values = {"value_one"}) + public DummyEnum dummyField; + } + + /** + * Client binding POJO where the runtime field resolves to uppercase. + */ + public static class MockCaseMismatchBinding extends water.bindings.pojos.ModelParametersSchemaV3 { + public DummyEnum dummyField = DummyEnum.VALUE_ONE; + } + + /** + * Client binding POJO where the enum initializes as null. + */ + public static class MockFallbackBinding extends water.bindings.pojos.ModelParametersSchemaV3 { + public DummyEnum dummyField = null; + } }