Auswahl der AusrĂŒstung fĂŒr ein persisches Spiel unter Verwendung von Genetik / Evolution in Python

Wie wĂ€hle ich die beste AusrĂŒstung in deinem Lieblingsspiel aus? NatĂŒrlich können Sie alle möglichen Kombinationen (zum Beispiel fĂŒr den RĂ€uber von World of Warcraft) banal sortieren und die besten finden. Ohne Magie oder maschinelles Lernen. Aber ist es möglich, dieses Ergebnis nicht „frontal“, sondern mit Hilfe genetischer Algorithmen zu erzielen, ohne jede Kombination auszuprobieren? Es ist interessant zu wissen, wie RĂ€uber brĂŒten und sich entwickeln? Gehen.

Bild

Dieser Artikel ist eine Vertiefung des vorherigen Themas , in dem bereits die Details der Implementierung des Charakters und die Auswirkungen der AusrĂŒstung beschrieben werden.

Zweck: Interessenten zeigen, wie sie selbst eine virtuelle Population von Spielagenten erstellen, zusammenschieben, einen einfachen genetischen Algorithmus (mit Mutationen) implementieren und all dies mithilfe von HTML, CSS und Javascript visualisieren können.

Was Sie brauchen:

1. Python 3 + installieren Sie das matplotlib- Modul ; IDE (ich habe PyCharm);
2. Wenn Sie eine interaktive Berichtsvorlage bearbeiten möchten, mĂŒssen Sie ĂŒber grundlegende Kenntnisse in HTML, CSS und JavaScript (jQuery) verfĂŒgen.

SCHRITT 1 - Setzen Sie sich ein Ziel, beschÀftigen Sie sich mit OOP


Das ultimative Ziel unserer Codierung ist es also, das beste Outfit mit einer Simulation von Genetik / Evolution / natĂŒrlicher Selektion zu erhalten. Und erhalten Sie die Möglichkeit, diesen Weg visuell zu verfolgen.

Wir verwandeln unser Ziel in spezifische Aufgaben:

  1. den genetischen Mechanismus auf individueller Ebene zu ĂŒberdenken und umzusetzen (die „Gene“ des RĂ€ubers sollten sich direkt auf die Wahl der AusrĂŒstung auswirken)
  2. Implementieren Sie die Mechanismen der Geburt von Nachkommen, die Übertragung von Genen auf sie sowie zufĂ€llige geringfĂŒgige Mutationen, um die VariabilitĂ€t und die Möglichkeit sicherzustellen, die besten zu finden
  3. die genetische "Konkurrenz" zu realisieren, die Auswahl von Genen (um ihre TrĂ€ger zusammenzuschieben, so dass das Ergebnis der Kollision die "schlechten" Gene stĂŒrzt und die "guten" erhöht)
  4. Bauen Sie alles zu einem kohÀrenten System zusammen, in dem sich RÀuber zu idealen KÀmpfern entwickeln können
  5. Daten zu sammeln und zu visualisieren, damit es etwas zu bewundern gibt (sonst ist es interessant!)
  6. bewerten den Nutzen dieser AktivitÀten

Hier wird uns OOP helfen, wir werden 4 grundlegende Klassen erstellen:

Bild

SCHRITT 2 - SchĂ€rfen der Rogue-Klasse fĂŒr die Zucht


vereinbaren Sie Bedingungen am Ufer


In diesem Artikel werde ich mich von den Begriffen "Gen" (im Code - Gen ) und "Genotyp" (im Code - Genotyp ) trennen . Es wird auch „Mutationen“ (im Code - sein mutieren , Mutation ), eine Änderung in einem oder mehreren Genen ihrer Bedeutung impliziert. Die Fortpflanzung erfolgt einfach durch „Knospen“ des Nachkommen des Elternteils, sodass es nicht zu Überkreuzungen und Ă€hnlichen Komplikationen kommt. Der Genotyp des RĂ€ubers ist eine Liste von 7 Zahlen , die seine Gene sind:

Bild

Die Rogue-Klasse wÀchst also im Vergleich zum letzten Mal erheblich.

1.Der RĂ€uber empfĂ€ngt die Gene auf eine der möglichen Arten (wurde am ersten Tag erzeugt oder vom „Elternteil“ mit oder ohne Mutation geerbt). Verantwortlich dafĂŒr sind die Methoden generate_random_genes , mutate_genes , mutate_gene :

