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}