代码实现及理论参考自中国大学mooc人工智能与信息社会(陈斌)
遗传算法及案例描述
所谓遗传算法,是一种受达尔文自然进化理论启发的搜索启发式算法。主要包括了交叉繁殖、自然选择、变异这几个部分。
先来看案例。现在我手头有一张图片。我想让128个半透明的三角形进行位置大小及颜色上的不同排列组合,最终组成下面的滑稽图案,那么要如何做呢?
我们知道基因是遗传中最基本的单元,可以认为由这128个不同的三角形基因共同组成了一个生物个体。
一个三角形即一个基因的确定因素有三个,分别是:
颜色、三顶点坐标(坐标同时构成了大小和位置)、透明度
有了这个父代个体后,我们就可以通过遗传算法生成子代。
遗传包括基因复制,即父代基因原封不动复制给子代,还包括交叉,即将来自父母双方的基因组合在一起,得到新的子代
比如对于下面两个父代个体,通过交叉(即交换父代三角形基因),就生成了两个新的子代个体。
变异就很好理解了,父代在生成子代的时候,父代中的某些三角形基因随机地发生了坐标、颜色、透明度的改变
最重要的就是自然选择,有利的个体留存下来繁衍后代,不利的个体逐渐被淘汰。也就是说我们看生成的这个图形中是否和给定的滑稽图案相似,那些不像滑稽图案的就被淘汰。
我们可以通过比较每个像素点rgb的差值,值越小就越相似,值越大的就淘汰掉。
我们规定,如果子代比父代更接近给定的图案,就子代取代后代,反之子代被淘汰。
最终,当适应度函数 f 小于一定的阈值或者循环迭代的次数超过了一定的限制,遗传算法终止。
我们来看这个算法最终运行后的结果:
当算法遗传到第500代时,已经显示出来一个圆形
而当繁衍到第29900代时,已经很接近滑稽图案了
最后总结这个算法,就是通过不断地繁殖和变异,淘汰掉那些与图案相似度不高的个体,这就是遗传算法。
代码运行环境搭建
1.安装mxnet及git
sudo apt-get update
sudo apt-get install -y build-essential git libatlas-base-dev libopencv-dev
git clone --recursive https://github.com/dmlc/mxnet
cd mxnet; make -j4
2.如果git 克隆速度较慢,可以先ctrl+z取消,先执行下面操作,再重新克隆。速度快的可忽略第二步
git config --global http.postBuffer 524288000
sudo vim /etc/hosts
在末尾加入
151.101.185.194 github.global.ssl.fastly.net
192.30.253.113 github.com
3. pthon接口配置
sudo apt-get install python-numpy
完成后可以用以下命令进行测试。这里的mxnet路径根据实际情况去修改
python mxnet/example/image-classification/train_mnist.py
接着添加路径以解决No module named XXX
/*
* ~表示我的mxnet克隆在home下面,请根据实际情况修改mxnet路径。
* 比如克隆在了桌面,就是~/Desktop/mxnet
*/
export PYTHONPATH=~/mxnet/python
cp -r ~/mxnet/python/mxnet .
cp ~/mxnet/lib/libmxnet.so mxnet/
4.安装minpy
sudo pip install minpy
5.在写好python文件的目录下,新建一个results的文件夹,并准备任意一张图片
程序会要求输入文件路径,比如文件在当前路径就加上 " ./ "
代码实现
#coding:utf-8
# -*- coding: utf-8 -*-
#代码参考自中国大学mooc人工智能与信息社会(陈斌)
from PIL import Image, ImageDraw
import os
import gc
import random as r
import minpy.numpy as np
class Color(object):
'''
定义颜色的类,这个类包含r,g,b,a表示颜色属性
'''
def __init__(self):
self.r = r.randint(0, 255)
self.g = r.randint(0, 255)
self.b = r.randint(0, 255)
self.a = r.randint(95, 115)
def mutate_or_not(rate):
'''
生成随机数,判断是否需要变异
'''
return True if rate > r.random() else False
class Triangle(object):
'''
定义三角形的类
属性:
ax,ay,bx,by,cx,cy:表示每个三角形三个顶点的坐标
color : 表示三角形的颜色
img_t : 三角形绘制成的图,用于合成图片
方法:
mutate_from(self, parent): 从父代三角形变异
draw_it(self, size=(256, 256)): 绘制三角形
'''
max_mutate_rate = 0.08
mid_mutate_rate = 0.3
min_mutate_rate = 0.8
def __init__(self, size=(255, 255)):
self.ax = r.randint(0, size[0])
self.ay = r.randint(0, size[1])
self.bx = r.randint(0, size[0])
self.by = r.randint(0, size[1])
self.cx = r.randint(0, size[0])
self.cy = r.randint(0, size[1])
self.color = Color()
self.img_t = None
def mutate_from(self, parent):
if mutate_or_not(self.max_mutate_rate):
self.ax = r.randint(0, 255)
self.ay = r.randint(0, 255)
if mutate_or_not(self.mid_mutate_rate):
self.ax = min(max(0, parent.ax + r.randint(-15, 15)), 255)
self.ay = min(max(0, parent.ay + r.randint(-15, 15)), 255)
if mutate_or_not(self.min_mutate_rate):
self.ax = min(max(0, parent.ax + r.randint(-3, 3)), 255)
self.ay = min(max(0, parent.ay + r.randint(-3, 3)), 255)
if mutate_or_not(self.max_mutate_rate):
self.bx = r.randint(0, 255)
self.by = r.randint(0, 255)
if mutate_or_not(self.mid_mutate_rate):
self.bx = min(max(0, parent.bx + r.randint(-15, 15)), 255)
self.by = min(max(0, parent.by + r.randint(-15, 15)), 255)
if mutate_or_not(self.min_mutate_rate):
self.bx = min(max(0, parent.bx + r.randint(-3, 3)), 255)
self.by = min(max(0, parent.by + r.randint(-3, 3)), 255)
if mutate_or_not(self.max_mutate_rate):
self.cx = r.randint(0, 255)
self.cy = r.randint(0, 255)
if mutate_or_not(self.mid_mutate_rate):
self.cx = min(max(0, parent.cx + r.randint(-15, 15)), 255)
self.cy = min(max(0, parent.cy + r.randint(-15, 15)), 255)
if mutate_or_not(self.min_mutate_rate):
self.cx = min(max(0, parent.cx + r.randint(-3, 3)), 255)
self.cy = min(max(0, parent.cy + r.randint(-3, 3)), 255)
# color
if mutate_or_not(self.max_mutate_rate):
self.color.r = r.randint(0, 255)
if mutate_or_not(self.mid_mutate_rate):
self.color.r = min(max(0, parent.color.r + r.randint(-30, 30)), 255)
if mutate_or_not(self.min_mutate_rate):
self.color.r = min(max(0, parent.color.r + r.randint(-10, 10)), 255)
if mutate_or_not(self.max_mutate_rate):
self.color.g = r.randint(0, 255)
if mutate_or_not(self.mid_mutate_rate):
self.color.g = min(max(0, parent.color.g + r.randint(-30, 30)), 255)
if mutate_or_not(self.min_mutate_rate):
self.color.g = min(max(0, parent.color.g + r.randint(-10, 10)), 255)
if mutate_or_not(self.max_mutate_rate):
self.color.b = r.randint(0, 255)
if mutate_or_not(self.mid_mutate_rate):
self.color.b = min(max(0, parent.color.b + r.randint(-30, 30)), 255)
if mutate_or_not(self.min_mutate_rate):
self.color.b = min(max(0, parent.color.b + r.randint(-10, 10)), 255)
# alpha
if mutate_or_not(self.mid_mutate_rate):
self.color.a = r.randint(95, 115)
# if mutate_or_not(self.mid_mutate_rate):
# self.color.a = min(max(0, parent.color.a + r.randint(-30, 30)), 255)
# if mutate_or_not(self.min_mutate_rate):
# self.color.a = min(max(0, parent.color.a + r.randint(-10, 10)), 255)
def draw_it(self, size=(256, 256)):
self.img_t = Image.new('RGBA', size)
draw = ImageDraw.Draw(self.img_t)
draw.polygon([(self.ax, self.ay),
(self.bx, self.by),
(self.cx, self.cy)],
fill=(self.color.r, self.color.g, self.color.b, self.color.a))
return self.img_t
class Canvas(object):
'''
定义每一张图片的类
属性:
mutate_rate : 变异概率
size : 图片大小
target_pixels: 目标图片像素值
方法:
add_triangles(self, num=1) : 在图片类中生成num个三角形
mutate_from_parent(self, parent): 从父代图片对象进行变异
calc_match_rate(self) : 计算环境适应度
draw_it(self, i) : 保存图片
'''
mutate_rate = 0.01
size = (256, 256)
target_pixels = []
def __init__(self):
self.triangles = []
self.match_rate = 0
self.img = None
def add_triangles(self, num=1):
for i in range(0, num):
triangle = Triangle()
self.triangles.append(triangle)
def mutate_from_parent(self, parent):
flag = False
for triangle in parent.triangles:
t = triangle
if mutate_or_not(self.mutate_rate):
flag = True
a = Triangle()
a.mutate_from(t)
self.triangles.append(a)
continue
self.triangles.append(t)
if not flag:
self.triangles.pop()
t = parent.triangles[r.randint(0, len(parent.triangles) - 1)]
a = Triangle()
a.mutate_from(t)
self.triangles.append(a)
def calc_match_rate(self):
if self.match_rate > 0:
return self.match_rate
self.match_rate = 0
self.img = Image.new('RGBA', self.size)
draw = ImageDraw.Draw(self.img)
draw.polygon([(0, 0), (0, 255), (255, 255), (255, 0)], fill=(255, 255, 255, 255))
for triangle in self.triangles:
self.img = Image.alpha_composite(self.img, triangle.img_t or triangle.draw_it(self.size))
# 与下方代码功能相同,此版本便于理解但效率低
# pixels = [self.img.getpixel((x, y)) for x in range(0, self.size[0], 2) for y in range(0, self.size[1], 2)]
# for i in range(0, min(len(pixels), len(self.target_pixels))):
# delta_red = pixels[i][0] - self.target_pixels[i][0]
# delta_green = pixels[i][1] - self.target_pixels[i][1]
# delta_blue = pixels[i][2] - self.target_pixels[i][2]
# self.match_rate += delta_red * delta_red + \
# delta_green * delta_green + \
# delta_blue * delta_blue
arrs = [np.array(x) for x in list(self.img.split())] # 分解为RGBA四通道
for i in range(3): # 对RGB通道三个矩阵分别与目标图片相应通道作差取平方加和评估相似度
self.match_rate += np.sum(np.square(arrs[i]-self.target_pixels[i]))[0]
def draw_it(self, i):
#self.img.save(os.path.join(PATH, "%s_%d_%d_%d.png" % (PREFIX, len(self.triangles), i, self.match_rate)))
self.img.save(os.path.join(PATH, "%d.png" % (i)))
def main():
global LOOP, PREFIX, PATH, TARGET, TRIANGLE_NUM
# 声明全局变量
img = Image.open(TARGET).resize((256, 256)).convert('RGBA')
size = (256, 256)
Canvas.target_pixels = [np.array(x) for x in list(img.split())]
# 生成一系列的图片作为父本,选择其中最好的一个进行遗传
parentList = []
for i in range(20):
print('正在生成第%d个初代个体' % (i))
parentList.append(Canvas())
parentList[i].add_triangles(TRIANGLE_NUM)
parentList[i].calc_match_rate()
parent = sorted(parentList, key=lambda x: x.match_rate)[0]
del parentList
gc.collect()
# 进入遗传算法的循环
i = 0
while i < 30000:
childList = []
# 每一代从父代中变异出10个个体
for j in range(10):
childList.append(Canvas())
childList[j].mutate_from_parent(parent)
childList[j].calc_match_rate()
child = sorted(childList, key=lambda x: x.match_rate)[0]
# 选择其中适应度最好的一个个体
del childList
gc.collect()
parent.calc_match_rate()
if i % LOOP == 0:
print ('%10d parent rate %11d \t child1 rate %11d' % (i, parent.match_rate, child.match_rate))
parent = parent if parent.match_rate < child.match_rate else child
# 如果子代比父代更适应环境,那么子代成为新的父代
# 否则保持原样
child = None
if i % LOOP == 0:
# 每隔LOOP代保存一次图片
parent.draw_it(i)
#print(parent.match_rate)
#print ('%10d parent rate %11d \t child1 rate %11d' % (i, parent.match_rate, child.match_rate))
i += 1
'''
定义全局变量,获取待处理的图片名
'''
NAME = input('请输入原图片文件名:')
LOOP = 100
PREFIX = NAME.split('/')[-1].split('.')[0] # 取文件名
PATH = os.path.abspath('.') # 取当前路径
PATH = os.path.join(PATH,'results')
TARGET = NAME # 源图片文件名
TRIANGLE_NUM = 256 # 三角形个数
if __name__ == '__main__':
#print('开始进行遗传算法')
main()