Memilih peralatan untuk game Persia menggunakan genetika / evolusi dengan Python

Bagaimana memilih peralatan terbaik di gim favorit Anda? Tentu saja, Anda dapat menyortir semua kombinasi yang mungkin (misalnya, untuk perampok dari World of Warcraft) dan menemukan yang terbaik. Tanpa sihir atau pembelajaran mesin. Tetapi apakah mungkin untuk mencapai hasil ini bukan "langsung", tetapi dengan bantuan algoritma genetika, tanpa mencoba setiap kombinasi? Sangat menarik untuk mengetahui bagaimana perampok berkembang biak dan berkembang? Pergilah.

gambar

Artikel ini adalah pendalaman topik sebelumnya , yang sudah menjelaskan detail implementasi karakter dan dampak peralatan.

Tujuan: untuk menunjukkan kepada mereka yang tertarik bagaimana membuat populasi virtual agen game sendiri, mendorong mereka bersama-sama, menerapkan algoritma genetika sederhana (dengan mutasi) dan memvisualisasikan semua ini menggunakan HTML, CSS dan Javascript.

Yang Anda butuhkan:

1. Python 3 + instal modul matplotlib ; IDE (Saya punya PyCharm);
2. jika Anda ingin mengedit templat laporan interaktif, maka pemahaman dasar tentang HTML, CSS, JavaScript (jQuery).

LANGKAH 1 - menetapkan tujuan, berurusan dengan OOP


Jadi, tujuan akhir dari pengkodean kami adalah untuk memungkinkan untuk mendapatkan pakaian terbaik dengan simulasi genetika / evolusi / seleksi alam. Dan dapatkan kesempatan untuk melacak secara visual jalur ini.

Kami mengubah tujuan kami menjadi tugas khusus:

  1. untuk memikirkan dan menerapkan mekanisme genetik pada tingkat individu ("gen" perampok harus secara langsung mempengaruhi pilihan peralatan)
  2. menerapkan mekanisme kelahiran keturunan, transfer gen kepada mereka, serta mutasi kecil acak, untuk memastikan variabilitas dan kemungkinan menemukan yang terbaik
  3. untuk mewujudkan "persaingan" genetik, pemilihan gen (untuk mendorong pembawa mereka bersama-sama, sehingga hasil dari tabrakan menggulingkan gen "buruk" dan meninggikan yang "baik")
  4. merakit segala sesuatu menjadi sistem yang koheren di mana perampok dapat berkembang menjadi pejuang yang ideal
  5. untuk mengumpulkan dan memvisualisasikan data sehingga ada sesuatu untuk dikagumi (kalau tidak itu menarik!)
  6. mengevaluasi manfaat dari kegiatan-kegiatan ini

Di sini OOP akan membantu kami, kami akan membuat 4 kelas dasar:

gambar

LANGKAH 2 - mengasah kelas Rogue untuk berkembang biak


menyetujui persyaratan di pantai


Dalam artikel ini saya akan berpisah dengan istilah "gen" (dalam kode - gen ) dan "genotipe" (dalam kode - genotipe ). Juga akan ada "mutasi" (dalam kode - mutasi , mutasi ), menyiratkan perubahan dalam satu atau lebih gen artinya. Reproduksi akan terjadi hanya dengan "pemula" keturunan dari induk, sehingga tidak akan ada persimpangan dan komplikasi serupa lainnya. Genotipe perampok adalah daftar 7 angka yang merupakan gennya:

gambar

Jadi, kelas Rogue tumbuh secara signifikan dibandingkan dengan yang terakhir kali .

1.Perampok menerima gen dengan salah satu cara yang mungkin (dihasilkan pada hari pertama atau diwarisi dari "induk" dengan atau tanpa mutasi). Metode menghasilkan_random_genes , mutate_genes , mutate_gene bertanggung jawab untuk ini :

kode metode pembentukan gen
    #     ():
    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. Gen (genotipe) menentukan perampok pemakai perlengkapan mana (segera saat kelahiran inisialisasi objek). Untuk melakukan ini, metode apply_genes disebut :

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. Peralatan akan menentukan nilai akhir dari "peringkat" perampok. Untuk melakukan hal ini, calculate_rate metode akan dipanggil , yang menghitung ekspektasi matematis kerusakan:

calcul_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. Peringkat perampok akan menjadi faktor penentu yang menyebabkan reproduksi atau kematian yang memalukan. Untuk ini, konsep "kemenangan" (metode embody_win ) dan "kekalahan" (metode embody_defeat ) diperkenalkan :

embody_win dan 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 + ' ...')


Nah, konstruktor kelas telah dirancang ulang sesuai untuk membuat rantai ini "gen - peralatan - peringkat" bekerja:

Konstruktor kelas nakal
    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()


Untuk meringkas logika kelas ini dengan gambar:

gambar

TAHAP 3 - Populasi, Statistik dan Kelas Tantangan


Kelas Populasi hanya diperlukan untuk tiga hal:

  1. menciptakan populasi dari jumlah perampok yang ditentukan dengan gen acak dan parameter biologis yang akan selalu tidak berubah ( wins_to_reproduce - berapa banyak kemenangan yang perlu diperoleh perampok untuk reproduksi, defeats_to_die - berapa banyak kekalahan akan membuat mati individu);
  2. "Reset" dari populasi, mis. ketika hari terakhir Tahap saat ini berakhir, semua perampok dihancurkan dan perampok dengan genotipe terbaik dibuat (lihat metode memuat ulang );
  3. menyimpan beberapa data populasi untuk statistik.

populasi kelas
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)


Kelas Stats melakukan dua tugas utama:

  1. pengumpulan data selama simulasi (misalnya, indikator setiap hari: jumlah perampok hidup, jumlah genotipe unik, dll.)
  2. ( matplotlib, HTML-, ).



gambar

Ke depan, saya menunjukkan bagaimana laporan ini akan terlihat: Kode template HTML untuk laporan tersedia di github .

Kelas Stats juga lebih baik dilihat di github (jika tidak, akan banyak baris yang terluka di sini).

Kelas Challenger bertanggung jawab untuk mensimulasikan tabrakan antara perampok yang dipilih secara acak. Setiap hari, metode ini disebut perform_battles , yang terbentuk dari pasangan duel perampok yang hidup dan menghadapkan mereka dengan metode perform_battle , dan akhirnya untuk setiap perampok disebabkan metode apa pun embody_win , metode apa pun embody_defeat. Omong-omong, jika ternyata undian telah terjadi (genotipe, dan oleh karena itu peringkat perampok adalah sama), maka mereka menyimpang tanpa konsekuensi:

penantang kelas
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 !  !')


Nah, sekarang kita beralih ke badan program, yang menghubungkan semua bagian kode yang disebutkan. Pertama, datang konstanta yang menentukan parameter kunci dari simulasi: jumlah tahap, hari dalam tahap, jumlah awal perampok dalam suatu populasi, dll. Kemudian datang konstanta bantu untuk debugging. Dan tantangannya sendiri:

kode untuk menjalankan simulasi
# :
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__".')


Di akhir simulasi, file dengan nama jenis "laporan 2020-04-25_10-33-54.html" dihasilkan dalam folder program. Lebih lanjut tentang hal ini di bagian artikel selanjutnya.

LANGKAH 4 - memvisualisasikan data


Untuk penjelasan lebih lanjut saya akan menggunakan set peralatan ini:

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)


Berikut adalah kombinasi yang mungkin dari 192 set (3 * 2 * 2 * 2 * 2 * 2 * 2). Dengan demikian, akan ada 192 kemungkinan genotipe .

Gagasan utama visualisasi : untuk mewakili seluruh ruang genotipe yang mungkin sebagai bidang persegi panjang, di mana setiap kotak mewakili genotipe terpisah. Algoritme sederhana menemukan dua pembagi tengah dari angka 192, ini adalah 16 dan 12:

kode untuk algoritma ini dari konstruktor dari kelas Stats
        #      - :
        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)


Kami mendapatkan area 16x12:

gambar

Daftar kemungkinan genotipe dihasilkan oleh kode ini:

        #    :
        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)

