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