Skip to content
All essays
AIMarch 15, 202530 min

Machine Learning with TensorFlow.js

Master browser-based machine learning with TensorFlow.js. Learn to train, run, and deploy ML models directly in the browser.

Ü
Ümit Uz
Mobile & Full Stack Developer

TensorFlow.js has revolutionized the way we think about machine learning in web applications. By bringing ML capabilities directly to the browser, it enables real-time inference, privacy-preserving computations, and interactive ML experiences without server dependencies. This comprehensive guide will take you from basics to advanced techniques in browser-based ML.

Why TensorFlow.js?

Key Advantages

Privacy: User data never leaves the browser Real-time: Zero-latency inference Cost-effective: No server-side inference costs Interactive: ML responds to user input instantly Offline capable: Works without internet connection

typescript
// Traditional server-side ML vs TensorFlow.js
// Server-side: User data → Server → Inference → Result (latency, privacy concerns)
// TensorFlow.js: User data → Browser → Inference → Result (instant, private)

Getting Started

Installation

bash
# NPM
npm install @tensorflow/tfjs

# Yarn
yarn add @tensorflow/tfjs

# CDN for quick prototyping
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest/dist/tf.min.js"></script>

Basic Setup

typescript
import * as tf from '@tensorflow/tfjs';

// Initialize TensorFlow.js
async function initTF() {
  // Check for WebGL backend
  await tf.ready();
  console.log('Backend:', tf.getBackend());
  console.log('Backend:', tf.ENV.getBool('WEBGL_RENDER_FLOAT32_CAPABLE'));
}

initTF();

Core Concepts

Tensors: The Building Blocks

Tensors are the fundamental data structure in TensorFlow.js - multidimensional arrays that flow through the computational graph.

typescript
// Creating tensors
const scalar = tf.scalar(3.14);
const tensor1D = tf.tensor1d([1, 2, 3, 4]);
const tensor2D = tf.tensor2d([[1, 2], [3, 4]]);
const tensor3D = tf.tensor3d([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]);

// Special tensor creations
const zeros = tf.zeros([2, 3]); // [[0, 0, 0], [0, 0, 0]]
const ones = tf.ones([2, 2]);    // [[1, 1], [1, 1]]
const random = tf.randomNormal([3, 3]); // Normal distribution
const uniform = tf.randomUniform([2, 2], 0, 1); // Uniform distribution

// Always dispose tensors to prevent memory leaks
scalar.dispose();
tensor1D.dispose();

// Or use tf.tidy() for automatic cleanup
const result = tf.tidy(() => {
  const a = tf.tensor1d([1, 2, 3]);
  const b = tf.tensor1d([4, 5, 6]);
  return a.add(b);
});

console.log(result.dataSync()); // [5, 7, 9]
result.dispose();

Operations and Mathematical Functions

typescript
// Basic operations
const a = tf.tensor1d([1, 2, 3, 4]);
const b = tf.tensor1d([5, 6, 7, 8]);

const add = a.add(b);      // [6, 8, 10, 12]
const sub = a.sub(b);      // [-4, -4, -4, -4]
const mul = a.mul(b);      // [5, 12, 21, 32]
const div = a.div(b);      // [0.2, 0.33, 0.43, 0.5]
const pow = a.pow(2);      // [1, 4, 9, 16]

// Matrix operations
const matrix1 = tf.tensor2d([[1, 2], [3, 4]]);
const matrix2 = tf.tensor2d([[5, 6], [7, 8]]);

const matMul = matrix1.matMul(matrix2); // [[19, 22], [43, 50]]
const transpose = matrix1.transpose();  // [[1, 3], [2, 4]]

// Statistical operations
const mean = a.mean();        // 2.5
const std = a.sub(mean).pow(2).mean().sqrt(); // Standard deviation
const max = a.max();          // 4
const min = a.min();          // 1

// Cleanup
tf.dispose([a, b, add, sub, mul, div, pow, matrix1, matrix2, matMul, transpose]);

Building Your First Model

Linear Regression from Scratch

typescript
// Create a simple linear regression model
class LinearRegression {
  private weights: tf.Tensor;
  private bias: tf.Tensor;

  constructor() {
    // Initialize parameters
    this.weights = tf.variable(tf.scalar(Math.random()));
    this.bias = tf.variable(tf.scalar(Math.random()));
  }

  predict(x: tf.Tensor): tf.Tensor {
    // y = wx + b
    return x.mul(this.weights).add(this.bias);
  }

