示例#1
0
    /// <summary>
    /// Activate the network. Activation reads input signals from InputSignalArray and writes output signals
    /// to OutputSignalArray.
    /// </summary>
    public void Activate()
    {
        ReadOnlySpan <int>    srcIds      = _connIds.GetSourceIdSpan();
        ReadOnlySpan <int>    tgtIds      = _connIds.GetTargetIdSpan();
        ReadOnlySpan <double> weights     = _weightArr.AsSpan();
        Span <double>         activations = _activationArr.AsSpan();

        // Reset hidden and output node activation levels, ready for next activation.
        // Note. this reset is performed here instead of after the below loop because this resets the output
        // node values, which are the outputs of the network as a whole following activation; hence
        // they need to be remain unchanged until they have been read by the caller of Activate().
        activations.Slice(_inputCount).Clear();

        // Process all layers in turn.
        int conIdx  = 0;
        int nodeIdx = _inputCount;

        // Loop through network layers.
        for (int layerIdx = 0; layerIdx < _layerInfoArr.Length - 1; layerIdx++)
        {
            LayerInfo layerInfo = _layerInfoArr[layerIdx];

            // Push signals through the current layer's connections to the target nodes (that are all in 'downstream' layers).
            for (; conIdx < layerInfo.EndConnectionIdx; conIdx++)
            {
                // Get the connection source signal, multiply it by the connection weight, add the result
                // to the target node's current pre-activation level, and store the result.
                activations[tgtIds[conIdx]] =
                    Math.FusedMultiplyAdd(
                        activations[srcIds[conIdx]],
                        weights[conIdx],
                        activations[tgtIds[conIdx]]);
            }

            // Activate the next layer's nodes. This is possible because we know that all connections that
            // target these nodes have been processed, either during processing on the current layer's
            // connections, or earlier layers. This means that the final output value/signal (i.e post
            // activation function output) is available for all connections and nodes in the lower/downstream
            // layers.
            //
            // Pass the pre-activation levels through the activation function.
            // Note. The resulting post-activation levels are stored in _activationArr.
            layerInfo = _layerInfoArr[layerIdx + 1];
            _activationFn(
                ref activations[nodeIdx],
                layerInfo.EndNodeIdx - nodeIdx);

            // Update nodeIdx to point at first node in the next layer.
            nodeIdx = layerInfo.EndNodeIdx;
        }

        // Copy the output signals from _activationArr into _outputArr.
        // These signals are scattered through _activationArr, and here we bring them together into a
        // contiguous segment of memory that is indexable by output index.
        for (int i = 0; i < _outputNodeIdxArr.Length; i++)
        {
            _outputArr[i] = _activationArr[_outputNodeIdxArr[i]];
        }
    }
