先看这里!!!
只想要源码的可以直接到我的github上下载,文件为jupyter形式,相应的包下载上可以直接运行,代码中仍有不足的地方,欢迎指正,由于只是课程的作业,所以实现的比较简单,只可以二元函数运行,否则会报错,源码有自定义函数的案例,其他问题可以联系我。
本文不讲解原理,只给出代码和其逻辑。
代码用到的自带函数不懂得可以进行查阅,网上的信息非常全。
函数讲解
初始化,设置种群大小,染色体长度,函数范围等
def __init__(self,NP=100,L=20,G=10,Pc=0.8,Pm=0.1,Xs=7,Xx=0,Ys=7,Yx=0,is_max=False) -> None:
self.NP = NP # 种群数目
self.L = L # 染色体编码长度
self.G = G # 最大进化代数
self.Pc= Pc # 交叉概率
self.Pm = Pm # 变异概率
self.Xs = Xs # x边界
self.Xx = Xx
self.Ys = Ys # y边界
self.Yx = Yx
self.is_max = is_max # 是否求最大值 True为最大值
目标函数
# 定义目标函数
def f(self,x,y):
return x**2+y**2
自定义函数可以直接替换即可这里目标函数为
f
(
x
,
y
)
=
x
2
+
y
2
f(x,y) = {x^2} + {y^2}
f(x,y)=x2+y2
产生种群&编码
def generate_population(self):
l = 2
'''产生种群 已经编码好 0 1编码长度 L'''
x = np.random.randint(2, size=(self.NP, self.L))
y = np.random.randint(2, size=(self.NP, self.L))
return x,y
产生NP数量的染色体长度为L编码为0 1的种群
解码函数
def decode(self,x,y):
# 进行解码
X = x.dot(2**np.arange(self.L)[::-1])/(2**self.L-1)*(self.Xs-self.Xx)+self.Xx
Y = y.dot(2**np.arange(self.L)[::-1])/(2**self.L-1)*(self.Ys-self.Yx)+self.Yx
return X,Y
- 由于编码使用的0 1编码,这里解码要把二进制转为十进制数
- 具体过程就是先将整个染色体映射到0~1之间,再通过函数定义域放大
- 映射过程可以自定义,这里采用的是2的i次幂/(2的染色体长度次幂-1)之后求和,i为染色体的下标
- 至于为什么这样解码不再赘述,解码方式有很多,本文只是采用其中一种,只要知道染色体长度越长,越精确即可
适应度函数
def fitness(self,x,y):
re = self.f(x,y)
# 由于结果可能为负值 但是再下面的选择阶段不允许出现负值,需要进行处理
# 0值相当于概率0,所以需要加个非常小的正数
if self.is_max: # 选最大值
re = re - np.min(re) + 1e-5
else:
re = np.max(re) - re + 1e-5
return re
- 对于函数求最值过程中,以求最小值为例,肯定是f(x,y)的值越小越好,所以这里用目标函数为适应度函数
- 但由于种群的需要选择,所以为了避免后续数值为0的出现,需要进行变换,加上一个很小的正数,防止概率为0
- 逻辑分支的代码以if为例,当寻找最大值时,为了避免负值出现影响挑选,所以减去了最小值,避免0加上极小的正数,else同理
挑选操作
def select_population(self,x,y):
x_ ,y_ =self.decode(x,y)
result = self.fitness(x_,y_)
# 挑选适应度高的个体
index = np.random.choice(np.arange(self.NP),replace=True,size=self.NP,p=result/result.sum())
selected_x = x[index]
selected_y = y[index]
return selected_x,selected_y
- 挑选过程将解码与适应度函数连接了起来,先解码后通过f得到适应度,再通过numpy模拟轮盘赌法进行不放回挑选个体,最后返回挑选过的个体
交叉变异
def cross_mutation(self,x,y):
father = []
father.extend(x)
father.extend(y)
children = []
for i in father:
child = i
# 交叉操作
if np.random.rand() < self.Pc:
# 产生随机交叉点
point = np.random.randint(self.L)
# 选取母节点
# print(point)
child[point:] = father[np.random.randint(self.NP)][point:]
# 变异操作
if np.random.rand() < self.Pm:
point = np.random.randint(self.L)
child[point]^=1 # 取反
children.append(child)
return np.array(children[:len(children)//2]),np.array(children[len(children)//2:])
- 先将x和y全取出便于交叉操作即为father列表
- 交叉操作在迭代总个体过程中,自己作为父亲,随机抽一个作为母亲,采用单点交叉
- 变异直接取反即可
种群进化
def population_iteration(self):
self.process = []
x,y = self.generate_population()
for i in range(self.G):
x,y = self.select_population(x,y)
x,y = self.cross_mutation(x,y)
a,b =self.decode(x,y)
self.process.append(self.f(a,b).sum())
self.x ,self.y= self.decode(x,y)
self.plot_3d()
self.plot_coss()
- process列表记录了种群总体的函数和,每次迭代可以看到是否朝向有利方向进化
- 先生成种群,随后挑选,交叉变异。继续重复知道迭代完成
- 注意,进化过程中,全部操作x,y均为染色体形式进行
- 迭代完成后将最后一代种群赋值self.x ,self.y用于存储种群信息即标点
- 最后两个调用函数画图不再赘婿,可以在整体代码查看
运行样例
temp = Ga(L=40,G=50)
temp.population_iteration()
整体源码
class Ga:
def __init__(self,NP=100,L=20,G=10,Pc=0.8,Pm=0.1,Xs=7,Xx=0,Ys=7,Yx=0,is_max=False) -> None:
self.NP = NP # 种群数目
self.L = L # 染色体编码长度
self.G = G # 最大进化代数
self.Pc= Pc # 交叉概率
self.Pm = Pm # 变异概率
self.Xs = Xs # x边界
self.Xx = Xx
self.Ys = Ys # y边界
self.Yx = Yx
self.choose = []
self.is_max = is_max # 是否求最大值
# 定义目标函数
def f(self,x,y):
return x**2+y**2
# 解码函数
def decode(self,x,y):
# 进行解码
X = x.dot(2**np.arange(self.L)[::-1])/(2**self.L-1)*(self.Xs-self.Xx)+self.Xx
Y = y.dot(2**np.arange(self.L)[::-1])/(2**self.L-1)*(self.Ys-self.Yx)+self.Yx
return X,Y
def generate_population(self):
l = 2
'''产生种群 已经编码好 0 1编码长度 L'''
x = np.random.randint(2, size=(self.NP, self.L))
y = np.random.randint(2, size=(self.NP, self.L))
# return np.array([np.random.randint(2, size=(NP, L)) for i in range(l)])
return x,y
def fitness(self,x,y):
re = self.f(x,y)
# 由于结果可能为负值 但是再下面的选择阶段不允许出现负值,需要进行处理
# 0值相当于概率0,所以需要加个非常小的正数
if self.is_max: # 选最大值
re = re - np.min(re) + 1e-5
else:
re = np.max(re) - re + 1e-5
return re
def select_population(self,x,y):
x_ ,y_ =self.decode(x,y)
result = self.fitness(x_,y_)
# 挑选适应度高的个体
index = np.random.choice(np.arange(self.NP),replace=True,size=self.NP,p=result/result.sum())
selected_x = x[index]
selected_y = y[index]
self.choose.append(self.decode(selected_x,selected_y))
return selected_x,selected_y
def cross_mutation(self,x,y):
father = []
father.extend(x)
father.extend(y)
children = []
for i in father:
child = i
# 交叉操作
if np.random.rand() < self.Pc:
# 产生随机交叉点
point = np.random.randint(self.L)
# 选取母节点
# print(point)
child[point:] = i[point:]
# 变异操作
if np.random.rand() < self.Pm:
point = np.random.randint(self.L)
child[point]^=1 # 取反
children.append(child)
return np.array(children[:len(children)//2]),np.array(children[len(children)//2:])
def population_iteration(self):
self.process = []
x,y = self.generate_population()
# x , y= decode(x,y)
for i in range(self.G):
pre_x,pre_y = x , y
x,y = self.select_population(x,y)
x,y = self.cross_mutation(x,y)
a,b =self.decode(x,y)
self.process.append(self.f(a,b).sum())
self.x ,self.y= self.decode(x,y)
self.plot_3d()
self.plot_coss()
def plot_3d(self):
fig = plt.figure(figsize=(10,7))
ax = Axes3D(fig)
X = np.linspace(self.Xs,self.Xx,100)
Y = np.linspace(self.Ys,self.Yx,100)
X,Y = np.meshgrid(X, Y)
Z = self.f(X, Y)
ax.plot_surface(X,Y,Z,rstride=1, cstride=1, cmap='rainbow')
# ax.set_zlim(-10,10)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.scatter(self.x,self.y,self.f(self.x,self.y),c='black')
plt.title("result")
plt.show()
def plot_coss(self):
plt.plot(range(len(self.process)),self.process)
plt.title("Total Population Values")
plt.show()