File size: 10,790 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_classifier_v6.py

import neat
import numpy as np
import os
import logging
import pickle
import datetime
import traceback

# --- YENİ: Scikit-learn importları ---
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix

# --- Loglama Ayarları ---
log_filename = f"quanta_log_v6_classifier_{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 Classifier Başlatılıyor (Sürüm 6 - Veri Seti ile Sınıflandırma)")
logger.info(f"Başlangıç Zamanı: {datetime.datetime.now()}")
logger.info("="*70)

# --- Global Değişkenler (Veri Seti) ---
# Not: Global değişkenler büyük projelerde önerilmez, ancak NEAT'in
# fitness fonksiyonuna kolayca veri aktarmak için bu örnekte kullanılmıştır.
# Daha büyük projelerde sınıf yapısı veya functools.partial tercih edilebilir.
X_train, X_test, y_train, y_test = None, None, None, None

# --- Veri Seti Oluşturma ve Hazırlama ---
def prepare_data(n_samples=500, n_features=2, n_informative=2, n_redundant=0, random_state=42):
    """

    Scikit-learn kullanarak sentetik bir sınıflandırma veri seti oluşturur ve böler.

    """
    global X_train, X_test, y_train, y_test
    logger.info(f"{n_samples} örnekli, {n_features} özellikli sentetik veri seti oluşturuluyor...")

    X, y = make_classification(
        n_samples=n_samples,
        n_features=n_features,
        n_informative=n_informative,
        n_redundant=n_redundant,
        n_clusters_per_class=1, # Sınıf başına küme sayısı
        flip_y=0.05, # Gürültü ekle (etiketlerin %5'ini ters çevir)
        random_state=random_state
    )

    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.3, random_state=random_state, stratify=y # Sınıf oranlarını koru
    )
    logger.info(f"Veri seti bölündü: {len(X_train)} eğitim, {len(X_test)} test örneği.")
    logger.info(f"Girdi Boyutu (Özellik Sayısı): {X_train.shape[1]}")
    logger.info(f"Sınıf Dağılımı (Eğitim): {np.bincount(y_train)}")


