@@ -51,8 +51,8 @@ def fashion_mnist():
5151 images = ds ["images" ].astype ("float64" ).values
5252 labels = ds ["labels" ].values .astype ("int64" )
5353 except Exception :
54- # Offline fallback: a separable synthetic set (per-class template + noise),
55- # so the same pipeline still learns without the network.
54+ # Offline fallback: a separable synthetic set (per-class template +
55+ # noise), so the same pipeline still learns without the network.
5656 rng = np .random .default_rng (0 )
5757 n = N_TRAIN + N_TEST
5858 templates = rng .standard_normal ((10 , SIDE , SIDE ))
@@ -115,7 +115,7 @@ def main():
115115 },
116116 )
117117
118- frac = N_TRAIN / (N_TRAIN + N_TEST ) # ratio: ~0.7
118+ frac = N_TRAIN / (N_TRAIN + N_TEST ) # default ratio: ~0.7
119119 # Train-test split
120120 data = ctx .sql (f"""
121121 SELECT sample,
@@ -130,7 +130,7 @@ def main():
130130 ).to_pandas ()["n" ][0 ]
131131
132132 def init_weight (inp : int , out : int ):
133- """Small random weights over ``inp`` inputs, with a zero bias row appended."""
133+ """Small random weights with a zero bias row appended."""
134134 weight = rng .standard_normal ((inp , out )) * 0.1
135135 bias = np .zeros ((1 , out ))
136136 return np .concatenate ((weight , bias ), axis = 0 ) # (inp + 1, out)
@@ -161,20 +161,23 @@ def init_weight(inp: int, out: int):
161161 # Each layer augments its activation with a constant-1 bias unit (
162162 # index = width), contracts with the weight table (JOIN on the shared
163163 # index + grouped SUM), and keeps the pre-activation z (tanh(z) for
164- # hidden, softmax later ). .cache() materialises each stage so the
164+ # hidden, linear output ). .cache() materialises each stage so the
165165 # per-step plan stays flat.
166+ #
167+ # The forward runs over ALL samples: train rows drive learning, test
168+ # rows ride along so we can score them from the same logits. Only delta2
169+ # is restricted to train, so the gradients (and the trained weights) are
170+ # identical to a train-only forward — test is never backpropagated.
166171 fwd0 = ctx .sql (f"""
167172 WITH a AS (
168173 SELECT sample, height * { SIDE } + width AS inp, images AS val
169174 FROM mnist.pixels
170- WHERE sample IN (SELECT sample FROM data WHERE split = 'train')
171175 UNION ALL
172176 -- the constant-1 bias unit
173177 SELECT sample,
174178 (SELECT DISTINCT width FROM weight WHERE layer = 0) AS inp,
175179 1.0 AS val
176180 FROM mnist.labels
177- WHERE sample IN (SELECT sample FROM data WHERE split = 'train')
178181 )
179182 SELECT a.sample, w.out AS out, SUM(a.val * w.val) AS z,
180183 tanh(SUM(a.val * w.val)) AS val
@@ -230,6 +233,8 @@ def init_weight(inp: int, out: int):
230233 e.e / s.s - CASE WHEN e.out = y.labels THEN 1.0 ELSE 0.0 END AS val
231234 FROM e JOIN s ON e.sample = s.sample
232235 JOIN mnist.labels y ON y.sample = e.sample
236+ -- restrict the error to train, so every downstream gradient is train-only
237+ WHERE e.sample IN (SELECT sample FROM data WHERE split = 'train')
233238 """ ).cache ()
234239 ctx .deregister_table ("delta2" )
235240 ctx .register_table ("delta2" , delta2 )
@@ -337,6 +342,7 @@ def init_weight(inp: int, out: int):
337342 ctx .register_table ("weight" , w )
338343
339344 if step % 5 == 0 or step == STEPS - 1 :
345+ # Train cross-entropy (logits span all samples, so filter to train).
340346 loss = ctx .sql (f"""
341347 WITH m AS (SELECT sample, MAX(z) AS m FROM logits GROUP BY sample),
342348 e AS (SELECT logits.sample, logits.out, exp(logits.z - m.m) AS e
@@ -346,17 +352,30 @@ def init_weight(inp: int, out: int):
346352 FROM e JOIN s ON e.sample = s.sample
347353 JOIN mnist.labels y ON y.sample = e.sample
348354 WHERE e.out = y.labels
355+ AND e.sample IN (SELECT sample FROM data WHERE split = 'train')
349356 """ ).to_pandas ()["loss" ][0 ]
350- acc = ctx .sql (f"""
357+ # Accuracy per split: argmax the shared logits, join the split label.
358+ # Both come from the one all-samples forward — no second pass.
359+ acc = (
360+ ctx .sql (f"""
351361 WITH pred AS (
352362 SELECT sample, out,
353363 ROW_NUMBER() OVER (PARTITION BY sample ORDER BY z DESC) AS rk
354364 FROM logits)
355- SELECT AVG(CASE WHEN p.out = y.labels THEN 1.0 ELSE 0.0 END) AS acc
365+ SELECT d.split,
366+ AVG(CASE WHEN p.out = y.labels THEN 1.0 ELSE 0.0 END) AS acc
356367 FROM pred p JOIN mnist.labels y ON p.sample = y.sample
368+ JOIN data d ON d.sample = p.sample
357369 WHERE p.rk = 1
358- """ ).to_pandas ()["acc" ][0 ]
359- print (f"step { step :2d} : loss { loss :.3f} train_acc { acc :.3f} " )
370+ GROUP BY d.split
371+ """ )
372+ .to_pandas ()
373+ .set_index ("split" )["acc" ]
374+ )
375+ print (
376+ f"step { step :2d} : loss { loss :.3f} "
377+ f"train_acc { acc ['train' ]:.3f} test_acc { acc ['test' ]:.3f} "
378+ )
360379
361380 # The trained weights come back out as xarray as one relation: a ragged
362381 # weight(layer, inp, out) array (absent cells are NaN where layers are narrower).
0 commit comments