User:Chase-san/KohonenMap
Jump to navigation
Jump to search
This is my implementation of a Self-organizing map. It is untested, but it should work just fine.
Contents
org.csdgn.nn.KohonenMap
package org.csdgn.nn; import java.util.Random; import org.csdgn.nn.density.StandardDensity; import org.csdgn.nn.distance.EulerDistanceSquared; /** * A Self-Organizing Map implementation. * * Requires: <br> * org.csdgn.nn.DensityFunction<br> * org.csdgn.nn.DistanceFunction<br> * org.csdgn.nn.density.StandardDensity<br> * org.csdgn.nn.distance.EulerDistanceSquared * * @author Chase * */ public class KohonenMap { /** * Holds the neighborhood layout; */ private final Node[] map; private DensityFunction density; private DistanceFunction distance; private int BMU; /** * @param mapSize Size of the neighborhood. Example: {10,10} produces a 2 dimensional * map, each dimension having 10 nodes. Total nodes would be 100. * @param input The length of the input vector (2D only) * @param output The length of the output vector (2D only) */ public KohonenMap(int[] mapSize, int input, int output) { /* Setup the map */ int size = 1; for(int m : mapSize) size *= m; this.map = new Node[size]; this.density = new StandardDensity(); this.distance = new EulerDistanceSquared(); int[] pos = new int[mapSize.length]; for(int i=0; i<map.length; ++i) { this.map[i] = new Node(mapSize.length,input,output); /* Setup the location of each node, for speed reasons. */ System.arraycopy(pos, 0, this.map[i].position, 0, pos.length); /* Update the position marker */ ++pos[0]; for(int j=0;j<pos.length-1;++j) { if(pos[j] >= mapSize[j]) { ++pos[j+1]; pos[j] = 0; } } } } /** * Initializes the map to random values */ public final void initialize() { Random r = new Random(); initialize(r); } /** * Initializes the map with the given random function. * Uses the nextDouble function. */ public final void initialize(Random random) { for(Node n : map) { for(int i = 0; i < n.input.length; ++i) n.input[i] = random.nextDouble(); for(int i = 0; i < n.output.length; ++i) n.output[i] = random.nextDouble(); } } /** * Finds the Best Matching Unit for the given input. * @return the BMUs identifier */ public final int findBMInput(double[] input) { BMU = 0; double distance = Double.MAX_VALUE; for(int i=0; i<map.length; ++i) { double dist = this.distance.calculate(map[i].input, input); if(dist < distance) { distance = dist; BMU = i; } } return BMU; } /** * Finds the Best Matching Unit for the given output. * @return the BMUs identifier */ public final int findBMOutput(double[] output) { BMU = 0; double distance = Double.MAX_VALUE; for(int i=0; i<map.length; ++i) { double dist = this.distance.calculate(map[i].output, output); if(dist < distance) { distance = dist; BMU = i; } } return BMU; } /** * Finds the Worst Matching Unit for the given input * @return the WMUs identifier */ public final int findWMInput(double[] input) { BMU = 0; double distance = Double.MIN_VALUE; for(int i=0; i<map.length; ++i) { double dist = this.distance.calculate(map[i].input, input); if(dist > distance) { distance = dist; BMU = i; } } return BMU; } /** * Finds the Worst Matching Unit for the given output * @return the WMUs identifier */ public final int findWMOutput(double[] output) { BMU = 0; double distance = Double.MIN_VALUE; for(int i=0; i<map.length; ++i) { double dist = this.distance.calculate(map[i].output, output); if(dist > distance) { distance = dist; BMU = i; } } return BMU; } /** * This returns the input of the last found BMU or WMU. * @return the input vector */ public final double[] getInput() { return this.map[BMU].input; } /** * This returns the output of the last found BMU or WMU. * @return the output vector */ public final double[] getOutput() { return this.map[BMU].output; } /** * This returns the input of the given ID. * @return the input vector */ public final double[] getInput(int id) { if(id > 0 && id < map.length) return this.map[id].input; return new double[0]; } /** * This returns the output of the given ID. * @return the output vector */ public final double[] getOutput(int id) { if(id > 0 && id < map.length) return this.map[id].output; return new double[0]; } /** * Sets the density function this map uses for updating nearby nodes. * If unset it uses the StandardDensity class. * @param func */ public final void setDensityFunction(DensityFunction func) { this.density = func; } /** * Sets the distance function used to find the best or worst matching unit. * If unset, this map uses the EulerDistanceSquared class. * @param func */ public final void setDistanceFunction(DistanceFunction func) { this.distance = func; } /** * Updates the map with the given data. * @param input input vector * @param output output vector */ public final void update(double input[], double output[]) { Node bmu = map[BMU]; for(int i=0; i<map.length; ++i) { map[i].update(bmu.position, input, output); } } /** * <math>distSqr(p,q) = \sum_{i=0}^n (p_i - q_i)^2</math> where <math>n</math> is the * size of the smaller of <math>p</math> or <math>q</math> */ private static final int neighborhood(int[] p, int[] q) { if(p == null || q == null) return 0; int len = Math.min(p.length, q.length); int output = 0; for(int i=0; i<len; ++i) output += (p[i] - q[i])*(p[i] - q[i]); return output; } private final class Node { /** Location in the neighborhood */ private final int[] position; /** Input vector */ private final double[] input; /** Output vector */ private final double[] output; public Node(int mapSize, int inputSize, int outputSize) { position = new int[mapSize]; input = new double[inputSize]; output = new double[outputSize]; } private final double weight(double c, double t, double n) { return c + n * (t - c); } private final void update(int[] pos, double[] in, double[] out) { double distance = neighborhood(pos,position); double neighborhood = density.calculate(distance); /* Changes below this point benefits are negligible */ if(neighborhood < 1e-4) return; for(int i=0; i<input.length; ++i) input[i] = weight(input[i], in[i], neighborhood); for(int i=0; i<output.length; ++i) output[i] = weight(output[i], out[i], neighborhood); } } }
org.csdgn.nn.DensityFunction
package org.csdgn.nn; public interface DensityFunction { /** * Calculates the density at the given point, where x is a certain distance from the center of the distribution. */ public double calculate(double x); }
org.csdgn.nn.DistanceFunction
package org.csdgn.nn; /** * A function for determining the distance between two double arrays. * @author Chase */ public interface DistanceFunction { public double calculate(double[] p, double[] q); }
org.csdgn.nn.density.StandardDensity
package org.csdgn.nn.density; import org.csdgn.nn.DensityFunction; /** * <math>density(x) = 2^{-x^2}</math> */ public final class StandardDensity implements DensityFunction { /** * <math>density(x) = 2^{-x^2}</math> */ @Override public double calculate(double x) { return Math.pow(2, -(x*x)); } }
org.csdgn.nn.density.NormalDistribution
package org.csdgn.nn.density; import org.csdgn.nn.DensityFunction; public final class NormalDistribution implements DensityFunction { private final double multi; private final double variance; private final double mean; public NormalDistribution() { this(1,0); } public NormalDistribution(double variance, double mean) { this.multi = 1.0 / Math.sqrt(2*Math.PI*variance); this.variance = variance; this.mean = mean; } @Override public double calculate(double x) { double e = ((x - mean)*(x - mean)) / (2*variance); return multi*Math.exp(-e); } }
org.csdgn.nn.distance.EulerDistanceSquared
package org.csdgn.nn.distance; import org.csdgn.nn.DistanceFunction; public class EulerDistanceSquared implements DistanceFunction { /** * <math>distSqr(p,q) = \sum_{i=0}^n (p_i - q_i)^2</math> where <math>n</math> is the * size of the smaller of <math>p</math> or <math>q</math> */ @Override public final double calculate(double[] p, double[] q) { if(p == null || q == null) return 0; int len = Math.min(p.length, q.length); double k,output = 0; for(int i=0; i<len; ++i) output += (k=(p[i] - q[i]))*k; return output; } }