Как я могу заставить свою ценность для поколений работать на мой генетический алгоритм (Python)

Я создал генетический алгоритм, чтобы решить проблему просмотра шоу, в каком порядке максимизировать прибыль, используя код, вдохновленный Kie Codes, следуя коду в этом видео https://www.youtube.com/watch?v=nhT56blfRpE

К сожалению, я не могу заставить часть поколений работать, и это приводит к тому, что в результате получается 0 поколений, хотя он создал решение, которое каждый раз отличается в прогоне. Переменные, которые у меня есть, — это имя, физическая форма и вес, которые переводятся в название шоу, его рейтинг и количество серий. Что касается генерации, то часть кода не будет работать, она также не будет учитывать время, необходимое программе для создания оптимального решения.

from random import choices, randint, randrange, random #allows for a random creations to be made
from typing import List, Callable, Tuple #allows to use list functions and callable
from collections import namedtuple #allows the use for named tuple
import time #allows for time to be used
from functools import partial #allows for partial to be used

Genome=List[int]
Population=List[Genome]
FitnessFunc=Callable[[Genome],int] #puts the fitness with the genome
PopulateFunc=Callable[[], Population]#gives out new solutions
SelectionFunc=Callable[[Population, FitnessFunc], Tuple[Genome,Genome]] #takes the population and fitness to make select for a new solution
CrossoverFunc=Callable[[Genome, Genome], Tuple[Genome,Genome]]#takes two genomes and produces two genomes
MutationFunc=Callable[[Genome], Genome]#takes a genome and sometimes produces a new genome
PrinterFunc=Callable[[Population, int, FitnessFunc], None]
#puts the functions into perameters 
Thing=namedtuple('Thing',['name','value','weight'])#gives a structure for the list below

things=[
    Thing('Seishun Buta Yarou wa Bunny Girl Senpai no Yume wo Minai (TV)',8.38,13),
    Thing('Bakemonogatari',8.36,15),
    Thing('Kodomo no Jikan (TV)',6.82,12),
    Thing('Tsuki ga Kirei',8.18,12),
    Thing('Tokyo Ravens',7.53,24),
    Thing('Kono Subarashii Sekai ni Shukufuku wo!',8.15,10),
    Thing('Shingeki no Kyojin S4',9.2,12),
    Thing('Dr Stone S2',8.33,11),
    Thing('The Promised Neverland',8.11,11),
    Thing('Re:Zero',8.7,12),
    Thing('Toradora!',8.24,25),
    Thing('Sousei no Onmyouji',7.33,50),
    Thing('Rurouni Kenshin: Meiji Kenkaku Romantan',8.31,94),
    Thing('Fullmetal Alchemist: Brotherhood',9.2,64),
    Thing('Steins;Gate',9.11,24),
    Thing('Boku no Pico',4.31,3)
]

def generate_genome(length: int) -> Genome:
    return choices([0,1], k=length)
    #creates a random genome 
##genome

def generate_population(size: int, genome_length: int) -> Population:
    return [generate_genome(genome_length)for _ in range(size)]
    #generates a list of genomes
##population

def fitness(genome: Genome, things: things, weight_limit: int) -> int:
    if len(genome)!=len(things):#checks if the two are the same length
        raise ValueError("genome and things must be of the same length")

    weight=0
    value=0

    for i, thing in enumerate (things):
        if genome[i]==1:
            weight+=thing.weight
            value+=thing.value #adds the weight values and fitness values
            
            if weight>weight_limit: #checks if the genome is under the weight limit
                return 0
    return value
#fitness function

def selection_pair(population: Population, fitness_func: FitnessFunc) -> Population:
    return choices(
        population=population,
        weights=[fitness_func(genome) for genome in population],
        k=2 #draw twice (two parents)
    )
#selection function

