blob: 38b774bbdedb9812bc7ebbd15a1a385edf102f43 [file] [log] [blame]
package org.apache.samoa.moa.classifiers.core.driftdetection;
/*
* #%L
* SAMOA
* %%
* Copyright (C) 2014 - 2015 Apache Software Foundation
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.apache.samoa.moa.AbstractMOAObject;
/**
* ADaptive sliding WINdow method. This method is a change detector and estimator. It keeps a variable-length window of
* recently seen items, with the property that the window has the maximal length statistically consistent with the
* hypothesis "there has been no change in the average value inside the window".
*
*
* @author Albert Bifet (abifet at cs dot waikato dot ac dot nz)
* @version $Revision: 7 $
*/
public class ADWIN extends AbstractMOAObject {
private class List extends AbstractMOAObject {
protected int count;
protected ListItem head;
protected ListItem tail;
public List() {
// post: initializes the list to be empty.
clear();
addToHead();
}
/* Interface Store Methods */
public int size() {
// post: returns the number of elements in the list.
return this.count;
}
public ListItem head() {
// post: returns the number of elements in the list.
return this.head;
}
public ListItem tail() {
// post: returns the number of elements in the list.
return this.tail;
}
public boolean isEmpty() {
// post: returns the true iff store is empty.
return (this.size() == 0);
}
public void clear() {
// post: clears the list so that it contains no elements.
this.head = null;
this.tail = null;
this.count = 0;
}
/* Interface List Methods */
public void addToHead() {
// pre: anObject is non-null
// post: the object is added to the beginning of the list
this.head = new ListItem(this.head, null);
if (this.tail == null) {
this.tail = this.head;
}
this.count++;
}
public void removeFromHead() {
// pre: list is not empty
// post: removes and returns first object from the list
// ListItem temp;
// temp = this.head;
this.head = this.head.next();
if (this.head != null) {
this.head.setPrevious(null);
} else {
this.tail = null;
}
this.count--;
}
public void addToTail() {
// pre: anObject is non-null
// post: the object is added at the end of the list
this.tail = new ListItem(null, this.tail);
if (this.head == null) {
this.head = this.tail;
}
this.count++;
}
public void removeFromTail() {
// pre: list is not empty
// post: the last object in the list is removed and returned
// ListItem temp;
// temp = this.tail;
this.tail = this.tail.previous();
if (this.tail == null) {
this.head = null;
} else {
this.tail.setNext(null);
}
this.count--;
// temp=null;
}
@Override
public void getDescription(StringBuilder sb, int indent) {
}
}
private class ListItem extends AbstractMOAObject {
protected ListItem next;
protected ListItem previous;
protected int bucketSizeRow = 0;
protected int MAXBUCKETS = ADWIN.MAXBUCKETS;
protected double bucketTotal[] = new double[MAXBUCKETS + 1];
protected double bucketVariance[] = new double[MAXBUCKETS + 1];
public ListItem() {
// post: initializes the node to be a tail node
// containing the given value.
this(null, null);
}
public void clear() {
bucketSizeRow = 0;
for (int k = 0; k <= MAXBUCKETS; k++) {
clearBucket(k);
}
}
private void clearBucket(int k) {
setTotal(0, k);
setVariance(0, k);
}
public ListItem(ListItem nextNode, ListItem previousNode) {
// post: initializes the node to contain the given
// object and link to the given next node.
// this.data = element;
this.next = nextNode;
this.previous = previousNode;
if (nextNode != null) {
nextNode.previous = this;
}
if (previousNode != null) {
previousNode.next = this;
}
clear();
}
public void insertBucket(double Value, double Variance) {
// insert a Bucket at the end
int k = bucketSizeRow;
bucketSizeRow++;
// Insert new bucket
setTotal(Value, k);
setVariance(Variance, k);
}
public void RemoveBucket() {
// Removes the first Buvket
compressBucketsRow(1);
}
public void compressBucketsRow(int NumberItemsDeleted) {
// Delete first elements
for (int k = NumberItemsDeleted; k <= MAXBUCKETS; k++) {
bucketTotal[k - NumberItemsDeleted] = bucketTotal[k];
bucketVariance[k - NumberItemsDeleted] = bucketVariance[k];
}
for (int k = 1; k <= NumberItemsDeleted; k++) {
clearBucket(MAXBUCKETS - k + 1);
}
bucketSizeRow -= NumberItemsDeleted;
// BucketNumber-=NumberItemsDeleted;
}
public ListItem previous() {
// post: returns the previous node.
return this.previous;
}
public void setPrevious(ListItem previous) {
// post: sets the previous node to be the given node
this.previous = previous;
}
public ListItem next() {
// post: returns the next node.
return this.next;
}
public void setNext(ListItem next) {
// post: sets the next node to be the given node
this.next = next;
}
public double Total(int k) {
// post: returns the element in this node
return bucketTotal[k];
}
public double Variance(int k) {
// post: returns the element in this node
return bucketVariance[k];
}
public void setTotal(double value, int k) {
// post: sets the element in this node to the given
// object.
bucketTotal[k] = value;
}
public void setVariance(double value, int k) {
// post: sets the element in this node to the given
// object.
bucketVariance[k] = value;
}
/*
* public ListItem(Object element, ListItem nextNode){ // post: initializes
* the node to contain the given // object and link to the given next node.
* this.data = element; this.next = nextNode; } public ListItem(Object
* element) { // post: initializes the node to be a tail node // containing
* the given value. this(element, null); }
*
*
* public Object value() { // post: returns the element in this node return
* this.data; } public void setValue(Object anObject) { // post: sets the
* element in this node to the given // object. this.data = anObject; }
*/
@Override
public void getDescription(StringBuilder sb, int indent) {
}
}
public static final double DELTA = .002; // .1;
private static final int mintMinimLongitudWindow = 10; // 10
private double mdbldelta = .002; // .1;
private int mintTime = 0;
private int mintClock = 32;
private double mdblWidth = 0; // Mean of Width = mdblWidth/Number of items
// BUCKET
public static final int MAXBUCKETS = 5;
private int lastBucketRow = 0;
private double TOTAL = 0;
private double VARIANCE = 0;
private int WIDTH = 0;
private int BucketNumber = 0;
private int Detect = 0;
private int numberDetections = 0;
private int DetectTwice = 0;
private boolean blnBucketDeleted = false;
private int BucketNumberMAX = 0;
private int mintMinWinLength = 5;
private List listRowBuckets;
public boolean getChange() {
return blnBucketDeleted;
}
public void resetChange() {
blnBucketDeleted = false;
}
public int getBucketsUsed() {
return BucketNumberMAX;
}
public int getWidth() {
return WIDTH;
}
public void setClock(int intClock) {
mintClock = intClock;
}
public int getClock() {
return mintClock;
}
public boolean getWarning() {
return false;
}
public boolean getDetect() {
return (Detect == mintTime);
}
public int getNumberDetections() {
return numberDetections;
}
public double getTotal() {
return TOTAL;
}
public double getEstimation() {
return TOTAL / WIDTH;
}
public double getVariance() {
return VARIANCE / WIDTH;
}
public double getWidthT() {
return mdblWidth;
}
private void initBuckets() {
// Init buckets
listRowBuckets = new List();
lastBucketRow = 0;
TOTAL = 0;
VARIANCE = 0;
WIDTH = 0;
BucketNumber = 0;
}
private void insertElement(double Value) {
WIDTH++;
insertElementBucket(0, Value, listRowBuckets.head());
double incVariance = 0;
if (WIDTH > 1) {
incVariance = (WIDTH - 1) * (Value - TOTAL / (WIDTH - 1)) * (Value - TOTAL / (WIDTH - 1)) / WIDTH;
}
VARIANCE += incVariance;
TOTAL += Value;
compressBuckets();
}
private void insertElementBucket(double Variance, double Value, ListItem Node) {
// Insert new bucket
Node.insertBucket(Value, Variance);
BucketNumber++;
if (BucketNumber > BucketNumberMAX) {
BucketNumberMAX = BucketNumber;
}
}
private int bucketSize(int Row) {
return (int) Math.pow(2, Row);
}
public int deleteElement() {
// LIST
// Update statistics
ListItem Node;
Node = listRowBuckets.tail();
int n1 = bucketSize(lastBucketRow);
WIDTH -= n1;
TOTAL -= Node.Total(0);
double u1 = Node.Total(0) / n1;
double incVariance = Node.Variance(0) + n1 * WIDTH * (u1 - TOTAL / WIDTH) * (u1 - TOTAL / WIDTH) / (n1 + WIDTH);
VARIANCE -= incVariance;
// Delete Bucket
Node.RemoveBucket();
BucketNumber--;
if (Node.bucketSizeRow == 0) {
listRowBuckets.removeFromTail();
lastBucketRow--;
}
return n1;
}
public void compressBuckets() {
// Traverse the list of buckets in increasing order
int n1, n2;
double u2, u1, incVariance;
ListItem cursor;
ListItem nextNode;
cursor = listRowBuckets.head();
int i = 0;
do {
// Find the number of buckets in a row
int k = cursor.bucketSizeRow;
// If the row is full, merge buckets
if (k == MAXBUCKETS + 1) {
nextNode = cursor.next();
if (nextNode == null) {
listRowBuckets.addToTail();
nextNode = cursor.next();
lastBucketRow++;
}
n1 = bucketSize(i);
n2 = bucketSize(i);
u1 = cursor.Total(0) / n1;
u2 = cursor.Total(1) / n2;
incVariance = n1 * n2 * (u1 - u2) * (u1 - u2) / (n1 + n2);
nextNode.insertBucket(cursor.Total(0) + cursor.Total(1), cursor.Variance(0) + cursor.Variance(1) + incVariance);
BucketNumber++;
cursor.compressBucketsRow(2);
if (nextNode.bucketSizeRow <= MAXBUCKETS) {
break;
}
} else {
break;
}
cursor = cursor.next();
i++;
} while (cursor != null);
}
public boolean setInput(double intEntrada) {
return setInput(intEntrada, mdbldelta);
}
public boolean setInput(double intEntrada, double delta) {
boolean blnChange = false;
boolean blnExit;
ListItem cursor;
mintTime++;
// 1,2)Increment window in one element
insertElement(intEntrada);
blnBucketDeleted = false;
// 3)Reduce window
if (mintTime % mintClock == 0 && getWidth() > mintMinimLongitudWindow) {
boolean blnReduceWidth = true; // Diference
while (blnReduceWidth) // Diference
{
blnReduceWidth = false; // Diference
blnExit = false;
int n0 = 0;
int n1 = WIDTH;
double u0 = 0;
double u1 = getTotal();
double v0 = 0;
double v1 = VARIANCE;
double n2;
double u2;
cursor = listRowBuckets.tail();
int i = lastBucketRow;
do {
for (int k = 0; k <= (cursor.bucketSizeRow - 1); k++) {
n2 = bucketSize(i);
u2 = cursor.Total(k);
if (n0 > 0) {
v0 += cursor.Variance(k) + (double) n0 * n2 * (u0 / n0 - u2 / n2) * (u0 / n0 - u2 / n2) / (n0 + n2);
}
if (n1 > 0) {
v1 -= cursor.Variance(k) + (double) n1 * n2 * (u1 / n1 - u2 / n2) * (u1 / n1 - u2 / n2) / (n1 + n2);
}
n0 += bucketSize(i);
n1 -= bucketSize(i);
u0 += cursor.Total(k);
u1 -= cursor.Total(k);
if (i == 0 && k == cursor.bucketSizeRow - 1) {
blnExit = true;
break;
}
double absvalue = (u0 / n0) - (u1 / n1); // n1<WIDTH-mintMinWinLength-1
if ((n1 > mintMinWinLength + 1 && n0 > mintMinWinLength + 1) && // Diference NEGATIVE if(
blnCutexpression(n0, n1, u0, u1, v0, v1, absvalue, delta)) {
blnBucketDeleted = true;
Detect = mintTime;
if (Detect == 0) {
Detect = mintTime;
// blnFirst=true;
// blnWarning=true;
} else if (DetectTwice == 0) {
DetectTwice = mintTime;
// blnDetect=true;
}
blnReduceWidth = true; // Diference
blnChange = true;
if (getWidth() > 0) { // Reduce width of the window
// while (n0>0) // Diference NEGATIVE
n0 -= deleteElement();
blnExit = true;
break;
}
} // End if
}// Next k
cursor = cursor.previous();
i--;
} while (((!blnExit && cursor != null)));
}// End While // Diference
}// End if
mdblWidth += getWidth();
if (blnChange) {
numberDetections++;
}
return blnChange;
}
private boolean blnCutexpression(int n0, int n1, double u0, double u1, double v0, double v1, double absvalue,
double delta) {
int n = getWidth();
double dd = Math.log(2 * Math.log(n) / delta); // -- ull perque el ln n va al numerador.
// Formula Gener 2008
double v = getVariance();
double m = ((double) 1 / ((n0 - mintMinWinLength + 1))) + ((double) 1 / ((n1 - mintMinWinLength + 1)));
double epsilon = Math.sqrt(2 * m * v * dd) + (double) 2 / 3 * dd * m;
return (Math.abs(absvalue) > epsilon);
}
public ADWIN() {
mdbldelta = DELTA;
initBuckets();
Detect = 0;
numberDetections = 0;
DetectTwice = 0;
}
public ADWIN(double d) {
mdbldelta = d;
initBuckets();
Detect = 0;
numberDetections = 0;
DetectTwice = 0;
}
public ADWIN(int cl) {
mdbldelta = DELTA;
initBuckets();
Detect = 0;
numberDetections = 0;
DetectTwice = 0;
mintClock = cl;
}
public String getEstimatorInfo() {
return "ADWIN;;";
}
public void setW(int W0) {
}
@Override
public void getDescription(StringBuilder sb, int indent) {
}
}