示例#2
0
    /// <summary>
    /// Activate the network. Activation reads input signals from InputSignalArray and writes output signals
    /// to OutputSignalArray.
    /// </summary>
    public void Activate()
    {
        ReadOnlySpan <int>    srcIds      = _connIds.GetSourceIdSpan();
        ReadOnlySpan <int>    tgtIds      = _connIds.GetTargetIdSpan();
        ReadOnlySpan <double> weights     = _weightArr.AsSpan();
        Span <double>         activations = _activationMem.Span;
        Span <double>         outputs     = _outputMem.Span;

        ref int    srcIdsRef      = ref MemoryMarshal.GetReference(srcIds);
示例#3
0
    /// <summary>
    /// Activate the network for a fixed number of iterations defined by the 'maxIterations' parameter
    /// at construction time. Activation reads input signals from InputSignalArray and writes output signals
    /// to OutputSignalArray.
    /// </summary>
    public void Activate()
    {
        ReadOnlySpan <int>    srcIds          = _connIds.GetSourceIdSpan();
        ReadOnlySpan <int>    tgtIds          = _connIds.GetTargetIdSpan();
        ReadOnlySpan <double> weights         = _weightArr.AsSpan();
        Span <double>         preActivations  = _preActivationMem.Span;
        Span <double>         postActivations = _postActivationMem.Span;

        // Note. Here we skip over the activations corresponding to the input neurons, as these have no
        // incoming connections, and therefore have fixed post-activation values and are never activated.
        int           nonInputCount            = _totalNodeCount - _inputCount;
        Span <double> preActivationsNonInputs  = preActivations.Slice(_inputCount);
        Span <double> postActivationsNonInputs = postActivations.Slice(_inputCount);

        ref int    srcIdsRef                   = ref MemoryMarshal.GetReference(srcIds);
    private static ConnectionIds CopyAndMapIds(
        Span <DirectedConnection> connSpan,
        INodeIdMap nodeIdMap)
    {
        int count   = connSpan.Length;
        var connIds = new ConnectionIds(count);
        var srcIds  = connIds.GetSourceIdSpan();
        var tgtIds  = connIds.GetTargetIdSpan();

        for (int i = 0; i < count; i++)
        {
            srcIds[i] = nodeIdMap.Map(connSpan[i].SourceId);
            tgtIds[i] = nodeIdMap.Map(connSpan[i].TargetId);
        }

        return(connIds);
    }
示例#5
0
    private static void CopyAndMapIds(
        DirectedConnection[] connArr,
        INodeIdMap nodeIdMap,
        out ConnectionIds connIds)
    {
        int count = connArr.Length;

        connIds = new ConnectionIds(count);
        var srcIds = connIds.GetSourceIdSpan();
        var tgtIds = connIds.GetTargetIdSpan();

        for (int i = 0; i < count; i++)
        {
            srcIds[i] = nodeIdMap.Map(connArr[i].SourceId);
            tgtIds[i] = nodeIdMap.Map(connArr[i].TargetId);
        }
    }
示例#6
0
    /// <summary>
    /// Split each IWeightedDirectedConnection in a list into an array of DirectedConnections(s), and an array of weights.
    /// Map the node IDs to indexes as we go.
    /// </summary>
    private static void CopyAndMapIds(
        Span <WeightedDirectedConnection <T> > connSpan,
        INodeIdMap nodeIdMap,
        out ConnectionIds connIds,
        out T[] weightArr)
    {
        int count = connSpan.Length;

        connIds = new ConnectionIds(count);
        var srcIds = connIds.GetSourceIdSpan();
        var tgtIds = connIds.GetTargetIdSpan();

        weightArr = new T[count];

        for (int i = 0; i < count; i++)
        {
            srcIds[i]    = nodeIdMap.Map(connSpan[i].SourceId);
            tgtIds[i]    = nodeIdMap.Map(connSpan[i].TargetId);
            weightArr[i] = connSpan[i].Weight;
        }
    }
示例#7
0
    /// <summary>
    /// Activate the network for a fixed number of iterations defined by the 'maxIterations' parameter
    /// at construction time. Activation reads input signals from InputSignalArray and writes output signals
    /// to OutputSignalArray.
    /// </summary>
    public void Activate()
    {
        ReadOnlySpan <int>    srcIds          = _connIds.GetSourceIdSpan();
        ReadOnlySpan <int>    tgtIds          = _connIds.GetTargetIdSpan();
        ReadOnlySpan <double> weights         = _weightArr.AsSpan();
        Span <double>         preActivations  = _preActivationArr.AsSpan();
        Span <double>         postActivations = _postActivationArr.AsSpan();

        // Note. Here we skip over the activations corresponding to the input neurons, as these have no
        // incoming connections, and therefore have fixed post-activation values and are never activated.
        int           nonInputCount            = _preActivationArr.Length - _inputCount;
        Span <double> preActivationsNonInputs  = preActivations.Slice(_inputCount);
        Span <double> postActivationsNonInputs = postActivations.Slice(_inputCount);

        // Activate the network for a fixed number of timesteps.
        for (int i = 0; i < _cyclesPerActivation; i++)
        {
            // Loop connections. Get each connection's input signal, apply the weight and add the result to
            // the pre-activation signal of the target neuron.
            for (int j = 0; j < srcIds.Length; j++)
            {
                preActivations[tgtIds[j]] =
                    Math.FusedMultiplyAdd(
                        postActivations[srcIds[j]],
                        weights[j],
                        preActivations[tgtIds[j]]);
            }

            // Pass the pre-activation levels through the activation function, storing the results in the
            // post-activation span/array.
            _activationFn(
                ref preActivationsNonInputs[0],
                ref postActivationsNonInputs[0],
                nonInputCount);

            // Reset the elements of _preActivationArray that represent the output and hidden nodes.
            preActivationsNonInputs.Clear();
        }
    }
示例#8
0
    /// <summary>
    /// Creates a new directed acyclic graph instance from the provided graph structure, accompanying graph
    /// layer information, and node ID mappings.
    /// </summary>
    /// <param name="digraph">A directed graph structure.</param>
    /// <param name="depthInfo">Depth/layer information, describing what layer each node of the graph is within.</param>
    /// <param name="newIdByOldId">Returns a set of node ID mappings. These describe a mapping from the
    /// non-contiguous node ID space of <paramref name="digraph"/>, to the contiguous node ID space of the
    /// returned <see cref="DirectedGraphAcyclic"/>
    /// contiguous space.</param>
    /// <param name="connectionIndexMap">Returns a set of connection index mappings. The connections of
    /// <paramref name="digraph"/> are re-ordered based on the layer/depth of each connection's source node;
    /// this structure conveys the new index of each connection given its original/old index.</param>
    /// <param name="timsortWorkArr">A re-usable working array for use as temporary storage by the timsort algorithm.</param>
    /// <param name="timsortWorkVArr">A secondary re-usable working array for use as temporary storage by the timsort algorithm.</param>
    /// <returns>A new instance of <see cref="DirectedGraphAcyclic"/>.</returns>
    public static DirectedGraphAcyclic CreateDirectedGraphAcyclic(
        DirectedGraph digraph,
        GraphDepthInfo depthInfo,
        out int[] newIdByOldId,
        out int[] connectionIndexMap,
        ref int[]?timsortWorkArr,
        ref int[]?timsortWorkVArr)
    {
        int inputCount  = digraph.InputCount;
        int outputCount = digraph.OutputCount;

        // Assert that all input nodes are at depth zero.
        // Any input node with a non-zero depth must have an input connection, and this is not supported.
        Debug.Assert(SpanUtils.Equal(depthInfo._nodeDepthArr.AsSpan(0, inputCount), 0));

        // Compile a mapping from current node IDs to new IDs (based on node depth in the graph).
        newIdByOldId = CompileNodeIdMap(depthInfo, digraph.TotalNodeCount, inputCount, ref timsortWorkArr, ref timsortWorkVArr);

        // Map the connection node IDs.
        ConnectionIds connIds = digraph.ConnectionIds;

        MapIds(connIds, newIdByOldId);

        // Init connection index map.
        int connCount = connIds.Length;

        connectionIndexMap = new int[connCount];
        for (int i = 0; i < connCount; i++)
        {
            connectionIndexMap[i] = i;
        }

        // Sort the connections based on sourceID, targetId; this will arrange the connections based on the depth
        // of the source nodes.
        // Note. This sort routine will also sort a secondary array, i.e. keep the items in both arrays aligned;
        // here we use this to create connectionIndexMap.
        ConnectionSorter <int> .Sort(connIds, connectionIndexMap);

        // Make a copy of the sub-range of newIdByOldId that represents the output nodes.
        // This is required later to be able to locate the output nodes now that they have been sorted by depth.
        int[] outputNodeIdxArr = new int[outputCount];
        newIdByOldId.AsSpan(inputCount, outputCount).CopyTo(outputNodeIdxArr);

        // 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 (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 graphDepth = depthInfo._graphDepth;

        LayerInfo[] layerInfoArr = new LayerInfo[graphDepth];

        // Note. Scanning over nodes can start at inputCount instead of zero, because all nodes prior to that index
        // are input nodes and are therefore at depth zero. (input nodes are never the target of a connection,
        // therefore are always guaranteed to be at the start of a connectivity graph, and thus at depth zero).
        int nodeCount = digraph.TotalNodeCount;
        int nodeIdx   = inputCount;
        int connIdx   = 0;

        int[] nodeDepthArr        = depthInfo._nodeDepthArr;
        ReadOnlySpan <int> srcIds = connIds.GetSourceIdSpan();

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

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

            // Store node and connection end indexes for the layer.
            layerInfoArr[currDepth] = new LayerInfo(nodeIdx, connIdx);
        }

        // Construct and return.
        return(new DirectedGraphAcyclic(
                   inputCount, outputCount, nodeCount,
                   connIds,
                   layerInfoArr,
                   outputNodeIdxArr));
    }