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; 019 020import java.util.concurrent.atomic.AtomicReference; 021import java.util.concurrent.atomic.AtomicLong; 022 023import org.apache.commons.numbers.core.Precision; 024import org.apache.commons.math4.neuralnet.internal.NeuralNetException; 025 026/** 027 * Describes a neuron element of a neural network. 028 * 029 * This class aims to be thread-safe. 030 * 031 * @since 3.3 032 */ 033public class Neuron { 034 /** Identifier. */ 035 private final long identifier; 036 /** Length of the feature set. */ 037 private final int size; 038 /** Neuron data. */ 039 private final AtomicReference<double[]> features; 040 /** Number of attempts to update a neuron. */ 041 private final AtomicLong numberOfAttemptedUpdates = new AtomicLong(0); 042 /** Number of successful updates of a neuron. */ 043 private final AtomicLong numberOfSuccessfulUpdates = new AtomicLong(0); 044 045 /** 046 * Creates a neuron. 047 * The size of the feature set is fixed to the length of the given 048 * argument. 049 * <br> 050 * Constructor is package-private: Neurons must be 051 * {@link Network#createNeuron(double[]) created} by the network 052 * instance to which they will belong. 053 * 054 * @param identifier Identifier (assigned by the {@link Network}). 055 * @param features Initial values of the feature set. 056 */ 057 Neuron(long identifier, 058 double[] features) { 059 this.identifier = identifier; 060 this.size = features.length; 061 this.features = new AtomicReference<>(features.clone()); 062 } 063 064 /** 065 * Performs a deep copy of this instance. 066 * Upon return, the copied and original instances will be independent: 067 * Updating one will not affect the other. 068 * 069 * @return a new instance with the same state as this instance. 070 * @since 3.6 071 */ 072 public synchronized Neuron copy() { 073 final Neuron copy = new Neuron(getIdentifier(), 074 getFeatures()); 075 copy.numberOfAttemptedUpdates.set(numberOfAttemptedUpdates.get()); 076 copy.numberOfSuccessfulUpdates.set(numberOfSuccessfulUpdates.get()); 077 078 return copy; 079 } 080 081 /** 082 * Gets the neuron's identifier. 083 * 084 * @return the identifier. 085 */ 086 public long getIdentifier() { 087 return identifier; 088 } 089 090 /** 091 * Gets the length of the feature set. 092 * 093 * @return the number of features. 094 */ 095 public int getSize() { 096 return size; 097 } 098 099 /** 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}