Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

=== Enhancements

* Add identification of multimodal distribution to anomaly explanations. (See {ml-pull}2440[#2440].)
* Upgrade PyTorch to version 1.13.1. (See {ml-pull}2430[#2430].)

== {es} version 8.6.0
Expand Down
5 changes: 4 additions & 1 deletion include/maths/common/CModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ struct MATHS_COMMON_EXPORT SAnomalyScoreExplanation {
double s_UpperConfidenceBound{std::numeric_limits<double>::quiet_NaN()};
bool s_HighVariancePenalty{false};
bool s_IncompleteBucketPenalty{false};
bool s_MultimodalDistribution{false};

std::string print() const {
return "Anomaly Score Explanation:\ntype: " + std::to_string(s_AnomalyType) +
Expand All @@ -273,7 +274,9 @@ struct MATHS_COMMON_EXPORT SAnomalyScoreExplanation {
"\n high variance penalty: " +
std::to_string(static_cast<int>(s_HighVariancePenalty)) +
", incomplete bucket_penalty: " +
std::to_string(static_cast<int>(s_IncompleteBucketPenalty));
std::to_string(static_cast<int>(s_IncompleteBucketPenalty)) +
", is multimodal distribution" +
std::to_string(static_cast<int>(s_MultimodalDistribution));
}
};

Expand Down
3 changes: 3 additions & 0 deletions include/maths/common/COneOfNPrior.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ class MATHS_COMMON_EXPORT COneOfNPrior : public CPrior {
//! \note \p numberSamples is truncated to the number of samples received.
void sampleMarginalLikelihood(std::size_t numberSamples, TDouble1Vec& samples) const override;

//! Determines whether the selected distribution is multimodal.
bool isSelectedModelMultimodal() const override;

private:
//! The common c.d.f. implementation.
bool minusLogJointCdfImpl(bool complement,
Expand Down
3 changes: 3 additions & 0 deletions include/maths/common/CPrior.h
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,9 @@ class MATHS_COMMON_EXPORT CPrior {
TDouble1Vec& resamples,
TDoubleWeightsAry1Vec& resamplesWeights) const;

//! Determines whether the selected distribution is multimodal.
virtual bool isSelectedModelMultimodal() const;

protected:
//! \brief Defines a set of operations to adjust the offset parameter
//! of those priors with non-negative support.
Expand Down
6 changes: 6 additions & 0 deletions lib/api/CJsonOutputWriter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const std::string TYPICAL_VALUE("typical_value");
const std::string UPPER_CONFIDENCE_BOUND("upper_confidence_bound");
const std::string HIGH_VARIANCE_PENALTY("high_variance_penalty");
const std::string INCOMPLETE_BUCKET_PENALTY("incomplete_bucket_penalty");
const std::string MULTIMODAL_DISTRIBUTION("multimodal_distribution");

//! Get a numeric field from a JSON document.
//! Assumes the document contains the field.
Expand Down Expand Up @@ -1001,6 +1002,11 @@ void CJsonOutputWriter::writeAnomalyScoreExplanationObject(
results.s_AnomalyScoreExplanation.s_IncompleteBucketPenalty,
anomalyScoreExplanation);
}
if (results.s_AnomalyScoreExplanation.s_MultimodalDistribution) {
m_Writer.addBoolFieldToObj(MULTIMODAL_DISTRIBUTION,
results.s_AnomalyScoreExplanation.s_MultimodalDistribution,
anomalyScoreExplanation);
}
}
}
}
10 changes: 10 additions & 0 deletions lib/maths/common/COneOfNPrior.cc
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,16 @@ void COneOfNPrior::sampleMarginalLikelihood(std::size_t numberSamples,
LOG_TRACE(<< "samples = " << samples);
}

bool COneOfNPrior::isSelectedModelMultimodal() const {
auto weights = this->weights();
for (std::size_t i = 0; i < weights.size(); ++i) {
if (weights[i] > 0.1 && m_Models[i].second->type() == EPrior::E_Multimodal) {
return true;
}
}
return false;
}

bool COneOfNPrior::minusLogJointCdfImpl(bool complement,
const TDouble1Vec& samples,
const TDoubleWeightsAry1Vec& weights,
Expand Down
5 changes: 5 additions & 0 deletions lib/maths/common/CPrior.cc
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,11 @@ CPrior::TStrStrPr CPrior::printMarginalLikelihoodStatistics() const {

return this->doPrintMarginalLikelihoodStatistics();
}

bool CPrior::isSelectedModelMultimodal() const {
return false;
}

const double CPrior::FALLBACK_DECAY_RATE = 0.001;
const std::size_t CPrior::ADJUST_OFFSET_SAMPLE_SIZE = 50;
const std::string CPrior::UNKNOWN_VALUE_STRING = "<unknown>";
Expand Down
3 changes: 3 additions & 0 deletions lib/maths/common/unittest/COneOfNPriorTest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ BOOST_AUTO_TEST_CASE(testModelSelection) {

BOOST_TEST_REQUIRE(logWeightRatio > expectedLogWeightRatio);
BOOST_TEST_REQUIRE(logWeightRatio < 0.95 * expectedLogWeightRatio);
BOOST_REQUIRE_EQUAL(filter.isSelectedModelMultimodal(), false);
}

{
Expand Down Expand Up @@ -485,6 +486,7 @@ BOOST_AUTO_TEST_CASE(testModelSelection) {

BOOST_TEST_REQUIRE(logWeightRatio > expectedLogWeightRatio);
BOOST_TEST_REQUIRE(logWeightRatio < 0.75 * expectedLogWeightRatio);
BOOST_REQUIRE_EQUAL(filter.isSelectedModelMultimodal(), false);
}
{
// Check we correctly select the multimodal model when the data have
Expand Down Expand Up @@ -522,6 +524,7 @@ BOOST_AUTO_TEST_CASE(testModelSelection) {

LOG_DEBUG(<< "logWeightRatio = " << logWeightRatio);
BOOST_TEST_REQUIRE(std::exp(logWeightRatio) < 1e-6);
BOOST_REQUIRE_EQUAL(filter.isSelectedModelMultimodal(), true);
}
}

Expand Down
5 changes: 5 additions & 0 deletions lib/maths/time_series/CTimeSeriesModel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,11 @@ bool CUnivariateTimeSeriesModel::uncorrelatedProbability(
result.s_AnomalyScoreExplanation.s_UpperConfidenceBound = interval[2][0];
}

result.s_AnomalyScoreExplanation.s_MultimodalDistribution =
m_ResidualModel->isSelectedModelMultimodal();
LOG_DEBUG(<< "Multimodel distribution: "
<< result.s_AnomalyScoreExplanation.s_MultimodalDistribution);

result.s_Probability = pOverall;
result.s_FeatureProbabilities = std::move(featureProbabilities);
result.s_Tail = {tail};
Expand Down