/
Mihaham
/
CNN-NEAT
Обзор
Документация
Войти
/
Mihaham
/
CNN-NEAT
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
docs/pdf_builder/ch04_algorithm.py
324 строки
18 KB
MihahamYT
docs: thesis report builders and Jupyter notebooks
09 июн 2026, 17:18
09 июн 2026, 17:18
2e0c962
Код
Авторство
О чём код?
"""Глава 4. Алгоритм CNN-NEAT и гибридный пайплайн (Фазы 1–7).""" from __future__ import annotations from reportlab.lib.units import cm from reportlab.platypus import PageBreak from ._helpers import ( bullet, caption, code_block, enum, fig_placeholder, h1, h2, h3, make_table, math_formula, p, sp, ) from ._symbols import ( NDASH, MDASH, TIMES, GEQ, LEQ, APPROX, SUM, SQRT, CDOT, ARROW, DARROW, ELEM, UNION, SUBSET, FLOOR_L, FLOOR_R, CEIL_L, CEIL_R, RR, α, β, σ, ρ, τ, θ, μ, Δ, DELTA, SUP2, ) def build() -> list: S = [] S += [h1("Глава 4. Алгоритм CNN-NEAT и гибридный пайплайн")] # ───────────────────────────────────────────────────────────────────────── # 4.1 Операторы мутации # ───────────────────────────────────────────────────────────────────────── S += [ h2("4.1 Операторы мутации CNN-NEAT"), make_table( ["Оператор", "Тип", "Действие и параметры"], [ ["split", "Структурная", "Ребро (<i>u</i>" + ARROW + "<i>w</i>) disable; вставка <i>n</i>: " "<i>u</i>" + ARROW + "<i>n</i>" + ARROW + "<i>w</i>; " "<b>s</b>(<i>n</i>) = " + FLOOR_L + "(<b>s</b>(<i>u</i>)+<b>s</b>(<i>w</i>))/2" + FLOOR_R], ["without_split", "Структурная", "Добавление ребра (<i>u</i>,<i>w</i>) при соблюдении DAG-инвариантов"], ["mutate_weights", "Весовая", σ + "' = clip(" + σ + " + N(0," + τ + SUP2 + "), [" + NDASH + "8, 8]);" " rate=0,8, " + τ + "=0,5; вероятность полного resample = 0,1"], ["aggressive_mutate", "Весовая", "rate=1,0, " + τ + "=1,5 (применяется к «застрявшим» особям)"], ["remove_node", "Структурная", "Удаление скрытой вершины и всех инцидентных рёбер"], ["crossover", "Рекомбинация", "Union enabled-рёбер двух родителей (§4.2)"], ], col_widths=[3.5 * cm, 3 * cm, 10.5 * cm], ), caption("Таблица 4.1 " + NDASH + " Операторы мутации CNN-NEAT"), sp(0.3), p("Полная мутация весового масштаба " + σ + ":"), math_formula( σ + "' = U(-2, 2)\u2003с вер. 0,1;\u2003" + σ + "' = clip(" + σ + " + N(0, " + τ + "<sup>2</sup>), [" + σ + "<sub>min</sub>, " + σ + "<sub>max</sub>])\u2003с вер. 0,9", "4.1", ), p( "где " + τ + " = 0,5; " + σ + "<sub>min</sub> = " + NDASH + "8, " + σ + "<sub>max</sub> = 8. " "При aggressive_mutate: " + τ + " = 1,5, rate = 1,0." ), ] # 4.2 Скрещивание S += [ h2("4.2 Скрещивание (Crossover)"), p( "Union crossover двух родителей <i>A</i> и <i>B</i>. " "Рёбра <i>B</i> транслируются в пространство ID вершин <i>A</i>:" ), math_formula( "<i>E</i><sub>child</sub> = <i>E<sub>A</sub></i> " + UNION + " {(<i>u</i>, <i>w</i>+" + DELTA + ") : (<i>u</i>,<i>w</i>) " + ELEM + " <i>E<sub>B</sub></i>},\u2003" + DELTA + " = max <i>V<sub>A</sub></i> + 1", "4.2", ), p( "Ребро из <i>B</i> включается только если ключ " "(<i>u</i>, <i>w</i>+" + DELTA + ") не занят в <i>A</i>. " "<b>Примечание.</b> В данной реализации crossover не учитывает " "относительный fitness родителей (в отличие от оригинального NEAT [1]), " "что является направлением для будущего улучшения." ), ] # 4.3 Отбор S += [ h2("4.3 Отбор и репродукция"), p("Elite selection:"), math_formula( ELEM + " = top-" + CEIL_L + ρ + "<i>P</i>" + CEIL_R + " по <i>f</i>(<i>G</i>);\u2003tie-break: меньше |<i>E</i><sub>enabled</sub>|", "4.3", ), p("где " + ρ + " = elite_percent = 0,2; <i>P</i> = population_size."), sp(0.2), p("<b>Формирование следующего поколения:</b>"), enum(1, "Каждый геном из элиты " + ARROW + " потомки всеми операторами мутации."), enum(2, "Каждый кандидат проверяется на уникальность structure_hash."), enum(3, "Fallback: случайные сети при нехватке уникальных топологий."), enum(4, "Archive mixing: часть потомков из исторического архива (по fitness)."), ] # 4.4 Полный цикл эволюции S += [ h2("4.4 Полный цикл эволюции (Фаза 1 пайплайна)"), p( "Фаза 1 гибридного пайплайна полностью реализована в виде " "следующего алгоритма:" ), ] S += code_block([ "Вход: ExperimentConfig cfg, метка класса k", "Выход: лучший геном G*_k для класса k", "───────────────────────────────────────────────────────────", "1. seed(cfg.seed + k); configure_gpu()", "2. D_val ← build_val_loader(cfg, class=k) # OvR", "3. P ← random_unique_population(cfg.population_size)", "4. для g = 0, …, cfg.num_generations - 1:", " a. f_i ← evaluate_fitness(G_i, D_val) для всех G_i", " b. set_fitness(P, {f_i}); save_generation(g, P)", " c. если periodic_gd_enabled и g mod every = 0:", " run_periodic_gd(P, D_train, cfg)", " d. если elite_refinement_enabled:", " ε ← elite_refinement(P, D_val, D_train, D_test, cfg)", " иначе:", " ε ← select_elite(P, ρ)", " e. P ← evolve(ε, cfg.population_size)", "5. вернуть G*_k = argmax_{G ∈ P} f(G)", ]) S.append(sp(0.3)) # ───────────────────────────────────────────────────────────────────────── # 4.5 ГИБРИДНЫЙ ПАЙПЛАЙН (Фазы 1–7) # ───────────────────────────────────────────────────────────────────────── S += [ h2("4.5 Гибридный пайплайн CNN-NEAT + GBM (Фазы 1–7)"), p( "После завершения нейроэволюции (Фаза 1) запускаются " "шесть дополнительных фаз. Полный пайплайн описывается ниже." ), h3("4.5.1 Фаза 2. Извлечение признаков"), p( "Для каждого класса <i>k</i> " + ELEM + " {0,…,9} " "из лучшего генома G*<sub>k</sub> извлекается " "вектор признаков для всего обучающего набора:" ), math_formula( "<b>F</b><sub>k</sub> = flatten(G*<sub>k</sub>(<b>X</b><sub>train</sub>)) " + ELEM + " " + RR + "<sup>N<sub>train</sub> " + TIMES + " d<sub>k</sub></sup>", "4.4", ), p( "где <i>d<sub>k</sub></i> = 3 " + CDOT + " <i>H</i><sub>out</sub> " + CDOT + " <i>W</i><sub>out</sub> " + NDASH + " размерность признакового вектора " "(зависит от output_image_size генома). " "При output_image_size = (1,1): <i>d<sub>k</sub></i> = 3. " "Более богатые признаковые пространства получаются при " "output_image_size = (4,4) (<i>d<sub>k</sub></i> = 48) " "или (8,8) (<i>d<sub>k</sub></i> = 192)." ), p( "Аналогично извлекаются признаки для val- и test-подмножеств. " "Признаки кэшируются на диске в формате numpy .npy для переиспользования. " "Нормализация признаков (Z-score) применяется опционально." ), h3("4.5.2 Фаза 3. Обучение GBM по классам"), p( "Для каждого класса <i>k</i> обучается бинарный GBM-классификатор " "в схеме «один против всех»:" ), math_formula( "gbm<sub>k</sub> = GBM.fit(<b>F</b><sub>k</sub><sup>train</sup>, " "<b>y</b><sub>k</sub><sup>train</sup>),\u2003" "<i>y</i><sub>k,i</sub> = [class(i) = k]", "4.5", ), p( "Гиперпараметры GBM для CIFAR-10 (рекомендуемые начальные значения):" ), make_table( ["Параметр", "XGBoost", "LightGBM", "Пояснение"], [ ["n_estimators", "500", "500", "Число деревьев"], ["max_depth", "6", "7", "Глубина дерева"], ["learning_rate", "0,05", "0,05", "Шаг градиентного спуска"], ["subsample", "0,8", "0,8", "Доля образцов на дерево"], ["colsample", "0,8", "0,8", "Доля признаков на дерево"], ["scale_pos_weight","9,0", "9,0", "Вес позитивных (дисбаланс 1:9)"], ["eval_metric", "auc", "auc", "Метрика ранней остановки"], ["early_stopping", "50", "50", "Раундов без улучшения"], ], col_widths=[4 * cm, 3 * cm, 3 * cm, 7 * cm], ), caption("Таблица 4.2 " + NDASH + " Гиперпараметры GBM для фазы поклассового обучения"), p( "Дисбаланс классов в OvR (~10% позитивных) компенсируется " "параметром scale_pos_weight = (N_neg / N_pos) " + APPROX + " 9. " "Отбор гиперпараметров проводится методом Bayesian Optimization " "(Optuna, 50 trials) по ROC-AUC на val." ), h3("4.5.3 Фаза 4. Отбор лучших пар"), p( "Ранжирование пар (G*<sub>k</sub>, gbm<sub>k</sub>) " "по метрике на val-подмножестве:" ), math_formula( "score<sub>k</sub> = 0,65 " + CDOT + " ROC-AUC<sub>val,k</sub> " "+ 0,35 " + CDOT + " bal_acc<sub>val,k</sub>", "4.6", ), p( "Для каждого класса выбирается пара с наивысшим score<sub>k</sub>. " "При наличии нескольких геномов (например, при multi-start эволюции) " "для каждого обучается отдельный GBM, и отбирается лучший. " "Это позволяет совместно оптимизировать архитектуру экстрактора " "и гиперпараметры классификатора." ), h3("4.5.4 Фаза 5. Ансамблирование"), p( "10 бинарных классификаторов объединяются в многоклассовый " "через нормировку логитов:" ), math_formula( "<i>p</i>(<i>y</i>=<i>k</i>|<b>x</b>) = " "e<sup><i>s<sub>k</sub></i></sup> / " + SUM + "<sub><i>j</i>=0</sub><sup>9</sup>" " e<sup><i>s<sub>j</sub></i></sup>," "\u2003" "<i>s<sub>k</sub></i> = gbm<sub>k</sub>.predict_proba(<b>f<sub>k</sub></b>)[:, 1]", "4.7", ), p( "Альтернативно: взвешенное голосование с весами, " "равными ROC-AUC<sub>val,k</sub>. " "Оба варианта тестируются в эксперименте." ), h3("4.5.5 Фаза 6. Fine-tune CNN (градиентный спуск на ансамбле)"), p( "Финальное дообучение ансамбля для минимизации 10-классовой " "кросс-энтропии. Топологии геномов заморожены; " "оптимизируются только весa Conv2d и выходной линейный слой." ), make_table( ["Компонент", "Описание"], [ ["Архитектура", "10 геномов-экстракторов " + ARROW + " concat/pool " + ARROW + " Linear(10d, 10)"], ["Потери", "CrossEntropyLoss(input=logits, target=class_idx)"], ["Оптимизатор", "Adam, lr=10<sup>" + NDASH + "3</sup>, weight_decay=10<sup>" + NDASH + "4</sup>"], ["Расписание", "Cosine Annealing с T_max = num_epochs"], ["Аугментации", "RandomCrop(32, padding=4), RandomHorizontalFlip"], ["Эпохи", "50 эпох, early stopping по val accuracy (patience=10)"], ["AMP", "torch.cuda.amp.autocast() для GPU"], ], col_widths=[4 * cm, 13 * cm], ), caption("Таблица 4.3 " + NDASH + " Конфигурация Fine-tune CNN"), math_formula( "<i>L</i><sub>CE</sub> = " + NDASH + "(1/<i>B</i>) " + SUM + "<sub><i>b</i></sub> " + SUM + "<sub><i>k</i>=0..9</sub>" " <i>y</i><sub><i>b,k</i></sub> log <i>p̂</i><sub><i>b,k</i></sub>", "4.8", ), p( "Инициализация выходного слоя " + NDASH + " Xavier uniform. " "Заморозка весов геномов на первых 10 эпохах (feature extractor warm-up). " "После разморозка и совместное обучение со скоростью 10<sup>" + NDASH + "4</sup>." ), h3("4.5.6 Фаза 7. Сравнение с ResNet"), p( "Итоговая оценка гибридного пайплайна сравнивается с " "ResNet-18, ResNet-34 и ResNet-50, " "обученными с нуля (from scratch) на полном CIFAR-10 " "при идентичных условиях аугментации и оптимизации. " "Подробнее " + NDASH + " в Главе 8." ), sp(0.3), h2("4.6 Схема полного пайплайна"), ] S += code_block([ "ГИБРИДНЫЙ ПАЙПЛАЙН CNN-NEAT + GBM", "═══════════════════════════════════════════════════════════", "Фаза 1: CNN-NEAT эволюция (отдельно для каждого класса k=0..9)", " → G*_k = best_genome(class=k)", "", "Фаза 2: Извлечение признаков", " F_k = flatten(G*_k(X)) для X ∈ {train, val, test}", "", "Фаза 3: GBM-обучение per class", " gbm_k = XGBoost.fit(F_k^train, y_k^train) # OvR", "", "Фаза 4: Отбор лучших пар (G*_k, gbm_k) по val score_k", "", "Фаза 5: Ансамблирование", " p(y=k|x) = softmax([s_0(x), …, s_9(x)])", "", "Фаза 6: Fine-tune CNN (Adam, 50 эпох, CE-loss 10 классов)", " топологии заморожены; оптимизируются веса Conv2d", "", "Фаза 7: Сравнение с ResNet-18/34/50 (top-1 acc, AUC, время)", "═══════════════════════════════════════════════════════════", ]) S.append(PageBreak()) return S