  loss(predictions: tf.Tensor, labels: tf.Tensor): tf.Tensor {
    // Mean squared error
    const diff = predictions.sub(labels);
    return diff.square().mean();
  }

  async train(
    xData: number[],
    yData: number[],
    epochs: number = 100,
    learningRate: number = 0.01
  ) {
    const x = tf.tensor1d(xData);
    const y = tf.tensor1d(yData);

    const optimizer = tf.train.sgd(learningRate);

    for (let epoch = 0; epoch < epochs; epoch++) {
      optimizer.minimize(() => {
        const predictions = this.predict(x);
        return this.loss(predictions, y);
      });

      if (epoch % 10 === 0) {
        const currentLoss = this.loss(this.predict(x), y).dataSync()[0];
        console.log(`Epoch ${epoch}: loss = ${currentLoss.toFixed(4)}`);
      }
    }

    console.log('Training complete!');
    console.log('Weights:', this.weights.dataSync()[0]);
    console.log('Bias:', this.bias.dataSync()[0]);
  }
}

// Usage
const model = new LinearRegression();

// Training data: simple linear relationship y = 2x + 1
const xTrain = [1, 2, 3, 4, 5];
const yTrain = [3, 5, 7, 9, 11];

await model.train(xTrain, yTrain, 100, 0.01);

// Prediction
const testX = tf.tensor1d([6]);
const prediction = model.predict(testX).dataSync()[0];
console.log('Prediction for x=6:', prediction); // Should be close to 13

testX.dispose();

Building a Neural Network

typescript
// Create a sequential model
const model = tf.sequential();

// Add layers
model.add(tf.layers.dense({
  units: 64,
  activation: 'relu',
  inputShape: [10] // Input features
}));

model.add(tf.layers.dropout({ rate: 0.2 })); // Prevent overfitting

model.add(tf.layers.dense({
  units: 32,
  activation: 'relu'
}));

model.add(tf.layers.dense({
  units: 1,
  activation: 'sigmoid' // Binary classification
}));

// Compile the model
model.compile({
  optimizer: tf.train.adam(0.001),
  loss: 'binaryCrossentropy',
  metrics: ['accuracy']
});

// Model summary
model.summary();

/*
_________________________________________________________________
Layer (type)                 Output shape              Param #
=================================================================
dense_Dense1 (Dense)         [null,64]                 704
_________________________________________________________________
dropout_Dropout1 (Dropout)   [null,64]                 0
_________________________________________________________________
dense_Dense2 (Dense)         [null,32]                 2080
_________________________________________________________________
dense_Dense3 (Dense)         [null,1]                  33
=================================================================
Total params: 2817
Trainable params: 2817
Non-trainable params: 0
_________________________________________________________________
*/

Training the Model

typescript
// Generate synthetic data
function generateData(numSamples: number): { xs: tf.Tensor, ys: tf.Tensor } {
  const xs: number[][] = [];
  const ys: number[] = [];

  for (let i = 0; i < numSamples; i++) {
    const x1 = Math.random();
    const x2 = Math.random();
    const x3 = Math.random();
    const x4 = Math.random();
    const x5 = Math.random();
    const x6 = Math.random();
    const x7 = Math.random();
    const x8 = Math.random();
    const x9 = Math.random();
    const x10 = Math.random();

    xs.push([x1, x2, x3, x4, x5, x6, x7, x8, x9, x10]);

    // Create a pattern: if sum > 5, class 1, else 0
    const sum = x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10;
    ys.push(sum > 5 ? 1 : 0);
  }

  return {
    xs: tf.tensor2d(xs),
    ys: tf.tensor1d(ys)
  };
}

// Train the model
async function trainModel() {
  const { xs, ys } = generateData(1000);

  // Split into train and validation
  const split = Math.floor(xs.shape[0] * 0.8);

  const trainXs = xs.slice([0, 0], [split, 10]);
  const trainYs = ys.slice([0], [split]);

  const valXs = xs.slice([split, 0], [-1, 10]);
  const valYs = ys.slice([split], [-1]);

  // Train
  const history = await model.fit(trainXs, trainYs, {
    epochs: 50,
    batchSize: 32,
    validationData: [valXs, valYs],
    shuffle: true,
    callbacks: {
      onEpochEnd: (epoch, logs) => {
        console.log(`Epoch ${epoch}: loss = ${logs?.loss?.toFixed(4)}, accuracy = ${logs?.acc?.toFixed(4)}`);
      }
    }
  });

  return history;
}

await trainModel();

Practical Examples