Oleh karena itu, kotak di lapangan mewakili genotipe dalam urutan ini:

gambar

Ini adalah fitur penting, karena, misalnya, genotipe yang mengandung "gen Pedang Kuat" ( sudut biru ) berada di bagian atas lapangan, dan yang juga mengandung "gen Belati Kuat" ( sudut biru ) - menempati wilayah yang lebih tinggi:

gambar

Dengan demikian, genotipe terkuat ( 0-0-0-0-0-0-0-0 ) di sudut kiri atas, dan yang terlemah ( 2-1-1-1- 1-1-1 ) - sebaliknya. Ini akan membantu kita dalam mengamati dinamika situasi, karena kita akan tahu bahwa selama simulasi, “kumpulan gen dominan” harus bergeser ke sudut kiri atas lapangan:

gambar

Sekarang jalankan simulasi dengan parameter berikut:

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

Yaitu, 8 pencuri di awal, <div> -kontainer dengan bidang (selanjutnya disebut slide ) dibuat untuk setiap hari, 5 tahap masing-masing 40 hari. Hasilnya, kita mendapatkan dua set slide yang masing-masing terdiri dari 200 lembar: " prevalensi genotipe " (warna biru-hijau) dan " kemenangan genotipe " (warna merah dan hijau). Saturasi warna kotak tergantung pada nilai-nilai.

Kami membuka laporan HTML yang dihasilkan dan melihat gambar ini untuk hari pertama dan ke-10:

gambar

Jadi, pada hari pertama, 8 perampok dihasilkan, kotak-kotak mereka akan memberikan bayangan sampai akhir simulasi. Lebih jauh, kita melihat bahwa mereka mulai bertarung, yang berarti kemenangan pertama kali muncul, yang mengarah ke reproduksi. Reproduksi dengan probabilitas tinggi dikaitkan dengan mutasi, sehingga ada lebih banyak kotak berwarna.

Lihatlah hari-hari berikutnya. Pada hari ke-44, genotipe " 0-0-0-0-0-0-0-0 " muncul (lihat di belakang panah biru). Pada hari ke-59, ia sudah unggul dalam kemenangan (lihat di belakang panah merah).

gambar

Pada hari ke 137 dapat dilihat bahwa " 0-0-0-0-0-0-0-0 " dipukuli di depan oleh jumlah penampilan dalam populasi (lihat di belakang panah biru). Dan pada slide hari terakhir ia memiliki bayangan emas, saat ia memegang tempat pertama dalam hal jumlah kemenangan.

gambar

Seperti yang Anda lihat, memang, seleksi alam menggeser "kumpulan gen" populasi ke kiri dan ke atas, yaitu, menuju peralatan yang lebih kuat. Genotipe “ kanan bawahlebih lemah daripada genotipe “ kiri atas ”, oleh karena itu mereka menghilang segera setelah mereka muncul (merah menunjukkan tidak ada kemenangan).Ini lebih jelas terlihat pada b tentangpada skala yang lebih besar:

gambar

Ya, putusan dalam laporan itu benar: genotipe yang menang adalah 0-0-0-0-0-0-0-0-0 :

gambar

Grafik yang diperoleh dengan menggunakan matplotlib akan membantu untuk mengevaluasi lebih lanjut apa yang terjadi:

1. Grafik " populasi hidup " menunjukkan dinamika perubahan jumlah perampok yang hidup serentak;
2. grafik " lahir semua " sebagian besar dekat dengan garis lurus, jika Anda tidak mulai bermain dengan parameter demografis:

gambar

3. grafik " keragaman genotipe " - biasanya pertumbuhan cepat pada awalnya, kemudian melambat, perlahan-lahan mendekati secara maksimum;
4. jadwal " gambar dinamika"- menunjukkan bagaimana jumlah undian berubah dari waktu ke waktu (ini adalah perkelahian ketika dua genotipe identik bertabrakan dalam pertarungan yang tidak berarti):

gambar

Kebetulan suatu populasi" mandek "ketika perampok dengan genotipe yang sama tetap di dalamnya (grafik menunjukkan: peningkatan tajam dalam jumlah menggambar, kekekalan . dalam hal populasi dan jumlah genotipe unik)