Methoden zur Bildung von Codegenen
    #     ():
    def generate_random_genes(self):
        dbg = DBG_rogue_generate_random_genes

        self.my_genes[0] = randrange(0, len(RIGHT_HANDS))    # <--   :
        self.my_genes[1] = randrange(0, len(LEFT_HANDS))     # <--   :
        self.my_genes[2] = randrange(0, len(GLOVES))         # <--  :
        self.my_genes[3] = randrange(0, len(HEADS))          # <--  :
        self.my_genes[4] = randrange(0, len(CHESTS))         # <--  :
        self.my_genes[5] = randrange(0, len(PANTS))          # <--  :
        self.my_genes[6] = randrange(0, len(BOOTS))          # <--  :

        if dbg:  #  :
            print('\nf "generate_random_genes":' + '\n\tgenes generated:\n\t', end='')
            print(self.my_genes)


    #      :
    def mutate_genes(self, parent_genes):
        dbg = DBG_rogue_mutate_genes

        #     :
        self.my_genes = parent_genes.copy()

        #    :
        event_mutation = randint(1, 10)

        #     ,    :
        if event_mutation == 10:
            if dbg:  #  :
                print('\nf "mutate_genes"   :' + '\n\t  \n\told genes: ', end='')
                print(parent_genes)
                print('\tnew genes: ', end='')
                print(self.my_genes)
            return 0

        #    :
        else:
            #  ""  =  ,   :
            mutation_volume = randint(0, 30)
            mutation_iters = 1
            if 22 <= mutation_volume <= 28:
                mutation_iters = 2
            elif 29 <= mutation_volume <= 30:
                mutation_iters = 3

            if dbg:  #  :
                print('\nf "mutate_genes" :' + '\n\t : ' + str(mutation_iters))

            #  ,   :
            genes_available = [0, 1, 2, 3, 4, 5, 6]

            #   :
            genes_mutated = []

            current_iter = 0
            while current_iter < mutation_iters:
                if dbg:  #  :
                    print('\tw1')

                #     :
                gene_with_forced_mutation = choice(genes_available)

                #      :
                if gene_with_forced_mutation not in genes_mutated:
                    self.mutate_gene(gene_with_forced_mutation)
                    genes_mutated.append(gene_with_forced_mutation)
                    current_iter += 1
                    if dbg:  #  :
                        print('\tcurrent_iter =', current_iter)
                else:
                    if dbg:  #  :
                        print('\telse, because ' + str(gene_with_forced_mutation) + ' already in genes_mutated')

        if dbg:  #  :
            genes_mutated_str = ''
            if len(genes_mutated) > 1:
                for x in genes_mutated:
                    genes_mutated_str += str(x) + ', '
            else:
                genes_mutated_str = str(genes_mutated[0])
            print('\nf "mutate_genes" :' + '\n\told genes: ', end='')
            print(parent_genes)
            print('\tgenes_mutated: ' + genes_mutated_str)
            print('\tnew genes: ', end='')
            print(self.my_genes)


    #     ,      :
    def mutate_gene(self, gene_id):
        dbg = DBG_rogue_mutate_gene

        current_value = self.my_genes[gene_id]
        new_value = current_value

        if dbg:  #  :
            print('\nf "mutate_gene":' + '\n\tgene_id: ' + str(gene_id) + '\n\told gene value: ' + str(current_value))

        tries = 0
        while new_value == current_value:
            if dbg and tries > 0:  #  :
                print('\tw2, because ' + str(new_value) + ' = ' + str(current_value) )
            new_value = randrange(0, len(LINKS_TO_EQUIP_DICTS[gene_id]))
            self.my_genes[gene_id] = new_value
            tries += 1

        if dbg:  #  :
            print('\tnew gene value: ' + str(new_value) + '\n\ttries: ' + str(tries))


2. die Zahnradverschleiß RĂ€uber (unmittelbar an Genes (Genotyp) bestimmen Geburt Initialisierungsobjekt). Dazu heißt die Methode apply_genes :

apply_genes
    # ""  ()     :
    def apply_genes(self):
        dbg = DBG_rogue_apply_genes

        pointer = 0
        for item_id in self.my_genes:
            self.wear_item(pointer, item_id, LINKS_TO_EQUIP_DICTS[pointer])
            pointer += 1

        if dbg:  #  :
            print('\nf "apply_genes":' + '\n\t.')
            print(self)


3. Das GerÀt bestimmt den Endwert der "Bewertung" des RÀubers. Dazu wird die Methode berechne_rate aufgerufen , die die mathematische Schadenserwartung berechnet:

berechne_rate
    #     :
    def calculate_rate(self):
        dbg = DBG_rogue_calculate_rate

        #   :
        p_hit = self.stat_hit / 100

        #      :
        p_glancing = self.stat_glancing_percent / 100
        not_p_glancing = 1 - self.stat_glancing_percent / 100

        #      :
        p_crit = self.stat_crit / 100
        not_p_crit = 1 - self.stat_crit / 100

        #   :
        expectation_modificator = p_hit * (p_glancing * 0.7 + not_p_glancing * (p_crit * 2 + not_p_crit))

        #      :
        expectation_damage = expectation_modificator * self.stat_attackpower
        expectation_damage = round(expectation_damage, 3)

        if dbg:
            print('\t  =', expectation_modificator)
            print('\t  =', expectation_damage)

        return expectation_damage


4. Die Bewertung des RĂ€ubers ist der entscheidende Faktor, der zur Fortpflanzung oder zum schĂ€ndlichen Tod fĂŒhrt. Hierzu werden die Konzepte „Sieg“ (Methode embody_win ) und „Niederlage“ (Methode embody_defeat ) vorgestellt :

