莫烦python:Evolution Strategy Basic 进化策略基础
莫烦老师网课链接:https://www.bilibili.com/video/av16926245?p=8&spm_id_from=pageDriver
解析全在代码里了,可直接copy去看
"""
The Evolution Strategy can be summarized as the following term:
{mu/rho +, lambda}-ES
Here we use following term to find a maximum point.
{n_pop/n_pop + n_kid}-ES
Visit my tutorial website for more: https://mofanpy.com/tutorials/
要我用一句话概括ES: 在程序里生宝宝, 杀死不乖的宝宝, 让乖宝宝继续生宝宝.
"""
import numpy as np
import matplotlib.pyplot as plt
DNA_SIZE = 1 # DNA (real number)
DNA_BOUND = [0, 5] # solution upper and lower bounds
N_GENERATIONS = 200
POP_SIZE = 100 # population size
N_KID = 50 # n kids per generation
def F(x): return np.sin(10*x)*x + np.cos(2*x)*x # to find the maximum of this function
# find non-zero fitness for selection
def get_fitness(pred): return pred.flatten()
# 首先的 make_kid 功能. 我们随机找到一对父母, 然后将父母的 DNA 和 mut_strength 基因都 crossover 给 kid.
# 然后再根据 mut_strength mutate 一下 kid 的 DNA. 也就是用正态分布抽一个 DNA sample.
# 而且 mut_strength 也能变异. 将变异强度变异以后, 他就能在快收敛的时候很自觉的逐渐减小变异强度, 方便收敛.
def make_kid(pop, n_kid):
# generate empty kid holder
# 根据正态分布生孩子
kids = {'DNA': np.empty((n_kid, DNA_SIZE))} # kids' DNA-->(50, 1)
kids['mut_strength'] = np.empty_like(kids['DNA']) # kids' mut_strength-->(50, 1)
for kv, ks in zip(kids['DNA'], kids['mut_strength']):
# crossover (roughly half p1 and half p2)
# parents1 parents2-->随机选择
p1, p2 = np.random.choice(np.arange(POP_SIZE), size=2, replace=False)
# replace表示抽样后是否放回,为True表示有放回,则可能会出现相同的索引值,为False则不会
# crossover points
cp = np.random.randint(0, 2, DNA_SIZE, dtype=np.bool)
# 爸妈的crossover points
kv[cp] = pop['DNA'][p1, cp]
kv[~cp] = pop['DNA'][p2, ~cp]
# kids mut_strength
ks[cp] = pop['mut_strength'][p1, cp]
ks[~cp] = pop['mut_strength'][p2, ~cp]
# 以上便是将爸妈的DNA交叉后便合并到一块的结果
# 怎么合并的?--》比如 爸爸的DNA是[[50], [49],...]
# kv[cp] = pop['DNA'][p1, cp]-->将50替换掉
# kv[~cp] = pop['DNA'][p2, ~cp]剩下的全是妈妈的
# 又因为是随机配对的,所以有可能会把相同的基因又换回去,但是这没关系的
# mutate (change DNA based on normal distribution)
# 让变异强度也产生一定幅度的变异
# rand-->返回shape为(d0, d1, ..., dn)的标准正态分布(均值为0,标准差为1)的数组
ks[:] = np.maximum(ks + (np.random.rand(*ks.shape)-0.5), 0.) # must > 0
# kids value-->每一个的变异强度*正态分布的标准差
kv += ks * np.random.randn(*kv.shape)
# 放到0~5的范围内
kv[:] = np.clip(kv, *DNA_BOUND) # clip the mutated value
return kids
def kill_bad(pop, kids):
# put pop and kids together
# 不是按照比例来选择,是按照排序来选择的
for key in ['DNA', 'mut_strength']:
pop[key] = np.vstack((pop[key], kids[key]))
# vstack是垂直(按照行顺序)的把数组给堆叠起来
fitness = get_fitness(F(pop['DNA'])) # calculate global fitness
idx = np.arange(pop['DNA'].shape[0])
# 选取最大的前100位
good_idx = idx[fitness.argsort()][-POP_SIZE:] # selected by fitness ranking (not value)
for key in ['DNA', 'mut_strength']:
pop[key] = pop[key][good_idx]
return pop
pop = dict(DNA=5 * np.random.rand(1, DNA_SIZE).repeat(POP_SIZE, axis=0), # initialize the pop DNA values
mut_strength=np.random.rand(POP_SIZE, DNA_SIZE)) # initialize the pop mutation strength values
# 这个点只有一个, 所以我们的 DNA 的长度就只有一个. 我们用一个 python 字典来存这两种 DNA 的信息. 这里 DNA 存的是均值, mut_strength 存的是标准差.
# 传统的 ES DNA 形式分两种, 它有两条 DNA. 一个 DNA 是控制数值的, 第二个 DNA 是控制这个数值的变异强度. 比如一个问题有4个变量.
# 那一个 DNA 中就有4个位置存放这4个变量的值 (这就是我们要得到的答案值). 第二个 DNA 中就存放4个变量的变动幅度值.
# DNA1=1.23, -0.13, 2.35, 112.5 可以理解为4个正态分布的4个平均值.
# DNA2=0.1, 2.44, 5.112, 2.144 可以理解为4个正态分布的4个标准差.
# 所以这两条 DNA 都需要被 crossover 和 mutate.
plt.ion() # something about plotting
x = np.linspace(*DNA_BOUND, 200)
plt.plot(x, F(x))
for _ in range(N_GENERATIONS):
# something about plotting
if 'sca' in globals():
sca.remove()
sca = plt.scatter(pop['DNA'], F(pop['DNA']), s=200, lw=0, c='red', alpha=0.5); plt.pause(0.05)
# ES part
kids = make_kid(pop, N_KID) # 生宝宝
pop = kill_bad(pop, kids) # keep some good parent for elitism
plt.ioff()
plt.show()