diff options
| -rw-r--r-- | BASELINES.md | 6 | ||||
| -rw-r--r-- | KP_BASELINE.md | 78 | ||||
| -rw-r--r-- | NOVELTY.md | 7 | ||||
| -rw-r--r-- | ROADMAP.md | 9 | ||||
| -rwxr-xr-x | experiments/analyze_kp_short.py | 126 | ||||
| -rwxr-xr-x | experiments/kp_short_development.py | 36 |
6 files changed, 262 insertions, 0 deletions
diff --git a/BASELINES.md b/BASELINES.md index 52fd12c..14c969c 100644 --- a/BASELINES.md +++ b/BASELINES.md @@ -32,6 +32,12 @@ The audited implementations and completed results are in `RESULTS.md`: stochastic update vanishes sample-by-sample at Q=W; the extra feedback prediction convolution is included in MAC accounting. It is also an inherited learned-alignment control; +- modified Kolen--Pollack (`kp`) is the reciprocal local-plasticity baseline of + Akrout et al. Forward and feedback synapses independently recompute the same + correlation from their own local activity pair and use matched optimizer and + decay dynamics. The feedback update never reads W or its update. A separate + reciprocal correlation is charged in the MAC audit; `KP_BASELINE.md` freezes + its staged ResNet gate before any KP task endpoint; - direct node perturbation uses a causal target on every hidden update and no learned vectorizer; - the no-traffic, `P=0` SDIL backbone is learned direct feedback by node diff --git a/KP_BASELINE.md b/KP_BASELINE.md new file mode 100644 index 0000000..25696f7 --- /dev/null +++ b/KP_BASELINE.md @@ -0,0 +1,78 @@ +# Modified Kolen--Pollack baseline protocol + +## Attribution and boundary + +This is the reciprocal local-plasticity mechanism of Akrout et al., *Deep +Learning without Weight Transport* (NeurIPS 2019), not an SDIL contribution. +Their equations 16--18 give forward and feedback synapses equal adjustments +from reciprocal local activity pairs and equal decay, so initially distinct +weights converge without transmitting either weight. The public author +repository is frozen for reference at revision +`688f47addd2131684da1b7829b20365f585eee66`: + +<https://github.com/makrout/Deep-Learning-without-Weight-Transport> + +The public Python implementation is a fully connected pedagogical version and +explicitly transposes a precomputed update. The implementation here instead +recomputes each reciprocal convolutional correlation from its own cached +parent activity and child teaching field. It follows the actual ResNet +residual DAG, including local ReLU and BatchNorm Jacobians and parameter-free +shortcut adjoints. The reciprocal update never reads a forward weight or a +forward update tensor. + +KP is introduced because the frozen residual-response mirror full run tests a +different, intermittent probe-response mechanism. KP is not a cadence or rate +rescue of that branch. No KP task endpoint was generated before this protocol +and its executable analyzer were committed. + +## KP-0: mechanics gate + +The convolutional smoke suite requires all of: + +- independently recomputed forward and reciprocal local directions agree to + absolute error below `1e-14` under the storage/sign convention; +- changing every W and Q value after local activities are fixed changes the + reciprocal update by exactly zero; +- exact `Q=W`, `R=-W_out^T` remains exact after two momentum-plus-decay steps; +- the public KP task step leaves all parameters graph-free; +- every pre-existing convolutional local-gradient and feedback check remains + green. + +The implemented errors are all exactly zero in float64. + +## KP-1: frozen 20-epoch useful-scale gate + +Run exactly one seed-0 ResNet-20 development record on the frozen 45k/5k +CIFAR-10 split: batch 128, standard augmentation, hidden/output LR 0.1, +epoch-100/150 step drops (therefore constant during this screen), no warmup, +momentum 0.9, weight decay `1e-4`, BatchNorm, feedback scale 1, and a 32-example +alignment audit. There is no LR, decay, initialization, or feedback-scale grid. + +The already frozen matched BP trajectory has 81.02% validation accuracy at +epoch 20 and `1.09487808e14` linearly scaled training MACs. KP-1 passes only if: + +- the record, every epoch loss, and every tracking diagnostic are finite; +- validation accuracy is at least 70% and within 15 points of BP epoch 20; +- final early-third teaching alignment is at least 0.50; +- final mean feedback/forward cosine is at least 0.80 and its epoch-11--20 + mean is at least 0.70; +- feedback learning uses zero task-loss queries; +- total MACs, including a separate correlation at every reciprocal synapse, + are at most `1.40x` matched BP. + +Failure closes KP without an extra learning-rate/decay screen. Passing opens +one full development run. + +## KP-2: conditional full baseline + +Copy KP-1 exactly for 200 epochs. KP-2 passes only if it is finite, reaches +88% validation accuracy, retains early alignment 0.80, reaches final mean +feedback/forward cosine 0.95, keeps the epoch-151--200 mean cosine at least +0.95, uses zero task-loss queries, and costs at most `1.40x` the frozen full BP +MACs. It never authorizes test access. + +KP remains an inherited baseline even if both gates pass, so it cannot raise +the reviewer score. A KP-2 pass only opens a separately frozen +raw-versus-norm-matched-raw-versus-innovation mixed-traffic experiment. The +somato-dendritic innovation must be load-bearing there to affect the SDIL +paper assessment. @@ -76,6 +76,13 @@ this attribution. Its zero-noise fixed point and any resulting scale belong to local predictive weight estimation; they become relevant to SDIL only as the feedback substrate on which innovation is separately ablated. +The modified Kolen--Pollack reciprocal-plasticity path is likewise inherited +from Akrout et al. Equal local activity products and matched decay in reciprocal +synapses are not SDIL. Even if the audited implementation reaches BP-level +accuracy, it only supplies a stable feedback substrate. The candidate novelty +still requires neutral somato-dendritic subtraction to outperform raw and +norm-matched raw apical activity under the same mixed-traffic intervention. + ## Candidate SDIL contribution The candidate contribution is the combination @@ -447,6 +447,15 @@ two accuracy margins by only 0.08 and 0.14 points; the LR-0.03 run reaches validation run opens at the selected settings. The result remains baseline evidence and the reviewer score stays 5/10. +**Modified Kolen--Pollack baseline protocol: mechanics passed, task gate +frozen.** The separate local reciprocal correlations, post-observation W/Q +independence, and exact symmetric-limit momentum updates all pass at zero +numerical error. `KP_BASELINE.md` freezes one 20-epoch full-development screen +with no LR/decay grid and requires both validation accuracy and training-period +feedback tracking. No KP task endpoint had been generated when this protocol +was committed. KP is inherited Akrout et al. machinery and cannot raise the +paper score; only a later load-bearing innovation ablation can do that. + Prepare convolutional local-update primitives and ResNet-20/32/56 protocols early. Queue frozen runs opportunistically on authorized idle GPUs. Because BurstCCN already reports CIFAR-10 and ImageNet scaling, dataset scale alone is not novel. The oral-level target is a memorable joint diff --git a/experiments/analyze_kp_short.py b/experiments/analyze_kp_short.py new file mode 100755 index 0000000..76d86e6 --- /dev/null +++ b/experiments/analyze_kp_short.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""Audit and gate the frozen KP-1 ResNet-20 development record.""" +import argparse +import json +import math +import os + + +SPLIT_HASH = "8328b206a97c420e49e54e3eca4abe3274c4756b084355784ea3fb8059e4515b" +BP_EPOCH20_ACCURACY = 0.8102 +BP_EPOCH20_MACS = 109_487_808_000_000 + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--input", default="results/kp_short/kp.json") + parser.add_argument( + "--bp", default="results/oral_a_dev/bp_reference_primary.json") + parser.add_argument("--out", default="results/kp_short_gate.json") + args = parser.parse_args() + with open(args.input) as handle: + record = json.load(handle) + run_args = record["args"] + expected = { + "mode": "kp", "depth": 20, "width": 16, "seed": 0, + "loader_seed": 0, "batch_size": 128, "epochs": 20, + "train_limit": 0, "val_examples": 5000, "split_seed": 2027, + "eval_split": "validation", "eval_every": 0, + "augment_train": 1, "lr": 0.1, "output_lr": 0.1, + "lr_schedule": "step", "lr_milestones": "100,150", + "lr_gamma": 0.1, "warmup_epochs": 0, "momentum": 0.9, + "weight_decay": 1e-4, "normalization": "batchnorm", + "a_scale": 1.0, "alignment_probe": 32, + } + for key, value in expected.items(): + if run_args.get(key) != value: + raise ValueError(f"KP-1 {key} drift") + if record["provenance"]["git_tracked_dirty"]: + raise ValueError("tracked-dirty KP-1 result") + if record["split"]["validation_index_sha256"] != SPLIT_HASH: + raise ValueError("KP-1 split drift") + protocol = record["evaluation_protocol"] + if protocol["test_evaluations"] or protocol["test_used_for_selection"]: + raise ValueError("KP-1 touched test") + if record.get("calibration_metric_space") != ( + "reciprocal_local_activity_products"): + raise ValueError("KP-1 metric-space drift") + + with open(args.bp) as handle: + bp = json.load(handle) + bp_epoch20 = [row for row in bp["epochs"] if row["epoch"] == 20] + if (len(bp_epoch20) != 1 + or float(bp_epoch20[0]["eval_accuracy"]) != BP_EPOCH20_ACCURACY): + raise ValueError("matched BP epoch-20 endpoint drift") + if int(bp["work"]["total_macs_estimate"]) // 10 != BP_EPOCH20_MACS: + raise ValueError("matched BP epoch-20 MAC reference drift") + + epoch_tracking = [row.get("feedback_tracking") for row in record["epochs"]] + if len(epoch_tracking) != 20 or any(value is None for value in epoch_tracking): + raise ValueError("KP-1 tracking trajectory is incomplete") + trajectory_values = [] + for row, tracking in zip(record["epochs"], epoch_tracking): + trajectory_values.extend([ + float(row["train_loss"]), + float(tracking["mean_feedback_forward_cosine"]), + float(tracking["mean_feedback_forward_relative_error"]), + float(tracking["min_feedback_forward_cosine"]), + float(tracking["max_feedback_forward_relative_error"]), + ]) + diagnostics = record["diagnostics"] + accuracy = float(record["final"]["accuracy"]) + loss = float(record["final"]["loss"]) + early = float(diagnostics["early_third_mean"]) + final_cosine = float(diagnostics["mean_feedback_forward_cosine"]) + late_cosine = sum(float(value["mean_feedback_forward_cosine"]) + for value in epoch_tracking[10:]) / 10 + total_macs = int(record["work"]["total_macs_estimate"]) + queries = int(record["work"]["logical_batch_loss_queries"]) + finite = (bool(record["final"]["finite"]) + and all(math.isfinite(value) for value in + trajectory_values + [accuracy, loss, early, final_cosine])) + checks = { + "record_and_trajectory_finite": finite, + "accuracy_at_least_0.70": accuracy >= 0.70, + "within_15_points_of_bp_epoch20": ( + accuracy >= BP_EPOCH20_ACCURACY - 0.15), + "early_alignment_at_least_0.50": early >= 0.50, + "final_feedback_cosine_at_least_0.80": final_cosine >= 0.80, + "epoch11_to20_feedback_cosine_at_least_0.70": late_cosine >= 0.70, + "zero_task_loss_queries": queries == 0, + "macs_at_most_1.40x_bp": total_macs <= 1.40 * BP_EPOCH20_MACS, + } + status = "passed" if all(checks.values()) else "failed" + metrics = { + "accuracy": accuracy, "loss": loss, + "bp_epoch20_accuracy": BP_EPOCH20_ACCURACY, + "early_third_alignment": early, + "final_mean_feedback_forward_cosine": final_cosine, + "epoch11_to20_mean_feedback_forward_cosine": late_cosine, + "final_mean_feedback_forward_relative_error": float( + diagnostics["mean_feedback_forward_relative_error"]), + "total_macs": total_macs, "bp_epoch20_total_macs": BP_EPOCH20_MACS, + "mac_ratio_to_bp": total_macs / BP_EPOCH20_MACS, + "logical_batch_loss_queries": queries, + "peak_memory_allocated_bytes": int( + record["hardware"]["peak_memory_allocated_bytes"]), + "wall_s": float(record["timing"]["total_timed_wall_s"]), + "source_commit": record["provenance"]["git_commit"], + } + output = { + "protocol": "kolen_pollack_short_v1", "status": status, + "checks": checks, "metrics": metrics, + "full_validation_opened": status == "passed", + "confirmation_test_seeds_touched": False, + "review_score_before": 5, "review_score_after": 5, + "score_change_rule": "inherited KP baseline cannot raise score", + } + os.makedirs(os.path.dirname(os.path.abspath(args.out)), exist_ok=True) + with open(args.out, "w") as handle: + json.dump(output, handle, indent=2, sort_keys=True) + handle.write("\n") + print(json.dumps(output, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/experiments/kp_short_development.py b/experiments/kp_short_development.py new file mode 100755 index 0000000..deefb59 --- /dev/null +++ b/experiments/kp_short_development.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +"""Run the single frozen KP-1 ResNet-20 development record.""" +import argparse +import os +import subprocess +import sys + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--device", default="cuda") + parser.add_argument("--dry_run", action="store_true") + args = parser.parse_args() + command = [ + sys.executable, "experiments/conv_run.py", "--mode", "kp", + "--device", args.device, "--depth", "20", "--width", "16", + "--seed", "0", "--loader_seed", "0", "--batch_size", "128", + "--epochs", "20", "--train_limit", "0", + "--val_examples", "5000", "--split_seed", "2027", + "--eval_split", "validation", "--eval_every", "0", + "--augment_train", "1", "--lr", "0.1", "--output_lr", "0.1", + "--lr_schedule", "step", "--lr_milestones", "100,150", + "--lr_gamma", "0.1", "--warmup_epochs", "0", + "--momentum", "0.9", "--weight_decay", "1e-4", + "--normalization", "batchnorm", "--a_scale", "1", + "--alignment_probe", "32", + "--out", "results/kp_short/kp.json", + ] + os.makedirs("results/kp_short", exist_ok=True) + print(" ".join(command), flush=True) + if not args.dry_run: + subprocess.run(command, check=True) + + +if __name__ == "__main__": + main() |
