1 /* 2 * This file is part of ELKI: 3 * Environment for Developing KDD-Applications Supported by Index-Structures 4 * 5 * Copyright (C) 2018 6 * ELKI Development Team 7 * 8 * This program is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU Affero General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU Affero General Public License for more details. 17 * 18 * You should have received a copy of the GNU Affero General Public License 19 * along with this program. If not, see <http://www.gnu.org/licenses/>. 20 */ 21 package de.lmu.ifi.dbs.elki.algorithm.outlier.spatial.neighborhood.weighted; 22 23 import java.util.ArrayList; 24 import java.util.Collection; 25 import java.util.List; 26 27 import de.lmu.ifi.dbs.elki.algorithm.outlier.spatial.neighborhood.NeighborSetPredicate; 28 import de.lmu.ifi.dbs.elki.data.type.TypeInformation; 29 import de.lmu.ifi.dbs.elki.database.Database; 30 import de.lmu.ifi.dbs.elki.database.ids.DBIDIter; 31 import de.lmu.ifi.dbs.elki.database.ids.DBIDRef; 32 import de.lmu.ifi.dbs.elki.database.ids.DBIDUtil; 33 import de.lmu.ifi.dbs.elki.database.ids.DBIDs; 34 import de.lmu.ifi.dbs.elki.database.ids.DoubleDBIDPair; 35 import de.lmu.ifi.dbs.elki.database.ids.ModifiableDBIDs; 36 import de.lmu.ifi.dbs.elki.database.relation.Relation; 37 import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer; 38 import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID; 39 import de.lmu.ifi.dbs.elki.utilities.optionhandling.constraints.CommonConstraints; 40 import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization; 41 import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.IntParameter; 42 import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.ObjectParameter; 43 44 /** 45 * Neighborhood obtained by computing the k-fold closure of an existing 46 * neighborhood. Objects are weighted linearly by their distance: the object 47 * itself has a weight of 1 and this decreases linearly to 1/(n+1) for the 48 * nth-step neighbors. 49 * 50 * TODO: make actual weighting parameterizable? 51 * 52 * @author Erich Schubert 53 * @since 0.4.0 54 */ 55 public class LinearWeightedExtendedNeighborhood implements WeightedNeighborSetPredicate { 56 /** 57 * The data store to use 58 */ 59 private NeighborSetPredicate inner; 60 61 /** 62 * The number of steps to extend to. 63 */ 64 private int steps; 65 66 /** 67 * Constructor. 68 * 69 * @param inner Inner neighborhood 70 * @param steps Number of steps to expand 71 */ LinearWeightedExtendedNeighborhood(NeighborSetPredicate inner, int steps)72 public LinearWeightedExtendedNeighborhood(NeighborSetPredicate inner, int steps) { 73 super(); 74 this.inner = inner; 75 this.steps = steps; 76 } 77 78 /** 79 * Compute the weight from the number of steps needed. 80 * 81 * @param tsteps steps to target 82 * @return weight 83 */ computeWeight(int tsteps)84 private double computeWeight(int tsteps) { 85 return 1.0 - (tsteps / (float) (steps + 1)); 86 } 87 88 @Override getWeightedNeighbors(DBIDRef reference)89 public Collection<DoubleDBIDPair> getWeightedNeighbors(DBIDRef reference) { 90 ModifiableDBIDs seen = DBIDUtil.newHashSet(); 91 List<DoubleDBIDPair> result = new ArrayList<>(); 92 93 // Add starting object 94 result.add(DBIDUtil.newPair(computeWeight(0), reference)); 95 seen.add(reference); 96 // Extend. 97 DBIDs cur = DBIDUtil.deref(reference); 98 for(int i = 1; i <= steps; i++) { 99 final double weight = computeWeight(i); 100 // Collect newly discovered IDs 101 ModifiableDBIDs add = DBIDUtil.newHashSet(); 102 for(DBIDIter iter = cur.iter(); iter.valid(); iter.advance()) { 103 for(DBIDIter iter2 = inner.getNeighborDBIDs(iter).iter(); iter2.valid(); iter2.advance()) { 104 // Seen before? 105 if(seen.contains(iter2)) { 106 continue; 107 } 108 add.add(iter2); 109 result.add(DBIDUtil.newPair(weight, iter2)); 110 } 111 } 112 if(add.size() == 0) { 113 break; 114 } 115 cur = add; 116 } 117 return result; 118 } 119 120 /** 121 * Factory class. 122 * 123 * @author Erich Schubert 124 * 125 * @stereotype factory 126 * @navhas - produces - LinearWeightedExtendedNeighborhood 127 */ 128 public static class Factory<O> implements WeightedNeighborSetPredicate.Factory<O> { 129 /** 130 * Inner neighbor set predicate 131 */ 132 private NeighborSetPredicate.Factory<O> inner; 133 134 /** 135 * Number of steps to do 136 */ 137 private int steps; 138 139 /** 140 * Constructor. 141 * 142 * @param inner Inner neighbor set predicate 143 * @param steps Number of steps to do 144 */ Factory(NeighborSetPredicate.Factory<O> inner, int steps)145 public Factory(NeighborSetPredicate.Factory<O> inner, int steps) { 146 super(); 147 this.inner = inner; 148 this.steps = steps; 149 } 150 151 @Override instantiate(Database database, Relation<? extends O> relation)152 public LinearWeightedExtendedNeighborhood instantiate(Database database, Relation<? extends O> relation) { 153 return new LinearWeightedExtendedNeighborhood(inner.instantiate(database, relation), steps); 154 } 155 156 @Override getInputTypeRestriction()157 public TypeInformation getInputTypeRestriction() { 158 return inner.getInputTypeRestriction(); 159 } 160 161 /** 162 * Parameterization class. 163 * 164 * @author Erich Schubert 165 */ 166 public static class Parameterizer<O> extends AbstractParameterizer { 167 /** 168 * Parameter to specify the neighborhood predicate to use. 169 */ 170 public static final OptionID NEIGHBORHOOD_ID = new OptionID("extendedneighbors.neighborhood", "The inner neighborhood predicate to use."); 171 172 /** 173 * Parameter to specify the number of steps allowed 174 */ 175 public static final OptionID STEPS_ID = new OptionID("extendedneighbors.steps", "The number of steps allowed in the neighborhood graph."); 176 177 /** 178 * The number of steps to do. 179 */ 180 private int steps; 181 182 /** 183 * Inner neighbor set predicate 184 */ 185 private NeighborSetPredicate.Factory<O> inner; 186 187 /** 188 * Inner neighborhood parameter. 189 * 190 * @param config Parameterization 191 * @return Inner neighborhood. 192 */ getParameterInnerNeighborhood(Parameterization config)193 protected static <O> NeighborSetPredicate.Factory<O> getParameterInnerNeighborhood(Parameterization config) { 194 final ObjectParameter<NeighborSetPredicate.Factory<O>> param = new ObjectParameter<>(NEIGHBORHOOD_ID, NeighborSetPredicate.Factory.class); 195 if(config.grab(param)) { 196 return param.instantiateClass(config); 197 } 198 return null; 199 } 200 201 @Override makeOptions(Parameterization config)202 protected void makeOptions(Parameterization config) { 203 super.makeOptions(config); 204 inner = getParameterInnerNeighborhood(config); 205 steps = getParameterSteps(config); 206 } 207 208 /** 209 * Get the number of steps to do in the neighborhood graph. 210 * 211 * @param config Parameterization 212 * @return number of steps, default 1 213 */ getParameterSteps(Parameterization config)214 public static int getParameterSteps(Parameterization config) { 215 final IntParameter param = new IntParameter(STEPS_ID) // 216 .addConstraint(CommonConstraints.GREATER_EQUAL_ONE_INT); 217 if(config.grab(param)) { 218 return param.getValue(); 219 } 220 return 1; 221 } 222 223 @Override makeInstance()224 protected LinearWeightedExtendedNeighborhood.Factory<O> makeInstance() { 225 return new LinearWeightedExtendedNeighborhood.Factory<>(inner, steps); 226 } 227 } 228 } 229 }