def single_point_crossover(a: Genome,b: Genome) -> Tuple[Genome,Genome]:
    if len(a)!=len(b):
        raise ValueError("Genomes a and b must be of same length")
    #checks the size of the genomes that was selected
    #the genomes have to be the same in length or it will not work

    length=len(a)
    if length<2:#checks the length as they would have to be at least 2 or else you are unable to cut them in half
        return a,b

    p=randint(1, length-1)#randomly takes parts of the two genomes
    return a[0:p]+b[p:],b[0:p]+a[p:]#and then creates them
#crossover function

def mutation(genome: Genome, num: int=1, probability: float=0.5) -> Genome:
    for _ in range(num):
        index=randrange(len(genome))#if the number made is higher than the probability it is left alone
        genome[index]=genome[index] if random() > probability else abs(genome[index]-1)#makes the absolute value so that it is either 1 or 0
        #people hatee this one but why ^^
    return genome
#mutation function

def run_evolution(
    populate_func: PopulateFunc,
    fitness_func: FitnessFunc,#for these two it populates with variables
    fitness_limit: int,#if the fitness limit is reached then the program is done
    selection_func: SelectionFunc=selection_pair,
    crossover_func: CrossoverFunc=single_point_crossover,
    mutation_func: MutationFunc=mutation,# initialises the above three
    generation_limit: int=100 #limits on how much is made
) -> Tuple[Population, int]:
    population=populate_func()#populates with random genomes

    for generations in range(generation_limit):
        population=sorted(
            population, 
            key=lambda genome: fitness_func(genome),
            reverse=True
            #sorts the fitness of the genomes so that the higher number is at the start
        )
        if fitness_func(population[0])>=fitness_limit:
            break
        #if the fitness limit is reached then the program ends (breaks)

        next_generation=population[0:2]#gets the top two genomes for the next generation

        for j in range(int(len(population)/2)-1):
            parents=selection_func(population, fitness_func) #gets the top two to be made as parents
            offspring_a, offspring_b=crossover_func(parents[0], parents[1]) #puts the genomes into seprate variables
            offspring_a=mutation_func(offspring_a)
            offspring_b=mutation_func(offspring_b)#creates a mutation for both genomes randomly
            next_generation+=[offspring_a, offspring_b]
            #creates the next genomes and puts them into population
        population=next_generation

    population=sorted(
         population, 
         key=lambda genome: fitness_func(genome),
        reverse=True
       #sorts the fitness of the genomes so that the higher number is at the start
    )

    return population, generations
#evolutionary main loop

def genome_to_things(genome: Genome, things: things) -> things:
    result=[]
    for i, thing in enumerate(things):
        if genome[i]==1:
            result+=[thing.name]

    return result 
#collects the best solutions so that it may be printied out for later  


start=time.time()
population, generations=run_evolution(
    populate_func=partial(
        generate_population, size=10, genome_length=len(things)
    ),
    fitness_func=partial(
        fitness, things=things, weight_limit=94
    ),
    fitness_limit=10,
    generation_limit=100
)
end=time.time()
#runs the program

print(f"number of generations: {generations}")
print(f"time: {end-start}s")
print(f"best solution: {genome_to_things(population[0], things)}")
#prints results

person Kyaki    schedule 01.03.2021    source источник


Ответы (1)


Это означает, что ваш ГА находит решение, удовлетворяющее вашему пределу пригодности, непосредственно в первом начальном поколении 0.

Если я запускаю ваш код с помощью fitness_limit=100, я получаю следующий вывод.

number of generations: 99
time: 0.014637947082519531s
best solution: ['Seishun Buta Yarou wa Bunny Girl Senpai no Yume wo Minai (TV)', 'Bakemonogatari', 'Tsuki ga Kirei', 'Kono Subarashii Sekai ni Shukufuku wo!', 'Shingeki no Kyojin S4', 'Dr Stone S2', 'Re:Zero', 'Boku no Pico']
person Kie    schedule 17.05.2021