Browse Source

[ML] Add multimodal distribution field processing for anomaly score explanation (#92978)

The companion PR to elastic/ml-cpp#2440 adds processing of multimodal_distribution field in the anomaly score explanation. I added a changelog entry in the ml-cpp PR hence I mark this PR as a non-issue.
Valeriy Khakhutskyy 2 years ago
parent
commit
c24712bfa7

+ 5 - 0
docs/reference/ml/anomaly-detection/apis/get-record.asciidoc

@@ -135,6 +135,11 @@ reduced. If the bucket contains fewer samples than expected, the score is reduce
 `lower_confidence_bound`::::
 (Optional, double) Lower bound of the 95% confidence interval.
 
+`multimodal_distribution`::::
+(Optional, boolean) Indicates whether the bucket values' probability distribution has 
+several modes. When there are multiple modes, the typical value may not be the most 
+likely.
+
 `multi_bucket_impact`::::
 (Optional, integer) Impact of the deviation between actual and typical values in the 
 past 12 buckets.

+ 25 - 2
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/results/AnomalyScoreExplanation.java

@@ -7,6 +7,7 @@
 
 package org.elasticsearch.xpack.core.ml.job.results;
 
+import org.elasticsearch.Version;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.Writeable;
@@ -31,6 +32,7 @@ public class AnomalyScoreExplanation implements ToXContentObject, Writeable {
     public static final ParseField UPPER_CONFIDENCE_BOUND = new ParseField("upper_confidence_bound");
     public static final ParseField HIGH_VARIANCE_PENALTY = new ParseField("high_variance_penalty");
     public static final ParseField INCOMPLETE_BUCKET_PENALTY = new ParseField("incomplete_bucket_penalty");
+    public static final ParseField MULTIMODAL_DISTRIBUTION = new ParseField("multimodal_distribution");
 
     public static final ObjectParser<AnomalyScoreExplanation, Void> STRICT_PARSER = createParser(false);
     public static final ObjectParser<AnomalyScoreExplanation, Void> LENIENT_PARSER = createParser(true);
@@ -51,6 +53,7 @@ public class AnomalyScoreExplanation implements ToXContentObject, Writeable {
         parser.declareDouble(AnomalyScoreExplanation::setUpperConfidenceBound, UPPER_CONFIDENCE_BOUND);
         parser.declareBoolean(AnomalyScoreExplanation::setHighVariancePenalty, HIGH_VARIANCE_PENALTY);
         parser.declareBoolean(AnomalyScoreExplanation::setIncompleteBucketPenalty, INCOMPLETE_BUCKET_PENALTY);
+        parser.declareBoolean(AnomalyScoreExplanation::setMultimodalDistribution, MULTIMODAL_DISTRIBUTION);
         return parser;
     }
 
@@ -64,6 +67,7 @@ public class AnomalyScoreExplanation implements ToXContentObject, Writeable {
     private Double upperConfidenceBound;
     private Boolean highVariancePenalty;
     private Boolean incompleteBucketPenalty;
+    private Boolean multimodalDistribution;
 
     AnomalyScoreExplanation() {}
 
@@ -78,6 +82,9 @@ public class AnomalyScoreExplanation implements ToXContentObject, Writeable {
         this.upperConfidenceBound = in.readOptionalDouble();
         this.highVariancePenalty = in.readOptionalBoolean();
         this.incompleteBucketPenalty = in.readOptionalBoolean();
+        if (in.getVersion().onOrAfter(Version.V_8_7_0)) {
+            this.multimodalDistribution = in.readOptionalBoolean();
+        }
     }
 
     @Override
@@ -92,6 +99,9 @@ public class AnomalyScoreExplanation implements ToXContentObject, Writeable {
         out.writeOptionalDouble(upperConfidenceBound);
         out.writeOptionalBoolean(highVariancePenalty);
         out.writeOptionalBoolean(incompleteBucketPenalty);
+        if (out.getVersion().onOrAfter(Version.V_8_7_0)) {
+            out.writeOptionalBoolean(multimodalDistribution);
+        }
     }
 
     @Override
@@ -127,6 +137,9 @@ public class AnomalyScoreExplanation implements ToXContentObject, Writeable {
         if (incompleteBucketPenalty != null) {
             builder.field(INCOMPLETE_BUCKET_PENALTY.getPreferredName(), incompleteBucketPenalty);
         }
+        if (multimodalDistribution != null) {
+            builder.field(MULTIMODAL_DISTRIBUTION.getPreferredName(), multimodalDistribution);
+        }
         builder.endObject();
         return builder;
     }
@@ -143,7 +156,8 @@ public class AnomalyScoreExplanation implements ToXContentObject, Writeable {
             typicalValue,
             upperConfidenceBound,
             highVariancePenalty,
-            incompleteBucketPenalty
+            incompleteBucketPenalty,
+            multimodalDistribution
         );
     }
 
@@ -166,7 +180,8 @@ public class AnomalyScoreExplanation implements ToXContentObject, Writeable {
             && Objects.equals(this.typicalValue, that.typicalValue)
             && Objects.equals(this.upperConfidenceBound, that.upperConfidenceBound)
             && Objects.equals(this.highVariancePenalty, that.highVariancePenalty)
-            && Objects.equals(this.incompleteBucketPenalty, that.incompleteBucketPenalty);
+            && Objects.equals(this.incompleteBucketPenalty, that.incompleteBucketPenalty)
+            && Objects.equals(this.multimodalDistribution, that.multimodalDistribution);
     }
 
     public String getAnomalyType() {
@@ -249,4 +264,12 @@ public class AnomalyScoreExplanation implements ToXContentObject, Writeable {
         this.incompleteBucketPenalty = incompleteBucketPenalty;
     }
 
+    public Boolean isMultimodalDistribution() {
+        return multimodalDistribution;
+    }
+
+    public void setMultimodalDistribution(Boolean multimodalDistribution) {
+        this.multimodalDistribution = multimodalDistribution;
+    }
+
 }

+ 1 - 0
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/results/ReservedFieldNames.java

@@ -95,6 +95,7 @@ public final class ReservedFieldNames {
         AnomalyScoreExplanation.UPPER_CONFIDENCE_BOUND.getPreferredName(),
         AnomalyScoreExplanation.HIGH_VARIANCE_PENALTY.getPreferredName(),
         AnomalyScoreExplanation.INCOMPLETE_BUCKET_PENALTY.getPreferredName(),
+        AnomalyScoreExplanation.MULTIMODAL_DISTRIBUTION.getPreferredName(),
 
         GeoResults.TYPICAL_POINT.getPreferredName(),
         GeoResults.ACTUAL_POINT.getPreferredName(),

+ 3 - 0
x-pack/plugin/core/src/main/resources/org/elasticsearch/xpack/core/ml/anomalydetection/results_index_mappings.json

@@ -271,6 +271,9 @@
         },
         "incomplete_bucket_penalty": {
           "type": "boolean"
+        },
+        "multimodal_distribution": {
+          "type": "boolean"
         }
       }
     },

+ 1 - 0
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/results/AnomalyRecordTests.java

@@ -100,6 +100,7 @@ public class AnomalyRecordTests extends AbstractXContentSerializingTestCase<Anom
             anomalyScoreExplanation.setUpperConfidenceBound(randomDouble());
             anomalyScoreExplanation.setHighVariancePenalty(randomBoolean());
             anomalyScoreExplanation.setIncompleteBucketPenalty(randomBoolean());
+            anomalyScoreExplanation.setMultimodalDistribution(randomBoolean());
         }
 
         return anomalyRecord;