Neural Networks
Tensors
Metrics
Datasets
Preprocessing
Neural Network Image Classifier
A neural image classification project built with Deepbox, demonstrating an end-to-end MLP workflow for the digits dataset. It combines deepbox/nn, deepbox/ndarray, deepbox/metrics, deepbox/datasets, deepbox/preprocess, deepbox/plot to deliver a larger production-style Deepbox workflow with reproducible outputs and documented architecture.
Features
- Model Architecture: Multi-layer perceptron (MLP) with fully connected layers and activations (ReLU, GELU, or LeakyReLU)
- Data Pipeline: Dataset loading, train/test split, feature scaling
- Evaluation Metrics: Precision, recall, F1-score, confusion matrix
- Visualizations: Training loss and accuracy curves
Deepbox Modules Used
deepbox/nndeepbox/ndarraydeepbox/metricsdeepbox/datasetsdeepbox/preprocessdeepbox/plotProject Architecture
- 02-neural-image-classifier/
- ├── index.ts # Main entry point
- ├── README.md # This file
- └── src/
- ├── models.ts # MLP model definitions
- └── trainer.ts # Training helpers
Source Files
index.ts
1/**2 * Neural Network Image Classifier3 *4 * A demonstration of neural network training for image classification5 * using the Deepbox Framework's deep learning modules.6 *7 * Deepbox Modules Used:8 * - deepbox/nn: Neural network layers, Sequential, loss functions9 * - deepbox/ndarray: GradTensor, autograd, tensor operations10 * - deepbox/datasets: loadDigits dataset11 * - deepbox/metrics: Classification metrics12 * - deepbox/preprocess: train/test split13 */1415import { existsSync, mkdirSync, writeFileSync } from "node:fs";16import { isNumericTypedArray, isTypedArray } from "deepbox/core";17import { loadDigits } from "deepbox/datasets";18import { confusionMatrix, f1Score, precision, recall } from "deepbox/metrics";19import { GradTensor, type Tensor, tensor } from "deepbox/ndarray";20import { crossEntropyLoss } from "deepbox/nn";21import { Adam } from "deepbox/optim";22import { Figure } from "deepbox/plot";23import { StandardScaler, trainTestSplit } from "deepbox/preprocess";24import { setSeed } from "deepbox/random";2526import { createModel, createSimpleMLP, getModelSummary } from "./src/models";2728// ============================================================================29// Configuration30// ============================================================================3132const OUTPUT_DIR = "docs/projects/02-neural-image-classifier/output";33const NUM_CLASSES = 10;34const LEARNING_RATE = 0.001;35const NUM_EPOCHS = 40;36const BATCH_SIZE = 32;3738// ============================================================================39// Main Execution40// ============================================================================4142console.log("═".repeat(70));43console.log(" NEURAL NETWORK IMAGE CLASSIFIER");44console.log(" Built with Deepbox — TypeScript toolkit for AI & numerical computing");45console.log("═".repeat(70));4647setSeed(42);4849// Create output directory50if (!existsSync(OUTPUT_DIR)) {51 mkdirSync(OUTPUT_DIR, { recursive: true });52}5354// ============================================================================55// Step 1: Load and Prepare Data56// ============================================================================5758console.log("\n📊 STEP 1: Loading Digits Dataset");59console.log("─".repeat(70));6061const digits = loadDigits();62console.log(`✓ Loaded digits dataset`);63console.log(` Samples: ${digits.data.shape[0]}`);64console.log(` Features: ${digits.data.shape[1]} (8x8 pixel images)`);65console.log(` Classes: ${NUM_CLASSES} (digits 0-9)`);6667// Split data68const [XTrain, XTest, yTrain, yTest] = trainTestSplit(digits.data, digits.target, {69 testSize: 0.2,70 randomState: 42,71 shuffle: true,72});7374console.log(`\n✓ Split data:`);75console.log(` Training samples: ${XTrain.shape[0]}`);76console.log(` Test samples: ${XTest.shape[0]}`);7778// Scale features79const scaler = new StandardScaler();80scaler.fit(XTrain);81const XTrainScaled = scaler.transform(XTrain);82const XTestScaled = scaler.transform(XTest);8384console.log(`✓ Scaled features using StandardScaler`);8586// Convert to arrays for neural network87const trainSize = XTrainScaled.shape[0];88const testSize = XTestScaled.shape[0];89const numFeatures = XTrainScaled.shape[1];9091// ============================================================================92// Step 2: Build Neural Network Model93// ============================================================================9495console.log("\n🏗️ STEP 2: Building Neural Network");96console.log("─".repeat(70));9798// Create a simple MLP99const model = createSimpleMLP(numFeatures, 128, NUM_CLASSES);100101console.log("\nModel Architecture:");102console.log(model.toString());103104const summary = getModelSummary(model);105console.log(`\nModel Summary:`);106console.log(` Total Layers: ${summary.numLayers}`);107console.log(` Total Parameters: ${summary.numParameters}`);108109// ============================================================================110// Step 3: Training Setup111// ============================================================================112113console.log("\n⚙️ STEP 3: Training Setup");114console.log("─".repeat(70));115116// Get model parameters for optimizer117const modelParams = Array.from(model.parameters());118console.log(` Trainable parameters collected: ${modelParams.length} tensors`);119120// Create optimizer121const optimizer = new Adam(model.parameters(), { lr: LEARNING_RATE });122console.log(` Optimizer: Adam (lr=${LEARNING_RATE})`);123console.log(` Epochs: ${NUM_EPOCHS}`);124console.log(` Batch Size: ${BATCH_SIZE}`);125126// ============================================================================127// Step 4: Training Loop (Demonstration)128// ============================================================================129130console.log("\n🚀 STEP 4: Training Neural Network");131console.log("─".repeat(70));132133// Training history134const history: { epochs: number[]; trainLoss: number[]; trainAcc: number[] } = {135 epochs: [],136 trainLoss: [],137 trainAcc: [],138};139140const expectNumericTypedArray = (141 value: unknown142): Float32Array | Float64Array | Int32Array | Uint8Array => {143 if (!isTypedArray(value) || !isNumericTypedArray(value)) {144 throw new Error("Expected numeric typed array");145 }146 return value;147};148149const scalarFromTensor = (value: Tensor): number => {150 const data = expectNumericTypedArray(value.data);151 return Number(data[value.offset] ?? 0);152};153154// Helper to extract data as arrays155function extractData(X: Tensor, y: Tensor): { XArr: number[][]; yArr: number[] } {156 const n = X.shape[0];157 const f = X.shape[1] || 1;158159 const XArr: number[][] = [];160 const yArr: number[] = [];161162 for (let i = 0; i < n; i++) {163 const row: number[] = [];164 for (let j = 0; j < f; j++) {165 row.push(Number(X.at(i, j)));166 }167 XArr.push(row);168 yArr.push(Number(y.at(i)));169 }170171 return { XArr, yArr };172}173174// Training with mini-batches175console.log("\nTraining Progress:");176console.log("─".repeat(50));177178const { XArr: XTrainArr, yArr: yTrainArr } = extractData(XTrainScaled, yTrain);179const createEpochOrder = (epoch: number): number[] => {180 const order = Array.from({ length: trainSize }, (_, index) => index);181 let state = 42 + epoch * 9973;182183 for (let i = order.length - 1; i > 0; i--) {184 state = (state * 1664525 + 1013904223) >>> 0;185 const j = state % (i + 1);186 const current = order[i];187 order[i] = order[j] ?? current;188 order[j] = current ?? order[j] ?? 0;189 }190191 return order;192};193194// Simplified training demonstration195for (let epoch = 0; epoch < NUM_EPOCHS; epoch++) {196 model.train(true);197 let epochLoss = 0;198 let epochCorrect = 0;199 const epochOrder = createEpochOrder(epoch);200 const numBatches = Math.ceil(epochOrder.length / BATCH_SIZE);201202 // Process in batches203 for (let batch = 0; batch < numBatches; batch++) {204 const startIdx = batch * BATCH_SIZE;205 const endIdx = Math.min(startIdx + BATCH_SIZE, epochOrder.length);206 const batchSize = endIdx - startIdx;207208 // Extract batch209 const batchIndices = epochOrder.slice(startIdx, endIdx);210 const XBatch = batchIndices.map((index) => XTrainArr[index] ?? []);211 const yBatch = batchIndices.map((index) => yTrainArr[index] ?? 0);212213 // Create GradTensor input214 const input = GradTensor.fromTensor(tensor(XBatch, { dtype: "float32" }), {215 requiresGrad: false,216 });217218 optimizer.zeroGrad();219220 // Forward pass221 const output = model.forward(input);222 if (!(output instanceof GradTensor)) {223 throw new Error("Expected GradTensor output during training");224 }225226 // Create targets - crossEntropyLoss expects 1D class labels, not one-hot227 const targetsTensor = tensor(yBatch, { dtype: "int32" });228 const loss = crossEntropyLoss(output, targetsTensor);229 epochLoss += scalarFromTensor(loss.tensor);230 loss.backward();231 optimizer.step();232233 // Calculate accuracy234 const outData = expectNumericTypedArray(output.tensor.data);235 for (let i = 0; i < batchSize; i++) {236 let maxVal = -Infinity;237 let predClass = 0;238 for (let j = 0; j < NUM_CLASSES; j++) {239 const val = outData[i * NUM_CLASSES + j];240 if (val > maxVal) {241 maxVal = val;242 predClass = j;243 }244 }245 if (predClass === Math.round(yBatch[i])) {246 epochCorrect++;247 }248 }249 }250251 const avgLoss = epochLoss / numBatches;252 const trainAcc = epochCorrect / trainSize;253254 history.epochs.push(epoch + 1);255 history.trainLoss.push(avgLoss);256 history.trainAcc.push(trainAcc);257258 if ((epoch + 1) % 5 === 0 || epoch === 0) {259 console.log(260 ` Epoch ${String(epoch + 1).padStart(2)}: Loss = ${avgLoss.toFixed(4)}, ` +261 `Accuracy = ${(trainAcc * 100).toFixed(2)}%`262 );263 }264}265266console.log("─".repeat(50));267console.log("✓ Training Complete!");268269// ============================================================================270// Step 5: Model Evaluation271// ============================================================================272273console.log("\n📈 STEP 5: Model Evaluation");274console.log("─".repeat(70));275276model.train(false);277278// Evaluate on test set279const { XArr: XTestArr, yArr: yTestArr } = extractData(XTestScaled, yTest);280const testInput = GradTensor.fromTensor(tensor(XTestArr, { dtype: "float32" }), {281 requiresGrad: false,282});283const testOutputRaw = model.forward(testInput);284const testOutput = testOutputRaw instanceof GradTensor ? testOutputRaw.tensor : testOutputRaw;285286// Get predictions287const testOutData = expectNumericTypedArray(testOutput.data);288const predictions: number[] = [];289let testCorrect = 0;290291for (let i = 0; i < testSize; i++) {292 let maxVal = -Infinity;293 let predClass = 0;294 for (let j = 0; j < NUM_CLASSES; j++) {295 const val = testOutData[i * NUM_CLASSES + j];296 if (val > maxVal) {297 maxVal = val;298 predClass = j;299 }300 }301 predictions.push(predClass);302 if (predClass === Math.round(yTestArr[i])) {303 testCorrect++;304 }305}306307const testAccuracy = testCorrect / testSize;308const predTensor = tensor(predictions);309const yTestTensor = tensor(yTestArr);310311console.log("\nTest Set Performance:");312console.log(` Accuracy: ${(testAccuracy * 100).toFixed(2)}%`);313console.log(314 ` Precision: ${(Number(precision(yTestTensor, predTensor, "macro")) * 100).toFixed(2)}%`315);316console.log(` Recall: ${(Number(recall(yTestTensor, predTensor, "macro")) * 100).toFixed(2)}%`);317console.log(318 ` F1 Score: ${(Number(f1Score(yTestTensor, predTensor, "macro")) * 100).toFixed(2)}%`319);320321// Confusion matrix322console.log("\nConfusion Matrix:");323const cm = confusionMatrix(yTestTensor, predTensor);324const cmData = expectNumericTypedArray(cm.data);325326// Print confusion matrix327console.log(" Predicted");328console.log(329 ` ${Array.from({ length: NUM_CLASSES }, (_, i) => i.toString().padStart(3)).join("")}`330);331console.log(` +${"-".repeat(NUM_CLASSES * 3 + 1)}`);332for (let i = 0; i < NUM_CLASSES; i++) {333 let row = ` ${i} |`;334 for (let j = 0; j < NUM_CLASSES; j++) {335 row += String(cmData[i * NUM_CLASSES + j]).padStart(3);336 }337 console.log(row);338}339340// ============================================================================341// Step 6: Model Comparison342// ============================================================================343344console.log("\n🔬 STEP 6: Architecture Overview");345console.log("─".repeat(70));346347const architectureConfigs = [348 { name: "simple", label: "Simple + ReLU" },349 { name: "gelu", label: "GELU" },350 { name: "leaky", label: "LeakyReLU" },351] as const;352353for (const config of architectureConfigs) {354 const candidate = createModel({355 inputSize: numFeatures,356 hiddenSize: 128,357 numClasses: NUM_CLASSES,358 architecture: config.name,359 });360 const candidateSummary = getModelSummary(candidate);361 console.log(362 ` ${config.label.padEnd(16)}: ${candidateSummary.numLayers} layers, ${candidateSummary.numParameters} parameters`363 );364}365366// ============================================================================367// Step 7: Visualizations368// ============================================================================369370console.log("\n📊 STEP 7: Generating Visualizations");371console.log("─".repeat(70));372373// Learning curve plot374try {375 const fig = new Figure({ width: 800, height: 400 });376 const ax = fig.addAxes();377378 ax.plot(tensor(history.epochs), tensor(history.trainLoss), {379 color: "#2196F3",380 linewidth: 2,381 });382 ax.setTitle("Training Loss Curve");383 ax.setXLabel("Epoch");384 ax.setYLabel("Loss");385386 const svg = fig.renderSVG();387 writeFileSync(`${OUTPUT_DIR}/loss-curve.svg`, svg.svg);388 console.log(` ✓ Saved: ${OUTPUT_DIR}/loss-curve.svg`);389} catch (e) {390 console.log(` ⚠ Could not generate loss curve: ${e}`);391}392393// Accuracy curve394try {395 const fig = new Figure({ width: 800, height: 400 });396 const ax = fig.addAxes();397398 ax.plot(tensor(history.epochs), tensor(history.trainAcc.map((a) => a * 100)), {399 color: "#4CAF50",400 linewidth: 2,401 });402 ax.setTitle("Training Accuracy Curve");403 ax.setXLabel("Epoch");404 ax.setYLabel("Accuracy (%)");405406 const svg = fig.renderSVG();407 writeFileSync(`${OUTPUT_DIR}/accuracy-curve.svg`, svg.svg);408 console.log(` ✓ Saved: ${OUTPUT_DIR}/accuracy-curve.svg`);409} catch (e) {410 console.log(` ⚠ Could not generate accuracy curve: ${e}`);411}412413// ============================================================================414// Step 8: Summary415// ============================================================================416417console.log(`\n${"═".repeat(70)}`);418console.log(" TRAINING COMPLETE - SUMMARY");419console.log("═".repeat(70));420421console.log("\n📌 Results Summary:\n");422console.log(" Dataset:");423console.log(` • ${trainSize} training samples`);424console.log(` • ${testSize} test samples`);425console.log(` • ${numFeatures} features per sample`);426427console.log("\n Model:");428console.log(` • Architecture: Simple MLP`);429console.log(` • Hidden Size: 128`);430console.log(` • Parameters: ${summary.numParameters}`);431432console.log("\n Training:");433console.log(` • Epochs: ${NUM_EPOCHS}`);434console.log(` • Final Loss: ${history.trainLoss[history.trainLoss.length - 1].toFixed(4)}`);435console.log(436 ` • Final Train Acc: ${(history.trainAcc[history.trainAcc.length - 1] * 100).toFixed(2)}%`437);438439console.log("\n Evaluation:");440console.log(` • Test Accuracy: ${(testAccuracy * 100).toFixed(2)}%`);441442console.log("\n📁 Output Files:");443console.log(` • ${OUTPUT_DIR}/loss-curve.svg`);444console.log(` • ${OUTPUT_DIR}/accuracy-curve.svg`);445446console.log(`\n${"═".repeat(70)}`);447console.log(" ✅ Neural Network Image Classifier Complete!");448console.log("═".repeat(70));449Console Output
$ npx tsx 02-neural-image-classifier/index.ts
Training progress with loss/accuracy
Model evaluation metrics
Confusion matrix visualization
Learning curves plotKey Takeaways
- Model Architecture: Multi-layer perceptron (MLP) with fully connected layers and activations (ReLU, GELU, or LeakyReLU)
- Data Pipeline: Dataset loading, train/test split, feature scaling
- Evaluation Metrics: Precision, recall, F1-score, confusion matrix
- Visualizations: Training loss and accuracy curves
- Use deepbox/nn for Sequential model layers and loss computation.
- Use deepbox/ndarray for Tensor/GradTensor data handling.