001Notes

From Pixels and Words to Semantic Search

On this page84

A beginner-first tutorial on representations, pixels, features, embeddings, image and text encoders, shared vector spaces, and measured CLIP, SigLIP, and DINO experiments.

Article details
Status
Building Publicly
Subcategory
ClusterLens
Last reviewed
2 Sept 2026
Prerequisites
None; the article starts from pixels and vectors
84 sections

Suppose a folder contains ten thousand photographs with names such as:

IMG_1042.jpg
DSC_8821.jpg
20260814_173411.jpg

You remember a red sports car, but you do not remember its filename, date, or folder. How can a computer find it from the words red sports car?

The short answer is “use embeddings.” That answer is correct and almost useless to a beginner. It hides every important transformation between the JPEG file, the text query, and the final ranked images.

This article builds the complete idea from scratch. We will first decide what a representation is, open an image into pixels, discover why raw pixels are poor search features, construct features by hand, and only then introduce learned embeddings. After that we will follow both images and text through encoders, align them in one vector space, create embeddings in code, and finally perform semantic search.

The synthetic image collection reused throughout this tutorial

The examples use one synthetic collection throughout: red and blue cars, road and showroom scenes, a toy car, a beach, an invoice, and altered copies of the same images. Reusing one collection lets us see exactly what each representation preserves and loses.

1. What is a representation?

A representation is a chosen description of an object.

Consider one photograph of a red car. We can describe it as a filename:

IMG_1042.jpg

We can describe it using metadata:

1920 × 1080
JPEG
captured at 14:32

We can describe its visible content in words:

a red sports car on a road

Or we can describe it using numbers:

raw pixels:       [241, 36, 42, 239, 35, 40, ...]
colour histogram: [0.42, 0.18, 0.14, ...]
learned vector:   [0.12, -0.34, 0.81, ...]

These are all representations of the same image. None is the image itself. Each preserves different information.

RepresentationPreservesDiscards or hides
filenamefile identity and perhaps human labelsmost visual content
metadatasize, time, camera, formatsubject and appearance
captionconcepts expressible in languageexact pixels and fine layout
raw pixelsnearly all decoded visual measurementsrobust semantic relationships
histogramcolour distributionobject position and shape
embeddingrelationships learned during trainingdetails the objective did not reward

This is the first principle of the whole article:

A representation is not neutral. Its construction decides which differences later algorithms can observe.

Interactive representation switcher

Choose a description of the same red-car photograph.

IMG_1042.jpg

Excellent for exact file identity. It says nothing reliable about a car unless a person named it helpfully.

A comparison that exposes the choice

Compare a red Ferrari, a red apple, and a blue Porsche.

A colour representation may put the Ferrari near the apple because both are red. A shape-and-subject representation may put the Ferrari near the Porsche because both are sports cars. Neither distance calculation can repair a representation that retained the wrong information.

2. How a computer initially sees an image

When you open a photograph, you see a dog, a beach, or a receipt. The computer does not begin there. It begins with a file: a sequence of bytes stored on disk. Those bytes are instructions for reconstructing the picture, not little objects labelled “dog” or “beach.”

An image decoder reads a JPEG or PNG file and reconstructs a rectangular grid of tiny coloured squares called pixels. Each pixel has an address—its row and column—and a colour recipe. In an ordinary RGB image, that recipe says how much red, green, and blue light to mix.

The lesson below follows one deliberately tiny 2×2 picture. Real photographs work the same way; they simply contain millions of pixels instead of four.

Interactive lesson · from image file to model input

1. The file and the visible picture are not the same thing

A JPEG file stores compressed data. A decoder turns that data into the pixel grid that a screen can display.

tiny-image.jpg compressed bytes FF D8 FF E0 …
JPEG decoder reconstructs the grid
2 rows × 2 columns

What to remember: decoding gives us coloured pixels. It has not yet discovered objects or meaning.

Scaling and normalization are different operations

The two operations answer different questions:

  • Scaling asks: how can we express this number in a smaller, expected range?
  • Normalization asks: how far is this number from what the model usually saw during training?

Neither operation discovers a dog, a wheel, or a beach. They only prepare numbers for the encoder.

Step 1: scaling changes the numeric range

An 8-bit colour channel lives between 0 and 255. Dividing by 255 maps that range to 0 through 1.

Scaling rule

scaled = channel255

channel is the stored 0–255 value; scaled is the corresponding 0–1 value.

For a channel value of 204:

Worked scaling example

scaled = 204255 = 0.80

The amount of colour did not change. Only its numeric representation changed: 204/255 and 0.80 describe the same intensity.

Step 2: normalization adds the training context

A model may then compare the scaled value with the values it encountered while training. Two statistics from that training recipe are used:

  • the mean is the typical value;
  • the standard deviation describes the usual amount of variation around that mean.

Normalization rule

normalized = scaled − training meantraining standard deviation

Subtracting the mean centres the data. Dividing by the standard deviation expresses the result in units of typical variation.

Continue the same example with a training mean of 0.50 and a training standard deviation of 0.25:

Worked normalization example

normalized = 0.80 − 0.500.25 = +1.20

The positive result says this channel is 1.2 standard deviations above the training mean.

stored pixel channel204range: 0…255
scaled value0.80range: 0…1
normalized value+1.20relative to mean

The values 204, 0.80, and +1.20 are three descriptions of the same channel at different preparation stages. If the wrong mean or standard deviation is used, the model receives a distribution it was not trained to interpret.

At this point we still have prepared pixel values. We do not yet have a semantic embedding.

3. Why not search using raw pixels?

At first, raw pixels sound sufficient. Every visible detail is already stored in them. Why introduce features or embeddings when we can compare those numbers directly?

The problem is not a lack of detail. The problem is what the comparison means.

Raw-pixel distance behaves like placing identical sheets of graph paper over two images. The value at row 1, column 1 in the first image is compared only with row 1, column 1 in the second. Row 1, column 2 is compared only with row 1, column 2, and so on.

For two 100×100 RGB images, this creates 30,000 fixed-coordinate comparisons:

referencepixel at row r, column c candidatepixel at row r, column c

The calculation does not ask whether a red pixel moved nearby. It asks whether the same numeric address still contains the same value.

Run the one-pixel experiment

In this tiny experiment, a red six-pixel shape represents a car. Treat every red cell as 1 and every background cell as 0. Switch the candidate among three states and watch what the fixed-coordinate comparison reports.

Interactive experiment · same appearance, different coordinates

Reference imagenever moves
Candidate imageexact copy

Inspect one fixed addressrow 4, column 2

Reference containscar pixel = 1.0
Candidate containscar pixel = 1.0

match: difference 0

Human judgementsame car, same place
Changed pixel addresses0 of 42
Squared raw distance0.00

The copy occupies the same coordinates, so every comparison lines up.

After the one-pixel shift, a person still sees the same car. Raw comparison sees four disagreements: two addresses where red disappeared and two new addresses where red appeared. It has no rule saying “look one cell to the right.”

The faded car exposes an even stranger result. Its six pixels changed from 1.0 to 0.6, giving a squared distance of 0.96. That is smaller than the shifted car’s distance of 4.00. Raw distance therefore considers the faded, altered car closer than the unchanged car that merely moved.

The same failure appears in real photographs

Real images contain softer edges, lighting, texture, and compression artifacts, but the coordinate problem is identical:

CandidateHuman judgementRaw-pixel problem
exact copysame imageworks well
JPEG copysame apparent imagecompression changes values
one-pixel shiftsame subject and scenecoordinate alignment breaks
brighter copysame subject and layoutmost intensities change
cropsame subject may remaindimensions and positions change
different red objectdifferent subjectmatching colour can look deceptively close

Tiny dry run

We can reduce the animation to two grayscale pixels. Here 0 means black and 1 means white:

original[0, 1]
shifted[1, 0]

Squared L2 distance is:

Distance from original to shifted copy

(0 − 1)2 + (1 − 0)2 = 2

Now compare an altered gray pattern whose light and dark values remain at the original coordinates:

aligned gray pattern[0.4, 0.6]

