File size: 12,845 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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# quanta_sim_v4_correlated_outputs.py

import neat
import numpy as np
import os
import logging
import pickle
import random
import math
import datetime
import itertools

# --- Loglama Ayarları ---
log_filename = f"quanta_log_v4_corr_{datetime.datetime.now():%Y%m%d_%H%M%S}.log" # <-- V4 için dosya adı
logging.basicConfig(
    level=logging.INFO, # DEBUG seviyesi daha fazla bilgi verir ama yavaşlatabilir
    format='%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s',
    handlers=[
        logging.FileHandler(log_filename),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

logger.info("="*70)
logger.info("Quanta Simülatörü Başlatılıyor (Sürüm 4 - Korelasyonlu Çıktılar)") # <-- GÜNCELLENDİ
logger.info("="*70)

# --- Simülasyon Parametreleri ---
NUM_TEST_VALUES_PER_AXIS = 3 # Değerlendirme için ızgara boyutu (3x3=9 nokta)
NUM_TRIALS_PER_PAIR = 30     # Her girdi çifti için deneme sayısı (artırıldı)

MAX_GENERATIONS = 250 # <-- Problem çok daha zor, nesil sayısı artırıldı
FITNESS_THRESHOLD = 0.97 # <-- Hedef zor, eşik ayarlanabilir

num_test_points = NUM_TEST_VALUES_PER_AXIS ** 2
total_trials_per_genome = num_test_points * NUM_TRIALS_PER_PAIR

# Fitness fonksiyonundaki hata ağırlıkları
W_PA0 = 1.0 # P(A=0) hatasının ağırlığı
W_PB0 = 1.0 # P(B=0) hatasının ağırlığı
W_CORR = 1.5 # Korelasyon hatasının ağırlığı (genellikle daha zordur, ağırlığı artırılabilir)
TOTAL_WEIGHT = W_PA0 + W_PB0 + W_CORR

logger.info(f"Eksen Başına Test Değeri: {NUM_TEST_VALUES_PER_AXIS} ({num_test_points} test noktası)")
logger.info(f"Girdi Çifti Başına Deneme: {NUM_TRIALS_PER_PAIR} (Toplam {total_trials_per_genome} deneme/genom)")
logger.info(f"Maksimum Nesil: {MAX_GENERATIONS}")
logger.info(f"Fitness Eşiği: {FITNESS_THRESHOLD}")
logger.info(f"Fitness Hata Ağırlıkları: P(A=0)={W_PA0:.1f}, P(B=0)={W_PB0:.1f}, Corr={W_CORR:.1f}")

# --- YENİ: Hedef Fonksiyonları (P(A=0), P(B=0), Korelasyon) ---
def target_PA0(x1, x2):
    return 0.2 + 0.6 * x1

def target_PB0(x1, x2):
    return 0.7 - 0.5 * x2

def target_Corr(x1, x2):
    # Hedef korelasyon (-1 ile +1 arasında olmalı)
    return -0.8 * x1 * x2

logger.info(f"Hedef P(A=0|x1,x2) = 0.2 + 0.6*x1")
logger.info(f"Hedef P(B=0|x1,x2) = 0.7 - 0.5*x2")
logger.info(f"Hedef Corr(A,B|x1,x2) = -0.8 * x1 * x2")

# --- Korelasyon Hesaplama Yardımcı Fonksiyonu ---
def calculate_correlation(pA0, pB0, pA0B0, epsilon=1e-9):
    """ Verilen olasılıklardan korelasyonu hesaplar. """
    pA1 = 1.0 - pA0
    pB1 = 1.0 - pB0

    # Kovaryans = P(A=0,B=0) - P(A=0)*P(B=0)
    covariance = pA0B0 - pA0 * pB0

    # Standart sapmaların karesi (varyanslar)
    varA = pA0 * pA1
    varB = pB0 * pB1

    # Sıfıra bölme hatasını önlemek için epsilon ekle
    denominator = math.sqrt((varA + epsilon) * (varB + epsilon))

    if denominator < epsilon: # Eğer varyans çok küçükse (olasılıklar 0 veya 1'e çok yakınsa)
        return 0.0 # Korelasyon anlamsızdır veya sıfırdır

    correlation = covariance / denominator
    # Korelasyonun teorik olarak -1 ile 1 arasında kalmasını sağla (küçük hatalar olabilir)
    return max(-1.0, min(1.0, correlation))

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

    Popülasyondaki genomların fitness'ını hesaplar. Fitness, ağın

    P(A=0), P(B=0) ve Corr(A,B) hedeflerini farklı girdilerde

    ne kadar iyi yakaladığına göre belirlenir.

    """
    axis_values = np.linspace(0.0, 1.0, NUM_TEST_VALUES_PER_AXIS)
    test_input_pairs = list(itertools.product(axis_values, repeat=2))

    for genome_id, genome in genomes:
        genome.fitness = 0.0
        try:
            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 = -20.0 # Daha büyük ceza
            continue

        total_weighted_error_sum = 0.0

        for input_pair in test_input_pairs:
            x1, x2 = input_pair
            targ_pA0 = target_PA0(x1, x2)
            targ_pB0 = target_PB0(x1, x2)
            targ_corr = target_Corr(x1, x2)

            count_A0 = 0
            count_B0 = 0
            count_A0B0 = 0 # Hem A=0 hem B=0 olan durumların sayısı

            # Denemeleri yap
            for _ in range(NUM_TRIALS_PER_PAIR):
                try:
                    output1, output2 = net.activate(input_pair)
                    # Çıktıları ikili sonuçlara çevir
                    res_A = 1 if output1 >= 0.5 else 0 # A=0 veya A=1
                    res_B = 1 if output2 >= 0.5 else 0 # B=0 veya B=1

                    if res_A == 0: count_A0 += 1
                    if res_B == 0: count_B0 += 1
                    if res_A == 0 and res_B == 0: count_A0B0 += 1

                except Exception as e:
                    logger.warning(f"Genome {genome_id}, Input {input_pair} aktivasyon hatası: {e}")
                    # Hata durumunda bu denemeyi geçebiliriz veya penaltı verebiliriz
                    pass

            # Gözlemlenen olasılıkları hesapla
            if NUM_TRIALS_PER_PAIR > 0:
                obs_pA0 = count_A0 / NUM_TRIALS_PER_PAIR
                obs_pB0 = count_B0 / NUM_TRIALS_PER_PAIR
                obs_pA0B0 = count_A0B0 / NUM_TRIALS_PER_PAIR
            else:
                obs_pA0, obs_pB0, obs_pA0B0 = 0.5, 0.5, 0.25 # Varsayılan

            # Gözlemlenen korelasyonu hesapla
            obs_corr = calculate_correlation(obs_pA0, obs_pB0, obs_pA0B0)

            # Hataları hesapla
            error_pA0 = (obs_pA0 - targ_pA0) ** 2
            error_pB0 = (obs_pB0 - targ_pB0) ** 2
            error_corr = (obs_corr - targ_corr) ** 2

            # Ağırlıklı toplam hatayı hesapla
            weighted_error = (W_PA0 * error_pA0 + W_PB0 * error_pB0 + W_CORR * error_corr) / TOTAL_WEIGHT
            total_weighted_error_sum += weighted_error

            # logger.debug(f"  G:{genome_id} In:{input_pair} Tgt:({targ_pA0:.2f},{targ_pB0:.2f},{targ_corr:.2f}) Obs:({obs_pA0:.2f},{obs_pB0:.2f},{obs_corr:.2f}) Err:{weighted_error:.4f}")

        # Ortalama ağırlıklı hatayı hesapla
        average_weighted_error = total_weighted_error_sum / len(test_input_pairs)

        # Fitness (1 - sqrt(AvgError))
        fitness = max(0.0, 1.0 - math.sqrt(average_weighted_error))
        genome.fitness = fitness
        # logger.info(f"Genome {genome_id}: AvgWeightedError = {average_weighted_error:.4f}, Fitness = {fitness:.4f}")


# --- NEAT Çalıştırma Fonksiyonu ---
def run_neat(config_file):
    logger.info(f"NEAT yapılandırması yükleniyor: {config_file}")
    try:
        config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
                             neat.DefaultSpeciesSet, neat.DefaultStagnation,
                             config_file)
        config.fitness_threshold = FITNESS_THRESHOLD
        logger.info(f"Yapılandırma: Giriş={config.genome_config.num_inputs}, Çıkış={config.genome_config.num_outputs}, Pop={config.pop_size}, Eşik={config.fitness_threshold}")
    except Exception as e:
        logger.critical(f"Yapılandırma dosyası yüklenemedi: {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_prefix = 'neat-checkpoint-v4-'
    p.add_reporter(neat.Checkpointer(20, filename_prefix=checkpoint_prefix)) # Daha seyrek checkpoint
    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(' ' + "="*30 + " Evrim Tamamlandı " + "="*30)
    except Exception as e:
        logger.critical(f"Evrim sırasında kritik bir hata oluştu: {e}")
        # Hata durumunda son checkpoint'i yüklemeyi deneyebiliriz (ileriki sürüm?)
        return None # Şimdilik None dönelim

    # En iyi genomu işle
    if winner:
        logger.info(f'En iyi genom bulundu (Fitness: {winner.fitness:.6f}):')
        # logger.info(f' {winner}') # Çok uzun olabilir, log dosyasında kalsın

        # En iyi genomu kaydet
        winner_filename = "winner_genome_v4.pkl"
        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 Korelasyon İçin Detaylı Test Etme ---
        logger.info(" " + "="*25 + " En İyi Genom Detaylı Testi (Korelasyon) " + "="*25)
        try:
            winner_net = neat.nn.FeedForwardNetwork.create(winner, config)
            test_trials_final = 1000 # Final test için daha fazla deneme
            logger.info(f"En iyi ağ, farklı girdi çiftleriyle {test_trials_final} kez test ediliyor...")

            final_axis_values = np.linspace(0.0, 1.0, 5) # 5x5 = 25 nokta
            final_test_pairs = list(itertools.product(final_axis_values, repeat=2))
            final_total_weighted_error_sq_sum = 0.0

            hdr = f"{'Input (x1,x2)': <14} {'Tgt(PA0,PB0,Corr)': <22} {'Obs(PA0,PB0,Corr)': <22} {'Err^2(Comb)': <10}"
            logger.info(hdr)
            logger.info("-" * len(hdr))

            for input_pair in final_test_pairs:
                x1, x2 = input_pair
                targ_pA0 = target_PA0(x1, x2)
                targ_pB0 = target_PB0(x1, x2)
                targ_corr = target_Corr(x1, x2)

                count_A0, count_B0, count_A0B0 = 0, 0, 0
                for _ in range(test_trials_final):
                    output1, output2 = winner_net.activate(input_pair)
                    res_A = 0 if output1 < 0.5 else 1
                    res_B = 0 if output2 < 0.5 else 1
                    if res_A == 0: count_A0 += 1
                    if res_B == 0: count_B0 += 1
                    if res_A == 0 and res_B == 0: count_A0B0 += 1

                obs_pA0 = count_A0 / test_trials_final
                obs_pB0 = count_B0 / test_trials_final
                obs_pA0B0 = count_A0B0 / test_trials_final
                obs_corr = calculate_correlation(obs_pA0, obs_pB0, obs_pA0B0)

                error_pA0 = (obs_pA0 - targ_pA0) ** 2
                error_pB0 = (obs_pB0 - targ_pB0) ** 2
                error_corr = (obs_corr - targ_corr) ** 2
                weighted_error_sq = (W_PA0 * error_pA0 + W_PB0 * error_pB0 + W_CORR * error_corr) / TOTAL_WEIGHT
                final_total_weighted_error_sq_sum += weighted_error_sq

                tgt_str = f"({targ_pA0:.2f},{targ_pB0:.2f},{targ_corr:.2f})"
                obs_str = f"({obs_pA0:.2f},{obs_pB0:.2f},{obs_corr:.2f})"
                logger.info(f"({x1:.2f}, {x2:.2f})       {tgt_str: <22} {obs_str: <22} {weighted_error_sq:<10.5f}")

            final_avg_weighted_error_sq = final_total_weighted_error_sq_sum / len(final_test_pairs)
            final_rmse_equivalent = math.sqrt(final_avg_weighted_error_sq)
            logger.info("-" * len(hdr))
            logger.info(f"Final Ortalama Ağırlıklı Karesel Hata (MSE ~): {final_avg_weighted_error_sq:.6f}")
            logger.info(f"Final Kök Ort. Karesel Hata (~RMSE): {final_rmse_equivalent:.6f}")
            logger.info(f"Final Fitness Yaklaşımı (1 - ~RMSE): {1.0 - final_rmse_equivalent:.6f}")
            logger.info("-" * len(hdr))

        except Exception as e:
            logger.error(f"En iyi genom test edilirken hata oluştu: {e}")
            import traceback
            logger.error(traceback.format_exc()) # Hatanın detayını logla
    else:
        logger.warning("Test edilecek bir kazanan genom bulunamadı.")

    logger.info("="*70)
    logger.info("Quanta Simülatörü Adım 4 (Korelasyonlu Çıktılar) tamamlandı.")
    logger.info("="*70)
    return winner


if __name__ == '__main__':
    local_dir = os.path.dirname(os.path.abspath(__file__)) # Daha sağlam yol bulma
    config_path = os.path.join(local_dir, 'config-feedforward-v4.txt') # <-- V4 Config Dosyası

    if not os.path.exists(config_path):
        logger.critical(f"Yapılandırma dosyası bulunamadı: {config_path}")
    else:
        run_neat(config_path)