View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.math4.neuralnet.twod;
19  
20  import java.util.List;
21  import java.util.ArrayList;
22  import java.util.Iterator;
23  import java.util.Collection;
24  
25  import org.apache.commons.math4.neuralnet.DistanceMeasure;
26  import org.apache.commons.math4.neuralnet.EuclideanDistance;
27  import org.apache.commons.math4.neuralnet.FeatureInitializer;
28  import org.apache.commons.math4.neuralnet.Network;
29  import org.apache.commons.math4.neuralnet.Neuron;
30  import org.apache.commons.math4.neuralnet.SquareNeighbourhood;
31  import org.apache.commons.math4.neuralnet.MapRanking;
32  import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
33  import org.apache.commons.math4.neuralnet.twod.util.LocationFinder;
34  
35  /**
36   * Neural network with the topology of a two-dimensional surface.
37   * Each neuron defines one surface element.
38   * <br>
39   * This network is primarily intended to represent a
40   * <a href="http://en.wikipedia.org/wiki/Kohonen">
41   *  Self Organizing Feature Map</a>.
42   *
43   * @see org.apache.commons.math4.neuralnet.sofm
44   * @since 3.3
45   */
46  public class NeuronSquareMesh2D
47      implements Iterable<Neuron> {
48      /** Minimal number of rows or columns. */
49      private static final int MIN_ROWS = 2;
50      /** Underlying network. */
51      private final Network network;
52      /** Number of rows. */
53      private final int numberOfRows;
54      /** Number of columns. */
55      private final int numberOfColumns;
56      /** Wrap. */
57      private final boolean wrapRows;
58      /** Wrap. */
59      private final boolean wrapColumns;
60      /** Neighbourhood type. */
61      private final SquareNeighbourhood neighbourhood;
62      /**
63       * Mapping of the 2D coordinates (in the rectangular mesh) to
64       * the neuron identifiers (attributed by the {@link #network}
65       * instance).
66       */
67      private final long[][] identifiers;
68  
69      /**
70       * Horizontal (along row) direction.
71       * @since 3.6
72       */
73      public enum HorizontalDirection {
74          /** Column at the right of the current column. */
75         RIGHT,
76         /** Current column. */
77         CENTER,
78         /** Column at the left of the current column. */
79         LEFT,
80      }
81      /**
82       * Vertical (along column) direction.
83       * @since 3.6
84       */
85      public enum VerticalDirection {
86          /** Row above the current row. */
87          UP,
88          /** Current row. */
89          CENTER,
90          /** Row below the current row. */
91          DOWN,
92      }
93  
94      /**
95       * @param wrapRowDim Whether to wrap the first dimension (i.e the first
96       * and last neurons will be linked together).
97       * @param wrapColDim Whether to wrap the second dimension (i.e the first
98       * and last neurons will be linked together).
99       * @param neighbourhoodType Neighbourhood type.
100      * @param featuresList Arrays that will initialize the features sets of
101      * the network's neurons.
102      * @throws IllegalArgumentException if {@code numRows < 2} or
103      * {@code numCols < 2}.
104      */
105     public NeuronSquareMesh2D(boolean wrapRowDim,
106                               boolean wrapColDim,
107                               SquareNeighbourhood neighbourhoodType,
108                               double[][][] featuresList) {
109         numberOfRows = featuresList.length;
110         numberOfColumns = featuresList[0].length;
111 
112         if (numberOfRows < MIN_ROWS) {
113             throw new NeuralNetException(NeuralNetException.TOO_SMALL, numberOfRows, MIN_ROWS);
114         }
115         if (numberOfColumns < MIN_ROWS) {
116             throw new NeuralNetException(NeuralNetException.TOO_SMALL, numberOfColumns, MIN_ROWS);
117         }
118 
119         wrapRows = wrapRowDim;
120         wrapColumns = wrapColDim;
121         neighbourhood = neighbourhoodType;
122 
123         final int fLen = featuresList[0][0].length;
124         network = new Network(0, fLen);
125         identifiers = new long[numberOfRows][numberOfColumns];
126 
127         // Add neurons.
128         for (int i = 0; i < numberOfRows; i++) {
129             for (int j = 0; j < numberOfColumns; j++) {
130                 identifiers[i][j] = network.createNeuron(featuresList[i][j]);
131             }
132         }
133 
134         // Add links.
135         createLinks();
136     }
137 
138     /**
139      * Creates a two-dimensional network composed of square cells:
140      * Each neuron not located on the border of the mesh has four
141      * neurons linked to it.
142      * <br>
143      * The links are bi-directional.
144      * <br>
145      * The topology of the network can also be a cylinder (if one
146      * of the dimensions is wrapped) or a torus (if both dimensions
147      * are wrapped).
148      *
149      * @param numRows Number of neurons in the first dimension.
150      * @param wrapRowDim Whether to wrap the first dimension (i.e the first
151      * and last neurons will be linked together).
152      * @param numCols Number of neurons in the second dimension.
153      * @param wrapColDim Whether to wrap the second dimension (i.e the first
154      * and last neurons will be linked together).
155      * @param neighbourhoodType Neighbourhood type.
156      * @param featureInit Array of functions that will initialize the
157      * corresponding element of the features set of each newly created
158      * neuron. In particular, the size of this array defines the size of
159      * feature set.
160      * @throws IllegalArgumentException if {@code numRows < 2} or
161      * {@code numCols < 2}.
162      */
163     public NeuronSquareMesh2D(int numRows,
164                               boolean wrapRowDim,
165                               int numCols,
166                               boolean wrapColDim,
167                               SquareNeighbourhood neighbourhoodType,
168                               FeatureInitializer[] featureInit) {
169         if (numRows < MIN_ROWS) {
170             throw new NeuralNetException(NeuralNetException.TOO_SMALL, numRows, MIN_ROWS);
171         }
172         if (numCols < MIN_ROWS) {
173             throw new NeuralNetException(NeuralNetException.TOO_SMALL, numCols, MIN_ROWS);
174         }
175 
176         numberOfRows = numRows;
177         wrapRows = wrapRowDim;
178         numberOfColumns = numCols;
179         wrapColumns = wrapColDim;
180         neighbourhood = neighbourhoodType;
181         identifiers = new long[numberOfRows][numberOfColumns];
182 
183         final int fLen = featureInit.length;
184         network = new Network(0, fLen);
185 
186         // Add neurons.
187         for (int i = 0; i < numRows; i++) {
188             for (int j = 0; j < numCols; j++) {
189                 final double[] features = new double[fLen];
190                 for (int fIndex = 0; fIndex < fLen; fIndex++) {
191                     features[fIndex] = featureInit[fIndex].value();
192                 }
193                 identifiers[i][j] = network.createNeuron(features);
194             }
195         }
196 
197         // Add links.
198         createLinks();
199     }
200 
201     /**
202      * Constructor with restricted access, solely used for making a
203      * {@link #copy() deep copy}.
204      *
205      * @param wrapRowDim Whether to wrap the first dimension (i.e the first
206      * and last neurons will be linked together).
207      * @param wrapColDim Whether to wrap the second dimension (i.e the first
208      * and last neurons will be linked together).
209      * @param neighbourhoodType Neighbourhood type.
210      * @param net Underlying network.
211      * @param idGrid Neuron identifiers.
212      */
213     private NeuronSquareMesh2D(boolean wrapRowDim,
214                                boolean wrapColDim,
215                                SquareNeighbourhood neighbourhoodType,
216                                Network net,
217                                long[][] idGrid) {
218         numberOfRows = idGrid.length;
219         numberOfColumns = idGrid[0].length;
220         wrapRows = wrapRowDim;
221         wrapColumns = wrapColDim;
222         neighbourhood = neighbourhoodType;
223         network = net;
224         identifiers = idGrid;
225     }
226 
227     /**
228      * Performs a deep copy of this instance.
229      * Upon return, the copied and original instances will be independent:
230      * Updating one will not affect the other.
231      *
232      * @return a new instance with the same state as this instance.
233      * @since 3.6
234      */
235     public synchronized NeuronSquareMesh2D copy() {
236         final long[][] idGrid = new long[numberOfRows][numberOfColumns];
237         for (int r = 0; r < numberOfRows; r++) {
238             System.arraycopy(identifiers[r], 0, idGrid[r], 0, numberOfColumns);
239         }
240 
241         return new NeuronSquareMesh2D(wrapRows,
242                                       wrapColumns,
243                                       neighbourhood,
244                                       network.copy(),
245                                       idGrid);
246     }
247 
248     /** {@inheritDoc} */
249     @Override
250     public Iterator<Neuron> iterator() {
251         return network.iterator();
252     }
253 
254     /**
255      * Retrieves the underlying network.
256      * A reference is returned (enabling, for example, the network to be
257      * trained).
258      * This also implies that calling methods that modify the {@link Network}
259      * topology may cause this class to become inconsistent.
260      *
261      * @return the network.
262      */
263     public Network getNetwork() {
264         return network;
265     }
266 
267     /**
268      * Gets the number of neurons in each row of this map.
269      *
270      * @return the number of rows.
271      */
272     public int getNumberOfRows() {
273         return numberOfRows;
274     }
275 
276     /**
277      * Gets the number of neurons in each column of this map.
278      *
279      * @return the number of column.
280      */
281     public int getNumberOfColumns() {
282         return numberOfColumns;
283     }
284 
285     /**
286      * Indicates whether the map is wrapped along the first dimension.
287      *
288      * @return {@code true} if the last neuron of a row is linked to
289      * the first neuron of that row.
290      */
291     public boolean isWrappedRow() {
292         return wrapRows;
293     }
294 
295     /**
296      * Indicates whether the map is wrapped along the second dimension.
297      *
298      * @return {@code true} if the last neuron of a column is linked to
299      * the first neuron of that column.
300      */
301     public boolean isWrappedColumn() {
302         return wrapColumns;
303     }
304 
305     /**
306      * Indicates the {@link SquareNeighbourhood type of connectivity}
307      * between neurons.
308      *
309      * @return the neighbourhood type.
310      */
311     public SquareNeighbourhood getSquareNeighbourhood() {
312         return neighbourhood;
313     }
314 
315     /**
316      * Retrieves the neuron at location {@code (i, j)} in the map.
317      * The neuron at position {@code (0, 0)} is located at the upper-left
318      * corner of the map.
319      *
320      * @param i Row index.
321      * @param j Column index.
322      * @return the neuron at {@code (i, j)}.
323      * @throws IllegalArgumentException if {@code i} or {@code j} is
324      * out of range.
325      *
326      * @see #getNeuron(int,int,HorizontalDirection,VerticalDirection)
327      */
328     public Neuron getNeuron(int i,
329                             int j) {
330         if (i < 0 ||
331             i >= numberOfRows) {
332             throw new NeuralNetException(NeuralNetException.OUT_OF_RANGE,
333                                          i, 0, numberOfRows - 1);
334         }
335         if (j < 0 ||
336             j >= numberOfColumns) {
337             throw new NeuralNetException(NeuralNetException.OUT_OF_RANGE,
338                                          i, 0, numberOfColumns - 1);
339         }
340 
341         return network.getNeuron(identifiers[i][j]);
342     }
343 
344     /**
345      * Retrieves the requested neuron relative to the given {@code (row, col)}
346      * position.
347      * The neuron at position {@code (0, 0)} is located at the upper-left
348      * corner of the map.
349      *
350      * @param row Row index.
351      * @param col Column index.
352      * @param alongRowDir Direction along the given {@code row} (i.e. an
353      * offset will be added to the given <em>column</em> index.
354      * @param alongColDir Direction along the given {@code col} (i.e. an
355      * offset will be added to the given <em>row</em> index.
356      * @return the neuron at the requested location, or {@code null} if
357      * the location is not on the map.
358      *
359      * @see #getNeuron(int,int)
360      */
361     public Neuron getNeuron(int row,
362                             int col,
363                             HorizontalDirection alongRowDir,
364                             VerticalDirection alongColDir) {
365         final int[] location = getLocation(row, col, alongRowDir, alongColDir);
366 
367         return location == null ? null : getNeuron(location[0], location[1]);
368     }
369 
370     /**
371      * Computes various {@link DataVisualization indicators} of the quality
372      * of the representation of the given {@code data} by this map.
373      *
374      * @param data Features.
375      * @return a new instance holding quality indicators.
376      */
377     public DataVisualization computeQualityIndicators(Iterable<double[]> data) {
378         return DataVisualization.from(copy(), data);
379     }
380 
381     /**
382      * Computes the location of a neighbouring neuron.
383      * Returns {@code null} if the resulting location is not part
384      * of the map.
385      * Position {@code (0, 0)} is at the upper-left corner of the map.
386      *
387      * @param row Row index.
388      * @param col Column index.
389      * @param alongRowDir Direction along the given {@code row} (i.e. an
390      * offset will be added to the given <em>column</em> index.
391      * @param alongColDir Direction along the given {@code col} (i.e. an
392      * offset will be added to the given <em>row</em> index.
393      * @return an array of length 2 containing the indices of the requested
394      * location, or {@code null} if that location is not part of the map.
395      *
396      * @see #getNeuron(int,int)
397      */
398     private int[] getLocation(int row,
399                               int col,
400                               HorizontalDirection alongRowDir,
401                               VerticalDirection alongColDir) {
402         final int colOffset;
403         switch (alongRowDir) {
404         case LEFT:
405             colOffset = -1;
406             break;
407         case RIGHT:
408             colOffset = 1;
409             break;
410         case CENTER:
411             colOffset = 0;
412             break;
413         default:
414             // Should never happen.
415             throw new IllegalStateException();
416         }
417         int colIndex = col + colOffset;
418         if (wrapColumns) {
419             if (colIndex < 0) {
420                 colIndex += numberOfColumns;
421             } else {
422                 colIndex %= numberOfColumns;
423             }
424         }
425 
426         final int rowOffset;
427         switch (alongColDir) {
428         case UP:
429             rowOffset = -1;
430             break;
431         case DOWN:
432             rowOffset = 1;
433             break;
434         case CENTER:
435             rowOffset = 0;
436             break;
437         default:
438             // Should never happen.
439             throw new IllegalStateException();
440         }
441         int rowIndex = row + rowOffset;
442         if (wrapRows) {
443             if (rowIndex < 0) {
444                 rowIndex += numberOfRows;
445             } else {
446                 rowIndex %= numberOfRows;
447             }
448         }
449 
450         if (rowIndex < 0 ||
451             rowIndex >= numberOfRows ||
452             colIndex < 0 ||
453             colIndex >= numberOfColumns) {
454             return null;
455         } else {
456             return new int[] {rowIndex, colIndex};
457         }
458     }
459 
460     /**
461      * Creates the neighbour relationships between neurons.
462      */
463     private void createLinks() {
464         // "linkEnd" will store the identifiers of the "neighbours".
465         final List<Long> linkEnd = new ArrayList<>();
466         final int iLast = numberOfRows - 1;
467         final int jLast = numberOfColumns - 1;
468         for (int i = 0; i < numberOfRows; i++) {
469             for (int j = 0; j < numberOfColumns; j++) {
470                 linkEnd.clear();
471 
472                 switch (neighbourhood) {
473 
474                 case MOORE:
475                     // Add links to "diagonal" neighbours.
476                     if (i > 0) {
477                         if (j > 0) {
478                             linkEnd.add(identifiers[i - 1][j - 1]);
479                         }
480                         if (j < jLast) {
481                             linkEnd.add(identifiers[i - 1][j + 1]);
482                         }
483                     }
484                     if (i < iLast) {
485                         if (j > 0) {
486                             linkEnd.add(identifiers[i + 1][j - 1]);
487                         }
488                         if (j < jLast) {
489                             linkEnd.add(identifiers[i + 1][j + 1]);
490                         }
491                     }
492                     if (wrapRows) {
493                         if (i == 0) {
494                             if (j > 0) {
495                                 linkEnd.add(identifiers[iLast][j - 1]);
496                             }
497                             if (j < jLast) {
498                                 linkEnd.add(identifiers[iLast][j + 1]);
499                             }
500                         } else if (i == iLast) {
501                             if (j > 0) {
502                                 linkEnd.add(identifiers[0][j - 1]);
503                             }
504                             if (j < jLast) {
505                                 linkEnd.add(identifiers[0][j + 1]);
506                             }
507                         }
508                     }
509                     if (wrapColumns) {
510                         if (j == 0) {
511                             if (i > 0) {
512                                 linkEnd.add(identifiers[i - 1][jLast]);
513                             }
514                             if (i < iLast) {
515                                 linkEnd.add(identifiers[i + 1][jLast]);
516                             }
517                         } else if (j == jLast) {
518                             if (i > 0) {
519                                 linkEnd.add(identifiers[i - 1][0]);
520                             }
521                             if (i < iLast) {
522                                 linkEnd.add(identifiers[i + 1][0]);
523                             }
524                         }
525                     }
526                     if (wrapRows &&
527                         wrapColumns) {
528                         if (i == 0 &&
529                             j == 0) {
530                             linkEnd.add(identifiers[iLast][jLast]);
531                         } else if (i == 0 &&
532                                    j == jLast) {
533                             linkEnd.add(identifiers[iLast][0]);
534                         } else if (i == iLast &&
535                                    j == 0) {
536                             linkEnd.add(identifiers[0][jLast]);
537                         } else if (i == iLast &&
538                                    j == jLast) {
539                             linkEnd.add(identifiers[0][0]);
540                         }
541                     }
542 
543                     // Case falls through since the "Moore" neighbourhood
544                     // also contains the neurons that belong to the "Von
545                     // Neumann" neighbourhood.
546 
547                     // fallthru (CheckStyle)
548                 case VON_NEUMANN:
549                     // Links to preceding and following "row".
550                     if (i > 0) {
551                         linkEnd.add(identifiers[i - 1][j]);
552                     }
553                     if (i < iLast) {
554                         linkEnd.add(identifiers[i + 1][j]);
555                     }
556                     if (wrapRows) {
557                         if (i == 0) {
558                             linkEnd.add(identifiers[iLast][j]);
559                         } else if (i == iLast) {
560                             linkEnd.add(identifiers[0][j]);
561                         }
562                     }
563 
564                     // Links to preceding and following "column".
565                     if (j > 0) {
566                         linkEnd.add(identifiers[i][j - 1]);
567                     }
568                     if (j < jLast) {
569                         linkEnd.add(identifiers[i][j + 1]);
570                     }
571                     if (wrapColumns) {
572                         if (j == 0) {
573                             linkEnd.add(identifiers[i][jLast]);
574                         } else if (j == jLast) {
575                             linkEnd.add(identifiers[i][0]);
576                         }
577                     }
578                     break;
579 
580                 default:
581                     throw new IllegalStateException(); // Cannot happen.
582                 }
583 
584                 final Neuron aNeuron = network.getNeuron(identifiers[i][j]);
585                 for (final long b : linkEnd) {
586                     final Neuron bNeuron = network.getNeuron(b);
587                     // Link to all neighbours.
588                     // The reverse links will be added as the loop proceeds.
589                     network.addLink(aNeuron, bNeuron);
590                 }
591             }
592         }
593     }
594 
595     /**
596      * Miscellaneous indicators of the map quality.
597      * <ul>
598      *  <li>Hit histogram</li>
599      *  <li>Quantization error</li>
600      *  <li>Topographic error</li>
601      *  <li>Unified distance matrix</li>
602      * </ul>
603      */
604     public static final class DataVisualization {
605         /** Distance function. */
606         private static final DistanceMeasure DISTANCE = new EuclideanDistance();
607         /** Total number of samples. */
608         private final int numberOfSamples;
609         /** Hit histogram. */
610         private final double[][] hitHistogram;
611         /** Quantization error. */
612         private final double[][] quantizationError;
613         /** Mean quantization error. */
614         private final double meanQuantizationError;
615         /** Topographic error. */
616         private final double[][] topographicError;
617         /** Mean topographic error. */
618         private final double meanTopographicError;
619         /** U-matrix. */
620         private final double[][] uMatrix;
621 
622         /**
623          * @param numberOfSamples Number of samples.
624          * @param hitHistogram Hit histogram.
625          * @param quantizationError Quantization error.
626          * @param topographicError Topographic error.
627          * @param uMatrix U-matrix.
628          */
629         private DataVisualization(int numberOfSamples,
630                                   double[][] hitHistogram,
631                                   double[][] quantizationError,
632                                   double[][] topographicError,
633                                   double[][] uMatrix) {
634             this.numberOfSamples = numberOfSamples;
635             this.hitHistogram = hitHistogram;
636             this.quantizationError = quantizationError;
637             meanQuantizationError = hitWeightedMean(quantizationError, hitHistogram);
638             this.topographicError = topographicError;
639             meanTopographicError = hitWeightedMean(topographicError, hitHistogram);
640             this.uMatrix = uMatrix;
641         }
642 
643         /**
644          * @param map Map
645          * @param data Data.
646          * @return the metrics.
647          */
648         static DataVisualization from(NeuronSquareMesh2D map,
649                                       Iterable<double[]> data) {
650             final LocationFinder finder = new LocationFinder(map);
651             final MapRanking rank = new MapRanking(map, DISTANCE);
652             final Network net = map.getNetwork();
653             final int nR = map.getNumberOfRows();
654             final int nC = map.getNumberOfColumns();
655 
656             // Hit bins.
657             final int[][] hitCounter = new int[nR][nC];
658             // Hit bins.
659             final double[][] hitHistogram = new double[nR][nC];
660             // Quantization error bins.
661             final double[][] quantizationError = new double[nR][nC];
662             // Topographic error bins.
663             final double[][] topographicError = new double[nR][nC];
664             // U-matrix.
665             final double[][] uMatrix = new double[nR][nC];
666 
667             int numSamples = 0;
668             for (final double[] sample : data) {
669                 ++numSamples;
670 
671                 final List<Neuron> winners = rank.rank(sample, 2);
672                 final Neuron best = winners.get(0);
673                 final Neuron secondBest = winners.get(1);
674 
675                 final LocationFinder.Location locBest = finder.getLocation(best);
676                 final int rowBest = locBest.getRow();
677                 final int colBest = locBest.getColumn();
678                 // Increment hit counter.
679                 hitCounter[rowBest][colBest] += 1;
680 
681                 // Aggregate quantization error.
682                 quantizationError[rowBest][colBest] += DISTANCE.applyAsDouble(sample, best.getFeatures());
683 
684                 // Aggregate topographic error.
685                 if (!net.getNeighbours(best).contains(secondBest)) {
686                     // Increment count if first and second best matching units
687                     // are not neighbours.
688                     topographicError[rowBest][colBest] += 1;
689                 }
690             }
691 
692             for (int r = 0; r < nR; r++) {
693                 for (int c = 0; c < nC; c++) {
694                     final Neuron neuron = map.getNeuron(r, c);
695                     final Collection<Neuron> neighbours = net.getNeighbours(neuron);
696                     final double[] features = neuron.getFeatures();
697                     double uDistance = 0;
698                     int neighbourCount = 0;
699                     for (final Neuron n : neighbours) {
700                         ++neighbourCount;
701                         uDistance += DISTANCE.applyAsDouble(features, n.getFeatures());
702                     }
703 
704                     final int hitCount = hitCounter[r][c];
705                     if (hitCount != 0) {
706                         hitHistogram[r][c] = hitCount / (double) numSamples;
707                         quantizationError[r][c] /= hitCount;
708                         topographicError[r][c] /= hitCount;
709                     }
710 
711                     uMatrix[r][c] = uDistance / neighbourCount;
712                 }
713             }
714 
715             return new DataVisualization(numSamples,
716                                          hitHistogram,
717                                          quantizationError,
718                                          topographicError,
719                                          uMatrix);
720         }
721 
722         /**
723          * @return the total number of samples.
724          */
725         public int getNumberOfSamples() {
726             return numberOfSamples;
727         }
728 
729         /**
730          * @return the quantization error.
731          * Each bin will contain the average of the distances between samples
732          * mapped to the corresponding unit and the weight vector of that unit.
733          * @see #getMeanQuantizationError()
734          */
735         public double[][] getQuantizationError() {
736             return copy(quantizationError);
737         }
738 
739         /**
740          * @return the topographic error.
741          * Each bin will contain the number of data for which the first and
742          * second best matching units are not adjacent in the map.
743          * @see #getMeanTopographicError()
744          */
745         public double[][] getTopographicError() {
746             return copy(topographicError);
747         }
748 
749         /**
750          * @return the hits histogram (normalized).
751          * Each bin will contain the number of data for which the corresponding
752          * neuron is the best matching unit.
753          */
754         public double[][] getNormalizedHits() {
755             return copy(hitHistogram);
756         }
757 
758         /**
759          * @return the U-matrix.
760          * Each bin will contain the average distance between a unit and all its
761          * neighbours will be computed (and stored in the pixel corresponding to
762          * that unit of the 2D-map).  The number of neighbours taken into account
763          * depends on the network {@link org.apache.commons.math4.neuralnet.SquareNeighbourhood
764          * neighbourhood type}.
765          */
766         public double[][] getUMatrix() {
767             return copy(uMatrix);
768         }
769 
770         /**
771          * @return the mean (hit-weighted) quantization error.
772          * @see #getQuantizationError()
773          */
774         public double getMeanQuantizationError() {
775             return meanQuantizationError;
776         }
777 
778         /**
779          * @return the mean (hit-weighted) topographic error.
780          * @see #getTopographicError()
781          */
782         public double getMeanTopographicError() {
783             return meanTopographicError;
784         }
785 
786         /**
787          * @param orig Source.
788          * @return a deep copy of the original array.
789          */
790         private static double[][] copy(double[][] orig) {
791             final double[][] copy = new double[orig.length][];
792             for (int i = 0; i < orig.length; i++) {
793                 copy[i] = orig[i].clone();
794             }
795 
796             return copy;
797         }
798 
799         /**
800          * @param metrics Metrics.
801          * @param normalizedHits Hits histogram (normalized).
802          * @return the hit-weighted mean of the given {@code metrics}.
803          */
804         private static double hitWeightedMean(double[][] metrics,
805                                               double[][] normalizedHits) {
806             double mean = 0;
807             final int rows = metrics.length;
808             final int cols = metrics[0].length;
809             for (int i = 0; i < rows; i++) {
810                 for (int j = 0; j < cols; j++) {
811                     mean += normalizedHits[i][j] * metrics[i][j];
812                 }
813             }
814 
815             return mean;
816         }
817     }
818 }