| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| |
| package org.apache.samoa.moa.clusterers; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Random; |
| |
| import org.apache.samoa.instances.Instance; |
| import org.apache.samoa.moa.cluster.Clustering; |
| import org.apache.samoa.moa.cluster.SphereCluster; |
| import org.apache.samoa.moa.core.DataPoint; |
| import org.apache.samoa.moa.core.Measurement; |
| |
| import com.github.javacliparser.FloatOption; |
| import com.github.javacliparser.IntOption; |
| |
| public class ClusterGenerator extends AbstractClusterer { |
| |
| private static final long serialVersionUID = 1L; |
| |
| public IntOption timeWindowOption = new IntOption("timeWindow", |
| 't', "Rang of the window.", 1000); |
| |
| public FloatOption radiusDecreaseOption = new FloatOption("radiusDecrease", 'r', |
| "The average radii of the centroids in the model.", 0, 0, 1); |
| |
| public FloatOption radiusIncreaseOption = new FloatOption("radiusIncrease", 'R', |
| "The average radii of the centroids in the model.", 0, 0, 1); |
| |
| public FloatOption positionOffsetOption = new FloatOption("positionOffset", 'p', |
| "The average radii of the centroids in the model.", 0, 0, 1); |
| |
| public FloatOption clusterRemoveOption = new FloatOption("clusterRemove", 'D', |
| "Deletes complete clusters from the clustering.", 0, 0, 1); |
| |
| public FloatOption joinClustersOption = new FloatOption("joinClusters", 'j', |
| "Join two clusters if their hull distance is less minRadius times this factor.", 0, 0, 1); |
| |
| public FloatOption clusterAddOption = new FloatOption("clusterAdd", 'A', |
| "Adds additional clusters.", 0, 0, 1); |
| |
| private ArrayList<DataPoint> points; |
| private int instanceCounter; |
| private int windowCounter; |
| private Random random; |
| private Clustering sourceClustering = null; |
| |
| @Override |
| public void resetLearningImpl() { |
| points = new ArrayList<>(); |
| instanceCounter = 0; |
| windowCounter = 0; |
| random = new Random(227); |
| |
| // joinClustersOption.set(); |
| // evaluateMicroClusteringOption.set(); |
| } |
| |
| @Override |
| public void trainOnInstanceImpl(Instance inst) { |
| if (windowCounter >= timeWindowOption.getValue()) { |
| points.clear(); |
| windowCounter = 0; |
| } |
| windowCounter++; |
| instanceCounter++; |
| points.add(new DataPoint(inst, instanceCounter)); |
| } |
| |
| @Override |
| public boolean implementsMicroClusterer() { |
| return true; |
| } |
| |
| public void setSourceClustering(Clustering source) { |
| sourceClustering = source; |
| } |
| |
| @Override |
| public Clustering getMicroClusteringResult() { |
| // System.out.println("Numcluster:"+clustering.size()+" / "+num); |
| // Clustering source_clustering = new Clustering(points, overlapThreshold, |
| // microInitMinPoints); |
| if (sourceClustering == null) { |
| |
| System.out.println("You need to set a source clustering for the ClusterGenerator to work"); |
| return null; |
| } |
| return alterClustering(sourceClustering); |
| } |
| |
| public Clustering getClusteringResult() { |
| sourceClustering = new Clustering(points); |
| // if(sourceClustering == null){ |
| // System.out.println("You need to set a source clustering for the ClusterGenerator to work"); |
| // return null; |
| // } |
| return alterClustering(sourceClustering); |
| } |
| |
| private Clustering alterClustering(Clustering scclustering) { |
| // percentage of the radius that will be cut off |
| // 0: no changes to radius |
| // 1: radius of 0 |
| double errLevelRadiusDecrease = radiusDecreaseOption.getValue(); |
| |
| // 0: no changes to radius |
| // 1: radius 100% bigger |
| double errLevelRadiusIncrease = radiusIncreaseOption.getValue(); |
| |
| // 0: no changes |
| // 1: distance between centers is 2 * original radius |
| double errLevelPosition = positionOffsetOption.getValue(); |
| |
| int numRemoveCluster = (int) (clusterRemoveOption.getValue() * scclustering.size()); |
| |
| int numAddCluster = (int) (clusterAddOption.getValue() * scclustering.size()); |
| |
| for (int c = 0; c < numRemoveCluster; c++) { |
| int delId = random.nextInt(scclustering.size()); |
| scclustering.remove(delId); |
| } |
| |
| int numCluster = scclustering.size(); |
| double[] err_seeds = new double[numCluster]; |
| double err_seed_sum = 0.0; |
| double tmp_seed; |
| for (int i = 0; i < numCluster; i++) { |
| tmp_seed = random.nextDouble(); |
| err_seeds[i] = err_seed_sum + tmp_seed; |
| err_seed_sum += tmp_seed; |
| } |
| |
| double sumWeight = 0; |
| for (int i = 0; i < numCluster; i++) { |
| sumWeight += scclustering.get(i).getWeight(); |
| } |
| |
| Clustering clustering = new Clustering(); |
| |
| for (int i = 0; i < numCluster; i++) { |
| if (!(scclustering.get(i) instanceof SphereCluster)) { |
| System.out.println("Not a Sphere Cluster"); |
| continue; |
| } |
| SphereCluster sourceCluster = (SphereCluster) scclustering.get(i); |
| double[] center = Arrays.copyOf(sourceCluster.getCenter(), sourceCluster.getCenter().length); |
| double weight = sourceCluster.getWeight(); |
| double radius = sourceCluster.getRadius(); |
| |
| // move cluster center |
| double err_interval_width = 0.0; |
| if (errLevelPosition > 0) { |
| double errOffset = random.nextDouble() * err_interval_width / 2.0; |
| double errOffsetDirection = ((random.nextBoolean()) ? 1 : -1); |
| double level = errLevelPosition + errOffsetDirection * errOffset; |
| double[] vector = new double[center.length]; |
| double vectorLength = 0; |
| for (int d = 0; d < center.length; d++) { |
| vector[d] = (random.nextBoolean() ? 1 : -1) * random.nextDouble(); |
| vectorLength += Math.pow(vector[d], 2); |
| } |
| vectorLength = Math.sqrt(vectorLength); |
| |
| // max is when clusters are next to each other |
| double length = 2 * radius * level; |
| |
| for (int d = 0; d < center.length; d++) { |
| // normalize length and then strecht to reach error position |
| vector[d] = vector[d] / vectorLength * length; |
| } |
| // System.out.println("Center "+Arrays.toString(center)); |
| // System.out.println("Vector "+Arrays.toString(vector)); |
| // check if error position is within bounds |
| double[] newCenter = new double[center.length]; |
| for (int d = 0; d < center.length; d++) { |
| // check bounds, otherwise flip vector |
| if (center[d] + vector[d] >= 0 && center[d] + vector[d] <= 1) { |
| newCenter[d] = center[d] + vector[d]; |
| } |
| else { |
| newCenter[d] = center[d] + (-1) * vector[d]; |
| } |
| } |
| center = newCenter; |
| for (int d = 0; d < center.length; d++) { |
| if (newCenter[d] < 0 || newCenter[d] > 1) { |
| System.out.println("This shouldnt have happend, Cluster center out of bounds:" + Arrays.toString(newCenter)); |
| } |
| } |
| } |
| |
| // alter radius |
| if (errLevelRadiusDecrease > 0 || errLevelRadiusIncrease > 0) { |
| double errOffset = random.nextDouble() * err_interval_width / 2.0; |
| int errOffsetDirection = ((random.nextBoolean()) ? 1 : -1); |
| |
| if (errLevelRadiusDecrease > 0 && (errLevelRadiusIncrease == 0 || random.nextBoolean())) { |
| double level = (errLevelRadiusDecrease + errOffsetDirection * errOffset);// *sourceCluster.getWeight()/sumWeight; |
| level = (level < 0) ? 0 : level; |
| level = (level > 1) ? 1 : level; |
| radius *= (1 - level); |
| } |
| else { |
| double level = errLevelRadiusIncrease + errOffsetDirection * errOffset; |
| level = (level < 0) ? 0 : level; |
| level = (level > 1) ? 1 : level; |
| radius += radius * level; |
| } |
| } |
| |
| SphereCluster newCluster = new SphereCluster(center, radius, weight); |
| newCluster.setMeasureValue("Source Cluster", "C" + sourceCluster.getId()); |
| |
| clustering.add(newCluster); |
| } |
| |
| if (joinClustersOption.getValue() > 0) { |
| clustering = joinClusters(clustering); |
| } |
| |
| // add new clusters by copying clusters and set a random center |
| for (int c = 0; c < numAddCluster; c++) { |
| int copyId = random.nextInt(clustering.size()); |
| SphereCluster scorg = (SphereCluster) clustering.get(copyId); |
| int dim = scorg.getCenter().length; |
| double[] center = new double[dim]; |
| double radius = scorg.getRadius(); |
| |
| boolean outofbounds = true; |
| int tryCounter = 0; |
| while (outofbounds && tryCounter < 20) { |
| tryCounter++; |
| outofbounds = false; |
| for (int j = 0; j < center.length; j++) { |
| center[j] = random.nextDouble(); |
| if (center[j] - radius < 0 || center[j] + radius > 1) { |
| outofbounds = true; |
| break; |
| } |
| } |
| } |
| if (outofbounds) { |
| System.out.println("Coludn't place additional cluster"); |
| } |
| else { |
| SphereCluster scnew = new SphereCluster(center, radius, scorg.getWeight() / 2); |
| scorg.setWeight(scorg.getWeight() - scnew.getWeight()); |
| clustering.add(scnew); |
| } |
| } |
| |
| return clustering; |
| |
| } |
| |
| private Clustering joinClusters(Clustering clustering) { |
| |
| double radiusFactor = joinClustersOption.getValue(); |
| boolean[] merged = new boolean[clustering.size()]; |
| |
| Clustering mclustering = new Clustering(); |
| |
| if (radiusFactor > 0) { |
| for (int c1 = 0; c1 < clustering.size(); c1++) { |
| SphereCluster sc1 = (SphereCluster) clustering.get(c1); |
| double minDist = Double.MAX_VALUE; |
| int maxIndexCon = -1; |
| for (int c2 = 0; c2 < clustering.size(); c2++) { |
| SphereCluster sc2 = (SphereCluster) clustering.get(c2); |
| // double over = sc1.overlapRadiusDegree(sc2); |
| // if(over > 0 && over < minOver){ |
| // minOver = over; |
| // maxindexOver = c2; |
| // } |
| double dist = sc1.getHullDistance(sc2); |
| double threshold = Math.min(sc1.getRadius(), sc2.getRadius()) * radiusFactor; |
| if (dist > 0 && dist < minDist && dist < threshold) { |
| minDist = dist; |
| maxIndexCon = c2; |
| } |
| } |
| int maxindex; |
| maxindex = maxIndexCon; |
| |
| if (maxindex != -1 && !merged[c1]) { |
| merged[c1] = true; |
| merged[maxindex] = true; |
| SphereCluster scnew = new SphereCluster(sc1.getCenter(), sc1.getRadius(), sc1.getWeight()); |
| SphereCluster sc2 = (SphereCluster) clustering.get(maxindex); |
| scnew.merge(sc2); |
| mclustering.add(scnew); |
| } |
| } |
| } |
| |
| for (int i = 0; i < merged.length; i++) { |
| if (!merged[i]) |
| mclustering.add(clustering.get(i)); |
| } |
| |
| return mclustering; |
| |
| } |
| |
| @Override |
| protected Measurement[] getModelMeasurementsImpl() { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| @Override |
| public void getModelDescription(StringBuilder out, int indent) { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| @Override |
| public boolean isRandomizable() { |
| return false; |
| } |
| |
| @Override |
| public boolean keepClassLabel() { |
| return true; |
| } |
| |
| public double[] getVotesForInstance(Instance inst) { |
| return null; |
| } |
| } |