1. 程式人生 > >機器學習第十一篇——智慧進化

機器學習第十一篇——智慧進化

智慧進化(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等幾乎肯定會掌握‘不能在一行裡朝同一方向移動兩次’的策略,因為那樣會很快導致死亡,但是它掌握其他對抗策略的程度則要視每一次進化情況的不同而定。

補充

記憶力

不同型別