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.math4.ml.neuralnet.twod;
019
020import java.util.List;
021import java.util.ArrayList;
022import java.util.Iterator;
023import java.util.Collection;
024import java.io.Serializable;
025import java.io.ObjectInputStream;
026
027import org.apache.commons.math4.exception.MathInternalError;
028import org.apache.commons.math4.exception.NumberIsTooSmallException;
029import org.apache.commons.math4.exception.OutOfRangeException;
030import org.apache.commons.math4.ml.neuralnet.FeatureInitializer;
031import org.apache.commons.math4.ml.neuralnet.Network;
032import org.apache.commons.math4.ml.neuralnet.Neuron;
033import org.apache.commons.math4.ml.neuralnet.SquareNeighbourhood;
034import org.apache.commons.math4.ml.neuralnet.MapRanking;
035import org.apache.commons.math4.ml.neuralnet.twod.util.LocationFinder;
036import org.apache.commons.math4.ml.distance.DistanceMeasure;
037import org.apache.commons.math4.ml.distance.EuclideanDistance;
038
039/**
040 * Neural network with the topology of a two-dimensional surface.
041 * Each neuron defines one surface element.
042 * <br>
043 * This network is primarily intended to represent a
044 * <a href="http://en.wikipedia.org/wiki/Kohonen">
045 *  Self Organizing Feature Map</a>.
046 *
047 * @see org.apache.commons.math4.ml.neuralnet.sofm
048 * @since 3.3
049 */
050public class NeuronSquareMesh2D
051    implements Iterable<Neuron>,
052               Serializable {
053    /** Serial version ID */
054    private static final long serialVersionUID = 1L;
055    /** Underlying network. */
056    private final Network network;
057    /** Number of rows. */
058    private final int numberOfRows;
059    /** Number of columns. */
060    private final int numberOfColumns;
061    /** Wrap. */
062    private final boolean wrapRows;
063    /** Wrap. */
064    private final boolean wrapColumns;
065    /** Neighbourhood type. */
066    private final SquareNeighbourhood neighbourhood;
067    /**
068     * Mapping of the 2D coordinates (in the rectangular mesh) to
069     * the neuron identifiers (attributed by the {@link #network}
070     * instance).
071     */
072    private final long[][] identifiers;
073
074    /**
075     * Horizontal (along row) direction.
076     * @since 3.6
077     */
078    public enum HorizontalDirection {
079        /** Column at the right of the current column. */
080       RIGHT,
081       /** Current column. */
082       CENTER,
083       /** Column at the left of the current column. */
084       LEFT,
085    }
086    /**
087     * Vertical (along column) direction.
088     * @since 3.6
089     */
090    public enum VerticalDirection {
091        /** Row above the current row. */
092        UP,
093        /** Current row. */
094        CENTER,
095        /** Row below the current row. */
096        DOWN,
097    }
098
099    /**
100     * Constructor with restricted access, solely used for deserialization.
101     *
102     * @param wrapRowDim Whether to wrap the first dimension (i.e the first
103     * and last neurons will be linked together).
104     * @param wrapColDim Whether to wrap the second dimension (i.e the first
105     * and last neurons will be linked together).
106     * @param neighbourhoodType Neighbourhood type.
107     * @param featuresList Arrays that will initialize the features sets of
108     * the network's neurons.
109     * @throws NumberIsTooSmallException if {@code numRows < 2} or
110     * {@code numCols < 2}.
111     */
112    NeuronSquareMesh2D(boolean wrapRowDim,
113                       boolean wrapColDim,
114                       SquareNeighbourhood neighbourhoodType,
115                       double[][][] featuresList) {
116        numberOfRows = featuresList.length;
117        numberOfColumns = featuresList[0].length;
118
119        if (numberOfRows < 2) {
120            throw new NumberIsTooSmallException(numberOfRows, 2, true);
121        }
122        if (numberOfColumns < 2) {
123            throw new NumberIsTooSmallException(numberOfColumns, 2, true);
124        }
125
126        wrapRows = wrapRowDim;
127        wrapColumns = wrapColDim;
128        neighbourhood = neighbourhoodType;
129
130        final int fLen = featuresList[0][0].length;
131        network = new Network(0, fLen);
132        identifiers = new long[numberOfRows][numberOfColumns];
133
134        // Add neurons.
135        for (int i = 0; i < numberOfRows; i++) {
136            for (int j = 0; j < numberOfColumns; j++) {
137                identifiers[i][j] = network.createNeuron(featuresList[i][j]);
138            }
139        }
140
141        // Add links.
142        createLinks();
143    }
144
145    /**
146     * Creates a two-dimensional network composed of square cells:
147     * Each neuron not located on the border of the mesh has four
148     * neurons linked to it.
149     * <br>
150     * The links are bi-directional.
151     * <br>
152     * The topology of the network can also be a cylinder (if one
153     * of the dimensions is wrapped) or a torus (if both dimensions
154     * are wrapped).
155     *
156     * @param numRows Number of neurons in the first dimension.
157     * @param wrapRowDim Whether to wrap the first dimension (i.e the first
158     * and last neurons will be linked together).
159     * @param numCols Number of neurons in the second dimension.
160     * @param wrapColDim Whether to wrap the second dimension (i.e the first
161     * and last neurons will be linked together).
162     * @param neighbourhoodType Neighbourhood type.
163     * @param featureInit Array of functions that will initialize the
164     * corresponding element of the features set of each newly created
165     * neuron. In particular, the size of this array defines the size of
166     * feature set.
167     * @throws NumberIsTooSmallException if {@code numRows < 2} or
168     * {@code numCols < 2}.
169     */
170    public NeuronSquareMesh2D(int numRows,
171                              boolean wrapRowDim,
172                              int numCols,
173                              boolean wrapColDim,
174                              SquareNeighbourhood neighbourhoodType,
175                              FeatureInitializer[] featureInit) {
176        if (numRows < 2) {
177            throw new NumberIsTooSmallException(numRows, 2, true);
178        }
179        if (numCols < 2) {
180            throw new NumberIsTooSmallException(numCols, 2, true);
181        }
182
183        numberOfRows = numRows;
184        wrapRows = wrapRowDim;
185        numberOfColumns = numCols;
186        wrapColumns = wrapColDim;
187        neighbourhood = neighbourhoodType;
188        identifiers = new long[numberOfRows][numberOfColumns];
189
190        final int fLen = featureInit.length;
191        network = new Network(0, fLen);
192
193        // Add neurons.
194        for (int i = 0; i < numRows; i++) {
195            for (int j = 0; j < numCols; j++) {
196                final double[] features = new double[fLen];
197                for (int fIndex = 0; fIndex < fLen; fIndex++) {
198                    features[fIndex] = featureInit[fIndex].value();
199                }
200                identifiers[i][j] = network.createNeuron(features);
201            }
202        }
203
204        // Add links.
205        createLinks();
206    }
207
208    /**
209     * Constructor with restricted access, solely used for making a
210     * {@link #copy() deep copy}.
211     *
212     * @param wrapRowDim Whether to wrap the first dimension (i.e the first
213     * and last neurons will be linked together).
214     * @param wrapColDim Whether to wrap the second dimension (i.e the first
215     * and last neurons will be linked together).
216     * @param neighbourhoodType Neighbourhood type.
217     * @param net Underlying network.
218     * @param idGrid Neuron identifiers.
219     */
220    private NeuronSquareMesh2D(boolean wrapRowDim,
221                               boolean wrapColDim,
222                               SquareNeighbourhood neighbourhoodType,
223                               Network net,
224                               long[][] idGrid) {
225        numberOfRows = idGrid.length;
226        numberOfColumns = idGrid[0].length;
227        wrapRows = wrapRowDim;
228        wrapColumns = wrapColDim;
229        neighbourhood = neighbourhoodType;
230        network = net;
231        identifiers = idGrid;
232    }
233
234    /**
235     * Performs a deep copy of this instance.
236     * Upon return, the copied and original instances will be independent:
237     * Updating one will not affect the other.
238     *
239     * @return a new instance with the same state as this instance.
240     * @since 3.6
241     */
242    public synchronized NeuronSquareMesh2D copy() {
243        final long[][] idGrid = new long[numberOfRows][numberOfColumns];
244        for (int r = 0; r < numberOfRows; r++) {
245            for (int c = 0; c < numberOfColumns; c++) {
246                idGrid[r][c] = identifiers[r][c];
247            }
248        }
249
250        return new NeuronSquareMesh2D(wrapRows,
251                                      wrapColumns,
252                                      neighbourhood,
253                                      network.copy(),
254                                      idGrid);
255    }
256
257    /** {@inheritDoc} */
258    @Override
259    public Iterator<Neuron> iterator() {
260        return network.iterator();
261    }
262
263    /**
264     * Retrieves the underlying network.
265     * A reference is returned (enabling, for example, the network to be
266     * trained).
267     * This also implies that calling methods that modify the {@link Network}
268     * topology may cause this class to become inconsistent.
269     *
270     * @return the network.
271     */
272    public Network getNetwork() {
273        return network;
274    }
275
276    /**
277     * Gets the number of neurons in each row of this map.
278     *
279     * @return the number of rows.
280     */
281    public int getNumberOfRows() {
282        return numberOfRows;
283    }
284
285    /**
286     * Gets the number of neurons in each column of this map.
287     *
288     * @return the number of column.
289     */
290    public int getNumberOfColumns() {
291        return numberOfColumns;
292    }
293
294    /**
295     * Retrieves the neuron at location {@code (i, j)} in the map.
296     * The neuron at position {@code (0, 0)} is located at the upper-left
297     * corner of the map.
298     *
299     * @param i Row index.
300     * @param j Column index.
301     * @return the neuron at {@code (i, j)}.
302     * @throws OutOfRangeException if {@code i} or {@code j} is
303     * out of range.
304     *
305     * @see #getNeuron(int,int,HorizontalDirection,VerticalDirection)
306     */
307    public Neuron getNeuron(int i,
308                            int j) {
309        if (i < 0 ||
310            i >= numberOfRows) {
311            throw new OutOfRangeException(i, 0, numberOfRows - 1);
312        }
313        if (j < 0 ||
314            j >= numberOfColumns) {
315            throw new OutOfRangeException(j, 0, numberOfColumns - 1);
316        }
317
318        return network.getNeuron(identifiers[i][j]);
319    }
320
321    /**
322     * Retrieves the requested neuron relative to the given {@code (row, col)}
323     * position.
324     * The neuron at position {@code (0, 0)} is located at the upper-left
325     * corner of the map.
326     *
327     * @param row Row index.
328     * @param col Column index.
329     * @param alongRowDir Direction along the given {@code row} (i.e. an
330     * offset will be added to the given <em>column</em> index.
331     * @param alongColDir Direction along the given {@code col} (i.e. an
332     * offset will be added to the given <em>row</em> index.
333     * @return the neuron at the requested location, or {@code null} if
334     * the location is not on the map.
335     *
336     * @see #getNeuron(int,int)
337     */
338    public Neuron getNeuron(int row,
339                            int col,
340                            HorizontalDirection alongRowDir,
341                            VerticalDirection alongColDir) {
342        final int[] location = getLocation(row, col, alongRowDir, alongColDir);
343
344        return location == null ? null : getNeuron(location[0], location[1]);
345    }
346
347    /**
348     * Computes various {@link DataVisualization indicators} of the quality
349     * of the representation of the given {@code data} by this map.
350     *
351     * @param data Features.
352     * @return a new instance holding quality indicators.
353     */
354    public DataVisualization computeQualityIndicators(Iterable<double[]> data) {
355        return DataVisualization.from(copy(), data);
356    }
357
358    /**
359     * Computes the location of a neighbouring neuron.
360     * Returns {@code null} if the resulting location is not part
361     * of the map.
362     * Position {@code (0, 0)} is at the upper-left corner of the map.
363     *
364     * @param row Row index.
365     * @param col Column index.
366     * @param alongRowDir Direction along the given {@code row} (i.e. an
367     * offset will be added to the given <em>column</em> index.
368     * @param alongColDir Direction along the given {@code col} (i.e. an
369     * offset will be added to the given <em>row</em> index.
370     * @return an array of length 2 containing the indices of the requested
371     * location, or {@code null} if that location is not part of the map.
372     *
373     * @see #getNeuron(int,int)
374     */
375    private int[] getLocation(int row,
376                              int col,
377                              HorizontalDirection alongRowDir,
378                              VerticalDirection alongColDir) {
379        final int colOffset;
380        switch (alongRowDir) {
381        case LEFT:
382            colOffset = -1;
383            break;
384        case RIGHT:
385            colOffset = 1;
386            break;
387        case CENTER:
388            colOffset = 0;
389            break;
390        default:
391            // Should never happen.
392            throw new MathInternalError();
393        }
394        int colIndex = col + colOffset;
395        if (wrapColumns) {
396            if (colIndex < 0) {
397                colIndex += numberOfColumns;
398            } else {
399                colIndex %= numberOfColumns;
400            }
401        }
402
403        final int rowOffset;
404        switch (alongColDir) {
405        case UP:
406            rowOffset = -1;
407            break;
408        case DOWN:
409            rowOffset = 1;
410            break;
411        case CENTER:
412            rowOffset = 0;
413            break;
414        default:
415            // Should never happen.
416            throw new MathInternalError();
417        }
418        int rowIndex = row + rowOffset;
419        if (wrapRows) {
420            if (rowIndex < 0) {
421                rowIndex += numberOfRows;
422            } else {
423                rowIndex %= numberOfRows;
424            }
425        }
426
427        if (rowIndex < 0 ||
428            rowIndex >= numberOfRows ||
429            colIndex < 0 ||
430            colIndex >= numberOfColumns) {
431            return null;
432        } else {
433            return new int[] { rowIndex, colIndex };
434        }
435     }
436
437    /**
438     * Creates the neighbour relationships between neurons.
439     */
440    private void createLinks() {
441        // "linkEnd" will store the identifiers of the "neighbours".
442        final List<Long> linkEnd = new ArrayList<>();
443        final int iLast = numberOfRows - 1;
444        final int jLast = numberOfColumns - 1;
445        for (int i = 0; i < numberOfRows; i++) {
446            for (int j = 0; j < numberOfColumns; j++) {
447                linkEnd.clear();
448
449                switch (neighbourhood) {
450
451                case MOORE:
452                    // Add links to "diagonal" neighbours.
453                    if (i > 0) {
454                        if (j > 0) {
455                            linkEnd.add(identifiers[i - 1][j - 1]);
456                        }
457                        if (j < jLast) {
458                            linkEnd.add(identifiers[i - 1][j + 1]);
459                        }
460                    }
461                    if (i < iLast) {
462                        if (j > 0) {
463                            linkEnd.add(identifiers[i + 1][j - 1]);
464                        }
465                        if (j < jLast) {
466                            linkEnd.add(identifiers[i + 1][j + 1]);
467                        }
468                    }
469                    if (wrapRows) {
470                        if (i == 0) {
471                            if (j > 0) {
472                                linkEnd.add(identifiers[iLast][j - 1]);
473                            }
474                            if (j < jLast) {
475                                linkEnd.add(identifiers[iLast][j + 1]);
476                            }
477                        } else if (i == iLast) {
478                            if (j > 0) {
479                                linkEnd.add(identifiers[0][j - 1]);
480                            }
481                            if (j < jLast) {
482                                linkEnd.add(identifiers[0][j + 1]);
483                            }
484                        }
485                    }
486                    if (wrapColumns) {
487                        if (j == 0) {
488                            if (i > 0) {
489                                linkEnd.add(identifiers[i - 1][jLast]);
490                            }
491                            if (i < iLast) {
492                                linkEnd.add(identifiers[i + 1][jLast]);
493                            }
494                        } else if (j == jLast) {
495                             if (i > 0) {
496                                 linkEnd.add(identifiers[i - 1][0]);
497                             }
498                             if (i < iLast) {
499                                 linkEnd.add(identifiers[i + 1][0]);
500                             }
501                        }
502                    }
503                    if (wrapRows &&
504                        wrapColumns) {
505                        if (i == 0 &&
506                            j == 0) {
507                            linkEnd.add(identifiers[iLast][jLast]);
508                        } else if (i == 0 &&
509                                   j == jLast) {
510                            linkEnd.add(identifiers[iLast][0]);
511                        } else if (i == iLast &&
512                                   j == 0) {
513                            linkEnd.add(identifiers[0][jLast]);
514                        } else if (i == iLast &&
515                                   j == jLast) {
516                            linkEnd.add(identifiers[0][0]);
517                        }
518                    }
519
520                    // Case falls through since the "Moore" neighbourhood
521                    // also contains the neurons that belong to the "Von
522                    // Neumann" neighbourhood.
523
524                    // fallthru (CheckStyle)
525                case VON_NEUMANN:
526                    // Links to preceding and following "row".
527                    if (i > 0) {
528                        linkEnd.add(identifiers[i - 1][j]);
529                    }
530                    if (i < iLast) {
531                        linkEnd.add(identifiers[i + 1][j]);
532                    }
533                    if (wrapRows) {
534                        if (i == 0) {
535                            linkEnd.add(identifiers[iLast][j]);
536                        } else if (i == iLast) {
537                            linkEnd.add(identifiers[0][j]);
538                        }
539                    }
540
541                    // Links to preceding and following "column".
542                    if (j > 0) {
543                        linkEnd.add(identifiers[i][j - 1]);
544                    }
545                    if (j < jLast) {
546                        linkEnd.add(identifiers[i][j + 1]);
547                    }
548                    if (wrapColumns) {
549                        if (j == 0) {
550                            linkEnd.add(identifiers[i][jLast]);
551                        } else if (j == jLast) {
552                            linkEnd.add(identifiers[i][0]);
553                        }
554                    }
555                    break;
556
557                default:
558                    throw new MathInternalError(); // Cannot happen.
559                }
560
561                final Neuron aNeuron = network.getNeuron(identifiers[i][j]);
562                for (long b : linkEnd) {
563                    final Neuron bNeuron = network.getNeuron(b);
564                    // Link to all neighbours.
565                    // The reverse links will be added as the loop proceeds.
566                    network.addLink(aNeuron, bNeuron);
567                }
568            }
569        }
570    }
571
572    /**
573     * Prevents proxy bypass.
574     *
575     * @param in Input stream.
576     */
577    private void readObject(ObjectInputStream in) {
578        throw new IllegalStateException();
579    }
580
581    /**
582     * Custom serialization.
583     *
584     * @return the proxy instance that will be actually serialized.
585     */
586    private Object writeReplace() {
587        final double[][][] featuresList = new double[numberOfRows][numberOfColumns][];
588        for (int i = 0; i < numberOfRows; i++) {
589            for (int j = 0; j < numberOfColumns; j++) {
590                featuresList[i][j] = getNeuron(i, j).getFeatures();
591            }
592        }
593
594        return new SerializationProxy(wrapRows,
595                                      wrapColumns,
596                                      neighbourhood,
597                                      featuresList);
598    }
599
600    /**
601     * Serialization.
602     */
603    private static class SerializationProxy implements Serializable {
604        /** Serializable. */
605        private static final long serialVersionUID = 20130226L;
606        /** Wrap. */
607        private final boolean wrapRows;
608        /** Wrap. */
609        private final boolean wrapColumns;
610        /** Neighbourhood type. */
611        private final SquareNeighbourhood neighbourhood;
612        /** Neurons' features. */
613        private final double[][][] featuresList;
614
615        /**
616         * @param wrapRows Whether the row dimension is wrapped.
617         * @param wrapColumns Whether the column dimension is wrapped.
618         * @param neighbourhood Neighbourhood type.
619         * @param featuresList List of neurons features.
620         * {@code neuronList}.
621         */
622        SerializationProxy(boolean wrapRows,
623                           boolean wrapColumns,
624                           SquareNeighbourhood neighbourhood,
625                           double[][][] featuresList) {
626            this.wrapRows = wrapRows;
627            this.wrapColumns = wrapColumns;
628            this.neighbourhood = neighbourhood;
629            this.featuresList = featuresList;
630        }
631
632        /**
633         * Custom serialization.
634         *
635         * @return the {@link Neuron} for which this instance is the proxy.
636         */
637        private Object readResolve() {
638            return new NeuronSquareMesh2D(wrapRows,
639                                          wrapColumns,
640                                          neighbourhood,
641                                          featuresList);
642        }
643    }
644
645    /**
646     * Miscellaneous indicators of the map quality:
647     * <ul>
648     *  <li>Hit histogram</li>
649     *  <li>Quantization error</li>
650     *  <li>Topographic error</li>
651     *  <li>Unified distance matrix</li>
652     * </ul>
653     */
654    public static class DataVisualization {
655        /** Distance function. */
656        private static final DistanceMeasure DISTANCE = new EuclideanDistance();
657        /** Total number of samples. */
658        private final int numberOfSamples;
659        /** Hit histogram. */
660        private final double[][] hitHistogram;
661        /** Quantization error. */
662        private final double[][] quantizationError;
663        /** Mean quantization error. */
664        private final double meanQuantizationError;
665        /** Topographic error. */
666        private final double[][] topographicError;
667        /** Mean topographic error. */
668        private final double meanTopographicError;
669        /** U-matrix. */
670        private final double[][] uMatrix;
671
672        /**
673         * @param numberOfSamples Number of samples.
674         * @param hitHistogram Hit histogram.
675         * @param quantizationError Quantization error.
676         * @param topographicError Topographic error.
677         * @param uMatrix U-matrix.
678         */
679        private DataVisualization(int numberOfSamples,
680                                  double[][] hitHistogram,
681                                  double[][] quantizationError,
682                                  double[][] topographicError,
683                                  double[][] uMatrix) {
684            this.numberOfSamples = numberOfSamples;
685            this.hitHistogram = hitHistogram;
686            this.quantizationError = quantizationError;
687            meanQuantizationError = hitWeightedMean(quantizationError, hitHistogram);
688            this.topographicError = topographicError;
689            meanTopographicError = hitWeightedMean(topographicError, hitHistogram);
690            this.uMatrix = uMatrix;
691        }
692
693        /**
694         * @param map Map
695         * @param data Data.
696         * @return the metrics.
697         */
698        static DataVisualization from(NeuronSquareMesh2D map,
699                                      Iterable<double[]> data) {
700            final LocationFinder finder = new LocationFinder(map);
701            final MapRanking rank = new MapRanking(map, DISTANCE);
702            final Network net = map.getNetwork();
703            final int nR = map.getNumberOfRows();
704            final int nC = map.getNumberOfColumns();
705
706            // Hit bins.
707            final int[][] hitCounter = new int[nR][nC];
708            // Hit bins.
709            final double[][] hitHistogram = new double[nR][nC];
710            // Quantization error bins.
711            final double[][] quantizationError = new double[nR][nC];
712            // Topographic error bins.
713            final double[][] topographicError = new double[nR][nC];
714            // U-matrix.
715            final double[][] uMatrix = new double[nR][nC];
716
717            int numSamples = 0;
718            for (double[] sample : data) {
719                ++numSamples;
720
721                final List<Neuron> winners = rank.rank(sample, 2);
722                final Neuron best = winners.get(0);
723                final Neuron secondBest = winners.get(1);
724
725                final LocationFinder.Location locBest = finder.getLocation(best);
726                final int rowBest = locBest.getRow();
727                final int colBest = locBest.getColumn();
728                // Increment hit counter.
729                hitCounter[rowBest][colBest] += 1;
730
731                // Aggregate quantization error.
732                quantizationError[rowBest][colBest] += DISTANCE.compute(sample, best.getFeatures());
733
734                // Aggregate topographic error.
735                if (!net.getNeighbours(best).contains(secondBest)) {
736                    // Increment count if first and second best matching units
737                    // are not neighbours.
738                    topographicError[rowBest][colBest] += 1;
739                }
740            }
741
742            for (int r = 0; r < nR; r++) {
743                for (int c = 0; c < nC; c++) {
744                    final Neuron neuron = map.getNeuron(r, c);
745                    final Collection<Neuron> neighbours = net.getNeighbours(neuron);
746                    final double[] features = neuron.getFeatures();
747                    double uDistance = 0;
748                    int neighbourCount = 0;
749                    for (Neuron n : neighbours) {
750                        ++neighbourCount;
751                        uDistance += DISTANCE.compute(features, n.getFeatures());
752                    }
753
754                    final int hitCount = hitCounter[r][c];
755                    if (hitCount != 0) {
756                        hitHistogram[r][c] = hitCount / (double) numSamples;
757                        quantizationError[r][c] /= hitCount;
758                        topographicError[r][c] /= hitCount;
759                    }
760
761                    uMatrix[r][c] = uDistance / neighbourCount;
762                }
763            }
764
765            return new DataVisualization(numSamples,
766                                         hitHistogram,
767                                         quantizationError,
768                                         topographicError,
769                                         uMatrix);
770        }
771
772        /**
773         * @return the total number of samples.
774         */
775        public final int getNumberOfSamples() {
776            return numberOfSamples;
777        }
778
779        /**
780         * @return the quantization error.
781         * Each bin will contain the average of the distances between samples
782         * mapped to the corresponding unit and the weight vector of that unit.
783         * @see #getMeanQuantizationError()
784         */
785        public double[][] getQuantizationError() {
786            return copy(quantizationError);
787        }
788
789        /**
790         * @return the topographic error.
791         * Each bin will contain the number of data for which the first and
792         * second best matching units are not adjacent in the map.
793         * @see #getMeanTopographicError()
794         */
795        public double[][] getTopographicError() {
796            return copy(topographicError);
797        }
798
799        /**
800         * @return the hits histogram (normalized).
801         * Each bin will contain the number of data for which the corresponding
802         * neuron is the best matching unit.
803         */
804        public double[][] getNormalizedHits() {
805            return copy(hitHistogram);
806        }
807
808        /**
809         * @return the U-matrix.
810         * Each bin will contain the average distance between a unit and all its
811         * neighbours will be computed (and stored in the pixel corresponding to
812         * that unit of the 2D-map).  The number of neighbours taken into account
813         * depends on the network {@link org.apache.commons.math4.ml.neuralnet.SquareNeighbourhood
814         * neighbourhood type}.
815         */
816        public double[][] getUMatrix() {
817            return copy(uMatrix);
818        }
819
820        /**
821         * @return the mean (hit-weighted) quantization error.
822         * @see #getQuantizationError()
823         */
824        public double getMeanQuantizationError() {
825            return meanQuantizationError;
826        }
827
828        /**
829         * @return the mean (hit-weighted) topographic error.
830         * @see #getTopographicError()
831         */
832        public double getMeanTopographicError() {
833            return meanTopographicError;
834        }
835
836        /**
837         * @param orig Source.
838         * @return a deep copy of the original array.
839         */
840        private static double[][] copy(double[][] orig) {
841            final double[][] copy = new double[orig.length][];
842            for (int i = 0; i < orig.length; i++) {
843                copy[i] = orig[i].clone();
844            }
845
846            return copy;
847        }
848
849        /**
850         * @param metrics Metrics.
851         * @param normalizedHits Hits histogram (normalized).
852         * @return the hit-weighted mean of the given {@code metrics}.
853         */
854        private double hitWeightedMean(double[][] metrics,
855                                       double[][] normalizedHits) {
856            double mean = 0;
857            final int rows = metrics.length;
858            final int cols = metrics[0].length;
859            for (int i = 0; i < rows; i++) {
860                for (int j = 0; j < cols; j++) {
861                    mean += normalizedHits[i][j] * metrics[i][j];
862                }
863            }
864
865            return mean;
866        }
867    }
868}