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.neuralnet.twod; 019 020import java.util.List; 021import java.util.ArrayList; 022import java.util.Iterator; 023import java.util.Collection; 024 025import org.apache.commons.math4.neuralnet.DistanceMeasure; 026import org.apache.commons.math4.neuralnet.EuclideanDistance; 027import org.apache.commons.math4.neuralnet.FeatureInitializer; 028import org.apache.commons.math4.neuralnet.Network; 029import org.apache.commons.math4.neuralnet.Neuron; 030import org.apache.commons.math4.neuralnet.SquareNeighbourhood; 031import org.apache.commons.math4.neuralnet.MapRanking; 032import org.apache.commons.math4.neuralnet.internal.NeuralNetException; 033import org.apache.commons.math4.neuralnet.twod.util.LocationFinder; 034 035/** 036 * Neural network with the topology of a two-dimensional surface. 037 * Each neuron defines one surface element. 038 * <br> 039 * This network is primarily intended to represent a 040 * <a href="http://en.wikipedia.org/wiki/Kohonen"> 041 * Self Organizing Feature Map</a>. 042 * 043 * @see org.apache.commons.math4.neuralnet.sofm 044 * @since 3.3 045 */ 046public class NeuronSquareMesh2D 047 implements Iterable<Neuron> { 048 /** Minimal number of rows or columns. */ 049 private static final int MIN_ROWS = 2; 050 /** Underlying network. */ 051 private final Network network; 052 /** Number of rows. */ 053 private final int numberOfRows; 054 /** Number of columns. */ 055 private final int numberOfColumns; 056 /** Wrap. */ 057 private final boolean wrapRows; 058 /** Wrap. */ 059 private final boolean wrapColumns; 060 /** Neighbourhood type. */ 061 private final SquareNeighbourhood neighbourhood; 062 /** 063 * Mapping of the 2D coordinates (in the rectangular mesh) to 064 * the neuron identifiers (attributed by the {@link #network} 065 * instance). 066 */ 067 private final long[][] identifiers; 068 069 /** 070 * Horizontal (along row) direction. 071 * @since 3.6 072 */ 073 public enum HorizontalDirection { 074 /** Column at the right of the current column. */ 075 RIGHT, 076 /** Current column. */ 077 CENTER, 078 /** Column at the left of the current column. */ 079 LEFT, 080 } 081 /** 082 * Vertical (along column) direction. 083 * @since 3.6 084 */ 085 public enum VerticalDirection { 086 /** Row above the current row. */ 087 UP, 088 /** Current row. */ 089 CENTER, 090 /** Row below the current row. */ 091 DOWN, 092 } 093 094 /** 095 * @param wrapRowDim Whether to wrap the first dimension (i.e the first 096 * and last neurons will be linked together). 097 * @param wrapColDim Whether to wrap the second dimension (i.e the first 098 * and last neurons will be linked together). 099 * @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}