File size: 10,470 Bytes
8e35b08
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# quanta_sim_v3_multi_input.py

import neat
import numpy as np
import os
import logging
import pickle
import random
import math
import datetime
import itertools # <-- YENİ: Girdi kombinasyonları için

# --- Loglama Ayarları ---
log_filename = f"quanta_log_v3_multi_input_{datetime.datetime.now():%Y%m%d_%H%M%S}.log" # <-- V3 için dosya adı
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s',
    handlers=[
        logging.FileHandler(log_filename),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

logger.info("="*50)
logger.info("Quanta Simülatörü Başlatılıyor (Sürüm 3 - Çoklu Girdi)") # <-- GÜNCELLENDİ
logger.info("="*50)

# --- Simülasyon Parametreleri ---
NUM_TEST_VALUES_PER_AXIS = 3 # Her bir girdi ekseni için test edilecek değer sayısı (örn: 0.0, 0.5, 1.0)
NUM_TRIALS_PER_INPUT_PAIR = 20 # Her bir girdi çifti için yapılacak deneme sayısı
# Toplam test noktası sayısı = NUM_TEST_VALUES_PER_AXIS * NUM_TEST_VALUES_PER_AXIS
# Toplam değerlendirme denemesi/Genom = NUM_TEST_POINTS * NUM_TRIALS_PER_INPUT_PAIR

MAX_GENERATIONS = 150 # <-- Problem zorlaştığı için artırılabilir
FITNESS_THRESHOLD = 0.97 # <-- Hedef zorlaştığı için eşik ayarlanabilir

num_test_points = NUM_TEST_VALUES_PER_AXIS ** 2 # İki girdi olduğu için karesi
total_trials_per_genome = num_test_points * NUM_TRIALS_PER_INPUT_PAIR

logger.info(f"Eksen Başına Test Değeri Sayısı: {NUM_TEST_VALUES_PER_AXIS}")
logger.info(f"Toplam Test Girdi Çifti Sayısı: {num_test_points}")
logger.info(f"Girdi Çifti Başına Deneme Sayısı: {NUM_TRIALS_PER_INPUT_PAIR}")
logger.info(f"Toplam Değerlendirme Denemesi/Genom: {total_trials_per_genome}")
logger.info(f"Maksimum Nesil Sayısı: {MAX_GENERATIONS}")
logger.info(f"Fitness Eşiği: {FITNESS_THRESHOLD}")

# --- YENİ: İki Girdi İçin Hedef Olasılık Fonksiyonu ---
def calculate_target_prob0(input1, input2):
    """

    Verilen iki girdiye göre hedef P(0) olasılığını hesaplar.

    Lineer Fonksiyon: target_P0(x1, x2) = 0.1 + 0.5*x1 + 0.3*x2

    """
    # Değerlerin 0-1 aralığında kalmasını sağlayalım (isteğe bağlı, fonksiyona göre)
    target = 0.1 + 0.5 * input1 + 0.3 * input2
    return max(0.0, min(1.0, target)) # Güvenlik önlemi

logger.info(f"Hedef P(0) Fonksiyonu: P(0|x1, x2) = 0.1 + 0.5*x1 + 0.3*x2")

# --- NEAT Fitness Fonksiyonu ---
# (Bu fonksiyon Sürüm 3 için önemli ölçüde güncellendi)
def eval_genomes(genomes, config):
    """

    Popülasyondaki tüm genomların fitness değerlerini hesaplar.

    Fitness, ağın farklı girdi çiftleri için hedeflenen olasılıkları

    ne kadar iyi üretebildiğine göre belirlenir.

    """
    # Test edilecek girdi değerlerini her eksen için belirleyelim
    axis_values = np.linspace(0.0, 1.0, NUM_TEST_VALUES_PER_AXIS)
    # Örnek: NUM_TEST_VALUES_PER_AXIS=3 ise -> [0.0, 0.5, 1.0]

    # Tüm girdi çiftlerini (kombinasyonlarını) oluşturalım
    # Örnek: [(0.0, 0.0), (0.0, 0.5), (0.0, 1.0), (0.5, 0.0), ..., (1.0, 1.0)]
    test_input_pairs = list(itertools.product(axis_values, repeat=2))

    for genome_id, genome in genomes:
        genome.fitness = 0.0 # Başlangıç fitness'ı sıfırla
        try:
            # İki girdili config ile ağı oluştur
            net = neat.nn.FeedForwardNetwork.create(genome, config)
        except Exception as e:
            logger.error(f"Genome {genome_id} için ağ oluşturulamadı: {e}")
            genome.fitness = -10.0
            continue

        total_squared_error = 0.0

        # Her bir test girdi çifti için ağı değerlendir
        for input_pair in test_input_pairs:
            x1, x2 = input_pair # Girdi çiftini ayır
            target_prob_0 = calculate_target_prob0(x1, x2) # Bu çift için hedef P(0)

            count_0 = 0
            # Her girdi çifti için N kez test et
            for _ in range(NUM_TRIALS_PER_INPUT_PAIR):
                try:
                    # Ağı iki girdi ile aktive et
                    output = net.activate(input_pair)
                    # Ağın çıktısını yorumla (< 0.5 ise 0)
                    if output[0] < 0.5:
                        count_0 += 1
                except Exception as e:
                    logger.warning(f"Genome {genome_id}, Input {input_pair} ağ aktivasyonunda hata: {e}")
                    pass

            # Gözlemlenen olasılığı hesapla
            if NUM_TRIALS_PER_INPUT_PAIR > 0:
                observed_prob_0 = count_0 / NUM_TRIALS_PER_INPUT_PAIR
            else:
                observed_prob_0 = 0.5

            # Bu girdi çifti için hatanın karesini hesapla
            error = (observed_prob_0 - target_prob_0) ** 2
            total_squared_error += error

        # Ortalama Karesel Hatayı (Mean Squared Error - MSE) hesapla
        average_squared_error = total_squared_error / len(test_input_pairs)

        # Fitness'ı hesapla (1 - RMSE)
        fitness = max(0.0, 1.0 - math.sqrt(average_squared_error))
        genome.fitness = fitness
        # logger.info(f"Genome {genome_id}: AvgError^2 = {average_squared_error:.4f}, Fitness = {fitness:.4f}")


# --- NEAT Çalıştırma Fonksiyonu ---
def run_neat(config_file):
    """

    NEAT evrimini başlatır ve yönetir.

    """
    logger.info(f"NEAT yapılandırması yükleniyor: {config_file}")
    try:
        # V3 config dosyasını kullanarak config nesnesini oluştur
        config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
                             neat.DefaultSpeciesSet, neat.DefaultStagnation,
                             config_file) # <-- config_file şimdi v3 dosyasını gösteriyor
        config.fitness_threshold = FITNESS_THRESHOLD
        logger.info(f"Yapılandırma yüklendi. Giriş Sayısı: {config.genome_config.num_inputs}, Fitness Eşiği: {config.fitness_threshold}")

    except Exception as e:
        logger.critical(f"Yapılandırma dosyası yüklenemedi veya geçersiz: {config_file} - Hata: {e}")
        return None

    logger.info("Yeni popülasyon oluşturuluyor...")
    p = neat.Population(config)

    # Raporlayıcılar
    p.add_reporter(neat.StdOutReporter(True))
    # Checkpoint
    checkpoint_prefix = 'neat-checkpoint-v3-' # <-- V3 için prefix
    p.add_reporter(neat.Checkpointer(15, filename_prefix=checkpoint_prefix)) # Daha seyrek kaydedilebilir
    logger.info(f"Checkpoint dosyaları '{checkpoint_prefix}*' olarak kaydedilecek.")

    logger.info(f"Evrim başlıyor (Maksimum {MAX_GENERATIONS} nesil)...")
    try:
        winner = p.run(eval_genomes, MAX_GENERATIONS)
        logger.info(' ' + "="*20 + " Evrim Tamamlandı " + "="*20)
    except Exception as e:
        logger.critical(f"Evrim sırasında kritik bir hata oluştu: {e}")
        return None

    # En iyi genomu işle
    if winner:
        logger.info(f'En iyi genom bulundu (Fitness: {winner.fitness:.6f}):')
        logger.info(f' {winner}')

        # En iyi genomu kaydet
        winner_filename = "winner_genome_v3.pkl" # <-- V3 için dosya adı
        try:
            with open(winner_filename, 'wb') as f:
                pickle.dump(winner, f)
            logger.info(f"En iyi genom '{winner_filename}' dosyasına başarıyla kaydedildi.")
        except Exception as e:
            logger.error(f"En iyi genom kaydedilemedi: {e}")

        # --- YENİ: En İyi Genomu Çoklu Girdi İle Detaylı Test Etme ---
        logger.info(" " + "="*20 + " En İyi Genom Detaylı Testi (Çoklu Girdi) " + "="*20)
        try:
            winner_net = neat.nn.FeedForwardNetwork.create(winner, config)
            test_trials_final = 500 # Final test için deneme sayısı
            logger.info(f"En iyi ağ, farklı girdi çiftleriyle {test_trials_final} kez test ediliyor...")

            # Daha hassas bir girdi ızgarası oluşturalım (örn: 5x5 = 25 nokta)
            final_axis_values = np.linspace(0.0, 1.0, 5)
            final_test_pairs = list(itertools.product(final_axis_values, repeat=2))
            final_total_error_sq = 0.0

            logger.info(f"{'Input (x1,x2)': <15} {'Target P(0)': <12} {'Observed P(0)': <14} {'Error^2': <10}")
            logger.info("-" * 60)

            for input_pair in final_test_pairs:
                x1, x2 = input_pair
                target_p0 = calculate_target_prob0(x1, x2)
                count_0 = 0
                for _ in range(test_trials_final):
                    output = winner_net.activate(input_pair)
                    if output[0] < 0.5:
                        count_0 += 1

                observed_p0 = count_0 / test_trials_final
                error_sq = (observed_p0 - target_p0) ** 2
                final_total_error_sq += error_sq

                logger.info(f"({x1:.2f}, {x2:.2f})       {target_p0:<12.3f} {observed_p0:<14.3f} {error_sq:<10.5f}")

            final_avg_error_sq = final_total_error_sq / len(final_test_pairs)
            final_rmse = math.sqrt(final_avg_error_sq)
            logger.info("-" * 60)
            logger.info(f"Final Ortalama Karesel Hata (MSE): {final_avg_error_sq:.6f}")
            logger.info(f"Final Kök Ortalama Karesel Hata (RMSE): {final_rmse:.6f}")
            logger.info(f"Final Fitness Yaklaşımı (1 - RMSE): {1.0 - final_rmse:.6f}")
            logger.info("-" * 60)

        except Exception as e:
            logger.error(f"En iyi genom test edilirken hata oluştu: {e}")

    else:
        logger.warning("Test edilecek bir kazanan genom bulunamadı.")

    logger.info("="*50)
    logger.info("Quanta Simülatörü Adım 3 (Çoklu Girdi) tamamlandı.")
    logger.info("="*50)
    return winner


if __name__ == '__main__':
    # V3 için yeni yapılandırma dosyasının yolu
    local_dir = os.path.dirname(__file__)
    config_path = os.path.join(local_dir, 'config-feedforward-v3.txt') # <-- YENİ Config Dosyası

    if not os.path.exists(config_path):
        logger.critical(f"Yapılandırma dosyası bulunamadı: {config_path}")
        logger.critical("Lütfen 'config-feedforward-v3.txt' dosyasını Python betiğiyle aynı klasöre koyun.")
    else:
        # NEAT'i çalıştır
        run_neat(config_path)