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 }