Difference between revisions of "Kohonen Map"
Jump to navigation
Jump to search
(Updated with real actual code (I did test, but it compiles)) |
|||
| Line 1: | Line 1: | ||
| − | + | This is about all I can remember on how Kohonen maps work, you may tweak this if you like. Fix any bugs that keeps it from actually working in a robot, etc. | |
| − | |||
| − | This is all | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
[[User:Chase-san|Chase]] 15:44, 1 November 2009 (UTC) | [[User:Chase-san|Chase]] 15:44, 1 November 2009 (UTC) | ||
<pre> | <pre> | ||
| − | + | package chase.meh; | |
| − | // | + | /** |
| − | int | + | * This is a basic stand alone Kohonen map for use in Succubus, this allows me to worry |
| − | + | * about Kohonen Map stuff in the Kohonen Map, and robot stuff in the robot. | |
| − | + | * | |
| − | + | * @author Chase | |
| − | // | + | */ |
| − | // | + | public class KohonenMap { |
| − | + | private KohonenNode[] map; | |
| − | + | private int mapSize; | |
| − | + | private long time; | |
| + | |||
| + | /** | ||
| + | * The dimensions in the dim, are only used with the neighborhood function. The true | ||
| + | * dimensionality that makes Kohonen Maps work is the input and output weights.<br><br> | ||
| + | * The input weights are used to determine the BMU, while the output weights are unused | ||
| + | * @param dim Sets the size of the internal map. Where the length is the number of dimensions and | ||
| + | * each index determines the size of that particular dimension. So {8,4} would initialize a 2 | ||
| + | * dimensional map, 8 wide, and 4 high. | ||
| + | * @param inputL The lower range of the input weights that each node contains. The number of entries | ||
| + | * determines the number of input weights. | ||
| + | * @param inputU The upper range of the input weights that each node contains. The number of entries | ||
| + | * determines the number of input weights. | ||
| + | * @param outputL The lower range of the output weights that each node contains. The number of entries | ||
| + | * determines the number of output weights. | ||
| + | * @param outputU The upper range of the output weights that each node contains. The number of entries | ||
| + | * determines the number of output weights. | ||
| + | */ | ||
| + | public KohonenMap(int[] dim, double[] inputL, double[] inputU, | ||
| + | double[] outputL, double[] outputU) { | ||
| + | mapSize = 1; | ||
| + | for(int i=0; i<dim.length; ++i) | ||
| + | mapSize *= dim[i]; | ||
| + | map = new KohonenNode[mapSize]; | ||
| + | |||
| + | //Logarithms have this thing against zero :) | ||
| + | time = 1; | ||
| + | |||
| + | /** | ||
| + | * Initialize each node. | ||
| + | */ | ||
| + | int tDim[] = new int[dim.length]; | ||
| + | for(int i=0; i<dim.length; ++i) { | ||
| + | map[i] = new KohonenNode(inputL,inputU,outputL,outputU); | ||
| + | |||
| + | /** !! VERY IMPORTANT !! VERY IMPORTANT !! | ||
| + | * Here we are setting up the neighborhood, | ||
| + | * so it doesn't need to be calculated later | ||
| + | * which makes things much simpler. | ||
| + | */ | ||
| + | map[i].my_pos = tDim.clone(); | ||
| + | |||
| + | ++tDim[0]; | ||
| + | for(int j=0;j<dim.length-1;++j) { | ||
| + | if(tDim[j] >= dim[j]) { | ||
| + | tDim[j+1]++; | ||
| + | tDim[j] = 0; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | /** | ||
| + | * Returns the best matching unit for the given data | ||
| + | * @param input Data used to locate the BMU | ||
| + | * @return the best matching BMU for this input data | ||
| + | */ | ||
| + | public KohonenNode getBMU(double input[]) { | ||
| + | KohonenNode out = null; | ||
| + | double distance = Double.POSITIVE_INFINITY; | ||
| + | for(int i=0;i<mapSize;++i) { | ||
| + | KohonenNode n = map[i]; | ||
| + | double dist = distance(input,n.input); | ||
| + | if(dist < distance) { | ||
| + | distance = dist; | ||
| + | out = n; | ||
| + | } | ||
| + | } | ||
| + | return out; | ||
| + | } | ||
| + | |||
| + | /** | ||
| + | * Updates this KohonenMap with the given data. | ||
| + | * @param input The input data that will be updated to the map | ||
| + | * @param output The output data that will be updated to the map | ||
| + | */ | ||
| + | public void update(double input[], double output[]) { | ||
| + | KohonenNode bmu = getBMU(input); | ||
| + | for(int i=0;i<mapSize;++i) { | ||
| + | map[i].update(bmu.my_pos, input, output); | ||
| + | } | ||
| + | ++time; | ||
| + | } | ||
| + | |||
| + | /** | ||
| + | * Sets the internal time used for weighting the update data by time. | ||
| + | * The internal time automatically gets incremented with each update, | ||
| + | * calling this is unnecessary. | ||
| + | * @param time | ||
| + | */ | ||
| + | public void setTime(long time) { | ||
| + | if(time < 1) time = 1; | ||
| + | this.time = time; | ||
| + | } | ||
| − | + | /** | |
| − | + | * This is a KohonenNode, it just contains the data | |
| − | + | * @author Chase | |
| − | + | */ | |
| − | + | public class KohonenNode { | |
| − | + | /** this is the location of this node in the neighborhood. */ | |
| − | + | private int[] my_pos; | |
| − | + | ||
| − | + | public double[] input; | |
| − | / | + | public double[] output; |
| − | + | ||
| − | + | /** | |
| − | + | * @param inputL The lower range of the input weights that this node contains. The number of entries | |
| − | + | * determines the number of input weights. | |
| − | } | + | * @param inputU The upper range of the input weights that this node contains. The number of entries |
| − | + | * determines the number of input weights. | |
| − | + | * @param outputL The lower range of the output weights that this node contains. The number of entries | |
| − | + | * determines the number of output weights. | |
| − | + | * @param outputU The upper range of the output weights that this node contains. The number of entries | |
| − | + | * determines the number of output weights. | |
| − | + | */ | |
| − | + | public KohonenNode(double[] inputL, double[] inputU, | |
| − | + | double[] outputL, double[] outputU) { | |
| − | + | input = new double[inputL.length]; | |
| − | + | output = new double[outputL.length]; | |
| − | + | ||
| − | + | /** !! VERY IMPORTANT !! VERY IMPORTANT !! | |
| − | + | * Randomize the startup data | |
| + | */ | ||
| + | for(int i=0;i<input.length;++i) { | ||
| + | double range = inputU[i]-inputL[i]; | ||
| + | range = Math.sqrt(range*range); | ||
| + | input[i] = Math.random()*range+inputL[i]; | ||
| + | |||
| + | range = outputU[i]-outputL[i]; | ||
| + | range = Math.sqrt(range*range); | ||
| + | output[i] = Math.random()*range+outputL[i]; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | private double weight(double current, double target, double neighborhood, double time) { | ||
| + | return current + neighborhood * time * (target - current); | ||
| + | } | ||
| + | |||
| + | private void update(int[] bmu, double input[], double output[]) { | ||
| + | double distance = 1+neighborDistance(my_pos,bmu); | ||
| + | double neighborhood = Math.pow(Math.E, -(distance*distance)/2.0); | ||
| + | double timeFunc = Math.log(time); | ||
| + | |||
| + | /** Beyond this point its benefit is negligible. */ | ||
| + | if(neighborhood*timeFunc < 0.0001) return; | ||
| + | |||
| + | /** Update the input and output weights */ | ||
| + | for(int i=0;i<input.length;++i) { | ||
| + | this.input[i] = weight(this.input[i], input[i], neighborhood, timeFunc); | ||
| + | } | ||
| + | for(int i=0;i<output.length;++i) { | ||
| + | this.output[i] = weight(this.output[i], output[i], neighborhood, timeFunc); | ||
| + | } | ||
} | } | ||
} | } | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | // | + | /** |
| − | + | * The weight distance function. | |
| − | + | */ | |
| − | + | public static final double distance(double[] p, double[] q) { | |
| − | + | if(p == null || q == null) return Double.POSITIVE_INFINITY; | |
| − | + | double k, d = 0; | |
| − | + | for(int i = 0; i < p.length; i++) { | |
| − | + | k = p[i] - q[i]; | |
| − | / | + | d += k*k; |
| − | + | } | |
| − | + | return d; | |
| − | + | } | |
| − | + | /** | |
| − | + | * The neighborhood distance function. | |
| − | + | */ | |
| − | + | public static final double neighborDistance(int[] p, int[] q) { | |
| − | + | double k, d = 0; | |
| − | + | for(int i = 0; i < p.length; i++) { | |
| − | + | k = p[i] - q[i]; | |
| − | + | d += k*k; | |
| − | + | } | |
| + | return d; | ||
} | } | ||
} | } | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
</pre> | </pre> | ||
Revision as of 12:03, 8 November 2009
This is about all I can remember on how Kohonen maps work, you may tweak this if you like. Fix any bugs that keeps it from actually working in a robot, etc.
Chase 15:44, 1 November 2009 (UTC)
package chase.meh;
/**
* This is a basic stand alone Kohonen map for use in Succubus, this allows me to worry
* about Kohonen Map stuff in the Kohonen Map, and robot stuff in the robot.
*
* @author Chase
*/
public class KohonenMap {
private KohonenNode[] map;
private int mapSize;
private long time;
/**
* The dimensions in the dim, are only used with the neighborhood function. The true
* dimensionality that makes Kohonen Maps work is the input and output weights.<br><br>
* The input weights are used to determine the BMU, while the output weights are unused
* @param dim Sets the size of the internal map. Where the length is the number of dimensions and
* each index determines the size of that particular dimension. So {8,4} would initialize a 2
* dimensional map, 8 wide, and 4 high.
* @param inputL The lower range of the input weights that each node contains. The number of entries
* determines the number of input weights.
* @param inputU The upper range of the input weights that each node contains. The number of entries
* determines the number of input weights.
* @param outputL The lower range of the output weights that each node contains. The number of entries
* determines the number of output weights.
* @param outputU The upper range of the output weights that each node contains. The number of entries
* determines the number of output weights.
*/
public KohonenMap(int[] dim, double[] inputL, double[] inputU,
double[] outputL, double[] outputU) {
mapSize = 1;
for(int i=0; i<dim.length; ++i)
mapSize *= dim[i];
map = new KohonenNode[mapSize];
//Logarithms have this thing against zero :)
time = 1;
/**
* Initialize each node.
*/
int tDim[] = new int[dim.length];
for(int i=0; i<dim.length; ++i) {
map[i] = new KohonenNode(inputL,inputU,outputL,outputU);
/** !! VERY IMPORTANT !! VERY IMPORTANT !!
* Here we are setting up the neighborhood,
* so it doesn't need to be calculated later
* which makes things much simpler.
*/
map[i].my_pos = tDim.clone();
++tDim[0];
for(int j=0;j<dim.length-1;++j) {
if(tDim[j] >= dim[j]) {
tDim[j+1]++;
tDim[j] = 0;
}
}
}
}
/**
* Returns the best matching unit for the given data
* @param input Data used to locate the BMU
* @return the best matching BMU for this input data
*/
public KohonenNode getBMU(double input[]) {
KohonenNode out = null;
double distance = Double.POSITIVE_INFINITY;
for(int i=0;i<mapSize;++i) {
KohonenNode n = map[i];
double dist = distance(input,n.input);
if(dist < distance) {
distance = dist;
out = n;
}
}
return out;
}
/**
* Updates this KohonenMap with the given data.
* @param input The input data that will be updated to the map
* @param output The output data that will be updated to the map
*/
public void update(double input[], double output[]) {
KohonenNode bmu = getBMU(input);
for(int i=0;i<mapSize;++i) {
map[i].update(bmu.my_pos, input, output);
}
++time;
}
/**
* Sets the internal time used for weighting the update data by time.
* The internal time automatically gets incremented with each update,
* calling this is unnecessary.
* @param time
*/
public void setTime(long time) {
if(time < 1) time = 1;
this.time = time;
}
/**
* This is a KohonenNode, it just contains the data
* @author Chase
*/
public class KohonenNode {
/** this is the location of this node in the neighborhood. */
private int[] my_pos;
public double[] input;
public double[] output;
/**
* @param inputL The lower range of the input weights that this node contains. The number of entries
* determines the number of input weights.
* @param inputU The upper range of the input weights that this node contains. The number of entries
* determines the number of input weights.
* @param outputL The lower range of the output weights that this node contains. The number of entries
* determines the number of output weights.
* @param outputU The upper range of the output weights that this node contains. The number of entries
* determines the number of output weights.
*/
public KohonenNode(double[] inputL, double[] inputU,
double[] outputL, double[] outputU) {
input = new double[inputL.length];
output = new double[outputL.length];
/** !! VERY IMPORTANT !! VERY IMPORTANT !!
* Randomize the startup data
*/
for(int i=0;i<input.length;++i) {
double range = inputU[i]-inputL[i];
range = Math.sqrt(range*range);
input[i] = Math.random()*range+inputL[i];
range = outputU[i]-outputL[i];
range = Math.sqrt(range*range);
output[i] = Math.random()*range+outputL[i];
}
}
private double weight(double current, double target, double neighborhood, double time) {
return current + neighborhood * time * (target - current);
}
private void update(int[] bmu, double input[], double output[]) {
double distance = 1+neighborDistance(my_pos,bmu);
double neighborhood = Math.pow(Math.E, -(distance*distance)/2.0);
double timeFunc = Math.log(time);
/** Beyond this point its benefit is negligible. */
if(neighborhood*timeFunc < 0.0001) return;
/** Update the input and output weights */
for(int i=0;i<input.length;++i) {
this.input[i] = weight(this.input[i], input[i], neighborhood, timeFunc);
}
for(int i=0;i<output.length;++i) {
this.output[i] = weight(this.output[i], output[i], neighborhood, timeFunc);
}
}
}
/**
* The weight distance function.
*/
public static final double distance(double[] p, double[] q) {
if(p == null || q == null) return Double.POSITIVE_INFINITY;
double k, d = 0;
for(int i = 0; i < p.length; i++) {
k = p[i] - q[i];
d += k*k;
}
return d;
}
/**
* The neighborhood distance function.
*/
public static final double neighborDistance(int[] p, int[] q) {
double k, d = 0;
for(int i = 0; i < p.length; i++) {
k = p[i] - q[i];
d += k*k;
}
return d;
}
}