下面我将记录我是怎样用python实现遗传算法的
遗传算法的实现步骤:
1.初始化种群
2.评价种群
3.按评价进行淘汰
4.在剩余种群中进行繁衍
5.繁衍时可能变异(在我的程序中是每次变异)
6.评价种群
7.重复3、4、5、6一定次数后输出最好的结果
我希望找到与某一张300*300的图像的cos相似度较高的,用900个10*10的随机颜色的方块组成的300*300的图像。
代码与注释如下:
from PIL import Image
import numpy as np
import random
from time import time
#下两行用于求内积
from itertools import starmap#starmap的用法是starmap(a,b),a是一个映射,b是一个数组,starmap将每一个b中的元素经过a映射,再生成一个新的,与b的长度一样的数组(迭代器)
import operator#operator.mul函数:operator.mul(2,4)输出8,这是个乘法
non0=lambda x:1e-99 if x==0 else x #处理分母为0情况的函数
#设置种群数量
ppl=int(input('请设置种群数量(请确保这个数可以被3整除,原因见↓):'))
#设置淘汰数量
slct=int(input('请设置淘汰数量(请确保淘汰数可由未淘汰区的两两配对繁殖完全弥补→淘汰数是总数的1/3):'))
#设置变异数量
vary=int(input('请设置变异数量(DNA长度为900):'))
#设置运行次数
times=int(input('请设置运行次数'))
#载入EXAMPLE图像
im0=Image.open('EXAMPLE.BMP')
orign=np.array(im0)
orign.resize(1,orign.size)
orign=orign.astype('object')[0]#object形长度不限
#将EXAMPLE的RGB值-127.5 使cos相似度的范围变为[-1,1]
for i in range(len(orign)):
orign[i]=orign[i]-127.5
#初始化种群
race={}#种群集合
for idt in range(ppl):#idt是每个个体的身份序列 #这个循环耗时23.20s/100次
colormap={}#colormap记录了组成每个个体的小方块的颜色及其序号
race[idt]=[]#为每个个体创建一个空的数组
for cnum in range(len(orign)//(3*100)): #为这个个体中的每个小方块设置一个RGB值,载入到colormap中
colormap[cnum]=(random.randint(0,255)-127.5,random.randint(0,255)-127.5,random.randint(0,255)-127.5)#将RGB值分别-127.5,原因同上
for i in range(len(orign)): #往idt数组中按RGBRGB...的顺序从第一个像素(从左到右再从上到下)开始填充
cnum=((i//3)%300//10+((i//3)//300//10)*30)#将像素的序号转化为对应的小方块的序号
race[idt].append(colormap[cnum][i%3]) #按像素的次序将R、G或B值填入race[idt]这个数组中 #i%3确保了R处是R值
remain=0#remain在最后一行有详细注释#使得第一次输出的(事实上是未知的)剩余用时为0
for n in range(times+1):#繁殖了time次后还需要再评估一次来得出最好的图像
t1=time()#计时1
#评价:
markboard=[]#markboard记录了每一个idt对应的相似度
for idt in range(ppl):#这个循环耗时19.53s/100次,意味着对数量为100的种群评价一次需要19秒左右
pdt=sum(starmap(operator.mul, zip(orign,race[idt])))#计算EXAMPLE与idt的RGB数组的内积 #如果np的数组是int32型的则此处的运算结果就会错 #zip()函数来可以把n个列表合并,并创建一个元组对的列表 #sum()将一个迭代器中的每一个元素都架起来
pls=np.linalg.norm(orign)*np.linalg.norm(race[idt])#计算↑两个数组的模的和
mark=((pdt/non0(pls)+1)/2,idt) #计算cos相似度并转化为0~1的小数 #由元组比较大小优先比较元组中的第一个元素,讲相似度与idt以前后次序对应起来 #处理了分母为0的情况
markboard.append(mark)#实验表明这个指令在前期耗时较长,而在接下来的循环中几乎不占用时间
#排序:
markboard.sort()#将markboard排序按相似度从小到大排序
print('运行进度:'+str(n)+'/'+str(times)+'\n剩余用时:'+str(remain)+'s/ '+str(remain/60)+'min/ '+str(remain/3600)+'h\n现在的最高相似度:'+str(markboard[-1][0]))#输出程序运行进度、剩余用时与现在种群中的最高相似度 #第一次输出次数不计,因为在最后一次繁殖还需要进行一次评价。#输出结果耗时不计
#判断是否需要输出图像:
if n==times:#将来可改为相似度达到一定程度停止,或隔一段时间显示一次
outdata=np.array(race[markboard[-1][1]])#输出排名最好的RGB数组
outdata.resize(1,90000,3)#将RGBRGB...转化为(RGB)(RGB)...来让PIL解码
for i in range(len(outdata)):
outdata[i]=outdata[i]+127.5#将颜色值复原
outdata=outdata.astype('uint8')#uint8是PIL解码需要的格式
imout=Image.frombytes('RGB',(300,300),outdata)#将数组信息转化为图像
imout.save(str(ppl)+'-'+str(times)+'次运行结果.bmp')#保存结果,名字以'种群数量-运行次数'为区分
imout.show()#显示结果
file = open(str(ppl)+'-'+str(times)+'次运行结果.txt', 'w')#创建或写入以'种群数量-运行次数'为区分的txt文件
file.write('最好相似度:'+str(markboard[-1][0]))#写入最好的相似度值
file.close()#关闭并保存txt文件
break#跳出循环
#淘汰与选择(在非淘汰区自由选择):
spc=[]#spc指淘汰后的空位
for i in range(slct):#这个循环耗时0.0s/100次
spc.append(markboard[0][1])#把最差的数组的idt加入到spc中
markboard.pop(0)#把最差的数组从markboard中删除(这是为了让淘汰完毕的markboard就变成非淘汰区)
random.shuffle(markboard)#将非淘汰区打乱顺序
families=[]#families保存了配对了的父与母数组
for i in range(len(markboard)//2):#整除号保证了输出的整数不会是浮点型的(e.g:3.0)
i=2*i#i乘以2以后为0,2,4,6...以此取出随机后的markboard中的数组对
families.append((markboard[i][1],markboard[i+1][1]))
#繁殖、变异与诞生
tspc=0#tspc指的是spc(即存储了空余位置的idt值的数组)中元素的序号,其从0开始,逐渐增大(由于淘汰量被要求设置为种群数的1/3的整数,所以tspc会在最后一个family中取到可行的最大值)
for family in families:#family即单个的数组对
rand=[]
randvary=list(range(900))#这个从0到899的数组代表了每一个色块
random.shuffle(randvary)#将色块数组随机排序
randvary=randvary[-vary:]#依据vary的值选出vary个需要变异的色块的编号
for i in range(900):#可以优化吗#random.randint可能耗时较长
rand.append(random.randint(0,1))#这个数组与色块序号一一对应,0代表从父数组中取色,1代表从母数组中取色(父、母没有详细界定)
varycolordict={}#varycolordict字典确保了每一个需要变异的色块中的颜色是一致的(如a像素需要变异,若a所在的色块序号不在字典中,则创建一个新颜色;若在字典中,则按字典赋予颜色)(使用这个复杂的方法是因为RGB数组是逐行编写的,而相同色块的序号是不连续的)
for i in range(270000):#对于新图像的每一个像素:
color=(i//3)%300//10+((i//3)//300//10)*30#由像素序号映射到色块序号
if color in randvary:#如果这个色块序号是需要变异的
try:race[spc[tspc]][i]=varycolordict[color][i%3]#若可以从varycolordict中取出颜色则为这个像素赋予这个颜色
except:#若上述指令不能执行(即varycolordict中没有这个色块序号的信息)
varycolor=[random.randint(0,255)-127.5,random.randint(0,255)-127.5,random.randint(0,255)-127.5]#则为这个色块序号创建一个新颜色
race[spc[tspc]][i]=varycolor[i%3]#为这个像素赋予这个新颜色(依据i%3的值决定赋予R、G还是B的值)
varycolordict[color]=varycolor#将色块序号→颜色的映射加入varycolordict
else:#如果这个色块序号是不需要变异的
if rand[color]==0:#如果需要从父数组取色
race[spc[tspc]][i]=race[family[0]][i]#则这个像素的值等于父数组中同序号像素的值
else:#反之亦然
race[spc[tspc]][i]=race[family[1]][i]
tspc=tspc+1
t2=time()#计时2
remain=(t2-t1)*(times-1-n)#remain依据计时1与计时2的最新结果估计从下一次print后开始计算的剩余的用时
84行代码
注意,每次运行会将前一次同名的输出结果覆盖
运行1500次耗时6小时左右,结果如下:
这是目标图像
这是随机生成的图像,cos相似度约为51%左右
这是运行1000次后的图像1,cos相似度为:0.6624029672983661
与目标图像是有一定相似度的
使用说明.txt :
1.准备一张名为EXAMPLE.BMP的图像,放在根目录
2.按提示输入运行参数
例子:
种群数量:99
淘汰数量:33
变异数量:90
运行次数:1003.结果将保存为一张最优图像和一个记录了最高相似度的txt文件
- 该图像是源文件的截图,源文件因为操作失误丢失。 ↩