遗传算法(Genetic Algorithm,GA)最早是由美国的 John holland于20世纪70年代提出,该算法是根据大自然中生物体进化规律而设计提出的,是一种通过模拟自然进化过程搜索最优解的方法。
该算法通过数学的方式,利用计算机仿真运算,将问题的求解过程转换成类似生物进化中的染色体基因的交叉、变异等过程。在求解较为复杂的组合优化问题时,相对一些常规的优化算法,通常能够较快地获得较好的优化结果。遗传算法已被人们广泛地应用于组合优化、机器学习、信号处理、自适应控制和人工生命等领域。
(1)编码
(2)适应度函数的尺度变换
(3)选择
(4)交叉
(5)变异
(1)定义适应度函数
import math
def func(x, y):
num = 6.452 * (x + 0.125 * y) * (math.cos(x) - math.cos(2 * y)) ** 2
den = math.sqrt(0.8 + (x - 4.2) ** 2 + 2 * (y - 7) ** 2)
return num / den + 3.226 * y
(2)初始化GeneticAlgorith类
class GeneticAlgorithm:
def __init__(self, function, M, gen, Pc=0.85, Pm=0.05):
"""
:param function: fitness function
:param M: population
:param gen: total generations
:param Pc: probability of crossover
:param Pm: probability of mutation
"""
self.func = function
self.Pc = Pc
self.Pm = Pm
self.M = M
self.gen = gen
self.dec_num = 3
self.X = []
self.Y = []
self.chr = []
self.f = []
self.rank = []
self.history = {
'f': [],
'x': [],
'y': []
}
(3)编码函数
定义编码方式:x, y => chromosome
def num2str(self, num):
# return str(int(num)) + str(int(num - int(num)) * 1e3)
s = str(num).replace('.', '')
s += '0' * abs(int(num) // 10 + 1 + self.dec_num - len(s))
return s
def encoder(self, x, y):
chr_list = []
for i in range(len(x)):
chr = self.num2str(x[i]) + self.num2str(y[i])
chr_list.append(chr)
return chr_list
于此同时,定义编码方式所对应的解码方式:chromosome => x, y
def str2num(self, s):
num = int(s[:-self.dec_num]) + float(s[-self.dec_num:]) / 10 ** self.dec_num
return round(num, self.dec_num)
def decoder(self, chr):
cut = int(len(chr[0]) / 2)
x = [self.str2num(chr[i][:cut]) for i in range(len(chr))]
y = [self.str2num(chr[i][cut:]) for i in range(len(chr))]
return x, y
(4)选择后代染色体
这里以适应度比例作为选择方式,通过轮盘赌的方法随机选择M次,得到M个新后代(适应度比例越大,轮盘赌中随机数落在该扇区的概率就越大,被选中的概率就越高)
from random import random
def choose(self):
# calculate percentage
s = sum(self.f)
p = [self.f[i] / s for i in range(self.M)]
chosen = []
# choose M times
for i in range(self.M):
cum = 0
m = random()
# Roulette
for j in range(self.M):
cum += p[j]
if cum >= m:
chosen.append(self.chr[j])
break
return chosen
(5)交叉
使用一点交叉作为交叉算子,对每两条染色体以Pc的概率进行交叉
from random import randint
def crossover(self, chr):
crossed = []
# if chr list is odd
if len(chr) % 2:
crossed.append(chr.pop())
for i in range(0, len(chr), 2):
a = chr[i]
b = chr[i + 1]
# 0.85 probability of crossover
if random() < self.Pc:
loc = randint(1, len(chr[i]) - 1)
temp = a[loc:]
a = a[:loc] + b[loc:]
b = b[:loc] + temp
# add to crossed
crossed.append(a)
crossed.append(b)
return crossed
(6)变异
用位点变异的方法,对某条染色体的每一位基因以概率Pm进行变异,并确保变异后与变异前的基因不同
def mutation(self, chr):
res = []
for i in chr:
l = list(i)
for j in range(len(l)):
# 0.05 probability of mutation on each location
if random() < self.Pm:
while True:
r = str(randint(0, 9))
if r != l[j]:
l[j] = r
break
res.append(''.join(l))
return res
(7)主函数
先对染色体种群进行初始化并编码,然后开始迭代进化:
from random import uniform
def run(self):
# initialization
x = []
y = []
for i in range(self.M):
x.append(round(uniform(0, 10), self.dec_num))
y.append(round(uniform(0, 10), self.dec_num))
self.X = x
self.Y = y
self.chr = self.encoder(x, y)
# iteration
for iter in range(self.gen):
self.f = [func(self.X[i], self.Y[i]) for i in range(self.M)]
fitness_sort = sorted(enumerate(self.f), key=lambda x: x[1], reverse=True)
# 1st : fitness[rank[0]]
self.rank = [i[0] for i in fitness_sort]
winner = self.f[self.rank[0]]
print(f'Iter={iter + 1}, Max-Fitness={winner}')
# save to history
self.history['f'].append(winner)
self.history['x'].append(self.X[self.rank[0]])
self.history['y'].append(self.Y[self.rank[0]])
# choose, crossover and mutation
chosen = self.choose()
crossed = self.crossover(chosen)
self.chr = self.mutation(crossed)
self.X, self.Y = self.decoder(self.chr)
(8)运行结果
实例化GeneticAlgorith类,设置种群数为10,迭代次数为100次
if __name__ == '__main__':
# run
ga = GeneticAlgorithm(func, 10, 100)
ga.run()
# plot
plt.plot(ga.history['f'])
plt.title('Fitness value')
plt.xlabel('Iter')
plt.show()
运行得到结果如下
每一次遗传算法的运算结果都可能有较大的不同,这跟初始种群的选取、交叉等操作有密切的联系,如果想提高稳定性可以进行参数的调整,如扩大种群的数量,增加迭代次数,选用不同的交叉、变异方法和相应的概率,以优化这一模型,得到更加理想的结果。
以上是遗传算法的Python实现,最后附上完整代码:
# -*- coding: utf-8 -*-
# @Author : gyy
# @Email : [email protected]
# @File : GA.py
import math
import matplotlib.pyplot as plt
from random import random, randint, uniform
def func(x, y):
num = 6.452 * (x + 0.125 * y) * (math.cos(x) - math.cos(2 * y)) ** 2
den = math.sqrt(0.8 + (x - 4.2) ** 2 + 2 * (y - 7) ** 2)
return num / den + 3.226 * y
class GeneticAlgorithm:
def __init__(self, function, M, gen, Pc=0.85, Pm=0.05):
"""
:param function: fitness function
:param M: population
:param gen: total generations
:param Pc: probability of crossover
:param Pm: probability of mutation
"""
self.func = function
self.Pc = Pc
self.Pm = Pm
self.M = M
self.gen = gen
self.dec_num = 3
self.X = []
self.Y = []
self.chr = []
self.f = []
self.rank = []
self.history = {
'f': [],
'x': [],
'y': []
}
def num2str(self, num):
# return str(int(num)) + str(int(num - int(num)) * 1e3)
s = str(num).replace('.', '')
s += '0' * abs(int(num) // 10 + 1 + self.dec_num - len(s))
return s
def encoder(self, x, y):
chr_list = []
for i in range(len(x)):
chr = self.num2str(x[i]) + self.num2str(y[i])
chr_list.append(chr)
return chr_list
def str2num(self, s):
num = int(s[:-self.dec_num]) + float(s[-self.dec_num:]) / 10 ** self.dec_num
return round(num, self.dec_num)
def decoder(self, chr):
cut = int(len(chr[0]) / 2)
x = [self.str2num(chr[i][:cut]) for i in range(len(chr))]
y = [self.str2num(chr[i][cut:]) for i in range(len(chr))]
return x, y
def choose(self):
# calculate percentage
s = sum(self.f)
p = [self.f[i] / s for i in range(self.M)]
chosen = []
# choose M times
for i in range(self.M):
cum = 0
m = random()
# Roulette
for j in range(self.M):
cum += p[j]
if cum >= m:
chosen.append(self.chr[j])
break
return chosen
def crossover(self, chr):
crossed = []
# if chr list is odd
if len(chr) % 2:
crossed.append(chr.pop())
for i in range(0, len(chr), 2):
a = chr[i]
b = chr[i + 1]
# 0.85 probability of crossover
if random() < self.Pc:
loc = randint(1, len(chr[i]) - 1)
temp = a[loc:]
a = a[:loc] + b[loc:]
b = b[:loc] + temp
# add to crossed
crossed.append(a)
crossed.append(b)
return crossed
def mutation(self, chr):
res = []
for i in chr:
l = list(i)
for j in range(len(l)):
# 0.05 probability of mutation on each location
if random() < self.Pm:
while True:
r = str(randint(0, 9))
if r != l[j]:
l[j] = r
break
res.append(''.join(l))
return res
def run(self):
# initialization
x = []
y = []
for i in range(self.M):
x.append(round(uniform(0, 10), self.dec_num))
y.append(round(uniform(0, 10), self.dec_num))
self.X = x
self.Y = y
self.chr = self.encoder(x, y)
# iteration
for iter in range(self.gen):
self.f = [func(self.X[i], self.Y[i]) for i in range(self.M)]
fitness_sort = sorted(enumerate(self.f), key=lambda x: x[1], reverse=True)
# 1st : fitness[rank[0]]
self.rank = [i[0] for i in fitness_sort]
winner = self.f[self.rank[0]]
print(f'Iter={iter + 1}, Max-Fitness={winner}')
# save to history
self.history['f'].append(winner)
self.history['x'].append(self.X[self.rank[0]])
self.history['y'].append(self.Y[self.rank[0]])
# choose, crossover and mutation
chosen = self.choose()
crossed = self.crossover(chosen)
self.chr = self.mutation(crossed)
self.X, self.Y = self.decoder(self.chr)
if __name__ == '__main__':
# run
ga = GeneticAlgorithm(func, 10, 100)
ga.run()
# plot
plt.plot(ga.history['f'])
plt.title('Fitness value')
plt.xlabel('Iter')
plt.show()