├── .gitignore ├── EA.jpg ├── LICENSE ├── README.md ├── demo ├── es.gif ├── es_rl.gif ├── find path.gif ├── ga.gif ├── mga.gif ├── nes.gif ├── phrase.gif └── tsp.gif └── tutorial-contents ├── Evolution Strategy ├── (1+1)-ES.py ├── Evolution Strategy Basic.py └── Natural Evolution Strategy (NES).py ├── Genetic Algorithm ├── Find Path.py ├── Genetic Algorithm Basic.py ├── Match Phrase.py ├── Microbial Genetic Algorithm.py └── Travel Sales Person.py └── Using Neural Nets ├── Evolution Strategy with Neural Nets.py ├── NEAT ├── config-feedforward ├── run_xor.py └── visualize.py └── NEAT_gym ├── config ├── run_cartpole.py └── visualize.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /EA.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MorvanZhou/Evolutionary-Algorithm/9a9c703580553c84ba3d9ef5aba788e320afe301/EA.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andrew Gambardella 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | In these tutorials, we will demonstrate and visualize algorithms like Genetic Algorithm, 8 | Evolution Strategy, NEAT etc. 9 | 10 | All methods mentioned below have their video and text tutorial in Chinese. Visit [莫烦 Python](https://mofanpy.com/) for more. 11 | 12 | A python package for Evolution algorithm is now available. Details are found at [MEvo](https://github.com/MorvanZhou/mevo). 13 | 14 | * [Genetic Algorithm](tutorial-contents/Genetic%20Algorithm) 15 | * [Basic GA](tutorial-contents/Genetic%20Algorithm/Genetic%20Algorithm%20Basic.py) 16 | * [Match Phrase Example](tutorial-contents/Genetic%20Algorithm/Match%20Phrase.py) 17 | * [Travel Sales Problem](tutorial-contents/Genetic%20Algorithm/Travel%20Sales%20Person.py) 18 | * [Find Path Example](tutorial-contents/Genetic%20Algorithm/Find%20Path.py) 19 | * [Microbial GA](tutorial-contents/Genetic%20Algorithm/Microbial%20Genetic%20Algorithm.py) 20 | * [Evolution Strategy](tutorial-contents/Evolution%20Strategy) 21 | * [Basic ES](tutorial-contents/Evolution%20Strategy/Evolution%20Strategy%20Basic.py) 22 | * [(1+1)-ES](tutorial-contents/Evolution%20Strategy/(1%2B1)-ES.py) 23 | * [Natural Evolution Strategy (NES)](tutorial-contents/Evolution%20Strategy/Natural%20Evolution%20Strategy%20(NES).py) 24 | * [Using Neural Nets](tutorial-contents/Using%20Neural%20Nets) 25 | * [NEAT for Supervised-Learning](tutorial-contents/Using%20Neural%20Nets/NEAT) 26 | * [NEAT for Reinforcement-Learning](tutorial-contents/Using%20Neural%20Nets/NEAT_gym) 27 | * [Distributed ES with Neural Nets (OpenAI)](tutorial-contents/Using%20Neural%20Nets/Evolution%20Strategy%20with%20Neural%20Nets.py) 28 | 29 | 30 | ### [Basic GA](tutorial-contents/Genetic%20Algorithm/Genetic%20Algorithm%20Basic.py) 31 | 32 | 33 | 34 | 35 | 36 | 37 | ### [Match Phrase Example](tutorial-contents/Genetic%20Algorithm/Match%20Phrase.py) 38 | 39 | 40 | 41 | 42 | 43 | ### [Travel Sales Problem](tutorial-contents/Genetic%20Algorithm/Travel%20Sales%20Person.py) 44 | 45 | 46 | 47 | 48 | 49 | ### [Find Path Example](tutorial-contents/Genetic%20Algorithm/Find%20Path.py) 50 | 51 | 52 | 53 | 54 | ### [Basic ES](tutorial-contents/Evolution%20Strategy/Evolution%20Strategy%20Basic.py) 55 | 56 | 57 | 58 | 59 | 60 | ### [Natural Evolution Strategy (NES)](tutorial-contents/Evolution%20Strategy/Natural%20Evolution%20Strategy%20(NES).py) 61 | 62 | 63 | 64 | 65 | 66 | ### [Distributed ES with Neural Nets (OpenAI)](tutorial-contents/Using%20Neural%20Nets/Evolution%20Strategy%20with%20Neural%20Nets.py) 67 | 68 | 69 | 70 | 71 | 72 | # Donation 73 | 74 | *If this does help you, please consider donating to support me for better tutorials! Any contribution is greatly appreciated!* 75 | 76 |
77 | 78 | Paypal 82 |
83 | 84 |
85 | 86 | Patreon 89 | 90 |
91 | -------------------------------------------------------------------------------- /demo/es.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MorvanZhou/Evolutionary-Algorithm/9a9c703580553c84ba3d9ef5aba788e320afe301/demo/es.gif -------------------------------------------------------------------------------- /demo/es_rl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MorvanZhou/Evolutionary-Algorithm/9a9c703580553c84ba3d9ef5aba788e320afe301/demo/es_rl.gif -------------------------------------------------------------------------------- /demo/find path.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MorvanZhou/Evolutionary-Algorithm/9a9c703580553c84ba3d9ef5aba788e320afe301/demo/find path.gif -------------------------------------------------------------------------------- /demo/ga.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MorvanZhou/Evolutionary-Algorithm/9a9c703580553c84ba3d9ef5aba788e320afe301/demo/ga.gif -------------------------------------------------------------------------------- /demo/mga.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MorvanZhou/Evolutionary-Algorithm/9a9c703580553c84ba3d9ef5aba788e320afe301/demo/mga.gif -------------------------------------------------------------------------------- /demo/nes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MorvanZhou/Evolutionary-Algorithm/9a9c703580553c84ba3d9ef5aba788e320afe301/demo/nes.gif -------------------------------------------------------------------------------- /demo/phrase.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MorvanZhou/Evolutionary-Algorithm/9a9c703580553c84ba3d9ef5aba788e320afe301/demo/phrase.gif -------------------------------------------------------------------------------- /demo/tsp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MorvanZhou/Evolutionary-Algorithm/9a9c703580553c84ba3d9ef5aba788e320afe301/demo/tsp.gif -------------------------------------------------------------------------------- /tutorial-contents/Evolution Strategy/(1+1)-ES.py: -------------------------------------------------------------------------------- 1 | """ 2 | (1+1)-ES with 1/5th success rule with visualization. 3 | 4 | Visit my tutorial website for more: https://mofanpy.com/tutorials/ 5 | """ 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | 9 | DNA_SIZE = 1 # DNA (real number) 10 | DNA_BOUND = [0, 5] # solution upper and lower bounds 11 | N_GENERATIONS = 200 12 | MUT_STRENGTH = 5. # initial step size (dynamic mutation strength) 13 | 14 | 15 | def F(x): return np.sin(10*x)*x + np.cos(2*x)*x # to find the maximum of this function 16 | 17 | 18 | # find non-zero fitness for selection 19 | def get_fitness(pred): return pred.flatten() 20 | 21 | 22 | def make_kid(parent): 23 | # no crossover, only mutation 24 | k = parent + MUT_STRENGTH * np.random.randn(DNA_SIZE) 25 | k = np.clip(k, *DNA_BOUND) 26 | return k 27 | 28 | 29 | def kill_bad(parent, kid): 30 | global MUT_STRENGTH 31 | fp = get_fitness(F(parent))[0] 32 | fk = get_fitness(F(kid))[0] 33 | p_target = 1/5 34 | if fp < fk: # kid better than parent 35 | parent = kid 36 | ps = 1. # kid win -> ps = 1 (successful offspring) 37 | else: 38 | ps = 0. 39 | # adjust global mutation strength 40 | MUT_STRENGTH *= np.exp(1/np.sqrt(DNA_SIZE+1) * (ps - p_target)/(1 - p_target)) 41 | return parent 42 | 43 | 44 | parent = 5 * np.random.rand(DNA_SIZE) # parent DNA 45 | 46 | plt.ion() # something about plotting 47 | x = np.linspace(*DNA_BOUND, 200) 48 | 49 | for _ in range(N_GENERATIONS): 50 | # ES part 51 | kid = make_kid(parent) 52 | py, ky = F(parent), F(kid) # for later plot 53 | parent = kill_bad(parent, kid) 54 | 55 | # something about plotting 56 | plt.cla() 57 | plt.scatter(parent, py, s=200, lw=0, c='red', alpha=0.5,) 58 | plt.scatter(kid, ky, s=200, lw=0, c='blue', alpha=0.5) 59 | plt.text(0, -7, 'Mutation strength=%.2f' % MUT_STRENGTH) 60 | plt.plot(x, F(x)); plt.pause(0.05) 61 | 62 | plt.ioff(); plt.show() -------------------------------------------------------------------------------- /tutorial-contents/Evolution Strategy/Evolution Strategy Basic.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Evolution Strategy can be summarized as the following term: 3 | {mu/rho +, lambda}-ES 4 | 5 | Here we use following term to find a maximum point. 6 | {n_pop/n_pop + n_kid}-ES 7 | 8 | Visit my tutorial website for more: https://mofanpy.com/tutorials/ 9 | """ 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | 13 | DNA_SIZE = 1 # DNA (real number) 14 | DNA_BOUND = [0, 5] # solution upper and lower bounds 15 | N_GENERATIONS = 200 16 | POP_SIZE = 100 # population size 17 | N_KID = 50 # n kids per generation 18 | 19 | 20 | def F(x): return np.sin(10*x)*x + np.cos(2*x)*x # to find the maximum of this function 21 | 22 | 23 | # find non-zero fitness for selection 24 | def get_fitness(pred): return pred.flatten() 25 | 26 | 27 | def make_kid(pop, n_kid): 28 | # generate empty kid holder 29 | kids = {'DNA': np.empty((n_kid, DNA_SIZE))} 30 | kids['mut_strength'] = np.empty_like(kids['DNA']) 31 | for kv, ks in zip(kids['DNA'], kids['mut_strength']): 32 | # crossover (roughly half p1 and half p2) 33 | p1, p2 = np.random.choice(np.arange(POP_SIZE), size=2, replace=False) 34 | cp = np.random.randint(0, 2, DNA_SIZE, dtype=np.bool) # crossover points 35 | kv[cp] = pop['DNA'][p1, cp] 36 | kv[~cp] = pop['DNA'][p2, ~cp] 37 | ks[cp] = pop['mut_strength'][p1, cp] 38 | ks[~cp] = pop['mut_strength'][p2, ~cp] 39 | 40 | # mutate (change DNA based on normal distribution) 41 | ks[:] = np.maximum(ks + (np.random.rand(*ks.shape)-0.5), 0.) # must > 0 42 | kv += ks * np.random.randn(*kv.shape) 43 | kv[:] = np.clip(kv, *DNA_BOUND) # clip the mutated value 44 | return kids 45 | 46 | 47 | def kill_bad(pop, kids): 48 | # put pop and kids together 49 | for key in ['DNA', 'mut_strength']: 50 | pop[key] = np.vstack((pop[key], kids[key])) 51 | 52 | fitness = get_fitness(F(pop['DNA'])) # calculate global fitness 53 | idx = np.arange(pop['DNA'].shape[0]) 54 | good_idx = idx[fitness.argsort()][-POP_SIZE:] # selected by fitness ranking (not value) 55 | for key in ['DNA', 'mut_strength']: 56 | pop[key] = pop[key][good_idx] 57 | return pop 58 | 59 | 60 | pop = dict(DNA=5 * np.random.rand(1, DNA_SIZE).repeat(POP_SIZE, axis=0), # initialize the pop DNA values 61 | mut_strength=np.random.rand(POP_SIZE, DNA_SIZE)) # initialize the pop mutation strength values 62 | 63 | plt.ion() # something about plotting 64 | x = np.linspace(*DNA_BOUND, 200) 65 | plt.plot(x, F(x)) 66 | 67 | for _ in range(N_GENERATIONS): 68 | # something about plotting 69 | if 'sca' in globals(): sca.remove() 70 | sca = plt.scatter(pop['DNA'], F(pop['DNA']), s=200, lw=0, c='red', alpha=0.5); plt.pause(0.05) 71 | 72 | # ES part 73 | kids = make_kid(pop, N_KID) 74 | pop = kill_bad(pop, kids) # keep some good parent for elitism 75 | 76 | plt.ioff(); plt.show() -------------------------------------------------------------------------------- /tutorial-contents/Evolution Strategy/Natural Evolution Strategy (NES).py: -------------------------------------------------------------------------------- 1 | """ 2 | The basic idea about Nature Evolution Strategy with visualation. 3 | 4 | Visit my tutorial website for more: https://mofanpy.com/tutorials/ 5 | 6 | Dependencies: 7 | Tensorflow >= r1.2 8 | numpy 9 | matplotlib 10 | """ 11 | import numpy as np 12 | import matplotlib.pyplot as plt 13 | import tensorflow as tf 14 | from tensorflow.contrib.distributions import MultivariateNormalFullCovariance 15 | 16 | DNA_SIZE = 2 # parameter (solution) number 17 | N_POP = 20 # population size 18 | N_GENERATION = 100 # training step 19 | LR = 0.02 # learning rate 20 | 21 | 22 | # fitness function 23 | def get_fitness(pred): return -((pred[:, 0])**2 + pred[:, 1]**2) 24 | 25 | # build multivariate distribution 26 | mean = tf.Variable(tf.random_normal([2, ], 13., 1.), dtype=tf.float32) 27 | cov = tf.Variable(5. * tf.eye(DNA_SIZE), dtype=tf.float32) 28 | mvn = MultivariateNormalFullCovariance(loc=mean, covariance_matrix=cov) 29 | make_kid = mvn.sample(N_POP) # sampling operation 30 | 31 | # compute gradient and update mean and covariance matrix from sample and fitness 32 | tfkids_fit = tf.placeholder(tf.float32, [N_POP, ]) 33 | tfkids = tf.placeholder(tf.float32, [N_POP, DNA_SIZE]) 34 | loss = -tf.reduce_mean(mvn.log_prob(tfkids)*tfkids_fit) # log prob * fitness 35 | train_op = tf.train.GradientDescentOptimizer(LR).minimize(loss) # compute and apply gradients for mean and cov 36 | 37 | sess = tf.Session() 38 | sess.run(tf.global_variables_initializer()) # initialize tf variables 39 | 40 | # something about plotting (can be ignored) 41 | n = 300 42 | x = np.linspace(-20, 20, n) 43 | X, Y = np.meshgrid(x, x) 44 | Z = np.zeros_like(X) 45 | for i in range(n): 46 | for j in range(n): 47 | Z[i, j] = get_fitness(np.array([[x[i], x[j]]])) 48 | plt.contourf(X, Y, -Z, 100, cmap=plt.cm.rainbow); plt.ylim(-20, 20); plt.xlim(-20, 20); plt.ion() 49 | 50 | # training 51 | for g in range(N_GENERATION): 52 | kids = sess.run(make_kid) 53 | kids_fit = get_fitness(kids) 54 | sess.run(train_op, {tfkids_fit: kids_fit, tfkids: kids}) # update distribution parameters 55 | 56 | # plotting update 57 | if 'sca' in globals(): sca.remove() 58 | sca = plt.scatter(kids[:, 0], kids[:, 1], s=30, c='k');plt.pause(0.01) 59 | 60 | print('Finished'); plt.ioff(); plt.show() -------------------------------------------------------------------------------- /tutorial-contents/Genetic Algorithm/Find Path.py: -------------------------------------------------------------------------------- 1 | """ 2 | Visualize Genetic Algorithm to find a path to the target. 3 | 4 | Visit my tutorial website for more: https://mofanpy.com/tutorials/ 5 | """ 6 | import matplotlib.pyplot as plt 7 | import numpy as np 8 | 9 | N_MOVES = 150 10 | DNA_SIZE = N_MOVES*2 # 40 x moves, 40 y moves 11 | DIRECTION_BOUND = [0, 1] 12 | CROSS_RATE = 0.8 13 | MUTATE_RATE = 0.0001 14 | POP_SIZE = 100 15 | N_GENERATIONS = 100 16 | GOAL_POINT = [10, 5] 17 | START_POINT = [0, 5] 18 | OBSTACLE_LINE = np.array([[5, 2], [5, 8]]) 19 | 20 | 21 | class GA(object): 22 | def __init__(self, DNA_size, DNA_bound, cross_rate, mutation_rate, pop_size, ): 23 | self.DNA_size = DNA_size 24 | DNA_bound[1] += 1 25 | self.DNA_bound = DNA_bound 26 | self.cross_rate = cross_rate 27 | self.mutate_rate = mutation_rate 28 | self.pop_size = pop_size 29 | 30 | self.pop = np.random.randint(*DNA_bound, size=(pop_size, DNA_size)) 31 | 32 | def DNA2product(self, DNA, n_moves, start_point): # convert to readable string 33 | pop = (DNA - 0.5) / 2 34 | pop[:, 0], pop[:, n_moves] = start_point[0], start_point[1] 35 | lines_x = np.cumsum(pop[:, :n_moves], axis=1) 36 | lines_y = np.cumsum(pop[:, n_moves:], axis=1) 37 | return lines_x, lines_y 38 | 39 | def get_fitness(self, lines_x, lines_y, goal_point, obstacle_line): 40 | dist2goal = np.sqrt((goal_point[0] - lines_x[:, -1]) ** 2 + (goal_point[1] - lines_y[:, -1]) ** 2) 41 | fitness = np.power(1 / (dist2goal + 1), 2) 42 | points = (lines_x > obstacle_line[0, 0] - 0.5) & (lines_x < obstacle_line[1, 0] + 0.5) 43 | y_values = np.where(points, lines_y, np.zeros_like(lines_y) - 100) 44 | bad_lines = ((y_values > obstacle_line[0, 1]) & (y_values < obstacle_line[1, 1])).max(axis=1) 45 | fitness[bad_lines] = 1e-6 46 | return fitness 47 | 48 | def select(self, fitness): 49 | idx = np.random.choice(np.arange(self.pop_size), size=self.pop_size, replace=True, p=fitness/fitness.sum()) 50 | return self.pop[idx] 51 | 52 | def crossover(self, parent, pop): 53 | if np.random.rand() < self.cross_rate: 54 | i_ = np.random.randint(0, self.pop_size, size=1) # select another individual from pop 55 | cross_points = np.random.randint(0, 2, self.DNA_size).astype(np.bool) # choose crossover points 56 | parent[cross_points] = pop[i_, cross_points] # mating and produce one child 57 | return parent 58 | 59 | def mutate(self, child): 60 | for point in range(self.DNA_size): 61 | if np.random.rand() < self.mutate_rate: 62 | child[point] = np.random.randint(*self.DNA_bound) 63 | return child 64 | 65 | def evolve(self, fitness): 66 | pop = self.select(fitness) 67 | pop_copy = pop.copy() 68 | for parent in pop: # for every parent 69 | child = self.crossover(parent, pop_copy) 70 | child = self.mutate(child) 71 | parent[:] = child 72 | self.pop = pop 73 | 74 | 75 | class Line(object): 76 | def __init__(self, n_moves, goal_point, start_point, obstacle_line): 77 | self.n_moves = n_moves 78 | self.goal_point = goal_point 79 | self.start_point = start_point 80 | self.obstacle_line = obstacle_line 81 | 82 | plt.ion() 83 | 84 | def plotting(self, lines_x, lines_y): 85 | plt.cla() 86 | plt.scatter(*self.goal_point, s=200, c='r') 87 | plt.scatter(*self.start_point, s=100, c='b') 88 | plt.plot(self.obstacle_line[:, 0], self.obstacle_line[:, 1], lw=3, c='k') 89 | plt.plot(lines_x.T, lines_y.T, c='k') 90 | plt.xlim((-5, 15)) 91 | plt.ylim((-5, 15)) 92 | plt.pause(0.01) 93 | 94 | 95 | ga = GA(DNA_size=DNA_SIZE, DNA_bound=DIRECTION_BOUND, 96 | cross_rate=CROSS_RATE, mutation_rate=MUTATE_RATE, pop_size=POP_SIZE) 97 | 98 | env = Line(N_MOVES, GOAL_POINT, START_POINT, OBSTACLE_LINE) 99 | 100 | for generation in range(N_GENERATIONS): 101 | lx, ly = ga.DNA2product(ga.pop, N_MOVES, START_POINT) 102 | fitness = ga.get_fitness(lx, ly, GOAL_POINT, OBSTACLE_LINE) 103 | ga.evolve(fitness) 104 | print('Gen:', generation, '| best fit:', fitness.max()) 105 | env.plotting(lx, ly) 106 | 107 | plt.ioff() 108 | plt.show() 109 | 110 | 111 | -------------------------------------------------------------------------------- /tutorial-contents/Genetic Algorithm/Genetic Algorithm Basic.py: -------------------------------------------------------------------------------- 1 | """ 2 | Visualize Genetic Algorithm to find a maximum point in a function. 3 | 4 | Visit my tutorial website for more: https://mofanpy.com/tutorials/ 5 | """ 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | 9 | DNA_SIZE = 10 # DNA length 10 | POP_SIZE = 100 # population size 11 | CROSS_RATE = 0.8 # mating probability (DNA crossover) 12 | MUTATION_RATE = 0.003 # mutation probability 13 | N_GENERATIONS = 200 14 | X_BOUND = [0, 5] # x upper and lower bounds 15 | 16 | 17 | def F(x): return np.sin(10*x)*x + np.cos(2*x)*x # to find the maximum of this function 18 | 19 | 20 | # find non-zero fitness for selection 21 | def get_fitness(pred): return pred + 1e-3 - np.min(pred) 22 | 23 | 24 | # convert binary DNA to decimal and normalize it to a range(0, 5) 25 | def translateDNA(pop): return pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2**DNA_SIZE-1) * X_BOUND[1] 26 | 27 | 28 | def select(pop, fitness): # nature selection wrt pop's fitness 29 | idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True, 30 | p=fitness/fitness.sum()) 31 | return pop[idx] 32 | 33 | 34 | def crossover(parent, pop): # mating process (genes crossover) 35 | if np.random.rand() < CROSS_RATE: 36 | i_ = np.random.randint(0, POP_SIZE, size=1) # select another individual from pop 37 | cross_points = np.random.randint(0, 2, size=DNA_SIZE).astype(np.bool_) # choose crossover points 38 | parent[cross_points] = pop[i_, cross_points] # mating and produce one child 39 | return parent 40 | 41 | 42 | def mutate(child): 43 | for point in range(DNA_SIZE): 44 | if np.random.rand() < MUTATION_RATE: 45 | child[point] = 1 if child[point] == 0 else 0 46 | return child 47 | 48 | 49 | pop = np.random.randint(2, size=(POP_SIZE, DNA_SIZE)) # initialize the pop DNA 50 | 51 | plt.ion() # something about plotting 52 | x = np.linspace(*X_BOUND, 200) 53 | plt.plot(x, F(x)) 54 | 55 | for _ in range(N_GENERATIONS): 56 | F_values = F(translateDNA(pop)) # compute function value by extracting DNA 57 | 58 | # something about plotting 59 | if 'sca' in globals(): sca.remove() 60 | sca = plt.scatter(translateDNA(pop), F_values, s=200, lw=0, c='red', alpha=0.5); plt.pause(0.05) 61 | 62 | # GA part (evolution) 63 | fitness = get_fitness(F_values) 64 | print("Most fitted DNA: ", pop[np.argmax(fitness), :]) 65 | pop = select(pop, fitness) 66 | pop_copy = pop.copy() 67 | for parent in pop: 68 | child = crossover(parent, pop_copy) 69 | child = mutate(child) 70 | parent[:] = child # parent is replaced by its child 71 | 72 | plt.ioff(); plt.show() 73 | -------------------------------------------------------------------------------- /tutorial-contents/Genetic Algorithm/Match Phrase.py: -------------------------------------------------------------------------------- 1 | """ 2 | Visualize Genetic Algorithm to match the target phrase. 3 | 4 | Visit my tutorial website for more: https://mofanpy.com/tutorials/ 5 | """ 6 | import numpy as np 7 | 8 | TARGET_PHRASE = 'You get it!' # target DNA 9 | POP_SIZE = 300 # population size 10 | CROSS_RATE = 0.4 # mating probability (DNA crossover) 11 | MUTATION_RATE = 0.01 # mutation probability 12 | N_GENERATIONS = 1000 13 | 14 | DNA_SIZE = len(TARGET_PHRASE) 15 | TARGET_ASCII = np.fromstring(TARGET_PHRASE, dtype=np.uint8) # convert string to number 16 | ASCII_BOUND = [32, 126] 17 | 18 | 19 | class GA(object): 20 | def __init__(self, DNA_size, DNA_bound, cross_rate, mutation_rate, pop_size): 21 | self.DNA_size = DNA_size 22 | DNA_bound[1] += 1 23 | self.DNA_bound = DNA_bound 24 | self.cross_rate = cross_rate 25 | self.mutate_rate = mutation_rate 26 | self.pop_size = pop_size 27 | 28 | self.pop = np.random.randint(*DNA_bound, size=(pop_size, DNA_size)).astype(np.int8) # int8 for convert to ASCII 29 | 30 | def translateDNA(self, DNA): # convert to readable string 31 | return DNA.tostring().decode('ascii') 32 | 33 | def get_fitness(self): # count how many character matches 34 | match_count = (self.pop == TARGET_ASCII).sum(axis=1) 35 | return match_count 36 | 37 | def select(self): 38 | fitness = self.get_fitness() + 1e-4 # add a small amount to avoid all zero fitness 39 | idx = np.random.choice(np.arange(self.pop_size), size=self.pop_size, replace=True, p=fitness/fitness.sum()) 40 | return self.pop[idx] 41 | 42 | def crossover(self, parent, pop): 43 | if np.random.rand() < self.cross_rate: 44 | i_ = np.random.randint(0, self.pop_size, size=1) # select another individual from pop 45 | cross_points = np.random.randint(0, 2, self.DNA_size).astype(np.bool) # choose crossover points 46 | parent[cross_points] = pop[i_, cross_points] # mating and produce one child 47 | return parent 48 | 49 | def mutate(self, child): 50 | for point in range(self.DNA_size): 51 | if np.random.rand() < self.mutate_rate: 52 | child[point] = np.random.randint(*self.DNA_bound) # choose a random ASCII index 53 | return child 54 | 55 | def evolve(self): 56 | pop = self.select() 57 | pop_copy = pop.copy() 58 | for parent in pop: # for every parent 59 | child = self.crossover(parent, pop_copy) 60 | child = self.mutate(child) 61 | parent[:] = child 62 | self.pop = pop 63 | 64 | if __name__ == '__main__': 65 | ga = GA(DNA_size=DNA_SIZE, DNA_bound=ASCII_BOUND, cross_rate=CROSS_RATE, 66 | mutation_rate=MUTATION_RATE, pop_size=POP_SIZE) 67 | 68 | for generation in range(N_GENERATIONS): 69 | fitness = ga.get_fitness() 70 | best_DNA = ga.pop[np.argmax(fitness)] 71 | best_phrase = ga.translateDNA(best_DNA) 72 | print('Gen', generation, ': ', best_phrase) 73 | if best_phrase == TARGET_PHRASE: 74 | break 75 | ga.evolve() 76 | -------------------------------------------------------------------------------- /tutorial-contents/Genetic Algorithm/Microbial Genetic Algorithm.py: -------------------------------------------------------------------------------- 1 | """ 2 | Visualize Microbial Genetic Algorithm to find the maximum point in a graph. 3 | 4 | Visit my tutorial website for more: https://mofanpy.com/tutorials/ 5 | """ 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | 9 | DNA_SIZE = 10 # DNA length 10 | POP_SIZE = 20 # population size 11 | CROSS_RATE = 0.6 # mating probability (DNA crossover) 12 | MUTATION_RATE = 0.01 # mutation probability 13 | N_GENERATIONS = 200 14 | X_BOUND = [0, 5] # x upper and lower bounds 15 | 16 | 17 | def F(x): return np.sin(10*x)*x + np.cos(2*x)*x # to find the maximum of this function 18 | 19 | 20 | class MGA(object): 21 | def __init__(self, DNA_size, DNA_bound, cross_rate, mutation_rate, pop_size): 22 | self.DNA_size = DNA_size 23 | DNA_bound[1] += 1 24 | self.DNA_bound = DNA_bound 25 | self.cross_rate = cross_rate 26 | self.mutate_rate = mutation_rate 27 | self.pop_size = pop_size 28 | 29 | # initial DNAs for winner and loser 30 | self.pop = np.random.randint(*DNA_bound, size=(1, self.DNA_size)).repeat(pop_size, axis=0) 31 | 32 | def translateDNA(self, pop): 33 | # convert binary DNA to decimal and normalize it to a range(0, 5) 34 | return pop.dot(2 ** np.arange(self.DNA_size)[::-1]) / float(2 ** self.DNA_size - 1) * X_BOUND[1] 35 | 36 | def get_fitness(self, product): 37 | return product # it is OK to use product value as fitness in here 38 | 39 | def crossover(self, loser_winner): # crossover for loser 40 | cross_idx = np.empty((self.DNA_size,)).astype(np.bool) 41 | for i in range(self.DNA_size): 42 | cross_idx[i] = True if np.random.rand() < self.cross_rate else False # crossover index 43 | loser_winner[0, cross_idx] = loser_winner[1, cross_idx] # assign winners genes to loser 44 | return loser_winner 45 | 46 | def mutate(self, loser_winner): # mutation for loser 47 | mutation_idx = np.empty((self.DNA_size,)).astype(np.bool) 48 | for i in range(self.DNA_size): 49 | mutation_idx[i] = True if np.random.rand() < self.mutate_rate else False # mutation index 50 | # flip values in mutation points 51 | loser_winner[0, mutation_idx] = ~loser_winner[0, mutation_idx].astype(np.bool) 52 | return loser_winner 53 | 54 | def evolve(self, n): # nature selection wrt pop's fitness 55 | for _ in range(n): # random pick and compare n times 56 | sub_pop_idx = np.random.choice(np.arange(0, self.pop_size), size=2, replace=False) 57 | sub_pop = self.pop[sub_pop_idx] # pick 2 from pop 58 | product = F(self.translateDNA(sub_pop)) 59 | fitness = self.get_fitness(product) 60 | loser_winner_idx = np.argsort(fitness) 61 | loser_winner = sub_pop[loser_winner_idx] # the first is loser and second is winner 62 | loser_winner = self.crossover(loser_winner) 63 | loser_winner = self.mutate(loser_winner) 64 | self.pop[sub_pop_idx] = loser_winner 65 | 66 | DNA_prod = self.translateDNA(self.pop) 67 | pred = F(DNA_prod) 68 | return DNA_prod, pred 69 | 70 | 71 | plt.ion() # something about plotting 72 | x = np.linspace(*X_BOUND, 200) 73 | plt.plot(x, F(x)) 74 | 75 | ga = MGA(DNA_size=DNA_SIZE, DNA_bound=[0, 1], cross_rate=CROSS_RATE, mutation_rate=MUTATION_RATE, pop_size=POP_SIZE) 76 | 77 | for _ in range(N_GENERATIONS): # 100 generations 78 | DNA_prod, pred = ga.evolve(5) # natural selection, crossover and mutation 79 | 80 | # something about plotting 81 | if 'sca' in globals(): sca.remove() 82 | sca = plt.scatter(DNA_prod, pred, s=200, lw=0, c='red', alpha=0.5); plt.pause(0.05) 83 | 84 | plt.ioff();plt.show() -------------------------------------------------------------------------------- /tutorial-contents/Genetic Algorithm/Travel Sales Person.py: -------------------------------------------------------------------------------- 1 | """ 2 | Visualize Genetic Algorithm to find the shortest path for travel sales problem. 3 | 4 | Visit my tutorial website for more: https://mofanpy.com/tutorials/ 5 | """ 6 | import matplotlib.pyplot as plt 7 | import numpy as np 8 | 9 | N_CITIES = 20 # DNA size 10 | CROSS_RATE = 0.1 11 | MUTATE_RATE = 0.02 12 | POP_SIZE = 500 13 | N_GENERATIONS = 500 14 | 15 | 16 | class GA(object): 17 | def __init__(self, DNA_size, cross_rate, mutation_rate, pop_size, ): 18 | self.DNA_size = DNA_size 19 | self.cross_rate = cross_rate 20 | self.mutate_rate = mutation_rate 21 | self.pop_size = pop_size 22 | 23 | self.pop = np.vstack([np.random.permutation(DNA_size) for _ in range(pop_size)]) 24 | 25 | def translateDNA(self, DNA, city_position): # get cities' coord in order 26 | line_x = np.empty_like(DNA, dtype=np.float64) 27 | line_y = np.empty_like(DNA, dtype=np.float64) 28 | for i, d in enumerate(DNA): 29 | city_coord = city_position[d] 30 | line_x[i, :] = city_coord[:, 0] 31 | line_y[i, :] = city_coord[:, 1] 32 | return line_x, line_y 33 | 34 | def get_fitness(self, line_x, line_y): 35 | total_distance = np.empty((line_x.shape[0],), dtype=np.float64) 36 | for i, (xs, ys) in enumerate(zip(line_x, line_y)): 37 | total_distance[i] = np.sum(np.sqrt(np.square(np.diff(xs)) + np.square(np.diff(ys)))) 38 | fitness = np.exp(self.DNA_size * 2 / total_distance) 39 | return fitness, total_distance 40 | 41 | def select(self, fitness): 42 | idx = np.random.choice(np.arange(self.pop_size), size=self.pop_size, replace=True, p=fitness / fitness.sum()) 43 | return self.pop[idx] 44 | 45 | def crossover(self, parent, pop): 46 | if np.random.rand() < self.cross_rate: 47 | i_ = np.random.randint(0, self.pop_size, size=1) # select another individual from pop 48 | cross_points = np.random.randint(0, 2, self.DNA_size).astype(np.bool) # choose crossover points 49 | keep_city = parent[~cross_points] # find the city number 50 | swap_city = pop[i_, np.isin(pop[i_].ravel(), keep_city, invert=True)] 51 | parent[:] = np.concatenate((keep_city, swap_city)) 52 | return parent 53 | 54 | def mutate(self, child): 55 | for point in range(self.DNA_size): 56 | if np.random.rand() < self.mutate_rate: 57 | swap_point = np.random.randint(0, self.DNA_size) 58 | swapA, swapB = child[point], child[swap_point] 59 | child[point], child[swap_point] = swapB, swapA 60 | return child 61 | 62 | def evolve(self, fitness): 63 | pop = self.select(fitness) 64 | pop_copy = pop.copy() 65 | for parent in pop: # for every parent 66 | child = self.crossover(parent, pop_copy) 67 | child = self.mutate(child) 68 | parent[:] = child 69 | self.pop = pop 70 | 71 | 72 | class TravelSalesPerson(object): 73 | def __init__(self, n_cities): 74 | self.city_position = np.random.rand(n_cities, 2) 75 | plt.ion() 76 | 77 | def plotting(self, lx, ly, total_d): 78 | plt.cla() 79 | plt.scatter(self.city_position[:, 0].T, self.city_position[:, 1].T, s=100, c='k') 80 | plt.plot(lx.T, ly.T, 'r-') 81 | plt.text(-0.05, -0.05, "Total distance=%.2f" % total_d, fontdict={'size': 20, 'color': 'red'}) 82 | plt.xlim((-0.1, 1.1)) 83 | plt.ylim((-0.1, 1.1)) 84 | plt.pause(0.01) 85 | 86 | 87 | ga = GA(DNA_size=N_CITIES, cross_rate=CROSS_RATE, mutation_rate=MUTATE_RATE, pop_size=POP_SIZE) 88 | 89 | env = TravelSalesPerson(N_CITIES) 90 | for generation in range(N_GENERATIONS): 91 | lx, ly = ga.translateDNA(ga.pop, env.city_position) 92 | fitness, total_distance = ga.get_fitness(lx, ly) 93 | ga.evolve(fitness) 94 | best_idx = np.argmax(fitness) 95 | print('Gen:', generation, '| best fit: %.2f' % fitness[best_idx],) 96 | 97 | env.plotting(lx[best_idx], ly[best_idx], total_distance[best_idx]) 98 | 99 | plt.ioff() 100 | plt.show() -------------------------------------------------------------------------------- /tutorial-contents/Using Neural Nets/Evolution Strategy with Neural Nets.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple code for Distributed ES proposed by OpenAI. 3 | Based on this paper: Evolution Strategies as a Scalable Alternative to Reinforcement Learning 4 | Details can be found in : https://arxiv.org/abs/1703.03864 5 | 6 | Visit more on my tutorial site: https://mofanpy.com/tutorials/ 7 | """ 8 | import numpy as np 9 | import gym 10 | import multiprocessing as mp 11 | import time 12 | 13 | N_KID = 10 # half of the training population 14 | N_GENERATION = 5000 # training step 15 | LR = .05 # learning rate 16 | SIGMA = .05 # mutation strength or step size 17 | N_CORE = mp.cpu_count()-1 18 | CONFIG = [ 19 | dict(game="CartPole-v0", 20 | n_feature=4, n_action=2, continuous_a=[False], ep_max_step=700, eval_threshold=500), 21 | dict(game="MountainCar-v0", 22 | n_feature=2, n_action=3, continuous_a=[False], ep_max_step=200, eval_threshold=-120), 23 | dict(game="Pendulum-v0", 24 | n_feature=3, n_action=1, continuous_a=[True, 2.], ep_max_step=200, eval_threshold=-180) 25 | ][2] # choose your game 26 | 27 | 28 | def sign(k_id): return -1. if k_id % 2 == 0 else 1. # mirrored sampling 29 | 30 | 31 | class SGD(object): # optimizer with momentum 32 | def __init__(self, params, learning_rate, momentum=0.9): 33 | self.v = np.zeros_like(params).astype(np.float32) 34 | self.lr, self.momentum = learning_rate, momentum 35 | 36 | def get_gradients(self, gradients): 37 | self.v = self.momentum * self.v + (1. - self.momentum) * gradients 38 | return self.lr * self.v 39 | 40 | 41 | def params_reshape(shapes, params): # reshape to be a matrix 42 | p, start = [], 0 43 | for i, shape in enumerate(shapes): # flat params to matrix 44 | n_w, n_b = shape[0] * shape[1], shape[1] 45 | p = p + [params[start: start + n_w].reshape(shape), 46 | params[start + n_w: start + n_w + n_b].reshape((1, shape[1]))] 47 | start += n_w + n_b 48 | return p 49 | 50 | 51 | def get_reward(shapes, params, env, ep_max_step, continuous_a, seed_and_id=None,): 52 | # perturb parameters using seed 53 | if seed_and_id is not None: 54 | seed, k_id = seed_and_id 55 | np.random.seed(seed) 56 | params += sign(k_id) * SIGMA * np.random.randn(params.size) 57 | p = params_reshape(shapes, params) 58 | # run episode 59 | s = env.reset() 60 | ep_r = 0. 61 | for step in range(ep_max_step): 62 | a = get_action(p, s, continuous_a) 63 | s, r, done, _ = env.step(a) 64 | # mountain car's reward can be tricky 65 | if env.spec._env_name == 'MountainCar' and s[0] > -0.1: r = 0. 66 | ep_r += r 67 | if done: break 68 | return ep_r 69 | 70 | 71 | def get_action(params, x, continuous_a): 72 | x = x[np.newaxis, :] 73 | x = np.tanh(x.dot(params[0]) + params[1]) 74 | x = np.tanh(x.dot(params[2]) + params[3]) 75 | x = x.dot(params[4]) + params[5] 76 | if not continuous_a[0]: return np.argmax(x, axis=1)[0] # for discrete action 77 | else: return continuous_a[1] * np.tanh(x)[0] # for continuous action 78 | 79 | 80 | def build_net(): 81 | def linear(n_in, n_out): # network linear layer 82 | w = np.random.randn(n_in * n_out).astype(np.float32) * .1 83 | b = np.random.randn(n_out).astype(np.float32) * .1 84 | return (n_in, n_out), np.concatenate((w, b)) 85 | s0, p0 = linear(CONFIG['n_feature'], 30) 86 | s1, p1 = linear(30, 20) 87 | s2, p2 = linear(20, CONFIG['n_action']) 88 | return [s0, s1, s2], np.concatenate((p0, p1, p2)) 89 | 90 | 91 | def train(net_shapes, net_params, optimizer, utility, pool): 92 | # pass seed instead whole noise matrix to parallel will save your time 93 | noise_seed = np.random.randint(0, 2 ** 32 - 1, size=N_KID, dtype=np.uint32).repeat(2) # mirrored sampling 94 | 95 | # distribute training in parallel 96 | jobs = [pool.apply_async(get_reward, (net_shapes, net_params, env, CONFIG['ep_max_step'], CONFIG['continuous_a'], 97 | [noise_seed[k_id], k_id], )) for k_id in range(N_KID*2)] 98 | rewards = np.array([j.get() for j in jobs]) 99 | kids_rank = np.argsort(rewards)[::-1] # rank kid id by reward 100 | 101 | cumulative_update = np.zeros_like(net_params) # initialize update values 102 | for ui, k_id in enumerate(kids_rank): 103 | np.random.seed(noise_seed[k_id]) # reconstruct noise using seed 104 | cumulative_update += utility[ui] * sign(k_id) * np.random.randn(net_params.size) 105 | 106 | gradients = optimizer.get_gradients(cumulative_update/(2*N_KID*SIGMA)) 107 | return net_params + gradients, rewards 108 | 109 | 110 | if __name__ == "__main__": 111 | # utility instead reward for update parameters (rank transformation) 112 | base = N_KID * 2 # *2 for mirrored sampling 113 | rank = np.arange(1, base + 1) 114 | util_ = np.maximum(0, np.log(base / 2 + 1) - np.log(rank)) 115 | utility = util_ / util_.sum() - 1 / base 116 | 117 | # training 118 | net_shapes, net_params = build_net() 119 | env = gym.make(CONFIG['game']).unwrapped 120 | optimizer = SGD(net_params, LR) 121 | pool = mp.Pool(processes=N_CORE) 122 | mar = None # moving average reward 123 | for g in range(N_GENERATION): 124 | t0 = time.time() 125 | net_params, kid_rewards = train(net_shapes, net_params, optimizer, utility, pool) 126 | 127 | # test trained net without noise 128 | net_r = get_reward(net_shapes, net_params, env, CONFIG['ep_max_step'], CONFIG['continuous_a'], None,) 129 | mar = net_r if mar is None else 0.9 * mar + 0.1 * net_r # moving average reward 130 | print( 131 | 'Gen: ', g, 132 | '| Net_R: %.1f' % mar, 133 | '| Kid_avg_R: %.1f' % kid_rewards.mean(), 134 | '| Gen_T: %.2f' % (time.time() - t0),) 135 | if mar >= CONFIG['eval_threshold']: break 136 | 137 | # test 138 | print("\nTESTING....") 139 | p = params_reshape(net_shapes, net_params) 140 | while True: 141 | s = env.reset() 142 | for _ in range(CONFIG['ep_max_step']): 143 | env.render() 144 | a = get_action(p, s, CONFIG['continuous_a']) 145 | s, _, done, _ = env.step(a) 146 | if done: break -------------------------------------------------------------------------------- /tutorial-contents/Using Neural Nets/NEAT/config-feedforward: -------------------------------------------------------------------------------- 1 | #--- parameters for the XOR-2 experiment ---# 2 | 3 | [NEAT] 4 | fitness_criterion = max 5 | fitness_threshold = 3.9 6 | pop_size = 150 7 | reset_on_extinction = False 8 | 9 | [DefaultGenome] 10 | # node activation options 11 | activation_default = sigmoid 12 | activation_mutate_rate = 0.0 13 | activation_options = sigmoid 14 | 15 | # node aggregation options 16 | aggregation_default = sum 17 | aggregation_mutate_rate = 0.0 18 | aggregation_options = sum 19 | 20 | # node bias options 21 | bias_init_mean = 0.0 22 | bias_init_stdev = 1.0 23 | bias_max_value = 30.0 24 | bias_min_value = -30.0 25 | bias_mutate_power = 0.5 26 | bias_mutate_rate = 0.7 27 | bias_replace_rate = 0.1 28 | 29 | # genome compatibility options 30 | compatibility_disjoint_coefficient = 1.0 31 | compatibility_weight_coefficient = 0.5 32 | 33 | # connection add/remove rates 34 | conn_add_prob = 0.5 35 | conn_delete_prob = 0.5 36 | 37 | # connection enable options 38 | enabled_default = True 39 | enabled_mutate_rate = 0.01 40 | 41 | feed_forward = True 42 | initial_connection = full 43 | 44 | # node add/remove rates 45 | node_add_prob = 0.2 46 | node_delete_prob = 0.2 47 | 48 | # network parameters 49 | num_hidden = 0 50 | num_inputs = 2 51 | num_outputs = 1 52 | 53 | # node response options 54 | response_init_mean = 1.0 55 | response_init_stdev = 0.0 56 | response_max_value = 30.0 57 | response_min_value = -30.0 58 | response_mutate_power = 0.0 59 | response_mutate_rate = 0.0 60 | response_replace_rate = 0.0 61 | 62 | # connection weight options 63 | weight_init_mean = 0.0 64 | weight_init_stdev = 1.0 65 | weight_max_value = 30 66 | weight_min_value = -30 67 | weight_mutate_power = 0.5 68 | weight_mutate_rate = 0.8 69 | weight_replace_rate = 0.1 70 | 71 | [DefaultSpeciesSet] 72 | compatibility_threshold = 3.0 73 | 74 | [DefaultStagnation] 75 | species_fitness_func = max 76 | max_stagnation = 20 77 | species_elitism = 2 78 | 79 | [DefaultReproduction] 80 | elitism = 2 81 | survival_threshold = 0.2 -------------------------------------------------------------------------------- /tutorial-contents/Using Neural Nets/NEAT/run_xor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Using NEAT for supervised learning. This example comes from http://neat-python.readthedocs.io/en/latest/xor_example.html 3 | 4 | The detail for NEAT can be find in : http://nn.cs.utexas.edu/downloads/papers/stanley.cec02.pdf 5 | Visit my tutorial website for more: https://mofanpy.com/tutorials/ 6 | """ 7 | 8 | import os 9 | import neat 10 | import visualize 11 | 12 | # 2-input XOR inputs and expected outputs. 13 | xor_inputs = [(0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (1.0, 1.0)] 14 | xor_outputs = [ (0.0,), (1.0,), (1.0,), (0.0,)] 15 | 16 | 17 | def eval_genomes(genomes, config): 18 | for genome_id, genome in genomes: # for each individual 19 | genome.fitness = 4.0 # 4 xor evaluations 20 | net = neat.nn.FeedForwardNetwork.create(genome, config) 21 | for xi, xo in zip(xor_inputs, xor_outputs): 22 | output = net.activate(xi) 23 | genome.fitness -= (output[0] - xo[0]) ** 2 24 | 25 | 26 | def run(config_file): 27 | # Load configuration. 28 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 29 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 30 | config_file) 31 | 32 | # Create the population, which is the top-level object for a NEAT run. 33 | p = neat.Population(config) 34 | 35 | # Add a stdout reporter to show progress in the terminal. 36 | p.add_reporter(neat.StdOutReporter(True)) 37 | stats = neat.StatisticsReporter() 38 | p.add_reporter(stats) 39 | p.add_reporter(neat.Checkpointer(50)) 40 | 41 | # Run for up to 300 generations. 42 | winner = p.run(eval_genomes, 300) 43 | 44 | # Display the winning genome. 45 | print('\nBest genome:\n{!s}'.format(winner)) 46 | 47 | # Show output of the most fit genome against training data. 48 | print('\nOutput:') 49 | winner_net = neat.nn.FeedForwardNetwork.create(winner, config) 50 | for xi, xo in zip(xor_inputs, xor_outputs): 51 | output = winner_net.activate(xi) 52 | print("input {!r}, expected output {!r}, got {!r}".format(xi, xo, output)) 53 | 54 | node_names = {-1:'A', -2: 'B', 0:'A XOR B'} 55 | visualize.draw_net(config, winner, True, node_names=node_names) 56 | visualize.plot_stats(stats, ylog=False, view=True) 57 | visualize.plot_species(stats, view=True) 58 | 59 | p = neat.Checkpointer.restore_checkpoint('neat-checkpoint-49') 60 | p.run(eval_genomes, 10) 61 | 62 | 63 | if __name__ == '__main__': 64 | # Determine path to configuration file. This path manipulation is 65 | # here so that the script will run successfully regardless of the 66 | # current working directory. 67 | local_dir = os.path.dirname(__file__) 68 | config_path = os.path.join(local_dir, 'config-feedforward') 69 | run(config_path) -------------------------------------------------------------------------------- /tutorial-contents/Using Neural Nets/NEAT/visualize.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import copy 4 | import warnings 5 | 6 | import graphviz 7 | import matplotlib.pyplot as plt 8 | import numpy as np 9 | 10 | 11 | def plot_stats(statistics, ylog=False, view=False, filename='avg_fitness.svg'): 12 | """ Plots the population's average and best fitness. """ 13 | if plt is None: 14 | warnings.warn("This display is not available due to a missing optional dependency (matplotlib)") 15 | return 16 | 17 | generation = range(len(statistics.most_fit_genomes)) 18 | best_fitness = [c.fitness for c in statistics.most_fit_genomes] 19 | avg_fitness = np.array(statistics.get_fitness_mean()) 20 | stdev_fitness = np.array(statistics.get_fitness_stdev()) 21 | 22 | plt.plot(generation, avg_fitness, 'b-', label="average") 23 | plt.plot(generation, avg_fitness - stdev_fitness, 'g-.', label="-1 sd") 24 | plt.plot(generation, avg_fitness + stdev_fitness, 'g-.', label="+1 sd") 25 | plt.plot(generation, best_fitness, 'r-', label="best") 26 | 27 | plt.title("Population's average and best fitness") 28 | plt.xlabel("Generations") 29 | plt.ylabel("Fitness") 30 | plt.grid() 31 | plt.legend(loc="best") 32 | if ylog: 33 | plt.gca().set_yscale('symlog') 34 | 35 | plt.savefig(filename) 36 | if view: 37 | plt.show() 38 | 39 | plt.close() 40 | 41 | 42 | def plot_spikes(spikes, view=False, filename=None, title=None): 43 | """ Plots the trains for a single spiking neuron. """ 44 | t_values = [t for t, I, v, u, f in spikes] 45 | v_values = [v for t, I, v, u, f in spikes] 46 | u_values = [u for t, I, v, u, f in spikes] 47 | I_values = [I for t, I, v, u, f in spikes] 48 | f_values = [f for t, I, v, u, f in spikes] 49 | 50 | fig = plt.figure() 51 | plt.subplot(4, 1, 1) 52 | plt.ylabel("Potential (mv)") 53 | plt.xlabel("Time (in ms)") 54 | plt.grid() 55 | plt.plot(t_values, v_values, "g-") 56 | 57 | if title is None: 58 | plt.title("Izhikevich's spiking neuron model") 59 | else: 60 | plt.title("Izhikevich's spiking neuron model ({0!s})".format(title)) 61 | 62 | plt.subplot(4, 1, 2) 63 | plt.ylabel("Fired") 64 | plt.xlabel("Time (in ms)") 65 | plt.grid() 66 | plt.plot(t_values, f_values, "r-") 67 | 68 | plt.subplot(4, 1, 3) 69 | plt.ylabel("Recovery (u)") 70 | plt.xlabel("Time (in ms)") 71 | plt.grid() 72 | plt.plot(t_values, u_values, "r-") 73 | 74 | plt.subplot(4, 1, 4) 75 | plt.ylabel("Current (I)") 76 | plt.xlabel("Time (in ms)") 77 | plt.grid() 78 | plt.plot(t_values, I_values, "r-o") 79 | 80 | if filename is not None: 81 | plt.savefig(filename) 82 | 83 | if view: 84 | plt.show() 85 | plt.close() 86 | fig = None 87 | 88 | return fig 89 | 90 | 91 | def plot_species(statistics, view=False, filename='speciation.svg'): 92 | """ Visualizes speciation throughout evolution. """ 93 | if plt is None: 94 | warnings.warn("This display is not available due to a missing optional dependency (matplotlib)") 95 | return 96 | 97 | species_sizes = statistics.get_species_sizes() 98 | num_generations = len(species_sizes) 99 | curves = np.array(species_sizes).T 100 | 101 | fig, ax = plt.subplots() 102 | ax.stackplot(range(num_generations), *curves) 103 | 104 | plt.title("Speciation") 105 | plt.ylabel("Size per Species") 106 | plt.xlabel("Generations") 107 | 108 | plt.savefig(filename) 109 | 110 | if view: 111 | plt.show() 112 | 113 | plt.close() 114 | 115 | 116 | def draw_net(config, genome, view=False, filename=None, node_names=None, show_disabled=True, prune_unused=False, 117 | node_colors=None, fmt='svg'): 118 | """ Receives a genome and draws a neural network with arbitrary topology. """ 119 | # Attributes for network nodes. 120 | if graphviz is None: 121 | warnings.warn("This display is not available due to a missing optional dependency (graphviz)") 122 | return 123 | 124 | if node_names is None: 125 | node_names = {} 126 | 127 | assert type(node_names) is dict 128 | 129 | if node_colors is None: 130 | node_colors = {} 131 | 132 | assert type(node_colors) is dict 133 | 134 | node_attrs = { 135 | 'shape': 'circle', 136 | 'fontsize': '9', 137 | 'height': '0.2', 138 | 'width': '0.2'} 139 | 140 | dot = graphviz.Digraph(format=fmt, node_attr=node_attrs) 141 | 142 | inputs = set() 143 | for k in config.genome_config.input_keys: 144 | inputs.add(k) 145 | name = node_names.get(k, str(k)) 146 | input_attrs = {'style': 'filled', 147 | 'shape': 'box'} 148 | input_attrs['fillcolor'] = node_colors.get(k, 'lightgray') 149 | dot.node(name, _attributes=input_attrs) 150 | 151 | outputs = set() 152 | for k in config.genome_config.output_keys: 153 | outputs.add(k) 154 | name = node_names.get(k, str(k)) 155 | node_attrs = {'style': 'filled'} 156 | node_attrs['fillcolor'] = node_colors.get(k, 'lightblue') 157 | 158 | dot.node(name, _attributes=node_attrs) 159 | 160 | if prune_unused: 161 | connections = set() 162 | for cg in genome.connections.values(): 163 | if cg.enabled or show_disabled: 164 | connections.add((cg.in_node_id, cg.out_node_id)) 165 | 166 | used_nodes = copy.copy(outputs) 167 | pending = copy.copy(outputs) 168 | while pending: 169 | new_pending = set() 170 | for a, b in connections: 171 | if b in pending and a not in used_nodes: 172 | new_pending.add(a) 173 | used_nodes.add(a) 174 | pending = new_pending 175 | else: 176 | used_nodes = set(genome.nodes.keys()) 177 | 178 | for n in used_nodes: 179 | if n in inputs or n in outputs: 180 | continue 181 | 182 | attrs = {'style': 'filled', 183 | 'fillcolor': node_colors.get(n, 'white')} 184 | dot.node(str(n), _attributes=attrs) 185 | 186 | for cg in genome.connections.values(): 187 | if cg.enabled or show_disabled: 188 | #if cg.input not in used_nodes or cg.output not in used_nodes: 189 | # continue 190 | input, output = cg.key 191 | a = node_names.get(input, str(input)) 192 | b = node_names.get(output, str(output)) 193 | style = 'solid' if cg.enabled else 'dotted' 194 | color = 'green' if cg.weight > 0 else 'red' 195 | width = str(0.1 + abs(cg.weight / 5.0)) 196 | dot.edge(a, b, _attributes={'style': style, 'color': color, 'penwidth': width}) 197 | 198 | dot.render(filename, view=view) 199 | 200 | return dot -------------------------------------------------------------------------------- /tutorial-contents/Using Neural Nets/NEAT_gym/config: -------------------------------------------------------------------------------- 1 | # neat-python configuration for the LunarLander-v2 environment on OpenAI Gym 2 | 3 | [NEAT] 4 | pop_size = 100 5 | # Note: the fitness threshold will never be reached because 6 | # we are controlling the termination ourselves based on simulation performance. 7 | fitness_criterion = max 8 | fitness_threshold = 2. 9 | reset_on_extinction = 0 10 | 11 | [DefaultGenome] 12 | # node activation options 13 | activation_default = relu 14 | activation_mutate_rate = 0.0 15 | activation_options = relu 16 | 17 | # node aggregation options 18 | aggregation_default = sum 19 | aggregation_mutate_rate = 0.0 20 | aggregation_options = sum 21 | 22 | # node bias options 23 | bias_init_mean = 0.0 24 | bias_init_stdev = 1.0 25 | bias_max_value = 30.0 26 | bias_min_value = -30.0 27 | bias_mutate_power = 0.5 28 | bias_mutate_rate = 0.7 29 | bias_replace_rate = 0.1 30 | 31 | # genome compatibility options 32 | compatibility_disjoint_coefficient = 1.0 33 | compatibility_weight_coefficient = 1.0 34 | 35 | # connection add/remove rates 36 | conn_add_prob = 0.9 37 | conn_delete_prob = 0.2 38 | 39 | # connection enable options 40 | enabled_default = True 41 | enabled_mutate_rate = 0.01 42 | 43 | feed_forward = True 44 | initial_connection = full 45 | # options (unconnected, fs_neat, full) 46 | 47 | # node add/remove rates 48 | node_add_prob = 0.9 49 | node_delete_prob = 0.2 50 | 51 | # network parameters 52 | num_hidden = 0 53 | num_inputs = 4 54 | num_outputs = 2 55 | 56 | # node response options 57 | response_init_mean = 1.0 58 | response_init_stdev = 0.0 59 | response_max_value = 30.0 60 | response_min_value = -30.0 61 | response_mutate_power = 0.0 62 | response_mutate_rate = 0.0 63 | response_replace_rate = 0.0 64 | 65 | # connection weight options 66 | weight_init_mean = 0.0 67 | weight_init_stdev = 1.0 68 | weight_max_value = 30. 69 | weight_min_value = -30. 70 | weight_mutate_power = 0.5 71 | weight_mutate_rate = 0.8 72 | weight_replace_rate = 0.1 73 | 74 | [DefaultSpeciesSet] 75 | compatibility_threshold = 3.0 76 | 77 | [DefaultStagnation] 78 | species_fitness_func = max 79 | max_stagnation = 20 80 | species_elitism = 4 81 | 82 | [DefaultReproduction] 83 | elitism = 2 84 | survival_threshold = 0.2 -------------------------------------------------------------------------------- /tutorial-contents/Using Neural Nets/NEAT_gym/run_cartpole.py: -------------------------------------------------------------------------------- 1 | """ 2 | Using NEAT for reinforcement learning. 3 | 4 | The detail for NEAT can be find in : http://nn.cs.utexas.edu/downloads/papers/stanley.cec02.pdf 5 | Visit my tutorial website for more: https://mofanpy.com/tutorials/ 6 | """ 7 | import neat 8 | import numpy as np 9 | import gym 10 | import visualize 11 | 12 | GAME = 'CartPole-v0' 13 | env = gym.make(GAME).unwrapped 14 | 15 | CONFIG = "./config" 16 | EP_STEP = 300 # maximum episode steps 17 | GENERATION_EP = 10 # evaluate by the minimum of 10-episode rewards 18 | TRAINING = False # training or testing 19 | CHECKPOINT = 9 # test on this checkpoint 20 | 21 | 22 | def eval_genomes(genomes, config): 23 | for genome_id, genome in genomes: 24 | net = neat.nn.FeedForwardNetwork.create(genome, config) 25 | ep_r = [] 26 | for ep in range(GENERATION_EP): # run many episodes for the genome in case it's lucky 27 | accumulative_r = 0. # stage longer to get a greater episode reward 28 | observation = env.reset() 29 | for t in range(EP_STEP): 30 | action_values = net.activate(observation) 31 | action = np.argmax(action_values) 32 | observation_, reward, done, _ = env.step(action) 33 | accumulative_r += reward 34 | if done: 35 | break 36 | observation = observation_ 37 | ep_r.append(accumulative_r) 38 | genome.fitness = np.min(ep_r)/float(EP_STEP) # depends on the minimum episode reward 39 | 40 | 41 | def run(): 42 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 43 | neat.DefaultSpeciesSet, neat.DefaultStagnation, CONFIG) 44 | pop = neat.Population(config) 45 | 46 | # recode history 47 | stats = neat.StatisticsReporter() 48 | pop.add_reporter(stats) 49 | pop.add_reporter(neat.StdOutReporter(True)) 50 | pop.add_reporter(neat.Checkpointer(5)) 51 | 52 | pop.run(eval_genomes, 10) # train 10 generations 53 | 54 | # visualize training 55 | visualize.plot_stats(stats, ylog=False, view=True) 56 | visualize.plot_species(stats, view=True) 57 | 58 | 59 | def evaluation(): 60 | p = neat.Checkpointer.restore_checkpoint('neat-checkpoint-%i' % CHECKPOINT) 61 | winner = p.run(eval_genomes, 1) # find the winner in restored population 62 | 63 | # show winner net 64 | node_names = {-1: 'In0', -2: 'In1', -3: 'In3', -4: 'In4', 0: 'act1', 1: 'act2'} 65 | visualize.draw_net(p.config, winner, True, node_names=node_names) 66 | 67 | net = neat.nn.FeedForwardNetwork.create(winner, p.config) 68 | while True: 69 | s = env.reset() 70 | while True: 71 | env.render() 72 | a = np.argmax(net.activate(s)) 73 | s, r, done, _ = env.step(a) 74 | if done: break 75 | 76 | 77 | if __name__ == '__main__': 78 | if TRAINING: 79 | run() 80 | else: 81 | evaluation() 82 | -------------------------------------------------------------------------------- /tutorial-contents/Using Neural Nets/NEAT_gym/visualize.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import copy 4 | import warnings 5 | 6 | import graphviz 7 | import matplotlib.pyplot as plt 8 | import numpy as np 9 | 10 | 11 | def plot_stats(statistics, ylog=False, view=False, filename='avg_fitness.svg'): 12 | """ Plots the population's average and best fitness. """ 13 | if plt is None: 14 | warnings.warn("This display is not available due to a missing optional dependency (matplotlib)") 15 | return 16 | 17 | generation = range(len(statistics.most_fit_genomes)) 18 | best_fitness = [c.fitness for c in statistics.most_fit_genomes] 19 | avg_fitness = np.array(statistics.get_fitness_mean()) 20 | stdev_fitness = np.array(statistics.get_fitness_stdev()) 21 | 22 | plt.plot(generation, avg_fitness, 'b-', label="average") 23 | #plt.plot(generation, avg_fitness - stdev_fitness, 'g-.', label="-1 sd") 24 | plt.plot(generation, avg_fitness + stdev_fitness, 'g-.', label="+1 sd") 25 | plt.plot(generation, best_fitness, 'r-', label="best") 26 | 27 | plt.title("Population's average and best fitness") 28 | plt.xlabel("Generations") 29 | plt.ylabel("Fitness") 30 | plt.grid() 31 | plt.legend(loc="best") 32 | if ylog: 33 | plt.gca().set_yscale('symlog') 34 | 35 | plt.savefig(filename) 36 | if view: 37 | plt.show() 38 | 39 | plt.close() 40 | 41 | 42 | def plot_species(statistics, view=False, filename='speciation.svg'): 43 | """ Visualizes speciation throughout evolution. """ 44 | if plt is None: 45 | warnings.warn("This display is not available due to a missing optional dependency (matplotlib)") 46 | return 47 | 48 | species_sizes = statistics.get_species_sizes() 49 | num_generations = len(species_sizes) 50 | curves = np.array(species_sizes).T 51 | 52 | fig, ax = plt.subplots() 53 | ax.stackplot(range(num_generations), *curves) 54 | 55 | plt.title("Speciation") 56 | plt.ylabel("Size per Species") 57 | plt.xlabel("Generations") 58 | 59 | plt.savefig(filename) 60 | 61 | if view: 62 | plt.show() 63 | 64 | plt.close() 65 | 66 | 67 | def draw_net(config, genome, view=False, filename=None, node_names=None, show_disabled=True, prune_unused=False, 68 | node_colors=None, fmt='svg'): 69 | """ Receives a genome and draws a neural network with arbitrary topology. """ 70 | # Attributes for network nodes. 71 | if graphviz is None: 72 | warnings.warn("This display is not available due to a missing optional dependency (graphviz)") 73 | return 74 | 75 | if node_names is None: 76 | node_names = {} 77 | 78 | assert type(node_names) is dict 79 | 80 | if node_colors is None: 81 | node_colors = {} 82 | 83 | assert type(node_colors) is dict 84 | 85 | node_attrs = { 86 | 'shape': 'circle', 87 | 'fontsize': '9', 88 | 'height': '0.2', 89 | 'width': '0.2'} 90 | 91 | dot = graphviz.Digraph(format=fmt, node_attr=node_attrs) 92 | 93 | inputs = set() 94 | for k in config.genome_config.input_keys: 95 | inputs.add(k) 96 | name = node_names.get(k, str(k)) 97 | input_attrs = {'style': 'filled', 98 | 'shape': 'box'} 99 | input_attrs['fillcolor'] = node_colors.get(k, 'lightgray') 100 | dot.node(name, _attributes=input_attrs) 101 | 102 | outputs = set() 103 | for k in config.genome_config.output_keys: 104 | outputs.add(k) 105 | name = node_names.get(k, str(k)) 106 | node_attrs = {'style': 'filled'} 107 | node_attrs['fillcolor'] = node_colors.get(k, 'lightblue') 108 | 109 | dot.node(name, _attributes=node_attrs) 110 | 111 | if prune_unused: 112 | connections = set() 113 | for cg in genome.connections.values(): 114 | if cg.enabled or show_disabled: 115 | connections.add(cg.key) 116 | 117 | used_nodes = copy.copy(outputs) 118 | pending = copy.copy(outputs) 119 | while pending: 120 | #print(pending, used_nodes) 121 | new_pending = set() 122 | for a, b in connections: 123 | if b in pending and a not in used_nodes: 124 | new_pending.add(a) 125 | used_nodes.add(a) 126 | pending = new_pending 127 | else: 128 | used_nodes = set(genome.nodes.keys()) 129 | 130 | for n in used_nodes: 131 | if n in inputs or n in outputs: 132 | continue 133 | 134 | attrs = {'style': 'filled'} 135 | attrs['fillcolor'] = node_colors.get(n, 'white') 136 | dot.node(str(n), _attributes=attrs) 137 | 138 | for cg in genome.connections.values(): 139 | if cg.enabled or show_disabled: 140 | #if cg.input not in used_nodes or cg.output not in used_nodes: 141 | # continue 142 | input, output = cg.key 143 | a = node_names.get(input, str(input)) 144 | b = node_names.get(output, str(output)) 145 | style = 'solid' if cg.enabled else 'dotted' 146 | color = 'green' if cg.weight > 0 else 'red' 147 | width = str(0.1 + abs(cg.weight / 5.0)) 148 | dot.edge(a, b, _attributes={'style': style, 'color': color, 'penwidth': width}) 149 | 150 | dot.render(filename, view=view) 151 | 152 | return dot --------------------------------------------------------------------------------