# --- NEAT Fitness Fonksiyonu (Sınıflandırma için) ---
def eval_genomes_classification(genomes, config):
    """

    Genomları eğitim verisi üzerindeki sınıflandırma doğruluğuna göre değerlendirir.

    """
    global X_train, y_train # Global eğitim verisini kullan

    if X_train is None or y_train is None:
         logger.error("Eğitim verisi yüklenmemiş! Fitness hesaplanamıyor.")
         # Tüm genomlara düşük fitness ver
         for genome_id, genome in genomes:
              genome.fitness = -1.0
         return

    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 = -1.0 # Ceza
            continue

        correct_predictions = 0
        # Eğitim verisi üzerinde tahmin yap
        for xi, yi in zip(X_train, y_train):
            try:
                output = net.activate(xi)
                # Binary sınıflandırma: Tek sigmoid çıkışı 0.5 eşiği ile yorumla
                prediction = 1 if output[0] >= 0.5 else 0
                if prediction == yi:
                    correct_predictions += 1
            except Exception as e:
                # logger.warning(f"G:{genome_id} Aktivasyon hatası: {e}")
                pass # Hatalı aktivasyonları sayma

        # Fitness = Doğruluk Oranı (Accuracy)
        accuracy = correct_predictions / len(y_train)
        genome.fitness = accuracy
        # logger.debug(f"Genome {genome_id}: Accuracy = {accuracy:.4f}, Fitness = {genome.fitness:.4f}")


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

    NEAT evrimini başlatır, yönetir ve sonucu değerlendirir.

    """
    logger.info(f"NEAT yapılandırması yükleniyor: {config_file}")
    config = None
    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}")
        # Yapılandırmadaki girdi sayısı ile veri setinin özellik sayısını kontrol et
        if config.genome_config.num_inputs != X_train.shape[1]:
             logger.error(f"Yapılandırma hatası: Config'deki girdi sayısı ({config.genome_config.num_inputs}) veri özelliği sayısı ({X_train.shape[1]}) ile eşleşmiyor!")
             return None
        # Binary sınıflandırma için çıktı sayısını kontrol et
        if config.genome_config.num_outputs != 1:
             logger.warning(f"Yapılandırma uyarısı: Binary sınıflandırma için çıktı sayısı ({config.genome_config.num_outputs}) genellikle 1 olmalıdır.")

    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)) # Konsola detaylı raporlama
    stats = neat.StatisticsReporter() # İstatistikleri toplar (ileride görselleştirme için)
    p.add_reporter(stats)
    checkpoint_prefix = 'neat-checkpoint-v6-'
    p.add_reporter(neat.Checkpointer(generation_interval=20, filename_prefix=checkpoint_prefix)) # 20 nesilde bir checkpoint
    logger.info(f"Checkpoint dosyaları '{checkpoint_prefix}*' olarak kaydedilecek.")

    logger.info(f"Evrim başlıyor (Maksimum {generations} nesil)...")
    winner = None
    try:
        # Fitness fonksiyonunu (eval_genomes_classification) çalıştır
        winner = p.run(eval_genomes_classification, 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())
        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

    # En iyi genomu işle
    if winner:
        logger.info(f'En iyi genom bulundu/kullanıldı (Fitness - Eğitim Doğruluğu: {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_classifier_v6.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 Genomun TEST SETİ Üzerinde Değerlendirilmesi ---
        logger.info(" " + "="*20 + " En İyi Genomun Test Seti Performansı " + "="*20)
        try:
            if config is None:
                 logger.error("Yapılandırma yüklenemediği için final test atlanıyor.")
                 raise RuntimeError("Config object is None.")

            winner_net = neat.nn.FeedForwardNetwork.create(winner, config)

            # Test seti üzerinde tahmin yap
            y_pred = []
            logger.info(f"Test seti ({len(X_test)} örnek) üzerinde tahminler yapılıyor...")
            for xi in X_test:
                output = winner_net.activate(xi)
                prediction = 1 if output[0] >= 0.5 else 0
                y_pred.append(prediction)

            # Performans metriklerini hesapla ve raporla
            accuracy_test = accuracy_score(y_test, y_pred)
            conf_matrix = confusion_matrix(y_test, y_pred)
            class_report = classification_report(y_test, y_pred)

            logger.info(f"\n--- Test Seti Sonuçları ---")
            logger.info(f"Doğruluk (Accuracy): {accuracy_test:.4f}")
            logger.info(f"\nKarışıklık Matrisi (Confusion Matrix):\n{conf_matrix}")
            logger.info(f"\nSınıflandırma Raporu:\n{class_report}")
            logger.info("---------------------------\n")

        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 Classifier Adım 6 (Veri Seti ile Sınıflandırma) tamamlandı.")
    logger.info(f"Bitiş Zamanı: {datetime.datetime.now()}")
    logger.info("="*70)
    # return winner


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

    if not os.path.exists(config_path):
        logger.critical(f"Yapılandırma dosyası bulunamadı: {config_path}")
    else:
        try:
            # 1. Veri setini hazırla
            prepare_data(n_samples=1000, n_features=2, n_informative=2, random_state=123)

            # 2. NEAT evrimini çalıştır (Config'deki eşiğe ulaşana kadar veya max nesil)
            # MAX_GENERATIONS sabitini burada da tanımlayabiliriz:
            MAX_GENERATIONS_RUN = 150 # Deneme için 150 nesil, gerekirse artırılır
            run_neat(config_path, generations=MAX_GENERATIONS_RUN)

        except ImportError as ie:
             logger.critical(f"Gerekli kütüphane bulunamadı: {ie}")
             logger.critical("Lütfen 'pip install scikit-learn numpy neat-python' komutu ile kurun.")
        except Exception as main_e:
            logger.critical(f"Ana program akışında beklenmedik bir hata oluştu: {main_e}")
            logger.error(traceback.format_exc())