智能进化(Evolving Intelligence)
构造树状程序
from random import random, randint, choice
from copy import deepcopy
from math import log
class fwrapper:
def __init__(self, function, childcount, name):
self.function = function
self.childcount = childcount
self.name = name
class node:
def __init__(self, fw, children):
self.function = fw.function
self.name = fw.name
self.children = children
def evaluate(self, inp):
results = [n.evaluate(inp) for n in self.children]
return self.function(results)
def display(self, indent=0):
print((' ' * indent) + self.name)
for c in self.children:
c.display(indent + 1)
class paramnode:
def __init__(self, idx):
self.idx = idx
def evaluate(self, inp):
return inp[self.idx]
def display(self, indent=0):
print('%sp%d' % (' ' * indent, self.idx))
class constnode:
def __init__(self, v):
self.v = v
def evaluate(self, inp):
return self.v
def display(self, indent=0):
print('%s%d' % (' ' * indent, self.v))
addw = fwrapper(lambda l: l[0] + l[1], 2, 'add')
subw = fwrapper(lambda l: l[0] - l[1], 2, 'subtract')
mulw = fwrapper(lambda l: l[0] * l[1], 2, 'multiply')
def iffunc(l):
if l[0] > 0:
return l[1]
else:
return l[2]
ifw = fwrapper(iffunc, 3, 'if')
def isgreater(l):
if l[0] > l[1]:
return 1
else:
return 0
gtw = fwrapper(isgreater, 2, 'isgreater')
flist = [addw, mulw, ifw, gtw, subw]
def exampletree():
return node(ifw, [node(gtw, [paramnode(0), constnode(3)]),
node(addw, [paramnode(1), constnode(5)]),
node(subw, [paramnode(1), constnode(2)]),
])
exampletree=exampletree()
print(exampletree.evaluate([2,3]))
print(exampletree.evaluate([5,3]))
exampletree.display()
result :
创建一个随机程序
创建一个随机程序的步骤包括:创建根结点并为其随机指定一个关联函数,然后再随机创建尽可能多的子节点;相应地,这些子节点也可能会有它们自己的随机关联子节点。
其他函数:三角函数、数学函数(乘方、平方根、绝对值等)、统计分布(高斯分布等)、距离函数(欧几里德距离、Tanimoto距离)、一个包含3个参数的函数,如果第一个参数介于第二个和第三个之间,则返回1、一个包含3个参数的函数,如果前两个参数的差小于第三个,则返回1……
具体问题具体分析……
def makerandomtree(pc, maxdepth=4, fpr=0.5, ppr=0.6):
if random() < fpr and maxdepth > 0:
f = choice(flist)
children = [makerandomtree(pc, maxdepth - 1, fpr, ppr)
for i in range(f.childcount)]
return node(f, children)
elif random() < ppr:
return paramnode(randint(0, pc - 1))
else:
return constnode(randint(0, 10))
增加maxdepth约束条件是防止树的分支不断增长,导致堆栈溢出
random1 = makerandomtree(2)
random1.display()
print('::::::::::::::::::::::::::::::::::::::::::::::::::::::::::')
print(random1.evaluate([7, 1]))
print(random1.evaluate([2, 4]))
print('::::::::::::::::::::::::::::::::::::::::::::::::::::::::::')
random2 = makerandomtree(2)
random2.display()
print('::::::::::::::::::::::::::::::::::::::::::::::::::::::::::')
print(random1.evaluate([5, 3]))
print(random1.evaluate([5, 20]))
result :
测试题解
def buildhiddenset():
rows = []
for i in range(200):
x = randint(0, 40)
y = randint(0, 40)
rows.append([x, y, hiddenfunction(x, y)])
return rows
hiddenset = buildhiddenset()
print(hiddenset)
hiddenset :
def scorefunction(tree, s):
dif = 0
for data in s:
v = tree.evaluate([data[0], data[1]])
dif += abs(v - data[2])
return dif
print(scorefunction(random2, hiddenset))
print(scorefunction(random1, hiddenset))
对程序进行变异
def mutate(t, pc, probchange=0.1):
if random() < probchange:
return makerandomtree(pc)
else:
result = deepcopy(t)
if isinstance(t, node):
result.children = [mutate(c, pc, probchange) for c in t.children]
return result
random2.display()
print('mutate mutate mutate mutate mutate mutate mutate mutate')
muttree = mutate(random2, 2)
muttree.display()
print(scorefunction(random2, hiddenset))
print(scorefunction(muttree, hiddenset))
本次实验结果有所改善 ,但是变异是随机进行的,而且不必非得朝着有利于改善题解的方向进行。
交叉
除了变异外,另一种修改程序的方法称为交叉或者配对。其通常做法是:从众多程序中选出两个表现优异者,并将其组合在一起构造出一个新的程序,例如用一棵树的分支取代另一棵树的分支。
def crossover(t1, t2, probswap=0.7, top=1):
if random() < probswap and not top:
return deepcopy(t2)
else:
result = deepcopy(t1)
if isinstance(t1, node) and isinstance(t2, node):
result.children = [crossover(c, choice(t2.children), probswap, 0)
for c in t1.children]
return result
crossover函数以两棵树作为输入,并同时开始向下 遍历。当到达某个随机选定的阈值时,该函数便会返回前一棵树的一份拷贝,树上的某个分支会被后一棵树所取代。通过同时对两棵树的即时遍历,函数会在每棵树上大致位于相同层次的节点处实施交叉操作。
print('::::::::::::::::::::::::::::::::::::::::::::::::::::::::::')
random1.display()
print('::::::::::::::::::::::::::::::::::::::::::::::::::::::::::')
random2.display()
print('::::::::::::::::::::::::::::::::::::::::::::::::::::::::::')
cross=crossover(random1,random2)
cross.display()
result :
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
add
multiply
add
4
multiply
p0
p0
multiply
6
isgreater
6
p1
subtract
if
multiply
p1
p0
multiply
p0
p1
add
6
p0
isgreater
subtract
6
4
subtract
p1
p1
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
subtract
if
p1
subtract
p1
multiply
p0
9
p0
isgreater
p0
3
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
add
if
p1
subtract
p1
multiply
p0
9
p0
if
p1
subtract
p1
multiply
p0
9
p0
将两个程序合并后得到的结果可能会与前两者都截然不同。
构筑环境
思路:生成一组随机程序并择优复制和修改,然后一直重复这一过程直到终止条件满足为止
根据scorefunction得到的结果对population进行排序
def getrankfunction(dataset):
def rankfunction(population):
scores = [(scorefunction(t, dataset), t) for t in population]
scores=sorted(scores, key=itemgetter(0))
for score in scores:
print('分数: ',score)
return scores
return rankfunction
元组排序示例:
L = [( 75,'Bob'), ( 92,'Adam'), ( 66,'Bart'), (88, 'Lisa')]
print(sorted(L, key=itemgetter(0)))
print(sorted(L, key=lambda t: t[1]))
print(sorted(L, key=itemgetter(1), reverse=True))
rankfunction | 将一组程序按从优到劣的顺序进行排列的函数 |
mutationrate | 代表了发生变异的概率,该参数会被传递给mutate |
breedingrate | 代表了发生交叉的概率,该参数会被传递给crossover |
popsize | 初始种群大小 |
probexp | 该值越大,只选择评价最高者作为复制对象的概率就越严格 |
probnew | 引入一个全新的随机程序的概率 |
def evolve(pc, popsize, rankfunction, maxgen=500,
mutationrate=0.1, breedingrate=0.4, pexp=0.7, pnew=0.05):
# 返回一个随机数,通常是一个较小的数
# pexp的取值越小,得到的随机数就越小
def selectindex():
return int(log(random()) / log(pexp)) #………………………………………………………………
# 创建一个随机的初始种群
population = [makerandomtree(pc) for i in range(popsize)]
for i in range(maxgen):
scores = rankfunction(population)
print('第%d次%d' % (i, scores[0][0]))
if scores[0][0] == 0: break
# 总能得到两个最优的程序
newpop = [scores[0][1], scores[1][1]]
# 构造下一代
while len(newpop) < popsize:
if random() > pnew:
newpop.append(mutate(crossover(scores[selectindex()][1], #……………………
scores[selectindex()][1], #……………………
probswap=breedingrate),
pc, probchange=mutationrate))
else:
# 加入一个随机节点,以增加种群的多样性
newpop.append(makerandomtree(pc))
population = newpop
print('the best:***********************')
scores[0][1].display()
return scores[0][1]
evolve函数首先创造一个随机种群。然后循环至多maxgen次,每次循环都会调用rankfunction对程序按表现从优到劣的顺序进行排序。表现最优者会不加修改地自动进入到下一代(精英选拔法elitism)。至于下一代的其他程序,则是通过随机选择排名靠前者,再经交叉和变异之后得到的。这一过程会一直重复下去,直到某个程序达到了完美的0分值,或者重复次数达到了maxgen次为止。
rf = getrankfunction(buildhiddenset())
print(evolve(2, 500, rf, mutationrate=0.2, breedingrate=0.1, pexp=0.7, pnew=0.1))
result :
random1: 123122
muttree1: 123122
random2: 125322
muttree2: 125997
corss: 123122
第0次20555
第1次6569
第2次5582
第3次4157
第4次2681
第5次2681
第6次2679
第7次2556
第8次2556
第9次844
第10次844
......
第36次199
第37次199
第38次199
第39次199
第40次199
第41次199
第42次0
the best:***********************
add
if
5
add
5
if
2
add
p1
p1
if
p0
8
p0
add
4
0
multiply
add
if
subtract
7
if
p0
isgreater
p1
if
3
isgreater
2
p1
p0
subtract
p1
p0
0
p1
add
3
p0
p0
这里给出的解是完全正确的,但是它显然比此前构造数据集时所用的函数复杂的多,但它们是等价的。仅仅选择表现优异的少数题解很快就会使种群变得极端同质化,这些题解间进行的交叉操作最终会导致种群内的题解变得越来越相似,即局部最大化。将变现极为优异的题解和大量成绩尚可的题解组合在一起,往往能够得到更好的结果。要让程序保持简单是可以的,但是多数情况下这样做会增加寻找最优解的难度。解决这一问题的一种更好的方法是:允许程序不断进化以形成优解,然后再删除并简化树中不必要的部分,可以手工完成或借助剪枝算法。
Grid War
为游戏引入人工智能,通过彼此竞争以及真人对抗,为表现优异的程序提供更多的进入下一代的机会,可以让程序不断地得到进化。
游戏规则:
只要将自己移至对方所在的区域便赢,但不可在同一个方向移动两次
# 0:一号玩家,1:2号玩家
def gridgame(p):
# 游戏区域的大小
max = (3, 3)
# 记住每位玩家的上一步
lastmove = [-1, -1]
# 记住玩家的位置
location = [[randint(0, max[0]), randint(0, max[1])]]
# 将第二位玩家放在第一位玩家足够远的地方
# 0000000000000000000000000000000000000000000000000000000000000000000
location.append([(location[0][0] + 2) % 4, (location[0][1] + 2) % 4])
# 打成平局前的最大移动步数为50:
for o in range(50):
# 针对每位玩家
for i in range(2):
locs = location[i][:] + location[1 - i][:]
locs.append(lastmove[i])
#print('locs: ', locs)
move = p[i].evaluate(locs) % 4
# 如果在一行中朝着同一个方向移动了两次,就判定为你输
if lastmove[i] == move: return 1 - i
lastmove[i] = move
# 0:上,1:下,2:左,3:右
if move == 0:
location[i][0] -= 1
# 限制游戏区域
if location[i][0] < 0: location[i][0] = 0
if move == 1:
location[i][0] += 1
if location[i][0] > max[0]: location[i][0] = max[0]
if move == 2:
location[i][1] -= 1
if location[i][1] < 0: location[i][1] = 0
if move == 3:
location[i][1] += 1
if location[i][1] > max[1]: location[i][1] = max[1]
# 如果抓住了对方玩家,就判定为你赢
if location[i] == location[1 - i]: return i
return -1
p1 = makerandomtree(5)
p1.display()
print('@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@')
p2 = makerandomtree(5)
p2.display()
print(gridgame([p1, p2]))
以上程序是完全没有经过进化的,它们可能会因为在一行上朝同一方向移动两次而输掉游戏。
def tournament(pl):
# 统计失败的次数
losses = [0 for p in pl]
# 每位玩家都将和其他玩家一一对抗
for i in range(len(pl)):
for j in range(len(pl)):
if i == j: continue
# 谁是胜利者
winner = gridgame([pl[i], pl[j]])
# 失败得2分,打平得1分
if winner == 0:
losses[j] += 2
elif winner == 1:
losses[i] += 2
elif winner == -1:
losses[i] += 1
losses[i] += 1
pass
# 对结果排序并返回
z = zip(losses, pl)
z=sorted(z,key=itemgetter(0))
print('z: ',sorted(z,key=itemgetter(0)))
#z.sort()
return z
result :
tournament函数让程序在一场比赛中彼此展开竞争,借此得以进化。
此时的结果并没有像之前数学函数中那样严格递减,因为那有一个标准答案,grid war 每次的棋盘都是变化的,没有唯一答案。
class humanplayer:
def evaluate(self, board):
# 得到自己的位置和其他玩家的位置
me = tuple(board[0:2])
others = [tuple(board[x:x + 2]) for x in range(2, len(board) - 1, 2)]
# 显示游戏区域
for i in range(4):
for j in range(4):
if (i, j) == me:
print('O', end=' ')
elif (i, j) in others:
print('X', end=' ')
else:
print('-', end=' ')
print()
# 显示上一步,作为参考
print('Your last move was %d' % board[len(board) - 1])
print(' 0')
print('2 3')
print(' 1')
print('Enter move')
# 不论用户输入什么内容,均直接返回
move = int(input())
return move
gridgame([winner, humanplayer()])
I win (●'◡'●) (●'◡'●) (●'◡'●) (●'◡'●) (●'◡'●) (●'◡'●) (●'◡'●) (●'◡'●) (●'◡'●) (●'◡'●) (●'◡'●) (●'◡'●) (●'◡'●) (●'◡'●) (●'◡'●) (●'◡'●)
winner等几乎肯定会掌握‘不能在一行里朝同一方向移动两次’的策略,因为那样会很快导致死亡,但是它掌握其他对抗策略的程度则要视每一次进化情况的不同而定。
补充
记忆力
不同类型