这个遗传算法的问题是什么?

问题描述

我最近开始学习人工智能,并观看了 Code Bullet 的 this 视频,该视频展示了一个简单游戏的非常简单的遗传算法,其中点需要达到一个目标。我想使用 pygame 在 python 中重新创建这个游戏。因为它根本不起作用,我尝试重新设计它。 代码如下:

from DotGame.DotGameFunctions import *
import random as r
import time

pg.init()
WIN_WIDTH = 500
WIN_HEIGHT = 500

win = pg.display.set_mode((WIN_WIDTH,WIN_HEIGHT))
pg.display.set_caption('DotGame')

SPAWN = Vector2(WIN_WIDTH / 2,WIN_HEIGHT - 40)
GOAL = Vector2(WIN_WIDTH / 2,10)


class Dot:
    def __init__(self,pos: Vector2):
        self.pos = pos
        self.dead = False
        self.reachedGoal = False
        self.is_best = False
        self.fitness = 0

        self.brain = Brain(1000)
        self.vel = Vector2()

    def move(self):
        if self.brain.step < len(self.brain.directions):
            self.vel = self.brain.directions[self.brain.step]
            self.brain.step += 1
        else:
            self.dead = True

        self.pos = self.pos + self.vel

    def update(self):
        if not self.dead and not self.reachedGoal:
            self.move()
            if self.pos.x < 5 or self.pos.y < 5 or self.pos.x > WIN_WIDTH - 5 or self.pos.y > WIN_HEIGHT - 5:
                self.dead = True
            elif dis(self.pos.x,self.pos.y,GOAL.x,GOAL.y) < 5:
                self.reachedGoal = True

        if self.is_best:
            color = (0,255,0)
            width = 7
        else:
            color = (0,0)
            width = 5
        if self.fitness > .005:
            color = (0,255)
            width = 7
        pg.draw.circle(win,color,tuple(self.pos),width)

    def calculate_fitness(self):
        if self.reachedGoal:
            self.fitness = 1 / 16 + 10000 / self.brain.step
        else:
            distance_to_goal = dis(self.pos.x,GOAL.y)
            self.fitness = 1 / distance_to_goal

    def make_baby(self):
        baby = Dot(SPAWN)
        baby.brain = self.brain.clone()
        return baby


class Goal:
    def __init__(self):
        self.pos = GOAL

    def update(self):
        pg.draw.circle(win,(255,0),10)


class Brain:
    def __init__(self,size):
        self.size = size
        self.step = 0
        self.directions = []
        self.randomize()
        self.mutationRate = 1

    def randomize(self):
        self.directions = []
        for i in range(self.size):
            self.directions.append(Vector2.from_angle(r.random() * 2 * math.pi) * 4)

    def clone(self):
        clone = Brain(len(self.directions))
        clone.directions = self.directions
        return clone

    def mutate(self):
        for i in range(len(self.directions) - 1):
            rand = r.random()
            if rand < self.mutationRate:
                self.directions[i] = Vector2.from_angle(r.random() * 2 * math.pi) * 4