Distance from original to aligned gray pattern

(0 − 0.4)2 + (1 − 0.6)2 = 0.32

Raw distance prefers the unrelated aligned pattern over the shifted original. The numbers are tiny, but the failure mechanism scales to real images.

A detailed representation is not automatically a useful similarity representation.

4. From an image to features—and then to an embedding

The previous experiment exposed the weakness of raw pixels: they preserve exact locations but do not describe what is present. A feature is a measurement intended to preserve a more useful property.

For a car photograph, a feature might measure:

  • how much red appears in the image;
  • how bright the image is overall;
  • how many strong edges it contains;
  • whether its dominant shape is low, wide, round, or tall.

Unlike one pixel, a feature may summarize evidence from hundreds or millions of pixels. Moving the car a few pixels does not necessarily change its overall redness, width, or edge density very much.

Feature-extraction workbench

Use the controls below to inspect one image in several ways. The example uses human-designed measurements first because their meaning is visible. A learned encoder will later replace these named rules with measurements learned from data.

Interactive experiment · turn one image into measurements

wide, low silhouette

Start with the decoded photograph.

Rednessshare of strong red evidence0.82
Brightnessaverage light intensity0.64
Edge densityamount of strong boundary evidence0.71
Roundnessstrength of circular shape evidence0.31
Ordered output[—, —, —, —]redness · brightness · edges · roundness

The photograph is still a pixel grid. No feature measurements have been selected yet.

When all four measurements are assembled in a fixed order, the result is a feature vector:

One image becomes one ordered list

car features=[0.82, 0.64, 0.71, 0.31]

The order is part of the contract. Coordinate 1 always means redness in this toy representation; swapping coordinates would change the meaning.

The vector is not a miniature image. It is a summary. We deliberately discarded the exact position of every pixel and retained four measurements that may be more stable and useful for comparison.

A vector is also a point

If a vector contains two features, we can draw it directly on a two-dimensional graph. The horizontal coordinate below is redness and the vertical coordinate is edge density.

Visualizing a two-feature representation

redness →edge density → Ferrari(.82, .71) red apple(.88, .22) blue Porsche(.12, .69)

Ferrari and apple agree strongly on redness. Ferrari and Porsche agree strongly on edge density. Which pair is “nearer” depends on which measurements and weights define the space.

This picture is the first useful intuition for vector search:

Similar objects are represented by nearby points; dissimilar objects are represented by distant points.

But nearby according to what? The feature choices answer that before the search algorithm runs.

Worked comparison

ObjectRednessBrightnessEdge densityRoundness
Ferrari0.820.640.710.31
red apple0.880.600.220.91
blue Porsche0.120.610.690.30

If redness receives most of the weight, Ferrari approaches the apple. If edge and shape measurements receive more weight, Ferrari approaches the Porsche.

Weighted squared distance

d(a, b)=jwj(aj − bj)2

j selects a feature, wj controls its importance, and the squared difference measures disagreement on that feature.

The measurements, their order, their scale, and their weights jointly define the geometry. Search only operates on the geometry it receives.

From hand-designed features to learned embeddings

The four measurements above were chosen and named by a person. This is useful for teaching and can work well for narrow problems, but it requires us to anticipate every property that matters.

A learned image encoder follows the same broad input-output pattern while learning the measurements from examples:

Conceptual transformation · the encoder learns the measurements

Imagehuman sees a red car
Prepared pixelsnumbers arranged by location
edges + texturesparts + layoutslearned mixturesEncoderparameters learned during training
.12  −.34  .81  .07  …Embeddingone point in a learned space

The intermediate labels are intuition, not a promise that one layer or one coordinate has exactly one human-readable meaning.

The encoder is a function with learned parameters. Give it an image and it returns an ordered list of numbers. For example, a 512-dimensional model returns 512 numbers per image.

The crucial difference is how those numbers were obtained:

Hand-designed feature vectorLearned embedding
a person defines redness or edge densitytraining discovers useful mixtures of evidence
coordinates usually have explicit namesmeaning is distributed across many coordinates
designed for anticipated variationscan learn invariances from examples
quality depends on human rulesquality depends on data, objective, architecture, and training

An embedding therefore means more than “an array from a neural network.” Its geometry was shaped by a training objective. If matching car images were pulled together during training, the encoder can learn to keep car identity stable despite changes in pixel location, background, crop, or lighting.

How can we visualize a 512-dimensional embedding?

We cannot draw 512 perpendicular axes on a screen. Instead, we inspect the same embedding in three complementary ways.

1. Coordinate fingerprint

Colour can show sign and magnitude for each coordinate. This verifies shape and numeric behaviour, but individual cells rarely have simple names.

2. Nearest-neighbour behaviour

query carPorscheFerrarivehiclebeachreceipt

Ask which real images are closest. Neighbours reveal what the representation treats as similar and are usually more meaningful than staring at coordinates.

3. Two-dimensional projection

carsbeachesdocuments

PCA, UMAP, or t-SNE can compress many dimensions into two. The picture is useful for patterns, but projection necessarily distorts some distances.

These views answer different questions:

  • the coordinate fingerprint asks whether the vector looks numerically sane;
  • nearest neighbours ask whether useful relationships survived;
  • a projection asks whether large-scale groups or outliers are visible.

No visualization proves that an embedding is universally meaningful. Test it against the relationship the application needs.

Five rules to carry forward

  1. One image becomes one vector only after a chosen feature extractor or encoder processes it.
  2. The vector is a summary, not a compressed picture that can always be read by a person.
  3. A learned embedding gets its meaning from training relationships and its neighbours—not from arbitrary coordinate names.
  4. Different models can produce different embeddings for the same image.
  5. Images must use the same preprocessing and encoder before their vectors can be compared meaningfully.

5. What is an embedding?

An embedding is an ordered list of numbers produced by a learned encoder. The list represents one object as one point in a space where distance is supposed to express a useful relationship.

That sentence contains four separate ideas:

  1. Ordered list: coordinate 17 cannot be silently exchanged with coordinate 203.
  2. Learned encoder: training, rather than a person, determines how pixels influence the numbers.
  3. One point: the complete vector gives the object’s location in the learned space.
  4. Useful relationship: nearby must mean something chosen during training, such as same subject, matching caption, or same product.

The encoder contract

z=fθ(image)

image is the prepared pixel tensor, f is the encoder, θ is everything the encoder learned, and z is the resulting embedding.

The formula does not say that the encoder understands images as a person does. It says only that one fixed transformation maps an image to a vector. Whether that mapping is useful must be tested.

Every embedding is a vector; not every vector is an embedding

A vector is merely an ordered list of numbers. Raw pixels, random numbers, and handwritten measurements can all be vectors. We use the word embedding when an object has been mapped into a space whose geometry carries learned relationships.

RepresentationWhere do the numbers come from?What shapes proximity?
raw pixelsimage decodermatching values at fixed coordinates
colour histogramhuman-written counting rulesimilar colour proportions
hand feature vectorhuman-selected measurementschosen features and weights
random vectorrandom generatornothing useful by design
learned embeddingtrained encodertraining data and objective

The last column is the important one. A database can store any of these vectors, but storage does not make their distances meaningful.

How training creates meaning

Before training, an encoder’s vectors are not arranged for our task. Training repeats a feedback loop:

  1. send example images through the encoder;
  2. inspect the distances between their embeddings;
  3. penalize relationships that violate the training objective;
  4. propagate that error back through the encoder;
  5. adjust the learned parameters and try again.
1Examplesimages and known relationships2Encoderproduce embeddings3Distancescompare the points4Lossmeasure wrong geometry5Update θchange the encoder

The phrases “pull together” and “push apart” are geometric intuition for what a loss function encourages:

Conceptual training pressure

loss=penalty when related points are far+penalty when unrelated points are close

Real objectives have precise formulas. This version exposes their purpose: penalize a geometry that disagrees with the relationships supplied by training.

The encoder—not a database—changes during this loop. Once training finishes, the encoder can place new images into the learned space without moving the old points by hand.

Same images, different objective, different embedding

