File size: 10,619 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
# quanta_sim_v2_input_dep.py

import neat
import numpy as np
import os
import logging
import pickle
import random
import math
import datetime
# Görselleştirme importları YOKTUR.

# --- Loglama Ayarları ---
log_filename = f"quanta_log_v2_input_dep_{datetime.datetime.now():%Y%m%d_%H%M%S}.log" # <-- V2 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 2 - Girdiye Bağlı Olasılık)") # <-- GÜNCELLENDİ
logger.info("="*50)

# --- Simülasyon Parametreleri ---
# TARGET_PROB_0 artık sabit değil, girdiye göre hesaplanacak.
# Bu yüzden aşağıdaki satırları kaldırabilir veya yorum satırı yapabiliriz.
# TARGET_PROB_0 = 0.7
# TARGET_PROB_1 = 1.0 - TARGET_PROB_0

NUM_TEST_INPUTS = 6 # Farklı girdi değerlerini test etmek için kullanılacak girdi sayısı
NUM_TRIALS_PER_INPUT = 20 # Her bir girdi değeri için yapılacak deneme sayısı
# Toplam deneme sayısı = NUM_TEST_INPUTS * NUM_TRIALS_PER_INPUT (örn: 6 * 20 = 120)

MAX_GENERATIONS = 100 # <-- Gerekirse artırılabilir, problem biraz daha zorlaştı.
FITNESS_THRESHOLD = 0.98 # <-- Hedef biraz daha zor olduğu için eşiği hafifçe düşürebiliriz (isteğe bağlı)

logger.info(f"Test Edilecek Girdi Sayısı: {NUM_TEST_INPUTS}")
logger.info(f"Girdi Başına Deneme Sayısı: {NUM_TRIALS_PER_INPUT}")
logger.info(f"Toplam Değerlendirme Denemesi/Genom: {NUM_TEST_INPUTS * NUM_TRIALS_PER_INPUT}")
logger.info(f"Maksimum Nesil Sayısı: {MAX_GENERATIONS}")
logger.info(f"Fitness Eşiği: {FITNESS_THRESHOLD}")

# --- YENİ: Hedef Olasılık Fonksiyonu ---
def calculate_target_prob0(input_value):
    """

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

    Lineer Fonksiyon: target_P0(x) = 0.1 + 0.8 * x

    """
    return 0.1 + 0.8 * input_value

logger.info(f"Hedef P(0) Fonksiyonu: P(0|x) = 0.1 + 0.8 * x")

# --- NEAT Fitness Fonksiyonu ---
# (Bu fonksiyon Sürüm 2 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ı girdiler için hedeflenen olasılıkları

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

    """
    # Test edilecek girdi değerlerini belirleyelim (0 ile 1 arasında eşit aralıklı)
    test_inputs = np.linspace(0.0, 1.0, NUM_TEST_INPUTS)
    # Örnek: NUM_TEST_INPUTS=6 ise -> [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]

    for genome_id, genome in genomes:
        genome.fitness = 0.0 # Başlangıç fitness'ı sıfırla
        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 = -10.0 # Daha büyük ceza verilebilir
            continue

        total_squared_error = 0.0

        # Her bir test girdisi için ağı değerlendir
        for net_input_val in test_inputs:
            net_input = (net_input_val,) # Girdiyi tuple formatına getir
            target_prob_0 = calculate_target_prob0(net_input_val) # Bu girdi için hedef P(0)

            count_0 = 0
            # Her girdi için N kez test et
            for _ in range(NUM_TRIALS_PER_INPUT):
                try:
                    output = net.activate(net_input)
                    # 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 {net_input_val:.2f} ağ aktivasyonunda hata: {e}")
                    # Hata durumunda bu denemeyi atla veya penaltı ver
                    pass # Şimdilik sadece atlıyoruz

            # Gözlemlenen olasılığı hesapla
            if NUM_TRIALS_PER_INPUT > 0:
                observed_prob_0 = count_0 / NUM_TRIALS_PER_INPUT
            else:
                observed_prob_0 = 0.5 # Sıfıra bölme hatasını önle (pek olası değil ama güvenli)

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

            # logger.debug(f"  Genome {genome_id}, Input {net_input_val:.2f}: TargetP0={target_prob_0:.3f}, ObsP0={observed_prob_0:.3f}, Error^2={error:.4f}")

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

        # Fitness'ı hesapla: Hata ne kadar küçükse, fitness o kadar yüksek (Maks 1.0)
        # Hatanın karekökünü (RMSE gibi) 1'den çıkaralım
        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:
        config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
                             neat.DefaultSpeciesSet, neat.DefaultStagnation,
                             config_file)
        # Fitness eşiğini config dosyasından değil, koddan alalım
        config.fitness_threshold = FITNESS_THRESHOLD # <-- V2 için güncel eşik
        logger.info(f"Yapılandırma yüklendi. 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)

    # İstatistikleri ve loglamayı ayarlama
    p.add_reporter(neat.StdOutReporter(True)) # Konsola raporlama
    # stats = neat.StatisticsReporter() # Görselleştirme YOK, bu yüzden buna gerek yok
    # p.add_reporter(stats)

    # Checkpoint (yedek) alma
    checkpoint_prefix = 'neat-checkpoint-v2-' # <-- V2 için farklı prefix
    p.add_reporter(neat.Checkpointer(10, filename_prefix=checkpoint_prefix))
    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 göster/kaydet
    if winner:
        logger.info(f'En iyi genom bulundu (Fitness: {winner.fitness:.6f}):')
        logger.info(f' {winner}') # Konsola detaylı genom bilgisini yazdırır

        # En iyi genomu dosyaya kaydet (pickle ile)
        winner_filename = "winner_genome_v2.pkl" # <-- V2 için farklı 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 Farklı Girdilerle Test Etme ---
        logger.info(" " + "="*20 + " En İyi Genom Detaylı Testi " + "="*20)
        try:
            winner_net = neat.nn.FeedForwardNetwork.create(winner, config)
            test_trials_final = 1000 # Her girdi için final test deneme sayısı
            logger.info(f"En iyi ağ, farklı girdilerle {test_trials_final} kez test ediliyor...")

            test_inputs_final = np.linspace(0.0, 1.0, 11) # Daha fazla ara noktayla test edelim (0.0, 0.1, ..., 1.0)
            final_total_error_sq = 0.0
            results = []

            for net_input_val in test_inputs_final:
                target_p0 = calculate_target_prob0(net_input_val)
                net_input = (net_input_val,)
                count_0 = 0
                for _ in range(test_trials_final):
                    output = winner_net.activate(net_input)
                    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
                results.append({
                    "input": net_input_val,
                    "target_p0": target_p0,
                    "observed_p0": observed_p0,
                    "error_sq": error_sq
                })
                logger.info(f"  Input={net_input_val: <4.2f} -> TargetP(0)={target_p0: <5.3f}, ObservedP(0)={observed_p0: <5.3f}, Error^2={error_sq:.5f}")

            final_avg_error_sq = final_total_error_sq / len(test_inputs_final)
            final_rmse = math.sqrt(final_avg_error_sq)
            logger.info("-" * 40)
            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("-" * 40)

        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 2 (Girdiye Bağlı Olasılık) tamamlandı.")
    logger.info("="*50)
    return winner


if __name__ == '__main__':
    # Yapılandırma dosyasının yolu (Python betiği ile aynı klasörde olduğunu varsayar)
    local_dir = os.path.dirname(__file__)
    # V1'deki config dosyasını kullanıyoruz, ismi aynı kalabilir.
    config_path = os.path.join(local_dir, 'config-feedforward.txt')

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