| package com.yahoo.labs.samoa.moa.streams.clustering; |
| |
| /* |
| * #%L |
| * SAMOA |
| * %% |
| * Copyright (C) 2010 RWTH Aachen University, Germany |
| * %% |
| * 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 java.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Enumeration; |
| import java.util.LinkedList; |
| import java.util.Random; |
| import java.util.Vector; |
| |
| import com.yahoo.labs.samoa.moa.cluster.Clustering; |
| import com.yahoo.labs.samoa.moa.cluster.SphereCluster; |
| import com.yahoo.labs.samoa.moa.core.AutoExpandVector; |
| import com.yahoo.labs.samoa.moa.core.InstanceExample; |
| import com.yahoo.labs.samoa.instances.InstancesHeader; |
| import com.yahoo.labs.samoa.moa.core.ObjectRepository; |
| import com.yahoo.labs.samoa.moa.core.DataPoint; |
| import com.github.javacliparser.FlagOption; |
| import com.github.javacliparser.FloatOption; |
| import com.github.javacliparser.IntOption; |
| import com.yahoo.labs.samoa.moa.streams.InstanceStream; |
| import com.yahoo.labs.samoa.moa.tasks.TaskMonitor; |
| import com.yahoo.labs.samoa.instances.Attribute; |
| import com.yahoo.labs.samoa.instances.DenseInstance; |
| import com.yahoo.labs.samoa.moa.core.FastVector; |
| import com.yahoo.labs.samoa.instances.Instance; |
| import com.yahoo.labs.samoa.instances.Instances; |
| |
| public class RandomRBFGeneratorEvents extends ClusteringStream { |
| private transient Vector listeners; |
| |
| private static final long serialVersionUID = 1L; |
| |
| public IntOption modelRandomSeedOption = new IntOption("modelRandomSeed", |
| 'm', "Seed for random generation of model.", 1); |
| |
| public IntOption instanceRandomSeedOption = new IntOption( |
| "instanceRandomSeed", 'i', |
| "Seed for random generation of instances.", 5); |
| |
| public IntOption numClusterOption = new IntOption("numCluster", 'K', |
| "The average number of centroids in the model.", 5, 1, Integer.MAX_VALUE); |
| |
| public IntOption numClusterRangeOption = new IntOption("numClusterRange", 'k', |
| "Deviation of the number of centroids in the model.", 3, 0, Integer.MAX_VALUE); |
| |
| public FloatOption kernelRadiiOption = new FloatOption("kernelRadius", 'R', |
| "The average radii of the centroids in the model.", 0.07, 0, 1); |
| |
| public FloatOption kernelRadiiRangeOption = new FloatOption("kernelRadiusRange", 'r', |
| "Deviation of average radii of the centroids in the model.", 0, 0, 1); |
| |
| public FloatOption densityRangeOption = new FloatOption("densityRange", 'd', |
| "Offset of the average weight a cluster has. Value of 0 means all cluster " + |
| "contain the same amount of points.", 0, 0, 1); |
| |
| public IntOption speedOption = new IntOption("speed", 'V', |
| "Kernels move a predefined distance of 0.01 every X points", 500, 1, Integer.MAX_VALUE); |
| |
| public IntOption speedRangeOption = new IntOption("speedRange", 'v', |
| "Speed/Velocity point offset", 0, 0, Integer.MAX_VALUE); |
| |
| public FloatOption noiseLevelOption = new FloatOption("noiseLevel", 'N', |
| "Noise level", 0.1, 0, 1); |
| |
| public FlagOption noiseInClusterOption = new FlagOption("noiseInCluster", 'n', |
| "Allow noise to be placed within a cluster"); |
| |
| public IntOption eventFrequencyOption = new IntOption("eventFrequency", 'E', |
| "Event frequency. Enable at least one of the events below and set numClusterRange!", 30000, 0, Integer.MAX_VALUE); |
| |
| public FlagOption eventMergeSplitOption = new FlagOption("eventMergeSplitOption", 'M', |
| "Enable merging and splitting of clusters. Set eventFrequency and numClusterRange!"); |
| |
| public FlagOption eventDeleteCreateOption = new FlagOption("eventDeleteCreate", 'C', |
| "Enable emering and disapperaing of clusters. Set eventFrequency and numClusterRange!"); |
| |
| private double merge_threshold = 0.7; |
| private int kernelMovePointFrequency = 10; |
| private double maxDistanceMoveThresholdByStep = 0.01; |
| private int maxOverlapFitRuns = 50; |
| private double eventFrequencyRange = 0; |
| |
| private boolean debug = false; |
| |
| private AutoExpandVector<GeneratorCluster> kernels; |
| protected Random instanceRandom; |
| protected InstancesHeader streamHeader; |
| private int numGeneratedInstances; |
| private int numActiveKernels; |
| private int nextEventCounter; |
| private int nextEventChoice = -1; |
| private int clusterIdCounter; |
| private GeneratorCluster mergeClusterA; |
| private GeneratorCluster mergeClusterB; |
| private boolean mergeKernelsOverlapping = false; |
| |
| private class GeneratorCluster implements Serializable { |
| // TODO: points is redundant to microclusterpoints, we need to come |
| // up with a good strategy that microclusters get updated and |
| // rebuild if needed. Idea: Sort microclusterpoints by timestamp and let |
| // microclusterdecay hold the timestamp for when the last point in a |
| // microcluster gets kicked, then we rebuild... or maybe not... could be |
| // same as searching for point to be kicked. more likely is we rebuild |
| // fewer times then insert. |
| |
| private static final long serialVersionUID = -6301649898961112942L; |
| |
| SphereCluster generator; |
| int kill = -1; |
| boolean merging = false; |
| double[] moveVector; |
| int totalMovementSteps; |
| int currentMovementSteps; |
| boolean isSplitting = false; |
| |
| LinkedList<DataPoint> points = new LinkedList<DataPoint>(); |
| ArrayList<SphereCluster> microClusters = new ArrayList<SphereCluster>(); |
| ArrayList<ArrayList<DataPoint>> microClustersPoints = new ArrayList(); |
| ArrayList<Integer> microClustersDecay = new ArrayList(); |
| |
| public GeneratorCluster(int label) { |
| boolean outofbounds = true; |
| int tryCounter = 0; |
| while (outofbounds && tryCounter < maxOverlapFitRuns) { |
| tryCounter++; |
| outofbounds = false; |
| double[] center = new double[numAttsOption.getValue()]; |
| double radius = kernelRadiiOption.getValue() + (instanceRandom.nextBoolean() ? -1 : 1) |
| * kernelRadiiRangeOption.getValue() * instanceRandom.nextDouble(); |
| while (radius <= 0) { |
| radius = kernelRadiiOption.getValue() + (instanceRandom.nextBoolean() ? -1 : 1) |
| * kernelRadiiRangeOption.getValue() * instanceRandom.nextDouble(); |
| } |
| for (int j = 0; j < numAttsOption.getValue(); j++) { |
| center[j] = instanceRandom.nextDouble(); |
| if (center[j] - radius < 0 || center[j] + radius > 1) { |
| outofbounds = true; |
| break; |
| } |
| } |
| generator = new SphereCluster(center, radius); |
| } |
| if (tryCounter < maxOverlapFitRuns) { |
| generator.setId(label); |
| double avgWeight = 1.0 / numClusterOption.getValue(); |
| double weight = avgWeight + (instanceRandom.nextBoolean() ? -1 : 1) * avgWeight * densityRangeOption.getValue() |
| * instanceRandom.nextDouble(); |
| generator.setWeight(weight); |
| setDesitnation(null); |
| } |
| else { |
| generator = null; |
| kill = 0; |
| System.out.println("Tried " + maxOverlapFitRuns + " times to create kernel. Reduce average radii."); |
| } |
| } |
| |
| public GeneratorCluster(int label, SphereCluster cluster) { |
| this.generator = cluster; |
| cluster.setId(label); |
| setDesitnation(null); |
| } |
| |
| public int getWorkID() { |
| for (int c = 0; c < kernels.size(); c++) { |
| if (kernels.get(c).equals(this)) |
| return c; |
| } |
| return -1; |
| } |
| |
| private void updateKernel() { |
| if (kill == 0) { |
| kernels.remove(this); |
| } |
| if (kill > 0) { |
| kill--; |
| } |
| // we could be lot more precise if we would keep track of timestamps of |
| // points |
| // then we could remove all old points and rebuild the cluster on up to |
| // date point base |
| // BUT worse the effort??? so far we just want to avoid overlap with this, |
| // so its more |
| // konservative as needed. Only needs to change when we need a thighter |
| // representation |
| for (int m = 0; m < microClusters.size(); m++) { |
| if (numGeneratedInstances - microClustersDecay.get(m) > decayHorizonOption.getValue()) { |
| microClusters.remove(m); |
| microClustersPoints.remove(m); |
| microClustersDecay.remove(m); |
| } |
| } |
| |
| if (!points.isEmpty() |
| && numGeneratedInstances - points.getFirst().getTimestamp() >= decayHorizonOption.getValue()) { |
| // if(debug) |
| // System.out.println("Cleaning up macro cluster "+generator.getId()); |
| points.removeFirst(); |
| } |
| |
| } |
| |
| private void addInstance(Instance instance) { |
| DataPoint point = new DataPoint(instance, numGeneratedInstances); |
| points.add(point); |
| |
| int minMicroIndex = -1; |
| double minHullDist = Double.MAX_VALUE; |
| boolean inserted = false; |
| // we favour more recently build clusters so we can remove earlier cluster |
| // sooner |
| for (int m = microClusters.size() - 1; m >= 0; m--) { |
| SphereCluster micro = microClusters.get(m); |
| double hulldist = micro.getCenterDistance(point) - micro.getRadius(); |
| // point fits into existing cluster |
| if (hulldist <= 0) { |
| microClustersPoints.get(m).add(point); |
| microClustersDecay.set(m, numGeneratedInstances); |
| inserted = true; |
| break; |
| } |
| // if not, check if its at least the closest cluster |
| else { |
| if (hulldist < minHullDist) { |
| minMicroIndex = m; |
| minHullDist = hulldist; |
| } |
| } |
| } |
| // Reseting index choice for alternative cluster building |
| int alt = 1; |
| if (alt == 1) |
| minMicroIndex = -1; |
| if (!inserted) { |
| // add to closest cluster and expand cluster |
| if (minMicroIndex != -1) { |
| microClustersPoints.get(minMicroIndex).add(point); |
| // we should keep the miniball instances and just check in |
| // new points instead of rebuilding the whole thing |
| SphereCluster s = new SphereCluster(microClustersPoints.get(minMicroIndex), numAttsOption.getValue()); |
| // check if current microcluster is bigger then generating cluster |
| if (s.getRadius() > generator.getRadius()) { |
| // remove previously added point |
| microClustersPoints.get(minMicroIndex).remove(microClustersPoints.get(minMicroIndex).size() - 1); |
| minMicroIndex = -1; |
| } |
| else { |
| microClusters.set(minMicroIndex, s); |
| microClustersDecay.set(minMicroIndex, numGeneratedInstances); |
| } |
| } |
| // minMicroIndex might have been reset above |
| // create new micro cluster |
| if (minMicroIndex == -1) { |
| ArrayList<DataPoint> microPoints = new ArrayList<DataPoint>(); |
| microPoints.add(point); |
| SphereCluster s; |
| if (alt == 0) |
| s = new SphereCluster(microPoints, numAttsOption.getValue()); |
| else |
| s = new SphereCluster(generator.getCenter(), generator.getRadius(), 1); |
| |
| microClusters.add(s); |
| microClustersPoints.add(microPoints); |
| microClustersDecay.add(numGeneratedInstances); |
| int id = 0; |
| while (id < kernels.size()) { |
| if (kernels.get(id) == this) |
| break; |
| id++; |
| } |
| s.setGroundTruth(id); |
| } |
| } |
| |
| } |
| |
| private void move() { |
| if (currentMovementSteps < totalMovementSteps) { |
| currentMovementSteps++; |
| if (moveVector == null) { |
| return; |
| } |
| else { |
| double[] center = generator.getCenter(); |
| boolean outofbounds = true; |
| while (outofbounds) { |
| double radius = generator.getRadius(); |
| outofbounds = false; |
| center = generator.getCenter(); |
| for (int d = 0; d < center.length; d++) { |
| center[d] += moveVector[d]; |
| if (center[d] - radius < 0 || center[d] + radius > 1) { |
| outofbounds = true; |
| setDesitnation(null); |
| break; |
| } |
| } |
| } |
| generator.setCenter(center); |
| } |
| } |
| else { |
| if (!merging) { |
| setDesitnation(null); |
| isSplitting = false; |
| } |
| } |
| } |
| |
| void setDesitnation(double[] destination) { |
| |
| if (destination == null) { |
| destination = new double[numAttsOption.getValue()]; |
| for (int j = 0; j < numAttsOption.getValue(); j++) { |
| destination[j] = instanceRandom.nextDouble(); |
| } |
| } |
| double[] center = generator.getCenter(); |
| int dim = center.length; |
| |
| double[] v = new double[dim]; |
| |
| for (int d = 0; d < dim; d++) { |
| v[d] = destination[d] - center[d]; |
| } |
| setMoveVector(v); |
| } |
| |
| void setMoveVector(double[] vector) { |
| // we are ignoring the steps, otherwise we have to change |
| // speed of the kernels, do we want that? |
| moveVector = vector; |
| int speedInPoints = speedOption.getValue(); |
| if (speedRangeOption.getValue() > 0) |
| speedInPoints += (instanceRandom.nextBoolean() ? -1 : 1) * instanceRandom.nextInt(speedRangeOption.getValue()); |
| if (speedInPoints < 1) |
| speedInPoints = speedOption.getValue(); |
| |
| double length = 0; |
| for (int d = 0; d < moveVector.length; d++) { |
| length += Math.pow(vector[d], 2); |
| } |
| length = Math.sqrt(length); |
| |
| totalMovementSteps = (int) (length / (maxDistanceMoveThresholdByStep * kernelMovePointFrequency) * speedInPoints); |
| for (int d = 0; d < moveVector.length; d++) { |
| moveVector[d] /= (double) totalMovementSteps; |
| } |
| |
| currentMovementSteps = 0; |
| // if(debug){ |
| // System.out.println("Setting new direction for C"+generator.getId()+": distance " |
| // +length+" in "+totalMovementSteps+" steps"); |
| // } |
| } |
| |
| private String tryMerging(GeneratorCluster merge) { |
| String message = ""; |
| double overlapDegree = generator.overlapRadiusDegree(merge.generator); |
| if (overlapDegree > merge_threshold) { |
| SphereCluster mcluster = merge.generator; |
| double radius = Math.max(generator.getRadius(), mcluster.getRadius()); |
| generator.combine(mcluster); |
| |
| // //adjust radius, get bigger and bigger with high dim data |
| generator.setRadius(radius); |
| // double[] center = generator.getCenter(); |
| // double[] mcenter = mcluster.getCenter(); |
| // double weight = generator.getWeight(); |
| // double mweight = generator.getWeight(); |
| // // for (int i = 0; i < center.length; i++) { |
| // // center[i] = (center[i] * weight + mcenter[i] * mweight) / (mweight |
| // + weight); |
| // // } |
| // generator.setWeight(weight + mweight); |
| message = "Clusters merging: " + mergeClusterB.generator.getId() + " into " + mergeClusterA.generator.getId(); |
| |
| // clean up and restet merging stuff |
| // mark kernel so it gets killed when it doesn't contain any more |
| // instances |
| merge.kill = decayHorizonOption.getValue(); |
| // set weight to 0 so no new instances will be created in the cluster |
| mcluster.setWeight(0.0); |
| normalizeWeights(); |
| numActiveKernels--; |
| mergeClusterB = mergeClusterA = null; |
| merging = false; |
| mergeKernelsOverlapping = false; |
| } |
| else { |
| if (overlapDegree > 0 && !mergeKernelsOverlapping) { |
| mergeKernelsOverlapping = true; |
| message = "Merge overlapping started"; |
| } |
| } |
| return message; |
| } |
| |
| private String splitKernel() { |
| isSplitting = true; |
| // todo radius range |
| double radius = kernelRadiiOption.getValue(); |
| double avgWeight = 1.0 / numClusterOption.getValue(); |
| double weight = avgWeight + avgWeight * densityRangeOption.getValue() * instanceRandom.nextDouble(); |
| SphereCluster spcluster = null; |
| |
| double[] center = generator.getCenter(); |
| spcluster = new SphereCluster(center, radius, weight); |
| |
| if (spcluster != null) { |
| GeneratorCluster gc = new GeneratorCluster(clusterIdCounter++, spcluster); |
| gc.isSplitting = true; |
| kernels.add(gc); |
| normalizeWeights(); |
| numActiveKernels++; |
| return "Split from Kernel " + generator.getId(); |
| } |
| else { |
| System.out.println("Tried to split new kernel from C" + generator.getId() + |
| ". Not enough room for new cluster, decrease average radii, number of clusters or enable overlap."); |
| return ""; |
| } |
| } |
| |
| private String fadeOut() { |
| kill = decayHorizonOption.getValue(); |
| generator.setWeight(0.0); |
| numActiveKernels--; |
| normalizeWeights(); |
| return "Fading out C" + generator.getId(); |
| } |
| |
| } |
| |
| public RandomRBFGeneratorEvents() { |
| noiseInClusterOption.set(); |
| // eventDeleteCreateOption.set(); |
| // eventMergeSplitOption.set(); |
| } |
| |
| public InstancesHeader getHeader() { |
| return streamHeader; |
| } |
| |
| public long estimatedRemainingInstances() { |
| return -1; |
| } |
| |
| public boolean hasMoreInstances() { |
| return true; |
| } |
| |
| public boolean isRestartable() { |
| return true; |
| } |
| |
| @Override |
| public void prepareForUseImpl(TaskMonitor monitor, ObjectRepository repository) { |
| monitor.setCurrentActivity("Preparing random RBF...", -1.0); |
| generateHeader(); |
| restart(); |
| } |
| |
| public void restart() { |
| instanceRandom = new Random(instanceRandomSeedOption.getValue()); |
| nextEventCounter = eventFrequencyOption.getValue(); |
| nextEventChoice = getNextEvent(); |
| numActiveKernels = 0; |
| numGeneratedInstances = 0; |
| clusterIdCounter = 0; |
| mergeClusterA = mergeClusterB = null; |
| kernels = new AutoExpandVector<GeneratorCluster>(); |
| |
| initKernels(); |
| } |
| |
| protected void generateHeader() { // 2013/06/02: Noise label |
| ArrayList<Attribute> attributes = new ArrayList<Attribute>(); |
| for (int i = 0; i < this.numAttsOption.getValue(); i++) { |
| attributes.add(new Attribute("att" + (i + 1))); |
| } |
| |
| ArrayList<String> classLabels = new ArrayList<String>(); |
| for (int i = 0; i < this.numClusterOption.getValue(); i++) { |
| classLabels.add("class" + (i + 1)); |
| } |
| if (noiseLevelOption.getValue() > 0) |
| classLabels.add("noise"); // The last label = "noise" |
| |
| attributes.add(new Attribute("class", classLabels)); |
| streamHeader = new InstancesHeader(new Instances(getCLICreationString(InstanceStream.class), attributes, 0)); |
| streamHeader.setClassIndex(streamHeader.numAttributes() - 1); |
| } |
| |
| protected void initKernels() { |
| for (int i = 0; i < numClusterOption.getValue(); i++) { |
| kernels.add(new GeneratorCluster(clusterIdCounter)); |
| numActiveKernels++; |
| clusterIdCounter++; |
| } |
| normalizeWeights(); |
| } |
| |
| public InstanceExample nextInstance() { |
| numGeneratedInstances++; |
| eventScheduler(); |
| |
| // make room for the classlabel |
| double[] values_new = new double[numAttsOption.getValue()]; // +1 |
| double[] values = null; |
| int clusterChoice = -1; |
| |
| if (instanceRandom.nextDouble() > noiseLevelOption.getValue()) { |
| clusterChoice = chooseWeightedElement(); |
| values = kernels.get(clusterChoice).generator.sample(instanceRandom).toDoubleArray(); |
| } |
| else { |
| // get ranodm noise point |
| values = getNoisePoint(); |
| } |
| |
| if (Double.isNaN(values[0])) { |
| System.out.println("Instance corrupted:" + numGeneratedInstances); |
| } |
| System.arraycopy(values, 0, values_new, 0, values.length); |
| |
| Instance inst = new DenseInstance(1.0, values_new); |
| inst.setDataset(getHeader()); |
| if (clusterChoice == -1) { |
| // 2013/06/02 (Yunsu Kim) |
| // Noise instance has the last class value instead of "-1" |
| // Preventing ArrayIndexOutOfBoundsException in WriteStreamToARFFFile |
| inst.setClassValue(numClusterOption.getValue()); |
| } |
| else { |
| inst.setClassValue(kernels.get(clusterChoice).generator.getId()); |
| // Do we need micro cluster representation if have overlapping clusters? |
| // if(!overlappingOption.isSet()) |
| kernels.get(clusterChoice).addInstance(inst); |
| } |
| // System.out.println(numGeneratedInstances+": Overlap is"+updateOverlaps()); |
| |
| return new InstanceExample(inst); |
| } |
| |
| public Clustering getGeneratingClusters() { |
| Clustering clustering = new Clustering(); |
| for (int c = 0; c < kernels.size(); c++) { |
| clustering.add(kernels.get(c).generator); |
| } |
| return clustering; |
| } |
| |
| public Clustering getMicroClustering() { |
| Clustering clustering = new Clustering(); |
| int id = 0; |
| |
| for (int c = 0; c < kernels.size(); c++) { |
| for (int m = 0; m < kernels.get(c).microClusters.size(); m++) { |
| kernels.get(c).microClusters.get(m).setId(id); |
| kernels.get(c).microClusters.get(m).setGroundTruth(kernels.get(c).generator.getId()); |
| clustering.add(kernels.get(c).microClusters.get(m)); |
| id++; |
| } |
| } |
| |
| // System.out.println("numMicroKernels "+clustering.size()); |
| return clustering; |
| } |
| |
| /**************************** EVENTS ******************************************/ |
| private void eventScheduler() { |
| |
| for (int i = 0; i < kernels.size(); i++) { |
| kernels.get(i).updateKernel(); |
| } |
| |
| nextEventCounter--; |
| // only move kernels every 10 points, performance reasons???? |
| // should this be randomized as well??? |
| if (nextEventCounter % kernelMovePointFrequency == 0) { |
| // move kernels |
| for (int i = 0; i < kernels.size(); i++) { |
| kernels.get(i).move(); |
| // overlapControl(); |
| } |
| } |
| |
| if (eventFrequencyOption.getValue() == 0) { |
| return; |
| } |
| |
| String type = ""; |
| String message = ""; |
| boolean eventFinished = false; |
| switch (nextEventChoice) { |
| case 0: |
| if (numActiveKernels > 1 && numActiveKernels > numClusterOption.getValue() - numClusterRangeOption.getValue()) { |
| message = mergeKernels(nextEventCounter); |
| type = "Merge"; |
| } |
| if (mergeClusterA == null && mergeClusterB == null && message.startsWith("Clusters merging")) { |
| eventFinished = true; |
| } |
| break; |
| case 1: |
| if (nextEventCounter <= 0) { |
| if (numActiveKernels < numClusterOption.getValue() + numClusterRangeOption.getValue()) { |
| type = "Split"; |
| message = splitKernel(); |
| } |
| eventFinished = true; |
| } |
| break; |
| case 2: |
| if (nextEventCounter <= 0) { |
| if (numActiveKernels > 1 && numActiveKernels > numClusterOption.getValue() - numClusterRangeOption.getValue()) { |
| message = fadeOut(); |
| type = "Delete"; |
| } |
| eventFinished = true; |
| } |
| break; |
| case 3: |
| if (nextEventCounter <= 0) { |
| if (numActiveKernels < numClusterOption.getValue() + numClusterRangeOption.getValue()) { |
| message = fadeIn(); |
| type = "Create"; |
| } |
| eventFinished = true; |
| } |
| break; |
| |
| } |
| if (eventFinished) { |
| nextEventCounter = (int) (eventFrequencyOption.getValue() + (instanceRandom.nextBoolean() ? -1 : 1) |
| * eventFrequencyOption.getValue() * eventFrequencyRange * instanceRandom.nextDouble()); |
| nextEventChoice = getNextEvent(); |
| // System.out.println("Next event choice: "+nextEventChoice); |
| } |
| if (!message.isEmpty()) { |
| message += " (numKernels = " + numActiveKernels + " at " + numGeneratedInstances + ")"; |
| if (!type.equals("Merge") || message.startsWith("Clusters merging")) |
| fireClusterChange(numGeneratedInstances, type, message); |
| } |
| } |
| |
| private int getNextEvent() { |
| int choice = -1; |
| boolean lowerLimit = numActiveKernels <= numClusterOption.getValue() - numClusterRangeOption.getValue(); |
| boolean upperLimit = numActiveKernels >= numClusterOption.getValue() + numClusterRangeOption.getValue(); |
| |
| if (!lowerLimit || !upperLimit) { |
| int mode = -1; |
| if (eventDeleteCreateOption.isSet() && eventMergeSplitOption.isSet()) { |
| mode = instanceRandom.nextInt(2); |
| } |
| |
| if (mode == 0 || (mode == -1 && eventMergeSplitOption.isSet())) { |
| // have we reached a limit? if not free choice |
| if (!lowerLimit && !upperLimit) |
| choice = instanceRandom.nextInt(2); |
| else |
| // we have a limit. if lower limit, choose split |
| if (lowerLimit) |
| choice = 1; |
| // otherwise we reached upper level, choose merge |
| else |
| choice = 0; |
| } |
| |
| if (mode == 1 || (mode == -1 && eventDeleteCreateOption.isSet())) { |
| // have we reached a limit? if not free choice |
| if (!lowerLimit && !upperLimit) |
| choice = instanceRandom.nextInt(2) + 2; |
| else |
| // we have a limit. if lower limit, choose create |
| if (lowerLimit) |
| choice = 3; |
| // otherwise we reached upper level, choose delete |
| else |
| choice = 2; |
| } |
| } |
| |
| return choice; |
| } |
| |
| private String fadeOut() { |
| int id = instanceRandom.nextInt(kernels.size()); |
| while (kernels.get(id).kill != -1) |
| id = instanceRandom.nextInt(kernels.size()); |
| |
| String message = kernels.get(id).fadeOut(); |
| return message; |
| } |
| |
| private String fadeIn() { |
| GeneratorCluster gc = new GeneratorCluster(clusterIdCounter++); |
| kernels.add(gc); |
| numActiveKernels++; |
| normalizeWeights(); |
| return "Creating new cluster"; |
| } |
| |
| private String changeWeight(boolean increase) { |
| double changeRate = 0.1; |
| int id = instanceRandom.nextInt(kernels.size()); |
| while (kernels.get(id).kill != -1) |
| id = instanceRandom.nextInt(kernels.size()); |
| |
| int sign = 1; |
| if (!increase) |
| sign = -1; |
| double weight_old = kernels.get(id).generator.getWeight(); |
| double delta = sign * numActiveKernels * instanceRandom.nextDouble() * changeRate; |
| kernels.get(id).generator.setWeight(weight_old + delta); |
| |
| normalizeWeights(); |
| |
| String message; |
| if (increase) |
| message = "Increase "; |
| else |
| message = "Decrease "; |
| message += " weight on Cluster " + id + " from " + weight_old + " to " + (weight_old + delta); |
| return message; |
| |
| } |
| |
| private String changeRadius(boolean increase) { |
| double maxChangeRate = 0.1; |
| int id = instanceRandom.nextInt(kernels.size()); |
| while (kernels.get(id).kill != -1) |
| id = instanceRandom.nextInt(kernels.size()); |
| |
| int sign = 1; |
| if (!increase) |
| sign = -1; |
| |
| double r_old = kernels.get(id).generator.getRadius(); |
| double r_new = r_old + sign * r_old * instanceRandom.nextDouble() * maxChangeRate; |
| if (r_new >= 0.5) |
| return "Radius to big"; |
| kernels.get(id).generator.setRadius(r_new); |
| |
| String message; |
| if (increase) |
| message = "Increase "; |
| else |
| message = "Decrease "; |
| message += " radius on Cluster " + id + " from " + r_old + " to " + r_new; |
| return message; |
| } |
| |
| private String splitKernel() { |
| int id = instanceRandom.nextInt(kernels.size()); |
| while (kernels.get(id).kill != -1) |
| id = instanceRandom.nextInt(kernels.size()); |
| |
| String message = kernels.get(id).splitKernel(); |
| |
| return message; |
| } |
| |
| private String mergeKernels(int steps) { |
| if (numActiveKernels > 1 && ((mergeClusterA == null && mergeClusterB == null))) { |
| |
| // choose clusters to merge |
| double diseredDist = steps / speedOption.getValue() * maxDistanceMoveThresholdByStep; |
| double minDist = Double.MAX_VALUE; |
| // System.out.println("DisredDist:"+(2*diseredDist)); |
| for (int i = 0; i < kernels.size(); i++) { |
| for (int j = 0; j < i; j++) { |
| if (kernels.get(i).kill != -1 || kernels.get(j).kill != -1) { |
| continue; |
| } |
| else { |
| double kernelDist = kernels.get(i).generator.getCenterDistance(kernels.get(j).generator); |
| double d = kernelDist - 2 * diseredDist; |
| // System.out.println("Dist:"+i+" / "+j+" "+d); |
| if (Math.abs(d) < minDist && |
| (minDist != Double.MAX_VALUE || d > 0 || Math.abs(d) < 0.001)) { |
| minDist = Math.abs(d); |
| mergeClusterA = kernels.get(i); |
| mergeClusterB = kernels.get(j); |
| } |
| } |
| } |
| } |
| |
| if (mergeClusterA != null && mergeClusterB != null) { |
| double[] merge_point = mergeClusterA.generator.getCenter(); |
| double[] v = mergeClusterA.generator.getDistanceVector(mergeClusterB.generator); |
| for (int i = 0; i < v.length; i++) { |
| merge_point[i] = merge_point[i] + v[i] * 0.5; |
| } |
| |
| mergeClusterA.merging = true; |
| mergeClusterB.merging = true; |
| mergeClusterA.setDesitnation(merge_point); |
| mergeClusterB.setDesitnation(merge_point); |
| |
| if (debug) { |
| System.out.println("Center1" + Arrays.toString(mergeClusterA.generator.getCenter())); |
| System.out.println("Center2" + Arrays.toString(mergeClusterB.generator.getCenter())); |
| System.out.println("Vector" + Arrays.toString(v)); |
| |
| System.out.println("Try to merge cluster " + mergeClusterA.generator.getId() + |
| " into " + mergeClusterB.generator.getId() + |
| " at " + Arrays.toString(merge_point) + |
| " time " + numGeneratedInstances); |
| } |
| return "Init merge"; |
| } |
| } |
| |
| if (mergeClusterA != null && mergeClusterB != null) { |
| |
| // movekernels will move the kernels close to each other, |
| // we just need to check and merge here if they are close enough |
| return mergeClusterA.tryMerging(mergeClusterB); |
| } |
| |
| return ""; |
| } |
| |
| /************************* TOOLS **************************************/ |
| |
| public void getDescription(StringBuilder sb, int indent) { |
| |
| } |
| |
| private double[] getNoisePoint() { |
| double[] sample = new double[numAttsOption.getValue()]; |
| boolean incluster = true; |
| int counter = 20; |
| while (incluster) { |
| for (int j = 0; j < numAttsOption.getValue(); j++) { |
| sample[j] = instanceRandom.nextDouble(); |
| } |
| incluster = false; |
| if (!noiseInClusterOption.isSet() && counter > 0) { |
| counter--; |
| for (int c = 0; c < kernels.size(); c++) { |
| for (int m = 0; m < kernels.get(c).microClusters.size(); m++) { |
| Instance inst = new DenseInstance(1, sample); |
| if (kernels.get(c).microClusters.get(m).getInclusionProbability(inst) > 0) { |
| incluster = true; |
| break; |
| } |
| } |
| if (incluster) |
| break; |
| } |
| } |
| } |
| |
| // double [] sample = new double [numAttsOption.getValue()]; |
| // for (int j = 0; j < numAttsOption.getValue(); j++) { |
| // sample[j] = instanceRandom.nextDouble(); |
| // } |
| |
| return sample; |
| } |
| |
| private int chooseWeightedElement() { |
| double r = instanceRandom.nextDouble(); |
| |
| // Determine index of choosen element |
| int i = 0; |
| while (r > 0.0) { |
| r -= kernels.get(i).generator.getWeight(); |
| i++; |
| } |
| --i; // Overcounted once |
| // System.out.println(i); |
| return i; |
| } |
| |
| private void normalizeWeights() { |
| double sumWeights = 0.0; |
| for (int i = 0; i < kernels.size(); i++) { |
| sumWeights += kernels.get(i).generator.getWeight(); |
| } |
| for (int i = 0; i < kernels.size(); i++) { |
| kernels.get(i).generator.setWeight(kernels.get(i).generator.getWeight() / sumWeights); |
| } |
| } |
| |
| /*************** EVENT Listener *********************/ |
| // should go into the superclass of the generator, create new one for cluster |
| // streams? |
| |
| /** Add a listener */ |
| synchronized public void addClusterChangeListener(ClusterEventListener l) { |
| if (listeners == null) |
| listeners = new Vector(); |
| listeners.addElement(l); |
| } |
| |
| /** Remove a listener */ |
| synchronized public void removeClusterChangeListener(ClusterEventListener l) { |
| if (listeners == null) |
| listeners = new Vector(); |
| listeners.removeElement(l); |
| } |
| |
| /** Fire a ClusterChangeEvent to all registered listeners */ |
| protected void fireClusterChange(long timestamp, String type, String message) { |
| // if we have no listeners, do nothing... |
| if (listeners != null && !listeners.isEmpty()) { |
| // create the event object to send |
| ClusterEvent event = |
| new ClusterEvent(this, timestamp, type, message); |
| |
| // make a copy of the listener list in case |
| // anyone adds/removes listeners |
| Vector targets; |
| synchronized (this) { |
| targets = (Vector) listeners.clone(); |
| } |
| |
| // walk through the listener list and |
| // call the sunMoved method in each |
| Enumeration e = targets.elements(); |
| while (e.hasMoreElements()) { |
| ClusterEventListener l = (ClusterEventListener) e.nextElement(); |
| l.changeCluster(event); |
| |
| } |
| } |
| } |
| |
| @Override |
| public String getPurposeString() { |
| return "Generates a random radial basis function stream."; |
| } |
| |
| public String getParameterString() { |
| return ""; |
| } |
| |
| } |