Chapter 32: Example 2 Model
What is the “Example 2 Model”?
Example 2 Model = a small but powerful convolutional neural network (CNN) designed specifically for classifying 28×28 grayscale handwritten digits (MNIST dataset).
The code that defines it (this is the heart of every beginner TensorFlow.js MNIST tutorial):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
const model = tf.sequential(); model.add(tf.layers.conv2d({ inputShape: [28, 28, 1], filters: 32, kernelSize: 3, activation: 'relu' })); model.add(tf.layers.maxPooling2d({poolSize: [2, 2]})); model.add(tf.layers.conv2d({ filters: 64, kernelSize: 3, activation: 'relu' })); model.add(tf.layers.maxPooling2d({poolSize: [2, 2]})); model.add(tf.layers.flatten()); model.add(tf.layers.dense({units: 128, activation: 'relu'})); model.add(tf.layers.dropout({rate: 0.2})); model.add(tf.layers.dense({units: 10, activation: 'softmax'})); |
This is the classic Example 2 Model — almost every tutorial uses almost exactly this architecture (sometimes minor variations like more/less filters or epochs).
Why This Exact Model Architecture?
It’s a deliberately simple yet effective CNN that teaches the core ideas without overwhelming you:
| Layer Type | Purpose (what it learns) | Why we need it | Output shape after this layer (batch ignored) |
|---|---|---|---|
| Conv2D (32 filters) | Learn basic edges, lines, curves in small 3×3 patches | Detects simple patterns (horizontal/vertical lines) | [26, 26, 32] |
| MaxPooling2D (2×2) | Reduce size, keep strongest signals, translation invariance | Makes model smaller + more robust to small shifts | [13, 13, 32] |
| Conv2D (64 filters) | Learn more complex shapes (corners, loops, intersections) | Combines simple edges into digit parts | [11, 11, 64] |
| MaxPooling2D (2×2) | Further reduce size, focus on important features | Reduces computation, prevents overfitting | [5, 5, 64] |
| Flatten | Convert 3D feature map to 1D vector | Prepare for dense classification layers | [1600] (5×5×64 = 1600) |
| Dense (128 units) | Combine high-level features into decisions | Learn digit-specific combinations | [128] |
| Dropout (0.2) | Randomly ignore 20% neurons during training | Prevent overfitting, force robust learning | [128] |
| Dense (10 units) | Final classification — 10 possible digits | Output probabilities for each class | [10] |
Total Trainable Parameters (Roughly)
- Conv1: 3×3×1×32 + 32 biases ≈ 320
- Conv2: 3×3×32×64 + 64 biases ≈ 18,496
- Dense 128: 1600×128 + 128 biases ≈ 204,928
- Dense 10: 128×10 + 10 biases ≈ 1,290
Total ≈ 225,000 parameters — small by 2026 standards, but enough to reach 97–99% accuracy on MNIST.
Full Runnable Code for Example 2 Model (with Training & Prediction)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>TensorFlow.js Example 2 Model – MNIST CNN</title> <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest/dist/tf.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-vis@latest"></script> <style> body { font-family: Arial, sans-serif; text-align: center; padding: 40px; background: #f8f9fa; } h1 { color: #4285f4; } button { padding: 14px 32px; font-size: 1.3em; background: #4285f4; color: white; border: none; border-radius: 8px; cursor: pointer; margin: 20px; } button:hover { background: #3367d6; } #status { font-size: 1.5em; margin: 30px; min-height: 60px; color: #444; } #log { background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 10px; max-width: 900px; margin: 20px auto; text-align: left; font-family: monospace; max-height: 400px; overflow-y: auto; white-space: pre-wrap; } canvas { border: 3px solid #4285f4; border-radius: 10px; margin: 30px; background: black; } #prediction { font-size: 3em; margin: 30px; color: #4285f4; font-weight: bold; } </style> </head> <body> <h1>TensorFlow.js Example 2 Model: Handwritten Digit CNN</h1> <p style="max-width:800px; margin:20px auto; font-size:1.1em;"> This is the classic Example 2 model — a small CNN that learns to recognize digits 0–9.<br> Train it → draw your own digit → let it guess! </p> <button onclick="trainModel()">Train the Example 2 Model (1–3 min)</button> <div id="status">Waiting...</div> <div id="log">Training log will appear here...\n</div> <h3>Draw a digit (0–9) here</h3> <canvas id="canvas" width="280" height="280"></canvas><br> <button onclick="clearCanvas()">Clear</button> <button onclick="predictDigit()" disabled>Predict</button> <div id="prediction"></div> <script> let model; let isDrawing = false; const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); ctx.lineWidth = 24; ctx.lineCap = 'round'; ctx.strokeStyle = 'white'; canvas.addEventListener('mousedown', () => isDrawing = true); canvas.addEventListener('mouseup', () => { isDrawing = false; ctx.beginPath(); }); canvas.addEventListener('mousemove', (e) => { if (!isDrawing) return; ctx.lineTo(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop); ctx.stroke(); ctx.beginPath(); ctx.moveTo(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop); }); function clearCanvas() { ctx.fillStyle = 'black'; ctx.fillRect(0,0,canvas.width,canvas.height); } clearCanvas(); function log(msg) { console.log(msg); document.getElementById('log').innerHTML += msg + '\n'; document.getElementById('log').scrollTop = document.getElementById('log').scrollHeight; } async function trainModel() { const status = document.getElementById('status'); status.innerHTML = 'Loading MNIST...'; const {train, test} = await tf.data.mnist(); const trainData = train.map(({xs, ys}) => ({ xs: xs.reshape([28, 28, 1]).div(255.0), ys: ys })).shuffle(1000).batch(32); const testData = test.map(({xs, ys}) => ({ xs: xs.reshape([28, 28, 1]).div(255.0), ys: ys })).batch(32); status.innerHTML = 'Building Example 2 Model (CNN)...'; model = tf.sequential(); // Conv layer 1 – learns basic edges model.add(tf.layers.conv2d({ inputShape: [28, 28, 1], filters: 32, kernelSize: 3, activation: 'relu' })); model.add(tf.layers.maxPooling2d({poolSize: [2, 2]})); // Conv layer 2 – learns more complex shapes model.add(tf.layers.conv2d({ filters: 64, kernelSize: 3, activation: 'relu' })); model.add(tf.layers.maxPooling2d({poolSize: [2, 2]})); model.add(tf.layers.flatten()); // Dense – combines features model.add(tf.layers.dense({units: 128, activation: 'relu'})); model.add(tf.layers.dropout({rate: 0.2})); // Output – 10 digit classes model.add(tf.layers.dense({units: 10, activation: 'softmax'})); model.compile({ optimizer: 'adam', loss: 'categoricalCrossentropy', metrics: ['accuracy'] }); log("Example 2 Model built:"); model.summary(); status.innerHTML = 'Training... (watch Visor + log)'; const surface = tfvis.visor().surface({tab: 'Example 2 Training', name: 'Metrics'}); await model.fitDataset(trainData, { epochs: 6, validationData: testData, callbacks: tfvis.show.fitCallbacks(surface, ['loss', 'val_loss', 'acc', 'val_acc']) }); status.innerHTML = 'Training done! Accuracy usually 97–99%'; log("Training finished – check Visor for live graphs"); document.querySelector('button[onclick="predictDigit()"]').disabled = false; } async function predictDigit() { if (!model) return alert("Train first!"); let img = tf.browser.fromPixels(canvas, 1) .resizeBilinear([28, 28]) .mean(2) .expandDims() .expandDims(-1) .div(255.0); const pred = model.predict(img); const digit = (await pred.argMax(-1).data())[0]; document.getElementById('prediction').innerHTML = `Predicted: ${digit}`; img.dispose(); pred.dispose(); } </script> </body> </html> |
Final Teacher Summary
Example 2 Model = the small CNN used in the second TensorFlow.js tutorial — 2 convolutional blocks + pooling + dense layers — trained to recognize handwritten digits 0–9 from 28×28 images.
- Conv layers → learn edges → shapes → digit parts
- Pooling → reduce size, add shift invariance
- Dense + softmax → final decision (10 classes)
- Reaches 97–99% accuracy in browser
This model teaches you real deep learning — convolutions, feature hierarchy, classification, and why CNNs beat simple dense networks on images.
Got the full picture? 🌟
Want next?
- Improve it to 99.2%+?
- Visualize learned filters (what edges it detects)?
- Convert this model to TensorFlow Lite / tfjs for mobile/web app?
Just say — next class is ready! 🚀
