sourcedemos/cnn-digits/cnn-digits.xtl
1⍝!/usr/bin/env xetal
2⍝# A tiny convolutional network reads a handwritten digit: 28 x 28 ->
3⍝# 8 filters of 3 x 3 -> ReLU -> 2 x 2 max-pooling -> dense -> softmax.
4⍝# The convolution is the picture's nine shifted copies times the
5⍝# filters: one matrix product. Trained offline on MNIST (97.82% of
6⍝# the 10,000 test digits right); just cnn-train writes data/.
7
8ⁿⁿ⁼u̲se< "NN"
9
10⍝## The network, read from data/
11fb ← 8 10 r̲eshape n̲umbers ⎕N̲GET "data/filters.txt"
12k9 ← 9 t̲ake₂ fb ⍝ 8 filters, 3 x 3 each as 9
13bc ← r̲avel -1 t̲ake₂ fb ⍝ their biases
14wb ← 1353 10 r̲eshape n̲umbers ⎕N̲GET "data/dense.txt"
15samples ← 10 28 28 r̲eshape n̲umbers ⎕N̲GET "data/samples.txt"
16
17⍝## The core: every stage one array expression
18ᵘc̲onv ← { x →
19 v ← 1 d̲rop₄ -1 d̲rop₄ 1 d̲rop₃ -1 d̲rop₃ -1 0 1 o̲-₂ -1 0 1 o̲-₂ x
20 8 26 26 r̲eshape (k9 '+ '× i̲nner 9 676 r̲eshape v) + bc 'l̲eft t̲able o̲ffsets 676
21}
22ᵘp̲ool ← { x → 'm̲ax r̲/₃₅ 8 13 2 13 2 r̲eshape x }
23ᵘc̲lassify ← { x → f̲irst ⁿⁿs̲oftmax (1 1352 r̲eshape r̲avel ᵘp̲ool ⁿⁿr̲elu ᵘc̲onv x) ⁿⁿd̲ense wb }
24⍝ -- end of the core -------------------------------------
25
26⍝## Drawing: values as shades, maps side by side
27ᵘs̲hade ← { m → (1 + f̲loor 0 m̲ax 7 m̲in 8.0 × m) s̲elect " .:-=+*#" }
28⍝# Each of n maps scaled from its own smallest to largest value, then shaded.
29ᵘm̲aps ← { m →
30 n ← 1 t̲ake s̲hape m
31 e ← '× r̲/ 1 d̲rop s̲hape m
32 f ← (n c̲at e) r̲eshape m
33 lo ← (n c̲at e) r̲eshape (n r̲eshape e) r̲eplicate 'm̲in r̲/₂ f
34 hi ← (n c̲at e) r̲eshape (n r̲eshape e) r̲eplicate 'm̲ax r̲/₂ f
35 ᵘs̲hade (s̲hape m) r̲eshape (f − lo) ÷ 0.000001 m̲ax hi − lo
36}
37ᵘw̲ide ← { c → ((-1 t̲ake s̲hape c) r̲eshape 2) r̲eplicate₂ c }
38ᵘs̲ide ← { c →
39 g ← c c̲at₃ ((2 t̲ake s̲hape c) c̲at 2) r̲eshape " "
40 ((1 s̲elect 1 d̲rop s̲hape g) c̲at '× r̲/ 1 0 1 r̲eplicate s̲hape g) r̲eshape 2 1 3 t̲ranspose g
41}
42
43⍝# A test digit: a 7.
44x ← 8 s̲elect samples
45ᵘw̲ide 28 28 r̲eshape ᵘs̲hade x
46⍝ The 8 filters (3 x 3), from most negative to most positive.
47ᵘs̲ide (1 + f̲loor 0 m̲ax 4 m̲in 2.5 + 2.0 × 8 3 3 r̲eshape k9) s̲elect "=-.+#"
48⍝# Convolution and ReLU: 8 maps of 26 x 26, what each filter finds.
49c ← ⁿⁿr̲elu ᵘc̲onv x
50ᵘs̲ide ᵘm̲aps 4 t̲ake c
51ᵘs̲ide ᵘm̲aps 4 d̲rop c
52⍝# Max-pooling: 8 maps of 13 x 13 (drawn twice as wide).
53m ← ᵘp̲ool c
54ᵘw̲ide ᵘs̲ide ᵘm̲aps 4 t̲ake m
55ᵘw̲ide ᵘs̲ide ᵘm̲aps 4 d̲rop m
56⍝# The ten probabilities, as bars: the network reads a 7.
57p ← ᵘc̲lassify x
58ᵘb̲ars ← { p → (1 + (f̲loor 0.5 + 50.0 × p) '> t̲able o̲ffsets 50) s̲elect " #" }
59(10 1 r̲eshape "0123456789") c̲at₂ (10 2 r̲eshape " ") c̲at₂ ᵘb̲ars p
60f̲loor 0.5 + 100.0 × p
61⍝# The ten test digits (0 to 9): what the network reads, how sure (%).
62ᵘr̲ead ← { i → (ⁿⁿa̲rgmax ᵘc̲lassify i s̲elect samples) − 1 }
63ᵘs̲ure ← { i → f̲loor 0.5 + 100.0 × 'm̲ax r̲/ ᵘc̲lassify i s̲elect samples }
64'ᵘr̲ead e̲ach r̲ange 10
65'ᵘs̲ure e̲ach r̲ange 10
66⍝# The same probabilities as the trainer's Rust forward pass, for all ten?
67expected ← 10 10 r̲eshape n̲umbers ⎕N̲GET "data/expected.txt"
68ᵘg̲ap ← { i → 'm̲ax r̲/ a̲bs (ᵘc̲lassify i s̲elect samples) − i s̲elect expected }
691e-9 > 'm̲ax r̲/ 'ᵘg̲ap e̲ach r̲ange 10