Skip to content

Commit 0c5b4b9

Browse files
authored
Update RCF to v3.8 and Enable Auto AD with 'Alert Once' Option (#979)
* Update RCF to v3.8 and Enable Auto AD with 'Alert Once' Option This PR added support for automatic Anomaly Detection (AD) and the 'Alert Once' option introduced in RCF 3.8. Testing done: 1. Deserialization Test: * Verified model deserialization from 3.0-rc3. * Ensured consistent scoring using the rc3 checkpoint and rc3 dependency on identical data. 2. Backward Compatibility Test: * Executed a mixed cluster with versions 2.10 and 3.0. * Validated that older detectors still produce results without throwing any exceptions in a blue-green simulation scenario. Signed-off-by: Kaituo Li <kaituo@amazon.com> * reduce recall since alertOnce reduced recall Signed-off-by: Kaituo Li <kaituo@amazon.com> * remove commented out code Signed-off-by: Kaituo Li <kaituo@amazon.com> --------- Signed-off-by: Kaituo Li <kaituo@amazon.com>
1 parent cadc9bb commit 0c5b4b9

12 files changed

+380
-24
lines changed

build.gradle

+3-3
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ dependencies {
126126
implementation group: 'com.yahoo.datasketches', name: 'memory', version: '0.12.2'
127127
implementation group: 'commons-lang', name: 'commons-lang', version: '2.6'
128128
implementation group: 'org.apache.commons', name: 'commons-pool2', version: '2.10.0'
129-
implementation 'software.amazon.randomcutforest:randomcutforest-serialization:3.0-rc3'
130-
implementation 'software.amazon.randomcutforest:randomcutforest-parkservices:3.0-rc3'
131-
implementation 'software.amazon.randomcutforest:randomcutforest-core:3.0-rc3'
129+
implementation 'software.amazon.randomcutforest:randomcutforest-serialization:3.8.0'
130+
implementation 'software.amazon.randomcutforest:randomcutforest-parkservices:3.8.0'
131+
implementation 'software.amazon.randomcutforest:randomcutforest-core:3.8.0'
132132

133133
// we inherit jackson-core from opensearch core
134134
implementation "com.fasterxml.jackson.core:jackson-databind:2.14.1"

src/main/java/org/opensearch/ad/ml/EntityColdStarter.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.opensearch.timeseries.util.ExceptionUtil;
6262

6363
import com.amazon.randomcutforest.config.Precision;
64+
import com.amazon.randomcutforest.config.TransformMethod;
6465
import com.amazon.randomcutforest.parkservices.ThresholdedRandomCutForest;
6566

6667
/**
@@ -375,17 +376,18 @@ private void trainModelFromDataSegments(
375376
// overlapping x3, x4, and only store x5, x6.
376377
.shingleSize(shingleSize)
377378
.internalShinglingEnabled(true)
378-
.anomalyRate(1 - this.thresholdMinPvalue);
379+
.anomalyRate(1 - this.thresholdMinPvalue)
380+
.transformMethod(TransformMethod.NORMALIZE)
381+
.alertOnce(true)
382+
.autoAdjust(true);
379383

380384
if (rcfSeed > 0) {
381385
rcfBuilder.randomSeed(rcfSeed);
382386
}
383387
ThresholdedRandomCutForest trcf = new ThresholdedRandomCutForest(rcfBuilder);
384-
385388
while (!dataPoints.isEmpty()) {
386389
trcf.process(dataPoints.poll(), 0);
387390
}
388-
389391
EntityModel model = entityState.getModel();
390392
if (model == null) {
391393
model = new EntityModel(entity, new ArrayDeque<>(), null);

src/main/java/org/opensearch/ad/ml/ModelManager.java

+9
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151

5252
import com.amazon.randomcutforest.RandomCutForest;
5353
import com.amazon.randomcutforest.config.Precision;
54+
import com.amazon.randomcutforest.config.TransformMethod;
5455
import com.amazon.randomcutforest.parkservices.AnomalyDescriptor;
5556
import com.amazon.randomcutforest.parkservices.ThresholdedRandomCutForest;
5657

@@ -532,6 +533,10 @@ private void trainModelForStep(
532533
.boundingBoxCacheFraction(TimeSeriesSettings.REAL_TIME_BOUNDING_BOX_CACHE_RATIO)
533534
.shingleSize(detector.getShingleSize())
534535
.anomalyRate(1 - thresholdMinPvalue)
536+
.transformMethod(TransformMethod.NORMALIZE)
537+
.alertOnce(true)
538+
.autoAdjust(true)
539+
.internalShinglingEnabled(false)
535540
.build();
536541
Arrays.stream(dataPoints).forEach(s -> trcf.process(s, 0));
537542

@@ -622,6 +627,10 @@ public List<ThresholdingResult> getPreviewResults(double[][] dataPoints, int shi
622627
.boundingBoxCacheFraction(AnomalyDetectorSettings.BATCH_BOUNDING_BOX_CACHE_RATIO)
623628
.shingleSize(shingleSize)
624629
.anomalyRate(1 - this.thresholdMinPvalue)
630+
.transformMethod(TransformMethod.NORMALIZE)
631+
.alertOnce(true)
632+
.autoAdjust(true)
633+
.internalShinglingEnabled(false)
625634
.build();
626635
return Arrays.stream(dataPoints).map(point -> {
627636
AnomalyDescriptor descriptor = trcf.process(point, 0);

src/main/java/org/opensearch/ad/task/ADBatchTaskCache.java

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.opensearch.timeseries.model.Entity;
3131

3232
import com.amazon.randomcutforest.config.Precision;
33+
import com.amazon.randomcutforest.config.TransformMethod;
3334
import com.amazon.randomcutforest.parkservices.ThresholdedRandomCutForest;
3435

3536
/**
@@ -80,6 +81,10 @@ protected ADBatchTaskCache(ADTask adTask) {
8081
.boundingBoxCacheFraction(AnomalyDetectorSettings.BATCH_BOUNDING_BOX_CACHE_RATIO)
8182
.shingleSize(shingleSize)
8283
.anomalyRate(1 - AnomalyDetectorSettings.THRESHOLD_MIN_PVALUE)
84+
.transformMethod(TransformMethod.NORMALIZE)
85+
.alertOnce(true)
86+
.autoAdjust(true)
87+
.internalShinglingEnabled(false)
8388
.build();
8489

8590
this.thresholdModelTrained = false;

src/test/java/org/opensearch/ad/MemoryTrackerTests.java

+31
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.opensearch.timeseries.settings.TimeSeriesSettings;
3434

3535
import com.amazon.randomcutforest.config.Precision;
36+
import com.amazon.randomcutforest.config.TransformMethod;
3637
import com.amazon.randomcutforest.parkservices.ThresholdedRandomCutForest;
3738

3839
public class MemoryTrackerTests extends OpenSearchTestCase {
@@ -109,6 +110,9 @@ public void setUp() throws Exception {
109110
.boundingBoxCacheFraction(TimeSeriesSettings.REAL_TIME_BOUNDING_BOX_CACHE_RATIO)
110111
.shingleSize(shingleSize)
111112
.internalShinglingEnabled(true)
113+
.transformMethod(TransformMethod.NORMALIZE)
114+
.alertOnce(true)
115+
.autoAdjust(true)
112116
.build();
113117

114118
detector = mock(AnomalyDetector.class);
@@ -152,6 +156,9 @@ public void testEstimateModelSize() {
152156
.internalShinglingEnabled(true)
153157
// same with dimension for opportunistic memory saving
154158
.shingleSize(shingleSize)
159+
.transformMethod(TransformMethod.NORMALIZE)
160+
.alertOnce(true)
161+
.autoAdjust(true)
155162
.build();
156163
assertEquals(603708, tracker.estimateTRCFModelSize(rcf2));
157164
assertTrue(tracker.isHostingAllowed(detectorId, rcf2));
@@ -171,6 +178,9 @@ public void testEstimateModelSize() {
171178
.internalShinglingEnabled(false)
172179
// same with dimension for opportunistic memory saving
173180
.shingleSize(1)
181+
.transformMethod(TransformMethod.NORMALIZE)
182+
.alertOnce(true)
183+
.autoAdjust(true)
174184
.build();
175185
assertEquals(1685208, tracker.estimateTRCFModelSize(rcf3));
176186

@@ -188,6 +198,9 @@ public void testEstimateModelSize() {
188198
.internalShinglingEnabled(true)
189199
// same with dimension for opportunistic memory saving
190200
.shingleSize(1)
201+
.transformMethod(TransformMethod.NORMALIZE)
202+
.alertOnce(true)
203+
.autoAdjust(true)
191204
.build();
192205
assertEquals(521304, tracker.estimateTRCFModelSize(rcf4));
193206

@@ -205,6 +218,9 @@ public void testEstimateModelSize() {
205218
.internalShinglingEnabled(true)
206219
// same with dimension for opportunistic memory saving
207220
.shingleSize(2)
221+
.transformMethod(TransformMethod.NORMALIZE)
222+
.alertOnce(true)
223+
.autoAdjust(true)
208224
.build();
209225
assertEquals(467340, tracker.estimateTRCFModelSize(rcf5));
210226

@@ -222,6 +238,9 @@ public void testEstimateModelSize() {
222238
.internalShinglingEnabled(true)
223239
// same with dimension for opportunistic memory saving
224240
.shingleSize(4)
241+
.transformMethod(TransformMethod.NORMALIZE)
242+
.alertOnce(true)
243+
.autoAdjust(true)
225244
.build();
226245
assertEquals(603676, tracker.estimateTRCFModelSize(rcf6));
227246

@@ -239,6 +258,9 @@ public void testEstimateModelSize() {
239258
.internalShinglingEnabled(true)
240259
// same with dimension for opportunistic memory saving
241260
.shingleSize(16)
261+
.transformMethod(TransformMethod.NORMALIZE)
262+
.alertOnce(true)
263+
.autoAdjust(true)
242264
.build();
243265
assertEquals(401481, tracker.estimateTRCFModelSize(rcf7));
244266

@@ -256,6 +278,9 @@ public void testEstimateModelSize() {
256278
.internalShinglingEnabled(true)
257279
// same with dimension for opportunistic memory saving
258280
.shingleSize(32)
281+
.transformMethod(TransformMethod.NORMALIZE)
282+
.alertOnce(true)
283+
.autoAdjust(true)
259284
.build();
260285
assertEquals(1040432, tracker.estimateTRCFModelSize(rcf8));
261286

@@ -273,6 +298,9 @@ public void testEstimateModelSize() {
273298
.internalShinglingEnabled(true)
274299
// same with dimension for opportunistic memory saving
275300
.shingleSize(64)
301+
.transformMethod(TransformMethod.NORMALIZE)
302+
.alertOnce(true)
303+
.autoAdjust(true)
276304
.build();
277305
assertEquals(1040688, tracker.estimateTRCFModelSize(rcf9));
278306

@@ -290,6 +318,9 @@ public void testEstimateModelSize() {
290318
.internalShinglingEnabled(true)
291319
// same with dimension for opportunistic memory saving
292320
.shingleSize(65)
321+
.transformMethod(TransformMethod.NORMALIZE)
322+
.alertOnce(true)
323+
.autoAdjust(true)
293324
.build();
294325
expectThrows(IllegalArgumentException.class, () -> tracker.estimateTRCFModelSize(rcf10));
295326
}

src/test/java/org/opensearch/ad/e2e/SingleStreamModelPerfIT.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public void testDataset() throws Exception {
4747
// TODO: this test case will run for a much longer time and timeout with security enabled
4848
if (!isHttps()) {
4949
disableResourceNotFoundFaultTolerence();
50-
verifyAnomaly("synthetic", 1, 1500, 8, .4, .9, 10);
50+
verifyAnomaly("synthetic", 1, 1500, 8, .4, .7, 10);
5151
}
5252
}
5353

@@ -96,7 +96,7 @@ private void verifyTestResults(
9696

9797
// recall = windows containing predicted anomaly points / total anomaly windows
9898
double recall = anomalies.size() > 0 ? positiveAnomalies / anomalies.size() : 1;
99-
assertTrue(recall >= minRecall);
99+
assertTrue(String.format(Locale.ROOT, "recall should be %f but got %f", recall, minRecall), recall >= minRecall);
100100

101101
assertTrue(errors <= maxError);
102102
LOG.info("Precision: {}, Window recall: {}", precision, recall);

0 commit comments

Comments
 (0)