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 }