stagnasi populasi berarti bahwa perhitungan dan gambar slide akan sia-sia untuk mengurangi periode stagnasi, perlu untuk mengurangi nilai MAX_DAYS_AT_STAGE konstan, dan akan terlihat bahwa kemacetan telah menurun, dan di beberapa tempat benar-benar menghilang .:

gambar

LANGKAH 5 - mengalami ruang genotipe besar


Sekarang mari kita coba menjalankan simulasi untuk set peralatan evolution_equipment_custom yang dibahas dalam artikel terakhir dengan parameter yang sedikit berbeda. Di sini, nilai POSSIBLE_BIRTH_QUANTITIES = [1, 2] berarti bahwa dengan tindakan reproduksi dengan probabilitas 50%, salah satu atau 2 keturunan akan lahir . Ini akan meningkatkan dinamika dari apa yang terjadi:

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

Di sini pola distribusi genotipe pada slide akan berbeda, karena peralatan yang paling signifikan untuk hasil keseluruhan ("Pedang Sang Guru") sekarang ada di "tempat lain". Visualisasi set peralatan ini sudah ditandai dengan penampilan sejumlah "fokus daya" di tempat yang berbeda di mana kekuatan peralatan lebih tinggi daripada di "daerah" tetangga.

gambar

Ngomong-ngomong, algoritma ini tidak salah lagi menentukan set peralatan teratas ( 3-3-0-0-0-0-0-1 ), yang cocok dengan yang ditentukan oleh algoritma kombinatorial dari artikel sebelumnya :

gambar

Dan, akhirnya, saya menjalankan tes untuk set yang diperluas , yang memberikan 18432 kombinasi:

dengan parameter seperti itu
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


gambar

Secara umum, Anda dapat terus menghibur diri dengan ini, tetapi trennya tetap tidak berubah: selama simulasi, genotipe agak cepat mulai terakumulasi dalam "pusat kekuasaan" yang sangat ini. Dan genotipe dominan adalah salah satu "fokus" yang paling mencolok.

LANGKAH 6 - Apakah semuanya masuk akal?


Jika sekarang kita beralih ke sisi praktis dari pertanyaan, kita perlu memahami apakah algoritma genetika ini mampu menemukan jawaban yang tepat dengan biaya perkelahian yang lebih sedikit daripada pencarian kombinatorial sederhana dari semua genotipe. Jawab: ya, mampu . Bukti di bawah potongan:

bukti efektivitas algoritma genetika
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 Dimungkinkan untuk mendapatkan jawaban yang benar bahkan dengan ROGUES_AT_BEGIN = 2 dan POSSIBLE_BIRTH_QUANTITIES = [1] . Ini sepertinya mengejutkan, karena populasinya tidak pernah naik di atas 2 perampok. Karena yang satu kalah, yang lain menang, yang pertama mati, dan yang kedua melahirkan satu keturunan. Dan orang tua mulai bersaing dengan keturunan ini. Keturunannya lebih kuat atau lebih lemah. Dan dengan cara ini roda seleksi yang kejam bergerak di antara orang tua dan keturunannya sampai ia mencapai titik yang lebih baik (yang dapat ia capai dalam waktu yang ditentukan, jadi ini tidak selalu yang terbaik).

Ringkasan


  1. Masalahnya diselesaikan dengan menggunakan algoritma genetika.
  2. «» .
  3. , , ( calculate_rate Rogue, ).
  4. Tentu saja, pada program ini Anda masih dapat bereksperimen dan bereksperimen, meningkatkan efisiensi. Misalnya, di beberapa titik, mulailah untuk "melarang" jelas kehilangan genotipe, tidak membiarkan pemiliknya untuk bertarung atau bahkan muncul. Dengan demikian, corong "pemimpin" akan menyempit, di mana mereka harus menentukan dengan tepat siapa yang "paling kuat" di antara mereka.

Saya memposting semua kode proyek di github .

Komunitas yang terhormat, saya akan dengan senang hati menerima umpan balik tentang topik ini.

All Articles