Skip to content

Commit

Permalink
clone
Browse files Browse the repository at this point in the history
  • Loading branch information
kelindar committed Oct 3, 2020
1 parent bc75f4e commit 4e8ed94
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 121 deletions.
21 changes: 16 additions & 5 deletions evolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ func (p *Population) Evolve() {
for i := range p.values {

// Select 2 parents
p1 := p.pickMate()
p2 := p.pickMate()
p1, p2 := p.pickParents()

// Perform the crossover
gene := buffer[i]
Expand All @@ -98,15 +97,27 @@ func (p *Population) Evolve() {
p.commit(buffer)
}

// pickParents selects 2 parents from the population and sorts them by their fitness.
func (p *Population) pickParents() (Evolver, Evolver) {
p1, f1 := p.pickMate()
p2, f2 := p.pickMate()
if f1 > f2 {
return p1, p2
}

return p2, p1
}

// pickMate selects a parent from the population
func (p *Population) pickMate() Evolver {
func (p *Population) pickMate() (Evolver, float32) {
n := len(p.values)
max := p.fitnessMax
rng := p.rand
for {
i := rng.Intn(n)
if rng.Float32()*max <= p.fitnessOf[i] {
return p.values[i]
f := p.fitnessOf[i]
if rng.Float32()*max <= f {
return p.values[i], f
}
}
}
Expand Down
91 changes: 49 additions & 42 deletions neural/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package neural

import (
"math"
"sort"
"sync/atomic"
)
Expand All @@ -18,49 +17,25 @@ func next() uint32 {

// ----------------------------------------------------------------------------------

// Node represents a neuron in the network
// Neuron represents a neuron in the network
type neuron struct {
Serial uint32 // The innovation serial number
Conns []synapse // The incoming connections
value float64 // The output value (for activation)
}

// makeNeuron creates a new neuron.
func makeNode() neuron {
func makeNeuron() neuron {
return neuron{
Serial: next(),
}
}

// Value returns the value for the neuron
func (n *neuron) Value() float64 {
if n.value != 0 || len(n.Conns) == 0 {
return n.value
}

// Sum of the weighted inputs to the neuron
s := 0.0
for _, c := range n.Conns {
if c.Active {
s += c.Weight * c.From.Value()
}
}

// Keep the value to avoid recalculating
n.value = sigmoid(s)
return n.value
}

// connected checks whether the two neurons are connected or not.
func (n *neuron) connected(neuron *neuron) bool {
return searchNode(n, neuron) || searchNode(neuron, n)
}

// Sigmod activation function.
func sigmoid(x float64) float64 {
return 1.0 / (1 + math.Exp(-x))
}

// searchNode searches whether incoming connections of "to" contain a "from" neuron.
func searchNode(from, to *neuron) bool {
x := from.Serial
Expand All @@ -75,46 +50,78 @@ func searchNode(from, to *neuron) bool {
// Nodes represents a set of neurons
type neurons []neuron

// makeNodes creates a new neuron array.
func makeNodes(count int) neurons {
// makeNeurons creates a new neuron array.
func makeNeurons(count int) neurons {
arr := make(neurons, 0, count)
for i := 0; i < count; i++ {
arr = append(arr, makeNode())
arr = append(arr, makeNeuron())
}
return arr
}

// Len returns the number of neurons.
func (n neurons) Len() int {
return len(n)
}

// Less compares two neurons in the slice.
func (n neurons) Less(i, j int) bool {
return n[i].Serial < n[j].Serial
}

// Swap swaps two neurons.
func (n neurons) Swap(i, j int) {
n[i], n[j] = n[j], n[i]
}

// Find searches the neurons for a specific serial. For this to work correctly,
// the neurons array should be sorted.
func (n neurons) Find(serial uint32) *neuron {
i := sort.Search(len(n), func(i int) bool {
return n[i].Serial >= serial
})
if i < len(n) && n[i].Serial == serial {
return &n[i]
}

return nil
}

// ----------------------------------------------------------------------------------

// Synapse represents a synapse for the NEAT network.
type synapse struct {
Serial uint32 // The innovation serial number
Weight float64 // The weight of the connection
Active bool // Whether the connection is enabled or not
From, To *neuron // The neurons of the connection
Active bool // Whether the connection is enabled or not
}

// ID returns a unique key for the edge.
func (c *synapse) ID() uint64 {
return (uint64(c.To.Serial) << 32) | (uint64(c.From.Serial) & 0xffffffff)
func (s *synapse) ID() uint64 {
return (uint64(s.To.Serial) << 32) | (uint64(s.From.Serial) & 0xffffffff)
}

// Equal checks whether the connection is equal to another connection
func (s *synapse) Equal(other *synapse) bool {
return s.From == other.From && s.To == other.To
}

// ----------------------------------------------------------------------------------

// sortedByNode represents a connection list which is sorted by neuron ID
type sortedByNode []synapse
// synapses represents a connection list which is sorted by neuron ID
type synapses []synapse

// Len returns the number of connections.
func (c sortedByNode) Len() int {
return len(c)
func (s synapses) Len() int {
return len(s)
}

// Less compares two connections in the slice.
func (c sortedByNode) Less(i, j int) bool {
return c[i].ID() < c[j].ID()
func (s synapses) Less(i, j int) bool {
return s[i].ID() < s[j].ID()
}

// Swap swaps two connections
func (c sortedByNode) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
func (s synapses) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
6 changes: 5 additions & 1 deletion neural/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import (
)

func TestConnected(t *testing.T) {
n := makeNodes(2)
n := []neuron{
makeNeuron(),
makeNeuron(),
}

n0, n1 := &n[0], &n[1]

// Disjoint
Expand Down
124 changes: 77 additions & 47 deletions neural/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,62 +4,81 @@
package neural

import (
"math"
"sort"

"github.com/kelindar/evolve"
)

// Network represents a neural network.
type Network struct {
input neurons
hidden neurons
output neurons
conns []synapse
input neurons // Input neurons and a bias neuron
output neurons // Output neurons
hidden neurons // Hidden neurons
nodes neurons // Neurons for the network
conns synapses // Synapses, sorted by ID
}

// New creates a new neural network.
func New(in, out int) *Network {
// New creates a function for a random genome string
func New(in, out int) evolve.Genesis {
origin := newNetwork(in, out)
return func() evolve.Genome {
clone := new(Network)
origin.Clone(clone)
return clone
}
}

// newNetwork creates a new neural network.
func newNetwork(in, out int) *Network {
nodes := makeNeurons(1 + in + out)
nn := &Network{
input: makeNodes(in + 1),
output: makeNodes(out),
nodes: nodes,
input: nodes[1 : 1+in],
output: nodes[1+in : 1+in+out],
hidden: nodes[1+in+out:],
conns: make([]synapse, 0, 256),
}

// Bias neuron
nn.input[in].value = 1.0
// First is always a bias neuron
nn.input[0].value = 1.0
return nn
}

// Predict activates the network
func (n *Network) Predict(input, output []float64) []float64 {
if output == nil {
output = make([]float64, len(n.output))
}

// Set the values for the input neurons
for i, v := range input {
n.input[i].value = v
}

// Clean the hidden neurons values
for i := range n.hidden {
n.hidden[i].value = 0
// Clone clones the neural network by copying all of the neurons and synapses and
// re-assigning the synapse pointers accordingly.
func (n *Network) Clone(dst *Network) {
defer dst.sort()
dst.clear()

// Copy the nodes into the destination
for _, v := range n.nodes {
dst.nodes = append(dst.nodes, neuron{
Serial: v.Serial,
})
}

// Retrieve values and sum up exponentials
sum := 0.0
for i, neuron := range n.output {
v := math.Exp(neuron.Value())
output[i] = v
sum += v
// Assign accessors
in, out := len(n.input), len(n.output)
dst.input = dst.nodes[1 : 1+in]
dst.output = dst.nodes[1+in : 1+in+out]
dst.hidden = dst.nodes[1+in+out:]

// Sort the destination nodes so we can find the corresponding ones
sort.Sort(dst.nodes)
for _, v := range n.conns {
dst.conns = append(dst.conns, synapse{
From: dst.nodes.Find(v.From.Serial),
To: dst.nodes.Find(v.To.Serial),
Weight: v.Weight,
Active: v.Active,
})
}
}

// Normalize
for i := range output {
output[i] /= sum
}
return output
// Clear clears the network for reuse.
func (n *Network) clear() {
n.nodes = n.nodes[:0]
n.conns = n.conns[:0]
}

// sort sorts the connections depending on the neuron and assigns connection slices
Expand All @@ -70,9 +89,11 @@ func (n *Network) sort() {
}

// Sort by neuron ID
sort.Sort(sortedByNode(n.conns))
sort.Sort(n.conns)

// Assign connection slices to neurons
// Assign connection slices to neurons. This is basically sub-slicing the main
// array, so the "Data" pointer of the slice will point to the same underlying
// array, avoiding extra memory space and allocations.
prev, lo := n.conns[0].To, 0
curr, hi := n.conns[0].To, 0
for i, conn := range n.conns {
Expand All @@ -91,7 +112,6 @@ func (n *Network) sort() {
func (n *Network) connect(from, to *neuron, weight float64) {
defer n.sort() // Keep sorted
n.conns = append(n.conns, synapse{
Serial: next(), // Innovation number
From: from, // Left neuron
To: to, // Right neuron
Weight: weight, // Weight for the connection
Expand All @@ -105,16 +125,26 @@ func (n *Network) Mutate() {

}

// Crossover applies genetic crossover between two networks. The first parent is
// the fittest of the two.
func (n *Network) Crossover(p1, p2 evolve.Genome) {
//n1, n2 := p1.(*Network), p2.(*Network)

}
/*
* p1 should have the higher score
* - take all the genes of a
* - if there is a genome in a that is also in b, choose randomly
* - do not take disjoint genes of b
* - take excess genes of a if they exist
*/

// Equal checks whether the connection is equal to another connection
/*func (c *conn) Equal(other *conn) bool {
return c.From == other.From && c.To == other.To
}*/
// Copy of the nodes and synaposes from the fittest into this network
//n1.Clone(n)

// https://github.com/Luecx/NEAT/tree/master/vid%209/src
}

// https://sausheong.github.io/posts/how-to-build-a-simple-artificial-neural-network-with-go/
// https://stats.stackexchange.com/questions/459491/how-do-i-use-matrix-math-in-irregular-neural-networks-generated-from-neuroevolut
// Distance calculates the distance between two neural networks based on their
// genome structure.
func (n *Network) Distance(other *Network) float64 {
return 0
}
Loading

0 comments on commit 4e8ed94

Please sign in to comment.