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; 19 20 import java.util.concurrent.atomic.AtomicReference; 21 import java.util.concurrent.atomic.AtomicLong; 22 23 import org.apache.commons.numbers.core.Precision; 24 import org.apache.commons.math4.neuralnet.internal.NeuralNetException; 25 26 /** 27 * Describes a neuron element of a neural network. 28 * 29 * This class aims to be thread-safe. 30 * 31 * @since 3.3 32 */ 33 public class Neuron { 34 /** Identifier. */ 35 private final long identifier; 36 /** Length of the feature set. */ 37 private final int size; 38 /** Neuron data. */ 39 private final AtomicReference<double[]> features; 40 /** Number of attempts to update a neuron. */ 41 private final AtomicLong numberOfAttemptedUpdates = new AtomicLong(0); 42 /** Number of successful updates of a neuron. */ 43 private final AtomicLong numberOfSuccessfulUpdates = new AtomicLong(0); 44 45 /** 46 * Creates a neuron. 47 * The size of the feature set is fixed to the length of the given 48 * argument. 49 * <br> 50 * Constructor is package-private: Neurons must be 51 * {@link Network#createNeuron(double[]) created} by the network 52 * instance to which they will belong. 53 * 54 * @param identifier Identifier (assigned by the {@link Network}). 55 * @param features Initial values of the feature set. 56 */ 57 Neuron(long identifier, 58 double[] features) { 59 this.identifier = identifier; 60 this.size = features.length; 61 this.features = new AtomicReference<>(features.clone()); 62 } 63 64 /** 65 * Performs a deep copy of this instance. 66 * Upon return, the copied and original instances will be independent: 67 * Updating one will not affect the other. 68 * 69 * @return a new instance with the same state as this instance. 70 * @since 3.6 71 */ 72 public synchronized Neuron copy() { 73 final Neuron copy = new Neuron(getIdentifier(), 74 getFeatures()); 75 copy.numberOfAttemptedUpdates.set(numberOfAttemptedUpdates.get()); 76 copy.numberOfSuccessfulUpdates.set(numberOfSuccessfulUpdates.get()); 77 78 return copy; 79 } 80 81 /** 82 * Gets the neuron's identifier. 83 * 84 * @return the identifier. 85 */ 86 public long getIdentifier() { 87 return identifier; 88 } 89 90 /** 91 * Gets the length of the feature set. 92 * 93 * @return the number of features. 94 */ 95 public int getSize() { 96 return size; 97 } 98 99 /** 100 * Gets the neuron's features. 101 * 102 * @return a copy of the neuron's features. 103 */ 104 public double[] getFeatures() { 105 return features.get().clone(); 106 } 107 108 /** 109 * Tries to atomically update the neuron's features. 110 * Update will be performed only if the expected values match the 111 * current values.<br> 112 * In effect, when concurrent threads call this method, the state 113 * could be modified by one, so that it does not correspond to the 114 * the state assumed by another. 115 * Typically, a caller {@link #getFeatures() retrieves the current state}, 116 * and uses it to compute the new state. 117 * During this computation, another thread might have done the same 118 * thing, and updated the state: If the current thread were to proceed 119 * with its own update, it would overwrite the new state (which might 120 * already have been used by yet other threads). 121 * To prevent this, the method does not perform the update when a 122 * concurrent modification has been detected, and returns {@code false}. 123 * When this happens, the caller should fetch the new current state, 124 * redo its computation, and call this method again. 125 * 126 * @param expect Current values of the features, as assumed by the caller. 127 * Update will never succeed if the contents of this array does not match 128 * the values returned by {@link #getFeatures()}. 129 * @param update Features's new values. 130 * @return {@code true} if the update was successful, {@code false} 131 * otherwise. 132 * @throws IllegalArgumentException if the length of {@code update} is 133 * not the same as specified in the {@link #Neuron(long,double[]) 134 * constructor}. 135 */ 136 public boolean compareAndSetFeatures(double[] expect, 137 double[] update) { 138 if (update.length != size) { 139 throw new NeuralNetException(NeuralNetException.SIZE_MISMATCH, 140 update.length, size); 141 } 142 143 // Get the internal reference. Note that this must not be a copy; 144 // otherwise the "compareAndSet" below will always fail. 145 final double[] current = features.get(); 146 if (!containSameValues(current, expect)) { 147 // Some other thread already modified the state. 148 return false; 149 } 150 151 // Increment attempt counter. 152 numberOfAttemptedUpdates.incrementAndGet(); 153 154 if (features.compareAndSet(current, update.clone())) { 155 // The current thread could atomically update the state (attempt succeeded). 156 numberOfSuccessfulUpdates.incrementAndGet(); 157 return true; 158 } else { 159 // Some other thread came first (attempt failed). 160 return false; 161 } 162 } 163 164 /** 165 * Retrieves the number of calls to the 166 * {@link #compareAndSetFeatures(double[],double[]) compareAndSetFeatures} 167 * method. 168 * Note that if the caller wants to use this method in combination with 169 * {@link #getNumberOfSuccessfulUpdates()}, additional synchronization 170 * may be required to ensure consistency. 171 * 172 * @return the number of update attempts. 173 * @since 3.6 174 */ 175 public long getNumberOfAttemptedUpdates() { 176 return numberOfAttemptedUpdates.get(); 177 } 178 179 /** 180 * Retrieves the number of successful calls to the 181 * {@link #compareAndSetFeatures(double[],double[]) compareAndSetFeatures} 182 * method. 183 * Note that if the caller wants to use this method in combination with 184 * {@link #getNumberOfAttemptedUpdates()}, additional synchronization 185 * may be required to ensure consistency. 186 * 187 * @return the number of successful updates. 188 * @since 3.6 189 */ 190 public long getNumberOfSuccessfulUpdates() { 191 return numberOfSuccessfulUpdates.get(); 192 } 193 194 /** 195 * Checks whether the contents of both arrays is the same. 196 * 197 * @param current Current values. 198 * @param expect Expected values. 199 * @throws IllegalArgumentException if the length of {@code expect} 200 * is not the same as specified in the {@link #Neuron(long,double[]) 201 * constructor}. 202 * @return {@code true} if the arrays contain the same values. 203 */ 204 private boolean containSameValues(double[] current, 205 double[] expect) { 206 if (expect.length != size) { 207 throw new NeuralNetException(NeuralNetException.SIZE_MISMATCH, 208 expect.length, size); 209 } 210 211 for (int i = 0; i < size; i++) { 212 if (!Precision.equals(current[i], expect[i])) { 213 return false; 214 } 215 } 216 return true; 217 } 218 }