Project 02
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/plot

Project 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));449

Console Output

$ npx tsx 02-neural-image-classifier/index.ts
Training progress with loss/accuracy
Model evaluation metrics
Confusion matrix visualization
Learning curves plot

Key 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.