/*
 * Copyright 2015, Yahoo Inc.
 * Copyrights licensed under the GPL License.
 * See the accompanying LICENSE file for terms.
 */

// A simple thresholding model that returns an anomaly if it is above/below a certain threashold.

package com.yahoo.egads.models.adm;

import java.util.Properties;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;

import com.yahoo.egads.data.Anomaly.IntervalSequence;
import com.yahoo.egads.data.Anomaly.Interval;
import com.yahoo.egads.data.AnomalyErrorStorage;
import com.yahoo.egads.data.TimeSeries.DataSequence;
import com.yahoo.egads.utilities.DBSCANClusterer;

import org.apache.commons.math3.ml.clustering.Cluster;

import com.yahoo.egads.utilities.IdentifiedDoublePoint;

import org.apache.commons.math3.ml.distance.EuclideanDistance;
import org.json.JSONObject;
import org.json.JSONStringer;

public class DBScanModel extends AnomalyDetectionAbstractModel {

    // The constructor takes a set of properties
    // needed for the simple model. This includes the sensitivity.
    private Map threshold;
    private int maxHrsAgo;
    private long windowStart;
    // modelName.
    public String modelName = "DBScanModel";
    public AnomalyErrorStorage aes = new AnomalyErrorStorage();
    private DBSCANClusterer dbscan = null;
    private int minPoints = 2;
    private double eps = 500;
    
    public DBScanModel(Properties config) {
        super(config);
       
        if (config.getProperty("MAX_ANOMALY_TIME_AGO") == null) {
            throw new IllegalArgumentException("MAX_ANOMALY_TIME_AGO is NULL");
        }
        this.maxHrsAgo = new Integer(config.getProperty("MAX_ANOMALY_TIME_AGO"));

        this.windowStart = new Long(config.getProperty("DETECTION_WINDOW_START_TIME"));

        this.threshold = parseMap(config.getProperty("THRESHOLD"));
            
        if (config.getProperty("THRESHOLD") != null && this.threshold.isEmpty() == true) {
            throw new IllegalArgumentException("THRESHOLD PARSE ERROR");
        } 
    }

    public void toJson(JSONStringer json_out) {

    }

    public void fromJson(JSONObject json_obj) {

    }

    public String getModelName() {
        return modelName;
    }

    @Override
    public String getType() {
        return "point_outlier";
    }

    @Override
    public void reset() {
        // At this point, reset does nothing.
    }

    @Override
    public void tune(DataSequence observedSeries,
                     DataSequence expectedSeries,
                     IntervalSequence anomalySequence) throws Exception {
        // Compute the time-series of errors.
        HashMap> allErrors = aes.initAnomalyErrors(observedSeries, expectedSeries);
        List points = new ArrayList();
        EuclideanDistance ed = new EuclideanDistance();
        int n = observedSeries.size();
        
        for (int i = 0; i < n; i++) {
            double[] d = new double[(aes.getIndexToError().keySet()).size()];
           
            for (int e = 0; e < (aes.getIndexToError().keySet()).size(); e++) {
                 d[e] = allErrors.get(aes.getIndexToError().get(e)).get(i);
            }
            points.add(new IdentifiedDoublePoint(d, i));
        }
        
        double sum = 0.0;
        double count = 0.0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                sum += ed.compute(points.get(i).getPoint(), points.get(j).getPoint());
                count++;
            }
        }
        eps = ((double) this.sDAutoSensitivity) * (sum / count);   
        minPoints = ((int) Math.ceil(((double) this.amntAutoSensitivity) * ((double) n)));     
        dbscan = new DBSCANClusterer(eps, minPoints);
    }
  
    @Override
    public IntervalSequence detect(DataSequence observedSeries,
                                   DataSequence expectedSeries) throws Exception {
        
        IntervalSequence output = new IntervalSequence();
        int n = observedSeries.size();
        // Get an array of thresholds.
        Float[] thresholdErrors = new Float[aes.getErrorToIndex().size()];
        for (Map.Entry entry : this.threshold.entrySet()) {
            thresholdErrors[aes.getErrorToIndex().get(entry.getKey())] = Math.abs(entry.getValue());
        }
        
        // Compute the time-series of errors.
        HashMap> allErrors = aes.initAnomalyErrors(observedSeries, expectedSeries);
        List points = new ArrayList();
        
        for (int i = 0; i < n; i++) {
            double[] d = new double[(aes.getIndexToError().keySet()).size()];
           
            for (int e = 0; e < (aes.getIndexToError().keySet()).size(); e++) {
                 d[e] = allErrors.get(aes.getIndexToError().get(e)).get(i);
            }
            points.add(new IdentifiedDoublePoint(d, i));
        }
        
        List> cluster = dbscan.cluster(points);
        for(Cluster c: cluster) {
            for (IdentifiedDoublePoint p : c.getPoints()) {
            	int i = p.getId();
                Float[] errors = aes.computeErrorMetrics(expectedSeries.get(p.getId()).value, observedSeries.get(p.getId()).value);
                logger.debug("TS:" + observedSeries.get(i).time + ",E:" + arrayF2S(errors) + ",TE:" + arrayF2S(thresholdErrors) + ",OV:" + observedSeries.get(i).value + ",EV:" + expectedSeries.get(i).value);
                if (observedSeries.get(p.getId()).value != expectedSeries.get(p.getId()).value &&
                    (isDetectionWindowPoint(maxHrsAgo, windowStart, observedSeries.get(p.getId()).time, observedSeries.get(0).time) ||
                    (maxHrsAgo == 0 && p.getId() == (n - 1)))) {
                    output.add(new Interval(observedSeries.get(p.getId()).time,
                    		                p.getId(), 
                                            errors,
                                            thresholdErrors,
                                            observedSeries.get(p.getId()).value,
                                            expectedSeries.get(p.getId()).value));
                }
            }
        }

        return output;
    }
}

 

Ads help maintain this website.