@@ -115,6 +115,20 @@ def main():
115115 },
116116 )
117117
118+ frac = N_TRAIN / (N_TRAIN + N_TEST ) # ratio: ~0.7
119+ # Train-test split
120+ data = ctx .sql (f"""
121+ SELECT sample,
122+ CASE WHEN random() < { frac } THEN 'train' ELSE 'test' END AS split
123+ FROM mnist.labels
124+ """ ).cache ()
125+ ctx .register_table ("data" , data )
126+ # The gradient averages over the actual train count (random, ~frac * N),
127+ # read once from the materialized split.
128+ n_train = ctx .sql (
129+ "SELECT COUNT(*) AS n FROM data WHERE split = 'train'"
130+ ).to_pandas ()["n" ][0 ]
131+
118132 def init_weight (inp : int , out : int ):
119133 """Small random weights over ``inp`` inputs, with a zero bias row appended."""
120134 weight = rng .standard_normal ((inp , out )) * 0.1
@@ -142,23 +156,25 @@ def init_weight(inp: int, out: int):
142156
143157 for step in range (STEPS ):
144158 #
145- # --- forward pass ---------------------------------------------------------
146- #
147- # Each layer augments its activation with a constant-1 bias unit (index =
148- # width), contracts with the weight table (JOIN on the shared index + grouped
149- # SUM), and keeps the pre-activation z (tanh(z) for hidden, softmax later).
150- # .cache() materialises each stage so the per-step plan stays flat.
159+ # --- forward pass -----------------------------------------------------
151160 #
161+ # Each layer augments its activation with a constant-1 bias unit (
162+ # index = width), contracts with the weight table (JOIN on the shared
163+ # index + grouped SUM), and keeps the pre-activation z (tanh(z) for
164+ # hidden, softmax later). .cache() materialises each stage so the
165+ # per-step plan stays flat.
152166 fwd0 = ctx .sql (f"""
153167 WITH a AS (
154168 SELECT sample, height * { SIDE } + width AS inp, images AS val
155- FROM mnist.pixels WHERE sample < { N_TRAIN }
169+ FROM mnist.pixels
170+ WHERE sample IN (SELECT sample FROM data WHERE split = 'train')
156171 UNION ALL
157172 -- the constant-1 bias unit
158173 SELECT sample,
159174 (SELECT DISTINCT width FROM weight WHERE layer = 0) AS inp,
160175 1.0 AS val
161- FROM mnist.labels WHERE sample < { N_TRAIN }
176+ FROM mnist.labels
177+ WHERE sample IN (SELECT sample FROM data WHERE split = 'train')
162178 )
163179 SELECT a.sample, w.out AS out, SUM(a.val * w.val) AS z,
164180 tanh(SUM(a.val * w.val)) AS val
@@ -200,10 +216,11 @@ def init_weight(inp: int, out: int):
200216 ctx .deregister_table ("logits" )
201217 ctx .register_table ("logits" , logits )
202218 #
203- # --- backward pass --------------------------------------------------------
219+ # --- backward pass ----------------------------------------------------
204220 #
205- # Output error delta2 = softmax(logits) - onehot(label). The one hand-derived
206- # rule: softmax couples classes through a per-sample normaliser.
221+ # Output error delta2 = softmax(logits) - onehot(label). The one
222+ # hand-derived rule: softmax couples classes through a per-sample
223+ # normaliser.
207224 delta2 = ctx .sql (f"""
208225 WITH m AS (SELECT sample, MAX(z) AS m FROM logits GROUP BY sample),
209226 e AS (SELECT logits.sample, logits.out, exp(logits.z - m.m) AS e
@@ -212,13 +229,14 @@ def init_weight(inp: int, out: int):
212229 SELECT e.sample, e.out,
213230 e.e / s.s - CASE WHEN e.out = y.labels THEN 1.0 ELSE 0.0 END AS val
214231 FROM e JOIN s ON e.sample = s.sample
215- JOIN mnist.labels y ON y.sample = e.sample AND y.sample < { N_TRAIN }
232+ JOIN mnist.labels y ON y.sample = e.sample
216233 """ ).cache ()
217234 ctx .deregister_table ("delta2" )
218235 ctx .register_table ("delta2" , delta2 )
219236
220- # Weight gradient of layer 2: (bias-augmented fwd1).T @ delta2 / N. The bias
221- # row (inp = width) falls out for free — its gradient is the mean error.
237+ # Weight gradient of layer 2: (bias-augmented fwd1).T @ delta2 / N.
238+ # The bias row (inp = width) falls out for free — its gradient is the
239+ # mean error.
222240 g2 = ctx .sql (f"""
223241 WITH a AS (
224242 SELECT sample, out AS inp, val FROM fwd1
@@ -227,15 +245,16 @@ def init_weight(inp: int, out: int):
227245 (SELECT DISTINCT width FROM weight WHERE layer = 2) AS inp,
228246 1.0 AS val FROM fwd1
229247 )
230- SELECT a.inp AS inp, d.out AS out, SUM(a.val * d.val) / { N_TRAIN } AS val
248+ SELECT a.inp AS inp, d.out AS out, SUM(a.val * d.val) / { n_train } AS val
231249 FROM a JOIN delta2 d ON a.sample = d.sample
232250 GROUP BY a.inp, d.out
233251 """ ).cache ()
234252 ctx .deregister_table ("g2" )
235253 ctx .register_table ("g2" , g2 )
236254
237- # Propagate to layer 1: delta1 = (delta2 @ W2[non-bias].T) * tanh'(z1). The
238- # local derivative is grad(tanh(z), z) at fwd1's pre-activation.
255+ # Propagate to layer 1: delta1 = (delta2 @ W2[non-bias].T) * tanh'(
256+ # z1). The local derivative is grad(tanh(z), z) at fwd1's
257+ # pre-activation.
239258 delta1 = ctx .sql (f"""
240259 WITH dc AS (
241260 SELECT d.sample, w.inp AS out, SUM(d.val * w.val) AS val
@@ -258,7 +277,7 @@ def init_weight(inp: int, out: int):
258277 (SELECT DISTINCT width FROM weight WHERE layer = 1) AS inp,
259278 1.0 AS val FROM fwd0
260279 )
261- SELECT a.inp AS inp, d.out AS out, SUM(a.val * d.val) / { N_TRAIN } AS val
280+ SELECT a.inp AS inp, d.out AS out, SUM(a.val * d.val) / { n_train } AS val
262281 FROM a JOIN delta1 d ON a.sample = d.sample
263282 GROUP BY a.inp, d.out
264283 """ ).cache ()
@@ -283,24 +302,27 @@ def init_weight(inp: int, out: int):
283302 g0 = ctx .sql (f"""
284303 WITH a AS (
285304 SELECT sample, height * { SIDE } + width AS inp, images AS val
286- FROM mnist.pixels WHERE sample < { N_TRAIN }
305+ FROM mnist.pixels
306+ WHERE sample IN (SELECT sample FROM data WHERE split = 'train')
287307 UNION ALL
288- SELECT sample, (SELECT DISTINCT width FROM weight WHERE layer = 0) AS inp,
308+ SELECT sample,
309+ (SELECT DISTINCT width FROM weight WHERE layer = 0) AS inp,
289310 1.0 AS val
290- FROM mnist.labels WHERE sample < { N_TRAIN }
311+ FROM mnist.labels
312+ WHERE sample IN (SELECT sample FROM data WHERE split = 'train')
291313 )
292- SELECT a.inp AS inp, d.out AS out, SUM(a.val * d.val) / { N_TRAIN } AS val
314+ SELECT a.inp AS inp, d.out AS out, SUM(a.val * d.val) / { n_train } AS val
293315 FROM a JOIN delta0 d ON a.sample = d.sample
294316 GROUP BY a.inp, d.out
295317 """ ).cache ()
296318 ctx .deregister_table ("g0" )
297319 ctx .register_table ("g0" , g0 )
298320
299321 #
300- # --- SGD update: one query over the whole relation -----------------------
322+ # --- SGD update: one query over the whole relation --------------------
301323 #
302- # weight <- weight - lr * gradient, joining every layer at once against the
303- # per-layer gradients tagged with their layer index.
324+ # weight <- weight - lr * gradient, joining every layer at once
325+ # against the per-layer gradients tagged with their layer index.
304326 w = ctx .sql (f"""
305327 WITH grad AS (
306328 SELECT 0 AS layer, inp, out, val FROM g0
@@ -322,7 +344,7 @@ def init_weight(inp: int, out: int):
322344 s AS (SELECT sample, SUM(e) AS s FROM e GROUP BY sample)
323345 SELECT -AVG(ln(e.e / s.s)) AS loss
324346 FROM e JOIN s ON e.sample = s.sample
325- JOIN mnist.labels y ON y.sample = e.sample AND y.sample < { N_TRAIN }
347+ JOIN mnist.labels y ON y.sample = e.sample
326348 WHERE e.out = y.labels
327349 """ ).to_pandas ()["loss" ][0 ]
328350 acc = ctx .sql (f"""
@@ -346,7 +368,8 @@ def init_weight(inp: int, out: int):
346368 print (f"trained { WIDTHS } MLP; weights -> xarray { dict (trained .sizes )} ." )
347369 print (trained )
348370 trained .to_zarr (
349- f"fashion_mnist_mlp_{ datetime .datetime .now ().isoformat (timespec = 'minutes' )} .zarr"
371+ f"fashion_mnist_mlp_"
372+ f"{ datetime .datetime .now ().isoformat (timespec = 'seconds' )} .zarr"
350373 )
351374
352375
0 commit comments