embody_win und embody_defeat
    #    :
    def embody_win(self):
        dbg = DBG_rogue_embody_win

        self.my_wins += 1
        stats.genes_add_win(self.my_genes)

        #    :
        if self.my_wins % population.wins_to_reproduce == 0:

            #    :
            total_borns = choice(population.possible_birth_quantities)
            if dbg:
                print('  ' + str(total_borns))

            for x in range(0, total_borns):
                if dbg:
                    print(self.name + '  ...')

                #  -:
                new_rogue = Rogue(self.my_genes, self.my_generation, from_parent=True)
                ROGUES_LIST.append(new_rogue)

            Population.day_of_last_changes = current_day

        #         :
        if self.my_wins > Population.record_max_wins:
            Population.record_max_wins = self.my_wins
            Population.max_winner_name = self.name
            Population.max_winner_genes = self.my_genes


    #    :
    def embody_defeat(self):
        dbg = DBG_rogue_embody_defeat

        self.my_defeats += 1

        #    :
        if self.my_defeats == population.defeats_to_die:
            self.alive = False
            Population.how_many_rogues_alive -= 1
            Population.day_of_last_changes = current_day

            if dbg:
                print(self.name + ' ...')


Nun, der Klassenkonstruktor wurde entsprechend umgestaltet, damit diese Kette "Gene - AusrĂŒstung - Bewertung" funktioniert:

Rogue-Klassenkonstruktor
    def __init__(self, genes_list_inherited, parent_generation, from_parent=True, genes_can_mutate=True):

        #    ,    id  :
        # 0 -  , 1 -  , 2 - , 3 - , 4 - , 5 - , 6 - 
        self.equipment_slots = [0] * 7

        #    ,      :
        self.equipment_names = [''] * 7

        #    ( -     ):
        self.basic_stat_agility = 50
        self.basic_stat_power = 40
        self.basic_stat_hit = 80
        self.basic_stat_crit = 20
        self.basic_stat_mastery = 0

        #     :
        self.set_stats_without_equip()

        #  :
        Population.how_many_rogues += 1
        Population.how_many_rogues_alive += 1

        #  :
        self.my_generation = parent_generation + 1
        if self.my_generation > Population.generations:
            Population.generations = self.my_generation

        # "" :
        self.name = '"' + str(Population.how_many_rogues) + '-,   ' + str(parent_generation + 1) + '"'

        #  :
        self.alive = True

        #    :
        self.my_wins = 0
        self.my_defeats = 0

        #   :
        self.my_genes = [0] * 7

        if genes_can_mutate:
            #      ,     :
            if from_parent:
                self.mutate_genes(genes_list_inherited)
            else:
                self.generate_random_genes()
        else:
            self.my_genes = genes_list_inherited

        #     :
        stats.genes_add_presence(self.my_genes, self.my_generation)

        #     :
        self.apply_genes()


Um diese Klassenlogik mit einem Bild zusammenzufassen:

Bild

STUFE 3 - Bevölkerung, Statistiken und Herausforderungsklassen


Die Bevölkerungsklasse wird nur fĂŒr drei Dinge benötigt:

  1. Erstellen einer Population aus der angegebenen Anzahl von RĂ€ubern mit zufĂ€lligen Genen und biologischen Parametern, die immer unverĂ€ndert bleiben ( win_to_reproduce - wie viele Siege muss ein RĂ€uber fĂŒr die Reproduktion erzielen , defeats_to_die - wie viele Niederlagen fĂŒhren dazu, dass ein Individuum stirbt);
  2. "ZurĂŒcksetzen" der Population, d.h. Wenn der letzte Tag der aktuellen Phase endet, werden alle RĂ€uber zerstört und RĂ€uber mit den besten Genotypen erstellt (siehe Nachlademethode ).
  3. Speichern einiger Bevölkerungsdaten fĂŒr Statistiken.