There is no objective-free meaning of similarity. Use the experiment below to train the same illustrative images for two different relationships.

Interactive experiment · choose what “nearby” should mean

🚗red car🚙blue car🍎red apple🍏green apple🏖beach🌊ocean
Training instructionNo useful relationship has been trained.
Nearest neighbour of “red car”arbitrary
What distance now emphasizesnothing dependable

The starting positions are illustrative and unstructured. Proximity has not yet been trained to answer our question.

Under subject training, the red car approaches the blue car even though their colours differ. Under colour training, the same red car approaches the red apple even though their subjects differ.

Nothing about Euclidean distance or cosine similarity chose this behaviour. Training chose the representation on which those distance calculations operate.

Why individual dimensions are hard to name

In the four-feature example, coordinate 1 explicitly meant redness because a person designed it that way. A learned embedding is different. Evidence is usually distributed across many coordinates.

A “sports-car direction” might involve increasing coordinates 7, 91, and 304, decreasing coordinates 18 and 250, and changing many others slightly. No single coordinate needs to mean “sports car.”

There is also a geometric reason. Rotate every point in a space by the same amount and all angles and distances remain unchanged, even though every point’s coordinate values change.

Interactive proof · coordinates can change while geometry survives

car Acar Bbeachdim 1dim 2

car A coordinates[0.80, 0.20]

car B coordinates[0.70, 0.30]

distance A ↔ B0.141

The coordinates describe the points relative to the current axes. Car A and car B are close.

After rotation, the coordinate values change but the distance remains 0.141. Therefore, naming one axis is often arbitrary even when the neighbourhood structure is useful. Meaning lives more reliably in directions, distances, and neighbourhoods than in isolated coordinates.

What does embedding dimension mean?

The dimension is the number of coordinates in each embedding. A 32-dimensional embedding contains 32 numbers; a 512-dimensional embedding contains 512.

More dimensions give the model room to preserve more independent patterns, but they also increase memory, storage, transfer, and comparison work. More is not automatically better: unused or poorly trained dimensions can be redundant or noisy.

Use the dimension lab to see the systems cost for float32 embeddings. Each float32 coordinate requires four bytes.

Interactive experiment · embedding capacity and storage

Coordinates per image128
Bytes per float32 vector512 B
One million vectors512 MB
Dot-product multiplications128 per candidate

128 dimensions can be a compact representation, but adequacy depends on the model, data, and task—not the number alone.

Raw vector-storage estimate

bytes=number of objects×dimensions×bytes per coordinate

This estimates only raw vectors. Index structures, identifiers, metadata, allocator overhead, and replicas require additional space.

To test dimension rather than guess, hold the dataset, splits, preprocessing, model family, training budget, and evaluation procedure constant. Change the dimension and measure retrieval quality, latency, and memory together.

How do we know whether an embedding is good?

An embedding is not good because its values look sophisticated or its 2D plot looks attractive. It is good when it preserves relationships needed by the application while ignoring irrelevant variation.

Use controlled tests:

TestKeep fixedChangeDesired observation
translationsubject and scenemove image a few pixelsembedding changes little
brightnesssubject and layoutlighten or darkenneighbourhood stays useful
viewpointobject identitycamera anglesame object/class remains nearby
negative paircolour or backgroundsubjectdifferent subjects stay separable
duplicate testapparent imagecompression/resizeduplicate remains very near
collapse testnothingmany unrelated imagesembeddings do not all become identical

“Changes little” must be defined relative to the task. A forensic duplicate detector may care about an edit that a semantic photo organizer should ignore.

Complete neighbourhood dry run

Suppose the hidden query is a red Ferrari. Compare the five nearest neighbours under the two spaces from the training experiment:

RankSubject-trained spaceColour-trained space
1Porschered apple
2Lamborghinired shoe
3race carfire truck
4sports coupered rose
5blue Ferrarired Ferrari toy

The subject-trained space answers “what kind of object is this?” The colour-trained space answers “what has a similar dominant colour?” Both can be internally consistent. Only the application tells us which is useful.

Hide each labelled image in turn, ask its five nearest neighbours to vote, and count correct predictions:

Five-nearest-neighbour accuracy

5-NN accuracy=correct neighbour-vote predictionsnumber of tested images

If 84 of 100 hidden images receive the correct neighbour vote, accuracy is 84/100 = 0.84, or 84%.

Nearest-neighbour accuracy is only one probe. A real evaluation should also inspect failures, class balance, duplicates, out-of-distribution images, retrieval at several values of K, latency, and memory.

The central lesson is:

An embedding does not contain universal meaning. It contains geometry learned under a particular training contract, and that geometry earns trust through experiments.

6. How an image becomes an embedding

The previous section described training: repeated feedback changes the encoder’s parameters. This section describes inference: the parameters are now fixed, and we use the trained encoder to transform one new image.

The complete operation is:

1Image fileJPEG or PNG bytes2DecodeRGB pixel grid3Prepareresize and normalize4EncoderCNN or transformer5Poolone fixed-length vector6Projecttarget dimension7Normalizefinal embedding

An embedding model is therefore not a single mysterious operation. It is a pipeline with a strict input contract and several transformations.

Stage 1: create the exact input the model expects

The encoder cannot consume a filename or compressed JPEG bytes. The input processor must first produce a numeric tensor.

OperationWhat happensWhy it matters
decodecompressed bytes become RGB pixelsdifferent decoders must agree on colour and orientation
apply orientationEXIF rotation or mirroring is resolveda sideways image is a different pixel arrangement
convert colourgrayscale, RGBA, or CMYK becomes expected RGBthe model expects a fixed channel contract
resizeimage is scaled toward the model’s input sizeneural layers expect compatible spatial dimensions
crop or padfinal width and height are producedcropping can remove evidence; padding adds context
scale and normalizechannel values follow the training recipewrong statistics shift every downstream activation
add batch axisone or more images are groupedmodel input commonly begins with batch size

For a common 224×224 RGB input, the resulting channel-first batch tensor is:

Example input shape

[1, 3, 224, 224]=[images, channels, height, width]

This tensor contains 1 × 3 × 224 × 224 = 150,528 prepared channel values.

The model sees those values. It does not receive the words “red car.”

Stage 2A: the convolutional path

A convolutional neural network, or CNN, repeatedly applies small learned filters to local neighbourhoods. A filter is a small grid of weights. At every location, it multiplies its weights by the corresponding image values and adds the products.

The same filter is reused across the image. This weight sharing is why a local pattern can activate the filter whether it appears near the left edge or the right edge.

Dry run: scan a vertical-edge filter

The image below has dark pixels on the left and light pixels on the right. The 3×3 filter subtracts the left column of each window and adds the right column. Uniform windows produce zero; windows crossing the boundary produce a strong positive response.

Runnable convolution · slide one learned filter over an image

6×6 input

dark = 0 · light = 1

3×3 filter

−10+1−10+1−10+1
left − · centre 0 · right +

4×4 feature map

large value = edge detected
Ready: the window starts at row 1, column 1.

At a window that crosses the edge, each row contains [0, 0, 1]:

One convolution response

[(−1 × 0) + (0 × 0) + (1 × 1)]×3 rows=3

The result 3 is written into the feature map at the window's current position. A real layer learns many filters rather than receiving this hand-written one.

Step through a CNN encoder

The filter scan explained one operation at one layer. A full CNN repeats and combines several operations. Step through a simplified encoder from its input image to its final embedding.

CNN dry run · local filters become one whole-image vector

1. Begin with prepared pixels

batch1channels3height224width224

At this point the model has RGB values arranged by location. It has not yet produced learned visual features.

This five-step sequence is illustrative. Real CNN families differ in filter sizes, residual connections, normalization layers, activation functions, downsampling strategies, and pooling heads. The recurring idea is local learned processing followed by progressively wider context and a fixed-length output.

From one filter to a hierarchy

One edge filter is not an image encoder. A CNN layer may learn dozens or hundreds of filters, producing one feature map per output channel. Subsequent layers combine nearby responses over progressively larger portions of the original image.

