コード例 #1
0
 /// <summary>
 /// Constructs with the provided input/output node count, activation function library,
 /// node and connection lists.
 /// </summary>
 public NetworkDefinition(int inputNodeCount, int outputNodeCount,
                          IActivationFunctionLibrary activationFnLib,
                          INodeList nodeList, IConnectionList connectionList)
 {
     _inputNodeCount  = inputNodeCount;
     _outputNodeCount = outputNodeCount;
     _activationFnLib = activationFnLib;
     _nodeList        = nodeList;
     _connectionList  = connectionList;
     _isAcyclic       = !CyclicNetworkTest.IsNetworkCyclic(this);
 }
コード例 #2
0
        private static void InternalDecode(INetworkDefinition networkDef,
                                           out List <Neuron> neuronList,
                                           out List <Connection> connectionList)
        {
            // Build a list of neurons.
            INodeList nodeDefList = networkDef.NodeList;
            int       nodeCount   = nodeDefList.Count;

            neuronList = new List <Neuron>(nodeCount);

            // A dictionary of neurons keyed on their innovation ID.
            var neuronDictionary = new Dictionary <uint, Neuron>(nodeCount);

            // Loop neuron genes.
            IActivationFunctionLibrary activationFnLib = networkDef.ActivationFnLibrary;

            for (int i = 0; i < nodeCount; i++)
            {   // Create a Neuron, add it to the neuron list and add an entry into neuronDictionary -
                // required for next loop.
                INetworkNode nodeDef = nodeDefList[i];

                // Note that we explicitly translate between the two NeuronType enums even though
                // they define the same types and could therefore be cast from one to the other.
                // We do this to keep genome and phenome classes completely separated and also to
                // prevent bugs - e.g. if one of the enums is changed then TranslateNeuronType() will
                // need to be modified to prevent exceptions at runtime. Otherwise a silent bug may
                // be introduced.
                Neuron neuron = new Neuron(nodeDef.Id,
                                           nodeDef.NodeType,
                                           activationFnLib.GetFunction(nodeDef.ActivationFnId),
                                           nodeDef.AuxState);
                neuronList.Add(neuron);
                neuronDictionary.Add(nodeDef.Id, neuron);
            }

            // Build a list of connections.
            IConnectionList connectionDefList = networkDef.ConnectionList;
            int             connectionCount   = connectionDefList.Count;

            connectionList = new List <Connection>(connectionCount);

            // Loop connection genes.
            for (int i = 0; i < connectionCount; i++)
            {
                INetworkConnection connDef = connectionDefList[i];
                connectionList.Add(
                    new Connection(neuronDictionary[connDef.SourceNodeId],
                                   neuronDictionary[connDef.TargetNodeId],
                                   connDef.Weight));
            }
        }
