File size: 13,150 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
288
289
290
# quanta_sim_v5_nonlinear_simple.py

import neat
import numpy as np
import os
import logging
import pickle
import random
import math
import datetime
import itertools
import traceback # Hata ayıklama için

# --- Loglama Ayarları ---
log_filename = f"quanta_log_v5_nonsimple_{datetime.datetime.now():%Y%m%d_%H%M%S}.log"
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("="*70)
logger.info("Quanta Simülatörü Başlatılıyor (Sürüm 5 - Non-Linear Hedefler + Basitlik)")
logger.info(f"Başlangıç Zamanı: {datetime.datetime.now()}")
logger.info("="*70)

# --- Simülasyon Parametreleri ---
NUM_TEST_VALUES_PER_AXIS = 4 # Değerlendirme için ızgara boyutu (4x4=16 nokta)
NUM_TRIALS_PER_PAIR = 30     # Her girdi çifti için deneme sayısı

MAX_GENERATIONS = 300 # Zor problem için artırılmış nesil sayısı

# Fitness fonksiyonundaki hata ağırlıkları
W_PA0 = 1.0
W_PB0 = 1.0
W_CORR = 1.5 # Korelasyon genellikle daha zordur
TOTAL_WEIGHT = W_PA0 + W_PB0 + W_CORR

# Karmaşıklık Cezası Ağırlıkları (Deneme yanılma ile ayarlanabilir!)
COMPLEXITY_PENALTY_WEIGHT_NODE = 0.001
COMPLEXITY_PENALTY_WEIGHT_CONN = 0.0005

num_test_points = NUM_TEST_VALUES_PER_AXIS ** 2
total_trials_per_genome = num_test_points * NUM_TRIALS_PER_PAIR

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 Hata Ağırlıkları: P(A0)={W_PA0:.1f}, P(B0)={W_PB0:.1f}, Corr={W_CORR:.1f}")
logger.info(f"Karmaşıklık Ceza Ağırlıkları: Düğüm={COMPLEXITY_PENALTY_WEIGHT_NODE}, Bağlantı={COMPLEXITY_PENALTY_WEIGHT_CONN}")

# --- Doğrusal Olmayan Hedef Fonksiyonları ---
def target_PA0(x1, x2):
    # P(A=0) hedefi (0.1 ile 0.9 arasında salınır)
    return 0.5 + 0.4 * math.sin(math.pi * x1)

def target_PB0(x1, x2):
    # P(B=0) hedefi (0.1 ile 0.9 arasında salınır)
    return 0.5 + 0.4 * math.cos(math.pi * x2)

def target_Corr(x1, x2):
    # Hedef Korelasyon (0 ile -0.8 arasında salınır)
    return -0.8 * math.sin(math.pi * x1 * x2)

logger.info(f"Hedef P(A=0|x1,x2) = 0.5 + 0.4*sin(pi*x1)")
logger.info(f"Hedef P(B=0|x1,x2) = 0.5 + 0.4*cos(pi*x2)")
logger.info(f"Hedef Corr(A,B|x1,x2) = -0.8*sin(pi*x1*x2)")

# --- Korelasyon Hesaplama Yardımcı Fonksiyonu ---
def calculate_correlation(pA0, pB0, pA0B0, epsilon=1e-9):
    pA1 = 1.0 - pA0
    pB1 = 1.0 - pB0
    covariance = pA0B0 - pA0 * pB0
    varA = pA0 * pA1
    varB = pB0 * pB1
    denominator = math.sqrt((varA + epsilon) * (varB + epsilon))
    if denominator < epsilon: return 0.0
    correlation = covariance / denominator
    return max(-1.0, min(1.0, correlation))

# --- NEAT Fitness Fonksiyonu ---
def eval_genomes(genomes, config):
    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 # Başlangıç fitness
        try:
            net = neat.nn.FeedForwardNetwork.create(genome, config)
        except Exception as e:
            logger.error(f"Genome {genome_id} ağ oluşturma hatası: {e}")
            genome.fitness = -30.0
            continue

        total_weighted_error_sum = 0.0

        # Performans Değerlendirme
        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, count_B0, count_A0B0 = 0, 0, 0
            for _ in range(NUM_TRIALS_PER_PAIR):
                try:
                    output1, output2 = 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
                except Exception as e:
                    # Aktivasyon hatalarını loglamak çok fazla çıktı üretebilir
                    pass

            # Gözlemlenen değerler
            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

            obs_corr = calculate_correlation(obs_pA0, obs_pB0, obs_pA0B0)

            # Hatalar
            error_pA0 = (obs_pA0 - targ_pA0) ** 2
            error_pB0 = (obs_pB0 - targ_pB0) ** 2
            error_corr = (obs_corr - targ_corr) ** 2
            weighted_error = (W_PA0 * error_pA0 + W_PB0 * error_pB0 + W_CORR * error_corr) / TOTAL_WEIGHT
            total_weighted_error_sum += weighted_error

        # Temel fitness
        average_weighted_error = total_weighted_error_sum / len(test_input_pairs)
        base_fitness = max(0.0, 1.0 - math.sqrt(average_weighted_error))

        # Karmaşıklık Cezası
        num_nodes = len(genome.nodes)
        num_connections = len(genome.connections)
        complexity_penalty = (COMPLEXITY_PENALTY_WEIGHT_NODE * num_nodes +
                              COMPLEXITY_PENALTY_WEIGHT_CONN * num_connections)

        # Final fitness
        final_fitness = max(0.0, base_fitness - complexity_penalty)
        genome.fitness = final_fitness