inputpixelsearly layersedges + texturesmiddle layersparts + layoutslate layerslearned mixtures

“Edges,” “textures,” and “parts” are useful intuition, not a guarantee that one channel has one clean English name. The actual representation is distributed and learned.

Spatial dimensions often shrink while channel depth grows. An illustrative CNN shape ledger might look like this:

StageTensor shapeIntuition
input[1, 3, 224, 224]three colour channels
early feature maps[1, 64, 112, 112]many local response maps
middle feature maps[1, 256, 28, 28]richer evidence over larger context
late feature maps[1, 1024, 7, 7]1,024 channels at 49 spatial locations
global pooling[1, 1024]one value per channel

These exact numbers depend on the architecture. The pattern—spatial evidence becoming a fixed-length summary—is what matters.

Pooling removes the remaining spatial grid

Suppose one late feature channel has a 2×2 map:

1326
3.0

Global average pooling

1 + 3 + 2 + 64 locations=3.0

Repeat this for every channel. A 7×7×1,024 feature tensor then becomes a vector with 1,024 values.

Pooling deliberately forgets exact location. That can make the result more stable when an object moves, but it can also discard spatial information needed by tasks such as segmentation or precise localization.

Stage 2B: the Vision Transformer path

A Vision Transformer, or ViT, reorganizes the same prepared pixels differently. Instead of sliding convolution filters first, it divides the image into non-overlapping patches and treats each patch as a token.

For a 224×224 image with 16×16 patches:

Patch count

224 pixels16 pixels per patch=14 patches per side14 × 14 = 196 patches

Each RGB patch initially contains 16 × 16 × 3 = 768 channel values before its learned linear projection.

Step through the transformation:

Vision Transformer dry run · one image becomes communicating tokens

1. Draw a patch grid over the image

The teaching picture uses 4×4 = 16 patches so we can see them. A 224px image with 16px patches has 196.

Self-attention does not literally label patches “wheel” or “window.” Those names explain the desired intuition. The model manipulates token vectors and learned weights.

CNN and Vision Transformer: different route, same contract

QuestionCNNVision Transformer
first local unitsliding receptive fieldnon-overlapping image patch
core mixing operationconvolutionself-attention plus feed-forward layers
position handlingbuilt into spatial gridexplicit or learned positional information
spatial intermediatefeature mapspatch tokens
whole-image summaryglobal pooling or headsummary token, mean pooling, or head
final resultfixed-length vectorfixed-length vector

Modern models can combine both ideas. The important application-level contract is unchanged: prepared pixels enter; one vector exits.

Stage 3: projection chooses the embedding dimension

Pooling may produce a vector whose size is determined by the backbone—perhaps 1,024 values. A learned projection can mix those values into the dimension used for retrieval—perhaps 512.

pooled backbone vector1,024 values×learned matrix1,024 × 512=projected vector512 values

Projection is not selecting the first 512 coordinates. Every output coordinate can combine evidence from every input coordinate. The projection is part of the trained model and therefore part of embedding identity.

Stage 4: L2 normalization places the vector on a unit sphere

Many retrieval models divide the projected vector by its Euclidean, or L2, length. Consider the tiny vector [3, 4].

Step 1 · calculate vector length

∥[3, 4]∥2=√(32 + 42)=5

Step 2 · divide every coordinate by the length

[3, 4]5=[0.6, 0.8]

The direction is unchanged, but the new vector's length is 1. This makes dot product equivalent to cosine similarity.

[3, 4][0.6, 0.8]same direction, standardized length
Beforedirection + magnitudelength = 5Afterdirection retainedlength = 1

Normalization removes magnitude as a ranking signal. That is desirable only when the model was designed and evaluated with direction-based similarity.

Complete end-to-end shape dry run

Follow one image through a simplified Vision Transformer example:

StepRepresentationShape or size
1JPEG filecompressed bytes; no tensor shape yet
2decoded RGB image[H, W, 3]
3prepared model batch[1, 3, 224, 224]
4196 projected patch tokens plus one summary token[1, 197, 768]
5contextualized tokens after transformer blocks[1, 197, 768]
6selected or pooled summary[1, 768]
7learned output projection[1, 512]
8L2-normalized embedding[1, 512], each row has length 1

The batch dimension remains 1 because we embedded one image. The token axis appears and later disappears. The final 512 coordinates no longer correspond to pixel rows, columns, or individual patches.

What can invalidate an existing embedding?

An embedding is derived data. Its identity includes the complete recipe:

Embedding identitydecoder and orientationresize, crop, and paddingchannel scaling and normalizationencoder architecture and weightspooling ruleprojection weights and dimensionfinal normalization

Change any of these and old and new vectors may no longer occupy the same space. That is why an embedding cache should record a model/preprocessing version and invalidate vectors when the source image or recipe changes.

The final result may look like a plain array:

Output contract

image embedding=[0.12, −0.34, 0.81, 0.07, …]R512

The array is useful because training shaped its relationships, not because these illustrative numbers can be interpreted one at a time.

7. How text becomes an embedding

We can read the sentence:

a red sports car

and immediately imagine a kind of object. A neural network does not receive that experience. At the start it receives encoded characters. Before it can compare the sentence with an image, the system must turn a variable-length sequence of characters into a fixed-length vector.

That transformation has several distinct stages:

human input“a red sports car” tokenize → discrete pieces[a] [red] [sports] [car] encode → contextual numberstoken vectors pool → search representation[0.18, −0.42, …]

The important distinction is:

characters are not tokens
tokens are not token IDs
token IDs are not token vectors
token vectors are not yet the final sentence embedding

Let us construct the result one stage at a time.

Step through a text encoder

Interactive transformation · sentence → embedding

1. Start with a character string

The application holds a sequence of Unicode characters. Spaces and punctuation are part of that sequence; a model has not yet decided what counts as a useful unit.

aspaceredspacesportsspacecar

At this point: we have text that a program can store, but no learned representation of its meaning.

Why not keep one token per word?

A word-only vocabulary creates several problems:

known word:       car
new compound:     hypercar
misspelling:      hypercarr
inflection:       cars
another language: voiture

If every possible word needs its own row, the vocabulary becomes enormous and still cannot contain every name, spelling, compound, URL, or new term. Subword tokenization reuses smaller pieces:

InputOne possible illustrative splitWhat reuse buys us
carcarcommon word stays compact
carscar + splural reuses the base
sportscarsport + scaruncommon compound remains representable
unsearchableun + search + ableprefixes and suffixes are shared

Splitting more finely is not always better. More tokens increase compute and consume the model’s fixed context length. The tokenizer is therefore part of the model design, not a harmless text utility that can be swapped later.

A tiny self-attention dry run

The lookup table initially gives bank the same learned token vector wherever it appears. Self-attention creates a context-sensitive update.

For one attention head, use this beginner-sized mental model:

query from "bank" asks: which positions are useful to me?
key from every token answers: what kind of information do I contain?
value from every token carries: the information that can be mixed

The query–key comparisons become attention scores. A softmax converts those scores into non-negative weights that add to 1. The output for bank is a weighted mixture of the value vectors.

Interactive self-attention · update the token “bank”

Choose a sentence, then run the attention head. Watch where the evidence comes from.

query token bank “Which surrounding information should update me?”
the
? waiting to compare
river
? waiting to compare
bank
? waiting to compare
flooded
? waiting to compare
weighted value-vector mixture bank′ = ? The contextual meaning will appear after the mix.

Ready: the query from “bank” has not compared itself with the four keys yet.

In the river sentence, the teaching weights are:

One-head value mixture for the river sentence

bank′=0.05·Vthe+0.60·Vriver+0.20·Vbank+0.15·Vflooded

The 60% river contribution dominates this teaching head, while bank retains some of its original information and flooded adds supporting evidence.

The prime in bank′ means “the updated representation of bank.” Each V is a complete value vector, not one scalar. The weighted additions happen coordinate by coordinate across that vector.

Switch to open a bank account. The lookup vector for bank begins the same, but the 70% weight on account produces a different contextual update. This is how one token can participate in two meanings without requiring two token IDs.

