/// <summary> /// Updates activations thresholds according to the Contrastive Divergence bias update rules. /// </summary> /// <param name="layer">The layer in which the units reside.</param> /// <param name="thresholdDelta">The threshold delta (positive phase activation probabilities - negative phase activation probabilities).</param> private void UpdateThresholdsCD(int layer, double[] thresholdDelta) { System.Diagnostics.Debug.Assert( thresholdDelta.Length == this.GetUnitCount(layer), "The array with the threshold deltas should have the same number of elements as there are units in the layer"); var random = ThreadSafeRandom.GetThreadRandom(); for (int unit = 0; unit < thresholdDelta.Length; unit++) { if (thresholdDelta[unit] != 0 && random.NextDouble() < Math.Abs(thresholdDelta[unit]) * LearningProbability) { if (thresholdDelta[unit] > 0) { this.IndividualActivationThresholds[layer][unit]--; #if DEBUG //System.Diagnostics.Debug.WriteLine("Unit {0} in layer {1} lowered its threshold to {2}", unit, layer, this.IndividualActivationThresholds[layer][unit]); #endif } else if (thresholdDelta[unit] < 0) { this.IndividualActivationThresholds[layer][unit]++; #if DEBUG //System.Diagnostics.Debug.WriteLine("Unit {0} in layer {1} increased its threshold to {2}", unit, layer, this.IndividualActivationThresholds[layer][unit]); #endif } } } }
/// <summary> /// Randomly shuffles a list. /// </summary> /// <typeparam name="T">The type of elements in the list.</typeparam> /// <param name="list">The list to shuffle.</param> /// <returns>An enumerator that traverses the list in a random order.</returns> /// <remarks> /// The following code is courtesy of Michael Herman (foson). See: /// http://stackoverflow.com/questions/375351/most-efficient-way-to-randomly-sort-shuffle-a-list-of-integers-in-c-sharp/ /// </remarks> public static IEnumerable <T> AsRandom <T>(this IList <T> list) { int[] indexes = Enumerable.Range(0, list.Count).ToArray(); Random generator = ThreadSafeRandom.GetThreadRandom(); for (int i = 0; i < list.Count; ++i) { int position = generator.Next(i, list.Count); yield return(list[indexes[position]]); indexes[position] = indexes[i]; } }
/// <summary> /// Gets the degree of the specified unit. /// </summary> /// <param name="index">The index of the unit.</param> /// <param name="partition">The partition that the unit resides in.</param> /// <returns> /// The degree of the unit. /// </returns> //public abstract int GetDegree(int index, Partition partition); /// <summary> /// Stochastically generates connections (edges) according to the specified probabilities. /// </summary> /// <returns> /// The total number of connections (edges) created. /// </returns> public int Initialize(int minWeight, int maxWeight, double[] weightProbabilities) { int numberOfWeights = maxWeight - minWeight + 1; // // TODO: Use an exact number type for the probabilities? // if (numberOfWeights != weightProbabilities.Length) { throw new ArgumentException("The number of weights and the size of the weightProbabilities array should match", "weightProbabilities"); } var cumulativeProbabilities = new double[weightProbabilities.Length]; var runningSum = 0.0; for (int i = 0; i < numberOfWeights; i++) { ValidationUtils.ValidateProbability(weightProbabilities[i], "weightProbabilities[" + i + "]"); cumulativeProbabilities[i] = weightProbabilities[i] + runningSum; runningSum += weightProbabilities[i]; } var random = ThreadSafeRandom.GetThreadRandom(); // // TODO: Clear everything first? // Consider all possible edges, then flip a coin (so to speak) // int edgesCreated = 0; for (int leftUnit = 0; leftUnit < this.unitCountLeft; leftUnit++) { for (int rightUnit = 0; rightUnit < this.unitCountRight; rightUnit++) { var randomValue = random.NextDouble(); for (int weightIndex = 0; weightIndex < numberOfWeights; weightIndex++) { if (randomValue < cumulativeProbabilities[weightIndex]) { this.SetEdge(leftUnit, rightUnit, minWeight + weightIndex); edgesCreated++; break; } } } } // // Return the number of edges created // return(edgesCreated); }
/// <summary> /// Stochastically makes edges more excitatory or inhibitory according to the supplied <paramref name="changes" /> matrix. /// </summary> /// <param name="changes">A 2D matrix indicating where edges (synapses) should potentially be altered.</param> /// <param name="layer">The connection layer to alter.</param> /// <param name="joint">Should be set to <c>true</c> if joint learning is taking place, otherwise <c>false</c>.</param> protected virtual void UpdateEdges(double[,] changes, int layer, bool joint) { System.Diagnostics.Debug.Assert( changes.GetLength(0) == this.GetUnitCount(layer), "changes matrix size doesn't match the size of the weight matrix"); System.Diagnostics.Debug.Assert( changes.GetLength(1) == this.GetUnitCount(layer + 1), "changes matrix size doesn't match the size of the weight matrix"); // // Traverse the matrix // Parallel.For( 0, changes.GetLength(0), (leftUnit) => { var random = ThreadSafeRandom.GetThreadRandom(); for (int rightUnit = 0; rightUnit < changes.GetLength(1); rightUnit++) { // // Flip a coin to see if we should modify this edge // Note that the strength of the learning signal affects the probability // TODO: Parameterize the pseudo-learning-rate constant // if (changes[leftUnit, rightUnit] != 0.0 && random.NextDouble() < Math.Abs(changes[leftUnit, rightUnit]) * LearningProbability) { // // Get the current type of edge and, if possible, make it either more excitatory or inhibitory, // depending on the learning signal. // var edge = this.Connections[layer].GetEdge(leftUnit, rightUnit); if (changes[leftUnit, rightUnit] > 0) { if (edge.Weight < this.MaxWeight) { this.Connections[layer].SetEdge(leftUnit, rightUnit, edge.Weight + 1); } } else { if (edge.Weight > this.MinWeight) { this.Connections[layer].SetEdge(leftUnit, rightUnit, edge.Weight - 1); } } } } }); }
/// <summary> /// Processes a training sample. /// </summary> /// <param name="connections">The inter-layer connections.</param> /// <param name="activatedLeft">The activated units in the left partition.</param> /// <param name="activatedRight">The activated units in the right partition.</param> public override void ProcessSample(IInterLayerConnections connections, List <int> activatedLeft, List <int> activatedRight) { var random = ThreadSafeRandom.GetThreadRandom(); foreach (var outputUnit in activatedRight) { // // Find out how many strong (excitatory) edges are connected to this unit // int degree = connections.GetNeighbours(outputUnit, Partition.Right, true).Count(); // // If it's equal to the target degree, move on to the next output unit // if (degree == this.targetDegree) { ////Debug.WriteLine("Skipping output unit " + outputUnit + " because it has reached its target degree"); continue; } // // Calculate the probability of an edge strengthening/weakening // double p = (double)(this.targetDegree - degree) / (2.0 * this.MaxInputActivations); // // Stochastically modify edges which connect concurrently active units // foreach (var inputUnit in activatedLeft) { var edge = connections.GetEdge(inputUnit, outputUnit); if (edge.Weight == 0 && p > 0.0 && random.NextDouble() < p) { connections.SetEdge(inputUnit, outputUnit, 1); #if DEBUG Debug.WriteLine("Adding edge between " + inputUnit + " and " + outputUnit); #endif } else if (edge.Weight == 1 && p < 0.0 && random.NextDouble() < -p) { connections.SetEdge(inputUnit, outputUnit, 0); #if DEBUG Debug.WriteLine("Removing edge between " + inputUnit + " and " + outputUnit); #endif } } } // // TODO: Prune from the left as well? // }
/// <summary> /// Processes a training sample. /// </summary> /// <param name="connections">The inter-layer connections.</param> /// <param name="activatedLeft">The activated units in the left partition.</param> /// <param name="activatedRight">The activated units in the right partition.</param> public override void ProcessSample(IInterLayerConnections connections, List <int> activatedLeft, List <int> activatedRight) { var random = ThreadSafeRandom.GetThreadRandom(); foreach (var leftUnit in activatedLeft) { foreach (var rightUnit in activatedRight) { if (connections.GetEdge(leftUnit, rightUnit).Weight == 0) { if (random.NextDouble() < this.strengtheningProbability) { connections.SetEdge(leftUnit, rightUnit, 1); } } } } }
/// <summary> /// Stochastically makes edges more excitatory or inhibitory according to the supplied <paramref name="changes"/> matrix. /// </summary> /// <param name="changes">A 2D matrix indicating where edges (synapses) should potentially be altered.</param> /// <param name="layer">The connection layer to alter.</param> private void UpdateEdges(short[,] changes, int layer) { // // Traverse the matrix // Parallel.For( 0, changes.GetLength(0), (leftUnit) => { var random = ThreadSafeRandom.GetThreadRandom(); for (int rightUnit = 0; rightUnit < changes.GetLength(1); rightUnit++) { // // If the learning signal indicates that this edge should be modified, flip a coin // The probability of the edge being modified can be considered to be a pseudo-learning-rate // TODO: Parameterize the probability of an edge being updated // if (changes[leftUnit, rightUnit] != 0 && random.NextDouble() < LearningProbability) { // // Get the current type of edge and, if possible, make it either more excitatory or inhibitory, // depending on the learning signal. // var edge = this.Connections[layer].GetEdge(leftUnit, rightUnit); if (changes[leftUnit, rightUnit] > 0) { if (edge.Weight < this.MaxWeight) { this.Connections[layer].SetEdge(leftUnit, rightUnit, edge.Weight + 1); } } else { if (edge.Weight > this.MinWeight) { this.Connections[layer].SetEdge(leftUnit, rightUnit, edge.Weight - 1); } } } } }); }
/// <summary> /// Generates random XOR samples. /// </summary> /// <param name="count">The number of samples to generate.</param> /// <returns>The generated samples, labeled as 0 if the XOR of the two bits is false and 1 otherwise.</returns> /// <remarks> /// The visualization mechanism in the GUI assumes data to consist of square pictures. Each bit is therefore duplicated, /// resulting in each sample having size 4. Since the sample data is of type <code>byte[]</code>, every 1-bit is represented with /// <see cref="byte.MaxValue"/>.</remarks> private LabelledSample[] GenerateSamples(int count) { var random = ThreadSafeRandom.GetThreadRandom(); var result = new LabelledSample[count]; var bits = new bool[2]; for (int i = 0; i < count; i++) { // // Get two random bits // bits[0] = random.NextDouble() > 0.5 ? true : false; bits[1] = random.NextDouble() > 0.5 ? true : false; // // Are both bits zero? // if (!bits[0] && !bits[1]) { // // The current design of our neural nets makes it impossible to recognize an all-zero data vector since no // activations can result from it. For now, we work around this issue by // bits[0] = true; bits[1] = true; } // // Populate the sample data array // var sampleData = new byte[4]; sampleData[0] = sampleData[1] = bits[0] ? byte.MaxValue : byte.MinValue; sampleData[2] = sampleData[3] = bits[1] ? byte.MaxValue : byte.MinValue; // // Calculate the label // int label = bits[0] ^ bits[1] ? 1 : 0; // // Add this sample to the resulting array // result[i] = new LabelledSample(label, sampleData); } return(result); }