''' Genomes for Critters that have them that determine weights in neural network. ''' from utils import * class Genome(list): # Mutation rate MUTATION = .005 # Crossover rate CROSSOVER = 1.0 # Number of bits used for each weight or bias BITS_PER_WEIGHT = 4 # Range for real valued genes RANGE = 8.0 # Square of standard deviation for gaussian noise VAR = 1.0 # Scale noise by this much NOISE_SCALE = .05 def __init__(self, critter=None, real=True): self.critter = critter self.real = real def initialize(self): '''Set the length and random bits in the genome.''' if self.critter: self.network = self.critter.network self.length = self.get_length() else: self.length = 10 if not self.real: self.length *= Genome.BITS_PER_WEIGHT for a in range(self.length): self.append(self.random_value()) # random.random() < .5) # self.show() # for w in self.get_weights(): # print w, # print if self.critter: self.network.assign_weights(self.get_weights()) def get_length(self): length = self.network.get_n_weights() if not self.real: self.length *= Genome.BITS_PER_WEIGHT return length def random_value(self): """Random value for a gene.""" if self.real: return Genome.RANGE * (random.random() - .5) else: return random.random() < .5 def gaussian_noise(self): x = math.sqrt(-2 * math.log(random.random())) y = 2 * math.pi * random.random() if random.random() < .5: return Genome.NOISE_SCALE * x * math.cos(y) else: return Genome.NOISE_SCALE * x * math.sin(y) def copy(self, critter): '''Make a copy of this Genome, but for a different critter.''' g = Genome(critter = critter) g.network = critter.network g.length = self.get_length() # g.network.get_n_weights() * Genome.BITS_PER_WEIGHT for a in range(g.length): g.append(self[a]) g.mutate() g.network.assign_weights(g.get_weights()) return g def mutate(self): '''With probability MUTATION, flip the bits in the Genome.''' for a in range(len(self)): if self.real: self[a] += self.gaussian_noise() elif random.random() < Genome.MUTATION: self[a] = not self[a] def crossover_right(self, parent_genome, crossover_point): '''Replace the bits in self by those in parent_genome starting at crossover_point.''' # print 'Crossing over at', crossover_point for locus in range(crossover_point, self.length): self[locus] = parent_genome[locus] # Mutate # if random.random() < Genome.MUTATION: # self[locus] = not self[locus] ## def crossover(self, mate_genome, offspring1, offspring2): ## genome1 = Genome(offspring1) ## genome2 = Genome(offspring2) ## genome1.initialize() ## genome2.initialize() ## crossover_point = random.randint(1, self.length - 1) ## print 'Crossover point', crossover_point ## for locus in range(crossover_point): ## genome1[locus] = self[locus] ## genome2[locus] = mate_genome[locus] ## for locus in range(crossover_point, self.length): ## genome1[locus] = mate_genome[locus] ## genome2[locus] = self[locus] def get_groups(self): '''Sublists representing weights.''' return [self[start:start+Genome.BITS_PER_WEIGHT] for start\ in range(0, self.length, Genome.BITS_PER_WEIGHT)] def get_weights(self): '''List of weights represented by genome.''' if self.real: return self else: return [self.to_weight(ls) for ls in self.get_groups()] def to_weight(self, sublist): '''Convert the sublist (length BITS_PER_WEIGHT) to a weight.''' total = 0.0 #Xx xx: -1 if sublist[0]: total += -3.0 #xX xx: -2 if sublist[1]: total += -1.0 #xx Xx: 1 if sublist[2]: total += 1.0 #xx xX: 2 if sublist[3]: total += 3.0 # truncate return min([max([-4.0, total]), 4.0]) def show(self): '''Print out the weights in the genome.''' if self.real: for w in self: print '%+1.2f' % w, else: for w in self.get_weights(): print '%+1.0f' % w, print def show_bits(self): '''Print out the bits in the genome.''' if self.real: self.show() else: s = '' for allele in self: s += '1' if allele else '0' print s ## s = '' ## for i,a in enumerate(self): ## if i % Genome.BITS_PER_WEIGHT == 0 and i != 0: ## s += '|' ## s += ('1' if a else '0') ## print s