sciBASIC# knot logo sciBASIC# ↖

08 Tutorial — UMAP manifold

Project 20,000 handwritten digits onto a 2-D UMAP manifold, colored by label.

Module DataMining · UMAP Dataset MNIST subset · 20,000 × 784 Embedding 2-D · 128 neighbors Output PNG · 300 dpi

A 20,000-digit slice of the MNIST training split — grayscale 28 × 28 digits, 784 pixel features each — is read straight from the original idx3/idx1 binary files, wrapped into a NumericTable and pushed through the sciBASIC# UMAP implementation: 2 target dimensions, 128 nearest neighbors. The resulting manifold is rendered as a Nature-themed scatter in which every point carries its digit label as color, and the full coordinate table streams back to CSV.

02 Pipeline

From idx binaries to a labeled manifold

Step 1

Load

MNIST reads train-images-idx3-ubyte + train-labels-idx1-ubyte from the --data folder; ExtractDataSet(Of ClusterEntity) streams labeled 784-D feature rows and the first limit = 20000 land in a NumericTable (features p_1–p_784 + the class label column).

Step 2

Reduce

table.umap(dims := 2, neighbors := 128) builds the fuzzy simplicial set over the 784-dimensional space and optimizes the low-dimensional layout in one call — the returned manifold table carries features dim_1 / dim_2 and inherits the row names and class labels.

Step 3

Plot

A ScatterPlot (800 × 600, PlotTheme.Nature) draws UMAP1 vs UMAP2 with each point colored by its digit label, saved at 300 dpi.

Step 4

Export

manifold.WriteCsv streams the embedding table (dim_1, dim_2, label:class) to CSV in the standard label: layout.

01 The Script

Full demo source

The complete script exactly as executed by the sciBASIC# script engine (vbs.exe) — nothing elided.

mnist_umap.vb · 103 linesDownload mnist_umap.vb
#include "Microsoft.VisualBasic.DataMining.UMAP.dll"
#include "Microsoft.VisualBasic.MachineLearning.DataStorage.dll"
#include "Microsoft.VisualBasic.DataMining.Framework.dll"
#include "Microsoft.VisualBasic.Drawing.dll"
#include "Microsoft.VisualBasic.Data.DataPlot.dll"
#include "Microsoft.VisualBasic.Math.Randomizer.dll"
#include "Microsoft.VisualBasic.Data.Framework.dll"

imports Microsoft.VisualBasic.Data
imports Microsoft.VisualBasic.Data.Framework
imports Microsoft.VisualBasic.MachineLearning.DataStorage
imports Microsoft.VisualBasic.DataMining.ComponentModel.EntityModels
imports Microsoft.VisualBasic.DataMining.UMAP
imports Microsoft.VisualBasic.DataMining
imports microsoft.visualbasic.data.plots
imports microsoft.visualbasic.drawing

' The MNIST training set contains 60000 samples and running UMAP on the full set
' is very expensive, so this tutorial script only takes the first ``limit``
' samples for the demonstration (increase limit to process more samples)
dim limit = 20000
dim repo_data = ?"--data"
dim mnist as new MNIST(
    $"{repo_data}\train-images-idx3-ubyte",
    $"{repo_data}\train-labels-idx1-ubyte")

' ---------------------------------------------------------------------------
' 1. Build the handwritten digit samples of MNIST into a unified 2D table
'    object (NumericTable)
'
'    + feature columns = p_1 .. p_784, i.e. the pixel grey levels of the image
'    + label column   = class, i.e. the digit category of every sample
'
'    All the following operations (dimension reduction, clustering, export) are
'    performed directly on this single table
' ---------------------------------------------------------------------------
dim rows As Double()() = New Double(limit - 1)() {}
dim classes(limit - 1) as double
dim i = 0

for each digit in mnist.ExtractDataSet(of ClusterEntity)()
    if i >= limit then exit for

    rows(i) = digit.entityVector
    classes(i) = CDbl(digit.cluster)
    i += 1
next

' The number of samples actually read may be smaller than limit
if i > 0 andalso i < limit then
    redim preserve rows(i - 1)
    redim preserve classes(i - 1)
end if

dim table = NumericTable.FromRows(
    fieldname("sample", n := rows.length).toarray(),
    rows,
    fieldname("p", n := rows(0).length).toarray())
call table.SetLabel("class", classes)

call console.WriteLine($"mnist table: {table.nsamples} samples x {table.nfeatures} pixels")

' ---------------------------------------------------------------------------
' 2. Use UMAP to reduce the 784-dimensional pixel data to two dimensions
'
'    The umap method accepts the unified 2D table and returns a new table whose
'    features are the embedding coordinates dim_1..dim_2; the row names and the
'    existing class label column are inherited
' ---------------------------------------------------------------------------
dim manifold = table.umap(dims := 2, neighbors := 128)

call console.WriteLine($"umap embedding: {manifold.nsamples} samples x {manifold.nfeatures} dims")

' ---------------------------------------------------------------------------
' 3. Plot: UMAP1/UMAP2 scatter plot coloured by the handwritten digit class
' ---------------------------------------------------------------------------
dim x = manifold.Feature("dim_1")
dim y = manifold.Feature("dim_2")
dim number(classes.length - 1) as string

for k = 0 to classes.length - 1
    number(k) = classes(k).ToString()
next

call SkiaDriver.Register()

Using plt As New ScatterPlot(800, 600, PlotTheme.Nature())
    plt.Title = "MNIST dataset UMAP embedding"
    plt.SubTitle = "UMAP manifold of 2 dimensions"
    plt.XLabel = "UMAP1"
    plt.YLabel = "UMAP2"
    plt.Plot(DataSerials(x := x, y := y, number).tolist())
    plt.SavePng(here("mnist-umap.png"), 300)
End Using

' ---------------------------------------------------------------------------
' 4. Export the embedding table as csv
'    (dim_1, dim_2 feature columns + the label:class label column)
' ---------------------------------------------------------------------------
call manifold.WriteCsv(here("mnist-umap.csv"))

call console.WriteLine("done: mnist-umap.png")
call console.WriteLine("done: mnist-umap.csv")

03 Results

The 2-D manifold

UMAP embedding of 20,000 MNIST digits, points colored by digit label
Fig. 1 — UMAP embedding of 20,000 MNIST training digits; every point is one handwritten digit, colored by its label. The manifold resolves the handwriting styles into well-separated islands, and structurally similar digits settle into neighboring lobes of the embedding.
UMAP preserves local neighborhood structure: images that look alike stay adjacent after the 784-D → 2-D projection, so the digit classes emerge as islands without any use of the labels during optimization — labels only provide the colors in the final scatter.