/evolution.py

https://gitlab.com/hassanbot/NEATram
Python | 228 lines | 221 code | 5 blank | 2 comment | 4 complexity | a8eb199616c4f20f1af3649527636ebb MD5 | raw file
  1. import copy # For copying genome
  2. import collections # For making Population into a list with MutableSequence
  3. import logging
  4. import random
  5. next_gene_id = 0
  6. next_neuron_id = 0
  7. class Connection:
  8. """Connections are the genes of the individuals"""
  9. def __init__(self, from_neuron, to_neuron, weight, innovation, enabled=False):
  10. self.innovation = innovation
  11. self.from_neuron = from_neuron
  12. self.to_neuron = to_neuron
  13. self.weight = weight
  14. self.enabled = enabled
  15. def set_innovation(self, innovation):
  16. """Set the innovation number of the gene"""
  17. self.innovation = innovation
  18. def __eq__(self, other):
  19. """Check if another gene has the same innovation number"""
  20. if self.innovation == other.innovation:
  21. return True
  22. else:
  23. return False
  24. def get_neuron_values(neurons):
  25. value_list = []
  26. for neuron in neurons:
  27. value_list.append(neuron.value)
  28. return value_list
  29. def get_connection_values(connections):
  30. """Get the connection weight values as a matrix weights[input][output],
  31. where neurons is a list of the input neurons"""
  32. value_list = []
  33. for connection in connections:
  34. value_list.append(connection.weight)
  35. return value_list
  36. class Individual:
  37. def __init__(self, n_inputs, n_outputs, genome=None, neurons=None):
  38. """Constructor: Creates a new minimal individual, or from genome if supplied"""
  39. global next_gene_id
  40. global next_neuron_id
  41. self.mutate_rate = {'node': 0.1, 'connection': 0.1, 'weight': 0.1, 'enabled': 0.1}
  42. # Create neurons, neurons['inputs'][0] is a bias neuron
  43. if neurons:
  44. if n_inputs + 1 + n_outputs < len(neurons):
  45. raise ValueError("Not enough neurons supplied. "
  46. "Got {}, expected more than or equal to {}".format(len(neurons),
  47. n_inputs + 1 + n_outputs))
  48. self.neurons = copy.copy(neurons)
  49. else:
  50. self.neurons = {'inputs': [], 'hidden': [], 'outputs': []}
  51. for i in xrange(n_inputs + 1):
  52. self.neurons['inputs'].append(Neuron(next_neuron_id, (0, i)))
  53. next_neuron_id += 1
  54. self.neurons['inputs'][0].value = 1.0
  55. for i in xrange(n_outputs):
  56. self.neurons['outputs'].append(Neuron(next_neuron_id, (2, i)))
  57. next_neuron_id += 1
  58. # Create genome (i.e. connections)
  59. if genome:
  60. # Copy supplied genome
  61. if ((n_inputs + 1) * n_outputs) < len(genome):
  62. raise ValueError("Genome length is not large enough."
  63. " Expected {}, got {}.".format((n_inputs + 1) * n_outputs, len(genome)))
  64. self.genome = copy.copy(genome)
  65. else:
  66. # Create connections between inputs and outputs only
  67. self.genome = []
  68. for from_neuron in self.neurons['inputs']:
  69. for to_neuron in self.neurons['outputs']:
  70. self.add_connection(from_neuron, to_neuron)
  71. def get_all_neurons(self):
  72. """Returns a single list of all the neurons"""
  73. return self.neurons['inputs'] + self.neurons['hidden'] + self.neurons['outputs']
  74. def get_neuron(self, id_number):
  75. for neuron in self.get_all_neurons():
  76. if neuron.id == id_number:
  77. return neuron
  78. logging.warning("Neuron with id {} does not exist".format(id_number))
  79. return None
  80. def enable_all_connections(self):
  81. """Enable all connections of the network"""
  82. for connection in self.genome:
  83. connection.enabled = True
  84. def disable_all_connections(self):
  85. """Disable all connections of the network"""
  86. for connection in self.genome:
  87. connection.enabled = False
  88. def mutate(self):
  89. # TODO: Make sure the same mutation does not occur two times in the same generation
  90. # Add new nodes
  91. if random.random() < self.mutate_rate['node']:
  92. random.sample(self.genome)
  93. # Add new connections
  94. # Change weights
  95. # Disable/enable connections
  96. pass
  97. def add_connection(self, from_neuron, to_neuron, weight=1, enabled=False):
  98. """Add a connection between two neurons"""
  99. for connection in from_neuron.connections['outgoing']:
  100. if connection.to_neuron == to_neuron:
  101. logging.info("Connection already exists; innovation nr: " + str(connection.innovation))
  102. return
  103. global next_gene_id
  104. new_connection = Connection(from_neuron, to_neuron, weight, next_gene_id, enabled=enabled)
  105. from_neuron.add_outgoing(new_connection)
  106. to_neuron.add_incoming(new_connection)
  107. self.genome.append(new_connection)
  108. next_gene_id += 1
  109. return new_connection
  110. def add_neuron(self, connection):
  111. """Split the connection and add a neuron in the middle, creating two new connections.
  112. The old connection is disabled"""
  113. global next_neuron_id
  114. x_pos = (connection.to_neuron.position[0] + connection.from_neuron.position[0]) / 2.0
  115. y_pos = (connection.to_neuron.position[1] + connection.from_neuron.position[1]) / 2.0
  116. neuron = Neuron(next_neuron_id, (x_pos, y_pos))
  117. self.neurons['hidden'].append(neuron)
  118. self.add_connection(connection.from_neuron, neuron, 1, True)
  119. self.add_connection(neuron, connection.to_neuron, connection.weight, True)
  120. connection.enabled = False
  121. next_neuron_id += 1
  122. return neuron
  123. def reset_neurons(self):
  124. """Reset the values of all neurons to 0 (except for the bias neuron)"""
  125. for neuron in self.get_all_neurons()[1:]:
  126. # (self.neurons['inputs'][1:]+self.neurons['hidden']+self.neurons['outputs']):
  127. neuron.reset_value()
  128. def set_input_values(self, in_values):
  129. """Set the input values according to the specified list"""
  130. # TODO: Handle bias neuron
  131. if len(in_values) != (len(self.neurons['inputs'])-1):
  132. raise ValueError("Length of in-vector is not the same as the number of input neurons."
  133. "Got {}, expected {}".format(len(in_values),
  134. len(self.neurons['inputs'])-1))
  135. for i, value in enumerate(in_values):
  136. self.neurons['inputs'][i+1].value = value
  137. def feed_forward(self, in_values):
  138. """Do a feed-forward step of the network"""
  139. # Traverse the network recursively
  140. self.reset_neurons()
  141. self.set_input_values(in_values)
  142. output = []
  143. for neuron in self.neurons['outputs']:
  144. output.append(neuron.get_value())
  145. return output
  146. class Population(collections.MutableSequence):
  147. def __init__(self, pop_size, n_inputs, n_outputs):
  148. """Constructor: Initializes population as a list of individuals"""
  149. self._list = []
  150. self.mutate_rate = {'node': 0.5, 'link': 0.5, 'weights': 0.5}
  151. for i in xrange(pop_size):
  152. self._list.append(Individual(n_inputs, n_outputs))
  153. # Overloaded abstract functions
  154. def __getitem__(self, index):
  155. return self._list[index]
  156. def __len__(self):
  157. return len(self._list)
  158. def __setitem__(self, index, value):
  159. self._list[index] = value
  160. def insert(self, index, value):
  161. self._list.insert(index, value)
  162. def __delitem__(self, index):
  163. del self._list[index]
  164. class Neuron:
  165. def __init__(self, id_number, position, value=None):
  166. self.id = id_number # Unique ID
  167. self.value = value # The activated value of the neuron
  168. self.connections = {'incoming': [], 'outgoing': []} # Connections to and from the neuron
  169. self.position = position # Position, for drawing using netgraph
  170. def __eq__(self, other):
  171. if self.id == other.id:
  172. return True
  173. else:
  174. return False
  175. def add_incoming(self, connection):
  176. self.connections['incoming'].append(connection)
  177. def add_outgoing(self, connection):
  178. self.connections['outgoing'].append(connection)
  179. def reset_value(self):
  180. self.value = None
  181. def get_value(self):
  182. if not self.value:
  183. self.value = 0
  184. for connection in self.connections['incoming']:
  185. if connection.enabled:
  186. self.value += connection.weight*connection.from_neuron.get_value()
  187. return self.value