github.com/yaricom/goNEAT@v0.0.0-20210507221059-e2110b885482/neat/network/nnode.go (about) 1 package network 2 3 import ( 4 "bytes" 5 "fmt" 6 "github.com/yaricom/goNEAT/neat" 7 "github.com/yaricom/goNEAT/neat/utils" 8 ) 9 10 // A NODE is either a NEURON or a SENSOR. 11 // - If it's a sensor, it can be loaded with a value for output 12 // - If it's a neuron, it has a list of its incoming input signals ([]*Link is used) 13 // Use an activation count to avoid flushing 14 type NNode struct { 15 // The ID of the node 16 Id int 17 18 // The type of node activation function (SIGMOID, ...) 19 ActivationType utils.NodeActivationType 20 // The neuron type for this node (HIDDEN, INPUT, OUTPUT, BIAS) 21 NeuronType NodeNeuronType 22 23 // The node's activation value 24 Activation float64 25 // The number of activations for current node 26 ActivationsCount int32 27 // The activation sum 28 ActivationSum float64 29 30 // The list of all incoming connections 31 Incoming []*Link 32 // The list of all outgoing connections 33 Outgoing []*Link 34 // The trait linked to the node 35 Trait *neat.Trait 36 37 // Used for Gene decoding by referencing analogue to this node in organism phenotype 38 PhenotypeAnalogue *NNode 39 40 // the flag to use for loop detection 41 visited bool 42 43 /* ************ LEARNING PARAMETERS *********** */ 44 // The following parameters are for use in neurons that learn through habituation, 45 // sensitization, or Hebbian-type processes */ 46 Params []float64 47 48 // Activation value of node at time t-1; Holds the previous step's activation for recurrency 49 lastActivation float64 50 // Activation value of node at time t-2 Holds the activation before the previous step's 51 // This is necessary for a special recurrent case when the innode of a recurrent link is one time step ahead of the outnode. 52 // The innode then needs to send from TWO time steps ago 53 lastActivation2 float64 54 55 // If true the node is active - used during node activation 56 isActive bool 57 } 58 59 // Creates new node with specified ID and neuron type associated (INPUT, HIDDEN, OUTPUT, BIAS) 60 func NewNNode(nodeId int, neuronType NodeNeuronType) *NNode { 61 n := NewNetworkNode() 62 n.Id = nodeId 63 n.NeuronType = neuronType 64 return n 65 } 66 67 // Construct a NNode off another NNode with given trait for genome purposes 68 func NewNNodeCopy(n *NNode, t *neat.Trait) *NNode { 69 node := NewNetworkNode() 70 node.Id = n.Id 71 node.NeuronType = n.NeuronType 72 node.ActivationType = n.ActivationType 73 node.Trait = t 74 return node 75 } 76 77 // The default constructor 78 func NewNetworkNode() *NNode { 79 return &NNode{ 80 NeuronType: HiddenNeuron, 81 ActivationType: utils.SigmoidSteepenedActivation, 82 Incoming: make([]*Link, 0), 83 Outgoing: make([]*Link, 0), 84 } 85 } 86 87 // Set new activation value to this node 88 func (n *NNode) setActivation(input float64) { 89 // Keep a memory of activations for potential time delayed connections 90 n.saveActivations() 91 // Set new activation value 92 n.Activation = input 93 // Increment the activation_count 94 n.ActivationsCount++ 95 } 96 97 // Saves current node's activations for potential time delayed connections 98 func (n *NNode) saveActivations() { 99 n.lastActivation2 = n.lastActivation 100 n.lastActivation = n.Activation 101 } 102 103 // Returns activation for a current step 104 func (n *NNode) GetActiveOut() float64 { 105 if n.ActivationsCount > 0 { 106 return n.Activation 107 } else { 108 return 0.0 109 } 110 } 111 112 // Returns activation from PREVIOUS time step 113 func (n *NNode) GetActiveOutTd() float64 { 114 if n.ActivationsCount > 1 { 115 return n.lastActivation 116 } else { 117 return 0.0 118 } 119 } 120 121 // Returns true if this node is SENSOR 122 func (n *NNode) IsSensor() bool { 123 return n.NeuronType == InputNeuron || n.NeuronType == BiasNeuron 124 } 125 126 // returns true if this node is NEURON 127 func (n *NNode) IsNeuron() bool { 128 return n.NeuronType == HiddenNeuron || n.NeuronType == OutputNeuron 129 } 130 131 // If the node is a SENSOR, returns TRUE and loads the value 132 func (n *NNode) SensorLoad(load float64) bool { 133 if n.IsSensor() { 134 // Keep a memory of activations for potential time delayed connections 135 n.saveActivations() 136 // Puts sensor into next time-step 137 n.ActivationsCount++ 138 n.Activation = load 139 return true 140 } else { 141 return false 142 } 143 } 144 145 // Adds a non recurrent outgoing link to this node 146 func (n *NNode) addOutgoing(out *NNode, weight float64) { 147 newLink := NewLink(weight, n, out, false) 148 n.Outgoing = append(n.Outgoing, newLink) 149 } 150 151 // Adds a NONRECURRENT Link to an incoming NNode in the incoming List 152 func (n *NNode) addIncoming(in *NNode, weight float64) { 153 newLink := NewLink(weight, in, n, false) 154 n.Incoming = append(n.Incoming, newLink) 155 } 156 157 // Recursively deactivate backwards through the network 158 func (n *NNode) Flushback() { 159 n.ActivationsCount = 0 160 n.Activation = 0 161 n.lastActivation = 0 162 n.lastActivation2 = 0 163 n.isActive = false 164 n.visited = false 165 } 166 167 // Verify flushing for debugging 168 func (n *NNode) FlushbackCheck() error { 169 if n.ActivationsCount > 0 { 170 return fmt.Errorf("NNODE: %s has activation count %d", n, n.ActivationsCount) 171 } 172 if n.Activation > 0 { 173 return fmt.Errorf("NNODE: %s has activation %f", n, n.Activation) 174 } 175 if n.lastActivation > 0 { 176 return fmt.Errorf("NNODE: %s has last_activation %f", n, n.lastActivation) 177 } 178 if n.lastActivation2 > 0 { 179 return fmt.Errorf("NNODE: %s has last_activation2 %f", n, n.lastActivation2) 180 } 181 return nil 182 } 183 184 // Find the greatest depth starting from this neuron at depth d 185 func (n *NNode) Depth(d int) (int, error) { 186 if d > 1000 { 187 // to avoid infinite recursion 188 return 10, NetErrDepthCalculationFailedLoopDetected 189 } 190 n.visited = true 191 // Base Case 192 if n.IsSensor() { 193 return d, nil 194 } else { 195 // recursion 196 max := d // The max depth 197 for _, l := range n.Incoming { 198 if l.InNode.visited { 199 // was already visited (loop detected) - skipping 200 continue 201 } 202 curDepth, err := l.InNode.Depth(d + 1) 203 if err != nil { 204 return curDepth, err 205 } 206 if curDepth > max { 207 max = curDepth 208 } 209 } 210 return max, nil 211 } 212 213 } 214 215 // Convenient method to check network's node type (SENSOR, NEURON) 216 func (n *NNode) NodeType() NodeType { 217 if n.IsSensor() { 218 return SensorNode 219 } 220 return NeuronNode 221 } 222 223 func (n *NNode) String() string { 224 activation, _ := utils.NodeActivators.ActivationNameFromType(n.ActivationType) 225 active := "active" 226 if !n.isActive { 227 active = "inactive" 228 } 229 return fmt.Sprintf("(%s id:%03d, %s, %s,\t%s -> step: %d = %.3f %.3f)", 230 NodeTypeName(n.NodeType()), n.Id, NeuronTypeName(n.NeuronType), activation, active, 231 n.ActivationsCount, n.Activation, n.Params) 232 } 233 234 // Prints all node's fields to the string 235 func (n *NNode) Print() string { 236 str := "NNode fields\n" 237 b := bytes.NewBufferString(str) 238 _, _ = fmt.Fprintf(b, "\tId: %d\n", n.Id) 239 _, _ = fmt.Fprintf(b, "\tIsActive: %t\n", n.isActive) 240 _, _ = fmt.Fprintf(b, "\tActivation: %f\n", n.Activation) 241 activation, _ := utils.NodeActivators.ActivationNameFromType(n.ActivationType) 242 _, _ = fmt.Fprintf(b, "\tActivation Type: %s\n", activation) 243 _, _ = fmt.Fprintf(b, "\tNeuronType: %d\n", n.NeuronType) 244 _, _ = fmt.Fprintf(b, "\tActivationsCount: %d\n", n.ActivationsCount) 245 _, _ = fmt.Fprintf(b, "\tActivationSum: %f\n", n.ActivationSum) 246 _, _ = fmt.Fprintf(b, "\tIncoming: %s\n", n.Incoming) 247 _, _ = fmt.Fprintf(b, "\tOutgoing: %s\n", n.Outgoing) 248 _, _ = fmt.Fprintf(b, "\tTrait: %s\n", n.Trait) 249 _, _ = fmt.Fprintf(b, "\tPhenotypeAnalogue: %s\n", n.PhenotypeAnalogue) 250 _, _ = fmt.Fprintf(b, "\tParams: %f\n", n.Params) 251 _, _ = fmt.Fprintf(b, "\tlastActivation: %f\n", n.lastActivation) 252 _, _ = fmt.Fprintf(b, "\tlastActivation2: %f\n", n.lastActivation2) 253 254 return b.String() 255 }