Klassenbevölkerung
class Population():
    """     ."""

    how_many_rogues = 0 # <--   
    how_many_rogues_alive = 0 # <--   
    how_many_battles = 0 # <--  
    how_many_ties = 0 # <--  
    generations = 0 # <--  
    day_of_last_changes = 0  # <--       
    max_days_without_changes = 0  # <--    

    #        ,      :
    record_max_wins = 0
    max_winner_name = 'none'
    max_winner_genes = 'none'


    #       :
    def __init__(self, total, possible_birth_quantities, wins_to_reproduce, defeats_to_die):

        #   :
        self.initial_size = total

        #   ,        :
        self.wins_to_reproduce = wins_to_reproduce
        self.defeats_to_die = defeats_to_die
        self.possible_birth_quantities = possible_birth_quantities

        while total > 0:

            #   ,  " "    ,     :
            new_rogue = Rogue('', 0, from_parent=False)

            #   :
            ROGUES_LIST.append(new_rogue)

            total -= 1


    #     :
    def __str__(self):
        text = ':\n'
        text += ': ' + str(Population.generations) + '\n'
        text += ' : ' + str(Population.how_many_rogues) + '\n'
        text += ' : ' + str(Population.how_many_rogues_alive) + '\n'
        text += ' : ' + str(Population.record_max_wins) + '\n'
        text += ' : ' + str(Population.max_winner_name) + '\n'
        text += ' : ' + str(Population.max_winner_genes) + '\n'
        return text


    #  ,    how_many_to_save     :
    def reload(self, how_many_to_save):

        #  - :
        Population.how_many_rogues_alive = 0

        # ""   :
        for x in ROGUES_LIST:
            x.alive = False

        #        :
        list_genotypes_top = stats.get_ordered_list_from_dict(LIST_FOR_DICTS_GENOTYPES[current_stage], inner_index=2)

        #      :
        for x in range(0, how_many_to_save):

            #      :
            genotype_in_str = list_genotypes_top[x][0]
            genotype_in_list = []
            for char in genotype_in_str:
                if char != '-':
                    genotype_in_list.append( int(char) )

            #   ,      ,     :
            new_rogue = Rogue(genotype_in_list, 0, from_parent=True, genes_can_mutate=False)

            #   :
            ROGUES_LIST.append(new_rogue)


Die Stats- Klasse fĂŒhrt zwei Hauptaufgaben aus:

  1. Datenerfassung wĂ€hrend der Simulation (z. B. Indikatoren fĂŒr jeden Tag: Anzahl der lebenden RĂ€uber, Anzahl der eindeutigen Genotypen usw.)
  2. ( matplotlib, HTML-, ).

Mit Blick auf die Zukunft zeige ich, wie dieser Bericht aussehen wird:

Bild

Der HTML-Vorlagencode fĂŒr den Bericht ist auf dem Github verfĂŒgbar .

Die Stats-Klasse ist auch besser auf dem Github zu sehen (andernfalls werden hier zu viele Zeilen angezeigt ).

Die Challenger- Klasse ist fĂŒr die Simulation von Kollisionen zwischen zufĂ€llig ausgewĂ€hlten RĂ€ubern verantwortlich. Jeden Tag heißt die Methode perform_battles , die sich aus lebenden RĂ€uber- Duellpaaren zusammensetzt und sie mit einer Methode perform_battle konfrontiert. Schließlich wird fĂŒr jeden RĂ€uber entweder eine Methode embody_win oder eine Methode embody_defeat verursacht. Übrigens, wenn sich herausstellt, dass ein Unentschieden stattgefunden hat (Genotypen und damit die Bewertungen der RĂ€uber sind gleich), dann weichen sie ohne Konsequenzen voneinander ab:

