ml-finance-python
python scripts for finance machine learning
git clone https://9o.is/git/ml-finance-python.git
neuroevolution.py
(6006B)
1 from __future__ import print_function, division
2 import numpy as np
3 import copy
4
5 class Neuroevolution():
6 """ Evolutionary optimization of Neural Networks.
7
8 Parameters:
9 -----------
10 n_individuals: int
11 The number of neural networks that are allowed in the population at a time.
12 mutation_rate: float
13 The probability that a weight will be mutated.
14 model_builder: method
15 A method which returns a user specified NeuralNetwork instance.
16 """
17 def __init__(self, population_size, mutation_rate, model_builder):
18 self.population_size = population_size
19 self.mutation_rate = mutation_rate
20 self.model_builder = model_builder
21
22 def _build_model(self, id):
23 """ Returns a new individual """
24 model = self.model_builder(n_inputs=self.X.shape[1], n_outputs=self.y.shape[1])
25 model.id = id
26 model.fitness = 0
27 model.accuracy = 0
28
29 return model
30
31 def _initialize_population(self):
32 """ Initialization of the neural networks forming the population"""
33 self.population = []
34 for _ in range(self.population_size):
35 model = self._build_model(id=np.random.randint(1000))
36 self.population.append(model)
37
38 def _mutate(self, individual, var=1):
39 """ Add zero mean gaussian noise to the layer weights with probability mutation_rate """
40 for layer in individual.layers:
41 if hasattr(layer, 'W'):
42 # Mutation of weight with probability self.mutation_rate
43 mutation_mask = np.random.binomial(1, p=self.mutation_rate, size=layer.W.shape)
44 layer.W += np.random.normal(loc=0, scale=var, size=layer.W.shape) * mutation_mask
45 mutation_mask = np.random.binomial(1, p=self.mutation_rate, size=layer.w0.shape)
46 layer.w0 += np.random.normal(loc=0, scale=var, size=layer.w0.shape) * mutation_mask
47
48 return individual
49
50 def _inherit_weights(self, child, parent):
51 """ Copies the weights from parent to child """
52 for i in range(len(child.layers)):
53 if hasattr(child.layers[i], 'W'):
54 # The child inherits both weights W and bias weights w0
55 child.layers[i].W = parent.layers[i].W.copy()
56 child.layers[i].w0 = parent.layers[i].w0.copy()
57
58 def _crossover(self, parent1, parent2):
59 """ Performs crossover between the neurons in parent1 and parent2 to form offspring """
60 child1 = self._build_model(id=parent1.id+1)
61 self._inherit_weights(child1, parent1)
62 child2 = self._build_model(id=parent2.id+1)
63 self._inherit_weights(child2, parent2)
64
65 # Perform crossover
66 for i in range(len(child1.layers)):
67 if hasattr(child1.layers[i], 'W'):
68 n_neurons = child1.layers[i].W.shape[1]
69 # Perform crossover between the individuals' neuron weights
70 cutoff = np.random.randint(0, n_neurons)
71 child1.layers[i].W[:, cutoff:] = parent2.layers[i].W[:, cutoff:].copy()
72 child1.layers[i].w0[:, cutoff:] = parent2.layers[i].w0[:, cutoff:].copy()
73 child2.layers[i].W[:, cutoff:] = parent1.layers[i].W[:, cutoff:].copy()
74 child2.layers[i].w0[:, cutoff:] = parent1.layers[i].w0[:, cutoff:].copy()
75
76 return child1, child2
77
78 def _calculate_fitness(self):
79 """ Evaluate the NNs on the test set to get fitness scores """
80 for individual in self.population:
81 loss, acc = individual.test_on_batch(self.X, self.y)
82 individual.fitness = 1 / (loss + 1e-8)
83 individual.accuracy = acc
84
85 def evolve(self, X, y, n_generations):
86 """ Will evolve the population for n_generations based on dataset X and labels y"""
87 self.X, self.y = X, y
88
89 self._initialize_population()
90
91 # The 40% highest fittest individuals will be selected for the next generation
92 n_winners = int(self.population_size * 0.4)
93 # The fittest 60% of the population will be selected as parents to form offspring
94 n_parents = self.population_size - n_winners
95
96 for epoch in range(n_generations):
97 # Determine the fitness of the individuals in the population
98 self._calculate_fitness()
99
100 # Sort population by fitness
101 sorted_i = np.argsort([model.fitness for model in self.population])[::-1]
102 self.population = [self.population[i] for i in sorted_i]
103
104 # Get the individual with the highest fitness
105 fittest_individual = self.population[0]
106 print ("[%d Best Individual - Fitness: %.5f, Accuracy: %.1f%%]" % (epoch,
107 fittest_individual.fitness,
108 float(100*fittest_individual.accuracy)))
109 # The 'winners' are selected for the next generation
110 next_population = [self.population[i] for i in range(n_winners)]
111
112 total_fitness = np.sum([model.fitness for model in self.population])
113 # The probability that a individual will be selected as a parent is proportionate to its fitness
114 parent_probabilities = [model.fitness / total_fitness for model in self.population]
115 # Select parents according to probabilities (without replacement to preserve diversity)
116 parents = np.random.choice(self.population, size=n_parents, p=parent_probabilities, replace=False)
117 for i in np.arange(0, len(parents), 2):
118 # Perform crossover to produce offspring
119 child1, child2 = self._crossover(parents[i], parents[i+1])
120 # Save mutated offspring for next population
121 next_population += [self._mutate(child1), self._mutate(child2)]
122
123 self.population = next_population
124
125 return fittest_individual
126