コード例 #3
0
        /// <summary>
        /// Create an IOGraph that represents the structure described by the provided INetworkDefinition.
        /// </summary>
        public IOGraph CreateGraph(INetworkDefinition networkDef)
        {
            // Perform depth analysis of network.
            NetworkDepthInfo depthInfo;

            if (networkDef.IsAcyclic)
            {
                AcyclicNetworkDepthAnalysis depthAnalysis = new AcyclicNetworkDepthAnalysis();
                depthInfo = depthAnalysis.CalculateNodeDepths(networkDef);
            }
            else
            {
                CyclicNetworkDepthAnalysis depthAnalysis = new CyclicNetworkDepthAnalysis();
                depthInfo = depthAnalysis.CalculateNodeDepths(networkDef);
            }

            // Create an IOGraph, allocating storage for the node lists.
            INodeList nodeList    = networkDef.NodeList;
            int       nodeCount   = nodeList.Count;
            int       inputCount  = networkDef.InputNodeCount + 1; // + to count bias as an input layer node.
            int       outputCount = networkDef.OutputNodeCount;
            int       hiddenCount = nodeCount - (inputCount + outputCount);
            IOGraph   ioGraph     = new IOGraph(inputCount, outputCount, hiddenCount, 0f, depthInfo._networkDepth);

            // We also build a dictionary of nodes keyed by innovation ID. This is used later
            // to assign connections to nodes.
            Dictionary <uint, GraphNode> nodeDict = new Dictionary <uint, GraphNode>(nodeCount);

            // Loop input nodes.
            int idx = 0;

            for (int i = 0; i < inputCount; i++, idx++)
            {   // Create node, assign it a tag and add it to the node dictionary and the
                // input node list of the IOGraph.
                uint      innovationId = nodeList[idx].Id;
                GraphNode node         = new GraphNode(innovationId.ToString());
                node.AuxData = CreateGraphNodeAuxData(nodeList[idx]);
                node.Depth   = depthInfo._nodeDepthArr[idx];
                nodeDict.Add(innovationId, node);
                ioGraph.InputNodeList.Add(node);
            }

            // Loop output nodes.
            for (int i = 0; i < outputCount; i++, idx++)
            {   // Create node, assign it a tag and add it to the node dictionary and the
                // output node list of the IOGraph.
                uint      innovationId = nodeList[idx].Id;
                GraphNode node         = new GraphNode(innovationId.ToString());
                node.AuxData = CreateGraphNodeAuxData(nodeList[idx]);
                node.Depth   = depthInfo._nodeDepthArr[idx];
                nodeDict.Add(innovationId, node);
                ioGraph.OutputNodeList.Add(node);
            }

            // Loop hidden nodes.
            for (; idx < nodeCount; idx++)
            {   // Create node, assign it a tag and add it to the node dictionary and the
                // hidden node list of the IOGraph.
                uint      innovationId = nodeList[idx].Id;
                GraphNode node         = new GraphNode(innovationId.ToString());
                node.AuxData = CreateGraphNodeAuxData(nodeList[idx]);
                node.Depth   = depthInfo._nodeDepthArr[idx];
                nodeDict.Add(innovationId, node);
                ioGraph.HiddenNodeList.Add(node);
            }

            // Loop connections. Build GraphConnection objects and connect them to their source
            // and target nodes.
            double          maxAbsWeight   = 0.1; // Start at a non-zero value to prevent possibility of a divide by zero occuring.
            IConnectionList connectionList = networkDef.ConnectionList;
            int             connCount      = connectionList.Count;

            for (int i = 0; i < connCount; i++)
            {
                // Create connection object and assign its source and target nodes.
                INetworkConnection connection = connectionList[i];
                GraphNode          sourceNode = nodeDict[connection.SourceNodeId];
                GraphNode          targetNode = nodeDict[connection.TargetNodeId];
                GraphConnection    conn       = new GraphConnection(sourceNode, targetNode, (float)connection.Weight);

                // Add the connection to the connection lists on the source and target nodes.
                sourceNode.OutConnectionList.Add(conn);
                targetNode.InConnectionList.Add(conn);

                // Track weight range oevr all connections.
                double absWeight = Math.Abs(connection.Weight);
                if (absWeight > maxAbsWeight)
                {
                    maxAbsWeight = absWeight;
                }
            }

            ioGraph.ConnectionWeightRange = (float)maxAbsWeight;
            return(ioGraph);
        }