This does not mean a single attention head is a complete explanation of meaning. Real transformers use many heads and layers, residual connections, normalization, and nonlinear transformations. The dry run isolates the central intuition: a token representation can depend on the entire sentence.

Pooling: how many token vectors become one vector

After the transformer, we still have one vector per token:

6 tokens × 768 values per token

Search needs one vector per query. Common pooling choices include:

Pooling choiceOperationConsequence
special summary tokenuse one designated token’s final vectortraining teaches that position to collect sentence information
end tokenuse the final end-of-text representationthe end token can attend to the preceding sequence
mean poolingaverage valid token vectorsevery valid position contributes directly

There is no universally correct pooling rule. Use the rule that belongs to the trained model. Replacing end-token pooling with a casual average can move the sentence into a different geometry even when the encoder weights are unchanged.

Complete text tensor ledger

A tensor ledger is bookkeeping for the arrays moving through the model. At every stage it records:

what the numbers currently represent
how many axes the array has
what each axis counts
which axis appeared or disappeared

The shape does not reveal the values inside the tensor. It describes how those values are arranged.

For text, three axis names appear repeatedly:

BbatchHow many sentences are processed together? Lsequence lengthHow many token positions are reserved per sentence? Hhidden widthHow many learned features describe each token?

Therefore:

How to read a text tensor shape

[B, L, H]=[sentences, token positions, learned features per token]

Read the axes as counts. A shape of [1, 77, 768] means one sentence, 77 reserved positions, and 768 values describing every position.

We will dry-run a tiny model with only eight token positions and four hidden features. The smaller numbers let us draw every part. Afterwards we will map the same operations to a realistic [1, 77, 768] tensor.

Interactive tensor ledger · follow every axis

1. Tokenize, add boundary tokens, then pad the unused positions

Our miniature encoder always reserves eight positions. This sentence uses six; two positions remain as padding.

0<start> 1a 2red 3sports 4car 5<end> 6<pad> 7<pad>
attention mask 11111100
token IDs[1, 8]1 sentence × 8 reserved positions

The mask says which positions contain real sequence information. A `1` participates; a `0` is ignored by attention and pooling.

Translate the miniature ledger to a real model

The exact dimensions are model-specific, but the axis changes are the same:

StageExample production shapeWhat changed?
token IDs[1, 77]one sentence occupies 77 reserved token positions
token lookup[1, 77, 768]lookup adds 768 learned features to every position
transformer output[1, 77, 768]contextual values change; shape remains unchanged
pooled summary[1, 768]pooling removes the 77-position sequence axis
projected vector[1, 512]projection changes hidden width from 768 to 512
normalized embedding[1, 512]values rescale to unit length; shape remains unchanged

Why is the first number always 1 here?

It is 1 only because we embedded one sentence. If we process:

sentence A = "a red sports car"
sentence B = "waves on a beach"
sentence C = "a photographed invoice"

the token-ID shape becomes [3, 77], the transformer output becomes [3, 77, 768], and the final embedding matrix becomes [3, 512].

row 0 → embedding for sentence A
row 1 → embedding for sentence B
row 2 → embedding for sentence C

Batching adds more rows. It does not mix the three sentences into one embedding.

Padding, masking, and truncation are separate ideas

  • Padding fills unused positions so every sentence in a batch has the same rectangular shape.
  • Masking tells attention and pooling which positions are padding and should not contribute.
  • Truncation removes tokens beyond the model’s maximum context length. Those removed tokens are gone; a mask cannot recover them.

A shape of [1, 77] therefore does not mean the user typed 77 meaningful tokens. It means the model reserved 77 positions for one sentence.

Four failure experiments worth running

Use these as tests, not assumptions:

  1. Paraphrase: compare red sports car with crimson performance automobile. A useful semantic model should often keep them near despite low word overlap.
  2. Order: compare dog bites person with person bites dog. If the model ignores the changed relationship, its representation is too coarse for that task.
  3. Negation: compare a red car with not a red car. Embeddings can be weak at strict logical negation even when they capture the subject strongly.
  4. Truncation: place the important phrase after the maximum context length. If the tokenizer removes it, the encoder never sees it.

The final text vector is a learned summary, not a lossless compressed sentence. It may preserve subject, style, action, and broad intent while losing exact numbers, spelling, word order subtleties, or logical operators.

8. How images and text enter one shared space

We now have two pipelines:

image → image encoder → image vector
text  → text encoder  → text vector

That still does not make text-to-image search possible.

Equal dimensions do not imply compatible meaning

Suppose an image encoder and an unrelated text encoder both return two numbers:

car image     → [1, 0]
"a car"       → [0, 1]

Both vectors are two-dimensional. They still point at right angles:

Equal length, incompatible axes

[1, 0]·[0, 1]=0

The image model and text model may have invented unrelated coordinate systems. Matching array length only makes the multiplication legal; it does not make the result meaningful.

To compare modalities, training must give both encoders a shared contract:

matching images and captions should point in similar directions; mismatching images and captions should point in different directions.

The training material is made of pairs

One training batch might contain:

image 1“a red sports car” image 2“waves reaching a beach” image 3“a photographed invoice”

The image encoder produces three image vectors. The text encoder produces three caption vectors. Every image is compared with every caption, creating a 3 × 3 similarity matrix.

Read a contrastive similarity matrix

Before useful training, the matrix might be confused:

Imagesports-car textbeach textinvoice text
Ferrari image0.180.420.11
Beach image0.330.250.37
Invoice image0.290.310.22

The bold diagonal cells are the known matching pairs. They are not yet reliably larger than the alternatives.

After training:

Now each diagonal match is stronger than the other captions in its row and the other images in its column.

What one contrastive training step is trying to change

Take the Ferrari row:

correct caption:   sports car   0.91
wrong caption:     beach        0.12
wrong caption:     invoice      0.04

The objective rewards a large score for the paired caption and smaller scores for the alternatives. It also checks the reverse direction: given the caption sports car, the Ferrari image should outrank the beach and invoice images.

The similarities are often scaled by a learned or fixed temperature before a softmax:

Row-wise contrastive comparison

P(text j | image i)= exp(sim(i, j) / τ)Σk exp(sim(i, k) / τ)

The temperature τ controls how sharply small similarity differences affect the competition. The correct pair is trained to receive most of the probability mass.

The cosine scores do not change when we change the temperature. What changes is how strongly the softmax turns those scores into a competition. Drag the temperature below while keeping the three Ferrari-row similarities fixed.

Controlled dry run · temperature and softmax

colder · sharperwarmer · flatter
fixed cosinesports car · 0.91
fixed cosinebeach · 0.12
fixed cosineinvoice · 0.04

At a low temperature, a modest score advantage becomes a decisive probability advantage.

This is why raw cosine and contrastive probability must not be treated as the same number. Cosine describes geometry. Temperature and softmax turn a row of geometric scores into a normalized competition during this CLIP-style training objective.

A gradient update then changes both encoders:

matching image and caption directions     → closer
competing mismatched directions           → farther apart

Repeat this over many diverse batches and the two encoders gradually learn a compatible geometry.

Run one conceptual alignment step

The left and right halves begin as unrelated coordinate systems. Run the step to align each known pair.

car image beach image invoice image “sports car” “beach” “invoice”

Pairs begin in unrelated illustrative positions.

The animation is a two-dimensional teaching diagram. Production embeddings may have hundreds or thousands of dimensions, and one update does not neatly solve the space. What transfers is the relationship: paired items receive attraction; competing items create separation.

Why the captions determine what the model can learn

Consider one photograph containing:

a red car
a wet road
mountains
sunset light
two people

If its caption is only a red sports car, the training signal names the car but not the wet road or people. Across a large dataset, repeated captions teach which distinctions matter. Missing, noisy, culturally narrow, or biased captions shape the geometry too.

One image can also have many valid descriptions:

a red sports car
a vehicle on a mountain road
a photograph taken at sunset
an expensive automobile

These descriptions emphasize different properties. Shared space is therefore not a dictionary in which an image has one exact sentence. It is a geometry of learned compatibility.

