22
33Runs three sub-experiments to test whether the kernel-size-3 spatial-conv
44DistinguisherSpatial architecture surfaces signal at depths that collapsed
5- with the v1 1×1 -conv Distinguisher:
5+ with the v1 1x1 -conv Distinguisher:
66
77 1. Δ search at depth 56 (control — should reproduce v1's signal,
88 confirming v2 isn't broken).
2727from keeloq .neural .differences import _default_candidate_set
2828from keeloq .neural .distinguisher_v2 import DistinguisherSpatial
2929
30-
3130# ---------- Standalone training loop (uses v2 architecture) ----------
3231
3332
@@ -42,14 +41,23 @@ def _set_seeds(seed: int) -> None:
4241 random .seed (seed )
4342
4443
45- def _val_accuracy (model : nn .Module , rounds : int , delta : int , seed : int ,
46- n_samples : int = 5000 , batch_size : int = 1024 ) -> float :
44+ def _val_accuracy (
45+ model : nn .Module ,
46+ rounds : int ,
47+ delta : int ,
48+ seed : int ,
49+ n_samples : int = 5000 ,
50+ batch_size : int = 1024 ,
51+ ) -> float :
4752 model .train (False )
4853 correct , total = 0 , 0
4954 with torch .no_grad ():
5055 for batch in generate_pairs (
51- rounds = rounds , delta = delta , n_samples = n_samples ,
52- seed = seed , batch_size = min (batch_size , n_samples ),
56+ rounds = rounds ,
57+ delta = delta ,
58+ n_samples = n_samples ,
59+ seed = seed ,
60+ batch_size = min (batch_size , n_samples ),
5361 ):
5462 preds = (model (batch .pairs ) >= 0.5 ).float ()
5563 correct += (preds == batch .labels ).sum ().item ()
@@ -84,8 +92,11 @@ def train_v2(
8492 for epoch in range (epochs ):
8593 loss_sum , n_batches = 0.0 , 0
8694 for batch in generate_pairs (
87- rounds = rounds , delta = delta , n_samples = n_samples ,
88- seed = seed + epoch * 991 , batch_size = batch_size ,
95+ rounds = rounds ,
96+ delta = delta ,
97+ n_samples = n_samples ,
98+ seed = seed + epoch * 991 ,
99+ batch_size = batch_size ,
89100 ):
90101 opt .zero_grad ()
91102 preds = model (batch .pairs )
@@ -96,11 +107,13 @@ def train_v2(
96107 loss_sum += float (loss .item ())
97108 n_batches += 1
98109 val_acc = _val_accuracy (model , rounds , delta , seed = seed + 1_000_000 )
99- history .append ({
100- "epoch" : epoch ,
101- "train_loss" : loss_sum / max (1 , n_batches ),
102- "val_accuracy" : val_acc ,
103- })
110+ history .append (
111+ {
112+ "epoch" : epoch ,
113+ "train_loss" : loss_sum / max (1 , n_batches ),
114+ "val_accuracy" : val_acc ,
115+ }
116+ )
104117 return model , {
105118 "final_loss" : history [- 1 ]["train_loss" ],
106119 "final_val_accuracy" : history [- 1 ]["val_accuracy" ],
@@ -134,17 +147,25 @@ def search_delta_v2(
134147 results = []
135148 for i , delta in enumerate (uniq ):
136149 _ , res = train_v2 (
137- rounds = rounds , delta = delta ,
138- n_samples = tiny_budget_samples , batch_size = 1024 ,
139- epochs = tiny_budget_epochs , lr = 2e-3 , weight_decay = 1e-5 ,
140- seed = seed + i * 7919 , depth = depth , width = width ,
150+ rounds = rounds ,
151+ delta = delta ,
152+ n_samples = tiny_budget_samples ,
153+ batch_size = 1024 ,
154+ epochs = tiny_budget_epochs ,
155+ lr = 2e-3 ,
156+ weight_decay = 1e-5 ,
157+ seed = seed + i * 7919 ,
158+ depth = depth ,
159+ width = width ,
141160 kernel_size = kernel_size ,
142161 )
143- results .append ({
144- "delta" : delta ,
145- "val_accuracy" : res ["final_val_accuracy" ],
146- "training_loss_final" : res ["final_loss" ],
147- })
162+ results .append (
163+ {
164+ "delta" : delta ,
165+ "val_accuracy" : res ["final_val_accuracy" ],
166+ "training_loss_final" : res ["final_loss" ],
167+ }
168+ )
148169 results .sort (key = lambda c : c ["val_accuracy" ], reverse = True )
149170 return results
150171
@@ -166,57 +187,82 @@ def main() -> None:
166187 cands_56 = search_delta_v2 (rounds = 56 , tiny_budget_samples = 100_000 , tiny_budget_epochs = 2 , seed = 0 )
167188 elapsed_56 = time .perf_counter () - t0
168189 best_56 = cands_56 [0 ]
169- lines .append (f "## Control: Δ search at depth 56 (v1 got best 0.688)\n " )
190+ lines .append ("## Control: Δ search at depth 56 (v1 got best 0.688)\n " )
170191 lines .append (f"Wall clock: { elapsed_56 :.1f} s — top 5:\n " )
171192 lines .append ("| Δ | val_acc | loss |\n |---|---:|---:|" )
172193 for c in cands_56 [:5 ]:
173- lines .append (f"| 0x{ c ['delta' ]:08x} | { c ['val_accuracy' ]:.4f} | { c ['training_loss_final' ]:.4f} |" )
174- print (json .dumps ({"experiment" : "control_56" , "best" : best_56 , "wall_s" : elapsed_56 }), flush = True )
194+ lines .append (
195+ f"| 0x{ c ['delta' ]:08x} | { c ['val_accuracy' ]:.4f} | { c ['training_loss_final' ]:.4f} |"
196+ )
197+ print (
198+ json .dumps ({"experiment" : "control_56" , "best" : best_56 , "wall_s" : elapsed_56 }), flush = True
199+ )
175200
176201 # Experiment 2: Δ search at depth 88 (primary hypothesis).
177202 print ("\n [v2-exp] Δ search at depth 88 (primary hypothesis)..." , flush = True )
178203 t0 = time .perf_counter ()
179204 cands_88 = search_delta_v2 (rounds = 88 , tiny_budget_samples = 100_000 , tiny_budget_epochs = 2 , seed = 0 )
180205 elapsed_88 = time .perf_counter () - t0
181206 best_88 = cands_88 [0 ]
182- lines .append (f "\n ## Primary: Δ search at depth 88 (v1 all < 0.517)\n " )
207+ lines .append ("\n ## Primary: Δ search at depth 88 (v1 all < 0.517)\n " )
183208 lines .append (f"Wall clock: { elapsed_88 :.1f} s — top 10:\n " )
184209 lines .append ("| Δ | val_acc | loss |\n |---|---:|---:|" )
185210 for c in cands_88 [:10 ]:
186- lines .append (f"| 0x{ c ['delta' ]:08x} | { c ['val_accuracy' ]:.4f} | { c ['training_loss_final' ]:.4f} |" )
187- print (json .dumps ({"experiment" : "primary_88" , "best" : best_88 , "wall_s" : elapsed_88 }), flush = True )
211+ lines .append (
212+ f"| 0x{ c ['delta' ]:08x} | { c ['val_accuracy' ]:.4f} | { c ['training_loss_final' ]:.4f} |"
213+ )
214+ print (
215+ json .dumps ({"experiment" : "primary_88" , "best" : best_88 , "wall_s" : elapsed_88 }), flush = True
216+ )
188217
189218 # Experiment 3 (conditional): Δ search at depth 120.
190219 print ("\n [v2-exp] Δ search at depth 120 (stretch)..." , flush = True )
191220 t0 = time .perf_counter ()
192- cands_120 = search_delta_v2 (rounds = 120 , tiny_budget_samples = 100_000 , tiny_budget_epochs = 2 , seed = 0 )
221+ cands_120 = search_delta_v2 (
222+ rounds = 120 , tiny_budget_samples = 100_000 , tiny_budget_epochs = 2 , seed = 0
223+ )
193224 elapsed_120 = time .perf_counter () - t0
194225 best_120 = cands_120 [0 ]
195- lines .append (f "\n ## Stretch: Δ search at depth 120 (v1 all < 0.515)\n " )
226+ lines .append ("\n ## Stretch: Δ search at depth 120 (v1 all < 0.515)\n " )
196227 lines .append (f"Wall clock: { elapsed_120 :.1f} s — top 10:\n " )
197228 lines .append ("| Δ | val_acc | loss |\n |---|---:|---:|" )
198229 for c in cands_120 [:10 ]:
199- lines .append (f"| 0x{ c ['delta' ]:08x} | { c ['val_accuracy' ]:.4f} | { c ['training_loss_final' ]:.4f} |" )
200- print (json .dumps ({"experiment" : "stretch_120" , "best" : best_120 , "wall_s" : elapsed_120 }), flush = True )
230+ lines .append (
231+ f"| 0x{ c ['delta' ]:08x} | { c ['val_accuracy' ]:.4f} | { c ['training_loss_final' ]:.4f} |"
232+ )
233+ print (
234+ json .dumps ({"experiment" : "stretch_120" , "best" : best_120 , "wall_s" : elapsed_120 }),
235+ flush = True ,
236+ )
201237
202238 # Experiment 4 (conditional): if depth 88 has signal, full train.
203239 verdict_lines : list [str ] = []
204- verdict_lines .append (f "\n ## Verdict\n " )
240+ verdict_lines .append ("\n ## Verdict\n " )
205241 if best_88 ["val_accuracy" ] >= SIGNAL_THRESHOLD :
206242 verdict_lines .append (
207243 f"- Depth 88 best Δ=0x{ best_88 ['delta' ]:08x} reached val-acc "
208244 f"{ best_88 ['val_accuracy' ]:.4f} — **above the { SIGNAL_THRESHOLD } threshold**. "
209- "Spatial conv architecture surfaces signal where v1's 1×1 version failed. "
245+ "Spatial conv architecture surfaces signal where v1's 1x1 version failed. "
210246 "Proceeding with a full-scale train at this Δ.\n "
211247 )
212- print (f"\n [v2-exp] Depth 88 signal confirmed ({ best_88 ['val_accuracy' ]:.4f} ). "
213- "Kicking off full train (10M samples × 20 epochs)..." , flush = True )
248+ print (
249+ f"\n [v2-exp] Depth 88 signal confirmed ({ best_88 ['val_accuracy' ]:.4f} ). "
250+ "Kicking off full train (10M samples x 20 epochs)..." ,
251+ flush = True ,
252+ )
214253 t0 = time .perf_counter ()
215254 _ , full_res = train_v2 (
216- rounds = 88 , delta = best_88 ["delta" ],
217- n_samples = 10_000_000 , batch_size = 4096 ,
218- epochs = 20 , lr = 2e-3 , weight_decay = 1e-5 ,
219- seed = 1729 , depth = 5 , width = 256 , kernel_size = 3 ,
255+ rounds = 88 ,
256+ delta = best_88 ["delta" ],
257+ n_samples = 10_000_000 ,
258+ batch_size = 4096 ,
259+ epochs = 20 ,
260+ lr = 2e-3 ,
261+ weight_decay = 1e-5 ,
262+ seed = 1729 ,
263+ depth = 5 ,
264+ width = 256 ,
265+ kernel_size = 3 ,
220266 )
221267 verdict_lines .append (
222268 f"- Full train: val_acc={ full_res ['final_val_accuracy' ]:.4f} , "
@@ -230,7 +276,7 @@ def main() -> None:
230276 f"{ best_88 ['val_accuracy' ]:.4f} — **below the { SIGNAL_THRESHOLD } threshold**. "
231277 "Spatial conv architecture *also* fails to surface signal at depth 88. "
232278 "This tightens the negative result from 'v1 architecture fails' to "
233- "'both 1×1 and spatial 3-tap architectures fail' — suggesting the "
279+ "'both 1x1 and spatial 3-tap architectures fail' — suggesting the "
234280 "signal horizon is a genuine property of KeeLoq's diffusion at these "
235281 "depths, not an artifact of any one architecture.\n "
236282 )
0 commit comments