summaryrefslogtreecommitdiff
path: root/external/dualprop_patches/0013-analysis-audit-plain-CNN-P1-selector.patch
diff options
context:
space:
mode:
Diffstat (limited to 'external/dualprop_patches/0013-analysis-audit-plain-CNN-P1-selector.patch')
-rw-r--r--external/dualprop_patches/0013-analysis-audit-plain-CNN-P1-selector.patch260
1 files changed, 260 insertions, 0 deletions
diff --git a/external/dualprop_patches/0013-analysis-audit-plain-CNN-P1-selector.patch b/external/dualprop_patches/0013-analysis-audit-plain-CNN-P1-selector.patch
new file mode 100644
index 0000000..ef2612d
--- /dev/null
+++ b/external/dualprop_patches/0013-analysis-audit-plain-CNN-P1-selector.patch
@@ -0,0 +1,260 @@
+From 46b74984587f7b76c40f91cfccd3a6939695a121 Mon Sep 17 00:00:00 2001
+From: YurenHao0426 <Blackhao0426@gmail.com>
+Date: Mon, 27 Jul 2026 13:45:04 -0500
+Subject: [PATCH 13/19] analysis: audit plain CNN P1 selector
+
+---
+ analyze_p1.py | 241 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 241 insertions(+)
+ create mode 100644 analyze_p1.py
+
+diff --git a/analyze_p1.py b/analyze_p1.py
+new file mode 100644
+index 0000000..05dd52d
+--- /dev/null
++++ b/analyze_p1.py
+@@ -0,0 +1,241 @@
++#!/usr/bin/env python3
++"""Audit and mechanically select the frozen plain-CNN P1 learning rates."""
++import argparse
++import glob
++import hashlib
++import json
++import math
++import os
++
++import numpy as np
++
++
++ROOT = os.path.dirname(os.path.abspath(__file__))
++EXPECTED_SOURCE = {
++ "author_crossover_commit": "90697e8a17ab5e0757a62df40da521766763abe1",
++ "main_commit": "dab65f657ed22ce90fe2b38a360ca62cc8b00e65",
++ "protocol_sha256":
++ "345e0c1245f558a68ea2c212dfa427bdaa30eafd2c8d16c6763691ba354dae61",
++}
++SPECIFICATIONS = (
++ ("bp", 0.025),
++ ("fa", 0.003),
++ ("fa", 0.01),
++ ("fa", 0.03),
++ ("dfa", 0.003),
++ ("dfa", 0.01),
++ ("dfa", 0.03),
++ ("pepita", 0.01),
++ ("ff", 0.03),
++ ("ep", 0.003),
++ ("ep", 0.01),
++ ("ep", 0.03),
++ ("dualprop", 0.025),
++ ("clean_kp", 0.003),
++ ("clean_kp", 0.01),
++ ("clean_kp", 0.03),
++ ("sdil", 0.003),
++ ("sdil", 0.01),
++ ("sdil", 0.03),
++)
++GRIDDED_METHODS = ("fa", "dfa", "ep", "clean_kp", "sdil")
++CLI_METHODS = {
++ "bp": "backprop",
++ "clean_kp": "clean-kp",
++ "dualprop": "dualprop-lagr-ff",
++}
++
++
++def rate_tag(rate):
++ return f"{rate:g}".replace(".", "p")
++
++
++def experiment_name(method, rate):
++ return f"plain-p1-{method}-minicnn-lr{rate_tag(rate)}"
++
++
++def sha256(path):
++ digest = hashlib.sha256()
++ with open(path, "rb") as handle:
++ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
++ digest.update(chunk)
++ return digest.hexdigest()
++
++
++def flag(command, name):
++ try:
++ index = command.index(name)
++ except ValueError as error:
++ raise AssertionError(f"missing command flag {name}") from error
++ if index + 1 >= len(command):
++ raise AssertionError(f"missing value for command flag {name}")
++ return command[index + 1]
++
++
++def scalar(value):
++ return float(np.asarray(value))
++
++
++def audit_histogram(path, method):
++ history = np.load(path, allow_pickle=True).item()
++ if method == "ff":
++ assert history["method"] == "ff"
++ assert history["epochs_per_layer"] == 10
++ assert len(history["layers"]) == history["num_layers"]
++ assert all(
++ len(layer["epochs"]) == 10 for layer in history["layers"])
++ final = history["final"]
++ best_accuracy = float(final["validation_accuracy"])
++ final_accuracy = best_accuracy
++ final_loss = float(history["layers"][-1]["epochs"][-1]["loss"])
++ test_values = (final["test_accuracy"], final["test_time"])
++ epochs_completed = sum(
++ len(layer["epochs"]) for layer in history["layers"])
++ best_epoch = None
++ train_seconds = sum(
++ epoch["runtime"] for layer in history["layers"]
++ for epoch in layer["epochs"])
++ validation_seconds = float(final["validation_time"])
++ else:
++ validation_accuracy = np.asarray(
++ history["val_accuracy"], dtype=float)
++ validation_loss = np.asarray(history["val_loss"], dtype=float)
++ assert validation_accuracy.shape == (10,)
++ assert validation_loss.shape == (10,)
++ assert int(history["epochs_completed"]) == 10
++ assert np.all(np.isfinite(validation_accuracy))
++ best_accuracy = float(np.max(validation_accuracy))
++ final_accuracy = float(validation_accuracy[-1])
++ final_loss = float(validation_loss[-1])
++ assert math.isclose(
++ best_accuracy, scalar(history["best_validation_accuracy"]),
++ rel_tol=0, abs_tol=1e-6)
++ test_values = (
++ history["test_accuracy"], history["test_loss"],
++ history["test_top5accuracy"], history["test_time"])
++ epochs_completed = 10
++ best_epoch = int(history["best_epoch"])
++ train_seconds = float(np.sum(history["train_time"]))
++ validation_seconds = float(np.sum(history["val_time"]))
++ assert all(math.isnan(scalar(value)) for value in test_values)
++ return {
++ "best_validation_accuracy": best_accuracy,
++ "final_validation_accuracy": final_accuracy,
++ "final_validation_loss": final_loss,
++ "epochs_completed": epochs_completed,
++ "best_epoch": best_epoch,
++ "train_seconds": train_seconds,
++ "validation_seconds": validation_seconds,
++ "test_metrics_all_nan": True,
++ }
++
++
++def audit_record(method, rate):
++ name = experiment_name(method, rate)
++ run_root = os.path.join(ROOT, "runs", name)
++ histograms = glob.glob(os.path.join(run_root, "*", "hist.npy"))
++ manifests = glob.glob(
++ os.path.join(run_root, "*", "crossover_manifest.json"))
++ assert len(histograms) == 1, f"{name}: found {len(histograms)} histograms"
++ assert len(manifests) == 1, f"{name}: found {len(manifests)} manifests"
++ histogram = os.path.realpath(histograms[0])
++ with open(manifests[0], encoding="utf-8") as handle:
++ manifest = json.load(handle)
++ assert manifest["stage"] == "p1"
++ assert manifest["method"] == method
++ assert manifest["architecture"] == "minicnn"
++ assert math.isclose(float(manifest["rate"]), rate)
++ assert manifest["experiment_name"] == name
++ assert os.path.realpath(manifest["histogram"]) == histogram
++ assert manifest["histogram_sha256"] == sha256(histogram)
++ for key, expected in EXPECTED_SOURCE.items():
++ assert manifest["source"][key] == expected, f"{name}: source drift"
++ command = manifest["command"]
++ assert flag(command, "--model") == "miniCNN"
++ assert flag(command, "--dataset") == "cifar10"
++ assert flag(command, "--num-epochs") == "10"
++ assert flag(command, "--seeds") == "0"
++ assert flag(command, "--feedback-seed") == "1729"
++ assert flag(command, "--test-policy") == "none"
++ assert flag(command, "--early-stop-policy") == "none"
++ assert flag(command, "--gradient-diagnostics") == "none"
++ assert flag(command, "--spectral-diagnostics") == "none"
++ assert flag(command, "--learning-algorithm") == CLI_METHODS.get(
++ method, method)
++ assert math.isclose(float(flag(command, "--learning-rate")), rate)
++ metrics = audit_histogram(histogram, method)
++ return {
++ "method": method,
++ "rate": rate,
++ "experiment_name": name,
++ "manifest": os.path.relpath(manifests[0], ROOT),
++ "histogram_sha256": manifest["histogram_sha256"],
++ "driver_wall_seconds": manifest.get("driver_wall_seconds"),
++ **metrics,
++ }
++
++
++def selected_candidate(candidates):
++ def rank(candidate):
++ loss = candidate["final_validation_loss"]
++ return (
++ candidate["best_validation_accuracy"],
++ int(math.isfinite(loss)),
++ -candidate["rate"],
++ )
++ return max(candidates, key=rank)
++
++
++def main():
++ parser = argparse.ArgumentParser()
++ parser.add_argument("--output")
++ args = parser.parse_args()
++ expected_names = {
++ experiment_name(method, rate) for method, rate in SPECIFICATIONS}
++ actual_names = {
++ os.path.basename(path)
++ for path in glob.glob(os.path.join(ROOT, "runs", "plain-p1-*"))
++ if glob.glob(os.path.join(path, "*", "hist.npy"))
++ }
++ assert actual_names == expected_names, {
++ "missing": sorted(expected_names - actual_names),
++ "unexpected": sorted(actual_names - expected_names),
++ }
++ records = [
++ audit_record(method, rate) for method, rate in SPECIFICATIONS]
++ selected = {}
++ for method in dict(SPECIFICATIONS):
++ candidates = [
++ record for record in records if record["method"] == method]
++ chosen = (
++ selected_candidate(candidates)
++ if method in GRIDDED_METHODS else candidates[0])
++ selected[method] = {
++ "rate": chosen["rate"],
++ "best_validation_accuracy":
++ chosen["best_validation_accuracy"],
++ "experiment_name": chosen["experiment_name"],
++ }
++ report = {
++ "gate": "pass",
++ "stage": "plain_cnn_p1",
++ "selection_rule":
++ "maximum best validation accuracy, then finite final validation "
++ "loss, then lower learning rate",
++ "expected_source": EXPECTED_SOURCE,
++ "num_expected_records": len(SPECIFICATIONS),
++ "num_audited_records": len(records),
++ "test_policy": "none",
++ "records": records,
++ "selected": selected,
++ }
++ encoded = json.dumps(report, indent=2, sort_keys=True) + "\n"
++ if args.output:
++ with open(args.output, "w", encoding="utf-8") as handle:
++ handle.write(encoded)
++ else:
++ print(encoded, end="")
++
++
++if __name__ == "__main__":
++ main()
+--
+2.54.0
+