What the shared space enables

Once both modalities obey the same geometric contract:

text query → text encoder  ─┐
                            ├─ compare directions → rank images
stored image → image encoder┘

The model can retrieve an image for a phrase it has never stored as metadata. It can also perform zero-shot classification by comparing one image vector with several prompt vectors:

"a photo of a car"
"a photo of a beach"
"a photo of an invoice"

The highest compatibility becomes the predicted label. No new classifier was trained for those three labels; the prompts act as class prototypes in the shared space.

Compatibility checklist

Before comparing an image vector and text vector, verify:

  1. both encoders belong to the same multimodal model and weight revision;
  2. each input used that model’s matching preprocessing or tokenizer;
  3. both outputs use the expected projection heads;
  4. both vectors have the same dimension;
  5. both use the same normalization and similarity convention.

Passing only item 4 is not enough.

9. Creating and inspecting embeddings in practice

The first successful model call often prints an array of numbers. That proves only that code executed. A useful experiment must establish the complete contract and show that the vector behaves as intended.

We will design a small retrieval experiment that can expose mistakes quickly.

Build a tiny controlled dataset

Use at least three clear subjects and one altered copy:

A · red caroriginal image B · red carrecompressed copy of A C · beachunrelated scene D · invoiceunrelated document

And use text probes with deliberate controls:

T1 = "a red sports car"          expected: A or B
T2 = "waves on a sandy beach"   expected: C
T3 = "a photographed receipt"   expected: D
T4 = "a blue bicycle"           expected: none strongly

The altered copy checks invariance. The unrelated images check separation. The unsupported bicycle query checks whether the system still returns a top result even when no result is genuinely good.

Stage 1: load one model contract

Keep these components together:

model, image_preprocess, tokenizer = load_compatible_model(
    name="chosen-model",
    revision="exact-weight-revision",
)
model.eval()

eval() matters for models containing training-only behaviour such as dropout. The exact API depends on the model package, but the invariant is universal: image preprocessing, tokenizer, architecture, projection heads, and weights must come from one compatible revision.

Stage 2: preprocess without confusing input tensors and embeddings

image_tensor = image_preprocess(image).unsqueeze(0)
token_ids = tokenizer(["a red sports car"])

print(image_tensor.shape)  # [1, 3, 224, 224], for this example model
print(token_ids.shape)     # [1, 77], for this example tokenizer

At this point:

image_tensor = normalized pixels
token_ids    = integer vocabulary addresses

Neither is a semantic embedding yet.

Stage 3: run inference, then normalize the outputs

with no_grad():
    image_features = model.encode_image(image_tensor)
    text_features = model.encode_text(token_ids)

image_embedding = image_features / image_features.norm(dim=-1, keepdim=True)
text_embedding = text_features / text_features.norm(dim=-1, keepdim=True)

The no-gradient context avoids storing training intermediates when we only need inference. Normalizing each row makes cosine similarity equal to a dot product.

Inspect the contract before inspecting quality

Print structural facts:

image tensor shape:       [1, 3, 224, 224]
token ID shape:           [1, 77]
image feature shape:      [1, 512]
text feature shape:       [1, 512]
image feature dtype:      float32
image embedding norm:     1.0000
text embedding norm:      1.0000
all values finite:        true

Each check catches a different class of error:

CheckFailure it can expose
input shapewrong crop, channel order, or missing batch dimension
output shapewrong projection head or model variant
dtype/deviceaccidental precision or device mismatch
finite valuesoverflow, invalid input, or numerical failure
norm near 1missing or incorrect row normalization

A vector can pass every structural check and still be semantically poor. The next stage tests behaviour.

Stage 4: embed a batch and build the complete score matrix

Stack four image embeddings into a matrix:

I shape = [4, 512]

Stack four text embeddings:

T shape = [4, 512]

Then:

All image–text comparisons in one operation

S=I TTR4 × 4

Every row is one image. Every column is one text probe. Cell S(i,j) is their cosine similarity when both matrices are row-normalized.

In code:

scores = image_embeddings @ text_embeddings.T

An illustrative result:

image ↓ / query →sports carbeachreceiptbicycle
A · red car0.860.140.050.19
B · recompressed A0.840.130.060.18
C · beach0.100.910.080.12
D · invoice0.030.090.880.04

Do not obsess over these invented score values. Inspect the relationships:

correct pairs outrank controls
altered copy stays near the original
unrelated subjects separate
unsupported query lacks a convincing match

Stage 5: run invariance experiments

Create controlled variants of A:

VariantWhat changedQuestion
JPEG recompressionbytes and fine noisedoes semantic identity survive?
resize and restoresampling detailis scale handling robust?
mild brightness changechannel valuesis content stronger than illumination?
tight cropcomposition and contextdoes the model still recognize the subject?
horizontal flipspatial orientationdoes direction matter to this model?

For each variant, record:

similarity(original, variant)
rank for "a red sports car"
rank for an unrelated control prompt

This turns “the embeddings look good” into evidence. It also reveals what the chosen representation treats as invariant.

Stage 6: prove batching does not change individual results

Embed A alone, then as part of [A, B, C, D]:

single = encode_images([A])[0]
batched = encode_images([A, B, C, D])[0]
delta = abs(single - batched).max()

In deterministic evaluation, the difference should be zero or within a small floating-point tolerance. A large change can indicate missing evaluation mode, inconsistent preprocessing, or an incorrect batching path.

Stage 7: cache vectors with enough identity to invalidate them

Persisting only the path and vector is unsafe:

IMG_1042.jpg → [0.12, −0.34, ...]

The cache needs the source identity and embedding recipe:

image path
file size
modification time or content hash
model name
weight revision
preprocessing revision
embedding dimension
dtype
normalization rule
vector bytes

If the file changes, re-embed it. If the model or preprocessing changes, re-embed the whole affected space. Old and new readable arrays may be geometrically incompatible.

A practical debugging table

SymptomLikely causeFirst check
every result has almost the same scoremissing normalization or collapsed/incorrect outputnorms, variance, correct projection
text search is nonsense but image similarity worksincompatible tokenizer or text encoderexact model/tokenizer revision
colours are consistently wrongRGB/BGR channel mistakedecoded channel order
results change between identical runsmodel left in training modeeval(), dropout, deterministic settings
cached and fresh vectors disagreesource/model/preprocessing changedcache identity fields
GPU memory keeps growinggradients or outputs retainedinference/no-gradient context
correct result ranks first but all scores are lowscore calibration differsevaluate ranks before inventing a threshold

The central habit is to inspect shape, numeric health, and behaviour. None of the three replaces the others.

10. What semantic search is actually doing

We can now explain semantic search from first principles:

Encode stored objects and a query into one compatible learned space, measure their geometric similarity, and return the highest-scoring eligible objects.

There are two timelines: work done before the user searches, and work done for each query.

Timeline A: indexing the collection

Suppose a folder contains 50,000 images. Re-encoding all 50,000 for every query would waste time, so the system prepares them once:

1 · discoverread image pathsand file metadata 2 · decodecreate pixel tensorswith the model recipe 3 · encodecreate embeddingsusually in batches 4 · persiststore vectorswith identity and metadata 5 · indexprepare retrievalexact matrix or ANN structure

If 49,900 files are unchanged tomorrow, a correct invalidation strategy embeds only the 100 changed or new files.

Timeline B: answering one query

For the query red sports car:

1 · receivequery text“red sports car” 2 · encodequery vector[1 × 512] 3 · retrievecandidate vectorsexact or approximate 4 · scoresimilarityplus eligible filters 5 · returnranked top Kpaths and metadata

The stored images use the image encoder; a text query uses the compatible text encoder. An image query uses the image encoder. Both routes must end in the same space used by the index.

Interactive retrieval · one index, three queries

Change the query. The stored image vectors stay fixed; only the query vector and scores change.

query embedding[0.97, 0.12, 0.03]same learned 3D teaching space
1Ferrari0.963
2Porsche0.940
3Beach0.216
4Invoice0.115

Car images point most nearly in the query direction, so they rank first.