Klassenherausforderer
class Challenger():
    """      ."""

    #   :
    def perform_battles(self):
        dbg = DBG_challenger_perform_battles

        #    :
        rogues_alive = []
        for x in ROGUES_LIST:
            if x.alive:
                rogues_alive.append(x)

        #  :
        shuffle(rogues_alive)

        #       :
        pairs_total = int(len(rogues_alive) // 2)

        if dbg:
            print('pairs_total =', pairs_total)

        #       :
        counter = 0
        pointer = 0
        while counter < pairs_total:
            a_1 = rogues_alive[pointer]
            a_2 = rogues_alive[pointer + 1]
            self.perform_battle(a_1, a_2)
            counter += 1
            pointer += 2


    #     :
    def perform_battle(self, rogue_1, rogue_2):
        dbg = DBG_challenger_perform_battle

        if dbg:
            print('\n  :', rogue_1.name, '', rogue_2.name)

        #     (        ):
        rating_1 = rogue_1.calculate_rate()
        rating_2 = rogue_2.calculate_rate()

        #  :
        Population.how_many_battles += 1

        if dbg:
            print('\t :', rating_1, '', rating_2, '.')

        #      :
        if rating_1 > rating_2:
            rogue_1.embody_win()
            rogue_2.embody_defeat()
        elif rating_1 < rating_2:
            rogue_1.embody_defeat()
            rogue_2.embody_win()
        else:
            Population.how_many_ties += 1
            if dbg:
                print('\t !  !')


Nun wenden wir uns dem Hauptteil des Programms zu, der alle genannten Teile des Codes miteinander verbindet. Zuerst kommen die Konstanten, die die SchlĂŒsselparameter der Simulation bestimmen: die Anzahl der Stufen, Tage in einer Stufe, die anfĂ€ngliche Anzahl der RĂ€uber in einer Population usw. Dann kommen die Hilfskonstanten zum Debuggen. Und die Herausforderungen selbst:

Code zum AusfĂŒhren der Simulation
# :
MAX_STAGES = 8 # <--      
MAX_DAYS_AT_STAGE = 20 # <--        
SLIDING_FREQUENCY = 10 # <--     HTML-    (1 =   , 10 =   10 )
ROGUES_AT_BEGIN = 10 # <--    (  )
WINS_TO_REPRODUCE = 2 # <--     ,  
DEFEATS_TO_DIE = 2 # <--      
POSSIBLE_BIRTH_QUANTITIES = [1] # <--   ,       , :
# [1, 2] ,   50%-    1,  1 
# [1, 1, 2] ,   66%-   1 

HTML_GSQUARE_SIDE = 10 # <--   ,     
HTML_GSQUARE_MARGIN = 3 # <--   ,     

#     :
LINKS_TO_EQUIP_DICTS = [RIGHT_HANDS, LEFT_HANDS, GLOVES, HEADS, CHESTS, PANTS, BOOTS]

#         (    ):
LIST_FOR_DICTS_GENOTYPES = [{}] * MAX_STAGES

#        :
DICT_UNIQUE_GENOTYPES = {}

#      :
DICT_DAYS = {}

# ,       -  :
ROGUES_LIST = list()

#       (     )
current_stage = 0

#     "" (    /):
DBG_rogue_mutate_genes = False
DBG_rogue_generate_random_genes = False
DBG_rogue_apply_genes = False
DBG_rogue_calculate_rate = False
DBG_rogue_mutate_gene = False
DBG_rogue_embody_win = False
DBG_rogue_embody_defeat = False
DBG_rogue_wear_item = False
DBG_challenger_perform_battles = False
DBG_challenger_perform_battle = False
DBG_days_report = False  # <--     



# :
if __name__ == '__main__':

    #  :
    time_begin = time()

    #        :
    max_days_for_current_stage = 0
    current_day = 1
    while current_stage < MAX_STAGES:

        #    :
        if current_stage == 0:

            #   :
            stats = Stats()

            #          ,      :
            population = Population(ROGUES_AT_BEGIN, POSSIBLE_BIRTH_QUANTITIES, wins_to_reproduce=WINS_TO_REPRODUCE, defeats_to_die=DEFEATS_TO_DIE)

            #     :
            challenger = Challenger()

            # "" :
            print(population)

        #        :
        max_days_for_current_stage += MAX_DAYS_AT_STAGE

        # /      (1  = 1    (*) )
        # * -     -    , -           
        while current_day <= max_days_for_current_stage:
            print('st ' + str(current_stage) + ', day ' + str(current_day))
            if DBG_days_report:
                print('\n\n/DAY', current_day)

            #   :
            challenger.perform_battles()

            if DBG_days_report:
                print('\n', current_day, '.')
                print(population)

            #    :
            stats.add_new_day(current_day)

            #        :
            if current_day - Population.day_of_last_changes > Population.max_days_without_changes:
                Population.max_days_without_changes = current_day - Population.day_of_last_changes

            #    SLIDING_FREQUENCY  (     )     :
            if current_day % SLIDING_FREQUENCY == 0 or current_day == 1 or current_day == MAX_DAYS_AT_STAGE * MAX_STAGES:
                stats.draw_genes_distribution(current_day)

            #    SLIDING_FREQUENCY  (     )     :
            if current_day % SLIDING_FREQUENCY == 0 or current_day == 1 or current_day == MAX_DAYS_AT_STAGE * MAX_STAGES:
                stats.draw_genes_wins(current_day)

            current_day += 1

        #      ,  ,     -  :
        if current_stage < MAX_STAGES - 1:
            population.reload(ROGUES_AT_BEGIN)

        current_stage += 1


    #      ,      LIST_FOR_DICTS_GENOTYPES:
    current_stage -= 1

    #   :
    print('\n:')
    print(DICT_DAYS)

    #       :
    stats.draw_and_put_line_chart_to_file(DICT_DAYS, 1, ' ', '', '', 'charts/chart_population_demography.png')

    #       -  :
    stats.draw_and_put_line_chart_to_file(DICT_DAYS, 0, ' ', '', '', 'charts/chart_population_total.png')

    #      :
    stats.draw_and_put_line_chart_to_file(DICT_DAYS, 2, ' ', '', ' ', 'charts/chart_genotypes_variety.png')

    #      (=   ):
    stats.draw_and_put_line_chart_to_file(DICT_DAYS, 3, ' ', '', '', 'charts/chart_genotypes_ties.png')

    #   HTML   :
    stats.create_index_html()

    #   :
    time_session = time() - time_begin
    duration_info = '  : ' + str(round(time_session, 2)) + ' .'
    print('\n' + duration_info)

else:
    print('__name__ is not "__main__".')


Am Ende der Simulation wird im Programmordner eine Datei mit dem Typnamen „report 2020-04-25_10-33-54.html“ generiert. Mehr dazu im nĂ€chsten Teil des Artikels.

SCHRITT 4 - Visualisieren Sie die Daten


FĂŒr weitere ErklĂ€rungen werde ich diese AusrĂŒstung verwenden:

evolution_equipment_obvious_strong.py
#       .
#    ,     :
# 0 - , 1 - , 2 - , 3 - , 4 - , 5 - , 6 - 

EQUIPMENT_COLLECTION = 'obvious_strong'

RIGHT_HANDS = dict()
RIGHT_HANDS[1] = (' ', 5000, 0, 0, 0, 0, 0)
RIGHT_HANDS[2] = (' ', 800, 0, 0, 0, 0, 0)
RIGHT_HANDS[3] = (' ', 20, 0, 0, 0, 0, 0)

LEFT_HANDS = dict()
LEFT_HANDS[1] = (' ', 4000, 0, 0, 0, 0, 0)
LEFT_HANDS[2] = (' ', 10, 0, 0, 0, 0, 0)

GLOVES = dict()
GLOVES[1] = (' ', 300, 0, 0, 0, 0, 0)
GLOVES[2] = (' ', 1, 0, 0, 0, 0, 0)

HEADS = dict()
HEADS[1] = (' ', 300, 0, 0, 0, 0, 0)
HEADS[2] = (' ', 1, 0, 0, 0, 0, 0)

CHESTS = dict()
CHESTS[1] = (' ', 300, 0, 0, 0, 0, 0)
CHESTS[2] = (' ', 1, 0, 0, 0, 0, 0)

PANTS = dict()
PANTS[1] = (' ', 300, 0, 0, 0, 0, 0)
PANTS[2] = (' ', 1, 0, 0, 0, 0, 0)

BOOTS = dict()
BOOTS[1] = (' ', 300, 0, 0, 0, 0, 0)
BOOTS[2] = (' ', 1, 0, 0, 0, 0, 0)


Hier sind die möglichen Kombinationen von 192 SÀtzen (3 * 2 * 2 * 2 * 2 * 2 * 2). Dementsprechend wird es 192 mögliche Genotypen geben .

Die Hauptidee der Visualisierung : den gesamten Raum möglicher Genotypen als rechteckiges Feld darzustellen, wobei jedes Quadrat einen eigenen Genotyp darstellt. Ein einfacher Algorithmus findet zwei mittlere Teiler der Zahl 192, dies sind 16 und 12:

Der Code fĂŒr diesen Algorithmus vom Konstruktor der Stats-Klasse
        #      - :
        list_divisors = list()
        current_number = int(self.genotypes_total // 2)
        while current_number >= 2:
            if self.genotypes_total % current_number == 0:
                list_divisors.append(current_number)
            current_number -= 1
        print('list_divisors:', list_divisors)

        #        :
        somewhere_in_the_middle = len(list_divisors) // 2

        #     :
        side_1 = list_divisors[somewhere_in_the_middle]
        side_2 = self.genotypes_total / side_1

        # ,   ,     ""  :
        self.side_x = int(side_1 if side_1 >= side_2 else side_2)
        self.side_y = int(self.genotypes_total / self.side_x)
        print('side_x =', self.side_x, 'side_y =', self.side_y)


Wir erhalten einen 16x12-Bereich:

Bild

Eine Liste möglicher Genotypen wird durch diesen Code generiert:

        #    :
        self.list_of_possible_genotypes = list()

        #      :
        for g1 in RIGHT_HANDS:
            #      :
            for g2 in LEFT_HANDS:
                #   :
                for g3 in GLOVES:
                    #   :
                    for g4 in HEADS:
                        #   :
                        for g5 in CHESTS:
                            #   :
                            for g6 in PANTS:
                                #   :
                                for g7 in BOOTS:
                                    current_genotype = str(g1-1)+'-'+str(g2-1)+'-'+str(g3-1)+'-'+str(g4-1)+'-'+str(g5-1)+'-'+str(g6-1)+'-'+str(g7-1)
                                    self.list_of_possible_genotypes.append(current_genotype)

Daher stellen die Quadrate auf dem Feld die Genotypen in dieser Reihenfolge dar:

Bild

Dies ist ein wichtiges Merkmal, da sich beispielsweise Genotypen, die das „Strong Sword-Gen“ ( blaue Ecke) enthalten, im oberen Teil des Feldes befinden und solche, die auch das „Strong Dagger-Gen“ enthalten. ( blaue Ecke) - besetzen eine noch höhere Region:

Bild

Somit befindet sich der stĂ€rkste Genotyp ( 0-0-0-0-0-0-0-0 ) in der oberen linken Ecke und der schwĂ€chste ( 2-1-1-1- 1-1-1 ) - im Gegenteil. Dies hilft uns bei der Beobachtung der Dynamik der Situation, da wir wissen, dass sich der „dominante Genpool“ wĂ€hrend der Simulation in die obere linke Ecke des Feldes verschieben sollte:

Bild

FĂŒhren Sie nun die Simulation mit den folgenden Parametern aus:

MAX_STAGES = 5
MAX_DAYS_AT_STAGE = 40
SLIDING_FREQUENCY = 1
ROGUES_AT_BEGIN = 8
WINS_TO_REPRODUCE = 2
DEFEATS_TO_DIE = 2
POSSIBLE_BIRTH_QUANTITIES = [1]

HTML_GSQUARE_SIDE = 16
HTML_GSQUARE_MARGIN = 3

Das heißt, 8 Diebe zu Beginn, <div> -Container mit Feldern (im Folgenden als Folien bezeichnet ) werden fĂŒr jeden Tag erstellt, 5 Stufen zu je 40 Tagen. Als Ergebnis erhalten wir zwei SĂ€tze von Dias mit jeweils 200 StĂŒck: " PrĂ€valenz von Genotypen " (blaugrĂŒne Farben) und " Sieg von Genotypen " (rote und grĂŒne Farben). Die FarbsĂ€ttigung der Quadrate hĂ€ngt von den Werten ab.

Wir öffnen den generierten HTML-Bericht und sehen dieses Bild fĂŒr den 1. und 10. Tag:

Bild

Am ersten Tag wurden 8 RĂ€uber generiert, deren Quadrate bis zum Ende der Simulation einen Schatten werfen. Weiter sehen wir, dass sie zu kĂ€mpfen begannen, was bedeutet, dass die ersten Siege erscheinen, die zur Fortpflanzung fĂŒhren. Die Fortpflanzung mit hoher Wahrscheinlichkeit ist mit Mutationen verbunden, daher gibt es mehr farbige Quadrate.

Schauen Sie in den nÀchsten Tagen vorbei. Am 44. Tag erschien der Genotyp " 0-0-0-0-0-0-0-0 " (siehe hinter dem blauen Pfeil). Bereits am 59. Tag konnte er Siege verbuchen (siehe hinter dem roten Pfeil).

Bild

Am 137. Tag ist zu sehen, dass " 0-0-0-0-0-0-0-0-0 " durch die Anzahl der Auftritte in der Bevölkerung an der Spitze geschlagen wurde (siehe hinter dem blauen Pfeil). Und auf der Folie des letzten Tages hatte er einen goldenen Schatten, als er den ersten Platz in Bezug auf die Anzahl der Siege belegte.

Bild

Wie Sie tatsĂ€chlich sehen können, verschiebt die natĂŒrliche Selektion den „Genpool“ der Bevölkerung nach links und oben, dh in Richtung einer stĂ€rkeren AusrĂŒstung. Die Genotypen „ unten rechts “ sind schwĂ€cher als die Genotypen „ oben links “, daher verschwinden sie, sobald sie erscheinen (rot zeigt keine Siege an).Dies ist deutlicher auf b ĂŒber zu sehenin grĂ¶ĂŸerem Maßstab:

Bild

Nun, das Urteil im Bericht ist richtig: Der Gewinnergenotyp ist 0-0-0-0-0-0-0-0 : Die

Bild

mit matplotlib erhaltenen Diagramme helfen bei der weiteren Bewertung des Geschehens:

1. Das Diagramm „ lebende Bevölkerung “ zeigt die Dynamik VerĂ€nderungen in der Anzahl gleichzeitig lebender RĂ€uber;
2. Der Graph von " alles geboren " liegt meist nahe an einer geraden Linie, wenn Sie nicht mit demografischen Parametern spielen:

Bild

3. Der Graph von " Vielfalt der Genotypen " - normalerweise zuerst schnelles Wachstum, dann verlangsamt er sich und nÀhert sich allmÀhlich dem Maximum;
4. Zeitplan " Zeichnungsdynamik"- zeigt, wie sich die Anzahl der Ziehungen im Laufe der Zeit Àndert (dies sind KÀmpfe, wenn zwei identische Genotypen in einem bedeutungslosen Kampf aufeinander treffen):

Bild

Es kommt vor, dass eine Population" stagniert ", wenn RÀuber mit denselben Genotypen darin verbleiben (Grafiken zeigen: eine starke Zunahme der Anzahl von Draws, UnverÀnderlichkeit (in Bezug auf die Population und die Anzahl der eindeutigen Genotypen)

Stagnation der Population bedeutet, dass die Berechnung und das Zeichnen von Folien vergeblich sind, um die Stagnationsperioden zu verringern. Es ist notwendig, den Wert MAX_DAYS_AT_STAGE konstant zu reduzieren, und es ist ersichtlich, dass die Überlastung abgenommen hat und an einigen Stellen vollstĂ€ndig verschwunden ist.

Bild

SCHRITT 5 - Erleben Sie große GenotyprĂ€ume


Versuchen wir nun, die Simulation fĂŒr den im letzten Artikel beschriebenen GerĂ€tesatz evolution_equipment_custom mit leicht unterschiedlichen Parametern auszufĂŒhren . Hier bedeutet der Wert POSSIBLE_BIRTH_QUANTITIES = [1, 2] , dass bei der Reproduktion mit einer Wahrscheinlichkeit von 50% entweder 1 oder 2 Nachkommen geboren werden . Dies wird die Dynamik des Geschehens verbessern:

MAX_STAGES = 20
MAX_DAYS_AT_STAGE = 50
SLIDING_FREQUENCY = 10
ROGUES_AT_BEGIN = 8 
WINS_TO_REPRODUCE = 2
DEFEATS_TO_DIE = 2
POSSIBLE_BIRTH_QUANTITIES = [1, 2]

HTML_GSQUARE_SIDE = 10
HTML_GSQUARE_MARGIN = 3

Hier ist das Verteilungsmuster der Genotypen auf den ObjekttrĂ€gern unterschiedlich, da sich die fĂŒr das Gesamtergebnis wichtigste AusrĂŒstung („Schwert des Meisters“) jetzt an „anderen Orten“ befindet. Die Visualisierung dieses GerĂ€tesatzes ist bereits durch das Auftreten einer Reihe von „Kraftherden“ an verschiedenen Stellen gekennzeichnet, an denen die Leistung des GerĂ€ts höher ist als in den benachbarten „Gebieten“.

Bild

Übrigens hat dieser Algorithmus unverkennbar den obersten AusrĂŒstungssatz ( 3-3-0-0-0-0-0-1 ) bestimmt, der dem entspricht, der durch den kombinatorischen Algorithmus aus dem vorherigen Artikel bestimmt wurde :

Bild

Und schließlich habe ich den Test fĂŒr den erweiterten Satz durchgefĂŒhrt , der ergibt 18432 Kombinationen:

mit solchen Parametern
MAX_STAGES = 30
MAX_DAYS_AT_STAGE = 50
SLIDING_FREQUENCY = 100
ROGUES_AT_BEGIN = 20
WINS_TO_REPRODUCE = 2
DEFEATS_TO_DIE = 2
POSSIBLE_BIRTH_QUANTITIES = [1, 2]

HTML_GSQUARE_SIDE = 4
HTML_GSQUARE_MARGIN = 1


Bild

Im Allgemeinen können Sie sich weiterhin damit amĂŒsieren, aber der Trend bleibt unverĂ€ndert: WĂ€hrend der Simulation beginnen sich Genotypen ziemlich schnell in diesen „Machtzentren“ anzusammeln. Und der dominante Genotyp befindet sich in einem der auffĂ€lligsten „Brennpunkte“.

SCHRITT 6 - Ist alles sinnvoll?


Wenn wir uns nun der praktischen Seite der Frage zuwenden, mĂŒssen wir verstehen, ob dieser genetische Algorithmus auf Kosten weniger KĂ€mpfe die richtige Antwort finden kann als eine einfache kombinatorische Suche aller Genotypen. Antwort: Ja, fĂ€hig . Beweis unter dem Schnitt:

Nachweis der Wirksamkeit des genetischen Algorithmus
1728 .

, .. 1728 .

, 1728 . «» — 2 ( ). , , 1728 / 2 = 864. .

:

MAX_STAGES = 8
MAX_DAYS_AT_STAGE = 20
SLIDING_FREQUENCY = 10
ROGUES_AT_BEGIN = 10
WINS_TO_REPRODUCE = 2
DEFEATS_TO_DIE = 2
POSSIBLE_BIRTH_QUANTITIES = [1]

HTML_GSQUARE_SIDE = 10
HTML_GSQUARE_MARGIN = 3

, 3-3-0-0-0-0-1:

image

:

image

, 3-3-0-0-0-0-1 547 . 87 (), 16% .

.

PS Auch mit ROGUES_AT_BEGIN = 2 und POSSIBLE_BIRTH_QUANTITIES = [1] ist es möglich, die richtige Antwort zu erhalten . Dies scheint ĂŒberraschend, da die Bevölkerung nie ĂŒber 2 RĂ€uber steigt. Weil einer verliert, der andere gewinnt, der erste stirbt und der zweite einen Nachkommen zur Welt bringt. Und die Eltern beginnen mit diesem Nachkommen zu konkurrieren. Der Nachkomme ist entweder stĂ€rker oder schwĂ€cher. Und auf diese Weise bewegt sich das rĂŒcksichtslose Auswahlrad zwischen dem Elternteil und seinem Nachkommen, bis er zu einem besseren Punkt gelangt (den er in der vorgegebenen Zeit erreichen kann, daher ist dies nicht immer der beste).

Zusammenfassung


  1. Das Problem wird mit genetischen Algorithmen gelöst.
  2. «» .
  3. , , ( calculate_rate Rogue, ).
  4. NatĂŒrlich können Sie mit diesem Programm immer noch experimentieren und experimentieren, um die Effizienz zu verbessern. Beginnen Sie zum Beispiel irgendwann damit, offensichtlich verlorene Genotypen zu „verbieten“ , ohne dass ihre Besitzer kĂ€mpfen oder sogar auftauchen können. Somit wird sich der Trichter der „FĂŒhrer“ verengen, wo sie genau bestimmen mĂŒssen, wer unter sich „am stĂ€rksten“ ist.

Ich habe den gesamten Projektcode auf dem Github gepostet .

Liebe Community, ich freue mich ĂŒber Feedback zu diesem Thema.

All Articles