class Population:
    def __init__(self,size):
        self.size = size
        self.Dots = []
        for i in range(size):
            self.Dots.append(Dot(SPAWN))

        self.minStep = 20
        self.gen = 0
        self.bestDot = 0
        self.fit_sum = 0

    def update(self):
        for d in self.Dots:
            d.update()

    def fitness(self):
        for d in self.Dots:
            d.calculate_fitness()

    def end_of_generation(self):
        for d in self.Dots:
            if not d.dead and not d.reachedGoal:
                return False

        return True

    def natural_selection(self):
        self.fitness()

        new_dots = sorted(self.Dots,key=lambda x: x.fitness)
        new_dots.reverse()
        new_dots = new_dots[:len(new_dots)//2] * 2
        print([i.fitness for i in new_dots])
        self.Dots = [d.make_baby() for d in new_dots]

        self.Dots[0].is_best = True
        for d in range(len(self.Dots) // 2,len(self.Dots)):
            self.Dots[d].brain.mutate()


test = Population(100)
goal = Goal()

run = True
while run:
    win.fill('white')
    goal.update()

    if test.end_of_generation():
        test.natural_selection()
        time.sleep(1)
    else:
        test.update()

    # for event in pg.event.get():
    #     if event.type == pg.QUIT:
    #         run = False

    pg.display.update()
    time.sleep(0.005)

pg.quit()

这段代码的重要部分是population类中的natural_selection()函数

def natural_selection(self):
        self.fitness()

        new_dots = sorted(self.Dots,len(self.Dots)):
            self.Dots[d].brain.mutate()

它做(或应该做)的是计算点的适应度,按适应度从高到低对点列表进行排序,将列表切成两半并复制它,所以前半部分和后半部分是相同,然后将第一个点设置为最佳点并改变后半部分。 问题在于,正如第 6 行中的打印所示,它并没有真正改变点,结果是在每一代中它只是采用相同的重复列表并对其进行排序,并且适应度如下所示:

[0.003441755148372998,0.0034291486755453414,0.003070574887525978,0.0030339596318951327,0.003030855079397534,...]
[0.00481387362410465,0.00481387362410465,0.003468488512721536,0.0032419920180191478,...]
[0.004356736047656708,0.004356736047656708,0.003056862712974015,...]

我检查了 Brain.mutate 函数,它似乎工作得很好。

我想不出任何可能导致此问题的原因,因为一切似乎都正常。 我希望我足够清楚。

编辑:我导入了一个名为 DotGame.DotGameFunctions 的文件,但忘记显示它:

import math


class Vector2:
    def __init__(self,x=0.0,y=0.0):
        self.x = x
        self.y = y

    def __add__(self,other):
        return Vector2(self.x + other.x,self.y + other.y)

    def __mul__(self,other: int):
        return Vector2(self.x * other,self.y * other)

    def __iadd__(self,other):
        self.x += other.x
        self.y += other.y

    def __iter__(self):
        yield self.x
        yield self.y

    def copy(self):
        return Vector2(self.x,self.y)

    @staticmethod
    def from_angle(angle):
        return Vector2(math.cos(angle),math.sin(angle))


def dis(x1,y1,x2,y2):
    return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)

解决方法

我已经重写了您的 natural_selection 方法,使其更加结构化(至少对我而言),并且它似乎适用于我的简单测试用例。你能试试这对你有用吗?我添加了一些注释来解释我的步骤。

    def natural_selection(self):

        # Calculate fitness
        self.fitness()

        # Print initial generation
        print(pop.Dots)

        # Sort dots by fitness
        new_dots = sorted(self.Dots,key=lambda x: x.fitness)
        new_dots.reverse()

        # Save first half of best individuals
        best_half = new_dots[:len(new_dots)//2]

        # Get offspring dots from best half 
        offspring = [d.make_baby() for d in best_half]

        # copy best half and mutate all dots
        mutated_offspring = [d.make_baby() for d in best_half]
        for d in mutated_offspring:
            d.brain.mutate()

        # Join original best half and mutated best half to get new dots
        new_dots = offspring + mutated_offspring

        # Set first new dot as best dot
        new_dots[0].is_best = True

        # Set new_dots as self.Dots
        self.Dots = new_dots

        # Print new generation
        self.fitness()
        print(pop.Dots)

完整的测试用例:

import random


# Brains only consist of some random number.
# Mutations just add one to that number.
class Brain:
    def __init__(self):
        self.num = self.randomize()

    def clone(self):
        clone = Brain()
        clone.num = self.num
        return clone

    def randomize(self):
        return random.randint(1000,9000)

    def mutate(self):
        self.num += 1

# Dots consist of a brain an a fitness.
# Fitness is simply the brain's number.
class Dot:

    def __init__(self):
        self.brain = Brain()
        self.fitness = None

    def __repr__(self):
        return str(self.fitness)

    def make_baby(self):
        baby = Dot()
        baby.brain = self.brain.clone()
        return baby


class Population:

    def __init__(self):
        self.Dots = [Dot() for i in range(10)]

    def fitness(self):
        for d in self.Dots:
            d.fitness = d.brain.num

    def natural_selection(self):

        # Calculate fitness
        self.fitness()

        # Print initial generation
        print(pop.Dots)

        # Sort dots by fitness
        new_dots = sorted(self.Dots,key=lambda x: x.fitness)
        new_dots.reverse()

        # Save first half of best individuals
        best_half = new_dots[:len(new_dots)//2]

        # Get offspring dots from best half 
        offspring = [d.make_baby() for d in best_half]

        # copy best half and mutate all dots
        mutated_offspring = [d.make_baby() for d in best_half]
        for d in mutated_offspring:
            d.brain.mutate()

        # Join original best half and mutated best half to get new dots
        new_dots = offspring + mutated_offspring

        # Set first new dot as best dot
        new_dots[0].is_best = True

        # Set new_dots as self.Dots
        self.Dots = new_dots

        # Print new generation
        self.fitness()
        print(pop.Dots)



random.seed(1)
pop = Population()
pop.natural_selection()

返回:

[2100,5662,7942,7572,7256,1516,3089,1965,5058,7233]
[7942,7233,7943,7573,7257,7234,5663]

所以原始种群适应度打印在第一行。在第二行中,显示了变异的种群适应度。我的突变只是在点脑值上加 1,所以后半部分是前半部分的副本,每个值加 1。