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