Image Classification with MobileNet

typescript
import * as tf from '@tensorflow/tfjs';
import { mobilenet, load as loadMobilenet } from '@tensorflow-models/mobilenet';

class ImageClassifier {
  private model: mobilenet.MobileNet | null = null;

  async load() {
    console.log('Loading MobileNet...');
    this.model = await loadMobilenet();
    console.log('Model loaded!');
  }

  async classify(imageElement: HTMLImageElement | HTMLCanvasElement) {
    if (!this.model) {
      throw new Error('Model not loaded');
    }

    // Classify the image
    const predictions = await this.model.classify(imageElement);

    return predictions.map(pred => ({
      className: pred.className,
      probability: pred.probability
    }));
  }
}

// React component example
import { useRef, useState, useEffect } from 'react';

export function ImageClassifierComponent() {
  const imageRef = useRef<HTMLImageElement>(null);
  const [predictions, setPredictions] = useState<any[]>([]);
  const [loading, setLoading] = useState(false);
  const classifierRef = useRef<ImageClassifier>(new ImageClassifier());

  useEffect(() => {
    classifierRef.current.load();
  }, []);

  const handleClassify = async () => {
    if (!imageRef.current) return;

    setLoading(true);
    try {
      const results = await classifierRef.current.classify(imageRef.current);
      setPredictions(results);
    } catch (error) {
      console.error('Classification error:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="p-6 max-w-2xl mx-auto">
      <h2 className="text-2xl font-bold mb-4">Image Classifier</h2>

      <div className="mb-4">
        <input
          type="file"
          accept="image/*"
          onChange={(e) => {
            const file = e.target.files?.[0];
            if (file && imageRef.current) {
              imageRef.current.src = URL.createObjectURL(file);
            }
          }}
        />
      </div>

      <img
        ref={imageRef}
        alt="Upload"
        className="w-full max-w-md mx-auto mb-4 rounded"
        crossOrigin="anonymous"
      />

      <button
        onClick={handleClassify}
        disabled={loading}
        className="px-6 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
      >
        {loading ? 'Classifying...' : 'Classify'}
      </button>

      {predictions.length > 0 && (
        <div className="mt-6">
          <h3 className="font-bold mb-2">Predictions:</h3>
          <ul className="space-y-2">
            {predictions.map((pred, i) => (
              <li key={i} className="p-3 bg-gray-100 rounded">
                <div className="flex justify-between">
                  <span>{pred.className}</span>
                  <span className="font-mono">
                    {(pred.probability * 100).toFixed(1)}%
                  </span>
                </div>
                <div className="mt-2 h-2 bg-gray-200 rounded-full overflow-hidden">
                  <div
                    className="h-full bg-blue-600 transition-all"
                    style={{ width: `${pred.probability * 100}%` }}
                  />
                </div>
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}

Real-time Object Detection

typescript
import { cocoSsd, load as loadCocoSsd } from '@tensorflow-models/coco-ssd';

class ObjectDetector {
  private model: cocoSsd.ObjectDetection | null = null;

  async load() {
    console.log('Loading COCO-SSD model...');
    this.model = await loadCocoSsd();
    console.log('Model loaded!');
  }

  async detect(
    videoElement: HTMLVideoElement,
    callback: (predictions: cocoSsd.DetectedObject[]) => void
  ) {
    if (!this.model) {
      throw new Error('Model not loaded');
    }

    // Real-time detection loop
    const detectFrame = async () => {
      const predictions = await this.model!.detect(videoElement);
      callback(predictions);
      requestAnimationFrame(detectFrame);
    };

    detectFrame();
  }
}

// Usage with webcam
export function ObjectDetectionComponent() {
  const videoRef = useRef<HTMLVideoElement>(null);
  const canvasRef = useRef<HTMLCanvas>(null);
  const [predictions, setPredictions] = useState<cocoSsd.DetectedObject[]>([]);
  const detectorRef = useRef<ObjectDetector>(new ObjectDetector());

  useEffect(() => {
    const setupCamera = async () => {
      const video = videoRef.current;
      if (!video) return;

      try {
        const stream = await navigator.mediaDevices.getUserMedia({
          video: { width: 640, height: 480 }
        });

        video.srcObject = stream;
        await video.play();

        // Start detection
        await detectorRef.current.load();
        detectorRef.current.detect(video, setPredictions);
      } catch (error) {
        console.error('Camera setup error:', error);
      }
    };

    setupCamera();
  }, []);

  // Draw predictions on canvas
  useEffect(() => {
    const canvas = canvasRef.current;
    const video = videoRef.current;
    if (!canvas || !video) return;

    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    // Clear and draw video frame
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

    // Draw bounding boxes
    predictions.forEach(pred => {
      const [x, y, width, height] = pred.bbox;

      ctx.strokeStyle = '#00ff00';
      ctx.lineWidth = 2;
      ctx.strokeRect(x, y, width, height);

      ctx.fillStyle = '#00ff00';
      ctx.font = '16px Arial';
      ctx.fillText(
        `${pred.class} ${(pred.score * 100).toFixed(1)}%`,
        x,
        y - 5
      );
    });
  }, [predictions]);

  return (
    <div className="relative">
      <video
        ref={videoRef}
        width={640}
        height={480}
        className="rounded"
      />
      <canvas
        ref={canvasRef}
        width={640}
        height={480}
        className="absolute top-0 left-0"
      />
    </div>
  );
}

Sentiment Analysis

typescript
// Build a sentiment analysis model
async function createSentimentModel() {
  const model = tf.sequential();

  // Text preprocessing parameters
  const vocabSize = 10000;
  const maxLen = 100;
  const embeddingDim = 128;

  // Embedding layer
  model.add(tf.layers.embedding({
    inputDim: vocabSize,
    outputDim: embeddingDim,
    inputLength: maxLen
  }));

  // LSTM layers
  model.add(tf.layers.lstm({
    units: 64,
    returnSequences: true
  }));

  model.add(tf.layers.dropout({ rate: 0.5 }));

  model.add(tf.layers.lstm({
    units: 32
  }));

  model.add(tf.layers.dropout({ rate: 0.5 }));

  // Output layer
  model.add(tf.layers.dense({
    units: 3, // positive, neutral, negative
    activation: 'softmax'
  }));

  model.compile({
    optimizer: 'adam',
    loss: 'categoricalCrossentropy',
    metrics: ['accuracy']
  });

  return model;
}

// Text preprocessing utility
class TextPreprocessor {
  private vocab: Map<string, number> = new Map();
  private vocabSize = 10000;

  buildVocabulary(texts: string[]) {
    const wordCounts = new Map<string, number>();

    // Count word frequencies
    texts.forEach(text => {
      const words = text.toLowerCase().split(/\s+/);
      words.forEach(word => {
        wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
      });
    });

    // Sort by frequency and take top vocabSize
    const sorted = Array.from(wordCounts.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, this.vocabSize - 1);

    // Build vocabulary (reserve 0 for padding)
    sorted.forEach((word, index) => {
      this.vocab.set(word[0], index + 1);
    });
  }

  textToSequence(text: string, maxLen: number): number[] {
    const words = text.toLowerCase().split(/\s+/);
    const sequence = words.map(word => this.vocab.get(word) || 0);

    // Pad or truncate to maxLen
    if (sequence.length < maxLen) {
      return [...sequence, ...new Array(maxLen - sequence.length).fill(0)];
    }
    return sequence.slice(0, maxLen);
  }
}

// Usage example
const sentimentModel = await createSentimentModel();

const preprocessor = new TextPreprocessor();
preprocessor.buildVocabulary([
  'I love this product',
  'This is terrible',
  'Amazing experience',
  'Not good at all'
]);

const text = "This product is amazing!";
const sequence = preprocessor.textToSequence(text, 100);
const input = tf.tensor2d([sequence]);

const prediction = sentimentModel.predict(input) as tf.Tensor;
const sentiment = prediction.argMax(-1).dataSync()[0];

console.log('Sentiment:', ['negative', 'neutral', 'positive'][sentiment]);

Pose Estimation

typescript
import * as poseDetection from '@tensorflow-models/pose-detection';

class PoseEstimator {
  private detector: poseDetection.PoseDetector | null = null;

  async load() {
    const model = poseDetection.SupportedModels.MoveNet;
    const detectorConfig = {
      modelType: poseDetection.movenet.modelType.SINGLEPOSE_LIGHTNING
    };

    this.detector = await poseDetection.createDetector(model, detectorConfig);
  }

  async estimatePose(
    videoElement: HTMLVideoElement
  ): Promise<poseDetection.Pose | null> {
    if (!this.detector) {
      throw new Error('Detector not loaded');
    }

    const poses = await this.detector.estimatePoses(videoElement);
    return poses[0] || null;
  }

  // Calculate angle between three keypoints
  calculateAngle(
    a: [number, number],
    b: [number, number],
    c: [number, number]
  ): number {
    const radians = Math.atan2(c[1] - b[1], c[0] - b[0]) -
                    Math.atan2(a[1] - b[1], a[0] - b[0]);
    let angle = Math.abs(radians * 180.0 / Math.PI);

    if (angle > 180.0) {
      angle = 360 - angle;
    }

    return angle;
  }

  // Exercise counter (e.g., squats)
  async countSquats(
    videoElement: HTMLVideoElement,
    callback: (count: number) => void
  ) {
    let count = 0;
    let position = 'up';
    const threshold = 160; // Angle threshold for squat

    const checkSquat = async () => {
      const pose = await this.estimatePose(videoElement);

      if (pose) {
        const keypoints = pose.keypoints;

        // Get relevant keypoints
        const leftHip = keypoints.find(k => k.name === 'left_hip');
        const leftKnee = keypoints.find(k => k.name === 'left_knee');
        const leftAnkle = keypoints.find(k => k.name === 'left_ankle');

        if (leftHip && leftKnee && leftAnkle) {
          const angle = this.calculateAngle(
            [leftHip.x, leftHip.y],
            [leftKnee.x, leftKnee.y],
            [leftAnkle.x, leftAnkle.y]
          );

          if (angle < 90 && position === 'up') {
            position = 'down';
          } else if (angle > threshold && position === 'down') {
            position = 'up';
            count++;
            callback(count);
          }
        }
      }

      requestAnimationFrame(checkSquat);
    };

    checkSquat();
  }
}

// React component for squat counter
export function SquatCounterComponent() {
  const videoRef = useRef<HTMLVideoElement>(null);
  const [count, setCount] = useState(0);
  const [position, setPosition] = useState<'up' | 'down'>('up');
  const estimatorRef = useRef<PoseEstimator>(new PoseEstimator());

  useEffect(() => {
    const setup = async () => {
      await estimatorRef.current.load();

      const video = videoRef.current;
      if (!video) return;

      const stream = await navigator.mediaDevices.getUserMedia({
        video: { width: 640, height: 480 }
      });

      video.srcObject = stream;
      await video.play();

      estimatorRef.current.countSquats(video, (newCount) => {
        setCount(newCount);
      });
    };

    setup();
  }, []);

  return (
    <div className="p-6">
      <video ref={videoRef} width={640} height={480} className="rounded" />
      <div className="mt-4 text-center">
        <h2 className="text-4xl font-bold">{count}</h2>
        <p className="text-gray-600">Squats</p>
      </div>
    </div>
  );
}

Model Transfer Learning

Using Pre-trained Models

typescript
// Transfer learning with MobileNet
async function createCustomClassifier(numClasses: number) {
  // Load base MobileNet model
  const mobilenet = await tf.loadLayersModel(
    'https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_0.25_224/model.json'
  );

  // Remove the top classification layer
  const layerOutputs = mobilenet.getLayer('global_average_pooling2d_1').output;

  // Freeze base model layers
  mobilenet.trainable = false;

  // Add custom classification head
  const newModel = tf.sequential();
  newModel.add(tf.layers.dense({
    inputShape: [1024], // MobileNet output shape
    units: 128,
    activation: 'relu'
  }));

  newModel.add(tf.layers.dropout({ rate: 0.5 }));

  newModel.add(tf.layers.dense({
    units: numClasses,
    activation: 'softmax'
  }));

  newModel.compile({
    optimizer: 'adam',
    loss: 'categoricalCrossentropy',
    metrics: ['accuracy']
  });

  return { baseModel: mobilenet, classifier: newModel };
}

// Feature extraction
async function extractFeatures(image: tf.Tensor, baseModel: tf.LayersModel): Promise<tf.Tensor> {
  return baseModel.predict(image.expandDims(0)) as tf.Tensor;
}

// Train custom classifier
async function trainCustomClassifier(
  images: tf.Tensor[],
  labels: number[],
  numClasses: number
) {
  const { baseModel, classifier } = await createCustomClassifier(numClasses);

  // Extract features from all images
  const features = await Promise.all(
    images.map(img => extractFeatures(img, baseModel))
  );

  const featuresTensor = tf.stack(features);
  const labelsTensor = tf.oneHot(tf.tensor1d(labels, 'int32'), numClasses);

  // Train classifier
  await classifier.fit(featuresTensor, labelsTensor, {
    epochs: 50,
    batchSize: 32,
    validationSplit: 0.2
  });

  return { baseModel, classifier };
}

Performance Optimization

Model Optimization Techniques

typescript
// 1. Quantization: Reduce model size
async function quantizeModel(model: tf.LayersModel) {
  // Convert to use 8-bit integers instead of 32-bit floats
  const quantizationConfig = {
    quantizationBytes: 1 // 8-bit quantization
  };

  // Apply quantization-aware training
  // (This is a simplified example - actual implementation requires more setup)
  return model;
}

// 2. Model pruning: Remove unnecessary weights
async function pruneModel(model: tf.LayersModel) {
  // Remove weights with small magnitudes
  const pruningConfig = {
    initialSparsity: 0.5,
    finalSparsity: 0.9,
    beginStep: 0,
    endStep: 1000
  };

  // Apply pruning during training
  return model;
}

// 3. Model compression
async function compressModel(model: tf.LayersModel) {
  // Save in compressed format
  const saveResult = await model.save('localstorage://compressed-model');
  return saveResult;
}

// 4. Use WebGL backend for GPU acceleration
async function setupWebGL() {
  // Check WebGL support
  const gl = document.createElement('canvas').getContext('webgl2');

  if (gl) {
    await tf.setBackend('webgl2');
    console.log('Using WebGL2 backend');
  } else {
    await tf.setBackend('webgl');
    console.log('Using WebGL backend');
  }

  await tf.ready();
}

// 5. Batch predictions
async function batchPredict(model: tf.LayersModel, inputs: tf.Tensor[]) {
  const batch = tf.stack(inputs);
  const predictions = model.predict(batch) as tf.Tensor;
  return predictions;
}

// 6. Use memory management
function efficientInference(model: tf.LayersModel, input: tf.Tensor) {
  return tf.tidy(() => {
    const processed = input.div(255.0); // Normalize
    const prediction = model.predict(processed.expandDims(0)) as tf.Tensor;
    return prediction.dataSync();
  });
}

Deployment

Saving and Loading Models

typescript
// Save model
async function saveModel(model: tf.LayersModel, name: string) {
  // Save to local storage
  await model.save(`localstorage://${name}`);

  // Save to IndexedDB
  await model.save(`indexeddb://${name}`);

  // Download as file
  await model.save(`downloads://${name}`);
}

// Load model
async function loadModel(name: string): Promise<tf.LayersModel> {
  // Try local storage first
  try {
    return await tf.loadLayersModel(`localstorage://${name}`);
  } catch (e) {
    // Fallback to IndexedDB
    return await tf.loadLayersModel(`indexeddb://${name}`);
  }
}

// Export model for TensorFlow.js converter
async function exportForTFJS(model: tf.LayersModel) {
  await model.save('file://./model');
}

Best Practices

Memory Management

typescript
// Always use tf.tidy() for intermediate tensors
function badExample() {
  const a = tf.tensor1d([1, 2, 3]);
  const b = tf.tensor1d([4, 5, 6]);
  const c = a.add(b);
  const d = c.mul(2);
  // Memory leak! a, b, c not disposed
  return d;
}

function goodExample() {
  return tf.tidy(() => {
    const a = tf.tensor1d([1, 2, 3]);
    const b = tf.tensor1d([4, 5, 6]);
    const c = a.add(b);
    return c.mul(2);
  });
  // All intermediate tensors automatically disposed
}

// Monitor memory
function checkMemory() {
  console.log('Number of tensors:', tf.memory().numTensors);
  console.log('Number of data buffers:', tf.memory().numDataBuffers);
  console.log('Bytes in use:', tf.memory().numBytes);
}

// Dispose of unused variables
let model: tf.LayersModel | null = null;

async function loadAndUseModel() {
  model = await tf.loadLayersModel('model.json');
  // ... use model ...
}

function cleanup() {
  if (model) {
    model.dispose();
    model = null;
  }
}

Conclusion

TensorFlow.js brings the power of machine learning directly to the browser, enabling privacy-preserving, real-time AI applications. By mastering the concepts and techniques in this guide, you can build sophisticated ML-powered web applications that run entirely on the client side.

Key takeaways:

  1. 1Always manage memory properly with tf.tidy() and dispose()
  2. 2Use pre-trained models as a starting point
  3. 3Optimize models for browser deployment
  4. 4Leverage GPU acceleration with WebGL backend
  5. 5Implement proper error handling and fallbacks
  6. 6Test across different browsers and devices

The future of web-based ML is bright, and TensorFlow.js is at the forefront of this revolution. Start building today!

Related essays

Next essay
Smart Contract Security: Auditing and Best Practices for 2025