diff options
| author | YurenHao0426 <Blackhao0426@gmail.com> | 2026-07-26 12:27:52 -0500 |
|---|---|---|
| committer | YurenHao0426 <Blackhao0426@gmail.com> | 2026-07-26 12:27:52 -0500 |
| commit | 00baa870b8b61a0974438233d9240eebb40c384f (patch) | |
| tree | bae281f38fe7512a0e0ad46da43f6262eb3c94dc | |
| parent | 8f9364e8ee2e2a19eb83b58452fa0e8cb155353c (diff) | |
experiment: freeze KP teaching-signal ablation
| -rw-r--r-- | KP_TEACHING_SIGNAL_ABLATION.md | 83 | ||||
| -rw-r--r-- | experiments/analyze_kp_teaching_signal.py | 275 | ||||
| -rw-r--r-- | experiments/conv_local_smoke.py | 52 | ||||
| -rw-r--r-- | experiments/conv_run.py | 6 | ||||
| -rw-r--r-- | experiments/kp_teaching_signal_ablation.py | 130 | ||||
| -rw-r--r-- | sdil/conv.py | 14 |
6 files changed, 553 insertions, 7 deletions
diff --git a/KP_TEACHING_SIGNAL_ABLATION.md b/KP_TEACHING_SIGNAL_ABLATION.md new file mode 100644 index 0000000..7b1207e --- /dev/null +++ b/KP_TEACHING_SIGNAL_ABLATION.md @@ -0,0 +1,83 @@ +# KP teaching-signal ablation + +## Question + +The dynamic SDIL image model and clean KP use the same reciprocal KP credit +transport. Their relevant local signals are + +``` +clean KP: u = s +raw KP: u = a = s + t +matched raw KP: u = ||r|| / ||a|| * a +dynamic SDIL: u = r = s + t - prediction(t). +``` + +Here `s` is the ordinary clean KP instruction and `t` is four-RMS +soma-predictable apical traffic. This experiment therefore changes neither +the forward architecture nor the feedback-learning substrate. It asks +whether the subtractive innovation operation is necessary to recover useful +credit when the same KP substrate receives mixed apical traffic. + +Raw and norm-matched KP execute the same frozen 64-example slow fit and the +same instruction-off fast neutral projection on every ordinary minibatch. +They report the projection but do not apply its subtractive direction. Raw +uses the mixed apical vector unchanged. Matched raw preserves that vector's +direction and changes only its per-example norm to the projected innovation +norm. Thus a matched-raw deficit cannot be attributed to a larger update +norm, and neither deficit can be attributed to a cheaper observation budget +for SDIL. + +This is a conditional robustness intervention, not a claim that dynamic SDIL +outperforms clean KP when no nuisance traffic is present. + +## KTS-0: mechanics + +Before any endpoint launch, the CPU mechanics test must establish: + +- raw with the sham projection is bitwise equal to `s + t`; +- matched raw has projected innovation's per-example norm to relative error + below `1e-12` and raw's direction to error below `1e-12`; +- raw, matched, and innovation receive the identical instruction-off + projection report; +- the projection observes zero task-instruction examples and changes no slow + predictor parameter; +- all three rules form their local KP forward and reciprocal correlations from + the selected used vector without reverse-mode differentiation. + +## KTS-1: frozen validation endpoint + +KTS-1 inherits the passed +`results/kp_dynamic_projection_full_gate.json` endpoint without rerunning or +selecting it: clean KP is 91.26% and dynamic SDIL is 91.18% on the frozen +45,000/5,000 CIFAR-10 split. The only new endpoints are `raw` and `matched`. +Use ResNet-20, width 16, model/data-loader/traffic seed 0/0/4000, batch 128, +200 epochs, augmentation, batch normalization, SGD momentum 0.9, weight decay +`1e-4`, learning rate 0.1 with 0.1 drops at epochs 100 and 150, traffic ratio +4, one closed-form 64-example predictor fit, a frozen predictor thereafter, +and the fast instruction-off projection on every task batch. Evaluate the +validation split exactly once at the endpoint and never evaluate test. + +Both jobs must share one clean tracked source revision containing this +protocol, its runner, analyzer, mechanics test, and the sham-control +implementation. No endpoint may be deleted, replaced, or relaunched under +the same name. A nonfinite trajectory is retained as a terminal control +collapse, not repaired by lowering the learning rate or traffic strength. + +The mechanism advantage passes only if every provenance, split, argument, +traffic, observation, query, projection, norm-control, and cost invariant +passes and dynamic SDIL exceeds raw by at least 5 percentage points and +matched raw by at least 3 percentage points. A nonfinite control counts as a +performance/stability failure of that control, while the raw record and its +failure location remain part of the evidence. A finite control must have a +valid endpoint accuracy. Every job must use zero task-loss queries, one +64-example slow fit, one instruction-off projection observation per ordinary +example, no predictor updates during task learning, at most 1.34 times the +frozen BP affine-MAC estimate, and separately reported positive elementwise +work. + +Passing KTS-1 establishes that innovation subtraction, rather than KP +transport alone or signal-norm rescaling, is necessary under the specified +soma-predictable traffic. It does not establish superiority to Dual Prop, +BP, or clean KP in the clean setting. Multi-seed and cross-architecture +confirmation remain subsequent gates. + diff --git a/experiments/analyze_kp_teaching_signal.py b/experiments/analyze_kp_teaching_signal.py new file mode 100644 index 0000000..db8649a --- /dev/null +++ b/experiments/analyze_kp_teaching_signal.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 +"""Audit the frozen KTS-1 raw-KP teaching-signal controls.""" +import argparse +import hashlib +import json +import math +import os +import statistics +import subprocess + +from kp_teaching_signal_ablation import ROOT, RULES, command + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +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 close(left, right, tolerance=1e-12): + return abs(float(left) - float(right)) <= tolerance + + +def finite(value): + return isinstance(value, (int, float)) and math.isfinite(value) + + +def same_scalar(left, right, tolerance=1e-7): + left = float(left) + right = float(right) + if math.isnan(left) or math.isnan(right): + return math.isnan(left) and math.isnan(right) + if math.isinf(left) or math.isinf(right): + return left == right + return abs(left - right) <= tolerance + + +def same_list(left, right, tolerance=1e-7): + return len(left) == len(right) and all( + same_scalar(a, b, tolerance) for a, b in zip(left, right)) + + +def validate_record(path, rule, source_commit, bp_reference_macs): + with open(path) as handle: + row = json.load(handle) + require(row["provenance"]["git_commit"] == source_commit, + f"{path}: source commit") + require(row["provenance"]["git_tracked_dirty"] is False, + f"{path}: dirty source") + expected = command("cuda", rule, path)[2:-2] + expected_args = {} + for index in range(0, len(expected), 2): + key = expected[index].removeprefix("--") + expected_args[key] = expected[index + 1] + actual = row["args"] + numeric = { + "depth": int, "width": int, "seed": int, "loader_seed": int, + "batch_size": int, "epochs": int, "train_limit": int, + "val_examples": int, "split_seed": int, "eval_every": int, + "augment_train": int, "warmup_epochs": int, "learn_P": int, + "predictor_warmup_steps": int, "predictor_every": int, + "neutral_projection": int, "traffic_seed": int, + "traffic_calibration_examples": int, "alignment_probe": int, + "lr_gamma": float, "momentum": float, "weight_decay": float, + "lr": float, "output_lr": float, "eta_P": float, + "traffic_ratio": float, + } + for key, expected_value in expected_args.items(): + if key in ("device", "out"): + continue + caster = numeric.get(key, str) + require(actual[key] == caster(expected_value), + f"{path}: argument {key}") + + require(row["split"]["train_examples"] == 45000, f"{path}: train split") + require(row["split"]["validation_examples"] == 5000, + f"{path}: validation split") + require(row["split"]["test_examples"] == 10000, f"{path}: test split") + require(row["evaluation_protocol"] == { + "validation_evaluations": 1, + "test_evaluations": 0, + "test_used_for_selection": False, + }, f"{path}: evaluation protocol") + counters = row["counters"] + require(counters["ordinary_examples"] == 9_000_000, + f"{path}: ordinary observations") + require(counters["neutral_projection_examples"] + == counters["ordinary_examples"], + f"{path}: projection observation budget") + require(counters["predictor_update_examples"] == 64, + f"{path}: slow fit observations") + require(counters["logical_batch_loss_queries"] == 0, + f"{path}: task loss queries") + require(counters["causal_scalar_observations"] == 0, + f"{path}: causal observations") + require(row["work"]["neutral_projection_observations"] + == counters["ordinary_examples"], + f"{path}: reported projection observations") + require(row["work"]["elementwise_operations_estimate"] > 0, + f"{path}: elementwise work") + require(row["work"]["total_macs_estimate"] + <= 1.34 * bp_reference_macs, + f"{path}: MAC ceiling") + + calibration = row["traffic_calibration"] + require(max(abs(float(value) - 4.0) for value in + calibration["realized_traffic_instruction_rms_ratio"]) + <= 1e-5, f"{path}: traffic ratio") + warmup = row["predictor_warmup"] + require(warmup["mode"] == "closed_form" and warmup["examples"] == 64, + f"{path}: closed-form fit") + require(warmup["closed_form_fit"]["stability_margin"] == 0.0, + f"{path}: predictor margin") + + projections = [epoch["neutral_projection"] for epoch in row["epochs"]] + require(all(value["instruction_observations"] == 0 + for value in projections), + f"{path}: projection instruction leakage") + require(min(value["minimum_observations"] for value in projections) > 0 + and max(value["maximum_observations"] + for value in projections) <= 128, + f"{path}: projection batch observations") + require(max(value["maximum_post_projection_traffic_rms_ratio"] + for value in projections) <= 1e-5, + f"{path}: projected traffic remainder") + require(max(value["maximum_absolute_post_projection_soma_slope"] + for value in projections) <= 1e-5, + f"{path}: projected soma slope") + + diagnostics = row["diagnostics"] + if rule == "raw": + require(same_list( + diagnostics["used_negative_gradient_cosine"], + diagnostics["raw_negative_gradient_cosine"]), + f"{path}: raw direction") + else: + require(same_list( + diagnostics["used_negative_gradient_cosine"], + diagnostics["matched_negative_gradient_cosine"]), + f"{path}: matched direction") + if row["final"]["finite"]: + require(diagnostics["max_norm_match_relative_error"] <= 1e-6, + f"{path}: matched norm") + require(diagnostics["max_norm_match_direction_error"] <= 1e-6, + f"{path}: matched direction preservation") + + for epoch in row["epochs"]: + mixed = epoch["mixed_apical"] + if all(finite(value) for value in mixed.values()): + if rule == "raw": + require(close( + mixed["teaching_rms"], mixed["raw_apical_rms"], 1e-10), + f"{path}: epoch raw signal") + else: + require(close( + mixed["teaching_rms"], mixed["innovation_rms"], 1e-9), + f"{path}: epoch matched norm") + require(epoch["neutral_projection"]["instruction_observations"] == 0, + f"{path}: epoch projection leakage") + accuracy = row["final"]["accuracy"] + require(finite(accuracy) and 0.0 <= accuracy <= 1.0, + f"{path}: endpoint accuracy") + return row + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--outdir", default="results/kp_teaching_signal_ablation") + parser.add_argument( + "--dynamic", + default="results/kp_dynamic_projection_full/dynamic.json") + parser.add_argument( + "--prerequisite", + default="results/kp_dynamic_projection_full_gate.json") + parser.add_argument( + "--out", default="results/kp_teaching_signal_ablation_gate.json") + args = parser.parse_args() + + tracked_dirty = subprocess.run( + ["git", "status", "--porcelain", "--untracked-files=no"], + cwd=ROOT, check=True, capture_output=True, text=True).stdout.strip() + require(not tracked_dirty, "analysis requires a clean tracked source") + source_commit = subprocess.run( + ["git", "rev-parse", "HEAD"], cwd=ROOT, check=True, + capture_output=True, text=True).stdout.strip() + with open(args.prerequisite) as handle: + prerequisite = json.load(handle) + require(prerequisite["protocol"] + == "kp_dynamic_neutral_projection_full_v1", + "wrong prerequisite protocol") + require(prerequisite["status"] == "passed", "prerequisite did not pass") + with open(args.dynamic) as handle: + dynamic = json.load(handle) + require(close(dynamic["final"]["accuracy"], + prerequisite["metrics"]["accuracy"]), + "dynamic record/gate mismatch") + + rows = { + rule: validate_record( + os.path.join(args.outdir, f"{rule}.json"), rule, source_commit, + prerequisite["metrics"]["bp_total_macs"]) + for rule in RULES + } + dynamic_accuracy = float(dynamic["final"]["accuracy"]) + raw_accuracy = float(rows["raw"]["final"]["accuracy"]) + matched_accuracy = float(rows["matched"]["final"]["accuracy"]) + raw_gap = dynamic_accuracy - raw_accuracy + matched_gap = dynamic_accuracy - matched_accuracy + raw_finite = bool(rows["raw"]["final"]["finite"]) + matched_finite = bool(rows["matched"]["final"]["finite"]) + checks = { + "two_frozen_controls_present": len(rows) == 2, + "dynamic_reference_is_finite": bool(dynamic["final"]["finite"]), + "raw_degrades_or_collapses_by_at_least_5_points": ( + (not raw_finite) or raw_gap >= 0.05), + "matched_degrades_or_collapses_by_at_least_3_points": ( + (not matched_finite) or matched_gap >= 0.03), + "raw_and_matched_use_identical_source": ( + len({row["provenance"]["git_commit"] + for row in rows.values()}) == 1), + "all_control_invariants_audited": True, + } + status = "passed" if all(checks.values()) else "failed" + report = { + "protocol": "kp_teaching_signal_ablation_validation_v1", + "status": status, + "checks": checks, + "metrics": { + "dynamic_accuracy": dynamic_accuracy, + "clean_kp_accuracy": prerequisite["metrics"][ + "clean_kp_accuracy"], + "raw_accuracy": raw_accuracy, + "matched_accuracy": matched_accuracy, + "dynamic_minus_raw_points": 100.0 * raw_gap, + "dynamic_minus_matched_points": 100.0 * matched_gap, + "raw_finite": raw_finite, + "matched_finite": matched_finite, + "control_mean_wall_s": statistics.mean( + row["timing"]["total_timed_wall_s"] + for row in rows.values()), + "source_commit": source_commit, + "prerequisite_sha256": sha256(args.prerequisite), + }, + "claim": ( + "Under four-RMS soma-predictable apical traffic, innovation " + "subtraction is necessary relative to both raw and norm-matched " + "KP controls." + if status == "passed" else + "The frozen validation endpoint does not establish a KP " + "teaching-signal advantage." + ), + "scope_limit": ( + "This is a conditional robustness result on one validation seed; " + "it is not clean-setting or strong-baseline superiority." + ), + } + os.makedirs(os.path.dirname(os.path.abspath(args.out)), exist_ok=True) + with open(args.out, "w") as handle: + json.dump(report, handle, indent=2, sort_keys=True) + handle.write("\n") + print(json.dumps(report, indent=2, sort_keys=True)) + if status != "passed": + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/experiments/conv_local_smoke.py b/experiments/conv_local_smoke.py index cdf1ad0..1b18048 100644 --- a/experiments/conv_local_smoke.py +++ b/experiments/conv_local_smoke.py @@ -1023,6 +1023,51 @@ def kp_mixed_traffic_checks(): assert all(torch.equal(before, after) for before, after in zip( frozen_before_projection, net.P_traffic + net.P_traffic_bias)) + # Raw and matched controls pay for the identical neutral projection but do + # not apply its subtractive direction. Raw must remain exactly the mixed + # apical vector. Matched may borrow only projected innovation's norm. + projected_raw = net.mixed_apical_components( + instruction, forward["hiddens"], "raw", + neutral_projection=True) + projected_matched = net.mixed_apical_components( + instruction, forward["hiddens"], "matched", + neutral_projection=True) + sham_raw_error = max(float((left - right).abs().max()) + for left, right in zip( + projected_raw["used"], projected_raw["raw"])) + sham_projection_report_error = max( + abs(projected_raw["neutral_projection"][key] + - projected_matched["neutral_projection"][key]) + for key in ( + "pre_projection_traffic_rms_ratio", + "post_projection_traffic_rms_ratio", + "max_absolute_pre_projection_soma_slope", + "max_absolute_post_projection_soma_slope", + "max_positive_post_projection_soma_slope", + "min_post_projection_soma_slope", + "max_absolute_correction_slope", + )) + sham_matched_norm_errors = [] + sham_matched_direction_errors = [] + for raw, innovation, matched in zip( + projected_matched["raw"], projected_matched["innovation"], + projected_matched["matched"]): + raw_flat = raw.flatten(1) + innovation_flat = innovation.flatten(1) + matched_flat = matched.flatten(1) + sham_matched_norm_errors.append(float(( + (matched_flat.norm(dim=1) - innovation_flat.norm(dim=1)).abs() + / innovation_flat.norm(dim=1).clamp_min(1e-30)).max())) + sham_matched_direction_errors.append(float((F.cosine_similarity( + raw_flat, matched_flat, dim=1) - 1.0).abs().max())) + assert sham_raw_error == 0.0 + assert sham_projection_report_error == 0.0 + assert max(sham_matched_norm_errors) < 1e-12 + assert max(sham_matched_direction_errors) < 1e-12 + assert projected_raw["neutral_projection"]["instruction_observations"] == 0 + assert all(torch.equal(before, after) for before, after in zip( + frozen_before_projection, net.P_traffic + net.P_traffic_bias)) + for slope, bias in zip(net.P_traffic, net.P_traffic_bias): slope.zero_() bias.zero_() @@ -1142,6 +1187,13 @@ def kp_mixed_traffic_checks(): "post_projection_traffic_rms_ratio"], "kp_traffic_projected_residual_slope": projection[ "max_absolute_post_projection_soma_slope"], + "kp_traffic_sham_raw_error": sham_raw_error, + "kp_traffic_sham_projection_report_error": ( + sham_projection_report_error), + "kp_traffic_sham_matched_norm_error": max( + sham_matched_norm_errors), + "kp_traffic_sham_matched_direction_error": max( + sham_matched_direction_errors), "kp_traffic_matched_norm_error": max(norm_errors), "kp_traffic_matched_direction_error": max(direction_errors), "kp_traffic_reciprocal_correlation_error": max(correlation_errors), diff --git a/experiments/conv_run.py b/experiments/conv_run.py index b1372f5..15510c8 100644 --- a/experiments/conv_run.py +++ b/experiments/conv_run.py @@ -261,13 +261,13 @@ def run(args): if args.traffic_calibration_examples < 1 or args.traffic_ratio <= 0: raise ValueError("invalid mixed-traffic calibration") if args.neutral_projection: - if (args.traffic_rule != "innovation" - or args.predictor_mode != "closed_form" + if (args.predictor_mode != "closed_form" or args.predictor_warmup_steps != 1 or args.predictor_every != 0): raise ValueError( "neutral projection requires frozen one-step closed-form " - "innovation prediction") + "prediction; raw and matched rules compute it as a sham " + "control without applying the subtractive direction") elif args.predictor_every < 1: raise ValueError("mixed-traffic KP requires a predictor cadence") if (args.predictor_mode == "closed_form" diff --git a/experiments/kp_teaching_signal_ablation.py b/experiments/kp_teaching_signal_ablation.py new file mode 100644 index 0000000..537b262 --- /dev/null +++ b/experiments/kp_teaching_signal_ablation.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""Run the frozen raw-KP controls for the KTS-1 validation endpoint.""" +import argparse +import hashlib +import json +import os +import subprocess + + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +PROTOCOL = os.path.join(ROOT, "KP_TEACHING_SIGNAL_ABLATION.md") +PREREQUISITE = os.path.join( + ROOT, "results", "kp_dynamic_projection_full_gate.json") +RULES = ("raw", "matched") + + +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 git_output(*args): + return subprocess.run( + ["git", *args], cwd=ROOT, check=True, capture_output=True, + text=True).stdout.strip() + + +def require_source(): + with open(PREREQUISITE) as handle: + gate = json.load(handle) + if not ( + gate.get("protocol") == "kp_dynamic_neutral_projection_full_v1" + and gate.get("status") == "passed" + and gate.get("independent_confirmation_opened") is True + ): + raise RuntimeError("KTS-1 requires the passed dynamic validation gate") + required = [ + PROTOCOL, + os.path.abspath(__file__), + os.path.join(ROOT, "experiments", "analyze_kp_teaching_signal.py"), + os.path.join(ROOT, "experiments", "conv_local_smoke.py"), + os.path.join(ROOT, "experiments", "conv_run.py"), + os.path.join(ROOT, "sdil", "conv.py"), + ] + for path in required: + relative = os.path.relpath(path, ROOT) + subprocess.run( + ["git", "ls-files", "--error-unmatch", relative], + cwd=ROOT, check=True, capture_output=True) + if git_output("status", "--porcelain", "--untracked-files=no"): + raise RuntimeError("KTS-1 requires a clean tracked source") + return { + "git_commit": git_output("rev-parse", "HEAD"), + "protocol_sha256": sha256(PROTOCOL), + "prerequisite_sha256": sha256(PREREQUISITE), + } + + +def command(device, rule, path): + return [ + "python", "experiments/conv_run.py", + "--device", device, + "--depth", "20", + "--width", "16", + "--seed", "0", + "--loader_seed", "0", + "--batch_size", "128", + "--epochs", "200", + "--train_limit", "0", + "--val_examples", "5000", + "--split_seed", "2027", + "--eval_split", "validation", + "--eval_every", "0", + "--augment_train", "1", + "--lr_schedule", "step", + "--lr_milestones", "100,150", + "--lr_gamma", "0.1", + "--warmup_epochs", "0", + "--momentum", "0.9", + "--weight_decay", "1e-4", + "--normalization", "batchnorm", + "--mode", "kp_traffic", + "--lr", "0.1", + "--output_lr", "0.1", + "--learn_P", "1", + "--eta_P", "0.1", + "--predictor_mode", "closed_form", + "--predictor_warmup_steps", "1", + "--predictor_every", "0", + "--neutral_projection", "1", + "--traffic_rule", rule, + "--traffic_ratio", "4.0", + "--traffic_seed", "4000", + "--traffic_calibration_examples", "64", + "--alignment_probe", "32", + "--out", path, + ] + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--device", default="cuda") + parser.add_argument("--rule", choices=("all",) + RULES, default="all") + parser.add_argument( + "--outdir", default="results/kp_teaching_signal_ablation") + parser.add_argument("--dry-run", action="store_true") + args = parser.parse_args() + source = require_source() + rules = RULES if args.rule == "all" else (args.rule,) + os.makedirs(args.outdir, exist_ok=True) + for rule in rules: + path = os.path.join(args.outdir, f"{rule}.json") + if os.path.exists(path): + raise RuntimeError(f"refusing to overwrite frozen endpoint: {path}") + cmd = command(args.device, rule, path) + print(json.dumps({ + "rule": rule, + "command": cmd, + "source": source, + }, sort_keys=True), flush=True) + if not args.dry_run: + subprocess.run(cmd, cwd=ROOT, check=True) + + +if __name__ == "__main__": + main() + diff --git a/sdil/conv.py b/sdil/conv.py index ebb29ab..1034202 100644 --- a/sdil/conv.py +++ b/sdil/conv.py @@ -820,7 +820,16 @@ class CIFARKPMixedTrafficResNet(CIFARKPResNet): def mixed_apical_components(self, instruction, hiddens, rule, compute_matched=False, neutral_projection=False): - """Return used, raw, innovation, matched, and traffic fields.""" + """Return used, raw, innovation, matched, and traffic fields. + + When ``neutral_projection`` is enabled, every rule pays for and + reports the same instruction-off projection. Only the innovation + rule applies the subtractive direction. Raw keeps the unmodified + apical vector, while matched keeps its direction and borrows only the + projected innovation norm. This makes raw and matched strict sham + controls for the subtractive operation without changing their local + information or observation budget. + """ if rule not in ("raw", "matched", "innovation"): raise ValueError(f"unknown mixed-traffic rule: {rule}") if not (len(instruction) == len(hiddens) == self.n_hidden): @@ -829,9 +838,6 @@ class CIFARKPMixedTrafficResNet(CIFARKPResNet): projected_neutral = None projection_report = None if neutral_projection: - if rule != "innovation": - raise ValueError( - "neutral projection is defined only for innovation") projected_neutral, projection_report = ( self.neutral_residual_projection(hiddens)) raw = [] |