# --- NEAT Çalıştırma Fonksiyonu ---
def run_neat(config_file):
    logger.info(f"NEAT yapılandırması yükleniyor: {config_file}")
    config = None # Önce None olarak tanımla
    try:
        config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
                             neat.DefaultSpeciesSet, neat.DefaultStagnation,
                             config_file)
        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-v5-'
    # Checkpointer her 25 nesilde bir kaydetsin
    p.add_reporter(neat.Checkpointer(25, filename_prefix=checkpoint_prefix))
    logger.info(f"Checkpoint dosyaları '{checkpoint_prefix}*' olarak kaydedilecek.")

    logger.info(f"Evrim başlıyor (Maksimum {MAX_GENERATIONS} nesil)...")
    winner = None # Önce None olarak tanımla
    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}")
        logger.error(traceback.format_exc()) # Tam hata izini logla
        # Hata durumunda bile popülasyonun en iyisini almayı deneyebiliriz (checkpoint yoksa)
        if not winner and p and p.best_genome:
             logger.warning("Evrim hata ile durdu, bulunan son en iyi genom kullanılıyor.")
             winner = p.best_genome
        # return None # Hata durumunda çıkmak yerine devam etmeyi deneyelim

    # En iyi genomu işle (hata olsa bile 'winner' dolu olabilir)
    if winner:
        logger.info(f'En iyi genom bulundu/kullanıldı (Final Fitness: {winner.fitness:.6f}):')
        num_nodes = len(winner.nodes)
        num_connections = len(winner.connections)
        logger.info(f'Kazanan Karmaşıklığı: {num_nodes} Düğüm, {num_connections} Bağlantı')

        # En iyi genomu kaydet
        winner_filename = "winner_genome_v5.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}")

        # --- Final Test ---
        logger.info(" " + "="*20 + " En İyi Genom Detaylı Testi (Non-Linear) " + "="*20)
        try:
            # Eğer config yüklenmemişse (ilk hatada), testi atla
            if config is None:
                 logger.error("Yapılandırma yüklenemediği için final test atlanıyor.")
                 raise RuntimeError("Config object is None, cannot create network.")

            winner_net = neat.nn.FeedForwardNetwork.create(winner, config)
            test_trials_final = 1000 # Final test için deneme sayısı
            logger.info(f"En iyi ağ, farklı girdi çiftleriyle {test_trials_final} kez test ediliyor...")

            final_axis_values = np.linspace(0.0, 1.0, 6) # 6x6 = 36 nokta test
            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"Not: Bu RMSE, karmaşıklık cezasını içermez, sadece performans ölçümüdür.")
            logger.info("-" * len(hdr))

        except Exception as e:
            logger.error(f"En iyi genom test edilirken hata oluştu: {e}")
            logger.error(traceback.format_exc())
    else:
        logger.warning("Evrim sonunda test edilecek bir kazanan genom bulunamadı.")

    logger.info("="*70)
    logger.info("Quanta Simülatörü Adım 5 (Non-Linear + Basitlik) tamamlandı.")
    logger.info(f"Bitiş Zamanı: {datetime.datetime.now()}")
    logger.info("="*70)
    # return winner # Fonksiyondan bir şey döndürmeye gerek yoksa kaldırılabilir


if __name__ == '__main__':
    # Betiğin bulunduğu dizini al
    local_dir = os.path.dirname(os.path.abspath(__file__))
    # V5 config dosyasının tam yolunu oluştur
    config_path = os.path.join(local_dir, 'config-feedforward-v5.txt')

    if not os.path.exists(config_path):
        logger.critical(f"Yapılandırma dosyası bulunamadı: {config_path}")
        logger.critical(f"Lütfen '{os.path.basename(config_path)}' dosyasını betikle aynı klasöre koyun.")
    else:
        try:
            run_neat(config_path)
        except Exception as main_e:
            logger.critical(f"Ana program akışında beklenmedik bir hata oluştu: {main_e}")
            logger.error(traceback.format_exc())