This interactive diagram uses three dimensions so the numbers remain readable. Real search performs the same ranking over much larger vectors.

Complete numerical dry run

Use these unit-length teaching vectors:

Ferrari   [0.98, 0.10]
Porsche   [0.95, 0.15]
Beach     [0.10, 0.99]
Invoice   [0.02, 0.80]

The text encoder converts red sports car into:

Query embedding

q=[0.97, 0.12]

This teaching query points mostly along the first axis, where the car vectors also point.

For normalized vectors, cosine similarity becomes:

Similarity rule

score(q, x)=q · x=Σi=1…Dqixi

Multiply corresponding coordinates, then add every product.

Ferrari:

Ferrari score

0.97 × 0.98+0.12 × 0.10=0.9626

Porsche:

Porsche score

0.97 × 0.95+0.12 × 0.15=0.9395

Beach:

Beach score

0.97 × 0.10+0.12 × 0.99=0.2158

Sorting yields the ranking:

1. Ferrari   0.9626
2. Porsche   0.9395
3. Beach     0.2158
4. Invoice   lower still

The search system did not inspect a filename containing car. It compared the query’s learned direction with each stored direction.

Cosine, dot product, and Euclidean distance

These names often appear as if they were unrelated choices.

Cosine similarity ignores magnitude and compares angle:

Cosine similarity

cos(q, x)=q · x‖q‖ ‖x‖

If both vectors are normalized to length 1, the denominator is 1:

cosine similarity = dot product

For unit vectors, squared Euclidean distance is also linked:

Unit-vector equivalence

‖q − x‖2=2 − 2(q · x)

On the unit sphere, maximizing dot product and minimizing Euclidean distance produce the same ordering. This equivalence disappears when normalization assumptions change.

Top K and thresholds answer different questions

top_k = 10 means:

return the ten highest scores, even if every candidate is poor.

minimum_score = 0.30 means:

reject candidates below this chosen boundary.

The bicycle control query may still return a car as rank 1 because rank is relative. A threshold can express “none of these is convincing,” but one universal threshold rarely transfers perfectly across models, query types, and datasets. Calibrate it on representative labelled queries.

Metadata filtering changes the eligible search space

Suppose the user asks for:

"sunset beach"
date: 2025
folder: /vacation

There are two broad orders:

pre-filter:
metadata → eligible vectors → vector search

post-filter:
vector search → candidates → metadata filter

Pre-filtering avoids spending retrieval work on ineligible items but can make some approximate indexes harder to use efficiently. Post-filtering is simple but may discard many of the retrieved top candidates, leaving too few results. Real systems choose based on index support, filter selectivity, and required recall.

Keyword search and semantic search solve different failures

Query needKeyword/OCR/metadata searchSemantic vector search
exact filename IMG_1042.jpgexcellentinappropriate
invoice number AC-49217excellent if OCR indexed itmay lose exact digits
red car on a mountain roadonly if annotated with those wordsuseful when content is visible
camera model and dateexcellent structured filternot the embedding’s job
paraphrase or broad conceptbrittle without matching wordsoften robust

A practical hybrid score may combine:

semantic similarity
+ exact filename or OCR match
+ metadata/business rules
+ optional reranker

Combining signals is not an embarrassment. Each representation preserves different information.

With N stored vectors of dimension D, flat search computes roughly:

N × D coordinate products per query

For 10,000 images × 512 dimensions, this is about 5.12 million products—often quite manageable in optimized matrix code. At tens of millions of vectors or many concurrent queries, scanning everything can become too slow.

Approximate nearest-neighbour indexes such as HNSW and IVF-PQ try to inspect only a promising subset. That adds another question:

Did retrieval include the true nearest neighbours, or did approximation miss them before accurate scoring?

Keep an exact baseline on a manageable sample. Measure recall and end-to-end latency instead of assuming a sophisticated index is automatically faster.

Evaluate search as a retrieval system

Create labelled queries with expected relevant images. Then measure:

MetricQuestion
Recall@Kdid the top K contain the relevant items?
Precision@Khow many returned items were relevant?
Mean reciprocal rankhow early did the first relevant item appear?
latency percentileshow slow are typical and worst-case queries?
index freshnessdo changed files receive updated vectors?

Also maintain qualitative slices:

objects
scenes
colours
text inside images
fine-grained models
people and activities
negation
rare concepts
cropped or low-quality images

An average metric can hide a systematic failure in one slice.

11. Experimental proof: run the representations

Everything above explains a mechanism. This section asks whether the mechanism actually behaves that way when we run the real ClusterLens embedding path.

Actual run

Numbers produced by cached model weights over named, checksummed inputs.

Controlled dry run

Small arithmetic or synthetic examples whose values were chosen to expose one operation.

Conceptual diagram

A visual explanation of mechanism; it is not a benchmark result.

The experiment uses two datasets because one dataset cannot answer every question:

  1. A controlled set of twelve generated, non-personal images lets us change one thing at a time: JPEG quality, resolution, brightness, crop, or horizontal direction.
  2. A fixed CIFAR-10 test subset supplies 500 public benchmark images, with 400 used as the gallery and 100 held out as queries.

The generated fixtures contain three cars, three empty beaches, three bicycles, and three fictional invoices. They contain no people, brands, addresses, license plates, account numbers, or real company data.

Unbranded red sports car on a neutral studio background
car 1
Unbranded blue hatchback on an empty overlook
car 2
Unbranded yellow coupe on an empty desert road
car 3
Empty tropical beach in daylight
beach 1
Empty dark volcanic beach under clouds
beach 2
Empty calm beach at sunrise
beach 3
Unbranded black road bicycle in a studio
bicycle 1
Unbranded teal city bicycle beside a wall
bicycle 2
Unbranded orange mountain bicycle on an empty trail
bicycle 3
Fictional invoice layout with placeholder lines
invoice 1
Fictional receipt-like invoice with placeholder lines
invoice 2
Fictional modern invoice with placeholder blocks
invoice 3

The experiment contract

Actual run
Evidence schemasemantic-evidence-v1
Seed
Public subset
Device
Generated at

The harness calls ClusterLens’s own EmbeddingService. It records the model ID, local cache revision, preprocessing signature, input size, output dimension, package versions, dataset indices, image checksums, seed, and timing trials. The model weights are cached locally and the final run forbids model downloads.

Three models, three different promises

The word “embedding model” hides a crucial distinction. Switch models and read what supervision each one received before interpreting its result.

Actual run
Training signal
Text search?
Image input
Embedding
Exact model
Preprocessing and weight identity

CLIP and SigLIP both learn from image–text pairs, but their training losses are not identical. CLIP makes the batch compete through softmax. SigLIP treats each image–text pair as its own positive-or-negative sigmoid decision. DINO receives no captions at all; it learns visual structure through self-distillation. DINO can therefore be excellent for image-to-image neighbourhoods while having no compatible text encoder for text-to-image search.

Experiment A: which changes should an embedding ignore?

For every controlled image, create five variants:

original
├── JPEG quality 35
├── shrink to 64 × 64, then restore
├── brightness × 0.65
├── centre crop to 70%, then restore
└── horizontal flip

The base and altered images are encoded independently. Their normalized vectors are compared with cosine similarity. A score near 1 means the transformation changed the direction only slightly. That is evidence of invariance—not proof that the model is correct about every image.

Actual run
The selected original controlled fixture
original
cosine
The selected transformed controlled fixture
variant
Mean over all 12
Lowest of 12
Same-class text top-1

Measured transformation invariance across the three encoders

The horizontal flip is an especially useful control. A semantic organizer often wants a flipped bicycle to remain a bicycle; an application that cares about left-versus-right direction may not. “Invariant” is never automatically good. It is good only when the ignored change is irrelevant to the task.

Experiment B: can neighbours recover unseen labels?

The CIFAR-10 subset contains 50 deterministic samples from each of ten classes. Forty per class form the gallery. Ten per class are hidden as queries. For every query, the experiment retrieves five gallery neighbours and asks their labels to vote.

Actual run

CLIP

5-NN accuracy
Precision@5
Recall@5
Actual run

