001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.math3.ml.neuralnet.twod.util; 019 020import java.util.Collection; 021import org.apache.commons.math3.ml.neuralnet.Neuron; 022import org.apache.commons.math3.ml.neuralnet.Network; 023import org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D; 024import org.apache.commons.math3.ml.distance.DistanceMeasure; 025 026/** 027 * <a href="http://en.wikipedia.org/wiki/U-Matrix">U-Matrix</a> 028 * visualization of high-dimensional data projection. 029 * @since 3.6 030 */ 031public class UnifiedDistanceMatrix implements MapVisualization { 032 /** Whether to show distance between each pair of neighbouring units. */ 033 private final boolean individualDistances; 034 /** Distance. */ 035 private final DistanceMeasure distance; 036 037 /** 038 * Simple constructor. 039 * 040 * @param individualDistances If {@code true}, the 8 individual 041 * inter-units distances will be {@link #computeImage(NeuronSquareMesh2D) 042 * computed}. They will be stored in additional pixels around each of 043 * the original units of the 2D-map. The additional pixels that lie 044 * along a "diagonal" are shared by <em>two</em> pairs of units: their 045 * value will be set to the average distance between the units belonging 046 * to each of the pairs. The value zero will be stored in the pixel 047 * corresponding to the location of a unit of the 2D-map. 048 * <br> 049 * If {@code false}, only the average distance between a unit and all its 050 * neighbours will be computed (and stored in the pixel corresponding to 051 * that unit of the 2D-map). In that case, the number of neighbours taken 052 * into account depends on the network's 053 * {@link org.apache.commons.math3.ml.neuralnet.SquareNeighbourhood 054 * neighbourhood type}. 055 * @param distance Distance. 056 */ 057 public UnifiedDistanceMatrix(boolean individualDistances, 058 DistanceMeasure distance) { 059 this.individualDistances = individualDistances; 060 this.distance = distance; 061 } 062 063 /** {@inheritDoc} */ 064 public double[][] computeImage(NeuronSquareMesh2D map) { 065 if (individualDistances) { 066 return individualDistances(map); 067 } else { 068 return averageDistances(map); 069 } 070 } 071 072 /** 073 * Computes the distances between a unit of the map and its 074 * neighbours. 075 * The image will contain more pixels than the number of neurons 076 * in the given {@code map} because each neuron has 8 neighbours. 077 * The value zero will be stored in the pixels corresponding to 078 * the location of a map unit. 079 * 080 * @param map Map. 081 * @return an image representing the individual distances. 082 */ 083 private double[][] individualDistances(NeuronSquareMesh2D map) { 084 final int numRows = map.getNumberOfRows(); 085 final int numCols = map.getNumberOfColumns(); 086 087 final double[][] uMatrix = new double[numRows * 2 + 1][numCols * 2 + 1]; 088 089 // 1. 090 // Fill right and bottom slots of each unit's location with the 091 // distance between the current unit and each of the two neighbours, 092 // respectively. 093 for (int i = 0; i < numRows; i++) { 094 // Current unit's row index in result image. 095 final int iR = 2 * i + 1; 096 097 for (int j = 0; j < numCols; j++) { 098 // Current unit's column index in result image. 099 final int jR = 2 * j + 1; 100 101 final double[] current = map.getNeuron(i, j).getFeatures(); 102 Neuron neighbour; 103 104 // Right neighbour. 105 neighbour = map.getNeuron(i, j, 106 NeuronSquareMesh2D.HorizontalDirection.RIGHT, 107 NeuronSquareMesh2D.VerticalDirection.CENTER); 108 if (neighbour != null) { 109 uMatrix[iR][jR + 1] = distance.compute(current, 110 neighbour.getFeatures()); 111 } 112 113 // Bottom-center neighbour. 114 neighbour = map.getNeuron(i, j, 115 NeuronSquareMesh2D.HorizontalDirection.CENTER, 116 NeuronSquareMesh2D.VerticalDirection.DOWN); 117 if (neighbour != null) { 118 uMatrix[iR + 1][jR] = distance.compute(current, 119 neighbour.getFeatures()); 120 } 121 } 122 } 123 124 // 2. 125 // Fill the bottom-rigth slot of each unit's location with the average 126 // of the distances between 127 // * the current unit and its bottom-right neighbour, and 128 // * the bottom-center neighbour and the right neighbour. 129 for (int i = 0; i < numRows; i++) { 130 // Current unit's row index in result image. 131 final int iR = 2 * i + 1; 132 133 for (int j = 0; j < numCols; j++) { 134 // Current unit's column index in result image. 135 final int jR = 2 * j + 1; 136 137 final Neuron current = map.getNeuron(i, j); 138 final Neuron right = map.getNeuron(i, j, 139 NeuronSquareMesh2D.HorizontalDirection.RIGHT, 140 NeuronSquareMesh2D.VerticalDirection.CENTER); 141 final Neuron bottom = map.getNeuron(i, j, 142 NeuronSquareMesh2D.HorizontalDirection.CENTER, 143 NeuronSquareMesh2D.VerticalDirection.DOWN); 144 final Neuron bottomRight = map.getNeuron(i, j, 145 NeuronSquareMesh2D.HorizontalDirection.RIGHT, 146 NeuronSquareMesh2D.VerticalDirection.DOWN); 147 148 final double current2BottomRight = bottomRight == null ? 149 0 : 150 distance.compute(current.getFeatures(), 151 bottomRight.getFeatures()); 152 final double right2Bottom = (right == null || 153 bottom == null) ? 154 0 : 155 distance.compute(right.getFeatures(), 156 bottom.getFeatures()); 157 158 // Bottom-right slot. 159 uMatrix[iR + 1][jR + 1] = 0.5 * (current2BottomRight + right2Bottom); 160 } 161 } 162 163 // 3. Copy last row into first row. 164 final int lastRow = uMatrix.length - 1; 165 uMatrix[0] = uMatrix[lastRow]; 166 167 // 4. 168 // Copy last column into first column. 169 final int lastCol = uMatrix[0].length - 1; 170 for (int r = 0; r < lastRow; r++) { 171 uMatrix[r][0] = uMatrix[r][lastCol]; 172 } 173 174 return uMatrix; 175 } 176 177 /** 178 * Computes the distances between a unit of the map and its neighbours. 179 * 180 * @param map Map. 181 * @return an image representing the average distances. 182 */ 183 private double[][] averageDistances(NeuronSquareMesh2D map) { 184 final int numRows = map.getNumberOfRows(); 185 final int numCols = map.getNumberOfColumns(); 186 final double[][] uMatrix = new double[numRows][numCols]; 187 188 final Network net = map.getNetwork(); 189 190 for (int i = 0; i < numRows; i++) { 191 for (int j = 0; j < numCols; j++) { 192 final Neuron neuron = map.getNeuron(i, j); 193 final Collection<Neuron> neighbours = net.getNeighbours(neuron); 194 final double[] features = neuron.getFeatures(); 195 196 double d = 0; 197 int count = 0; 198 for (Neuron n : neighbours) { 199 ++count; 200 d += distance.compute(features, n.getFeatures()); 201 } 202 203 uMatrix[i][j] = d / count; 204 } 205 } 206 207 return uMatrix; 208 } 209}