コード例 #4
0
        /// <summary>
        /// Creates a AcyclicNetwork from an INetworkDefinition.
        /// </summary>
        public static FastAcyclicNetwork CreateFastAcyclicNetwork(INetworkDefinition networkDef)
        {
            Debug.Assert(!CyclicNetworkTest.IsNetworkCyclic(networkDef), "Attempt to decode a cyclic network into a FastAcyclicNetwork.");

            // Determine the depth of each node in the network.
            // Node depths are used to separate the nodes into depth based layers, these layers can then be
            // used to determine the order in which signals are propagated through the network.
            AcyclicNetworkDepthAnalysis depthAnalysis = new AcyclicNetworkDepthAnalysis();
            NetworkDepthInfo            netDepthInfo  = depthAnalysis.CalculateNodeDepths(networkDef);

            // Construct an array of NodeInfo, ordered by node depth.
            // Create/populate NodeInfo array.
            int[]     nodeDepthArr = netDepthInfo._nodeDepthArr;
            INodeList nodeList     = networkDef.NodeList;
            int       nodeCount    = nodeList.Count;

            NodeInfo[] nodeInfoByDepth = new NodeInfo[nodeCount];
            for (int i = 0; i < nodeCount; i++)
            {
                nodeInfoByDepth[i]._nodeId        = nodeList[i].Id;
                nodeInfoByDepth[i]._definitionIdx = i;
                nodeInfoByDepth[i]._nodeDepth     = nodeDepthArr[i];
            }

            // Sort NodeInfo array.
            // We use an IComparer here because an anonymous method is not accepted on the method overload that accepts
            // a sort range, which we use to avoid sorting the input and bias nodes. Sort() performs an unstable sort therefore
            // we must restrict the range of the sort to ensure the input and bias node indexes are unchanged. Restricting the
            // sort to the required range is also more efficient (less items to sort).
            int inputAndBiasCount = networkDef.InputNodeCount + 1;

            Array.Sort(nodeInfoByDepth, inputAndBiasCount, nodeCount - inputAndBiasCount, NodeDepthComparer.__NodeDepthComparer);

            // Array of live node indexes indexed by their index in the original network definition. This allows us to
            // locate the position of input and output nodes in their new positions in the live network data structures.
            int[] newIdxByDefinitionIdx = new int[nodeCount];

            // Dictionary of live node indexes keyed by node ID. This allows us to convert the network definition connection
            // endpoints from node IDs to indexes into the live/runtime network data structures.
            Dictionary <uint, int> newIdxById = new Dictionary <uint, int>(nodeCount);

            // Populate both the lookup array and dictionary.
            for (int i = 0; i < nodeCount; i++)
            {
                NodeInfo nodeInfo = nodeInfoByDepth[i];
                newIdxByDefinitionIdx[nodeInfo._definitionIdx] = i;
                newIdxById.Add(nodeInfo._nodeId, i);
            }

            // Make a copy of the sub-range of newIdxByDefinitionIdx that represents the output nodes.
            int outputCount = networkDef.OutputNodeCount;

            int[] outputNeuronIdxArr = new int[outputCount];
            // Note. 'inputAndBiasCount' holds the index of the first output node.
            Array.Copy(newIdxByDefinitionIdx, inputAndBiasCount, outputNeuronIdxArr, 0, outputCount);

            // Construct arrays with additional 'per node' data/refs (activation functions, activation fn auxiliary data).
            IActivationFunctionLibrary activationFnLibrary = networkDef.ActivationFnLibrary;

            IActivationFunction[] nodeActivationFnArr = new IActivationFunction[nodeCount];
            double[][]            nodeAuxArgsArray    = new double[nodeCount][];
            for (int i = 0; i < nodeCount; i++)
            {
                int definitionIdx = nodeInfoByDepth[i]._definitionIdx;
                nodeActivationFnArr[i] = activationFnLibrary.GetFunction(nodeList[definitionIdx].ActivationFnId);
                nodeAuxArgsArray[i]    = nodeList[definitionIdx].AuxState;
            }


            //=== Create array of FastConnection(s).

            // Loop the connections and lookup the node IDs for each connection's end points using newIdxById.
            IConnectionList connectionList  = networkDef.ConnectionList;
            int             connectionCount = connectionList.Count;

            FastConnection[] fastConnectionArray = new FastConnection[connectionCount];

            for (int i = 0; i < connectionCount; i++)
            {
                INetworkConnection conn = connectionList[i];
                fastConnectionArray[i]._srcNeuronIdx = newIdxById[conn.SourceNodeId];
                fastConnectionArray[i]._tgtNeuronIdx = newIdxById[conn.TargetNodeId];
                fastConnectionArray[i]._weight       = conn.Weight;
            }

            // Sort fastConnectionArray by source node index. This allows us to activate the connections in the
            // order they are present within the network (by depth). We also secondary sort by target index to
            // improve CPU cache coherency of the data (in order accesses that are as close to each other as possible).
            Array.Sort(fastConnectionArray, delegate(FastConnection x, FastConnection y)
            {
                if (x._srcNeuronIdx < y._srcNeuronIdx)
                {
                    return(-1);
                }
                if (x._srcNeuronIdx > y._srcNeuronIdx)
                {
                    return(1);
                }
                // Secondary sort on target index.
                if (x._tgtNeuronIdx < y._tgtNeuronIdx)
                {
                    return(-1);
                }
                if (x._tgtNeuronIdx > y._tgtNeuronIdx)
                {
                    return(1);
                }
                // Connections are equal (this should not actually happen).
                return(0);
            });

            // Create an array of LayerInfo(s). Each LayerInfo contains the index + 1 of both the last node and last
            // connection in that layer.
            // The array is in order of depth, from layer zero (bias and inputs nodes) to the last layer
            // (usually output nodes, but not necessarily if there is a dead end pathway with a high number of hops).
            // Note. There is guaranteed to be at least one connection with a source at a given depth level, this is
            // because for there to be a layer N there must necessarily be a connection from a node in layer N-1
            // to a node in layer N.
            int netDepth = netDepthInfo._networkDepth;

            LayerInfo[] layerInfoArr = new LayerInfo[netDepth];

            // Scanning over nodes can start at inputAndBiasCount instead of zero,
            // because we know that all nodes prior to that index are at depth zero.
            int nodeIdx = inputAndBiasCount;
            int connIdx = 0;

            for (int currDepth = 0; currDepth < netDepth; currDepth++)
            {
                // Scan for last node at the current depth.
                for (; nodeIdx < nodeCount && nodeInfoByDepth[nodeIdx]._nodeDepth == currDepth; nodeIdx++)
                {
                    ;
                }

                // Scan for last connection at the current depth.
                for (; connIdx < fastConnectionArray.Length && nodeInfoByDepth[fastConnectionArray[connIdx]._srcNeuronIdx]._nodeDepth == currDepth; connIdx++)
                {
                    ;
                }

                // Store node and connection end indexes for the layer.
                layerInfoArr[currDepth]._endNodeIdx       = nodeIdx;
                layerInfoArr[currDepth]._endConnectionIdx = connIdx;
            }

            return(new FastAcyclicNetwork(nodeActivationFnArr, nodeAuxArgsArray, fastConnectionArray, layerInfoArr, outputNeuronIdxArr,
                                          nodeCount, networkDef.InputNodeCount, networkDef.OutputNodeCount));
        }