SigLIP

5-NN accuracy
Precision@5
Recall@5
Actual run

DINO

5-NN accuracy
Precision@5
Recall@5

These three metrics answer different questions. 5-NN accuracy asks whether the majority label among five neighbours predicts the query class. Precision@5 asks what fraction of the five returned images share the query class. Recall@5 divides those relevant returns by all forty relevant gallery images, so it must be numerically smaller; five slots cannot recover forty items.

In this run, CLIP and SigLIP both reached 85.0% 5-NN accuracy. SigLIP’s neighbours were slightly purer at 81.6% Precision@5, compared with CLIP’s 78.2%. DINO reached 69.0% 5-NN accuracy and 59.2% Precision@5 on this tiny low-resolution benchmark. That does not rank the models universally; it describes one fixed transfer test.

Experiment C: text supervision can be tested directly

For CLIP and SigLIP, five prompt templates are embedded per CIFAR-10 class. The five vectors are normalized, averaged into one class prototype, and normalized again. Every image is assigned to its highest-scoring text prototype.

CLIP500-image zero-shot accuracy
SigLIP500-image zero-shot accuracy
DINON/Ano compatible text encoder

This is the cleanest practical distinction between “an image embedding” and “a shared image–text embedding.” DINO still produces vectors, but there is no DINO text vector to compare with them in this contract.

With the correct tokenizer contracts, the 500-image zero-shot run reached 90.4% for CLIP and 93.0% for SigLIP. Those results are not trained CIFAR classifiers: the only class definitions were the ten groups of five text prompts.

The experiment caught a real SigLIP preprocessing bug

The first run produced a suspicious combination:

SigLIP image 5-NN accuracy:     85.0%
SigLIP text zero-shot accuracy: 10.6%

Ten CIFAR classes make 10% approximately chance. The image encoder was clearly useful, so “SigLIP is bad” was a weak explanation. We inspected the exact text tensor contract.

ClusterLens had asked the tokenizer for padding=True. That pads every prompt only to the longest sequence in the current batch. This SigLIP checkpoint was trained with a fixed 64-position text sequence. Its pooling head depends on that layout, so changing the sequence length changed the representation before any image–text comparison occurred.

Actual run · bug → fix → rerun
Old input contractpadding = longest prompt in this batchzero-shot accuracy
Correct checkpoint contractpadding = fixed 64 positionszero-shot accuracy

We changed the SigLIP text path to use fixed maximum-length padding and truncation, then reran all 500 images with the same weights, prompts, indices, and seed. Accuracy rose to 93.0%. A regression test now asserts that SigLIP receives 64 token positions.

This is why “use the model’s preprocessing” is not ceremonial advice. A tensor can have valid integers, inference can complete, vectors can be finite and unit-normalized—and the semantic contract can still be wrong.

Experiment D: what is lost when we reduce dimensions?

The full gallery embeddings are compressed after inference with PCA to 256, 128, 64, 32, and 16 dimensions. This does not retrain a smaller model. It asks a narrower question: how much of the already-learned neighbourhood survives a linear post-hoc compression?

Actual run
Neighbour Recall@10
5-NN accuracy
Variance retained
float32 bytes/vector

Measured neighbour preservation after PCA compression

Here Neighbour Recall@10 compares the compressed top ten with the original full-dimensional top ten. If eight identities overlap, recall is 0.80. This is different from class Recall@5: it measures preservation of an exact neighbour set, not recovery of every same-class item.

At only 16 dimensions, exact-neighbour preservation fell to 62.1% for CLIP, 64.3% for SigLIP, and 56.5% for DINO. Yet the coarse 5-NN class accuracy did not collapse. This is not contradictory: several neighbours can exchange places while still belonging to the same class. “Quality” must name the relationship it is measuring.

Experiment E: choose a threshold and watch the errors move

A top-K search always returns something. A threshold can reject weak pairs, but where should the boundary go? The controlled set provides eighteen same-class base-image pairs and forty-eight different-class pairs for each model.

Actual run
−10+1
same class accepted
different class accepted
same class rejected
different class rejected

The slider is not offering a universal threshold. It demonstrates why one does not exist: the score distributions depend on the model, dataset, and definition of relevance. A threshold must be calibrated on representative labelled data.

Aggregate metrics compress all mistakes into one number. The gallery below shows held-out CIFAR-10 queries that the five-neighbour vote misclassified, beside their highest-scoring gallery neighbour.

The benchmark images are only 32×32 pixels. Some failures are therefore a useful reminder that preprocessing cannot restore evidence that the source never contained. Inspect whether the error is a plausible visual ambiguity, a resolution failure, or a neighbourhood that emphasizes the wrong property.

Timing is evidence about this machine, not every machine

Actual run
CLIPmedian ms/image
SigLIPmedian ms/image
DINOmedian ms/image

Each value is the median of three twelve-image CPU runs after one excluded warm-up. It includes image preparation and inference through the same service path used by ClusterLens. It is suitable for comparing these runs on this machine, not for claiming a universal model speed. The observed medians were 30.8 ms/image for CLIP, 81.9 ms/image for SigLIP, and 31.9 ms/image for DINO.

Inspect the reproducibility ledger and a raw result excerpt
Loading measured artifact…

The evidence file is bundled into this page, not hidden behind a separate dashboard. Its checksums and selected CIFAR indices let another run verify that the same inputs were used. The command that regenerates it is:

.venv/bin/python scripts/semantic_search_evidence.py \
  --models clip siglip dino \
  --samples-per-class 50 \
  --seed 20260901 \
  --offline

This section still has limits. Twelve controlled images are enough to expose a mechanism, not establish broad robustness. CIFAR-10 is public and reproducible, but tiny and unlike a real photo library. The honest conclusion is therefore not “model X wins.” It is that the representation contract, invariance, neighbour quality, text alignment, compression loss, threshold errors, and latency can all be measured instead of guessed.

What creates the semantics?

We can now assign responsibility precisely:

ComponentWhat it doesWhat it does not do
tokenizer/preprocessorcreates the expected model inputdecide semantic neighbours
embedding modellearns the geometry from data and objectivestore or retrieve the collection
normalization and metricdefine how directions are comparedcreate meaning from arbitrary vectors
databasestores vectors and metadatamake incompatible spaces compatible
exact or ANN indexretrieves likely neighboursdecide what the model learned
filters and rerankersenforce product context and refine orderrepair every representation failure

A dot product multiplies numbers. An index navigates numbers. The model’s training history is what makes closeness useful.

This gives us the durable mental model:

Semantic search places a query and stored objects on a learned map, then returns eligible objects nearest to the query. The model defines the map; the retrieval system navigates it; evaluation tells us whether that map is useful for our task.

Beginner checkpoint

After this foundation, the reader should be able to explain:

  1. why a representation is a deliberate choice rather than a neutral conversion;
  2. how decoded RGB pixels become a model tensor;
  3. why raw-pixel distance can fail on an unchanged translated object;
  4. how CNNs and Vision Transformers turn spatial evidence into a fixed vector;
  5. how characters become tokens, IDs, contextual vectors, and one text embedding;
  6. why token IDs are addresses rather than measurements of meaning;
  7. why equal-dimensional image and text vectors can still be incompatible;
  8. how contrastive image-caption training aligns two encoders;
  9. why captions, negatives, omissions, and bias shape the learned geometry;
  10. how to verify shapes, norms, finite values, batching, invariance, and retrieval behaviour;
  11. what must be stored to invalidate stale embeddings correctly;
  12. how indexing differs from querying;
  13. how cosine, dot product, and Euclidean distance relate after normalization;
  14. why top K always returns something while a threshold can reject weak matches;
  15. when keyword, metadata, semantic, and hybrid retrieval are appropriate;
  16. which component creates semantic geometry and which components only store or navigate it.

Those foundations are enough to study dimensions, model objectives, exact search, HNSW, IVF-PQ, reranking, perceptual hashes, and duplicate verification without treating any of them as magic.

Primary references

  1. OpenAI CLIP source and model documentation
  2. FAISS source and research references
Diagram