コード例 #5
0
        /// <summary>
        /// Create an array of FastConnection(s) representing the connectivity of the provided INetworkDefinition.
        /// </summary>
        private static FastConnection[] CreateFastConnectionArray(INetworkDefinition networkDef)
        {
            // We vary the decode logic depending on the size of the genome. The most CPU intensive aspect of
            // decoding is the conversion of the neuron IDs at connection endpoints into neuron indexes. For small
            // genomes we simply use the BinarySearch() method on NeuronGeneList for each lookup; Each lookup is
            // an operation with O(log n) time complexity. Thus for C connections and N neurons the number of operations
            // to perform all lookups is approximately = 2*C*Log(N)
            //
            // For larger genomes we invest time in building a Dictionary that maps neuron IDs to their indexes, this on the
            // basis that the time invested will be more than recovered in time saved performing lookups; The time complexity
            // of a dictionary lookup is near constant O(1). Thus number of operations is approximately = O(2*C*1) + the time
            // required to build the dictionary which is approximately O(N).
            //
            // Therefore the choice of lookup type is based on which of these two expressions gives the lowest value.
            //
            //      Binary search.      LookupOps = 2 * C * Log2(N) * x
            //      Dictionary Search.  LookupOps = (N * y) + (2 * C * z)
            //
            // Where x, y and z are constants that adjust for the relative speeds of the lookup and dictionary building operations.
            // Note that the actual time required to perform these separate algorithms is actually a far more complex problem, and
            // for modern CPUs exact times cannot be calculated because of large memory caches and superscalar architecture that
            // makes execution times in a real environment effectively non-deterministic. Thus these calculations are a rough
            // guide/heuristic that estimate which algorithm will perform best. The constants can be found experimentally but will
            // tend to vary depending on factors such as CPU and memory architecture, .Net framework version and what other tasks the
            // CPU is currently doing which may affect our utilisation of memory caches.
            // TODO: Experimentally determine reasonably good values for constants x,y and z in some common real world runtime platform.
            IConnectionList connectionList  = networkDef.ConnectionList;
            INodeList       nodeList        = networkDef.NodeList;
            int             connectionCount = connectionList.Count;
            int             nodeCount       = nodeList.Count;

            FastConnection[] fastConnectionArray = new FastConnection[connectionCount];

            if ((2.0 * connectionCount * Math.Log(nodeCount, 2.0)) < ((2.0 * connectionCount) + nodeCount))
            {
                // Binary search requires items to be sorted.
                Debug.Assert(nodeList.IsSorted());

                // Loop the connections and lookup the neuron IDs for each connection's end points using a binary search
                // on nGeneList. This is probably the quickest approach for small numbers of lookups.
                for (int i = 0; i < connectionCount; i++)
                {
                    INetworkConnection conn = connectionList[i];
                    fastConnectionArray[i]._srcNeuronIdx = nodeList.BinarySearch(conn.SourceNodeId);
                    fastConnectionArray[i]._tgtNeuronIdx = nodeList.BinarySearch(conn.TargetNodeId);
                    fastConnectionArray[i]._weight       = conn.Weight;

                    // Check that the correct neuron indexes were found.
                    Debug.Assert(
                        nodeList[fastConnectionArray[i]._srcNeuronIdx].Id == conn.SourceNodeId &&
                        nodeList[fastConnectionArray[i]._tgtNeuronIdx].Id == conn.TargetNodeId);
                }
            }
            else
            {
                // Build dictionary of neuron indexes keyed on neuron innovation ID.
                Dictionary <uint, int> neuronIndexDictionary = new Dictionary <uint, int>(nodeCount);
                for (int i = 0; i < nodeCount; i++)
                {
                    // ENHANCEMENT: Check if neuron innovation ID requires further manipulation to make a good hash code.
                    neuronIndexDictionary.Add(nodeList[i].Id, i);
                }

                // Loop the connections and lookup the neuron IDs for each connection's end points using neuronIndexDictionary.
                // This is probably the quickest approach for large numbers of lookups.
                for (int i = 0; i < connectionCount; i++)
                {
                    INetworkConnection conn = connectionList[i];
                    fastConnectionArray[i]._srcNeuronIdx = neuronIndexDictionary[conn.SourceNodeId];
                    fastConnectionArray[i]._tgtNeuronIdx = neuronIndexDictionary[conn.TargetNodeId];
                    fastConnectionArray[i]._weight       = conn.Weight;

                    // Check that the correct neuron indexes were found.
                    Debug.Assert(
                        nodeList[fastConnectionArray[i]._srcNeuronIdx].Id == conn.SourceNodeId &&
                        nodeList[fastConnectionArray[i]._tgtNeuronIdx].Id == conn.TargetNodeId);
                }
            }

            return(fastConnectionArray);
        }